图像连通域分析

连通域分割原理

一般对图像进行连通域分析时候,一般会先对图像进行二值化处理 两遍法:遍历第一行,遇到黄色框设置为1,遍历第2行时,如果上方与左方有标记数值,则把该数值设置为二者较小的数值,所以(2,1)和(2,2)先被标记为2,而(2,3)被标记为1,遍历第四行时候把(4,2)遍历标记为3 可以得到 接下来进行第二次遍历,将两个临近的像素相互同一成一个值 得到

连通域分割函数

还有最后一个参数用于选择算法 示例

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
    //对图像进行距离变换
    Mat img = imread("rice.png");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    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)  //背景的黑色不改变,如果像素为0说明为黑色背景
            {
                continue;
            }
            result.at<Vec3b>(row, col) = colors[label];
        }
    }
    //显示结果
    imshow("原图", img);
    imshow("标记后的图像", result);

    waitKey(0);
    return 0;
}

统计连通域并存放统计信息 连通域存放的信息

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
    system("color F0");  //更改输出界面颜色
    //对图像进行距离变换
    Mat img = imread("rice.png");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    imshow("原图", img);
    Mat rice, riceBW;

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

    //生成随机颜色,用于区分不同连通域
    RNG rng(10086);
    Mat out, stats, centroids;
    //统计图像中连通域的个数
    int number = connectedComponentsWithStats(riceBW, out, stats, centroids, 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 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, colors[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("标记后的图像", img);

    waitKey(0);
    return 0;
}

距离变换

像素间距离

距离变换函数 计算的是所有非零像素到零像素的距离,模板尺寸建议为5

图像腐蚀

目的:去除图像中微笑物体,分离较近的两个物体

图像腐蚀原理

结构元素遍历整个图像,当遇到结构元素的四个像素不能覆盖则删除该像素

图像腐蚀函数

获得结构元素 腐蚀函数 示例

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;
//绘制包含区域函数
void drawState(Mat& img, int number, Mat centroids, Mat stats, String str) {
    RNG rng(10086);
    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);
    }

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

        // 中心位置绘制
        circle(img, Point(center_x, center_y), 2, Scalar(0, 255, 0), 2, 8, 0);
        // 外接矩形
        Rect rect(x, y, w, h);
        rectangle(img, rect, colors[i], 1, 8, 0);
        putText(img, format("%d", i), Point(center_x, center_y),
            FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 255), 1);
    }
    imshow(str, img);
}

int main()
{
    //生成用于腐蚀的原图像
    Mat src = (Mat_<uchar>(6, 6) << 0, 0, 0, 0, 255, 0,
        0, 255, 255, 255, 255, 255,
        0, 255, 255, 255, 255, 0,
        0, 255, 255, 255, 255, 0,
        0, 255, 255, 255, 255, 0,
        0, 0, 0, 0, 0, 0);
    Mat struct1, struct2;
    struct1 = getStructuringElement(0, Size(3, 3));  //矩形结构元素
    struct2 = getStructuringElement(1, Size(3, 3));  //十字结构元素

    Mat erodeSrc;  //存放腐蚀后的图像
    erode(src, erodeSrc, struct2);
    namedWindow("src", WINDOW_GUI_NORMAL);
    namedWindow("erodeSrc", WINDOW_GUI_NORMAL);
    imshow("src", src);
    imshow("erodeSrc", erodeSrc);

    Mat LearnCV_black = imread("LearnCV_black.png", IMREAD_ANYCOLOR);
    Mat LearnCV_write = imread("LearnCV_white.png", IMREAD_ANYCOLOR);
    if (LearnCV_black.empty()|| LearnCV_write.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    Mat erode_black1, erode_black2, erode_write1, erode_write2;
    //黑背景图像腐蚀
    erode(LearnCV_black, erode_black1, struct1);
    erode(LearnCV_black, erode_black2, struct2);
    imshow("LearnCV_black", LearnCV_black);
    imshow("erode_black1", erode_black1);
    imshow("erode_black2", erode_black2);

    //白背景腐蚀
    erode(LearnCV_write, erode_write1, struct1);
    erode(LearnCV_write, erode_write2, struct2);
    imshow("LearnCV_write", LearnCV_write);
    imshow("erode_write1", erode_write1);
    imshow("erode_write2", erode_write2);

    //验证腐蚀对小连通域的去除
    Mat img = imread("rice.png");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    Mat img2;
    copyTo(img, img2, img);  //克隆一个单独的图像,用于后期图像绘制
    Mat rice, riceBW;

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

    Mat out, stats, centroids;
    //统计图像中连通域的个数
    int number = connectedComponentsWithStats(riceBW, out, stats, centroids, 8, CV_16U);
    drawState(img, number, centroids, stats, "未腐蚀时统计连通域");  //绘制图像

    erode(riceBW, riceBW, struct1);  //对图像进行腐蚀
    number = connectedComponentsWithStats(riceBW, out, stats, centroids, 8, CV_16U);
    drawState(img2, number, centroids, stats, "腐蚀后统计连通域");  //绘制图像

    waitKey(0);
    return 0;
}

图像膨胀

结构中心元素和原图像非零元素覆盖,然后把结构元素的其他位置覆盖为1,知道覆盖完原图的所有非零元素 结构元素和上一个函数相同

图像膨胀操作函数

示例

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
    //生成用于腐蚀的原图像
    Mat src = (Mat_<uchar>(6, 6) << 0, 0, 0, 0, 255, 0,
        0, 255, 255, 255, 255, 255,
        0, 255, 255, 255, 255, 0,
        0, 255, 255, 255, 255, 0,
        0, 255, 255, 255, 255, 0,
        0, 0, 0, 0, 0, 0);
    Mat struct1, struct2;
    struct1 = getStructuringElement(0, Size(3, 3));  //矩形结构元素
    struct2 = getStructuringElement(1, Size(3, 3));  //十字结构元素

    Mat erodeSrc;  //存放膨胀后的图像
    dilate(src, erodeSrc, struct2);
    namedWindow("src", WINDOW_GUI_NORMAL);
    namedWindow("dilateSrc", WINDOW_GUI_NORMAL);
    imshow("src", src);
    imshow("dilateSrc", erodeSrc);

    Mat LearnCV_black = imread("LearnCV_black.png", IMREAD_ANYCOLOR);
    Mat LearnCV_write = imread("LearnCV_white.png", IMREAD_ANYCOLOR);
    if (LearnCV_black.empty() || LearnCV_write.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }

    Mat dilate_black1, dilate_black2, dilate_write1, dilate_write2;
    //黑背景图像膨胀
    dilate(LearnCV_black, dilate_black1, struct1);
    dilate(LearnCV_black, dilate_black2, struct2);
    imshow("LearnCV_black", LearnCV_black);
    imshow("dilate_black1", dilate_black1);
    imshow("dilate_black2", dilate_black2);

    //白背景图像膨胀
    dilate(LearnCV_write, dilate_write1, struct1);
    dilate(LearnCV_write, dilate_write2, struct2);
    imshow("LearnCV_write", LearnCV_write);
    imshow("dilate_write1", dilate_write1);
    imshow("dilate_write2", dilate_write2);

    //比较膨胀和腐蚀的结果
    Mat erode_black1, resultXor, resultAnd;
    erode(LearnCV_black, erode_black1, struct1);
    bitwise_xor(erode_black1, dilate_write1, resultXor);
    bitwise_and(erode_black1, dilate_write1, resultAnd);
    imshow("resultXor", resultXor);
    imshow("resultAnd", resultAnd);
    waitKey(0);
    return 0;
}

形态学应用

开运算

闭运算

形态学梯度

顶帽运算

黑帽运算

击中击不中变换

原图像中必须和结构元素必须有相同布局才行

相关函数

示例

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
    //用于验证形态学应用的二值化矩阵
    Mat src = (Mat_<uchar>(9, 12) << 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
        0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
        0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
        0, 255, 255, 255, 0, 255, 255, 255, 0, 0, 0, 0,
        0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
        0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
        0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
    namedWindow("src", WINDOW_NORMAL);  //可以自由调节显示图像的尺寸
    imshow("src", src);
    //3×3矩形结构元素
    Mat kernel = getStructuringElement(0, Size(3, 3));

    //对二值化矩阵进行形态学操作
    Mat open, close, gradient, tophat, blackhat, hitmiss;

    //对二值化矩阵进行开运算
    morphologyEx(src, open, MORPH_OPEN, kernel);
    namedWindow("open", WINDOW_NORMAL);  //可以自由调节显示图像的尺寸
    imshow("open", open);

    //对二值化矩阵进行闭运算
    morphologyEx(src, close, MORPH_CLOSE, kernel);
    namedWindow("close", WINDOW_NORMAL);  //可以自由调节显示图像的尺寸
    imshow("close", close);

    //对二值化矩阵进行梯度运算
    morphologyEx(src, gradient, MORPH_GRADIENT, kernel);
    namedWindow("gradient", WINDOW_NORMAL);  //可以自由调节显示图像的尺寸
    imshow("gradient", gradient);

    //对二值化矩阵进行顶帽运算
    morphologyEx(src, tophat, MORPH_TOPHAT, kernel);
    namedWindow("tophat", WINDOW_NORMAL);  //可以自由调节显示图像的尺寸
    imshow("tophat", tophat);

    //对二值化矩阵进行黑帽运算
    morphologyEx(src, blackhat, MORPH_BLACKHAT, kernel);
    namedWindow("blackhat", WINDOW_NORMAL);   //可以自由调节显示图像的尺寸
    imshow("blackhat", blackhat);

    //对二值化矩阵进行击中击不中变换
    morphologyEx(src, hitmiss, MORPH_HITMISS, kernel);
    namedWindow("hitmiss", WINDOW_NORMAL);  //可以自由调节显示图像的尺寸
    imshow("hitmiss", hitmiss);

    //用图像验证形态学操作效果
    Mat keys = imread("keys.jpg", IMREAD_GRAYSCALE);
    imshow("原图像", keys);
    threshold(keys, keys, 80, 255, THRESH_BINARY);
    imshow("二值化后的keys", keys);

    //5×5矩形结构元素
    Mat kernel_keys = getStructuringElement(0, Size(5, 5));
    Mat open_keys, close_keys, gradient_keys, tophat_keys, blackhat_keys, hitmiss_keys;

    //对图像进行开运算
    morphologyEx(keys, open_keys, MORPH_OPEN, kernel_keys);
    imshow("open_keys", open_keys);

    //对图像进行闭运算
    morphologyEx(keys, close_keys, MORPH_CLOSE, kernel_keys);
    imshow("close_keys", close_keys);

    //对图像进行梯度运算
    morphologyEx(keys, gradient_keys, MORPH_GRADIENT, kernel_keys);
    imshow("gradient_keys", gradient_keys);

    //对图像进行顶帽运算
    morphologyEx(keys, tophat_keys, MORPH_TOPHAT, kernel_keys);
    imshow("tophat_keys", tophat_keys);

    //对图像进行黑帽运算
    morphologyEx(keys, blackhat_keys, MORPH_BLACKHAT, kernel_keys);
    imshow("blackhat_keys", blackhat_keys);

    //对图像进行击中击不中变换
    morphologyEx(keys, hitmiss_keys, MORPH_HITMISS, kernel_keys);
    imshow("hitmiss_keys", hitmiss_keys);

    waitKey(0);
    return 0;
}

图像细化(骨架化)

删除图像较宽的部分 遍历两次代码,将满足左侧和满足右侧条件的像素指定为待删除点 然后把待删除点全部删除

相关函数

thinning函数在扩展模块里面,需要额外安装 我没装凑活看把

#include <opencv2\opencv.hpp>
#include <opencv2\ximgproc.hpp>  //细化函数thining所在的头文件
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
    //中文字进行细化
    Mat img = imread("LearnCV_black.png", IMREAD_ANYCOLOR);
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    //英文字+实心圆和圆环细化
    Mat words = Mat::zeros(100, 200, CV_8UC1);  //创建一个黑色的背景图片
    putText(words, "Learn", Point(30, 30), 2, 1, Scalar(255), 2);  //添加英文
    putText(words, "OpenCV 4", Point(30, 60), 2, 1, Scalar(255), 2);
    circle(words, Point(80, 75), 10, Scalar(255), -1);  //添加实心圆
    circle(words, Point(130, 75), 10, Scalar(255), 3);  //添加圆环

    //进行细化
    Mat thin1, thin2;
    ximgproc::thinning(img, thin1, 0);  //注意类名
    ximgproc::thinning(words, thin2, 0);

    //显示处理结果
    imshow("thin1", thin1);
    imshow("img", img);
    namedWindow("thin2", WINDOW_NORMAL);
    imshow("thin2", thin2);
    namedWindow("words", WINDOW_NORMAL);
    imshow("words", words);
    waitKey(0);
    return 0;
}