Merge pull request #135 from nevion:master

pull/286/merge
Vadim Pisarevsky 12 years ago committed by OpenCV Buildbot
commit b68df415a9
  1. 32
      modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst
  2. 14
      modules/imgproc/include/opencv2/imgproc/imgproc.hpp
  3. 411
      modules/imgproc/src/connectedcomponents.cpp
  4. 108
      modules/imgproc/test/test_connectedcomponents.cpp
  5. 11
      modules/python/src2/cv2.cpp
  6. 36
      samples/cpp/connected_components.cpp

@ -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
----------------

@ -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
{

@ -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 <nevion@gmail.com>
//M*/
//
#include "precomp.hpp"
#include <vector>
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<Point2ui64> 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<int>::type);
statsv = _mstatsv->getMat();
_mcentroidsv->create(cv::Size(2, nlabels), cv::DataType<double>::type);
centroidsv = _mcentroidsv->getMat();
for(int l = 0; l < (int) nlabels; ++l){
int *row = (int *) &statsv.at<int>(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<int>(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<int>(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 = &centroidsv.at<double>(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<typename LabelT>
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<typename LabelT>
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<typename LabelT>
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<typename LabelT>
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<typename LabelT>
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<typename LabelT, typename PixelT, typename StatsOp = NoOp >
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<typename StatsOp>
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<uchar, uchar, StatsOp>()(I, L, connectivity, sop);
}else if(lDepth == CV_16U){
return (int) LabelingImpl<ushort, uchar, StatsOp>()(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<T> typechecks in debug mode
return (int) LabelingImpl<int, uchar, StatsOp>()(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;
}
}

@ -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 <string>
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<int>(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(); }

@ -410,7 +410,7 @@ static bool pyopencv_to(PyObject* obj, bool& value, const char* name = "<unknown
static PyObject* pyopencv_from(size_t value)
{
return PyLong_FromUnsignedLong((unsigned long)value);
return PyLong_FromSize_t(value);
}
static bool pyopencv_to(PyObject* obj, size_t& value, const char* name = "<unknown>")
@ -497,9 +497,16 @@ static bool pyopencv_to(PyObject* obj, float& value, const char* name = "<unknow
static PyObject* pyopencv_from(int64 value)
{
return PyFloat_FromDouble((double)value);
return PyLong_FromLongLong(value);
}
#if !defined(__LP64__)
static PyObject* pyopencv_from(uint64 value)
{
return PyLong_FromUnsignedLongLong(value);
}
#endif
static PyObject* pyopencv_from(const string& value)
{
return PyString_FromString(value.empty() ? "" : value.c_str());

@ -11,25 +11,21 @@ int threshval = 100;
static void on_trackbar(int, void*)
{
Mat bw = threshval < 128 ? (img < threshval) : (img > threshval);
vector<vector<Point> > contours;
vector<Vec4i> 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<Vec3b> 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<int>(r, c);
Vec3b &pixel = dst.at<Vec3b>(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<string>(1);
string inputImage = parser.get<string>("@image");
img = imread(inputImage.c_str(), 0);
if(img.empty())

Loading…
Cancel
Save