Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
vpContours.cpp
1/*
2 * ViSP, open source Visual Servoing Platform software.
3 * Copyright (C) 2005 - 2025 by Inria. All rights reserved.
4 *
5 * This software is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 * See the file LICENSE.txt at the root directory of this source
10 * distribution for additional information about the GNU GPL.
11 *
12 * For using ViSP with software that can not be combined with the GNU
13 * GPL, please contact Inria about acquiring a ViSP Professional
14 * Edition License.
15 *
16 * See https://visp.inria.fr for more information.
17 *
18 * This software was developed at:
19 * Inria Rennes - Bretagne Atlantique
20 * Campus Universitaire de Beaulieu
21 * 35042 Rennes Cedex
22 * France
23 *
24 * If you have questions regarding the use of this file, please contact
25 * Inria at visp@inria.fr
26 *
27 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
28 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29 *
30 * Description:
31 * Basic contours extraction based on the original work of
32 * Sina Samangooei (ss@ecs.soton.ac.uk).
33 */
34
65
70
71#include <map>
72#include <visp3/imgproc/vpImgproc.h>
73
74namespace VISP_NAMESPACE_NAME
75{
76#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) && defined(ENABLE_VISP_NAMESPACE)
77using namespace VISP_NAMESPACE_NAME;
78#endif
79
80bool fromTo(const vpImagePoint &from, const vpImagePoint &to, vpDirection &direction);
81bool crossesEastBorder(const vpImage<int> &I, bool checked[8], const vpImagePoint &point);
82void addContourPoint(vpImage<int> &I, vpContour *border, const vpImagePoint &point, bool checked[8], int nbd);
83void followBorder(vpImage<int> &I, const vpImagePoint &ij, vpImagePoint &i2j2, vpContour *border, int nbd);
84bool isOuterBorderStart(const vpImage<int> &I, unsigned int i, unsigned int j);
85bool isHoleBorderStart(const vpImage<int> &I, unsigned int i, unsigned int j);
86void getContoursList(const vpContour &root, int level, vpContour &contour_list);
87void drawContours(vpImage<unsigned char> &I, const std::vector<std::vector<vpImagePoint> > &contours, unsigned char grayValue);
88void drawContours(vpImage<vpRGBa> &I, const std::vector<std::vector<vpImagePoint> > &contours, const vpColor &color);
89void findContours(const vpImage<unsigned char> &I_original, vpContour &contours,
90 std::vector<std::vector<vpImagePoint> > &contourPts, const vpContourRetrievalType &retrievalMode);
91
92bool fromTo(const vpImagePoint &from, const vpImagePoint &to, vpDirection &direction)
93{
94 if (from == to) {
95 return false;
96 }
97
98 if (std::fabs(from.get_i() - to.get_i()) < std::numeric_limits<double>::epsilon()) {
99 if (from.get_j() < to.get_j()) {
100 direction.m_direction = EAST;
101 }
102 else {
103 direction.m_direction = WEST;
104 }
105 }
106 else if (from.get_i() < to.get_i()) {
107 if (std::fabs(from.get_j() - to.get_j()) < std::numeric_limits<double>::epsilon()) {
108 direction.m_direction = SOUTH;
109 }
110 else if (from.get_j() < to.get_j()) {
111 direction.m_direction = SOUTH_EAST;
112 }
113 else {
114 direction.m_direction = SOUTH_WEST;
115 }
116 }
117 else {
118 if (std::fabs(from.get_j() - to.get_j()) < std::numeric_limits<double>::epsilon()) {
119 direction.m_direction = NORTH;
120 }
121 else if (from.get_j() < to.get_j()) {
122 direction.m_direction = NORTH_EAST;
123 }
124 else {
125 direction.m_direction = NORTH_WEST;
126 }
127 }
128
129 return true;
130}
131
132bool crossesEastBorder(const vpImage<int> &I, bool checked[8], const vpImagePoint &point)
133{
134 vpDirection direction;
135 if (!fromTo(point, vpImagePoint(point.get_i(), point.get_j() + 1), direction)) {
136 return false;
137 }
138
139 bool b = checked[static_cast<int>(direction.m_direction)];
140
141 if ((point.get_i() < 0) || (point.get_j() < 0)) {
142 return false;
143 }
144
145 unsigned int i = static_cast<unsigned int>(point.get_i());
146 unsigned int j = static_cast<unsigned int>(point.get_j());
147
148 return (I[i][j] != 0) && ((static_cast<unsigned int>(point.get_j()) == (I.getWidth() - 1)) || b);
149}
150
151void addContourPoint(vpImage<int> &I, vpContour *border, const vpImagePoint &point, bool checked[8], int nbd)
152{
153 border->m_points.push_back(vpImagePoint(point.get_i() - 1, point.get_j() - 1)); // remove 1-pixel padding
154
155 unsigned int i = static_cast<unsigned int>(point.get_i());
156 unsigned int j = static_cast<unsigned int>(point.get_j());
157
158 if (crossesEastBorder(I, checked, point)) {
159 I[i][j] = -nbd;
160 }
161 else if (I[i][j] == 1) {
162 // Only set if the pixel has not been visited before (3.4) (b)
163 I[i][j] = nbd;
164 } // Otherwise leave it alone
165}
166
167void followBorder(vpImage<int> &I, const vpImagePoint &ij, vpImagePoint &i2j2, vpContour *border, int nbd)
168{
169 vpDirection dir;
170 if (!fromTo(ij, i2j2, dir)) {
171 throw vpException(vpException::fatalError, "ij == i2j2");
172 }
173
174 vpDirection trace = dir.clockwise();
175 vpImagePoint i1j1(-1, -1);
176
177 // --comment: find i1j1 3.1
178 bool activepixel_ij_neg = true;
179 while ((trace.m_direction != dir.m_direction) && activepixel_ij_neg) {
180 vpImagePoint activePixel = trace.active(I, ij);
181
182 if ((activePixel.get_i() >= 0) && (activePixel.get_j() >= 0)) {
183 i1j1 = activePixel;
184 activepixel_ij_neg = false;
185 // break
186 }
187 else {
188 trace = trace.clockwise();
189 }
190 }
191
192 if ((i1j1.get_i() < 0) || (i1j1.get_j() < 0)) {
193 //(3.1) ; single pixel contour
194 return;
195 }
196
197 i2j2 = i1j1;
198 vpImagePoint i3j3 = ij; //(3.2)
199
200 const int sizeChecked = 8;
201 bool checked[sizeChecked] = { false, false, false, false, false, false, false, false };
202
203 bool i4j4_eq_ij_and_i3j3_eq_i1j1 = false;
204 while (i4j4_eq_ij_and_i3j3_eq_i1j1 == false) {
205 if (!fromTo(i3j3, i2j2, dir)) {
206 throw vpException(vpException::fatalError, "i3j3 == i2j2");
207 }
208
209 trace = dir.counterClockwise();
210 vpImagePoint i4j4(-1, -1);
211
212 // Reset checked
213 for (int cpt = 0; cpt < sizeChecked; ++cpt) {
214 checked[cpt] = false;
215 }
216
217 bool i4j4_ij_neg = true;
218 while (true && i4j4_ij_neg) {
219 i4j4 = trace.active(I, i3j3); //(3.3)
220 if ((i4j4.get_i() >= 0) && (i4j4.get_j() >= 0)) {
221 i4j4_ij_neg = false;
222 //break
223 }
224 else {
225 checked[static_cast<int>(trace.m_direction)] = true;
226 trace = trace.counterClockwise();
227 }
228 }
229
230 addContourPoint(I, border, i3j3, checked, nbd);
231
232 if ((i4j4 == ij) && (i3j3 == i1j1)) {
233 //(3.5)
234 i4j4_eq_ij_and_i3j3_eq_i1j1 = true;
235 // break
236 }
237 else {
238 //(3.5)
239 i2j2 = i3j3;
240 i3j3 = i4j4;
241 }
242 }
243}
244
245bool isOuterBorderStart(const vpImage<int> &I, unsigned int i, unsigned int j)
246{
247 return ((I[i][j] == 1) && ((j == 0) || (I[i][j - 1] == 0)));
248}
249
250bool isHoleBorderStart(const vpImage<int> &I, unsigned int i, unsigned int j)
251{
252 return ((I[i][j] >= 1) && ((j == (I.getWidth() - 1)) || (I[i][j + 1] == 0)));
253}
254
255void getContoursList(const vpContour &root, int level, vpContour &contour_list)
256{
257 if ((level > 0) && (level < std::numeric_limits<int>::max())) {
258 vpContour *contour_node = new vpContour;
259 contour_node->m_contourType = root.m_contourType;
260 contour_node->m_points = root.m_points;
261
262 contour_list.m_children.push_back(contour_node);
263 }
264
265 std::vector<vpContour *>::const_iterator root_m_children_end = root.m_children.end();
266 for (std::vector<vpContour *>::const_iterator it = root.m_children.begin(); it != root_m_children_end; ++it) {
267 getContoursList(**it, level + 1, contour_list);
268 }
269}
270
271void drawContours(vpImage<unsigned char> &I, const std::vector<std::vector<vpImagePoint> > &contours, unsigned char grayValue)
272{
273 if (I.getSize() == 0) {
274 return;
275 }
276
277 std::vector<std::vector<vpImagePoint> >::const_iterator contours_end = contours.end();
278 for (std::vector<std::vector<vpImagePoint> >::const_iterator it1 = contours.begin(); it1 != contours_end; ++it1) {
279 std::vector<vpImagePoint>::const_iterator it1_end = it1->end();
280 for (std::vector<vpImagePoint>::const_iterator it2 = it1->begin(); it2 != it1_end; ++it2) {
281 unsigned int i = static_cast<unsigned int>(it2->get_i());
282 unsigned int j = static_cast<unsigned int>(it2->get_j());
283 I[i][j] = grayValue;
284 }
285 }
286}
287
288void drawContours(vpImage<vpRGBa> &I, const std::vector<std::vector<vpImagePoint> > &contours, const vpColor &color)
289{
290 if (I.getSize() == 0) {
291 return;
292 }
293
294 std::vector<std::vector<vpImagePoint> >::const_iterator contours_end = contours.end();
295 for (std::vector<std::vector<vpImagePoint> >::const_iterator it1 = contours.begin(); it1 != contours_end; ++it1) {
296 std::vector<vpImagePoint>::const_iterator it1_end = it1->end();
297 for (std::vector<vpImagePoint>::const_iterator it2 = it1->begin(); it2 != it1_end; ++it2) {
298 unsigned int i = static_cast<unsigned int>(it2->get_i());
299 unsigned int j = static_cast<unsigned int>(it2->get_j());
300 I[i][j] = vpRGBa(color.R, color.G, color.B);
301 }
302 }
303}
304
305void findContours(const vpImage<unsigned char> &I_original, vpContour &contours,
306 std::vector<std::vector<vpImagePoint> > &contourPts, const vpContourRetrievalType &retrievalMode)
307{
308 if (I_original.getSize() == 0) {
309 return;
310 }
311
312 // Clear output results
313 contourPts.clear();
314
315 // Copy uchar I_original into int I + padding
316 vpImage<int> I(I_original.getHeight() + 2, I_original.getWidth() + 2);
317
318 unsigned int i_height = I.getHeight();
319 unsigned int i_width = I.getWidth();
320 unsigned int i_original_width = I_original.getWidth();
321
322 for (unsigned int i = 0; i < i_height; ++i) {
323 if ((i == 0) || (i == (i_height - 1))) {
324 memset(I.bitmap, 0, sizeof(int) * i_width);
325 }
326 else {
327 I[i][0] = 0;
328 for (unsigned int j = 0; j < i_original_width; ++j) {
329 I[i][j + 1] = I_original[i - 1][j];
330 }
331 I[i][i_width - 1] = 0;
332 }
333 }
334
335 // Ref: http://openimaj.org/
336 // Ref: Satoshi Suzuki and others. Topological structural analysis of
337 // digitized binary images by border following.
338 int nbd = 1; // Newest border
339 int lnbd = 1; // Last newest border
340
341 // Background contour
342 // By default the root contour is a hole contour
343 vpContour *root = new vpContour(CONTOUR_HOLE);
344
345 std::map<int, vpContour *> borderMap;
346 borderMap[lnbd] = root;
347
348 for (unsigned int i = 0; i < i_height; ++i) {
349 lnbd = 1; // Reset LNBD at the beginning of each scan row
350
351 for (unsigned int j = 0; j < i_width; ++j) {
352 int fji = I[i][j];
353
354 bool isOuter = isOuterBorderStart(I, i, j);
355 bool isHole = isHoleBorderStart(I, i, j);
356
357 if (isOuter || isHole) { // else (1) (c)
358 vpContour *border = new vpContour;
359 vpContour *borderPrime = nullptr;
360 vpImagePoint from(i, j);
361
362 if (isOuter) {
363 //(1) (a)
364 ++nbd;
365 from.set_j(from.get_j() - 1);
367 borderPrime = borderMap[lnbd];
368
369 // Table 1
370 switch (borderPrime->m_contourType) {
371 case CONTOUR_OUTER:
372 border->setParent(borderPrime->m_parent);
373 break;
374
375 case CONTOUR_HOLE:
376 border->setParent(borderPrime);
377 break;
378
379 default:
380 break;
381 }
382 }
383 else {
384 //(1) (b)
385 ++nbd;
386
387 if (fji > 1) {
388 lnbd = fji;
389 }
390
391 borderPrime = borderMap[lnbd];
392 from.set_j(from.get_j() + 1);
393 border->m_contourType = CONTOUR_HOLE;
394
395 // Table 1
396 switch (borderPrime->m_contourType) {
397 case CONTOUR_OUTER:
398 border->setParent(borderPrime);
399 break;
400
401 case CONTOUR_HOLE:
402 border->setParent(borderPrime->m_parent);
403 break;
404
405 default:
406 break;
407 }
408 }
409
410 vpImagePoint ij(i, j);
411 followBorder(I, ij, from, border, nbd);
412
413 //(3) (1) ; single pixel contour
414 if (border->m_points.empty()) {
415 border->m_points.push_back(vpImagePoint(ij.get_i() - 1, ij.get_j() - 1)); // remove 1-pixel padding
416 I[i][j] = -nbd;
417 }
418
419 if ((retrievalMode == CONTOUR_RETR_LIST) || (retrievalMode == CONTOUR_RETR_TREE)) {
420 // Add contour points
421 contourPts.push_back(border->m_points);
422 }
423
424 borderMap[nbd] = border;
425 }
426
427 //(4)
428 if ((fji != 0) && (fji != 1)) {
429 lnbd = std::abs(fji);
430 }
431 }
432 }
433
434 if ((retrievalMode == CONTOUR_RETR_EXTERNAL) || (retrievalMode == CONTOUR_RETR_LIST)) {
435 // Delete contours content
436 contours.m_parent = nullptr;
437
438 std::vector<vpContour *>::iterator contours_m_children_end = contours.m_children.end();
439 for (std::vector<vpContour *>::iterator it = contours.m_children.begin(); it != contours_m_children_end; ++it) {
440 (*it)->m_parent = nullptr;
441 if (*it != nullptr) {
442 delete *it;
443 *it = nullptr;
444 }
445 }
446
447 contours.m_children.clear();
448 }
449
450 if (retrievalMode == CONTOUR_RETR_EXTERNAL) {
451 // Add only external contours
452 std::vector<vpContour *>::iterator root_m_children_end = root->m_children.end();
453 for (std::vector<vpContour *>::const_iterator it = root->m_children.begin(); it != root_m_children_end; ++it) {
454 // Save children
455 std::vector<vpContour *> children_copy = (*it)->m_children;
456 // Erase children
457 (*it)->m_children.clear();
458 // Copy contour
459 contours.m_children.push_back(new vpContour(**it));
460 // Restore children
461 (*it)->m_children = children_copy;
462 // Set parent to children
463 size_t contours_m_children_size = contours.m_children.size();
464 for (size_t i = 0; i < contours_m_children_size; ++i) {
465 contours.m_children[i]->m_parent = &contours;
466 }
467 contourPts.push_back((*it)->m_points);
468 }
469 }
470 else if (retrievalMode == CONTOUR_RETR_LIST) {
471 getContoursList(*root, 0, contours);
472
473 // Set parent to root
474 std::vector<vpContour *>::iterator contours_m_children_end = contours.m_children.end();
475 for (std::vector<vpContour *>::iterator it = contours.m_children.begin(); it != contours_m_children_end; ++it) {
476 (*it)->m_parent = &contours;
477 }
478 }
479 else {
480 // CONTOUR_RETR_TREE
481 contours = *root;
482 }
483
484 delete root;
485 root = nullptr;
486}
487
488} // namespace
VISP_NAMESPACE_ADDRESSING vpImagePoint active(const VISP_NAMESPACE_ADDRESSING vpImage< int > &I, const VISP_NAMESPACE_ADDRESSING vpImagePoint &point)
Definition vpContours.h:172
vpDirectionType m_direction
Direction.
Definition vpContours.h:103
Class to define RGB colors available for display functionalities.
Definition vpColor.h:157
error that can be emitted by ViSP classes.
Definition vpException.h:60
@ fatalError
Fatal error.
Definition vpException.h:72
Class that defines a 2D point in an image. This class is useful for image processing and stores only ...
void set_j(double jj)
double get_j() const
double get_i() const
Definition of the vpImage class member functions.
Definition vpImage.h:131
unsigned int getWidth() const
Definition vpImage.h:242
unsigned int getSize() const
Definition vpImage.h:221
unsigned int getHeight() const
Definition vpImage.h:181
VISP_EXPORT void findContours(const VISP_NAMESPACE_ADDRESSING vpImage< unsigned char > &I_original, vpContour &contours, std::vector< std::vector< VISP_NAMESPACE_ADDRESSING vpImagePoint > > &contourPts, const vpContourRetrievalType &retrievalMode=CONTOUR_RETR_TREE)
VISP_EXPORT void drawContours(VISP_NAMESPACE_ADDRESSING vpImage< unsigned char > &I, const std::vector< std::vector< VISP_NAMESPACE_ADDRESSING vpImagePoint > > &contours, unsigned char grayValue=255)
void followBorder(vpImage< int > &I, const vpImagePoint &ij, vpImagePoint &i2j2, vpContour *border, int nbd)
bool fromTo(const vpImagePoint &from, const vpImagePoint &to, vpDirection &direction)
bool crossesEastBorder(const vpImage< int > &I, bool checked[8], const vpImagePoint &point)
bool isOuterBorderStart(const vpImage< int > &I, unsigned int i, unsigned int j)
void addContourPoint(vpImage< int > &I, vpContour *border, const vpImagePoint &point, bool checked[8], int nbd)
@ SOUTH_EAST
South-East direction.
Definition vpContours.h:88
@ EAST
East direction.
Definition vpContours.h:87
@ SOUTH_WEST
South-West direction.
Definition vpContours.h:90
@ NORTH_EAST
North-East direction.
Definition vpContours.h:86
@ SOUTH
South direction.
Definition vpContours.h:89
@ NORTH
North direction.
Definition vpContours.h:85
@ NORTH_WEST
North-West direction.
Definition vpContours.h:92
@ WEST
West direction.
Definition vpContours.h:91
void getContoursList(const vpContour &root, int level, vpContour &contour_list)
bool isHoleBorderStart(const vpImage< int > &I, unsigned int i, unsigned int j)
std::vector< vpContour * > m_children
Children contour.
Definition vpContours.h:212
std::vector< VISP_NAMESPACE_ADDRESSING vpImagePoint > m_points
Vector of points belonging to the contour.
Definition vpContours.h:218
vpContourType m_contourType
Contour type.
Definition vpContours.h:214
vpContour * m_parent
Parent contour.
Definition vpContours.h:216
void setParent(vpContour *parent)
Definition vpContours.h:300