classicdesc.cpp
9.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
// classicdesc.cpp: model for classic ASM descriptors
//
// By "classic descriptor" we mean the Cootes' style one dimensional
// profile along the whisker orthogonal to the shape boundary.
//
// Copyright (C) 2005-2013, Stephen Milborrow
#include "stasm.h"
namespace stasm
{
static void NormalizeMat(
MAT& mat) // io: normalized so L2 length is 1
{
double norm = cv::norm(mat); // L2 norm
if (!IsZero(norm))
mat /= norm;
}
static const VEC Bisector( // return normalized bisector of three ordered points
const VEC& prev, // in: x,y coords of previous point (1x2 matrix)
const VEC& point, // in
const VEC& next) // in
{
VEC u(1, 2); // u is point - prev, rotated by 90 degrees
u(IX) = point(IY) - prev(IY);
u(IY) = prev(IX) - point(IX);
NormalizeMat(u);
VEC v(1, 2); // v is next - point, rotated by 90 degrees
v(IX) = next(IY) - point(IY);
v(IY) = point(IX) - next(IX);
NormalizeMat(v);
VEC w(u + v); NormalizeMat(w);
// are prev and next in the same line? if so, avoid numerical issues
if (IsZero(w(IX)) && IsZero(w(IY)))
{
w = point - prev;
NormalizeMat(w);
}
return w; // w is the direction of the bisector
}
// get x and y distances to take a single pixel step along the whisker
static void WhiskerStep(
double& xstep, // out: x dist to move one pix along whisker
double& ystep, // out: y dist to move one pix along whisker
const Shape& shape, // in
int ipoint) // in: index of the current point
{
int prev, next; PrevAndNextLandmarks(prev, next, ipoint, shape);
if ((Equal(shape(prev, IX), shape(ipoint, IX)) &&
Equal(shape(prev, IY), shape(ipoint, IY))) ||
(Equal(shape(next, IX), shape(ipoint, IX)) &&
Equal(shape(next, IY), shape(ipoint, IY))))
{
// The prev or next point is on top of the current point.
// Arbitrarily point the whisker in a horizontal direction.
// TODO Revisit, this is common at low resolution pyramid levels.
xstep = 1;
ystep = 0;
}
else
{
const VEC whisker_direction(Bisector(shape.row(prev),
shape.row(ipoint),
shape.row(next)));
xstep = -whisker_direction(IX);
ystep = -whisker_direction(IY);
// normalize so either xstep or ystep will be +-1,
// and the other will be smaller than +-1
const double abs_xstep = ABS(xstep);
const double abs_ystep = ABS(ystep);
if (abs_xstep >= abs_ystep)
{
xstep /= abs_xstep;
ystep /= abs_xstep;
}
else
{
xstep /= abs_ystep;
ystep /= abs_ystep;
}
}
}
static inline int Step( // return x coord at the given offset along whisker
double x, // in: x coord of center of whisker
double xstep, // in: x dist to move one pixel along whisker
int offset) // in: offset along whisker in pixels
{
return cvRound(x + (offset * xstep));
}
static inline int Pix( // get pixel at ix and iy, forcing ix and iy in range
const Image& img, // in
int ix, // in
int iy) // in
{
return img(Clamp(iy, 0, img.rows-1), Clamp(ix, 0, img.cols-1));
}
// fullprof is the 1D profile along the whisker, including extra elements
// to allow searching away from the current position of the landmark.
//
// shape[ipoint] is the current position of the landmark,
// and the center point of the whisker.
// We also use shape for figuring out the direction of the whisker.
static void FullProf(
VEC& fullprof, // out
const Image& img, // in
const MAT& shape, // in
int ipoint) // in: index of the current point
{
double xstep; // x axis dist corresponding to one pixel along whisker
double ystep;
WhiskerStep(xstep, ystep, shape, ipoint);
const double x = shape(ipoint, IX); // center point of the whisker
const double y = shape(ipoint, IY);
// number of pixs to sample in each direction along the whisker
const int n = (NSIZE(fullprof) - 1) / 2;
int prevpix = Pix(img,
Step(x, xstep, -n-1), Step(y, ystep, -n-1));
for (int i = -n; i <= n; i++)
{
const int pix = Pix(img,
Step(x, xstep, i), Step(y, ystep, i));
fullprof(i + n) = double(pix - prevpix); // signed gradient
prevpix = pix;
}
}
double SumAbsElems( // return the sum of the abs values of the elems of mat
const MAT& mat) // in
{
CV_Assert(mat.isContinuous());
const double* const data = Buf(mat);
double sum = 0;
int i = NSIZE(mat); // number of elements
while (i--)
sum += ABS(data[i]);
return sum;
}
// This returns a double equal to x.t() * mat * x.
//
// x is a vector (row or column, it doesn't matter).
//
// mat is assumed to be a symmetric matrix
// (but only the upper right triangle of mat is actually used).
//
// mat and x are not modified.
//
// This function is equivalent to x.t() * mat * x(), but is optimized
// for speed and is faster. It's faster because we use the fact that
// mat is symmetric to roughly halve the number of operations.
static double xAx(
const VEC& x, // in
const MAT& mat) //in: must be symmetric
{
const int n = NSIZE(x);
CV_Assert(mat.rows == n && mat.cols == n && x.isContinuous());
const double* px = Buf(x);
double diagsum = 0, sum = 0;
int i = n;
while (i--)
{
const double xi = px[i];
const double* const rowbuf = mat.ptr<double>(i);
diagsum += rowbuf[i] * SQ(xi); // sum diag elements
for (int j = i+1; j < n; j++) // sum upper right triangle elements
sum += rowbuf[j] * xi * px[j];
}
return diagsum + 2 * sum; // "2 *" to include lower left triangle elements
}
// Get the profile distance. That is, get the image profile at the given
// offset along the whisker, and return the Mahalanobis distance between
// it and the model mean profile. Low distance means good fit.
static double ProfDist(
int offset, // in: offset along whisker in pixels
int proflen, // in
const VEC& fullprof, // in
const VEC& meanprof, // in: mean of the training profiles for this point
const MAT& covi) // in: inverse of the covar of the training profiles
{
VEC prof(1, proflen); // the profile at the given offset along whisker
// copy the relevant part of fullprof into prof
memcpy(Buf(prof),
Buf(fullprof) + offset + NSIZE(fullprof)/2 - NSIZE(prof)/2,
NSIZE(prof) * sizeof(prof(0)));
// normalize prof
double sum = SumAbsElems(prof);
if (!IsZero(sum))
prof *= NSIZE(prof) / sum;
// The following code is equivalent to
// return (prof - meanprof).t() * covi * (prof - meanprof)
// but is optimized for speed.
prof -= meanprof; // for efficiency, use "-=" not "=" with "-"
return xAx(prof, covi);
}
// If OpenMP is enabled, multiple instances of this function will be called
// concurrently (each call will have a different value of x and y). Thus this
// function and its callees do not modify any data that is not on the stack.
void ClassicDescSearch( // search along whisker for best profile match
double& x, // io: (in: old posn of landmark, out: new posn)
double& y, // io:
const Image& img, // in: the image scaled to this pyramid level
const Shape& inshape, // in: current posn of landmarks (for whisker directions)
int ipoint, // in: index of the current landmark
const MAT& meanprof, // in: mean of the training profiles for this point
const MAT& covi) // in: inverse of the covar of the training profiles
{
const int proflen = NSIZE(meanprof);
CV_Assert(proflen % 2 == 1); // proflen must be odd in this implementation
// fullprof is the 1D profile along the whisker including the extra
// elements to allow search +-CLASSIC_MAX_OFFSET pixels away from
// the current position of the landmark.
// We precalculate the fullprof for efficiency in the for loop below.
VEC fullprof(1, proflen + 2 * CLASSIC_MAX_OFFSET);
CV_Assert(NSIZE(fullprof) % 2 == 1); // fullprof length must be odd
FullProf(fullprof, img, inshape, ipoint);
// move along the whisker looking for the best match
int bestoffset = 0;
double mindist = FLT_MAX;
for (int offset = -CLASSIC_MAX_OFFSET;
offset <= CLASSIC_MAX_OFFSET;
offset += CLASSIC_SEARCH_RESOL)
{
const double dist = ProfDist(offset, proflen, fullprof, meanprof, covi);
if (dist < mindist)
{
mindist = dist;
bestoffset = offset;
}
}
// change x,y to the best position along the whisker
double xstep, ystep;
WhiskerStep(xstep, ystep, inshape, ipoint);
x = inshape(ipoint, IX) + (bestoffset * xstep);
y = inshape(ipoint, IY) + (bestoffset * ystep);
}
} // namespace stasm