00001 /*---------------------------------------------------------------------+ 00002 | Library QgarLib, graphics analysis and recognition | 00003 | Copyright (C) 2002 Qgar Project, LORIA | 00004 | | 00005 | This library is free software; you can redistribute it and/or | 00006 | modify it under the terms of the GNU Lesser General Public | 00007 | License version 2.1, as published by the Free Software Foundation. | 00008 | | 00009 | This library is distributed in the hope that it will be useful, | 00010 | but WITHOUT ANY WARRANTY; without even the implied warranty of | 00011 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | 00012 | See the GNU Lesser General Public License for more details. | 00013 | | 00014 | The GNU Lesser General Public License is included in the file | 00015 | LICENSE.LGPL, in the root directory of the Qgar packaging. See | 00016 | http://www.gnu.org/licenses/lgpl.html for the terms of the licence. | 00017 | To receive a paper copy, write to the Free Software Foundation, | 00018 | Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. | 00019 | | 00020 | Contact Project Qgar for any information: | 00021 | LORIA - équipe Qgar | 00022 | B.P. 239, 54506 Vandoeuvre-lès-Nancy Cedex, France | 00023 | email: qgar-contact@loria.fr | 00024 | http://www.qgar.org/ | 00025 *---------------------------------------------------------------------*/ 00026 00027 00028 /** 00029 * @file Component.C 00030 * @brief Implementation of class qgar::Component. 00031 * 00032 * See file Component.H for the interface. 00033 * 00034 * @author <a href="mailto:qgar-develop@loria.fr?subject=Qgar fwd Gérald Masini">Gérald Masini</a> 00035 * @date March 17, 2004 17:17 00036 * @since Qgar 2.1 00037 */ 00038 00039 00040 ////////////////////////////////////////////////////////////////////////////// 00041 #include <iostream> 00042 ////////////////////////////////////////////////////////////////////////////// 00043 00044 // STD 00045 #include <cmath> 00046 // STL 00047 #include <list> 00048 // QGAR 00049 #include <qgarlib/BoundingBox.H> 00050 #include <qgarlib/Component.H> 00051 #include <qgarlib/GenImage.H> 00052 #include <qgarlib/Maer.H> 00053 #include <qgarlib/primitives.H> 00054 00055 00056 00057 namespace qgar 00058 { 00059 00060 // ------------------------------------------------------------------- 00061 // ------------------------------------------------------------------- 00062 // S P E C I A L L A B E L S (STATIC) 00063 // ------------------------------------------------------------------- 00064 // ------------------------------------------------------------------- 00065 00066 00067 // MAXIMUM LABEL 00068 00069 const Component::label_type 00070 Component::_MAX_LABEL = std::numeric_limits<Component::label_type>::max(); 00071 00072 00073 // NO LABEL 00074 00075 const Component::label_type 00076 Component::_NO_LABEL = -1; 00077 00078 00079 // ------------------------------------------------------------------- 00080 // ------------------------------------------------------------------- 00081 // AUXILIARY DATA FOR CONTOUR FOLLOWING (STATIC) 00082 // ------------------------------------------------------------------- 00083 // ------------------------------------------------------------------- 00084 00085 00086 // TABLE FOR X COORDINATES INCREMENTS 00087 00088 const int Component::_s_incr_x[14] = 00089 { 00090 -1, // W (N entry) 00091 -1, // NW (NE entry) 00092 0, // N (E entry) 00093 1, // NE (SE entry) 00094 1, // E (S entry) 00095 1, // SE (SW entry) 00096 0, // S (W entry) 00097 -1, // SW (NW entry) 00098 -1, // W 00099 -1, // NW 00100 0, // N 00101 1, // NE 00102 1, // E 00103 1 // SE 00104 }; 00105 00106 00107 // TABLE FOR Y COORDINATES INCREMENTS 00108 00109 const int Component::_s_incr_y[14] = 00110 { 00111 0, // W (N entry) 00112 -1, // NW (NE entry) 00113 -1, // N (E entry) 00114 -1, // NE (SE entry) 00115 0, // E (S entry) 00116 1, // SE (SW entry) 00117 1, // S (W entry) 00118 1, // SW (NW entry) 00119 0, // W 00120 -1, // NW 00121 -1, // N 00122 -1, // NE 00123 0, // E 00124 1 // SE 00125 }; 00126 00127 00128 // TABLE FOR NEW CONTOUR DIRECTIONS 00129 00130 const int Component::_s_new_dir[14] = 00131 { 00132 6, // W 00133 7, // NW 00134 0, // N 00135 1, // NE 00136 2, // E 00137 3, // SE 00138 4, // S 00139 5, // SW 00140 6, // W 00141 7, // NW 00142 0, // N 00143 1, // NE 00144 2, // E 00145 3 // SE 00146 }; 00147 00148 00149 // ------------------------------------------------------------------- 00150 // ------------------------------------------------------------------- 00151 // C O N S T R U C T O R S 00152 // ------------------------------------------------------------------- 00153 // ------------------------------------------------------------------- 00154 00155 00156 // DEFAULT CONSTRUCTOR. 00157 00158 Component::Component() 00159 { 00160 // VOID 00161 } 00162 00163 // CONSTRUCT FROM FULL DATA 00164 00165 Component::Component(GenImage<Component::label_type>* aPCompImg, 00166 int aLabel, 00167 int anInLabel, 00168 QGEbw aBW, 00169 int aXTopLeftPix, 00170 int aYTopLeftPix, 00171 int aXTopLeft, 00172 int aYTopLeft, 00173 int aXBottomRight, 00174 int aYBottomRight, 00175 int anAreaPixels) 00176 00177 : _pCompImg (aPCompImg), 00178 _label (aLabel), 00179 _inLabel (anInLabel), 00180 _color (aBW), 00181 _xTopLeftPix (aXTopLeftPix), 00182 _yTopLeftPix (aYTopLeftPix), 00183 _boundingBox (aXTopLeft, aYTopLeft, aXBottomRight, aYBottomRight), 00184 _areaPixels (anAreaPixels), 00185 _maer(0) 00186 00187 { 00188 // VOID 00189 } 00190 00191 // COPY CONSTRUCTOR. 00192 00193 Component::Component(const Component& aCComp) 00194 00195 : _pCompImg (aCComp._pCompImg), 00196 _label (aCComp._label), 00197 _inLabel (aCComp._inLabel), 00198 _color (aCComp._color), 00199 _xTopLeftPix (aCComp._xTopLeftPix), 00200 _yTopLeftPix (aCComp._yTopLeftPix), 00201 _boundingBox (aCComp._boundingBox), 00202 _areaPixels (aCComp._areaPixels) 00203 00204 { 00205 // Copy MAER if and only if it is computed 00206 if (_maer != 0) 00207 { 00208 _maer = new Maer(*(aCComp._maer)); 00209 } 00210 } 00211 00212 // ------------------------------------------------------------------- 00213 // ------------------------------------------------------------------- 00214 // D E S T R U C T O R 00215 // ------------------------------------------------------------------- 00216 // ------------------------------------------------------------------- 00217 00218 00219 Component::~Component() 00220 { 00221 ////////////////////////////////////////////////////////////////////////////// 00222 // std::cout << "Component> Destruction de la composante " << _label << std::endl; 00223 ////////////////////////////////////////////////////////////////////////////// 00224 00225 delete _maer; 00226 } 00227 00228 00229 // ------------------------------------------------------------------- 00230 // ------------------------------------------------------------------- 00231 // C O O R D I N A T E S 00232 // ------------------------------------------------------------------- 00233 // ------------------------------------------------------------------- 00234 00235 00236 // GET BOUNDING BOX 00237 00238 const BoundingBox& 00239 Component::accessBoundingBox() const 00240 { 00241 return _boundingBox; 00242 } 00243 00244 // GET A COPY OF THE BOUNDING BOX 00245 00246 BoundingBox 00247 Component::boundingBox() const 00248 { 00249 return _boundingBox; 00250 } 00251 00252 // GET DENSITY WITH REGARD TO THE BOUNDING BOX 00253 00254 double 00255 Component::densityBox() const 00256 { 00257 double a = _boundingBox.area(); 00258 00259 if (a < 1.) 00260 { 00261 // When the area is less than 1, it should mean that the component 00262 // is a degenerated one, with a null length and/or width 00263 return 1.; 00264 } 00265 else 00266 { 00267 return ((double) _areaPixels) / a; 00268 } 00269 } 00270 00271 00272 // ------------------------------------------------------------------- 00273 // ------------------------------------------------------------------- 00274 // A C C E S S T O M A E R (MINIMUM-AREA ENCASING RECTANGLE) 00275 // ------------------------------------------------------------------- 00276 // ------------------------------------------------------------------- 00277 00278 00279 // GET THE MAER 00280 00281 const Maer& 00282 Component::accessMaer() 00283 { 00284 if (_maer == 0) 00285 { 00286 PRIVATEcomputeMaer(); 00287 } 00288 00289 return *_maer; 00290 } 00291 00292 // GET A COPY OF THE MAER 00293 00294 Maer 00295 Component::maer() 00296 { 00297 if (_maer == 0) 00298 { 00299 PRIVATEcomputeMaer(); 00300 } 00301 00302 return *_maer; 00303 } 00304 00305 // GET MAER AREA 00306 00307 int 00308 Component::maerAreaPixels() 00309 { 00310 if (_maer == 0) 00311 { 00312 PRIVATEcomputeMaer(); 00313 } 00314 00315 return _maerAreaPixels; 00316 } 00317 00318 // GET DENSITY WITH REGARD TO THE MAER 00319 00320 double 00321 Component::densityMaer() 00322 { 00323 if (_maer == 0) 00324 { 00325 PRIVATEcomputeMaer(); 00326 } 00327 00328 return ((double) _areaPixels) / ((double) _maerAreaPixels); 00329 } 00330 00331 00332 // ------------------------------------------------------------------- 00333 // ------------------------------------------------------------------- 00334 // A C C E S S T O T H E C O N T O U R 00335 // ------------------------------------------------------------------- 00336 // ------------------------------------------------------------------- 00337 00338 00339 // GET A POINTER TO THE COMPONENT CONTOUR 00340 00341 const std::list<DPoint>& 00342 Component::accessContour() 00343 { 00344 if (_contour.empty()) 00345 { 00346 PRIVATEcomputeContour(); 00347 } 00348 00349 return _contour; 00350 } 00351 00352 // GET A COPY OF THE COMPONENT CONTOUR 00353 00354 std::list<DPoint> 00355 Component::contour() 00356 { 00357 if (_contour.empty()) 00358 { 00359 PRIVATEcomputeContour(); 00360 } 00361 00362 return _contour; 00363 } 00364 00365 00366 // ------------------------------------------------------------------- 00367 // ------------------------------------------------------------------- 00368 // O P E R A T O R S 00369 // ------------------------------------------------------------------- 00370 // ------------------------------------------------------------------- 00371 00372 00373 Component& 00374 Component::operator=(const Component& aCComp) 00375 { 00376 // Are left hand side and right hand side different objects? 00377 if (this != &aCComp) 00378 { 00379 _pCompImg = aCComp._pCompImg; 00380 _label = aCComp._label; 00381 _inLabel = aCComp._inLabel; 00382 _color = aCComp._color; 00383 _xTopLeftPix = aCComp._xTopLeftPix; 00384 _yTopLeftPix = aCComp._yTopLeftPix; 00385 _boundingBox = aCComp._boundingBox; 00386 _areaPixels = aCComp._areaPixels; 00387 00388 // Copy MAER if it is computed 00389 if (_maer != 0) 00390 { 00391 _maer = new Maer(*(aCComp._maer)); 00392 } 00393 } 00394 00395 return *this; 00396 } 00397 00398 00399 // ------------------------------------------------------------------- 00400 // ------------------------------------------------------------------- 00401 // C O N S T R U C T T H E C O N T O U R (PRIVATE FUNCTION) 00402 // ------------------------------------------------------------------- 00403 // ------------------------------------------------------------------- 00404 00405 00406 // Compute the component contour 00407 00408 void 00409 Component::PRIVATEcomputeContour() 00410 { 00411 if (_label == 0) 00412 00413 // _________________________________________________________________ 00414 // 00415 // WARNING: COMPONENT 0 (BACKGROUND) IS A PARTICULAR CASE 00416 { 00417 _contour.push_back(DPoint((double) _boundingBox.xTopLeft(), 00418 (double) _boundingBox.yTopLeft())); 00419 _contour.push_back(DPoint((double) _boundingBox.xBottomRight(), 00420 (double) _boundingBox.yTopLeft())); 00421 _contour.push_back(DPoint((double) _boundingBox.xBottomRight(), 00422 (double) _boundingBox.yBottomRight())); 00423 _contour.push_back(DPoint((double) _boundingBox.xTopLeft(), 00424 (double) _boundingBox.yBottomRight())); 00425 } 00426 // _________________________________________________________________ 00427 00428 00429 else if (_areaPixels != 1) 00430 00431 // _________________________________________________________________ 00432 // 00433 // The component is not a single point 00434 // WARNING: Contour following does not work if component is a single 00435 // point, as starting point (the point constituting the component) 00436 // cannot be reached again when searching for a new contour point 00437 // around the starting point! 00438 // _________________________________________________________________ 00439 // 00440 00441 { 00442 // Component image width 00443 int width = _pCompImg->width(); 00444 00445 // Compute offset table to get pointers to 8-connexity neighbors 00446 // See NOTE (1) in comments of BLOCK-WHILE 00447 int mapPtOffset[14]; 00448 for (int idx = 0 ; idx < 14 ; ++idx) 00449 { 00450 mapPtOffset[idx] = _s_incr_y[idx] * width; 00451 } 00452 00453 // Pointer to the first contour point 00454 Component::label_type* pMap = 00455 _pCompImg->pPixMap() + (_yTopLeftPix * width) + _xTopLeftPix; 00456 00457 // Mark this first point 00458 (*pMap) = _NO_LABEL; 00459 00460 // Because of the way the component is constructed, current 00461 // direction can always be EAST (i.e. 2) in the beginning 00462 int currDir = 2; 00463 00464 // Coordinates of the current contour point 00465 int currX = _xTopLeftPix; 00466 int currY = _yTopLeftPix; 00467 00468 bool notBackToStartingPoint = true; 00469 00470 while (notBackToStartingPoint) 00471 // WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW 00472 // BLOCK-WHILE 00473 // while the new contour point is not the starting point 00474 // WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW 00475 { 00476 00477 for (int iCnt = 0 ; iCnt < 8 ; ++iCnt) 00478 // ^^^ ^^^ 00479 // for each direction around the current point, 00480 // from NORTH (0) to NORTH-WEST (7) 00481 { 00482 // COORDINATES AND DIRECTION COMPUTATION 00483 00484 // +---+---+---+ 00485 // | | 1 | 2 | 00486 // +---+---+---+ 00487 // | 7 | *-->3 | 00488 // +---+---+---+ 00489 // | 6 | 5 | 4 | 00490 // +---+---+---+ 00491 // 00492 // The outside of the component is always on the left 00493 // when moving along the contour direction. The figure 00494 // above gives an example: When following a contour in 00495 // the EAST direction, the first new possible contour 00496 // point (1) is left-perpendicular to the direction, 00497 // i.e. at NORTH of the current point (*), the second 00498 // point (2) is then at NORTH-EAST, and so on. 00499 00500 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 00501 // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 00502 // _s_incr_x |-1|-1| 0| 1| 1| 1| 0|-1|-1|-1| 0| 1| 1| 1| 00503 // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 00504 // 00505 // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 00506 // _s_incr_y | 0|-1|-1|-1| 0| 1| 1| 1| 0|-1|-1|-1| 0| 1| 00507 // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 00508 // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 00509 // mapPtOffset | 0|-w|-w|-w| 0| w| w| w| 0|-w|-w|-w| 0| w| 00510 // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 00511 // initial N NE E SE S SW W NW 00512 // incrIdx 00513 // 00514 // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 00515 // _s_new_dir | 6| 7| 0| 1| 2| 3| 4| 5| 6| 7| 0| 1| 2| 3| 00516 // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 00517 // newDir W NW N NE E SE S SW W NW N NE E SE 00518 // new contour directions 00519 // 00520 // The _s_incr_x (resp. y) table contains the X (resp. Y) 00521 // increment to get the X (resp. Y) coordinates of the 7 00522 // possible next contour points (points 1 to 7 on 1st 00523 // figure) from the X (resp. Y) coordinate of the current 00524 // point (*). The current contour direction (EAST on 1st 00525 // figure) determines the first index to enter the tables. 00526 // 00527 // Once a new contour point is found, table _s_new_dir 00528 // gives the corresponding new contour direction, using 00529 // the index corresponding to the new contour point in 00530 // table _s_incr_x or _s_incr_y. For example, if the new 00531 // contour point is at position 5 (on 1st figure), the 00532 // corresponding index in table _s_new_dir is 2 (for EAST, 00533 // current contour direction) + 4 (index of point at 00534 // position 5 among the neighbors of central point) = 6, 00535 // which gives SOUTH as new contour direction. 00536 00537 // ------------------------------------------------------- 00538 // NOTE (1) To avoid multiplications, table _s_incr_y 00539 // is substituted by table mapPtOffset, in which each 00540 // element is multiplied by the width (w) of the image. 00541 // ------------------------------------------------------- 00542 00543 // Index to look for possible contour points around the 00544 // current point (points 1 to 7 on the example above), 00545 // using tables _s_incr_x and _s_incr_y 00546 int incrIdx = currDir + iCnt; 00547 00548 // Pointer to the point corresponding to this direction 00549 // in the pixel map of the component image 00550 Component::label_type* newPMap 00551 = pMap + mapPtOffset[incrIdx] + _s_incr_x[incrIdx]; 00552 // ******************** 00553 // See NOTE (1) in comments above 00554 00555 if (*newPMap == _NO_LABEL) 00556 { 00557 //__________________________________________________ 00558 // 00559 // THE NEW POINT IS MARKED 00560 // => starting point is reached 00561 00562 // New contour direction corresponding to the point 00563 // under consideration (see figure above) 00564 int newDir = _s_new_dir[incrIdx]; 00565 00566 if (currDir != newDir) 00567 { 00568 // Contour direction is changed => store the current 00569 // point in the list representing the contour 00570 _contour.push_back(DPoint((double) currX, (double) currY)); 00571 } 00572 00573 // Check that the starting point is not recorded 00574 // in the contour before storing it 00575 if ( ((int) (_contour.front()).x() != _xTopLeftPix) 00576 || ((int) (_contour.front()).y() != _yTopLeftPix) ) 00577 { 00578 _contour.push_front 00579 (DPoint((double) _xTopLeftPix, (double) _yTopLeftPix)); 00580 } 00581 00582 // Unmark the starting point 00583 (*newPMap) = _label; 00584 00585 // Leave loops 00586 notBackToStartingPoint = false; 00587 break; 00588 //__________________________________________________ 00589 } 00590 else if (*newPMap == _label) 00591 { 00592 //__________________________________________________ 00593 // 00594 // THE NEW POINT IS LABELLED LIKE THE COMPONENT 00595 // => new contour point 00596 pMap = newPMap; 00597 00598 // New contour direction corresponding to the point 00599 // under consideration (see figure above) 00600 int newDir = _s_new_dir[incrIdx]; 00601 00602 if (currDir != newDir) 00603 { 00604 // Contour direction is changed => store the current 00605 // point in the list representing the contour 00606 _contour.push_back(DPoint((double) currX, (double) currY)); 00607 00608 // Set new contour direction 00609 currDir = newDir; 00610 } 00611 00612 // Update coordinates of the new contour point 00613 currX += _s_incr_x[incrIdx]; 00614 currY += _s_incr_y[incrIdx]; 00615 00616 // Leave loop 00617 break; 00618 //__________________________________________________ 00619 } 00620 00621 } // END for 00622 00623 } 00624 // WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW 00625 // END BLOCK-WHILE 00626 // back to the starting point 00627 // WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW 00628 } 00629 00630 else 00631 00632 // _________________________________________________________________ 00633 // 00634 // The component is a single point 00635 // _________________________________________________________________ 00636 // 00637 00638 { 00639 _contour.push_back(DPoint((double) _boundingBox.xTopLeft(), 00640 (double) _boundingBox.yTopLeft())); 00641 } 00642 00643 // _________________________________________________________________ 00644 // 00645 // END if 00646 // _________________________________________________________________ 00647 // 00648 00649 } // END Component::PRIVATEcomputeContour() 00650 00651 00652 // ------------------------------------------------------------------- 00653 // ------------------------------------------------------------------- 00654 // M A E R (PRIVATE FUNCTION) 00655 // ------------------------------------------------------------------- 00656 // ------------------------------------------------------------------- 00657 00658 00659 // Compute the MAER (Minimum-Area Encasing Rectangle) 00660 00661 void 00662 Component::PRIVATEcomputeMaer() 00663 { 00664 // Construct the maer 00665 _maer = new Maer(accessContour()); 00666 00667 // WARNING: The computation of the MAER is performed in the continuous 00668 // 2D space, while the computation of connected components features is 00669 // performed in the 2D discrete space. In particular, a component area 00670 // is provided in terms of a number of pixels, but its MAER area is not 00671 // => calculate an APPROXIMATION of the MAER area in pixels, 00672 // by adding 1 to the width and length of the MAER 00673 00674 const std::list<DPoint>& lv = _maer->accessVertices(); 00675 std::list<DPoint>::const_iterator itV1 = lv.begin(); 00676 std::list<DPoint>::const_iterator itV2 = itV1; 00677 ++itV2; 00678 00679 double dx12 = itV1->x() - itV2->x(); 00680 double dy12 = itV1->y() - itV2->y(); 00681 00682 ++itV1; 00683 ++itV2; 00684 double dx23 = itV1->x() - itV2->x(); 00685 double dy23 = itV1->y() - itV2->y(); 00686 00687 _maerAreaPixels = (int) std::ceil( (hypot(dx12, dy12) + 1.) 00688 * (hypot(dx23, dy23) + 1.) ); 00689 } 00690 00691 // ------------------------------------------------------------------- 00692 // ------------------------------------------------------------------- 00693 00694 } // namespace qgar