diff --git a/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst b/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst index ee980db6dc..9647a76261 100644 --- a/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst +++ b/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst @@ -118,6 +118,38 @@ These values are proved to be invariants to the image scale, rotation, and refle .. seealso:: :ocv:func:`matchShapes` +connectedComponents +----------------------- +computes the connected components labeled image of boolean image ``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. + +.. ocv:function:: int connectedComponents(InputArray image, OutputArray labels, int connectivity = 8, int ltype=CV_32S) + +.. ocv:function:: int connectedComponentsWithStats(InputArray image, OutputArray labels, OutputArray stats, OutputArray centroids, int connectivity = 8, int ltype=CV_32S) + + :param image: the 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 statsv: statistics output for each label, including the background label, see below for available statistics. Statistics are accessed via statsv(label, COLUMN) where available columns are defined below. + + * **CC_STAT_LEFT** The leftmost (x) coordinate which is the inclusive start of the bounding box in the horizontal + direction. + + * **CC_STAT_TOP** The topmost (y) coordinate which is the inclusive start of the bounding box in the vertical + direction. + + * **CC_STAT_WIDTH** The horizontal size of the bounding box + + * **CC_STAT_HEIGHT** The vertical size of the bounding box + + * **CC_STAT_AREA** The total area (in pixels) of the connected component + + :param centroids: floating point centroid (x,y) output for each label, including the background label + findContours ---------------- diff --git a/modules/imgproc/include/opencv2/imgproc/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc/imgproc.hpp index c0c51f57aa..34df8f2fcf 100644 --- a/modules/imgproc/include/opencv2/imgproc/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc/imgproc.hpp @@ -1102,6 +1102,20 @@ enum { TM_SQDIFF=0, TM_SQDIFF_NORMED=1, TM_CCORR=2, TM_CCORR_NORMED=3, TM_CCOEFF CV_EXPORTS_W void matchTemplate( InputArray image, InputArray templ, OutputArray result, int method ); +enum { CC_STAT_LEFT=0, CC_STAT_TOP=1, CC_STAT_WIDTH=2, CC_STAT_HEIGHT=3, CC_STAT_AREA=4, CC_STAT_MAX = 5}; + +// computes the connected components labeled image of boolean image ``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. +CV_EXPORTS_W int connectedComponents(InputArray image, OutputArray labels, + int connectivity = 8, int ltype=CV_32S); +CV_EXPORTS_W int connectedComponentsWithStats(InputArray image, OutputArray labels, + OutputArray stats, OutputArray centroids, + int connectivity = 8, int ltype=CV_32S); + //! mode of the contour retrieval algorithm enum { diff --git a/modules/imgproc/src/connectedcomponents.cpp b/modules/imgproc/src/connectedcomponents.cpp new file mode 100644 index 0000000000..4fee0aacd1 --- /dev/null +++ b/modules/imgproc/src/connectedcomponents.cpp @@ -0,0 +1,411 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// Intel License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2000, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of Intel Corporation may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// 2011 Jason Newton +//M*/ +// +#include "precomp.hpp" +#include + +namespace cv{ + namespace connectedcomponents{ + + struct NoOp{ + NoOp(){ + } + void init(int /*labels*/){ + } + inline + void operator()(int r, int c, int l){ + (void) r; + (void) c; + (void) l; + } + void finish(){} + }; + struct Point2ui64{ + uint64 x, y; + Point2ui64(uint64 _x, uint64 _y):x(_x), y(_y){} + }; + + struct CCStatsOp{ + const _OutputArray* _mstatsv; + cv::Mat statsv; + const _OutputArray* _mcentroidsv; + cv::Mat centroidsv; + std::vector integrals; + + CCStatsOp(OutputArray _statsv, OutputArray _centroidsv): _mstatsv(&_statsv), _mcentroidsv(&_centroidsv){ + } + inline + void init(int nlabels){ + _mstatsv->create(cv::Size(CC_STAT_MAX, nlabels), cv::DataType::type); + statsv = _mstatsv->getMat(); + _mcentroidsv->create(cv::Size(2, nlabels), cv::DataType::type); + centroidsv = _mcentroidsv->getMat(); + + for(int l = 0; l < (int) nlabels; ++l){ + int *row = (int *) &statsv.at(l, 0); + row[CC_STAT_LEFT] = INT_MAX; + row[CC_STAT_TOP] = INT_MAX; + row[CC_STAT_WIDTH] = INT_MIN; + row[CC_STAT_HEIGHT] = INT_MIN; + row[CC_STAT_AREA] = 0; + } + integrals.resize(nlabels, Point2ui64(0, 0)); + } + void operator()(int r, int c, int l){ + int *row = &statsv.at(l, 0); + if(c > row[CC_STAT_WIDTH]){ + row[CC_STAT_WIDTH] = c; + }else{ + if(c < row[CC_STAT_LEFT]){ + row[CC_STAT_LEFT] = c; + } + } + if(r > row[CC_STAT_HEIGHT]){ + row[CC_STAT_HEIGHT] = r; + }else{ + if(r < row[CC_STAT_TOP]){ + row[CC_STAT_TOP] = r; + } + } + row[CC_STAT_AREA]++; + Point2ui64 &integral = integrals[l]; + integral.x += c; + integral.y += r; + } + void finish(){ + for(int l = 0; l < statsv.rows; ++l){ + int *row = &statsv.at(l, 0); + row[CC_STAT_LEFT] = std::min(row[CC_STAT_LEFT], row[CC_STAT_WIDTH]); + row[CC_STAT_WIDTH] = row[CC_STAT_WIDTH] - row[CC_STAT_LEFT] + 1; + row[CC_STAT_TOP] = std::min(row[CC_STAT_TOP], row[CC_STAT_HEIGHT]); + row[CC_STAT_HEIGHT] = row[CC_STAT_HEIGHT] - row[CC_STAT_TOP] + 1; + + Point2ui64 &integral = integrals[l]; + double *centroid = ¢roidsv.at(l, 0); + double area = ((unsigned*)row)[CC_STAT_AREA]; + centroid[0] = double(integral.x) / area; + centroid[1] = double(integral.y) / area; + } + } + }; + + //Find the root of the tree of node i + template + inline static + LabelT findRoot(const LabelT *P, LabelT i){ + LabelT root = i; + while(P[root] < root){ + root = P[root]; + } + return root; + } + + //Make all nodes in the path of node i point to root + template + inline static + void setRoot(LabelT *P, LabelT i, LabelT root){ + while(P[i] < i){ + LabelT j = P[i]; + P[i] = root; + i = j; + } + P[i] = root; + } + + //Find the root of the tree of the node i and compress the path in the process + template + inline static + LabelT find(LabelT *P, LabelT i){ + LabelT root = findRoot(P, i); + setRoot(P, i, root); + return root; + } + + //unite the two trees containing nodes i and j and return the new root + template + inline static + LabelT set_union(LabelT *P, LabelT i, LabelT j){ + LabelT root = findRoot(P, i); + if(i != j){ + LabelT rootj = findRoot(P, j); + if(root > rootj){ + root = rootj; + } + setRoot(P, j, root); + } + setRoot(P, i, root); + return root; + } + + //Flatten the Union Find tree and relabel the components + template + inline static + LabelT flattenL(LabelT *P, LabelT length){ + LabelT k = 1; + for(LabelT i = 1; i < length; ++i){ + if(P[i] < i){ + P[i] = P[P[i]]; + }else{ + P[i] = k; k = k + 1; + } + } + return k; + } + + //Based on "Two Strategies to Speed up Connected Components Algorithms", the SAUF (Scan array union find) variant + //using decision trees + //Kesheng Wu, et al + //Note: rows are encoded as position in the "rows" array to save lookup times + //reference for 4-way: {{-1, 0}, {0, -1}};//b, d neighborhoods + const int G4[2][2] = {{1, 0}, {0, -1}};//b, d neighborhoods + //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{ + 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); + CV_Assert(connectivity == 8 || connectivity == 4); + const int rows = L.rows; + const int cols = L.cols; + size_t Plength = (size_t(rows + 3 - 1)/3) * (size_t(cols + 3 - 1)/3); + if(connectivity == 4){ + Plength = 4 * Plength;//a quick and dirty upper bound, an exact answer exists if you want to find it + //the 4 comes from the fact that a 3x3 block can never have more than 4 unique labels + } + LabelT *P = (LabelT *) fastMalloc(sizeof(LabelT) * Plength); + P[0] = 0; + LabelT lunique = 1; + //scanning phase + for(int r_i = 0; r_i < rows; ++r_i){ + LabelT *Lrow = (LabelT *)(L.data + L.step.p[0] * r_i); + LabelT *Lrow_prev = (LabelT *)(((char *)Lrow) - L.step.p[0]); + const PixelT *Irow = (PixelT *)(I.data + I.step.p[0] * r_i); + const PixelT *Irow_prev = (const PixelT *)(((char *)Irow) - I.step.p[0]); + LabelT *Lrows[2] = { + Lrow, + Lrow_prev + }; + const PixelT *Irows[2] = { + Irow, + Irow_prev + }; + if(connectivity == 8){ + const int a = 0; + const int b = 1; + const int c = 2; + const int d = 3; + const bool T_a_r = (r_i - G8[a][0]) >= 0; + const bool T_b_r = (r_i - G8[b][0]) >= 0; + const bool T_c_r = (r_i - G8[c][0]) >= 0; + for(int c_i = 0; Irows[0] != Irow + cols; ++Irows[0], c_i++){ + if(!*Irows[0]){ + Lrow[c_i] = 0; + continue; + } + Irows[1] = Irow_prev + c_i; + Lrows[0] = Lrow + c_i; + Lrows[1] = Lrow_prev + c_i; + const bool T_a = T_a_r && (c_i + G8[a][1]) >= 0 && *(Irows[G8[a][0]] + G8[a][1]); + const bool T_b = T_b_r && *(Irows[G8[b][0]] + G8[b][1]); + const bool T_c = T_c_r && (c_i + G8[c][1]) < cols && *(Irows[G8[c][0]] + G8[c][1]); + const bool T_d = (c_i + G8[d][1]) >= 0 && *(Irows[G8[d][0]] + G8[d][1]); + + //decision tree + if(T_b){ + //copy(b) + *Lrows[0] = *(Lrows[G8[b][0]] + G8[b][1]); + }else{//not b + if(T_c){ + if(T_a){ + //copy(c, a) + *Lrows[0] = set_union(P, *(Lrows[G8[c][0]] + G8[c][1]), *(Lrows[G8[a][0]] + G8[a][1])); + }else{ + if(T_d){ + //copy(c, d) + *Lrows[0] = set_union(P, *(Lrows[G8[c][0]] + G8[c][1]), *(Lrows[G8[d][0]] + G8[d][1])); + }else{ + //copy(c) + *Lrows[0] = *(Lrows[G8[c][0]] + G8[c][1]); + } + } + }else{//not c + if(T_a){ + //copy(a) + *Lrows[0] = *(Lrows[G8[a][0]] + G8[a][1]); + }else{ + if(T_d){ + //copy(d) + *Lrows[0] = *(Lrows[G8[d][0]] + G8[d][1]); + }else{ + //new label + *Lrows[0] = lunique; + P[lunique] = lunique; + lunique = lunique + 1; + } + } + } + } + } + }else{ + //B & D only + const int b = 0; + const int d = 1; + const bool T_b_r = (r_i - G4[b][0]) >= 0; + for(int c_i = 0; Irows[0] != Irow + cols; ++Irows[0], c_i++){ + if(!*Irows[0]){ + Lrow[c_i] = 0; + continue; + } + Irows[1] = Irow_prev + c_i; + Lrows[0] = Lrow + c_i; + Lrows[1] = Lrow_prev + c_i; + const bool T_b = T_b_r && *(Irows[G4[b][0]] + G4[b][1]); + const bool T_d = (c_i + G4[d][1]) >= 0 && *(Irows[G4[d][0]] + G4[d][1]); + if(T_b){ + if(T_d){ + //copy(d, b) + *Lrows[0] = set_union(P, *(Lrows[G4[d][0]] + G4[d][1]), *(Lrows[G4[b][0]] + G4[b][1])); + }else{ + //copy(b) + *Lrows[0] = *(Lrows[G4[b][0]] + G4[b][1]); + } + }else{ + if(T_d){ + //copy(d) + *Lrows[0] = *(Lrows[G4[d][0]] + G4[d][1]); + }else{ + //new label + *Lrows[0] = lunique; + P[lunique] = lunique; + lunique = lunique + 1; + } + } + } + } + } + + //analysis + LabelT nLabels = flattenL(P, lunique); + sop.init(nLabels); + + for(int r_i = 0; r_i < rows; ++r_i){ + LabelT *Lrow_start = (LabelT *)(L.data + L.step.p[0] * r_i); + LabelT *Lrow_end = Lrow_start + cols; + LabelT *Lrow = Lrow_start; + for(int c_i = 0; Lrow != Lrow_end; ++Lrow, ++c_i){ + const LabelT l = P[*Lrow]; + *Lrow = l; + sop(r_i, c_i, l); + } + } + + sop.finish(); + fastFree(P); + + return nLabels; + }//End function LabelingImpl operator() + + };//End struct LabelingImpl +}//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){ + CV_Assert(L.channels() == 1 && I.channels() == 1); + CV_Assert(connectivity == 8 || connectivity == 4); + + 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); + } + + CV_Error(CV_StsUnsupportedFormat, "unsupported label/image type"); + return -1; +} + +} + +int cv::connectedComponents(InputArray _img, OutputArray _labels, int connectivity, int ltype){ + 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{ + CV_Error(CV_StsUnsupportedFormat, "the type of labels must be 16u or 32s"); + return 0; + } +} + +int cv::connectedComponentsWithStats(InputArray _img, OutputArray _labels, OutputArray statsv, + OutputArray centroids, int connectivity, int ltype) +{ + 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{ + CV_Error(CV_StsUnsupportedFormat, "the type of labels must be 16u or 32s"); + return 0; + } +} diff --git a/modules/imgproc/test/test_connectedcomponents.cpp b/modules/imgproc/test/test_connectedcomponents.cpp new file mode 100644 index 0000000000..c428cc0742 --- /dev/null +++ b/modules/imgproc/test/test_connectedcomponents.cpp @@ -0,0 +1,108 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +// Copyright (C) 2009, Willow Garage Inc., all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +#include "test_precomp.hpp" +#include + +using namespace cv; +using namespace std; + +class CV_ConnectedComponentsTest : public cvtest::BaseTest +{ +public: + CV_ConnectedComponentsTest(); + ~CV_ConnectedComponentsTest(); +protected: + void run(int); +}; + +CV_ConnectedComponentsTest::CV_ConnectedComponentsTest() {} +CV_ConnectedComponentsTest::~CV_ConnectedComponentsTest() {} + +void CV_ConnectedComponentsTest::run( int /* start_from */) +{ + 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 ); + 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; + } + } + } + + if( exp.empty() || orig.size() != exp.size() ) + { + imwrite(exp_path, labelImage); + exp = labelImage; + } + + if (0 != norm(labelImage > 0, exp > 0, NORM_INF)) + { + ts->set_failed_test_info( cvtest::TS::FAIL_MISMATCH ); + return; + } + if (nLabels != norm(labelImage, NORM_INF)+1) + { + ts->set_failed_test_info( cvtest::TS::FAIL_MISMATCH ); + return; + } + ts->set_failed_test_info(cvtest::TS::OK); +} + +TEST(Imgproc_ConnectedComponents, regression) { CV_ConnectedComponentsTest test; test.safe_run(); } + diff --git a/modules/python/src2/cv2.cpp b/modules/python/src2/cv2.cpp index 28cf00eac2..bc52f308c9 100644 --- a/modules/python/src2/cv2.cpp +++ b/modules/python/src2/cv2.cpp @@ -410,7 +410,7 @@ static bool pyopencv_to(PyObject* obj, bool& value, const char* name = " threshval); - - vector > contours; - vector hierarchy; - - findContours( bw, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE ); - - Mat dst = Mat::zeros(img.size(), CV_8UC3); - - if( !contours.empty() && !hierarchy.empty() ) - { - // iterate through all the top-level contours, - // draw each connected component with its own random color - int idx = 0; - for( ; idx >= 0; idx = hierarchy[idx][0] ) - { - Scalar color( (rand()&255), (rand()&255), (rand()&255) ); - drawContours( dst, contours, idx, color, CV_FILLED, 8, hierarchy ); - } + Mat labelImage(img.size(), CV_32S); + int nLabels = connectedComponents(bw, labelImage, 8); + std::vector colors(nLabels); + colors[0] = Vec3b(0, 0, 0);//background + for(int label = 1; label < nLabels; ++label){ + colors[label] = Vec3b( (rand()&255), (rand()&255), (rand()&255) ); } + Mat dst(img.size(), CV_8UC3); + for(int r = 0; r < dst.rows; ++r){ + for(int c = 0; c < dst.cols; ++c){ + int label = labelImage.at(r, c); + Vec3b &pixel = dst.at(r, c); + pixel = colors[label]; + } + } imshow( "Connected Components", dst ); } @@ -45,14 +41,14 @@ static void help() const char* keys = { - "{@image |stuff.jpg|image for converting to a grayscale}" + "{@image|stuff.jpg|image for converting to a grayscale}" }; int main( int argc, const char** argv ) { help(); CommandLineParser parser(argc, argv, keys); - string inputImage = parser.get(1); + string inputImage = parser.get("@image"); img = imread(inputImage.c_str(), 0); if(img.empty())