diff --git a/doc/opencv.bib b/doc/opencv.bib index 4a5cafd2d8da3dc46f482ae38f6b6c52be2c28a4..bace4c68c9a3794db1e7f6732239ec605859e797 100644 --- a/doc/opencv.bib +++ b/doc/opencv.bib @@ -1278,3 +1278,11 @@ pages={281--305}, year={1987} } +@article{Bolelli2021, + title={One DAG to Rule Them All}, + author={Bolelli, Federico and Allegretti, Stefano and Grana, Costantino}, + journal={IEEE Transactions on Pattern Analysis and Machine Intelligence}, + year={2021}, + publisher={IEEE}, + doi = {10.1109/TPAMI.2021.3055337} +} diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index c669f2cdef97c400907a07d127b38d98fe88dec0..98b074d46dd973de97daf86407bbaf615b05098f 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -406,10 +406,10 @@ enum ConnectedComponentsTypes { //! connected components algorithm enum ConnectedComponentsAlgorithmsTypes { - CCL_DEFAULT = -1, //!< BBDT @cite Grana2010 algorithm for 8-way connectivity, SAUF algorithm for 4-way connectivity. The parallel implementation described in @cite Bolelli2017 is available for both BBDT and SAUF. + CCL_DEFAULT = -1, //!< Spaghetti @cite Bolelli2019 algorithm for 8-way connectivity, Spaghetti4C @cite Bolelli2021 algorithm for 4-way connectivity. CCL_WU = 0, //!< SAUF @cite Wu2009 algorithm for 8-way connectivity, SAUF algorithm for 4-way connectivity. The parallel implementation described in @cite Bolelli2017 is available for SAUF. CCL_GRANA = 1, //!< BBDT @cite Grana2010 algorithm for 8-way connectivity, SAUF algorithm for 4-way connectivity. The parallel implementation described in @cite Bolelli2017 is available for both BBDT and SAUF. - CCL_BOLELLI = 2, //!< Spaghetti @cite Bolelli2019 algorithm for 8-way connectivity, SAUF algorithm for 4-way connectivity. + CCL_BOLELLI = 2, //!< Spaghetti @cite Bolelli2019 algorithm for 8-way connectivity, Spaghetti4C @cite Bolelli2021 algorithm for 4-way connectivity. The parallel implementation described in @cite Bolelli2017 is available for both Spaghetti and Spaghetti4C. CCL_SAUF = 3, //!< Same as CCL_WU. It is preferable to use the flag with the name of the algorithm (CCL_SAUF) rather than the one with the name of the first author (CCL_WU). CCL_BBDT = 4, //!< Same as CCL_GRANA. It is preferable to use the flag with the name of the algorithm (CCL_BBDT) rather than the one with the name of the first author (CCL_GRANA). CCL_SPAGHETTI = 5, //!< Same as CCL_BOLELLI. It is preferable to use the flag with the name of the algorithm (CCL_SPAGHETTI) rather than the one with the name of the first author (CCL_BOLELLI). @@ -3912,9 +3912,10 @@ image with 4 or 8 way connectivity - returns N, the total number of labels [0, N 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 (BBDT) and Wu's (SAUF) @cite Wu2009 algorithms are supported, see the #ConnectedComponentsAlgorithmsTypes -for details. Note that SAUF algorithm forces a row major ordering of labels while BBDT does not. -This function uses parallel version of both Grana and Wu's algorithms if at least one allowed +Bolelli (Spaghetti) @cite Bolelli2019, Grana (BBDT) @cite Grana2010 and Wu's (SAUF) @cite Wu2009 algorithms +are supported, see the #ConnectedComponentsAlgorithmsTypes for details. Note that SAUF algorithm forces +a row major ordering of labels while Spaghetti and BBDT do not. +This function uses parallel version of the algorithms if at least one allowed parallel framework is enabled and if the rows of the image are at least twice the number returned by #getNumberOfCPUs. @param image the 8-bit single-channel image to be labeled @@ -3944,9 +3945,10 @@ image with 4 or 8 way connectivity - returns N, the total number of labels [0, N 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) @cite Wu2009 algorithms are supported, see the #ConnectedComponentsAlgorithmsTypes -for details. Note that SAUF algorithm forces a row major ordering of labels while BBDT does not. -This function uses parallel version of both Grana and Wu's algorithms (statistics included) if at least one allowed +Bolelli (Spaghetti) @cite Bolelli2019, Grana (BBDT) @cite Grana2010 and Wu's (SAUF) @cite Wu2009 algorithms +are supported, see the #ConnectedComponentsAlgorithmsTypes for details. Note that SAUF algorithm forces +a row major ordering of labels while Spaghetti and BBDT do not. +This function uses parallel version of the algorithms (statistics included) if at least one allowed parallel framework is enabled and if the rows of the image are at least twice the number returned by #getNumberOfCPUs. @param image the 8-bit single-channel image to be labeled diff --git a/modules/imgproc/src/connectedcomponents.cpp b/modules/imgproc/src/connectedcomponents.cpp index e66766c8728fa369f8ccee8dc646881381fecb75..1ad74ed38af3c334a420fbf06736b2f563769e82 100644 --- a/modules/imgproc/src/connectedcomponents.cpp +++ b/modules/imgproc/src/connectedcomponents.cpp @@ -287,9 +287,898 @@ namespace cv{ return LT((y /*+ 1*/) / 2) * LT((w + 1) / 2) + 1; } - //Implementation of Spaghetti algorithm, as described in "Spaghetti Labeling: Directed Acyclic Graphs for Block-Based - //Connected Components Labeling" (only for 8-connectivity) - //Federico Bolelli et. al. + //Parallel implementation of Spaghetti algorithm, described in "Spaghetti Labeling: Directed Acyclic Graphs + //for Block-Based Connected Components Labeling", IEEE Transactions on Image Processing, Federico Bolelli et. al. + //Parallelization method described in "Two More Strategies to Speed Up Connected Components Labeling Algorithms", + //Image Analysis and Processing - ICIAP 2017, Federico Bolelli et. al. + template + struct LabelingBolelliParallel { + + class FirstScan : public cv::ParallelLoopBody { + private: + const cv::Mat& img_; + cv::Mat& imgLabels_; + LabelT* P_; + int* chunksSizeAndLabels_; + + public: + FirstScan(const cv::Mat& img, cv::Mat& imgLabels, LabelT* P, int* chunksSizeAndLabels) + : img_(img), imgLabels_(imgLabels), P_(P), chunksSizeAndLabels_(chunksSizeAndLabels) {} + + FirstScan& operator=(const FirstScan&) { return *this; } + + void operator()(const cv::Range& range2) const CV_OVERRIDE + { + const Range range(range2.start * 2, std::min(range2.end * 2, img_.rows)); + + const int startR = range.start; + chunksSizeAndLabels_[startR] = range.end; + + LabelT label = stripeFirstLabel8Connectivity(startR, imgLabels_.cols); + + const LabelT firstLabel = label; + //const int h = img_.rows; + const int w = img_.cols; + const int stripe_h = range.end - range.start; + //const int limitLine = startR + 1; + + const int e_rows = stripe_h & -2; + const bool o_rows = stripe_h % 2 == 1; + //const int e_cols = w & -2; + //const bool o_cols = w % 2 == 1; + + { +#define CONDITION_B img_row_prev_prev[c-1]>0 +#define CONDITION_C img_row_prev_prev[c]>0 +#define CONDITION_D img_row_prev_prev[c+1]>0 +#define CONDITION_E img_row_prev_prev[c+2]>0 + +#define CONDITION_G img_row_prev[c-2]>0 +#define CONDITION_H img_row_prev[c-1]>0 +#define CONDITION_I img_row_prev[c]>0 +#define CONDITION_J img_row_prev[c+1]>0 +#define CONDITION_K img_row_prev[c+2]>0 + +#define CONDITION_M img_row[c-2]>0 +#define CONDITION_N img_row[c-1]>0 +#define CONDITION_O img_row[c]>0 +#define CONDITION_P img_row[c+1]>0 + +#define CONDITION_R img_row_fol[c-1]>0 +#define CONDITION_S img_row_fol[c]>0 +#define CONDITION_T img_row_fol[c+1]>0 + + // Action 1: No action +#define ACTION_1 img_labels_row[c] = 0; +// Action 2: New label (the block has foreground pixels and is not connected to anything else) +#define ACTION_2 img_labels_row[c] = label; \ + P_[label] = label; \ + label = label + 1; +//Action 3: Assign label of block P +#define ACTION_3 img_labels_row[c] = img_labels_row_prev_prev[c - 2]; +// Action 4: Assign label of block Q +#define ACTION_4 img_labels_row[c] = img_labels_row_prev_prev[c]; +// Action 5: Assign label of block R +#define ACTION_5 img_labels_row[c] = img_labels_row_prev_prev[c + 2]; +// Action 6: Assign label of block S +#define ACTION_6 img_labels_row[c] = img_labels_row[c - 2]; +// Action 7: Merge labels of block P and Q +#define ACTION_7 img_labels_row[c] = set_union(P_, img_labels_row_prev_prev[c - 2], img_labels_row_prev_prev[c]); +//Action 8: Merge labels of block P and R +#define ACTION_8 img_labels_row[c] = set_union(P_, img_labels_row_prev_prev[c - 2], img_labels_row_prev_prev[c + 2]); +// Action 9 Merge labels of block P and S +#define ACTION_9 img_labels_row[c] = set_union(P_, img_labels_row_prev_prev[c - 2], img_labels_row[c - 2]); +// Action 10 Merge labels of block Q and R +#define ACTION_10 img_labels_row[c] = set_union(P_, img_labels_row_prev_prev[c], img_labels_row_prev_prev[c + 2]); +// Action 11: Merge labels of block Q and S +#define ACTION_11 img_labels_row[c] = set_union(P_, img_labels_row_prev_prev[c], img_labels_row[c - 2]); +// Action 12: Merge labels of block R and S +#define ACTION_12 img_labels_row[c] = set_union(P_, img_labels_row_prev_prev[c + 2], img_labels_row[c - 2]); +// Action 13: Merge labels of block P, Q and R +#define ACTION_13 img_labels_row[c] = set_union(P_, set_union(P_, img_labels_row_prev_prev[c - 2], img_labels_row_prev_prev[c]), img_labels_row_prev_prev[c + 2]); +// Action 14: Merge labels of block P, Q and S +#define ACTION_14 img_labels_row[c] = set_union(P_, set_union(P_, img_labels_row_prev_prev[c - 2], img_labels_row_prev_prev[c]), img_labels_row[c - 2]); +//Action 15: Merge labels of block P, R and S +#define ACTION_15 img_labels_row[c] = set_union(P_, set_union(P_, img_labels_row_prev_prev[c - 2], img_labels_row_prev_prev[c + 2]), img_labels_row[c - 2]); +//Action 16: labels of block Q, R and S +#define ACTION_16 img_labels_row[c] = set_union(P_, set_union(P_, img_labels_row_prev_prev[c], img_labels_row_prev_prev[c + 2]), img_labels_row[c - 2]); + } + // The following Directed Rooted Acyclic Graphs (DAGs) allow to choose which action to + // perform, checking as few conditions as possible. Special DAGs are used for the first/last + // line of the image and for single line images. Actions: the blocks label are provisionally + // stored in the top left pixel of the block in the labels image. + if (stripe_h == 1) { + // Single line + const PixelT* const img_row = img_.ptr(startR); + LabelT* const img_labels_row = imgLabels_.ptr(startR); + int c = -2; +#include "ccl_bolelli_forest_singleline.inc.hpp" + } + else { + // More than one line + + // First couple of lines + { + const PixelT* const img_row = img_.ptr(startR); + const PixelT* const img_row_fol = (PixelT*)(((char*)img_row) + img_.step.p[0]); + LabelT* const img_labels_row = imgLabels_.ptr(startR); + int c = -2; +#include "ccl_bolelli_forest_firstline.inc.hpp" + } + + // Every other line but the last one if image has an odd number of rows + for (int r = startR + 2; r < startR + e_rows; r += 2) { + // Get rows pointer + const PixelT* const img_row = img_.ptr(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 img_labels_row = imgLabels_.ptr(r); + LabelT* const img_labels_row_prev_prev = (LabelT*)(((char*)img_labels_row) - imgLabels_.step.p[0] - imgLabels_.step.p[0]); + + int c = -2; + goto tree_0; + +#include "ccl_bolelli_forest.inc.hpp" + } + + // Last line (in case the rows are odd) + if (o_rows) { + const int r = startR + stripe_h - 1; + const PixelT* const img_row = img_.ptr(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]); + LabelT* const img_labels_row = imgLabels_.ptr(r); + LabelT* const img_labels_row_prev_prev = (LabelT*)(((char*)img_labels_row) - imgLabels_.step.p[0] - imgLabels_.step.p[0]); + int c = -2; +#include "ccl_bolelli_forest_lastline.inc.hpp" + } + } + + //write in the follower memory location + chunksSizeAndLabels_[startR + 1] = label - firstLabel; + + // undef conditions and actions + { +#undef ACTION_1 +#undef ACTION_2 +#undef ACTION_3 +#undef ACTION_4 +#undef ACTION_5 +#undef ACTION_6 +#undef ACTION_7 +#undef ACTION_8 +#undef ACTION_9 +#undef ACTION_10 +#undef ACTION_11 +#undef ACTION_12 +#undef ACTION_13 +#undef ACTION_14 +#undef ACTION_15 +#undef ACTION_16 + +#undef CONDITION_B +#undef CONDITION_C +#undef CONDITION_D +#undef CONDITION_E + +#undef CONDITION_G +#undef CONDITION_H +#undef CONDITION_I +#undef CONDITION_J +#undef CONDITION_K + +#undef CONDITION_M +#undef CONDITION_N +#undef CONDITION_O +#undef CONDITION_P + +#undef CONDITION_R +#undef CONDITION_S +#undef CONDITION_T + } + } + }; + + class SecondScan : public cv::ParallelLoopBody { + private: + const cv::Mat& img_; + cv::Mat& imgLabels_; + LabelT* P_; + StatsOp& sop_; + StatsOp* sopArray_; + LabelT& nLabels_; + + public: + SecondScan(const cv::Mat& img, cv::Mat& imgLabels, LabelT* P, StatsOp& sop, StatsOp* sopArray, LabelT& nLabels) + : img_(img), imgLabels_(imgLabels), P_(P), sop_(sop), sopArray_(sopArray), nLabels_(nLabels) {} + + void operator()(const cv::Range& range2) const CV_OVERRIDE + { + const Range range(range2.start * 2, std::min(range2.end * 2, img_.rows)); + int r = range.start; + + const int rowBegin = r; + const int rowEnd = range.end; + + if (rowBegin > 0) { + sopArray_[rowBegin].initElement(nLabels_); + sopArray_[rowBegin].setNextLoc(rowEnd); //_nextLoc = rowEnd; + + if (imgLabels_.rows & 1) { + if (imgLabels_.cols & 1) { + //Case 1: both rows and cols odd + for (; r < rowEnd; r += 2) { + // Get rows pointer + const PixelT* const img_row = img_.ptr(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]); + // Get rows pointer + for (int c = 0; c < imgLabels_.cols; c += 2) { + LabelT iLabel = imgLabels_row[c]; + if (iLabel > 0) { + iLabel = P_[iLabel]; + if (img_row[c] > 0) { + imgLabels_row[c] = iLabel; + sopArray_[rowBegin](r, c, iLabel); + } + else { + imgLabels_row[c] = 0; + sopArray_[rowBegin](r, c, 0); + } + if (c + 1 < imgLabels_.cols) { + if (img_row[c + 1] > 0) { + imgLabels_row[c + 1] = iLabel; + sopArray_[rowBegin](r, c + 1, iLabel); + } + else { + imgLabels_row[c + 1] = 0; + sopArray_[rowBegin](r, c + 1, 0); + } + if (r + 1 < imgLabels_.rows) { + if (img_row_fol[c] > 0) { + imgLabels_row_fol[c] = iLabel; + sopArray_[rowBegin](r + 1, c, iLabel); + } + else { + imgLabels_row_fol[c] = 0; + sopArray_[rowBegin](r + 1, c, 0); + } + if (img_row_fol[c + 1] > 0) { + imgLabels_row_fol[c + 1] = iLabel; + sopArray_[rowBegin](r + 1, c + 1, iLabel); + } + else { + imgLabels_row_fol[c + 1] = 0; + sopArray_[rowBegin](r + 1, c + 1, 0); + } + } + } + else if (r + 1 < imgLabels_.rows) { + if (img_row_fol[c] > 0) { + imgLabels_row_fol[c] = iLabel; + sopArray_[rowBegin](r + 1, c, iLabel); + } + else { + imgLabels_row_fol[c] = 0; + sopArray_[rowBegin](r + 1, c, 0); + } + } + } + else { + imgLabels_row[c] = 0; + sopArray_[rowBegin](r, c, 0); + if (c + 1 < imgLabels_.cols) { + imgLabels_row[c + 1] = 0; + sopArray_[rowBegin](r, c + 1, 0); + if (r + 1 < imgLabels_.rows) { + imgLabels_row_fol[c] = 0; + imgLabels_row_fol[c + 1] = 0; + sopArray_[rowBegin](r + 1, c, 0); + sopArray_[rowBegin](r + 1, c + 1, 0); + } + } + else if (r + 1 < imgLabels_.rows) { + imgLabels_row_fol[c] = 0; + sopArray_[rowBegin](r + 1, c, 0); + } + } + } + } + }//END Case 1 + else { + //Case 2: only rows odd + for (; r < rowEnd; r += 2) { + // Get rows pointer + const PixelT* const img_row = img_.ptr(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]); + // Get rows pointer + for (int c = 0; c < imgLabels_.cols; c += 2) { + LabelT iLabel = imgLabels_row[c]; + if (iLabel > 0) { + iLabel = P_[iLabel]; + if (img_row[c] > 0) { + imgLabels_row[c] = iLabel; + sopArray_[rowBegin](r, c, iLabel); + } + else { + imgLabels_row[c] = 0; + sopArray_[rowBegin](r, c, 0); + } + if (img_row[c + 1] > 0) { + imgLabels_row[c + 1] = iLabel; + sopArray_[rowBegin](r, c + 1, iLabel); + } + else { + imgLabels_row[c + 1] = 0; + sopArray_[rowBegin](r, c + 1, 0); + } + if (r + 1 < imgLabels_.rows) { + if (img_row_fol[c] > 0) { + imgLabels_row_fol[c] = iLabel; + sopArray_[rowBegin](r + 1, c, iLabel); + } + else { + imgLabels_row_fol[c] = 0; + sopArray_[rowBegin](r + 1, c, 0); + } + if (img_row_fol[c + 1] > 0) { + imgLabels_row_fol[c + 1] = iLabel; + sopArray_[rowBegin](r + 1, c + 1, iLabel); + } + else { + imgLabels_row_fol[c + 1] = 0; + sopArray_[rowBegin](r + 1, c + 1, 0); + } + } + } + else { + imgLabels_row[c] = 0; + imgLabels_row[c + 1] = 0; + sopArray_[rowBegin](r, c, 0); + sopArray_[rowBegin](r, c + 1, 0); + if (r + 1 < imgLabels_.rows) { + imgLabels_row_fol[c] = 0; + imgLabels_row_fol[c + 1] = 0; + sopArray_[rowBegin](r + 1, c, 0); + sopArray_[rowBegin](r + 1, c + 1, 0); + } + } + } + } + }// END Case 2 + } + else { + if (imgLabels_.cols & 1) { + //Case 3: only cols odd + for (; r < rowEnd; r += 2) { + // Get rows pointer + const PixelT* const img_row = img_.ptr(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]); + // Get rows pointer + for (int c = 0; c < imgLabels_.cols; c += 2) { + LabelT iLabel = imgLabels_row[c]; + if (iLabel > 0) { + iLabel = P_[iLabel]; + if (img_row[c] > 0) { + imgLabels_row[c] = iLabel; + sopArray_[rowBegin](r, c, iLabel); + } + else { + imgLabels_row[c] = 0; + sopArray_[rowBegin](r, c, 0); + } + if (img_row_fol[c] > 0) { + imgLabels_row_fol[c] = iLabel; + sopArray_[rowBegin](r + 1, c, iLabel); + } + else { + imgLabels_row_fol[c] = 0; + sopArray_[rowBegin](r + 1, c, 0); + } + if (c + 1 < imgLabels_.cols) { + if (img_row[c + 1] > 0) { + imgLabels_row[c + 1] = iLabel; + sopArray_[rowBegin](r, c + 1, iLabel); + } + else { + imgLabels_row[c + 1] = 0; + sopArray_[rowBegin](r, c + 1, 0); + } + if (img_row_fol[c + 1] > 0) { + imgLabels_row_fol[c + 1] = iLabel; + sopArray_[rowBegin](r + 1, c + 1, iLabel); + } + else { + imgLabels_row_fol[c + 1] = 0; + sopArray_[rowBegin](r + 1, c + 1, 0); + } + } + } + else { + imgLabels_row[c] = 0; + imgLabels_row_fol[c] = 0; + sopArray_[rowBegin](r, c, 0); + sopArray_[rowBegin](r + 1, c, 0); + if (c + 1 < imgLabels_.cols) { + imgLabels_row[c + 1] = 0; + imgLabels_row_fol[c + 1] = 0; + sopArray_[rowBegin](r, c + 1, 0); + sopArray_[rowBegin](r + 1, c + 1, 0); + } + } + } + } + }// END case 3 + else { + //Case 4: nothing odd + for (; r < rowEnd; r += 2) { + // Get rows pointer + const PixelT* const img_row = img_.ptr(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]); + // Get rows pointer + for (int c = 0; c < imgLabels_.cols; c += 2) { + LabelT iLabel = imgLabels_row[c]; + if (iLabel > 0) { + iLabel = P_[iLabel]; + if (img_row[c] > 0) { + imgLabels_row[c] = iLabel; + sopArray_[rowBegin](r, c, iLabel); + } + else { + imgLabels_row[c] = 0; + sopArray_[rowBegin](r, c, 0); + } + if (img_row[c + 1] > 0) { + imgLabels_row[c + 1] = iLabel; + sopArray_[rowBegin](r, c + 1, iLabel); + } + else { + imgLabels_row[c + 1] = 0; + sopArray_[rowBegin](r, c + 1, 0); + } + if (img_row_fol[c] > 0) { + imgLabels_row_fol[c] = iLabel; + sopArray_[rowBegin](r + 1, c, iLabel); + } + else { + imgLabels_row_fol[c] = 0; + sopArray_[rowBegin](r + 1, c, 0); + } + if (img_row_fol[c + 1] > 0) { + imgLabels_row_fol[c + 1] = iLabel; + sopArray_[rowBegin](r + 1, c + 1, iLabel); + } + else { + imgLabels_row_fol[c + 1] = 0; + sopArray_[rowBegin](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; + sopArray_[rowBegin](r, c, 0); + sopArray_[rowBegin](r, c + 1, 0); + sopArray_[rowBegin](r + 1, c, 0); + sopArray_[rowBegin](r + 1, c + 1, 0); + } + } + }//END case 4 + } + } + } + else { + //the first thread uses sop in order to make less merges + sop_.setNextLoc(rowEnd); + if (imgLabels_.rows & 1) { + if (imgLabels_.cols & 1) { + //Case 1: both rows and cols odd + for (; r < rowEnd; r += 2) { + // Get rows pointer + const PixelT* const img_row = img_.ptr(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]); + // Get rows pointer + for (int c = 0; c < imgLabels_.cols; c += 2) { + LabelT iLabel = imgLabels_row[c]; + if (iLabel > 0) { + 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 < imgLabels_.cols) { + 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 + 1 < imgLabels_.rows) { + 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 if (r + 1 < imgLabels_.rows) { + 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); + } + } + } + else { + imgLabels_row[c] = 0; + sop_(r, c, 0); + if (c + 1 < imgLabels_.cols) { + imgLabels_row[c + 1] = 0; + sop_(r, c + 1, 0); + if (r + 1 < imgLabels_.rows) { + imgLabels_row_fol[c] = 0; + imgLabels_row_fol[c + 1] = 0; + sop_(r + 1, c, 0); + sop_(r + 1, c + 1, 0); + } + } + else if (r + 1 < imgLabels_.rows) { + imgLabels_row_fol[c] = 0; + sop_(r + 1, c, 0); + } + } + } + } + }//END Case 1 + else { + //Case 2: only rows odd + for (; r < rowEnd; r += 2) { + // Get rows pointer + const PixelT* const img_row = img_.ptr(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]); + // Get rows pointer + for (int c = 0; c < imgLabels_.cols; c += 2) { + LabelT iLabel = imgLabels_row[c]; + if (iLabel > 0) { + 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 + 1 < imgLabels_.rows) { + 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; + sop_(r, c, 0); + sop_(r, c + 1, 0); + if (r + 1 < imgLabels_.rows) { + imgLabels_row_fol[c] = 0; + imgLabels_row_fol[c + 1] = 0; + sop_(r + 1, c, 0); + sop_(r + 1, c + 1, 0); + } + } + } + } + }// END Case 2 + } + else { + if (imgLabels_.cols & 1) { + //Case 3: only cols odd + for (; r < rowEnd; r += 2) { + // Get rows pointer + const PixelT* const img_row = img_.ptr(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]); + // Get rows pointer + for (int c = 0; c < imgLabels_.cols; c += 2) { + LabelT iLabel = imgLabels_row[c]; + if (iLabel > 0) { + 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 + 1 < imgLabels_.cols) { + 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 + 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 < imgLabels_.cols) { + imgLabels_row[c + 1] = 0; + imgLabels_row_fol[c + 1] = 0; + sop_(r, c + 1, 0); + sop_(r + 1, c + 1, 0); + } + } + } + } + }// END case 3 + else { + //Case 4: nothing odd + for (; r < rowEnd; r += 2) { + // Get rows pointer + const PixelT* const img_row = img_.ptr(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]); + // Get rows pointer + for (int c = 0; c < imgLabels_.cols; c += 2) { + LabelT iLabel = imgLabels_row[c]; + if (iLabel > 0) { + 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 + } + } + } + } + }; + + inline static + void mergeLabels(const cv::Mat& img, cv::Mat& imgLabels, LabelT* P, int* chunksSizeAndLabels) { + + // Merge Mask + // +---+---+---+ + // |P -|Q -|R -| + // |- -|- -|- -| + // +---+---+---+ + // |X -| + // |- -| + // +---+ + const int w = imgLabels.cols, h = imgLabels.rows; + + for (int r = chunksSizeAndLabels[0]; r < h; r = chunksSizeAndLabels[r]) { + + 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]); + const PixelT* const img_row = img.ptr(r); + const PixelT* const img_row_prev = (PixelT*)(((char*)img_row) - img.step.p[0]); + + for (int c = 0; c < w; c += 2) { + +#define condition_x imgLabels_row[c] > 0 +#define condition_pppr c > 1 && imgLabels_row_prev_prev[c - 2] > 0 +#define condition_qppr imgLabels_row_prev_prev[c] > 0 +#define condition_qppr1 c < w - 1 +#define condition_qppr2 c < w +#define condition_rppr c < w - 2 && imgLabels_row_prev_prev[c + 2] > 0 + + if (condition_x) { + if (condition_pppr) { + //check in img + if (img_row[c] > 0 && img_row_prev[c - 1] > 0) + //assign the same label + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c - 2], imgLabels_row[c]); + } + if (condition_qppr) { + if (condition_qppr1) { + if ((img_row[c] > 0 && img_row_prev[c] > 0) || (img_row[c + 1] > 0 && img_row_prev[c] > 0) || + (img_row[c] > 0 && img_row_prev[c + 1] > 0) || (img_row[c + 1] > 0 && img_row_prev[c + 1] > 0)) { + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c]); + } + } + else /*if (condition_qppr2)*/ { + if (img_row[c] > 0 && img_row_prev[c] > 0) + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c]); + } + } + if (condition_rppr) { + if (img_row[c + 1] > 0 && img_row_prev[c + 2] > 0) + imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c]); + } + } + +#undef condition_x +#undef condition_pppr +#undef condition_qppr +#undef condition_qppr1 +#undef condition_qppr2 +#undef condition_rppr + + } + } + } + + 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); + + const int h = img.rows; + const int w = img.cols; + + //A quick and dirty upper bound for the maximum number of labels. + //Following formula comes from the fact that a 2x2 block in 8-connectivity case + //can never have more than 1 new label and 1 label for background. + //Worst case image example pattern: + //1 0 1 0 1... + //0 0 0 0 0... + //1 0 1 0 1... + //............ + const size_t Plength = size_t(((h + 1) / 2) * size_t((w + 1) / 2)) + 1; + + //Array used to store info and labeled pixel by each thread. + //Different threads affect different memory location of chunksSizeAndLabels + const int chunksSizeAndLabelsSize = roundUp(h, 2); + std::vector chunksSizeAndLabels(chunksSizeAndLabelsSize); + + //Tree of labels + std::vector P(Plength, 0); + //First label is for background + //P[0] = 0; + + cv::Range range2(0, divUp(h, 2)); + const double nParallelStripes = std::max(1, std::min(h / 2, getNumThreads() * 4)); + + //First scan + cv::parallel_for_(range2, FirstScan(img, imgLabels, P.data(), chunksSizeAndLabels.data()), nParallelStripes); + + //merge labels of different chunks + mergeLabels(img, imgLabels, P.data(), chunksSizeAndLabels.data()); + + LabelT nLabels = 1; + for (int i = 0; i < h; i = chunksSizeAndLabels[i]) { + CV_DbgAssert(i + 1 < chunksSizeAndLabelsSize); + flattenL(P.data(), stripeFirstLabel8Connectivity(i, w), chunksSizeAndLabels[i + 1], nLabels); + } + + //Array for statistics data + std::vector sopArray(h); + sop.init(nLabels); + + //Second scan + cv::parallel_for_(range2, SecondScan(img, imgLabels, P.data(), sop, sopArray.data(), nLabels), nParallelStripes); + + StatsOp::mergeStats(imgLabels, sopArray.data(), sop, nLabels); + sop.finish(); + + return nLabels; + } + };//End struct LabelingBolelliParallel + + //Implementation of Spaghetti algorithm, as described in "Spaghetti Labeling: Directed Acyclic Graphs + //for Block-Based Connected Components Labeling", IEEE Transactions on Image Processing, Federico Bolelli et. al. template struct LabelingBolelli { @@ -644,9 +1533,440 @@ namespace cv{ }//End function LabelingBolelli operator() };//End struct LabelingBolelli + //Parallel implementation of Spaghetti algorithm for 4-way connectivity, generated with the tool described in "One DAG to + //Rule Them All", IEEE Transactions on Pattern Analysis and Machine Intelligence, Federico Bolelli et. al. + //Parallelization method described in "Two More Strategies to Speed Up Connected Components Labeling Algorithms", + //Image Analysis and Processing - ICIAP 2017, Federico Bolelli et. al. + template + struct LabelingBolelli4CParallel { + + class FirstScan : public cv::ParallelLoopBody { + const cv::Mat& img_; + cv::Mat& imgLabels_; + LabelT* P_; + int* chunksSizeAndLabels_; + + public: + FirstScan(const cv::Mat& img, cv::Mat& imgLabels, LabelT* P, int* chunksSizeAndLabels) + : img_(img), imgLabels_(imgLabels), P_(P), chunksSizeAndLabels_(chunksSizeAndLabels) {} + + FirstScan& operator=(const FirstScan&) { return *this; } + + void operator()(const cv::Range& range2) const CV_OVERRIDE + { + const Range range(range2.start * 2, std::min(range2.end * 2, img_.rows)); + int r = range.start; + + chunksSizeAndLabels_[r] = range.end; + + LabelT label = stripeFirstLabel4Connectivity(r, imgLabels_.cols); + + const LabelT firstLabel = label; + const int w = img_.cols; + const int startR = r; + + { +#define CONDITION_Q img_row_prev[c] > 0 +#define CONDITION_S img_row[c - 1] > 0 +#define CONDITION_X img_row[c] > 0 + +#define ACTION_1 // nothing to do +#define ACTION_2 img_labels_row[c] = label; \ + P_[label] = label; \ + label = label + 1; +#define ACTION_3 img_labels_row[c] = img_labels_row_prev[c]; // x <- q +#define ACTION_4 img_labels_row[c] = img_labels_row[c - 1]; // x <- s +#define ACTION_5 img_labels_row[c] = set_union(P_, img_labels_row_prev[c], img_labels_row[c - 1]); // x <- q + s + } + + // First row + { + const PixelT* const img_row = img_.ptr(r); + LabelT* const img_labels_row = imgLabels_.ptr(r); + int c = -1; + + goto fl_tree_0; + fl_tree_0: if ((c += 1) >= w) goto fl_break; + if (CONDITION_X) { + ACTION_2 + goto fl_tree_1; + } + else { + ACTION_1 + goto fl_tree_0; + } + fl_tree_1: if ((c += 1) >= w) goto fl_break; + if (CONDITION_X) { + ACTION_4 + goto fl_tree_1; + } + else { + ACTION_1 + goto fl_tree_0; + } + fl_break:; + } + + // Other rows + ++r; + for (; r < range.end; ++r) { + // Get row pointers + const PixelT* const img_row = img_.ptr(r); + const PixelT* const img_row_prev = (PixelT*)(((char*)img_row) - img_.step.p[0]); + LabelT* const img_labels_row = imgLabels_.ptr(r); + LabelT* const img_labels_row_prev = (LabelT*)(((char*)img_labels_row) - imgLabels_.step.p[0]); + int c = -1; + + goto cl_tree_0; + cl_tree_0: if ((c += 1) >= w) goto cl_break; + if (CONDITION_X) { + if (CONDITION_Q) { + ACTION_3 + goto cl_tree_1; + } + else { + ACTION_2 + goto cl_tree_1; + } + } + else { + ACTION_1 + goto cl_tree_0; + } + cl_tree_1: if ((c += 1) >= w) goto cl_break; + if (CONDITION_X) { + if (CONDITION_Q) { + ACTION_5 + goto cl_tree_1; + } + else { + ACTION_4 + goto cl_tree_1; + } + } + else { + ACTION_1 + goto cl_tree_0; + } + cl_break:; + } + + // undef conditions and actions + { +#undef ACTION_1 +#undef ACTION_2 +#undef ACTION_3 +#undef ACTION_4 +#undef ACTION_5 + +#undef CONDITION_Q +#undef CONDITION_S +#undef CONDITION_X + } + + //write in the following memory location + chunksSizeAndLabels_[startR + 1] = label - firstLabel; + } + }; + + class SecondScan : public cv::ParallelLoopBody { + cv::Mat& imgLabels_; + const LabelT* P_; + StatsOp& sop_; + StatsOp* sopArray_; + LabelT& nLabels_; + public: + SecondScan(cv::Mat& imgLabels, const LabelT* P, StatsOp& sop, StatsOp* sopArray, LabelT& nLabels) + : imgLabels_(imgLabels), P_(P), sop_(sop), sopArray_(sopArray), nLabels_(nLabels) {} + + SecondScan& operator=(const SecondScan&) { return *this; } + + void operator()(const cv::Range& range2) const CV_OVERRIDE + { + const Range range(range2.start * 2, std::min(range2.end * 2, imgLabels_.rows)); + int r = range.start; + const int rowBegin = r; + const int rowEnd = range.end; + + if (rowBegin > 0) { + sopArray_[rowBegin].initElement(nLabels_); + sopArray_[rowBegin].setNextLoc(rowEnd); //_nextLoc = rowEnd; + + for (; r < rowEnd; ++r) { + LabelT* img_row_start = imgLabels_.ptr(r); + LabelT* const img_row_end = img_row_start + imgLabels_.cols; + for (int c = 0; img_row_start != img_row_end; ++img_row_start, ++c) { + *img_row_start = P_[*img_row_start]; + sopArray_[rowBegin](r, c, *img_row_start); + } + } + } + else { + //the first thread uses sop in order to make less merges + sop_.setNextLoc(rowEnd); + for (; r < rowEnd; ++r) { + LabelT* img_row_start = imgLabels_.ptr(r); + LabelT* const img_row_end = img_row_start + imgLabels_.cols; + for (int c = 0; img_row_start != img_row_end; ++img_row_start, ++c) { + *img_row_start = P_[*img_row_start]; + sop_(r, c, *img_row_start); + } + } + } + } + }; + + inline static + void mergeLabels(cv::Mat& imgLabels, LabelT* P, const int* chunksSizeAndLabels) { + + // Merge Mask + // +-+-+-+ + // |-|q|-| + // +-+-+-+ + // |x| + // +-+ + const int w = imgLabels.cols, h = imgLabels.rows; + + for (int r = chunksSizeAndLabels[0]; r < h; r = chunksSizeAndLabels[r]) { + + LabelT* const imgLabels_row = imgLabels.ptr(r); + LabelT* const imgLabels_row_prev = (LabelT*)(((char*)imgLabels_row) - imgLabels.step.p[0]); + + for (int c = 0; c < w; ++c) { + +#define condition_q imgLabels_row_prev[c] > 0 +#define condition_x imgLabels_row[c] > 0 + + if (condition_x) { + if (condition_q) { + //merge of two label + imgLabels_row[c] = set_union(P, imgLabels_row_prev[c], imgLabels_row[c]); + } + } + } + } +#undef condition_q +#undef condition_x + } + + 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 == 4); + + const int h = img.rows; + const int w = img.cols; + + //A quick and dirty upper bound for the maximum number of labels. + //Following formula comes from the fact that a 2x2 block in 4-way connectivity + //labeling can never have more than 2 new labels and 1 label for background. + //Worst case image example pattern: + //1 0 1 0 1... + //0 1 0 1 0... + //1 0 1 0 1... + //............ + const size_t Plength = (size_t(h) * size_t(w) + 1) / 2 + 1; + + //Array used to store info and labeled pixel by each thread. + //Different threads affect different memory location of chunksSizeAndLabels + std::vector chunksSizeAndLabels(roundUp(h, 2)); + + //Tree of labels + std::vector P_(Plength, 0); + LabelT* P = P_.data(); + //First label is for background + //P[0] = 0; + + cv::Range range2(0, divUp(h, 2)); + const double nParallelStripes = std::max(1, std::min(h / 2, getNumThreads() * 4)); + + LabelT nLabels = 1; + + //First scan + cv::parallel_for_(range2, FirstScan(img, imgLabels, P, chunksSizeAndLabels.data()), nParallelStripes); + + //merge labels of different chunks + mergeLabels(imgLabels, P, chunksSizeAndLabels.data()); + + for (int i = 0; i < h; i = chunksSizeAndLabels[i]) { + flattenL(P, stripeFirstLabel4Connectivity(i, w), chunksSizeAndLabels[i + 1], nLabels); + } + + //Array for statistics dataof threads + std::vector sopArray(h); + + sop.init(nLabels); + //Second scan + cv::parallel_for_(range2, SecondScan(imgLabels, P, sop, sopArray.data(), nLabels), nParallelStripes); + StatsOp::mergeStats(imgLabels, sopArray.data(), sop, nLabels); + sop.finish(); + + return nLabels; + } + };//End struct LabelingBolelli4CParallel + + //Implementation of Spaghetti algorithm for 4-way connectivity, generated with the tool described in "One DAG to + //Rule Them All", IEEE Transactions on Pattern Analysis and Machine Intelligence, Federico Bolelli et. al. + template + struct LabelingBolelli4C + { + 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 == 4); + + const int h = img.rows; + const int w = img.cols; + + // A quick and dirty upper bound for the maximum number of labels. + // Following formula comes from the fact that a 2x2 block in 4-connectivity case + // can never have more than 2 new labels and 1 label for background. + // Worst case image example pattern: + // 1 0 1 0 1... + // 0 1 0 1 0... + // 1 0 1 0 1... + // ............ + const size_t Plength = size_t((size_t(h) * size_t(w) + 1) / 2) + 1; + + std::vector P_(Plength, 0); + LabelT* P = P_.data(); + //P[0] = 0; + LabelT lunique = 1; + + // First scan + + // We work with the 4-conn Rosenfeld mask + // +-+ + // |q| + // +-+-+ + // |s|x| + // +-+-+ + + // A bunch of defines is used to check if the pixels are foreground + // and to define actions to be performed + { + +#define CONDITION_Q img_row_prev[c] > 0 +#define CONDITION_S img_row[c - 1] > 0 +#define CONDITION_X img_row[c] > 0 + +#define ACTION_1 // nothing to do +#define ACTION_2 img_labels_row[c] = lunique; \ + P[lunique] = lunique; \ + lunique = lunique + 1; // new label +#define ACTION_3 img_labels_row[c] = img_labels_row_prev[c]; // x <- q +#define ACTION_4 img_labels_row[c] = img_labels_row[c - 1]; // x <- s +#define ACTION_5 img_labels_row[c] = set_union(P, img_labels_row_prev[c], img_labels_row[c - 1]); // x <- q + s + } + + // First row + { + const PixelT* const img_row = img.ptr(0); + LabelT* const img_labels_row = imgLabels.ptr(0); + int c = -1; + + goto fl_tree_0; + fl_tree_0: if ((c += 1) >= w) goto fl_break; + if (CONDITION_X) { + ACTION_2 + goto fl_tree_1; + } + else { + ACTION_1 + goto fl_tree_0; + } + fl_tree_1: if ((c += 1) >= w) goto fl_break; + if (CONDITION_X) { + ACTION_4 + goto fl_tree_1; + } + else { + ACTION_1 + goto fl_tree_0; + } + fl_break:; + } + + + // Other rows + for (int r = 1; r < h; ++r) { + // Get row pointers + const PixelT* const img_row = img.ptr(r); + const PixelT* const img_row_prev = (PixelT*)(((char*)img_row) - img.step.p[0]); + LabelT* const img_labels_row = imgLabels.ptr(r); + LabelT* const img_labels_row_prev = (LabelT*)(((char*)img_labels_row) - imgLabels.step.p[0]); + int c = -1; + + goto cl_tree_0; + cl_tree_0: if ((c += 1) >= w) goto cl_break; + if (CONDITION_X) { + if (CONDITION_Q) { + ACTION_3 + goto cl_tree_1; + } + else { + ACTION_2 + goto cl_tree_1; + } + } + else { + ACTION_1 + goto cl_tree_0; + } + cl_tree_1: if ((c += 1) >= w) goto cl_break; + if (CONDITION_X) { + if (CONDITION_Q) { + ACTION_5 + goto cl_tree_1; + } + else { + ACTION_4 + goto cl_tree_1; + } + } + else { + ACTION_1 + goto cl_tree_0; + } + cl_break:; + } + + // undef conditions and actions + { +#undef ACTION_1 +#undef ACTION_2 +#undef ACTION_3 +#undef ACTION_4 +#undef ACTION_5 + +#undef CONDITION_Q +#undef CONDITION_S +#undef CONDITION_X + } + + // Second scan + analysis + LabelT nLabels = flattenL(P, lunique); + sop.init(nLabels); + + for (int r = 0; r < h; ++r) { + LabelT* img_row_start = imgLabels.ptr(r); + LabelT* const img_row_end = img_row_start + w; + for (int c = 0; img_row_start != img_row_end; ++img_row_start, ++c) { + *img_row_start = P[*img_row_start]; + sop(r, c, *img_row_start); + } + } + + sop.finish(); + + return nLabels; + + }//End function LabelingBolelli4C operator() + };//End struct LabelingBolelli4C + //Parallel implementation of Scan Array-based Union Find (SAUF) algorithm, as described in "Two More Strategies to Speed - //Up Connected Components Labeling Algorithms" - //Federico Bolelli et. al. + //Up Connected Components Labeling Algorithms", Image Analysis and Processing - ICIAP 2017, Federico Bolelli et. al. template struct LabelingWuParallel{ @@ -1029,8 +2349,7 @@ namespace cv{ };//End struct LabelingWuParallel //Based on "Two Strategies to Speed up Connected Components Algorithms", the SAUF (Scan Array-based Union Find) variant - //using decision trees - //Kesheng Wu et. al. + //using decision trees, Kesheng Wu et. al. template struct LabelingWu{ LabelT operator()(const cv::Mat& img, cv::Mat& imgLabels, int connectivity, StatsOp& sop){ @@ -1199,8 +2518,7 @@ namespace cv{ };//End struct LabelingWu //Parallel implementation of BBDT (Block-Based with Decision Tree) algorithm, as described in "Two More Strategies to Speed - //Up Connected Components Labeling Algorithms" - //Federico Bolelli et. al. + //Up Connected Components Labeling Algorithms", Image Analysis and Processing - ICIAP 2017, Federico Bolelli et. al. template struct LabelingGranaParallel{ @@ -2960,8 +4278,7 @@ namespace cv{ };//End struct LabelingGranaParallel //Implementation of BBDT (Block-Based with Decision Tree) algorithm, as described in "Optimized Block-based Connected - //Components Labeling with Decision Trees" (only for 8-connectivity) - //Costantino Grana et. al. + //Components Labeling with Decision Trees", IEEE Transactions on Image Processing, Costantino Grana et. al. template struct LabelingGrana{ LabelT operator()(const cv::Mat& img, cv::Mat& imgLabels, int connectivity, StatsOp& sop){ @@ -4317,7 +5634,7 @@ namespace cv{ //Run parallel labeling only if the rows of the image are at least twice the number of available threads const bool is_parallel = currentParallelFramework != NULL && nThreads > 1 && L.rows / nThreads >= 2; - if (ccltype == CCL_SAUF || ccltype == CCL_WU || connectivity == 4){ + if (ccltype == CCL_SAUF || ccltype == CCL_WU || ((ccltype == CCL_BBDT || ccltype == CCL_GRANA) && connectivity == 4)){ // SAUF algorithm is used using connectedcomponents::LabelingWu; using connectedcomponents::LabelingWuParallel; @@ -4337,7 +5654,7 @@ namespace cv{ return (int)LabelingWuParallel()(I, L, connectivity, sop); } } - else if ((ccltype == CCL_BBDT || ccltype == CCL_GRANA || ccltype == CCL_DEFAULT) && connectivity == 8){ + else if ((ccltype == CCL_BBDT || ccltype == CCL_GRANA) && connectivity == 8){ // BBDT algorithm is used using connectedcomponents::LabelingGrana; using connectedcomponents::LabelingGranaParallel; @@ -4357,21 +5674,45 @@ namespace cv{ return (int)LabelingGranaParallel()(I, L, connectivity, sop); } } - else if ((ccltype == CCL_SPAGHETTI || ccltype == CCL_BOLELLI) && connectivity == 8) { + else if (ccltype == CCL_SPAGHETTI || ccltype == CCL_BOLELLI || ccltype == CCL_DEFAULT) { // Spaghetti algorithm is used - using connectedcomponents::LabelingBolelli; - //using connectedcomponents::LabelingBolelliParallel; // Not implemented - //warn if L's depth is not sufficient? - if (lDepth == CV_8U) { - //Not supported yet - } - else if (lDepth == CV_16U) { - return (int)LabelingBolelli()(I, L, connectivity, sop); + if (connectivity == 8) { + using connectedcomponents::LabelingBolelli; + using connectedcomponents::LabelingBolelliParallel; + //warn if L's depth is not sufficient? + if (lDepth == CV_8U) { + //Not supported yet + } + else if (lDepth == CV_16U) { + return (int)LabelingBolelli()(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 + if (!is_parallel) + return (int)LabelingBolelli()(I, L, connectivity, sop); + else + return (int)LabelingBolelliParallel()(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)LabelingBolelli()(I, L, connectivity, sop); + else { + using connectedcomponents::LabelingBolelli4C; + using connectedcomponents::LabelingBolelli4CParallel; + //warn if L's depth is not sufficient? + if (lDepth == CV_8U) { + //Not supported yet + } + else if (lDepth == CV_16U) { + return (int)LabelingBolelli4C()(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 + if (!is_parallel) + return (int)LabelingBolelli4C()(I, L, connectivity, sop); + else + return (int)LabelingBolelli4CParallel()(I, L, connectivity, sop); + } } } diff --git a/modules/imgproc/test/test_connectedcomponents.cpp b/modules/imgproc/test/test_connectedcomponents.cpp index e57c24a937ca8238ef070a2ad811f01be7e6e8e7..ed11ea6fdad14c01c5c3516feb89c0cfdbf468ba 100644 --- a/modules/imgproc/test/test_connectedcomponents.cpp +++ b/modules/imgproc/test/test_connectedcomponents.cpp @@ -42,7 +42,8 @@ #include "test_precomp.hpp" -namespace opencv_test { namespace { +namespace opencv_test { +namespace { class CV_ConnectedComponentsTest : public cvtest::BaseTest { @@ -61,10 +62,10 @@ void normalizeLabels(Mat1i& imgLabels, int iNumLabels) { vector vecNewLabels(iNumLabels + 1, 0); int iMaxNewLabel = 0; - for (int r = 0; r0) { + if (iCurLabel > 0) { if (vecNewLabels[iCurLabel] == 0) { vecNewLabels[iCurLabel] = ++iMaxNewLabel; } @@ -74,7 +75,7 @@ void normalizeLabels(Mat1i& imgLabels, int iNumLabels) { } } -void CV_ConnectedComponentsTest::run( int /* start_from */) +void CV_ConnectedComponentsTest::run(int /* start_from */) { int ccltype[] = { cv::CCL_DEFAULT, cv::CCL_WU, cv::CCL_GRANA, cv::CCL_BOLELLI, cv::CCL_SAUF, cv::CCL_BBDT, cv::CCL_SPAGHETTI }; @@ -91,7 +92,7 @@ void CV_ConnectedComponentsTest::run( int /* start_from */) Mat bw = orig > 128; - for (uint cclt = 0; cclt < sizeof(ccltype)/sizeof(int); ++cclt) + for (uint cclt = 0; cclt < sizeof(ccltype) / sizeof(int); ++cclt) { Mat1i labelImage; @@ -100,11 +101,11 @@ void CV_ConnectedComponentsTest::run( int /* start_from */) normalizeLabels(labelImage, nLabels); // Validate test results - for (int r = 0; r < labelImage.rows; ++r){ - for (int c = 0; c < labelImage.cols; ++c){ + 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){ + if (!pass) { ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT); return; } @@ -166,12 +167,12 @@ static cv::Mat createCrashMat(int numThreads) { for (int s = stripeRange.start; s < stripeRange.end; s++) { cv::Range sr(s, s + 1); cv::Range r; - r.start = (int) (wholeRange.start + - ((uint64) sr.start * (wholeRange.end - wholeRange.start) + nstripes / 2) / nstripes); + r.start = (int)(wholeRange.start + + ((uint64)sr.start * (wholeRange.end - wholeRange.start) + nstripes / 2) / nstripes); r.end = sr.end >= nstripes ? - wholeRange.end : - (int) (wholeRange.start + - ((uint64) sr.end * (wholeRange.end - wholeRange.start) + nstripes / 2) / nstripes); + wholeRange.end : + (int)(wholeRange.start + + ((uint64)sr.end * (wholeRange.end - wholeRange.start) + nstripes / 2) / nstripes); if (r.start > 0 && r.start % 2 == 1 && r.end % 2 == 0 && r.end >= r.start + 2) { bugRange = r; @@ -203,7 +204,7 @@ static cv::Mat createCrashMat(int numThreads) { TEST(Imgproc_ConnectedComponents, parallel_wu_labels) { cv::Mat mat = createCrashMat(cv::getNumThreads()); - if(mat.empty()) { + if (mat.empty()) { return; } @@ -213,10 +214,10 @@ TEST(Imgproc_ConnectedComponents, parallel_wu_labels) cv::Mat stats; cv::Mat centroids; int nb = 0; - EXPECT_NO_THROW( nb = cv::connectedComponentsWithStats(mat, labels, stats, centroids, 8, CV_32S, cv::CCL_WU) ); + EXPECT_NO_THROW(nb = cv::connectedComponentsWithStats(mat, labels, stats, centroids, 8, CV_32S, cv::CCL_WU)); int area = 0; - for(int i=1; i(i, cv::CC_STAT_AREA); } @@ -229,7 +230,7 @@ TEST(Imgproc_ConnectedComponents, missing_background_pixels) cv::Mat labels; cv::Mat stats; cv::Mat centroids; - EXPECT_NO_THROW(cv::connectedComponentsWithStats(m, labels, stats, centroids, 8, CV_32S, cv::CCL_WU) ); + EXPECT_NO_THROW(cv::connectedComponentsWithStats(m, labels, stats, centroids, 8, CV_32S, cv::CCL_WU)); EXPECT_EQ(stats.at(0, cv::CC_STAT_WIDTH), 0); EXPECT_EQ(stats.at(0, cv::CC_STAT_HEIGHT), 0); EXPECT_EQ(stats.at(0, cv::CC_STAT_LEFT), -1); @@ -241,21 +242,21 @@ TEST(Imgproc_ConnectedComponents, spaghetti_bbdt_sauf_stats) { cv::Mat1b img(16, 16); img << 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, - 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, - 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, - 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, - 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1; + 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1; cv::Mat1i labels; cv::Mat1i stats; @@ -357,4 +358,436 @@ TEST(Imgproc_ConnectedComponents, spaghetti_bbdt_sauf_stats) } } -}} // namespace +TEST(Imgproc_ConnectedComponents, chessboard_even) +{ + cv::Size size(16, 16); + cv::Mat1b input(size); + cv::Mat1i output_8c(size); + cv::Mat1i output_4c(size); + + // Chessboard image with even number of rows and cols + // Note that this is the maximum number of labels for 4-way connectivity + { + input << + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1; + + output_8c << + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1; + + output_4c << + 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, + 0, 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16, + 17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24, 0, + 0, 25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32, + 33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40, 0, + 0, 41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48, + 49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0, + 0, 57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64, + 65, 0, 66, 0, 67, 0, 68, 0, 69, 0, 70, 0, 71, 0, 72, 0, + 0, 73, 0, 74, 0, 75, 0, 76, 0, 77, 0, 78, 0, 79, 0, 80, + 81, 0, 82, 0, 83, 0, 84, 0, 85, 0, 86, 0, 87, 0, 88, 0, + 0, 89, 0, 90, 0, 91, 0, 92, 0, 93, 0, 94, 0, 95, 0, 96, + 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0, 103, 0, 104, 0, + 0, 105, 0, 106, 0, 107, 0, 108, 0, 109, 0, 110, 0, 111, 0, 112, + 113, 0, 114, 0, 115, 0, 116, 0, 117, 0, 118, 0, 119, 0, 120, 0, + 0, 121, 0, 122, 0, 123, 0, 124, 0, 125, 0, 126, 0, 127, 0, 128; + } + + int ccltype[] = { cv::CCL_DEFAULT, cv::CCL_WU, cv::CCL_GRANA, cv::CCL_BOLELLI, cv::CCL_SAUF, cv::CCL_BBDT, cv::CCL_SPAGHETTI }; + + cv::Mat1i labels; + cv::Mat diff; + int nLabels = 0; + for (size_t cclt = 0; cclt < sizeof(ccltype) / sizeof(int); ++cclt) { + + EXPECT_NO_THROW(nLabels = cv::connectedComponents(input, labels, 8, CV_32S, ccltype[cclt])); + normalizeLabels(labels, nLabels); + + diff = labels != output_8c; + EXPECT_EQ(cv::countNonZero(diff), 0); + + + EXPECT_NO_THROW(nLabels = cv::connectedComponents(input, labels, 4, CV_32S, ccltype[cclt])); + normalizeLabels(labels, nLabels); + + diff = labels != output_4c; + EXPECT_EQ(cv::countNonZero(diff), 0); + } + +} + +TEST(Imgproc_ConnectedComponents, chessboard_odd) +{ + cv::Size size(15, 15); + cv::Mat1b input(size); + cv::Mat1i output_8c(size); + cv::Mat1i output_4c(size); + + // Chessboard image with odd number of rows and cols + // Note that this is the maximum number of labels for 4-way connectivity + { + input << + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1; + + output_8c << + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1; + + output_4c << + 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, + 0, 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, + 16, 0, 17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, + 0, 24, 0, 25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, + 31, 0, 32, 0, 33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, + 0, 39, 0, 40, 0, 41, 0, 42, 0, 43, 0, 44, 0, 45, 0, + 46, 0, 47, 0, 48, 0, 49, 0, 50, 0, 51, 0, 52, 0, 53, + 0, 54, 0, 55, 0, 56, 0, 57, 0, 58, 0, 59, 0, 60, 0, + 61, 0, 62, 0, 63, 0, 64, 0, 65, 0, 66, 0, 67, 0, 68, + 0, 69, 0, 70, 0, 71, 0, 72, 0, 73, 0, 74, 0, 75, 0, + 76, 0, 77, 0, 78, 0, 79, 0, 80, 0, 81, 0, 82, 0, 83, + 0, 84, 0, 85, 0, 86, 0, 87, 0, 88, 0, 89, 0, 90, 0, + 91, 0, 92, 0, 93, 0, 94, 0, 95, 0, 96, 0, 97, 0, 98, + 0, 99, 0, 100, 0, 101, 0, 102, 0, 103, 0, 104, 0, 105, 0, + 106, 0, 107, 0, 108, 0, 109, 0, 110, 0, 111, 0, 112, 0, 113; + } + + int ccltype[] = { cv::CCL_DEFAULT, cv::CCL_WU, cv::CCL_GRANA, cv::CCL_BOLELLI, cv::CCL_SAUF, cv::CCL_BBDT, cv::CCL_SPAGHETTI }; + + cv::Mat1i labels; + cv::Mat diff; + int nLabels = 0; + for (size_t cclt = 0; cclt < sizeof(ccltype) / sizeof(int); ++cclt) { + + EXPECT_NO_THROW(nLabels = cv::connectedComponents(input, labels, 8, CV_32S, ccltype[cclt])); + normalizeLabels(labels, nLabels); + + diff = labels != output_8c; + EXPECT_EQ(cv::countNonZero(diff), 0); + + + EXPECT_NO_THROW(nLabels = cv::connectedComponents(input, labels, 4, CV_32S, ccltype[cclt])); + normalizeLabels(labels, nLabels); + + diff = labels != output_4c; + EXPECT_EQ(cv::countNonZero(diff), 0); + } + +} + +TEST(Imgproc_ConnectedComponents, maxlabels_8conn_even) +{ + cv::Size size(16, 16); + cv::Mat1b input(size); + cv::Mat1i output_8c(size); + cv::Mat1i output_4c(size); + + { + input << + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; + + output_8c << + 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; + + output_4c << + 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; + } + + int ccltype[] = { cv::CCL_DEFAULT, cv::CCL_WU, cv::CCL_GRANA, cv::CCL_BOLELLI, cv::CCL_SAUF, cv::CCL_BBDT, cv::CCL_SPAGHETTI }; + + cv::Mat1i labels; + cv::Mat diff; + int nLabels = 0; + for (size_t cclt = 0; cclt < sizeof(ccltype) / sizeof(int); ++cclt) { + + EXPECT_NO_THROW(nLabels = cv::connectedComponents(input, labels, 8, CV_32S, ccltype[cclt])); + normalizeLabels(labels, nLabels); + + diff = labels != output_8c; + EXPECT_EQ(cv::countNonZero(diff), 0); + + + EXPECT_NO_THROW(nLabels = cv::connectedComponents(input, labels, 4, CV_32S, ccltype[cclt])); + normalizeLabels(labels, nLabels); + + diff = labels != output_4c; + EXPECT_EQ(cv::countNonZero(diff), 0); + } + +} + +TEST(Imgproc_ConnectedComponents, maxlabels_8conn_odd) +{ + cv::Size size(15, 15); + cv::Mat1b input(size); + cv::Mat1i output_8c(size); + cv::Mat1i output_4c(size); + + { + input << + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1; + + output_8c << + 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64; + + output_4c << + 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64; + } + + int ccltype[] = { cv::CCL_DEFAULT, cv::CCL_WU, cv::CCL_GRANA, cv::CCL_BOLELLI, cv::CCL_SAUF, cv::CCL_BBDT, cv::CCL_SPAGHETTI }; + + cv::Mat1i labels; + cv::Mat diff; + int nLabels = 0; + for (size_t cclt = 0; cclt < sizeof(ccltype) / sizeof(int); ++cclt) { + + EXPECT_NO_THROW(nLabels = cv::connectedComponents(input, labels, 8, CV_32S, ccltype[cclt])); + normalizeLabels(labels, nLabels); + + diff = labels != output_8c; + EXPECT_EQ(cv::countNonZero(diff), 0); + + + EXPECT_NO_THROW(nLabels = cv::connectedComponents(input, labels, 4, CV_32S, ccltype[cclt])); + normalizeLabels(labels, nLabels); + + diff = labels != output_4c; + EXPECT_EQ(cv::countNonZero(diff), 0); + } + +} + +TEST(Imgproc_ConnectedComponents, single_row) +{ + cv::Size size(1, 15); + cv::Mat1b input(size); + cv::Mat1i output_8c(size); + cv::Mat1i output_4c(size); + + { + input << + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1; + + + output_8c << + 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8; + + + output_4c << + 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8; + + } + + int ccltype[] = { cv::CCL_DEFAULT, cv::CCL_WU, cv::CCL_GRANA, cv::CCL_BOLELLI, cv::CCL_SAUF, cv::CCL_BBDT, cv::CCL_SPAGHETTI }; + + cv::Mat1i labels; + cv::Mat diff; + int nLabels = 0; + for (size_t cclt = 0; cclt < sizeof(ccltype) / sizeof(int); ++cclt) { + + EXPECT_NO_THROW(nLabels = cv::connectedComponents(input, labels, 8, CV_32S, ccltype[cclt])); + normalizeLabels(labels, nLabels); + + diff = labels != output_8c; + EXPECT_EQ(cv::countNonZero(diff), 0); + + + EXPECT_NO_THROW(nLabels = cv::connectedComponents(input, labels, 4, CV_32S, ccltype[cclt])); + normalizeLabels(labels, nLabels); + + diff = labels != output_4c; + EXPECT_EQ(cv::countNonZero(diff), 0); + } + +} + +TEST(Imgproc_ConnectedComponents, single_column) +{ + cv::Size size(15, 1); + cv::Mat1b input(size); + cv::Mat1i output_8c(size); + cv::Mat1i output_4c(size); + + { + input << + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1; + + + output_8c << + 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8; + + + output_4c << + 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8; + + } + + int ccltype[] = { cv::CCL_DEFAULT, cv::CCL_WU, cv::CCL_GRANA, cv::CCL_BOLELLI, cv::CCL_SAUF, cv::CCL_BBDT, cv::CCL_SPAGHETTI }; + + cv::Mat1i labels; + cv::Mat diff; + int nLabels = 0; + for (size_t cclt = 0; cclt < sizeof(ccltype) / sizeof(int); ++cclt) { + + EXPECT_NO_THROW(nLabels = cv::connectedComponents(input, labels, 8, CV_32S, ccltype[cclt])); + normalizeLabels(labels, nLabels); + + diff = labels != output_8c; + EXPECT_EQ(cv::countNonZero(diff), 0); + + + EXPECT_NO_THROW(nLabels = cv::connectedComponents(input, labels, 4, CV_32S, ccltype[cclt])); + normalizeLabels(labels, nLabels); + + diff = labels != output_4c; + EXPECT_EQ(cv::countNonZero(diff), 0); + } + +} + + +} +} // namespace