OpenCV4笔记之连通域分析

描述

Part11. 图像的连通域以及连通域分析

在该系列第六篇文章中,曾经介绍过连通的概念,下面再来回顾一下。

连通:若 S 是图像中的一个像素子集,对于任意的 。如果存在一条由 S 中像素组成的从 p 到 q 的通路,则称 p 在像素集 S 中与 q 连通

连通域是指具有相同属性的连通集合。例如,在一个二值图像中,具有相同像素值的区域构成一个连通域。

所以,连通连通域是两个不同的概念,连通域连通的子集。连通域具有以下特性:

一个连通域中的所有像素都具有相同的性质。

一个连通域中的任意两个像素都可以通过连续的路径连接起来。

一个连通域可以是单连通的,也可以是多连通的。

连通域分析是指在图像中查找标记连通域的算法。这是一种常用的图像处理技术,可以用于目标检测、图像分割和形状识别等等。本文是基于二值图像进行连通域的分析。

Part22. 连通域分析的算法

连通域分析的算法可以分为以下几类:

基于标记的算法:该类算法首先将每个像素分配一个唯一的标记,然后使用某种策略将具有相同标记的像素连接起来。基于标记的算法包括两遍扫描法、种子填充法和快速连通域查找法。

基于邻域查找的算法:该类算法从图像中的一个起始像素开始,逐个检查其邻域像素,如果邻域像素具有相同像素值,则将其加入到当前连通域中。基于邻域查找的算法包括深度优先搜索(DFS)和广度优先搜索(BFS)。

基于分割的算法:该类算法将图像分割成多个区域,每个区域都具有相同的像素值。基于分割的算法包括图论方法和区域生长法。

其中,基于标记的算法是比较常用的连通域分析的算法。

12.1 两遍扫描法

两遍扫描算法的步骤如下

第一遍扫描

从左到右,从上到下遍历图像。

将每个有效像素赋予一个唯一的标记。

第二遍扫描

再次从左到右,从上到下遍历图像。

检查两个相邻像素是否具有相同的标记。

如果两个相邻像素具有相同的标记,则将它们连接起来。

算法two-pass.gif

22.2 种子填充法

种子填充算法的步骤如下:

初始化:将图像中的所有像素标记为未访问。

选择一个起始像素作为种子。

将种子像素标记为已访问。

检查种子像素的邻域像素。

如果邻域像素具有相同的像素值,则将其标记为已访问。

如果邻域像素具有不同的像素值,则忽略。

重复步骤 4,直到图像中的所有像素都被标记为已访问或直到没有未访问的邻域像素为止。

算法seed-filling.gif

Part33. OpenCV 自带的连通域函数

OpenCV 提供了两个函数:connectedComponents()、connectedComponentsWithStats() 在二值图像中查找连通域。

下面的例子,在图中找到连通域并标记不同的颜色。

 

#include 
#include 
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"

using namespace std;
using namespace cv;

void labelColor(Mat& labelImg, Mat& dst)
{
    map colors;

    int width = labelImg.cols;
    int height = labelImg.rows;

    dst = Mat::zeros(labelImg.size(), CV_8UC3);

    uchar r = 255 * (rand()/(1.0 + RAND_MAX));
    uchar g = 255 * (rand()/(1.0 + RAND_MAX));
    uchar b = 255 * (rand()/(1.0 + RAND_MAX));

    for (int i = 0; i < height; i++)
    {
        int* data_src = (int*)labelImg.ptr(i);
        uchar* data_dst = dst.ptr(i);

        for (int j = 0; j < width; j++)
        {
            int pixelValue = data_src[j];
            if (pixelValue >= 1)
            {
                if (colors.count(pixelValue) == 0)
                {
                    colors[pixelValue] = Scalar(b,g,r);
                    r = 255 * (rand()/(1.0 + RAND_MAX));
                    g = 255 * (rand()/(1.0 + RAND_MAX));
                    b = 255 * (rand()/(1.0 + RAND_MAX));
                }

                Scalar color = colors[pixelValue];
                *data_dst++ = color[0];
                *data_dst++ = color[1];
                *data_dst++ = color[2];
            }
            else
            {
                data_dst++;
                data_dst++;
                data_dst++;
            }
        }
    }
}

int main(int argc, char **argv) {
    Mat src = imread(".../coins.jpg");
    imshow("src", src);

    Mat gray,thresh;
    cvtColor(src, gray, cv::COLOR_BGR2GRAY); // 灰度化
    imshow("gray", gray);

    Mat gauss;
    GaussianBlur(gray, gauss, Size(15, 15),0); //降噪

    threshold(gauss, thresh,0,255,THRESH_BINARY | THRESH_OTSU );
    imshow("thresh", thresh);

    Mat labels, stats, centroids;
    int num_labels = connectedComponents(thresh, labels,8,CV_32S);
    cout << "num_labels = " << num_labels << endl;

    Mat result;
    labelColor(labels,result);
    imshow("Connected Components", result);

    waitKey(0);

    return 0;
}
原图和灰度化.png 二值化和标记连通域.png
int connectedComponents(InputArray image, OutputArray labels,int connectivity, int ltype, int ccltype);

 

其各个参数的含义:

第一个参数 image:输入图像必须是二值图像1。第二个参数 labels:输出图像,其中每个像素值表示其所在连通域的标签。标签的值从 0 开始,依次递增。第三个参数 connectivity:标记连通域时使用的邻域种类。可选的是 4、8,默认值是 8。第四个参数 ltype:输出标签的类型。可以设置为 CV_32S 或 CV_16U,默认值是 CV_32S 。第五个参数 ccltype:标记连通域分析算法的类型。

参数类型 作用
CCL_WU 0 8 邻域和 4 邻域均用 SAUF 算法。
CCL_DEFAULT -1 8 邻域用 BBDT 算法,4 邻域用 SAUF 算法。
CCL_GRANA 1 8 邻域用 BBDT 算法,4 邻域用 SAUF 算法。

connectedComponentsWithStats() 函数除了返回每个连通域的标签之外,还返回每个连通域的其他信息,包括:

面积

外接矩形

中心坐标

 

int main(int argc, char **argv) {
    Mat src = imread(".../coins.jpg");
    imshow("src", src);

    Mat gray,thresh;
    cvtColor(src, gray, cv::COLOR_BGR2GRAY); // 灰度化
    imshow("gray", gray);

    Mat gauss;
    GaussianBlur(gray, gauss, Size(15, 15),0); //降噪

    threshold(gauss, thresh,0,255,THRESH_BINARY | THRESH_OTSU );
    imshow("thresh", thresh);

    Mat labels, stats, centroids;
    int num_labels = connectedComponentsWithStats(thresh, labels, stats, centroids);
    cout << "num_labels = " << num_labels << endl;

    for (int i = 1; i < num_labels; i++)
    {
        int area = stats.at(i, CC_STAT_AREA);
        int left = stats.at(i, CC_STAT_LEFT);
        int top = stats.at(i, CC_STAT_TOP);
        int width = stats.at(i, CC_STAT_WIDTH);
        int height = stats.at(i, CC_STAT_HEIGHT);
        int cx = centroids.at(i, 0);
        int cy = centroids.at(i, 1);

        cout << "Object " << i << ": " << "Area=" << area << ", Left=" << left << ", Top=" << top << ", Width=" << width << ", Height=" << height << ", Centroid=(" << cx << ", " << cy << ")" << endl;

        rectangle(src, Point(left, top), Point(left + width, top + height), Scalar(0,0,255), 2);
        circle(src, Point(cx, cy), 2, Scalar(0,0,255), 2);
    }
    imshow("Connected Components", src);
    waitKey(0);

    return 0;
}

 

执行结果:

 

num_labels = 8
Object 1: Area=17061, Left=344, Top=160, Width=144, Height=151, Centroid=(415, 234)
Object 2: Area=9655, Left=592, Top=411, Width=108, Height=114, Centroid=(645, 467)
Object 3: Area=9558, Left=776, Top=441, Width=108, Height=113, Centroid=(828, 497)
Object 4: Area=17874, Left=435, Top=517, Width=150, Height=152, Centroid=(509, 592)
Object 5: Area=17502, Left=660, Top=648, Width=148, Height=151, Centroid=(733, 723)
Object 6: Area=10420, Left=726, Top=864, Width=114, Height=117, Centroid=(782, 922)
Object 7: Area=15634, Left=628, Top=1075, Width=141, Height=143, Centroid=(698, 1146)

 

Part44. 总结

连通域分析是图像处理中常用的算法之一,用于在二值图像中找到具有相同像素值且相互连接的区域。它在图像处理中具有广泛的应用场景。

 

  审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分