-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathlevelLine.cpp
More file actions
335 lines (312 loc) · 13.3 KB
/
levelLine.cpp
File metadata and controls
335 lines (312 loc) · 13.3 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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
// SPDX-License-Identifier: GPL-3.0-or-later
/**
* @file levelLine.cpp
* @brief Extraction of level lines from an image
*
* (C) 2011-2016, 2019 Pascal Monasse <[email protected]>
*/
#include "levelLine.h"
#include <cmath>
#include <cassert>
/// South, East, North, West: directions of entry/exit in a dual pixel.
/// Left turn=+1. Right turn=-1. Opposite=+2.
typedef signed char Dir;
static const Dir S=0, /*E=1,*/ N=2 /*, W=3*/;
/// Vectors associated to the 4 directions
static const Point delta[] = {Point(0,+1), //S
Point(+1,0), //E
Point(0,-1), //N
Point(-1,0), //W
Point(0,+1)};//S again, to avoid modulo
/// Vector subtraction
inline Point operator-(Point p1, Point p2) {
return Point(p1.x-p2.x, p1.y-p2.y);
}
/// Vector multiplication
inline Point operator*(float f, Point p) {
return Point(f*p.x, f*p.y);
}
/// Print list of points of the line (but not the level): x0 y0 x1 y1...
std::ostream& operator<<(std::ostream& str, const LevelLine& l) {
std::vector<Point>::const_iterator it;
for(it=l.line.begin(); it!=l.line.end(); ++it)
str << it->x << " " << it->y << " ";
return str;
}
/// Apply zoom factor to points of \a line.
void zoom_line(std::vector<Point>& line, float zoom) {
std::vector<Point>::iterator it=line.begin(), end=line.end();
for(; it!=end; ++it) {
it->x *= zoom;
it->y *= zoom;
}
}
/// Parameters of a hyperbola.
/// Inside the dual pixel, the level set has implicit equation
/// \f[ D*(x-xs)(y-ys)+N/D = l. \f]
/// This is true only if \f$D\neq0\f$ (otherwise we have just a line segment).
/// The center of the hyperbola (xs,ys) is a saddle point, its level is N/D.
/// Of interest, the vertex of the hyperbola is a point of maximal curvature.
/// It is located at
/// \f[(xs,ys)+(\pm\sqrt{|(Dl-N)/D^2|},\pm\sqrt{|(Dl-N)/D^2|}).\f]
/// The signs are determined by the fact that the vertex is in the same quadrant
/// as the input point p with respect to (xs,ys).
/// The equation of hyperbola is written
/// \f[ (x-xs)(y-ys) = \delta. \f]
class Hyperbola {
public:
float num, denom; /// The saddle value is num/denom
Point s; ///< Saddle point=center of hyperbola
Point v; ///< Vertex of hyperbola=point of maximal curvature
float delta; ///< Parameter of hyperbola (sqrt(2*delta) = semi major axis)
Hyperbola(const Point& pos, const Point& p, unsigned char lev[4], float l);
bool valid() const { return (denom!=0); }
bool vertex_in_dual_pixel(const Point& p) const;
void sample(const Point& p1, const Point& p2, int ptsPixel,
std::vector<Point>& line) const;
private:
static float sign(float f) { return (f>0)? +1: -1; }
};
/// Decompose hyperbola branch.
/// \param pos the top-left vertex of the dual pixel.
/// \param p a point on an edgel of the dual pixel and on the hyperbola.
/// \param level the levels at the four vertices of the dual pixel.
/// \param l level.
/// The hyperbola can be degenerate (a segment), in which case \c s, \c v and
/// \c delta make no sense. The method \c valid() must be used to check.
Hyperbola::Hyperbola(const Point& pos, const Point& p,
unsigned char level[4],float l) {
num = level[0]*(float)level[2]-level[1]*(float)level[3];
denom = (float)level[0]+level[2]-level[1]-level[3];
delta = 0;
if(denom == 0)
return; // Degenerate hyperbola
float d = 1.0f/denom;
s.x = pos.x + (level[0]-level[1])*d;
s.y = pos.y + (level[0]-level[3])*d;
delta = (denom*l-num)*(d*d);
d = sqrt(std::abs(delta));
v.x = s.x + sign(p.x-s.x)*d;
v.y = s.y + sign(p.y-s.y)*d;
}
/// Tell if the vertex of the hyperbola branch is inside the dual pixel of
/// top-left corner \a p.
bool Hyperbola::vertex_in_dual_pixel(const Point& p) const {
return valid() && (p.x<v.x && v.x<p.x+1 && p.y<v.y && v.y<p.y+1);
}
/// Sample branch of hyperbola from p1 to p2 of equation (x-xs)(y-ys)=delta:
/// [2]Algorithm 3.
/// \param p1 start point.
/// \param p2 end point.
/// \param ptsPixel number of points of discretization per pixel.
/// \param[out] line where the sampled points are stored.
void Hyperbola::sample(const Point& p1, const Point& p2, int ptsPixel,
std::vector<Point>& line) const {
if(ptsPixel<2) return;
Point p = p2-p1;
if(p.x<0) p.x=-p.x;
if(p.y<0) p.y=-p.y;
if(p.x>p.y) { // Uniform sample along x
int n = ceil(p.x*ptsPixel);
float dx = (p2.x-p1.x)/n;
p = p1;
for(int i=1; i<n; i++) {
p.x += dx;
p.y = s.y + delta/(p.x-s.x);
line.push_back(p);
}
} else { // Uniform sample along y
int n = ceil(p.y*ptsPixel);
float dy = (p2.y-p1.y)/n;
p = p1;
for(int i=1; i<n; i++) {
p.y += dy;
p.x = s.x + delta/(p.y-s.y);
line.push_back(p);
}
}
}
/// A mobile dual pixel, square whose vertices are 4 data points.
/// This is the main structure to extract a level line, moving from dual pixel
/// to an adjacent one until coming back at starting point. The entry direction
/// of the level line is recorded: south means it enters from the top horizontal
/// edgel, east from the right vertical, north from the bottom horizontal and
/// west from the right.
/// The object stores the levels at its 4 vertices (data points of the image),
/// in clockwise order starting from the top left vertex.
class DualPixel {
public:
DualPixel(Point& p, float l, const unsigned char* im, size_t w);
void follow(Point& p, float l, int ptsPixel, std::vector<Point>& line);
bool mark_visit(std::vector<bool>& visit,
std::vector< std::vector<Inter> >* inter, size_t idx,
const Point& p) const;
private:
const unsigned char* _im; ///< The image stored as 1D array.
const size_t _w; ///< Number of columns of image.
unsigned char _level[4]; /// The levels at the 4 data points.
Point _pos; /// The position of the top-left vertex of the dual pixel.
Dir _d; /// Direction of entry into dual pixel.
void update_levels();
Point move(float l, float snum, float sdenom);
};
/// Return x for y=v on line joining (0,v0) and (1,v1).
inline float linear(float v0, float v, float v1) {
return (v-v0)/(v1-v0);
}
/// Constructor.
/// \param[in,out] p the edgel endpoint at the right of incoming direction.
/// \param l the level of the level line.
/// \param im the values of pixels in a 1D array.
/// \param w the number of pixel columns in \a im.
/// The incoming direction is always supposed to be south, so the level line is
/// crossing the edgel from \a p to \a p+(1,0). It means the starting point of
/// the level line is at \a p+(x,0), with 0<x<1. As output, \a p is moved to
/// this position.
DualPixel::DualPixel(Point& p, float l, const unsigned char* im, size_t w)
: _im(im), _w(w), _pos(p), _d(S) {
update_levels();
p += linear(_level[0],l,_level[3])*delta[_d+1]; // Safe: delta[4]==delta[0]
}
/// Update levels at vertices.
void DualPixel::update_levels() {
size_t ind = (size_t)_pos.y*_w+(size_t)_pos.x;
_level[0] = _im[ind]; _level[3] = _im[ind+1];
_level[1] = _im[ind+_w]; _level[2] = _im[ind+_w+1];
}
/// Move to next adjacent dual pixel: [2]Algorithm 2.
/// \param l the level of the level line
/// \param snum numerator of saddle level
/// \param sdenom denominator of saddle level
/// \return subpixel entry point in new dual pixel (=exit point of old one)
/// Only the saddle level (snum/sdenom) may be used, but most of the time it is
/// not. Pass two parameters in order not to pay an unnecessary division.
Point DualPixel::move(float l, float snum, float sdenom) {
bool left = (l>_level[(_d+2)%4]); // Is there an exit at the left?
bool right = (l<_level[(_d+1)%4]); // Is there an exit at the right?
if(left && right) { // Disambiguate saddle point
right = (l<snum/sdenom);
left = !right;
}
// update direction
if(left && ++_d>3) _d=0;
if(right && --_d<0) _d=3;
// update top-left vertex
_pos += delta[_d];
update_levels();
float coord = linear(_level[_d], l, _level[(_d+3)%4]);
Point p = _pos;
for(Dir d=0; d<_d; d++) p += delta[d];
p += coord*delta[_d+1]; // Safe: delta[4]==delta[0]
return p;
}
/// The dual pixel is moved to the adjacent one. Find exit point of level line
/// entering at \a p in the dual pixel. The level line is sampled up to there.
/// \param[in,out] p entry point into the dual pixel
/// \param l level of the level line
/// \param ptsPixel number of points of discretization per pixel.
/// \param[out] line intermediate samples stored here.
void DualPixel::follow(Point& p, float l, int ptsPixel,
std::vector<Point>& line) {
assert(_level[_d]<l && l<_level[(_d+3)%4]);
// 1. Compute hyperbola equation
Hyperbola h(_pos, p, _level, l);
bool vInside = h.vertex_in_dual_pixel(_pos);
// 2. Move dual pixel to new position
Point pIni = p; // Keep track of entry point before moving to exit
p = move(l, h.num, h.denom);
// 3. Sample hyperbola in previous dual pixel position
if(h.valid() && ptsPixel>0) { // Do not sample if not hyperbola (straight)
if(std::abs(h.delta) < 1.0e-2f) { // Saddle level: one or two segments
if(vInside)
line.push_back(h.v); // Put vertex only (almost saddle point)
return;
}
if(vInside) { // Sample from entry point to vertex of hyperbola
h.sample(pIni, h.v, ptsPixel, line);
line.push_back(pIni=h.v);
}
h.sample(pIni, p, ptsPixel, line); // Sample until end point
}
}
/// Mark the edge as "visited", return \c false if already visited.
/// \param visit stores the edgels traversed from the south at current level.
/// \param inter (optional) rows of image traversed are marked with \a idx.
/// \param idx a unique identifier for the level line.
/// \param p the current position in the tracking of the level line.
/// \return whether the tracking must continue (loop not closed yet).
/// When we go through a horizontal data row and going south, we store the
/// visit. If the edgel was already visited at current level, we came back
/// at starting point and must stop.
bool DualPixel::mark_visit(std::vector<bool>& visit,
std::vector< std::vector<Inter> >* inter,
size_t idx, const Point& p) const {
bool cont=true;
if(_d==S) {
size_t i = (size_t)_pos.y*_w+(size_t)_pos.x;
cont = !visit[i];
visit[i] = true;
}
if(inter && cont && (_d==S||_d==N))
(*inter)[(size_t)p.y].push_back( Inter(p.x,idx) );
return cont;
}
/// Extract level line passing through a given starting point.
/// \param data the values of pixels in a 1D array.
/// \param w the number of pixel columns in \a data.
/// \param visit array to store the visited explored horizontal edgels.
/// \param ptsPixel number of points of discretization per pixel.
/// \param p the starting point.
/// \param[in,out] ll the level line: level already stored, find its line.
/// \param idx a unique identifier for the level line.
/// \param inter[out] (optional) rows of image traversed are marked with \a idx.
/// \a inter is used to recover the tree hierarchy at the end, could be
/// omitted if the tree is not required, in which case \a idx is unused.
static void extract(const unsigned char* data, size_t w,
std::vector<bool>& visit, int ptsPixel,
Point p, LevelLine& ll, size_t idx,
std::vector< std::vector<Inter> >* inter) {
DualPixel dual(p, ll.level, data, w);
while(true) {
ll.line.push_back(p);
if(! dual.mark_visit(visit,inter,idx,p))
break;
dual.follow(p,ll.level,ptsPixel,ll.line);
}
}
/// Level lines extraction algorithm: [2]Algorithm 1.
/// \param data the values of pixels in a 1D array.
/// \param w the number of pixel columns in \a data.
/// \param h the number of pixel lines in \a data.
/// \param offset the minimal level to be extracted.
/// \param step the level step for extraction.
/// \param ptsPixel number of points of discretization per pixel.
/// \param[out] ll storage for the extracted level lines.
/// \param inter[out] (optional) rows of image traversed by ll are marked.
void extract(const unsigned char* data, size_t w, size_t h,
float offset, float step, int ptsPixel,
std::vector<LevelLine*>& ll,
std::vector< std::vector<Inter> >* inter) {
std::vector<bool> visit(w*h, false);
if(inter) {
assert(inter->empty());
inter->resize(h);
}
for(float l=offset; l<255.0f; l+=step) {
bool found=false;
std::vector<bool>::const_iterator it=visit.begin();
for(size_t i=0; i+1<h; i++) {
for(size_t j=0; j+1<w; j++, ++it)
if(data[i*w+j]<l && l<data[i*w+j+1] && !*it) {
found = true;
LevelLine* line = new LevelLine(l);
Point p((float)j,(float)i);
extract(data,w, visit, ptsPixel, p, *line, ll.size(),inter);
ll.push_back(line);
}
++it;
}
if(found) // Reinit for next level
std::fill(visit.begin(), visit.end(), false);
}
}