From 5ddd25313fa77f033325035f16adba0068a6f018 Mon Sep 17 00:00:00 2001 From: Vadim Pisarevsky Date: Fri, 26 Aug 2016 16:01:00 +0400 Subject: [PATCH] Add Grana's connected components algorithm for 8-way connectivity. (#6823) * Add Grana's connected components algorithm for 8-way connectivity. That algorithm is faster than Wu's one (currently implemented in opencv). For more details see https://github.com/prittt/YACCLAB. * New functions signature and distance transform compatibility * Add tests to imgproc/test/test_connectedcomponents.cpp * Change of test_connectedcomponents.cpp for c++98 support --- modules/imgproc/include/opencv2/imgproc.hpp | 51 +- modules/imgproc/src/connectedcomponents.cpp | 1401 ++++++++++++++++- modules/imgproc/src/distransform.cpp | 2 +- .../imgproc/test/test_connectedcomponents.cpp | 85 +- 4 files changed, 1486 insertions(+), 53 deletions(-) diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index f4698036b6..71c4f48d12 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -413,6 +413,13 @@ enum ConnectedComponentsTypes { CC_STAT_MAX = 5 }; +//! connected components algorithm +enum ConnectedComponentsAlgorithmsTypes { + CCL_WU = 0, //!< SAUF algorithm for 8-way connectivity, SAUF algorithm for 4-way connectivity + CCL_DEFAULT = -1, //!< BBDT algortihm for 8-way connectivity, SAUF algorithm for 4-way connectivity + CCL_GRANA = 1 //!< BBDT algorithm for 8-way connectivity, SAUF algorithm for 4-way connectivity +}; + //! mode of the contour retrieval algorithm enum RetrievalModes { /** retrieves only the extreme outer contours. It sets `hierarchy[i][2]=hierarchy[i][3]=-1` for @@ -3648,16 +3655,56 @@ CV_EXPORTS_W void matchTemplate( InputArray image, InputArray templ, image with 4 or 8 way connectivity - returns N, the total number of labels [0, N-1] where 0 represents the background label. ltype specifies the output label image type, an important consideration based on the total number of labels or alternatively the total number of pixels in -the source image. +the source image. ccltype specifies the connected components labeling algorithm to use, currently +Grana's (BBDT) and Wu's (SAUF) algorithms are supported, see the cv::ConnectedComponentsAlgorithmsTypes +for details. Note that SAUF algorithm forces a row major ordering of labels while BBDT does not. @param image the 8-bit single-channel image to be labeled @param labels destination labeled image @param connectivity 8 or 4 for 8-way or 4-way connectivity respectively @param ltype output image label type. Currently CV_32S and CV_16U are supported. - */ +@param ccltype connected components algorithm type (see the cv::ConnectedComponentsAlgorithmsTypes). +*/ +CV_EXPORTS_AS(connectedComponentsWithAlgorithm) int connectedComponents(InputArray image, OutputArray labels, + int connectivity, int ltype, int ccltype); + + +/** @overload + +@param image the 8-bit single-channel image to be labeled +@param labels destination labeled image +@param connectivity 8 or 4 for 8-way or 4-way connectivity respectively +@param ltype output image label type. Currently CV_32S and CV_16U are supported. +*/ CV_EXPORTS_W int connectedComponents(InputArray image, OutputArray labels, int connectivity = 8, int ltype = CV_32S); + +/** @brief computes the connected components labeled image of boolean image and also produces a statistics output for each label + +image with 4 or 8 way connectivity - returns N, the total number of labels [0, N-1] where 0 +represents the background label. ltype specifies the output label image type, an important +consideration based on the total number of labels or alternatively the total number of pixels in +the source image. ccltype specifies the connected components labeling algorithm to use, currently +Grana's (BBDT) and Wu's (SAUF) algorithms are supported, see the cv::ConnectedComponentsAlgorithmsTypes +for details. Note that SAUF algorithm forces a row major ordering of labels while BBDT does not. + + +@param image the 8-bit single-channel image to be labeled +@param labels destination labeled image +@param stats statistics output for each label, including the background label, see below for +available statistics. Statistics are accessed via stats(label, COLUMN) where COLUMN is one of +cv::ConnectedComponentsTypes. The data type is CV_32S. +@param centroids centroid output for each label, including the background label. Centroids are +accessed via centroids(label, 0) for x and centroids(label, 1) for y. The data type CV_64F. +@param connectivity 8 or 4 for 8-way or 4-way connectivity respectively +@param ltype output image label type. Currently CV_32S and CV_16U are supported. +@param ccltype connected components algorithm type (see the cv::ConnectedComponentsAlgorithmsTypes). +*/ +CV_EXPORTS_AS(connectedComponentsWithStatsWithAlgorithm) int connectedComponentsWithStats(InputArray image, OutputArray labels, + OutputArray stats, OutputArray centroids, + int connectivity, int ltype, int ccltype); + /** @overload @param image the 8-bit single-channel image to be labeled @param labels destination labeled image diff --git a/modules/imgproc/src/connectedcomponents.cpp b/modules/imgproc/src/connectedcomponents.cpp index 523eb14c09..bf53704144 100644 --- a/modules/imgproc/src/connectedcomponents.cpp +++ b/modules/imgproc/src/connectedcomponents.cpp @@ -38,6 +38,10 @@ // the use of this software, even if advised of the possibility of such damage. // // 2011 Jason Newton +// 2016 Costantino Grama +// 2016 Federico Bolelli +// 2016 Lorenzo Baraldi +// 2016 Roberto Vezzani //M*/ // #include "precomp.hpp" @@ -188,7 +192,7 @@ namespace cv{ //reference for 8-way: {{-1, -1}, {-1, 0}, {-1, 1}, {0, -1}};//a, b, c, d neighborhoods const int G8[4][2] = {{1, -1}, {1, 0}, {1, 1}, {0, -1}};//a, b, c, d neighborhoods template - struct LabelingImpl{ + struct LabelingWu{ LabelT operator()(const cv::Mat &I, cv::Mat &L, int connectivity, StatsOp &sop){ CV_Assert(L.rows == I.rows); CV_Assert(L.cols == I.cols); @@ -329,33 +333,1366 @@ namespace cv{ fastFree(P); return nLabels; - }//End function LabelingImpl operator() + }//End function LabelingWu operator() + };//End struct LabelingWu - };//End struct LabelingImpl + // Based on “Optimized Block-based Connected Components Labeling with Decision Trees”, Costantino Grana et al + // Only for 8-connectivity + template + struct LabelingGrana{ + LabelT operator()(const cv::Mat &img, cv::Mat &imgLabels, int connectivity, StatsOp &sop){ + CV_Assert(img.rows == imgLabels.rows); + CV_Assert(img.cols == imgLabels.cols); + CV_Assert(connectivity == 8 || connectivity == 4); + + const int h = img.rows; + const int w = img.cols; + + //A quick and dirty upper bound for the maximimum number of labels. + const size_t Plength = img.rows*img.cols / 4; + LabelT *P = (LabelT *)fastMalloc(sizeof(LabelT)* Plength); + P[0] = 0; + LabelT lunique = 1; + + // First scan + for (int r = 0; r(r); + const PixelT* const img_row_prev = (PixelT *)(((char *)img_row) - img.step.p[0]); + const PixelT* const img_row_prev_prev = (PixelT *)(((char *)img_row_prev) - img.step.p[0]); + const PixelT* const img_row_fol = (PixelT *)(((char *)img_row) + img.step.p[0]); + LabelT* const imgLabels_row = imgLabels.ptr(r); + LabelT* const imgLabels_row_prev_prev = (LabelT *)(((char *)imgLabels_row) - imgLabels.step.p[0] - imgLabels.step.p[0]); + for (int c = 0; c < w; c += 2) { + + // We work with 2x2 blocks + // +-+-+-+ + // |P|Q|R| + // +-+-+-+ + // |S|X| + // +-+-+ + + // The pixels are named as follows + // +---+---+---+ + // |a b|c d|e f| + // |g h|i j|k l| + // +---+---+---+ + // |m n|o p| + // |q r|s t| + // +---+---+ + + // Pixels a, f, l, q are not needed, since we need to understand the + // the connectivity between these blocks and those pixels only metter + // when considering the outer connectivities + + // A bunch of defines used to check if the pixels are foreground, + // without going outside the image limits. + #define condition_b c-1>=0 && r-2>=0 && img_row_prev_prev[c-1]>0 + #define condition_c r-2>=0 && img_row_prev_prev[c]>0 + #define condition_d c+1=0 && img_row_prev_prev[c+1]>0 + #define condition_e c+2=0 && img_row_prev_prev[c+2]>0 + + #define condition_g c-2>=0 && r-1>=0 && img_row_prev[c-2]>0 + #define condition_h c-1>=0 && r-1>=0 && img_row_prev[c-1]>0 + #define condition_i r-1>=0 && img_row_prev[c]>0 + #define condition_j c+1=0 && img_row_prev[c+1]>0 + #define condition_k c+2=0 && img_row_prev[c+2]>0 + + #define condition_m c-2>=0 && img_row[c-2]>0 + #define condition_n c-1>=0 && img_row[c-1]>0 + #define condition_o img_row[c]>0 + #define condition_p c+10 + + #define condition_r c-1>=0 && r+10 + #define condition_s r+10 + #define condition_t c+10 + + // This is a decision tree which allows to choose which action to + // perform, checking as few conditions as possible. + // Actions: the blocks label are provisionally stored in the top left + // pixel of the block in the labels image + + if (condition_o) { + if (condition_n) { + if (condition_j) { + if (condition_i) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + if (condition_c) { + if (condition_h) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + if (condition_g) { + if (condition_b) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + } + else { + if (condition_p) { + if (condition_k) { + if (condition_d) { + if (condition_i) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + if (condition_c) { + if (condition_h) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + if (condition_g) { + if (condition_b) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + } + else { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + } + } + else { + if (condition_r) { + if (condition_j) { + if (condition_m) { + if (condition_h) { + if (condition_i) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + if (condition_c) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + } + else { + if (condition_g) { + if (condition_b) { + if (condition_i) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + if (condition_c) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + } + else { + if (condition_i) { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + else { + if (condition_h) { + if (condition_c) { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + else { + //Action_14: Merge labels of block P, Q and S + imgLabels_row[c] = set_union(P, set_union(P, imgLabels_row_prev_prev[c - 2], imgLabels_row_prev_prev[c]), imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + } + } + else { + if (condition_p) { + if (condition_k) { + if (condition_m) { + if (condition_h) { + if (condition_d) { + if (condition_i) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + if (condition_c) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + else { + if (condition_d) { + if (condition_g) { + if (condition_b) { + if (condition_i) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + if (condition_c) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + else { + if (condition_i) { + if (condition_g) { + if (condition_b) { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + else { + //Action_16: labels of block Q, R and S + imgLabels_row[c] = set_union(P, set_union(P, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_16: labels of block Q, R and S + imgLabels_row[c] = set_union(P, set_union(P, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + } + } + else { + if (condition_i) { + if (condition_d) { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + else { + //Action_16: labels of block Q, R and S + imgLabels_row[c] = set_union(P, set_union(P, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]); + continue; + } + } + else { + if (condition_h) { + if (condition_d) { + if (condition_c) { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + else { + //Action_15: Merge labels of block P, R and S + imgLabels_row[c] = set_union(P, set_union(P, imgLabels_row_prev_prev[c - 2], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_15: Merge labels of block P, R and S + imgLabels_row[c] = set_union(P, set_union(P, imgLabels_row_prev_prev[c - 2], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + } + } + else { + if (condition_h) { + if (condition_m) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + // ACTION_9 Merge labels of block P and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c - 2], imgLabels_row[c - 2]); + continue; + } + } + else { + if (condition_i) { + if (condition_m) { + if (condition_g) { + if (condition_b) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + } + } + } + else { + if (condition_h) { + if (condition_m) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + // ACTION_9 Merge labels of block P and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c - 2], imgLabels_row[c - 2]); + continue; + } + } + else { + if (condition_i) { + if (condition_m) { + if (condition_g) { + if (condition_b) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + } + } + } + } + else { + if (condition_j) { + if (condition_i) { + //Action_4: Assign label of block Q + imgLabels_row[c] = imgLabels_row_prev_prev[c]; + continue; + } + else { + if (condition_h) { + if (condition_c) { + //Action_4: Assign label of block Q + imgLabels_row[c] = imgLabels_row_prev_prev[c]; + continue; + } + else { + //Action_7: Merge labels of block P and Q + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c - 2], imgLabels_row_prev_prev[c]); + continue; + } + } + else { + //Action_4: Assign label of block Q + imgLabels_row[c] = imgLabels_row_prev_prev[c]; + continue; + } + } + } + else { + if (condition_p) { + if (condition_k) { + if (condition_i) { + if (condition_d) { + //Action_5: Assign label of block R + imgLabels_row[c] = imgLabels_row_prev_prev[c + 2]; + continue; + } + else { + // ACTION_10 Merge labels of block Q and R + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]); + continue; + } + } + else { + if (condition_h) { + if (condition_d) { + if (condition_c) { + //Action_5: Assign label of block R + imgLabels_row[c] = imgLabels_row_prev_prev[c + 2]; + continue; + } + else { + //Action_8: Merge labels of block P and R + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c - 2], imgLabels_row_prev_prev[c + 2]); + continue; + } + } + else { + //Action_8: Merge labels of block P and R + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c - 2], imgLabels_row_prev_prev[c + 2]); + continue; + } + } + else { + //Action_5: Assign label of block R + imgLabels_row[c] = imgLabels_row_prev_prev[c + 2]; + continue; + } + } + } + else { + if (condition_i) { + //Action_4: Assign label of block Q + imgLabels_row[c] = imgLabels_row_prev_prev[c]; + continue; + } + else { + if (condition_h) { + //Action_3: Assign label of block P + imgLabels_row[c] = imgLabels_row_prev_prev[c - 2]; + continue; + } + else { + //Action_2: New label (the block has foreground pixels and is not connected to anything else) + imgLabels_row[c] = lunique; + P[lunique] = lunique; + lunique = lunique + 1; + continue; + } + } + } + } + else { + if (condition_i) { + //Action_4: Assign label of block Q + imgLabels_row[c] = imgLabels_row_prev_prev[c]; + continue; + } + else { + if (condition_h) { + //Action_3: Assign label of block P + imgLabels_row[c] = imgLabels_row_prev_prev[c - 2]; + continue; + } + else { + //Action_2: New label (the block has foreground pixels and is not connected to anything else) + imgLabels_row[c] = lunique; + P[lunique] = lunique; + lunique = lunique + 1; + continue; + } + } + } + } + } + } + } + else { + if (condition_s) { + if (condition_p) { + if (condition_n) { + if (condition_j) { + if (condition_i) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + if (condition_c) { + if (condition_h) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + if (condition_g) { + if (condition_b) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + } + else { + if (condition_k) { + if (condition_d) { + if (condition_i) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + if (condition_c) { + if (condition_h) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + if (condition_g) { + if (condition_b) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + } + } + else { + if (condition_r) { + if (condition_j) { + if (condition_m) { + if (condition_h) { + if (condition_i) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + if (condition_c) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + } + else { + if (condition_g) { + if (condition_b) { + if (condition_i) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + if (condition_c) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + else { + if (condition_k) { + if (condition_d) { + if (condition_m) { + if (condition_h) { + if (condition_i) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + if (condition_c) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + } + else { + if (condition_g) { + if (condition_b) { + if (condition_i) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + if (condition_c) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + else { + if (condition_i) { + if (condition_m) { + if (condition_h) { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + else { + if (condition_g) { + if (condition_b) { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + else { + //Action_16: labels of block Q, R and S + imgLabels_row[c] = set_union(P, set_union(P, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_16: labels of block Q, R and S + imgLabels_row[c] = set_union(P, set_union(P, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]); + continue; + } + } + } + else { + //Action_16: labels of block Q, R and S + imgLabels_row[c] = set_union(P, set_union(P, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_12: Merge labels of block R and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]); + continue; + } + } + } + else { + if (condition_i) { + if (condition_m) { + if (condition_h) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + if (condition_g) { + if (condition_b) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + } + else { + //Action_11: Merge labels of block Q and S + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]); + continue; + } + } + else { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + } + } + } + else { + if (condition_j) { + //Action_4: Assign label of block Q + imgLabels_row[c] = imgLabels_row_prev_prev[c]; + continue; + } + else { + if (condition_k) { + if (condition_i) { + if (condition_d) { + //Action_5: Assign label of block R + imgLabels_row[c] = imgLabels_row_prev_prev[c + 2]; + continue; + } + else { + // ACTION_10 Merge labels of block Q and R + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]); + continue; + } + } + else { + //Action_5: Assign label of block R + imgLabels_row[c] = imgLabels_row_prev_prev[c + 2]; + continue; + } + } + else { + if (condition_i) { + //Action_4: Assign label of block Q + imgLabels_row[c] = imgLabels_row_prev_prev[c]; + continue; + } + else { + //Action_2: New label (the block has foreground pixels and is not connected to anything else) + imgLabels_row[c] = lunique; + P[lunique] = lunique; + lunique = lunique + 1; + continue; + } + } + } + } + } + } + else { + if (condition_r) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + if (condition_n) { + //Action_6: Assign label of block S + imgLabels_row[c] = imgLabels_row[c - 2]; + continue; + } + else { + //Action_2: New label (the block has foreground pixels and is not connected to anything else) + imgLabels_row[c] = lunique; + P[lunique] = lunique; + lunique = lunique + 1; + continue; + } + } + } + } + else { + if (condition_p) { + if (condition_j) { + //Action_4: Assign label of block Q + imgLabels_row[c] = imgLabels_row_prev_prev[c]; + continue; + } + else { + if (condition_k) { + if (condition_i) { + if (condition_d) { + //Action_5: Assign label of block R + imgLabels_row[c] = imgLabels_row_prev_prev[c + 2]; + continue; + } + else { + // ACTION_10 Merge labels of block Q and R + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]); + continue; + } + } + else { + //Action_5: Assign label of block R + imgLabels_row[c] = imgLabels_row_prev_prev[c + 2]; + continue; + } + } + else { + if (condition_i) { + //Action_4: Assign label of block Q + imgLabels_row[c] = imgLabels_row_prev_prev[c]; + continue; + } + else { + //Action_2: New label (the block has foreground pixels and is not connected to anything else) + imgLabels_row[c] = lunique; + P[lunique] = lunique; + lunique = lunique + 1; + continue; + } + } + } + } + else { + if (condition_t) { + //Action_2: New label (the block has foreground pixels and is not connected to anything else) + imgLabels_row[c] = lunique; + P[lunique] = lunique; + lunique = lunique + 1; + continue; + } + else { + // Action_1: No action (the block has no foreground pixels) + imgLabels_row[c] = 0; + continue; + } + } + } + } + } + } + + // Second scan + analysis + LabelT nLabels = flattenL(P, lunique); + sop.init(nLabels); + + if (imgLabels.rows & 1){ + if (imgLabels.cols & 1){ + //Case 1: both rows and cols odd + for (int r = 0; r(r); + const PixelT* const img_row_fol = (PixelT *)(((char *)img_row) + img.step.p[0]); + LabelT* const imgLabels_row = imgLabels.ptr(r); + LabelT* const imgLabels_row_fol = (LabelT *)(((char *)imgLabels_row) + imgLabels.step.p[0]); + + for (int c = 0; c0) { + iLabel = P[iLabel]; + if (img_row[c] > 0){ + imgLabels_row[c] = iLabel; + sop(r, c, iLabel); + } + else{ + imgLabels_row[c] = 0; + sop(r, c, 0); + } + if (c + 1 0){ + imgLabels_row[c + 1] = iLabel; + sop(r, c + 1, iLabel); + } + else{ + imgLabels_row[c + 1] = 0; + sop(r, c + 1, 0); + } + if (r + 1 0){ + imgLabels_row_fol[c] = iLabel; + sop(r + 1, c, iLabel); + } else{ + imgLabels_row_fol[c] = 0; + sop(r + 1, c, 0); + } + if (img_row_fol[c + 1]>0){ + imgLabels_row_fol[c + 1] = iLabel; + sop(r + 1, c + 1, iLabel); + } else{ + imgLabels_row_fol[c + 1] = 0; + sop(r + 1, c + 1, 0); + } + } + } + else if (r + 10){ + imgLabels_row_fol[c] = iLabel; + sop(r + 1, c, iLabel); + }else{ + imgLabels_row_fol[c] = 0; + sop(r + 1, c, 0); + } + } + } + else { + imgLabels_row[c] = 0; + sop(r, c, 0); + if (c + 1(r); + const PixelT* const img_row_fol = (PixelT *)(((char *)img_row) + img.step.p[0]); + LabelT* const imgLabels_row = imgLabels.ptr(r); + LabelT* const imgLabels_row_fol = (LabelT *)(((char *)imgLabels_row) + imgLabels.step.p[0]); + + for (int c = 0; c0) { + iLabel = P[iLabel]; + if (img_row[c]>0){ + imgLabels_row[c] = iLabel; + sop(r, c, iLabel); + } else{ + imgLabels_row[c] = 0; + sop(r, c, 0); + } + if (img_row[c + 1]>0){ + imgLabels_row[c + 1] = iLabel; + sop(r, c + 1, iLabel); + }else{ + imgLabels_row[c + 1] = 0; + sop(r, c + 1, 0); + } + if (r + 10){ + imgLabels_row_fol[c] = iLabel; + sop(r + 1, c, iLabel); + }else{ + imgLabels_row_fol[c] = 0; + sop(r + 1, c, 0); + } + if (img_row_fol[c + 1]>0){ + imgLabels_row_fol[c + 1] = iLabel; + sop(r + 1, c + 1, iLabel); + }else{ + imgLabels_row_fol[c + 1] = 0; + sop(r + 1, c + 1, 0); + } + } + } + else { + imgLabels_row[c] = 0; + imgLabels_row[c + 1] = 0; + sop(r, c, 0); + sop(r, c + 1, 0); + if (r + 1(r); + const PixelT* const img_row_fol = (PixelT *)(((char *)img_row) + img.step.p[0]); + LabelT* const imgLabels_row = imgLabels.ptr(r); + LabelT* const imgLabels_row_fol = (LabelT *)(((char *)imgLabels_row) + imgLabels.step.p[0]); + + for (int c = 0; c0) { + iLabel = P[iLabel]; + if (img_row[c]>0){ + imgLabels_row[c] = iLabel; + sop(r, c, iLabel); + }else{ + imgLabels_row[c] = 0; + sop(r, c, 0); + } + if (img_row_fol[c]>0){ + imgLabels_row_fol[c] = iLabel; + sop(r + 1, c, iLabel); + }else{ + imgLabels_row_fol[c] = 0; + sop(r + 1, c, 0); + } + if (c + 10){ + imgLabels_row[c + 1] = iLabel; + sop(r, c + 1, iLabel); + }else{ + imgLabels_row[c + 1] = 0; + sop(r, c + 1, 0); + } + if (img_row_fol[c + 1]>0){ + imgLabels_row_fol[c + 1] = iLabel; + sop(r + 1, c + 1, iLabel); + }else{ + imgLabels_row_fol[c + 1] = 0; + sop(r + 1, c + 1, 0); + } + } + } + else{ + imgLabels_row[c] = 0; + imgLabels_row_fol[c] = 0; + sop(r, c, 0); + sop(r + 1, c, 0); + if (c + 1(r); + const PixelT* const img_row_fol = (PixelT *)(((char *)img_row) + img.step.p[0]); + LabelT* const imgLabels_row = imgLabels.ptr(r); + LabelT* const imgLabels_row_fol = (LabelT *)(((char *)imgLabels_row) + imgLabels.step.p[0]); + + for (int c = 0; c0) { + iLabel = P[iLabel]; + if (img_row[c] > 0){ + imgLabels_row[c] = iLabel; + sop(r, c, iLabel); + }else{ + imgLabels_row[c] = 0; + sop(r, c, 0); + } + if (img_row[c + 1] > 0){ + imgLabels_row[c + 1] = iLabel; + sop(r, c + 1, iLabel); + }else{ + imgLabels_row[c + 1] = 0; + sop(r, c + 1, 0); + } + if (img_row_fol[c] > 0){ + imgLabels_row_fol[c] = iLabel; + sop(r + 1, c, iLabel); + }else{ + imgLabels_row_fol[c] = 0; + sop(r + 1, c, 0); + } + if (img_row_fol[c + 1] > 0){ + imgLabels_row_fol[c + 1] = iLabel; + sop(r + 1, c + 1, iLabel); + }else{ + imgLabels_row_fol[c + 1] = 0; + sop(r + 1, c + 1, 0); + } + } + else { + imgLabels_row[c] = 0; + imgLabels_row[c + 1] = 0; + imgLabels_row_fol[c] = 0; + imgLabels_row_fol[c + 1] = 0; + sop(r, c, 0); + sop(r, c + 1, 0); + sop(r + 1, c, 0); + sop(r + 1, c + 1, 0); + } + } + } + }//END case 4 + } + + sop.finish(); + fastFree(P); + + return nLabels; + + } //End function LabelingGrana operator() + }; //End struct LabelingGrana }//end namespace connectedcomponents //L's type must have an appropriate depth for the number of pixels in I template static -int connectedComponents_sub1(const cv::Mat &I, cv::Mat &L, int connectivity, StatsOp &sop){ +int connectedComponents_sub1(const cv::Mat &I, cv::Mat &L, int connectivity, int ccltype, StatsOp &sop){ CV_Assert(L.channels() == 1 && I.channels() == 1); CV_Assert(connectivity == 8 || connectivity == 4); + CV_Assert(ccltype == CCL_GRANA || ccltype == CCL_WU || ccltype == CCL_DEFAULT); int lDepth = L.depth(); int iDepth = I.depth(); - using connectedcomponents::LabelingImpl; - //warn if L's depth is not sufficient? CV_Assert(iDepth == CV_8U || iDepth == CV_8S); - if(lDepth == CV_8U){ - return (int) LabelingImpl()(I, L, connectivity, sop); - }else if(lDepth == CV_16U){ - return (int) LabelingImpl()(I, L, connectivity, sop); - }else if(lDepth == CV_32S){ - //note that signed types don't really make sense here and not being able to use unsigned matters for scientific projects - //OpenCV: how should we proceed? .at typechecks in debug mode - return (int) LabelingImpl()(I, L, connectivity, sop); + if (ccltype == CCL_WU || connectivity == 4){ + // Wu algorithm is used + using connectedcomponents::LabelingWu; + //warn if L's depth is not sufficient? + if (lDepth == CV_8U){ + return (int)LabelingWu()(I, L, connectivity, sop); + } + else if (lDepth == CV_16U){ + return (int)LabelingWu()(I, L, connectivity, sop); + } + else if (lDepth == CV_32S){ + //note that signed types don't really make sense here and not being able to use unsigned matters for scientific projects + //OpenCV: how should we proceed? .at typechecks in debug mode + return (int)LabelingWu()(I, L, connectivity, sop); + } + }else if ((ccltype == CCL_GRANA || ccltype == CCL_DEFAULT) && connectivity == 8){ + // Grana algorithm is used + using connectedcomponents::LabelingGrana; + //warn if L's depth is not sufficient? + if (lDepth == CV_8U){ + return (int)LabelingGrana()(I, L, connectivity, sop); + } + else if (lDepth == CV_16U){ + return (int)LabelingGrana()(I, L, connectivity, sop); + } + else if (lDepth == CV_32S){ + //note that signed types don't really make sense here and not being able to use unsigned matters for scientific projects + //OpenCV: how should we proceed? .at typechecks in debug mode + return (int)LabelingGrana()(I, L, connectivity, sop); + } } CV_Error(CV_StsUnsupportedFormat, "unsupported label/image type"); @@ -364,33 +1701,49 @@ int connectedComponents_sub1(const cv::Mat &I, cv::Mat &L, int connectivity, Sta } +// Simple wrapper to ensure binary and source compatibility (ABI) int cv::connectedComponents(InputArray _img, OutputArray _labels, int connectivity, int ltype){ + return cv::connectedComponents(_img, _labels, connectivity, ltype, CCL_DEFAULT); +} + +int cv::connectedComponents(InputArray _img, OutputArray _labels, int connectivity, int ltype, int ccltype){ const cv::Mat img = _img.getMat(); _labels.create(img.size(), CV_MAT_DEPTH(ltype)); cv::Mat labels = _labels.getMat(); connectedcomponents::NoOp sop; - if(ltype == CV_16U){ - return connectedComponents_sub1(img, labels, connectivity, sop); - }else if(ltype == CV_32S){ - return connectedComponents_sub1(img, labels, connectivity, sop); - }else{ + if (ltype == CV_16U){ + return connectedComponents_sub1(img, labels, connectivity, ccltype, sop); + } + else if (ltype == CV_32S){ + return connectedComponents_sub1(img, labels, connectivity, ccltype, sop); + } + else{ CV_Error(CV_StsUnsupportedFormat, "the type of labels must be 16u or 32s"); return 0; } } +// Simple wrapper to ensure binary and source compatibility (ABI) int cv::connectedComponentsWithStats(InputArray _img, OutputArray _labels, OutputArray statsv, OutputArray centroids, int connectivity, int ltype) +{ + return cv::connectedComponentsWithStats(_img, _labels, statsv, centroids, connectivity, ltype, CCL_DEFAULT); +} + +int cv::connectedComponentsWithStats(InputArray _img, OutputArray _labels, OutputArray statsv, + OutputArray centroids, int connectivity, int ltype, int ccltype) { const cv::Mat img = _img.getMat(); _labels.create(img.size(), CV_MAT_DEPTH(ltype)); cv::Mat labels = _labels.getMat(); connectedcomponents::CCStatsOp sop(statsv, centroids); - if(ltype == CV_16U){ - return connectedComponents_sub1(img, labels, connectivity, sop); - }else if(ltype == CV_32S){ - return connectedComponents_sub1(img, labels, connectivity, sop); - }else{ + if (ltype == CV_16U){ + return connectedComponents_sub1(img, labels, connectivity, ccltype, sop); + } + else if (ltype == CV_32S){ + return connectedComponents_sub1(img, labels, connectivity, ccltype, sop); + } + else{ CV_Error(CV_StsUnsupportedFormat, "the type of labels must be 16u or 32s"); return 0; } diff --git a/modules/imgproc/src/distransform.cpp b/modules/imgproc/src/distransform.cpp index 070a649e63..36914d89d8 100644 --- a/modules/imgproc/src/distransform.cpp +++ b/modules/imgproc/src/distransform.cpp @@ -827,7 +827,7 @@ void cv::distanceTransform( InputArray _src, OutputArray _dst, OutputArray _labe if( labelType == CV_DIST_LABEL_CCOMP ) { Mat zpix = src == 0; - connectedComponents(zpix, labels, 8, CV_32S); + connectedComponents(zpix, labels, 8, CV_32S, CCL_WU); } else { diff --git a/modules/imgproc/test/test_connectedcomponents.cpp b/modules/imgproc/test/test_connectedcomponents.cpp index dd4d8339a0..d1c7b6a5fe 100644 --- a/modules/imgproc/test/test_connectedcomponents.cpp +++ b/modules/imgproc/test/test_connectedcomponents.cpp @@ -42,6 +42,7 @@ #include "test_precomp.hpp" #include +#include using namespace cv; using namespace std; @@ -58,49 +59,81 @@ protected: CV_ConnectedComponentsTest::CV_ConnectedComponentsTest() {} CV_ConnectedComponentsTest::~CV_ConnectedComponentsTest() {} +// This function force a row major order for the labels +void normalizeLabels(Mat1i& imgLabels, int iNumLabels) { + vector vecNewLabels(iNumLabels + 1, 0); + int iMaxNewLabel = 0; + + for (int r = 0; r0) { + if (vecNewLabels[iCurLabel] == 0) { + vecNewLabels[iCurLabel] = ++iMaxNewLabel; + } + imgLabels(r, c) = vecNewLabels[iCurLabel]; + } + } + } +} + + void CV_ConnectedComponentsTest::run( int /* start_from */) { + + int ccltype[] = { cv::CCL_WU, cv::CCL_DEFAULT, cv::CCL_GRANA }; + string exp_path = string(ts->get_data_path()) + "connectedcomponents/ccomp_exp.png"; Mat exp = imread(exp_path, 0); Mat orig = imread(string(ts->get_data_path()) + "connectedcomponents/concentric_circles.png", 0); if (orig.empty()) { - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); + ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); return; } Mat bw = orig > 128; - Mat labelImage; - int nLabels = connectedComponents(bw, labelImage, 8, CV_32S); - for(int r = 0; r < labelImage.rows; ++r){ - for(int c = 0; c < labelImage.cols; ++c){ - int l = labelImage.at(r, c); - bool pass = l >= 0 && l <= nLabels; - if(!pass){ - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT ); - return; + for (uint cclt = 0; cclt < sizeof(ccltype)/sizeof(int); ++cclt) + { + + Mat1i labelImage; + int nLabels = connectedComponents(bw, labelImage, 8, CV_32S, ccltype[cclt]); + + normalizeLabels(labelImage, nLabels); + + // Validate test results + for (int r = 0; r < labelImage.rows; ++r){ + for (int c = 0; c < labelImage.cols; ++c){ + int l = labelImage.at(r, c); + bool pass = l >= 0 && l <= nLabels; + if (!pass){ + ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT); + return; + } } } + + if (exp.empty() || orig.size() != exp.size()) + { + imwrite(exp_path, labelImage); + exp = labelImage; + } + + if (0 != cvtest::norm(labelImage > 0, exp > 0, NORM_INF)) + { + ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); + return; + } + if (nLabels != cvtest::norm(labelImage, NORM_INF) + 1) + { + ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); + return; + } + } - if( exp.empty() || orig.size() != exp.size() ) - { - imwrite(exp_path, labelImage); - exp = labelImage; - } - - if (0 != cvtest::norm(labelImage > 0, exp > 0, NORM_INF)) - { - ts->set_failed_test_info( cvtest::TS::FAIL_MISMATCH ); - return; - } - if (nLabels != cvtest::norm(labelImage, NORM_INF)+1) - { - ts->set_failed_test_info( cvtest::TS::FAIL_MISMATCH ); - return; - } ts->set_failed_test_info(cvtest::TS::OK); }