connected components in OpenCV

I am looking for an OpenCV function that can find connected components and perform a few tasks on them ( like getting the number of pixels, contour, list of pixels in the object etc.. )

Is there a function of OpenCV (C++) that is similar to MatLab's regionprops ?

Answers


Starting from version 3.0, OpenCV has connectedComponents function.


Have a look at the cvFindContours function. It's very versatile -- it can find both interior and exterior contours, and return the results in a variety of formats (e.g. flat list vs. tree structure). Once you have the contours, functions like cvContourArea allow you to determine basic properties of the connected component corresponding to a particular contour.

If you prefer to use the newer C++ interface (as opposed to the older C-style interface I described above), then the function names are similar.


set -std=c++0x option when compiling

.h file

//connected_components.h
#ifndef CONNECTED_COMPONENTS_H_
#define CONNECTED_COMPONENTS_H_
#include <opencv2/core/core.hpp>
#include <memory>

class DisjointSet {
  private:
    std::vector<int> m_disjoint_array;
    int m_subset_num;
  public:
    DisjointSet();
    DisjointSet(int size);
    ~DisjointSet();
    int add(); //add a new element, which is a subset by itself;
    int find(int x); //return the root of x
    void unite(int x, int y);
    int getSubsetNum(void);
};

class ConnectedComponent {
private:
  cv::Rect m_bb;
  int m_pixel_count;
  std::shared_ptr< std::vector<cv::Point2i> > m_pixels;
public:
  ConnectedComponent();
  ConnectedComponent(int x, int y);
  ~ConnectedComponent();
  void addPixel(int x, int y);
  int getBoundingBoxArea(void) const;
  cv::Rect getBoundingBox(void) const;
  int getPixelCount(void) const;
  std::shared_ptr< const std::vector<cv::Point2i> > getPixels(void) const;
};

void findCC(const cv::Mat& src, std::vector<ConnectedComponent>& cc);
#endif //CONNECTED_COMPONENTS_H_

.cc file

//connected_components.cpp
#include "connected_components.h"

using namespace std;
/** DisjointSet **/
DisjointSet::DisjointSet() :
  m_disjoint_array(),
  m_subset_num(0)
{  }

DisjointSet::DisjointSet(int size) :
  m_disjoint_array(),
  m_subset_num(0)
{
  m_disjoint_array.reserve(size);
}

DisjointSet::~DisjointSet()
{  }

//add a new element, which is a subset by itself;
int DisjointSet::add()
{
  int cur_size = m_disjoint_array.size();
  m_disjoint_array.push_back(cur_size);
  m_subset_num ++;
  return cur_size;
}
//return the root of x
int DisjointSet::find(int x)
{
  if (m_disjoint_array[x] < 0 || m_disjoint_array[x] == x)
    return x;
  else {
    m_disjoint_array[x] = this->find(m_disjoint_array[x]);
    return m_disjoint_array[x];
  }
}
// point the x and y to smaller root of the two
void DisjointSet::unite(int x, int y)
{
  if (x==y) {
    return;
  }
  int xRoot = find(x);
  int yRoot = find(y);
  if (xRoot == yRoot)
    return;
  else if (xRoot < yRoot) {
    m_disjoint_array[yRoot] = xRoot;
  }
  else {
    m_disjoint_array[xRoot] = yRoot;
  }
  m_subset_num--;
}

int DisjointSet::getSubsetNum()
{
  return m_subset_num;
}

/** ConnectedComponent **/
ConnectedComponent::ConnectedComponent() :
  m_bb(0,0,0,0),
  m_pixel_count(0),
  m_pixels()
{
  m_pixels = std::make_shared< std::vector<cv::Point2i> > ();
}

ConnectedComponent::ConnectedComponent(int x, int y) :
  m_bb(x,y,1,1),
  m_pixel_count(1),
  m_pixels()
{
  m_pixels = std::make_shared< std::vector<cv::Point2i> > ();
}

ConnectedComponent::~ConnectedComponent(void)
{ }

void ConnectedComponent::addPixel(int x, int y) {
  m_pixel_count++;
  // new bounding box;
  if (m_pixel_count == 0) {
    m_bb = cv::Rect(x,y,1,1);
  }
  // extend bounding box if necessary
  else {
    if (x < m_bb.x ) {
      m_bb.width+=(m_bb.x-x);
      m_bb.x = x;
    }
    else if ( x > (m_bb.x+m_bb.width) ) {
      m_bb.width=(x-m_bb.x);
    }
    if (y < m_bb.y ) {
      m_bb.height+=(m_bb.y-y);
      m_bb.y = y;
    }
    else if ( y > (m_bb.y+m_bb.height) ) {
      m_bb.height=(y-m_bb.y);
    }
  }
  m_pixels->push_back(cv::Point(x,y));
}

int ConnectedComponent::getBoundingBoxArea(void) const {
  return (m_bb.width*m_bb.height);
}

cv::Rect ConnectedComponent::getBoundingBox(void) const {
  return m_bb;
}

std::shared_ptr< const std::vector<cv::Point2i> > ConnectedComponent::getPixels(void) const {
  return m_pixels;
}


int ConnectedComponent::getPixelCount(void) const {
  return m_pixel_count;
}

/** find connected components **/

void findCC(const cv::Mat& src, std::vector<ConnectedComponent>& cc) {
  if (src.empty()) return;
  CV_Assert(src.type() == CV_8U);
  cc.clear();
  int total_pix = src.total();
  int frame_label[total_pix];
  DisjointSet labels(total_pix);
  int root_map[total_pix];
  int x, y;
  const uchar* cur_p;
  const uchar* prev_p = src.ptr<uchar>(0);
  int left_val, up_val;
  int cur_idx, left_idx, up_idx;
  cur_idx = 0;
  //first logic loop
  for (y = 0; y < src.rows; y++ ) {
    cur_p = src.ptr<uchar>(y);
    for (x = 0; x < src.cols; x++, cur_idx++) {
      left_idx = cur_idx - 1;
      up_idx = cur_idx - src.size().width;
      if ( x == 0)
        left_val = 0;
      else
        left_val = cur_p[x-1];
      if (y == 0)
        up_val = 0;
      else
        up_val = prev_p[x];
      if (cur_p[x] > 0) {
        //current pixel is foreground and has no connected neighbors
        if (left_val == 0 && up_val == 0) {
          frame_label[cur_idx] = (int)labels.add();
          root_map[frame_label[cur_idx]] = -1;
        }
        //current pixel is foreground and has left neighbor connected
        else if (left_val != 0 && up_val == 0) {
          frame_label[cur_idx] = frame_label[left_idx];
        }
        //current pixel is foreground and has up neighbor connect
        else if (up_val != 0 && left_val == 0) {
          frame_label[cur_idx] = frame_label[up_idx];
        }
        //current pixel is foreground and is connected to left and up neighbors
        else {
          frame_label[cur_idx] = (frame_label[left_idx] > frame_label[up_idx]) ? frame_label[up_idx] : frame_label[left_idx];
          labels.unite(frame_label[left_idx], frame_label[up_idx]);
        }
      }//endif
      else {
        frame_label[cur_idx] = -1;
      }
    } //end for x
    prev_p = cur_p;
  }//end for y
  //second loop logic
  cur_idx = 0;
  int curLabel;
  int connCompIdx = 0;
  for (y = 0; y < src.size().height; y++ ) {
    for (x = 0; x < src.size().width; x++, cur_idx++) {
      curLabel = frame_label[cur_idx];
      if (curLabel != -1) {
        curLabel = labels.find(curLabel);
        if( root_map[curLabel] != -1 ) {
          cc[root_map[curLabel]].addPixel(x, y);
        }
        else {
          cc.push_back(ConnectedComponent(x,y));
          root_map[curLabel] = connCompIdx;
          connCompIdx++;
        }
      }
    }//end for x
  }//end for y
}

If you don't mind using an external library that uses OpenCV, you can do that using cvBlobsLib.

A library to perform binary images connected component labelling (similar to regionprops Matlab function). It also provides functions to manipulate, filter and extract results from the extracted blobs, see features section for more information.


Following DXM's code above which assumes 4-connected components, here is a version for 'findCC' that detects 8-connected components.

void findCC(const cv::Mat& src, std::vector<ConnectedComponent>& cc) {
if (src.empty()) return;
CV_Assert(src.type() == CV_8U);
cc.clear();
int total_pix = int(src.total());
int *frame_label = new int[total_pix];
DisjointSet labels(total_pix);
int *root_map = new int[total_pix];
int x, y;
const uchar* cur_p;
const uchar* prev_p = src.ptr<uchar>(0);
int left_val, up_val, up_left_val, up_right_val;
int cur_idx, left_idx, up_idx, up_left_idx, up_right_idx;
cur_idx = 0;
//first logic loop
for (y = 0; y < src.rows; y++) {
    cur_p = src.ptr<uchar>(y);
    for (x = 0; x < src.cols; x++, cur_idx++) {
        left_idx = cur_idx - 1;
        up_idx = cur_idx - src.size().width;
        up_left_idx = up_idx - 1;
        up_right_idx = up_idx + 1;

        if (x == 0)
        {
            left_val = 0;
        }
        else
        {
            left_val = cur_p[x - 1];
        }
        if (y == 0)
        {
            up_val = 0;
        }
        else
        {
            up_val = prev_p[x];
        }
        if (x == 0 || y == 0)
        {
            up_left_val = 0;
        }
        else
        {
            up_left_val = prev_p[x-1];
        }
        if (x == src.cols - 1 || y == 0)
        {
            up_right_val = 0;
        }
        else
        {
            up_right_val = prev_p[x+1];
        }

        if (cur_p[x] > 0) {
            //current pixel is foreground and has no connected neighbors
            if (left_val == 0 && up_val == 0 && up_left_val == 0 && up_right_val == 0) {
                frame_label[cur_idx] = (int)labels.add();
                root_map[frame_label[cur_idx]] = -1;
            }

            //Current pixel is foreground and has at least one neighbor
            else
            {
                vector<int> frame_lbl;
                frame_lbl.reserve(4);
                //Find minimal label
                int min_frame_lbl = INT_MAX;
                int valid_entries_num = 0;

                if (left_val != 0)
                {
                    frame_lbl.push_back(frame_label[left_idx]);
                    min_frame_lbl = min(min_frame_lbl, frame_label[left_idx]);
                    valid_entries_num++;
                }
                if (up_val != 0)
                {
                    frame_lbl.push_back(frame_label[up_idx]);
                    min_frame_lbl = min(min_frame_lbl, frame_label[up_idx]);
                    valid_entries_num++;
                }
                if (up_left_val != 0)
                {
                    frame_lbl.push_back(frame_label[up_left_idx]);
                    min_frame_lbl = min(min_frame_lbl, frame_label[up_left_idx]);
                    valid_entries_num++;
                }
                if (up_right_val != 0)
                {
                    frame_lbl.push_back(frame_label[up_right_idx]);
                    min_frame_lbl = min(min_frame_lbl, frame_label[up_right_idx]);
                    valid_entries_num++;
                }

                CV_Assert(valid_entries_num > 0);
                frame_label[cur_idx] = min_frame_lbl;

                //Unite if necessary
                if (valid_entries_num > 1)
                {
                    for (size_t i = 0; i < frame_lbl.size(); i++)
                    {
                        labels.unite(frame_lbl[i], min_frame_lbl);
                    }
                }
            }

        }//endif
        else {
            frame_label[cur_idx] = -1;
        }
    } //end for x
    prev_p = cur_p;
}//end for y
//second loop logic
cur_idx = 0;
int curLabel;
int connCompIdx = 0;
for (y = 0; y < src.size().height; y++) {
    for (x = 0; x < src.size().width; x++, cur_idx++) {
        curLabel = frame_label[cur_idx];
        if (curLabel != -1) {
            curLabel = labels.find(curLabel);
            if (root_map[curLabel] != -1) {
                cc[root_map[curLabel]].addPixel(x, y);
            }
            else {
                cc.push_back(ConnectedComponent(x, y));
                root_map[curLabel] = connCompIdx;
                connCompIdx++;
            }
        }
    }//end for x
}//end for y

//Free up allocated memory
delete[] frame_label;
delete[] root_map;

}


You can use cv::connectedComponentsWithStats() function.

Here is an example.

    // ...
    cv::Mat labels, stats, centroids;
    int connectivity = 8; // or 4
    int label_count = cv::connectedComponentsWithStats(src, labels, stats, centroids, connectivity);
    for (int i = 0; i < label_count; i++)
    {
        int x = stats.at<int>(i, cv::CC_STAT_LEFT);
        int y = stats.at<int>(i, cv::CC_STAT_TOP);
        int w = stats.at<int>(i, cv::CC_STAT_WIDTH);
        int h = stats.at<int>(i, cv::CC_STAT_HEIGHT);
        int area = stats.at<int>(i, cv::CC_STAT_AREA);
        double cx = centroids.at<double>(i, 0);
        double cy = centroids.at<double>(i, 1);

        // ...
    }

Need Your Help

Create custom item in jsTree Context menu

asp.net-mvc treeview jstree

I create a treeview using jsTree with contextmenu in asp.net mvc3.

Python - can I programmatically decorate class methods from a class instance?

python decorator class-method

I have an object hierarchy in which almost all of the methods are class methods. It looks like the following: