一、连通域分析

1、连通域分割原理

1.1.1 像素邻域介绍

  • 4-邻域:对于像素(x,y),上下左右4个像素被称为 4-邻域。4-邻域的四个像素分别是:(x,y-1)、(x-1,y)、(x+1,y)、(x,y+1)。
  • D-邻域:对于像素(x, y), 其左上、右上、左下、右下的四个对角上的像素组成了 D-邻域。D-邻域四个像素分别是:(x - 1, y - 1)、( x + 1, y - 1)、(x - 1, y + 1)、(x + 1, y + 1)。
  • 8-邻域:对于像素(x,y),4-邻域的点和 D-邻域的点组成了 8-邻域。
1.1.2 两遍法分割连通域

两遍法分割连通域是通过对二值图像进行两次扫描,第一次为前景像素分配临时标签并记录标签之间的等价关系,第二次根据等价关系将所有属于同一连通区域的像素统一为相同标签,从而完成连通域标记。

  1. 输入二值图像
  2. 第一次扫描(从左到右,从上到下)
  3. 根据邻域分配标签 + 记录标签等价关系
  4. 使用并查集合并等价类
  5. 第二次扫描
  6. 将所有临时标签替换为最终统一标签
  7. 输出连通域标记结果

2、连通域分割函数

  • 只分割连通域的函数
/* 用途:用于对二值图像进行连通域标记,将图像中所有相互连接的像素区域
       分配唯一的标签编号,从而实现区域分割
       函数返回连通域的数量(包含背景标签0) */
int cv::connectedComponents(InputArray image, OutputArray labels,
                                     int connectivity = 8, int ltype = CV_32S);
/*
image:待标记连通域的输入图像(单通道CV_8U,通常为二值图)
labels:标记不同连通域后的输出图像,与输入图像具有相同的尺寸
connectivity:标记连通域时使用的邻域种类,4表示4-邻域,8表示8-邻域,默认参数为8
ltype:输出图像的数据类型,目前支持CV_32S和CV_16U两种数据类型,默认参数为CV_32S
*/
  • 分割并统计连通域信息的函数
/* 用途:在进行连通域标记的同时,获取每个连通区域的详细统计信息,
       包括区域位置、尺寸、面积以及中心位置等。
       相比connectedComponents,它不仅完成区域分割,还提供完整的区域属性,
       便于直接进行目标筛选、分析与测量,是目标检测与分析中的核心函数之一。 */
int cv::connectedComponentsWithStats(InputArray image, OutputArray labels,
                                    OutputArray stats, OutputArray centroids,
                                    int connectivity = 8, int ltype = CV_32S);
/*
image:待标记连通域的输入图像(单通道CV_8U,通常为二值图)
labels:标记不同连通域后的输出图像,与输入图像具有相同的尺寸
stats:不同连通域的统计信息矩阵,矩阵的数据类型为CV_32S。矩阵中第i行时
标签为i的连通域的统计特性
centroids:每个连通域的质心坐标,数据类型为CV_64F
connectivity:标记连通域时使用的邻域种类,4表示4-邻域,8表示8-邻域,默认参数为8
ltype:输出图像的数据类型,目前支持CV_32S和CV_16U两种数据类型,默认参数为CV_32S
*/
第三个参数存放的连通域的信息
标志参数 简记 作用
CC_STAT_LEFT 0 连通域内最左侧像素的x坐标,它是水平方向上的包含连通域边界框的开始
CC_STAT_TOP 1 连通域内最左侧像素的y坐标,它是垂直方向上的包含连通域边界框的开始
CC_STAT_WIDTH 2 包含连通域边界框的水平长度
CC_STAT_HEIGHT 3 包含连通域边界框的垂直长度
CC_STAT_AREA 4 连通域的面积(以像素为单位)
CC_STAT_MAX 5 统计信息种类数目,无实际含义

3、示例代码

    QString imgPath = QApplication::applicationDirPath() + "/Images";
    cv::String s_imgPath = imgPath.toLocal8Bit().data();
    Mat img = imread(s_imgPath + "/rice.png");

    if (img.empty())
    {
        qDebug() << "图片加载失败, 请确认图像文件名称是否正确";
        return;
    }

    Mat rice, riceBW;

    /*将图像转成二值图像,用于统计连通域*/
    cvtColor(img, rice, COLOR_BGR2GRAY);
    threshold(rice, riceBW, 50, 255, THRESH_BINARY);

    /*生成随机颜色,用于区分不同连通域*/
    RNG rng(10086);
    Mat out;
    int number = connectedComponents(riceBW, out, 8, CV_16U);/*统计图像中连通域的个数*/
    vector<Vec3b> colors;
    for (int i = 0; i < number; i++)
    {
        /*使用均匀分布的随机数确定颜色*/
        Vec3b vec3 = Vec3b(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
        colors.push_back(vec3);
    }
    Mat result = Mat::zeros(rice.size(), img.type());
    int w = result.cols;
    int h = result.rows;

    for (int row = 0; row < h; row++)
    {
        for (int col = 0; col < w; col++)
        {
            int label = out.at<uint16_t>(row, col);
            if (label == 0)
            {
                continue;
            }
            result.at<Vec3b>(row, col) = colors[label];
        }
    }
    imshow("Original Img", img);
    imshow("Marked Image", result);
    waitKey(0);
    Mat stats, centroids;
    number = connectedComponentsWithStats(riceBW, out, stats, centroids, 8, CV_16U);
    vector<Vec3b> color_new;
    for (int i = 0; i < number; i++)
    {
        Vec3b vec3 = Vec3b(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
        color_new.push_back(vec3);
    }

    for (int i = 1; i < number; i++)
    {
        /*中心位置*/
        int center_x = centroids.at<double>(i, 0);
        int center_y = centroids.at<double>(i, 1);
        /*矩形边框*/
        int x = stats.at<int>(i, CC_STAT_LEFT);
        int y = stats.at<int>(i, CC_STAT_TOP);
        int w = stats.at<int>(i, CC_STAT_WIDTH);
        int h = stats.at<int>(i, CC_STAT_HEIGHT);
        int area = stats.at<int>(i, CC_STAT_AREA);

        /*中心位置绘制*/
        circle(img, Point(center_x, center_y), 2, Scalar(0, 255, 0), 2, 8, 0);
        /*外接矩形*/
        Rect rect(x, y, w, h);
        rectangle(img, rect, color_new[i], 1, 8, 0);
        putText(img, format("%d", i), Point(center_x, center_y),
            FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 255), 1);
        cout << "number: " << i << ",area: " << area << endl;

    }
    imshow("Marked Image", img);

    waitKey(0);
    destroyAllWindows();

二、图像距离变换

1、像素间距离

  • 欧氏距离:d = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2}
  • 街区距离:d = |x_1 - x_2| + |y_1 - y_2|
  • 棋盘距离:d = \max(|x_1 - x_2|, |y_1 - y_2|)

欧式距离:反映直线距离
街区距离:表示网格路径距离
棋盘距离:表示最大坐标差

2、距离变换函数

/* 用途:用于计算图像中每个像素到最近“零像素(背景)”的距离,
       生成一幅距离图(Distance Map)。结果中像素值越大,
       表示该点离背景越远,通常位于目标区域的中心位置 */
void cv::distanceTransform( InputArray src, OutputArray dst,
                            int distanceType, int maskSize, int dstType=CV_32F);
/*
src:输入图像,必须是CV_8U单通道图像(通常为二值图)
dst:输出图像,与输入图像具有相同的尺寸,数据类型为CV_8U或者CV_32F的单通道图像
distanceType:选择计算两个像素之间距离方法的标志
                DIST_L2:欧式距离(Euclidean Distance)
                DIST_L1:街区距离(Manhattan Distance)
                DIST_C:棋盘距离(Chebyshev Distance)
maskSize:距离变换掩码矩阵大小,可以选择尺寸为:DIST_MASK_3(3 * 3)、DIST_MASK_5(5 * 5)
dstType:输出图像的数据类型,可以是CV_8U或者CV_32F
*/

3、示例代码

    /*构建简易矩阵,用于求取像素之间的距离*/
    Mat a = (Mat_<uchar>(5, 5) << 1, 1, 1, 1, 1,
        1, 1, 1, 1, 1,
        1, 1, 0, 1, 1,
        1, 1, 1, 1, 1,
        1, 1, 1, 1, 1);
    Mat dist_L1, dist_L2, dist_C, dist_L12;

    /*计算街区距离*/
    distanceTransform(a, dist_L1, DIST_L1, 3, CV_8U);
    cout << "Block distance: " << dist_L1 << endl;
    /*计算欧式距离*/
    distanceTransform(a, dist_L2, DIST_L2, 5, CV_8U);
    cout << "Euclidean distance: " << dist_L2 << endl;
    /*计算棋盘距离*/
    distanceTransform(a, dist_C, DIST_C, 5, CV_8U);
    cout << "Board distance: " << dist_C << endl;

    cout << "Perform distance transformation on the image" << endl;
    waitKey(0);

    QString imgPath = QApplication::applicationDirPath() + "/Images";
    cv::String s_imgPath = imgPath.toLocal8Bit().data();
    Mat rice = imread(s_imgPath + "/rice.png", IMREAD_GRAYSCALE);

    if (rice.empty())
    {
        qDebug() << "图片加载失败, 请确认图像文件名称是否正确";
        return;
    }

    Mat riceBW, riceBW_INV;
    /*将图像转成二值图像,同时把黑白区域颜色互换*/
    threshold(rice, riceBW, 50, 255, THRESH_BINARY);
    threshold(rice, riceBW_INV, 50, 255, THRESH_BINARY_INV);
    /*距离变换*/
    Mat dist, dist_inv;
    distanceTransform(riceBW, dist, DIST_L1, 3, CV_32F);/*为了显示清晰,将数据类型变成CV_32F*/
    distanceTransform(riceBW_INV, dist_inv, DIST_L1, 3, CV_8U);


    imshow("riceBW", riceBW);
    imshow("dist", dist);
    imshow("riceBW_INV", riceBW_INV);
    imshow("dist_inv", dist_inv);

    waitKey(0);
    destroyAllWindows();

更多推荐