OpenCV联合C++/Qt 学习笔记(十五)----形态学操作及应用

一、形态学操作

1、图像腐蚀

1.1.1 图像腐蚀目的
  • **去除图像中微小物体:**在二值图像中,一些由于噪声产生的小亮点、小区域通常面积较小。经过腐蚀处理后,这些小区域会被消除,从而达到去噪的效果,提高图像质量。
  • **分离较近的两个物体:**当两个目标之间连接较细或者距离非常接近时,腐蚀会使目标边界向内收缩,从而断开细小连接部分,使两个目标彼此分离,便于后续的目标检测、轮廓提取与连通域分析。
  • **细化目标边界:**腐蚀会减小目标区域面积,使目标轮廓更加紧凑,有利于后续形状分析与特征提取。
  • **配合膨胀完成开运算:**腐蚀通常与膨胀结合使用,先腐蚀后膨胀形成开运算,能够有效去除小噪声并保持主体形状。
1.1.2 图像腐蚀原理

中间的黄色十字区域为结构元素,红色部分为当前中心像素。

当结构元素覆盖区域内所有对应位置都为前景像素(值为1)时,中心像素才保留;只要有一个位置为背景(0),中心像素就被删除。

1.1.3 结构元素生成函数
cpp 复制代码
/* 用途:用于创建形态学操作所需的结构元素(卷积模板),
       生成指定形状和大小的核矩阵,供腐蚀、膨胀、开运算、
       闭运算等形态学处理函数使用 */
Mat cv::getStructuringElement(int shape, Size ksize, Point anchor = Point(-1,-1));
/*
shape:结构元素的种类
ksize:结构元素的尺寸大小
anchor:中心点的位置,默认参数为结构元素的几何中心点
*/
标志参数 简记 作用
MORPH_RECT 0 矩形结构元素,所有元素都为1
MORPH_CROSS 1 十字结构元素,中间的列和行元素为1
MORPH_ELLIPSE 2 椭圆结构元素,矩形的椭圆内接元素为1
[矩阵结构元素]
1.1.4 图像腐蚀操作函数
cpp 复制代码
/* 用途:用于对图像进行腐蚀操作,通过结构元素扫描图像,
       将目标区域边界向内收缩,使亮区域(白色前景)变小 */
void cv::erode( InputArray src, OutputArray dst, InputArray kernel,
            Point anchor = Point(-1,-1), int iterations = 1,
            int borderType = BORDER_CONSTANT,
            const Scalar& borderValue = morphologyDefaultBorderValue() );
/*
src:输入的待腐蚀图像,图像的通道数可以是任意的,但是图像的数据类型必须是CV_8U、CV_16U、
    CV_16S、CV_32F或CV_64F之一
dst:腐蚀后的输出图像,与输入图像src具有相同的尺寸和数据类型
kernel:用于腐蚀操作的结构元素,可以自己输入,也可以用getStructuringElement()函数生成
anchor:中心点再结构元素中的位置,默认参数为结构元素的几何中心点
iterations:腐蚀的次数
borderType:像素外推法选择标志
borderValue:边界不变的边界值
*/
1.1.5 示例代码
cpp 复制代码
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);
        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);

    }
    imshow(str, img);
}
void erodeStudy()
{
    /*生成用于腐蚀的原图像*/
    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(MORPH_RECT, Size(3, 3));/*矩形结构元素*/
    struct2 = getStructuringElement(MORPH_CROSS, 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);

    waitKey(0);

    QString imgPath = QApplication::applicationDirPath() + "/Images";
    cv::String s_imgPath = imgPath.toLocal8Bit().data();
    Mat LearnCV_black = imread(s_imgPath + "/LearnCV_black.png", IMREAD_ANYCOLOR);
    if (LearnCV_black.empty())
    {
        qDebug() << "图片加载失败, 请确认图像文件名称是否正确";
        return;
    }
    Mat erode_black1, erode_black2;
    /*黑背景图像腐蚀*/
    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);

    waitKey(0);
    Mat img = imread(s_imgPath + "/rice.png");
    if (img.empty())
    {
        qDebug() << "图片加载失败, 请确认图像文件名称是否正确";
        return;
    }
    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, "Blob Statistics Before Erosion");/*绘制图像*/

    erode(riceBW, riceBW, struct1);/*对图像进行腐蚀*/
    number = connectedComponentsWithStats(riceBW, out, stats, centroids, 8, CV_16U);
    drawState(img2, number, centroids, stats, "Blobs After Erosion");/*绘制图像*/

    waitKey(0);
    destroyAllWindows();
}

2、图像膨胀

1.2.1 图像膨胀目的
  • **扩大目标区域:**膨胀能够使前景目标变大,填补目标边界附近的空隙,使目标更加明显。
  • **填补目标内部的小孔洞:**当目标内部存在小黑洞或缺口时,膨胀可以填充这些小区域,使目标更加完整。
  • **连接相邻目标:**当两个目标距离较近时,膨胀会使边界向外扩张,从而将两个区域连接在一起。
  • **增强目标特征:**膨胀后目标区域更加明显。
  • 配合腐蚀完成闭运算: 膨胀通常与腐蚀结合使用,先膨胀再腐蚀形成闭运算,能够填补小孔洞、连接邻近区域、平滑边界。
1.2.2 图像膨胀原理

中间的黄色十字区域为结构元素,红色部分为当前中心像素。

只要结构元素覆盖区域中存在一个前景像素(值为1),当前中心位置就会被置为前景。

1.2.3 图像膨胀操作函数
cpp 复制代码
/* 用途:用于对图像进行膨胀操作,通过结构元素扫描图像,
       将目标区域边界向外扩张,使亮区域(白色前景)变大。
       膨胀能够填补小黑洞、连接邻近目标、增强前景区域 */
void cv::dilate( InputArray src, OutputArray dst, InputArray kernel,
            Point anchor = Point(-1,-1), int iterations = 1,
            int borderType = BORDER_CONSTANT,
            const Scalar& borderValue = morphologyDefaultBorderValue() );
/*
src:输入的待膨胀图像,图像的通道数可以是任意的,但是图像的数据类型必须是CV_8U、CV_16U、
    CV_16S、CV_32F或CV_64F之一
dst:膨胀后的输出图像,与输入图像src具有相同的尺寸和数据类型
kernel:用于膨胀操作的结构元素,可以自己输入,也可以用getStructuringElement()函数生成
anchor:中心点再结构元素中的位置,默认参数为结构元素的几何中心点
iterations:膨胀的次数
borderType:像素外推法选择标志
borderValue:边界不变的边界值
*/
1.2.4 示例代码
cpp 复制代码
    /*生成用于膨胀的原图像*/
    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(MORPH_RECT, Size(3, 3));/*矩形结构元素*/
    struct2 = getStructuringElement(MORPH_CROSS, Size(3, 3));/*十字结构元素*/

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



    imshow("src", src);
    imshow("dilateSrc", dilateSrc);

    waitKey(0);

    QString imgPath = QApplication::applicationDirPath() + "/Images";
    cv::String s_imgPath = imgPath.toLocal8Bit().data();
    Mat LearnCV_black = imread(s_imgPath + "/LearnCV_black.png", IMREAD_ANYCOLOR);
    if (LearnCV_black.empty())
    {
        qDebug() << "图片加载失败, 请确认图像文件名称是否正确";
        return;
    }
    Mat dilate_black1, dilate_black2;
    /*黑背景图像腐蚀*/
    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);

    waitKey(0);
    destroyAllWindows();

二、形态学应用

1、开运算、闭运算、形态学梯度等原理

2.1.1 开运算

**作用:**去除图像中的噪声,消除较小连通域,保留较大连通域

步骤:

  1. 对原图像腐蚀
  2. 腐蚀结果再次进行膨胀
2.1.2 闭运算

**作用:**去除连通域内的小型空洞,平滑物体轮廓,连接两个临近的连通域

步骤:

  1. 对原图像膨胀
  2. 膨胀结果再次进行腐蚀
2.1.3 形态学梯度

**作用:**得到轮廓的梯度

步骤:

  1. 对原图像膨胀
  2. 对原图像腐蚀
  3. 膨胀结果减去腐蚀结果
2.1.4 顶帽运算

**作用:**分离比邻近点亮一些的斑点

步骤:

  1. 对原图像开运算
  2. 原图像减去开运算
2.1.5 黑帽运算

**作用:**分离比邻近点暗一些的斑点

步骤:

  1. 对原图像闭运算
  2. 闭运算减去原图像
2.1.6 击中击不中变换

**作用:**比图像腐蚀要求更加苛刻的一种形态学操作

步骤:

  1. 与腐蚀操作类似

总结:用一个模板,同时检查前景和背景,找到完全符合条件的目标形状

2、形态学操作对比

3、相关函数

cpp 复制代码
/* 用途:用于执行高级形态学操作,是腐蚀(erode)和膨胀(dilate)
       的综合扩展函数。通过不同的op参数,可以实现开运算、
       闭运算、形态学梯度、顶帽运算、黑帽运算等多种图像处理效果。
       常用于图像去噪、目标增强、边缘提取、孔洞填充、
       背景校正以及轮廓分析等场景 */
void cv::morphologyEx( InputArray src, OutputArray dst,
                int op, InputArray kernel,
                Point anchor = Point(-1,-1), int iterations = 1,
                int borderType = BORDER_CONSTANT,
                const Scalar& borderValue = morphologyDefaultBorderValue() );
/*
src:输入图像,可为灰度图像或二值图像
dst:形态学处理后的输出图像,与src具有相同尺寸和数据类型
op:形态学操作类型标志
kernel:形态学操作使用的结构元素
anchor:结构元素中心位置,默认(-1,-1)表示几何中心
iterations:处理执行次数
borderType:像素边界外推方式
borderValue:边界填充值
*/
标志参数 简记 作用
MORPH_ERODE 0 图像腐蚀
MORPH_DILATE 1 图像膨胀
MORPH_OPEN 2 开运算
MORPH_CLOSE 3 闭运算
MORPH_GRADIENT 4 形态学梯度
MORPH_TOPHAT 5 顶帽运算
MORPH_BLACKHAT 6 黑帽运算
MORPH_HITMISS 7 击中击不中运算
[形态学应用对应的参数]

4、示例代码

cpp 复制代码
    /*用于验证形态学应用的二值化矩阵*/
    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, 0
        );
    namedWindow("src", WINDOW_NORMAL);/*可以自由调节显示图像的尺寸*/
    imshow("src", src);
    /*3 x 3矩阵结构元素*/
    Mat kernel = getStructuringElement(MORPH_RECT, 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);

    QString imgPath = QApplication::applicationDirPath() + "/Images";
    cv::String s_imgPath = imgPath.toLocal8Bit().data();
    Mat keys = imread(s_imgPath + "/keys.png", IMREAD_GRAYSCALE);
    if (keys.empty())
    {
        qDebug() << "图片加载失败, 请确认图像文件名称是否正确";
        return;
    }
    imshow("Original keys", keys);
    threshold(keys, keys, 80, 255, THRESH_BINARY);
    imshow("Binary keys", keys);
    /*5 x 5矩阵结构元素*/
    Mat kernel_keys = getStructuringElement(MORPH_RECT, 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);
    destroyAllWindows();
相关推荐
Coovally AI模型快速验证2 小时前
多校联合提出LLM-as-Judge:大模型评判无人机电力线分割,无真值场景下守护安全
人工智能·计算机视觉·电力巡检
程序员老舅2 小时前
深入底层:Linux MMU 工作原理全解
linux·服务器·网络·c++·linux内核·内存管理·linux内存
凤凰院凶涛QAQ2 小时前
《C++转Java快速入手系列》抽象类和接口篇
java·开发语言·c++
YouCanYouUp.2 小时前
SPI 总线多设备复用冲突:根因分析与工程解决方案
mcu·学习
wangcheng3032 小时前
我们是怎么用AI选题找到好内容的
笔记
雪度娃娃3 小时前
结构型设计模式——桥接模式
c++·设计模式·桥接模式
翎沣3 小时前
C++11异常处理机制
java·c++·算法
-To be number.wan3 小时前
操作系统核心机制:处理机调度与死锁全解析
学习·操作系统
chao1898443 小时前
Qt Modbus TCP 通讯源码
qt·tcp/ip·命令模式