OpenCV学习(二十一) :计算图像连通分量:connectedComponents(),connectedComponentsWithStats()

1、connectedComponents()函数

Connected Components即连通体算法用id标注图中每个连通体,将连通体中序号最小的顶点的id作为连通体的id。如果在图G中,任意2个顶点之间都存在路径,那么称G为连通图,否则称该图为非连通图,则其中的极大连通子图称为连通体,如下图所示,该图中有两个连通体:

Java 使用opencv连接蓝牙摄像头 opencv connect_i++


计算二值图像中为图像的连通分量标注(标记)

int  cv::connectedComponents(
cv::InputArrayn image,                // 输入8位单通道(二进制)
cv::OutputArray labels,               // 输出标签图
int             connectivity = 8,     // 4-或8-连接组件
int             ltype        = CV_32S // 输出标签类型 (CV_32S or CV_16U)
    );

2、connectedComponentsWithStats()函数

这是一个重载的成员函数,它与上述函数的不同之处在于它只接受什么参数。
注意0的区域标识的是background,而centroids则对应的是中心点,而label则对应于表示是当前像素是第几个轮廓

int  cv::connectedComponentsWithStats (
cv::InputArrayn image,                // 输入8位单通道(二进制)
cv::OutputArray labels,               // 输出标签地图
cv::OutputArray stats,                // 统计量的Nx5矩阵(CV_32S):分别对应各个轮廓的包围框的起始点坐标x,y,
									  // 各个轮廓的包围框的 width,height和面积:
                                                     // [x0, y0, width0, height0, area0;
                                                     //  ... ; x(N-1), y(N-1), width(N-1),
                                                     // height(N-1), area(N-1)]
cv::OutputArray centroids,            // Nx2 CV_64F中心矩阵:
                                                      // [ cx0, cy0; ... ; cx(N-1), cy(N-1)]
int             connectivity = 8,     // 4-或8-连接组件
int             ltype        = CV_32S // 输出标签类型 (CV_32S or CV_16U)
);

3、示例:

#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main()
{
    Mat src =imread("F:/C++/2. OPENCV 3.1.0/TEST/test5.PNG",1);
    if(!src.data ) { printf("读取图片错误,请确定目录下是否有imread函数指定图片存在~! \n"); return false; }
    imshow( "image", src );
    // 转灰度
    Mat src_gray;
    cvtColor( src, src_gray, CV_BGR2GRAY );

    cv::Mat  img_edge, labels, img_color, stats,centroids;
    // 二值化
    cv::threshold(src_gray, img_edge, 125, 255, cv::THRESH_BINARY);
    cv::imshow("Image before threshold", img_edge);
    // 白色代表有数据,黑色代表没有数据,所以图像输入之前要转换成”黑底白图“
    bitwise_not(img_edge,img_edge);     // 该函数计算输入数组的逐元素逐位反转:
    cv::imshow("Image after threshold", img_edge);
    // 计算连通分量
    //  labels 输出标签图
    // stats Nx5矩阵(CV_32S): 分别对应各个轮廓的x,y,width,height和面积
    int nccomps = cv::connectedComponentsWithStats ( img_edge, labels, stats, centroids);
    cout << "检测到总连接组件: " << nccomps << endl;

    vector<cv::Vec3b> colors(nccomps+1);
    colors[0] = Vec3b(0,0,0);                    // 背景像素保持黑色。
    for( int i = 1; i < nccomps; i++ )         // 为每个标签设置颜色
    {
        colors[i] = Vec3b(rand()%256, rand()%256, rand()%256);
        if( stats.at<int>(i, cv::CC_STAT_AREA) < 200 )      // 面积小于200 的
            colors[i] = Vec3b(0,0,0);                                   // 小区域也被涂成黑色
    }
    //
    img_color = Mat::zeros(src.size(), CV_8UC3);
    for( int y = 0; y < img_color.rows; y++ )
        for( int x = 0; x < img_color.cols; x++ )
        {
            int label = labels.at<int>(y, x);       // 获得每个 轮廓图标签
            CV_Assert(0 <= label && label <= nccomps);
            img_color.at<cv::Vec3b>(y, x) = colors[label];
        }
    cv::imshow("Labeled map", img_color);

    waitKey(0);
    return 0;
}

结果:

Java 使用opencv连接蓝牙摄像头 opencv connect_点集_02


Java 使用opencv连接蓝牙摄像头 opencv connect_点集_03


Java 使用opencv连接蓝牙摄像头 opencv connect_连通分量_04


分析:

1、看输出参数stats:Nx5矩阵(CV_32S),其中第1 2 6 个的面积小于200:

Java 使用opencv连接蓝牙摄像头 opencv connect_i++_05


2、labels 标签图中:

Java 使用opencv连接蓝牙摄像头 opencv connect_连通分量_06

4、通过findContours(),drawContours()函数计算连通量

在以前,常用的方法是”是先调用 cv::findContours() 函数(传入cv::RETR_CCOMP 标志),随后在得到的连通区域上循环调用 cv::drawContours() “
示例:

#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

// 寻找最大的轮廓、按面积排序 函数
vector<vector<Point>> connection_sort(Mat src)
{
    vector<vector<Point>>contours;
    findContours(src.clone(),contours,CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE);
    //由于给大的区域着色会覆盖小的区域,所以首先进行排序操作
    //冒泡排序算法,由小到大排序
    vector<Point> vptmp;
    for(size_t i=1;i<contours.size();i++)
    {
        for(size_t j=contours.size()-1;j>=i;j--)
        {
            if (contourArea(contours[j]) < contourArea(contours[j-1]))
            {
                vptmp = contours[j-1];
                contours[j-1] = contours[j];
                contours[j] = vptmp;
            }
        }
    }
    // 绘制 轮廓
    Mat draw = Mat::zeros(src.size(),CV_8UC3);
   for( int index = 0; index < contours.size(); index ++ ) // 绘制所有内外轮廓
   {
       Scalar color( rand()&255, rand()&255, rand()&255 );
       drawContours( draw, contours, index, color, 1, LINE_8 );    //CV_FILLED -1
       // 添加标记
       char text[100];
       sprintf(text, "%d", index+1);
       RotatedRect box = minAreaRect(contours.at(index)); // 获取中心
       putText(draw, text, box.center, FONT_HERSHEY_SIMPLEX, 0.5, cvScalar(0,0,255),1.5);
   }
    imshow("contours_sort_drawing",draw);

    return contours; // 返回排序后的 轮廓集
}

//  寻找并绘制出彩色联通区域
vector<Point> FindBigestContour(Mat src,int& imax, int& imaxcontour)
{
//     imax = 0; //代表最大轮廓的序号
//     imaxcontour = -1; //代表最大轮廓的大小
    vector<vector<Point>>contours,contour_max;
    findContours(src.clone(),contours,CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE);
    for (int i=0;i<contours.size();i++)	// 遍历每个轮廓点集
    {
        int itmp = contourArea(contours[i]);// 计算每个轮廓点集面积
        if (imaxcontour < itmp )
        {
            imax = i;
            imaxcontour = itmp;
        }
    }
    // 显示 最大轮廓
    Mat draw = Mat::zeros(src.size(),CV_8UC3);
    drawContours( draw, contours, imax, Scalar(0,255,0), 1, LINE_8 );
    imshow( "Contours_Max", draw );

    return contours[imax];
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Mat src =imread("F:/C++/2. OPENCV 3.1.0/TEST/test3.PNG",1);
    if(!src.data ) { printf("读取图片错误,请确定目录下是否有imread函数指定图片存在~! \n"); return false; }
    imshow( "image", src );
    // 转灰度
    Mat src_gray;
    cvtColor( src, src_gray, CV_BGR2GRAY );
    src_gray = src_gray >1;
    imshow( "src_gray", src_gray );

    // 获得 新排序后轮廓、显示
    vector<vector<Point>> contours_sort;
    contours_sort = connection_sort(src_gray);    // 绘制最大轮廓

    // 显示 最大轮廓
    int imax=0,imax_area=0;
    vector<vector<Point>>contour_max;
    contour_max.push_back( FindBigestContour(src_gray,imax,imax_area)); // 获得最大轮廓点集
    printf("  %d 轮廓为面积最大轮廓=%d (面积)",imax,imax_area);

    waitKey(0);
    return a.exec();
}

结果:

Java 使用opencv连接蓝牙摄像头 opencv connect_i++_07


Java 使用opencv连接蓝牙摄像头 opencv connect_i++_08