OpenCV 中的连接组件

Posted

技术标签:

【中文标题】OpenCV 中的连接组件【英文标题】:connected components in OpenCV 【发布时间】:2012-09-23 04:51:20 【问题描述】:

我正在寻找一个 OpenCV 函数,它可以找到连接的组件并对其执行一些任务(例如获取像素数、轮廓、对象中的像素列表等)

OpenCV(C++)有没有类似MatLab的regionprops的功能?

【问题讨论】:

【参考方案1】:

如果您不介意使用使用 OpenCV 的外部库,您可以使用 cvBlobsLib

执行二进制图像连接组件标记的库 (类似于 regionprops Matlab 函数)。它还提供功能 从提取的 blob 中操作、过滤和提取结果, 有关详细信息,请参阅功能部分。

【讨论】:

谢谢!如何将它与 cvMat 一起使用? @EyalG 如果您查看examples,您会发现他们使用 IplImage,所以我想您也可以轻松使用 cvMat。 您不需要为此使用单独的库。 OpenCV 本身就可以很好地处理连接的组件。 我同意 misha,cv::findContours 明确找到连接的组件。【参考方案2】:

看看cvFindContours 函数。它非常通用——它可以找到内部和外部轮廓,并以多种格式返回结果(例如平面列表与树结构)。获得轮廓后,cvContourArea 等函数允许您确定与特定轮廓相对应的连接组件的基本属性。

如果您更喜欢使用较新的 C++ 接口(与我上面描述的较旧的 C 样式接口相反),那么函数名称是 similar。

【讨论】:

不要将findContours 用于连接的组件。它非常缓慢。【参考方案3】:

编译时设置 -std=c++0x 选项

.h 文件

//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 文件

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

【讨论】:

total_pix 不是一个常量表达式 - 至少 Visual Studio 2012 会编译它,因为它需要一个编译时常量来在堆栈上创建一个数组。使用 std::vector&lt;int&gt; frame_label(total_pix);std::vector&lt;int&gt; root_map(total_pix); 代替为我解决了这个问题。 你能在下面评论marko.ristin的问题吗? 不相交集非常高效,解决方案结构良好!太棒了! ConnectedComponent::ConnectedComponent(int x, int y) : m_bb(x,y,1,1), m_pixel_count(1), m_pixels() m_pixels = std::make_shared > (); /* 没有添加 (x,y) 到 m_pixels 的错误 */ 我认为这里有一个错误,对吧?【参考方案4】:

从 3.0 版本开始,OpenCV 具有connectedComponents 功能。

【讨论】:

【参考方案5】:

按照上面假设 4 个连接组件的 DXM 代码,这里是一个用于检测 8 个连接组件的“findCC”版本。

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;

【讨论】:

【参考方案6】:

你可以使用cv::connectedComponentsWithStats()函数。

这是一个例子。

    // ...
    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);

        // ...
    

【讨论】:

以上是关于OpenCV 中的连接组件的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV 2.4.4 中的轮廓/连接组件

如何在openCV中对连接的组件进行分割?

python中的连接组件标签

如何在python中使用openCV的连接组件和统计信息?

如何使用 OpenCV 将细线提取为单独的轮廓/连接组件?

opencv——连通域标记与分析