形态学图像处理

news2024/9/17 8:36:43

1 工具

1.1 灰度腐蚀和膨胀

当平坦结构元b的原点是(x,y)时,它在(x,y)处对图像f的灰度腐蚀定义为,图像f与b重合区域中的最小值。结构元b在位置(x,y)处对图像f的腐蚀写为:

类似地,当b的反射的原点是(x,y)时,平坦结构元b在(x,y)处对图像f的膨胀,定义为图像f与b的反射重合区域中的最大值,即

示例:灰度X射线图像分别使用半径为2像素的圆形结构元腐蚀、膨胀图像

Mat src = imread("./7.png", 0);
Mat erodeImg, dilateImg;
Mat element = getStructuringElement(MORPH_ELLIPSE, Size(4, 4));
morphologyEx(src, erodeImg, MORPH_ERODE, element);
Mat element1 = getStructuringElement(MORPH_ELLIPSE, Size(4, 4));
morphologyEx(src, dilateImg, MORPH_DILATE, element1);

1.2 灰度开运算和闭运算

灰度图像开运算和闭运算的公式,形式上与二值图像开运算和闭运算的公式相同。结构元b对图像f的开运算是

开运算照例首先用b腐蚀f然后用b膨胀得到结果。类似地,b对f的闭运算是

 示例:灰度X射线图像分别使用a)半径为3像素的圆形结构元进行开运算,b)使用半径为5像素的结构元进行闭运算

Mat src = imread("./7.png", 0);
Mat openImg, closeImg;
Mat element = getStructuringElement(MORPH_ELLIPSE, Size(6, 6));
morphologyEx(src, openImg, MORPH_OPEN, element);
Mat element1 = getStructuringElement(MORPH_ELLIPSE, Size(10, 10));

2 灰度图像形态学算法

2.1 形态学平滑

天鹅星座环超新星图像使用半径为5像素的圆形结构元进行开运算+闭运算

Mat src = imread("./8.png", 0);
Mat openImg, closeImg;
Mat element = getStructuringElement(MORPH_ELLIPSE, Size(5, 5));
morphologyEx(src, openImg, MORPH_OPEN, element);
Mat element1 = getStructuringElement(MORPH_ELLIPSE, Size(5, 5));
morphologyEx(openImg, closeImg, MORPH_CLOSE, element1);

2.2 形态学梯度

脑部CT图像膨胀和腐蚀结合图像相减

Mat src = imread("./9.png", 0);
Mat erodeImg, dilateImg;
Mat element = getStructuringElement(MORPH_ELLIPSE, Size(4, 4));
morphologyEx(src, erodeImg, MORPH_ERODE, element);
Mat element1 = getStructuringElement(MORPH_ELLIPSE, Size(4, 4));
morphologyEx(src, dilateImg, MORPH_DILATE, element1);
Mat sub = dilateImg - erodeImg;

2.3 顶帽变换和底帽变换

图像相减结合开运算和闭运算,可以得到所谓的顶帽变换和底帽变换。灰度图像f的顶帽变换定义为f减去其开运算:

类似地,f的底帽变换定义为f的闭运算减去f:

这些变换的主要应用之一是,在开运算或闭运算中用一个结构元从图像中删除目标,而不是拟合将被删除的目标。然后,差运算得到一幅仅保留已删除分量的图像。顶帽变换用于暗背景上的亮目标,而底帽变换则用于亮背景上的暗目标。因此我们通常将这两个变换称为白顶帽变换黑底帽变换

使用顶帽变换校正阴影

Mat src = imread("./10.png", 0);
Mat binImg, tophatImg, blackhatImg, dst;
// 1. Otsu最优阈值处理方法
threshold(src, binImg, 127, 255, THRESH_OTSU);
 
// 2. 顶帽变换校正阴影后Otsu
Mat element = getStructuringElement(MORPH_CROSS, Size(41, 41));
morphologyEx(src, tophatImg, MORPH_TOPHAT, element);
threshold(tophatImg, dst, 127, 255, THRESH_OTSU);

2.4 粒度测定

粒度测定是指确定图像中颗粒的大小分布。由于颗粒急剧无法整齐的分开,因此采用逐个识别颗粒的方法来计算颗粒的属两个非常困难。形态学可间接估计颗粒的大小分布,而不需要识别和测量各个颗粒。对于比背景亮且形状规则的颗粒,这种方法是用逐渐增大的结构元对图像进行开运算。基本思想是,某个特殊大小的开运算会对包含类似大小颗粒的输入图像的那些区域产生最大影响。对于开运算得到的每幅图像,我们计算像素之和。这个和值称为表面区域,它随结构元的增大而减小,因为开运算会减小图像的亮特征。这一过程得到一个一维阵列,阵列中的每个元素都是对应大小的结构元开运算后的像素之和。为了强调两个连续开运算之间的变化,我们计算一维阵列中相邻两个元素的差。画出差值的图像,曲线中的峰值就会指明图像中主要大小颗粒的分布。

Mat src = imread("./11.png", 0);
Mat openImg, dst;
int tempGray = sum(src)[0];
for (size_t i = 1; i < 36; i=i+2)
{
    Mat element = getStructuringElement(MORPH_ELLIPSE, Size(i, i));
    morphologyEx(src, openImg, MORPH_OPEN, element);
    int sumGray = sum(openImg)[0];
    int d = tempGray - sumGray;
    cout << d << endl;
    tempGray = sumGray;
    if (i%10==5)
        imshow("openImg(ksize="+to_string(i)+")", openImg);
}

2.5 纹理分割

  形态学的纹理分割是以纹理内容为基础,找到两个区域的边界,将图像分割为不同的区域。下图是一幅在亮背景上叠加了暗斑点的噪声图像。图像有两个纹理区域,左侧区域包括一些较小的斑点,而右侧区域包括一些较大的斑点。由于目标斑点比背景暗,可以用一个尺寸大于较小斑点的圆形结构元对图像进行闭运算,删除较小的斑点,就得到只有大斑点的图像。再用尺寸大于较大斑点的圆形结构元对图像进行开运算,可以删除较大斑点之间的亮间距,整个图像形成左侧亮色和右侧暗色两个区域。通过形态学梯度运算,就得到两个区域的边界。最后将形态学梯度获得的边界叠加到原图像上,就实现了左右两种不同纹理的区域分割。

纹理分割

Mat src = imread("./12.png", 0);
Mat closeImg, dst, openImg, gradImg;
Mat element = getStructuringElement(MORPH_ELLIPSE, Size(19, 19));
morphologyEx(src, closeImg, MORPH_CLOSE, element);
Mat element1 = getStructuringElement(MORPH_ELLIPSE, Size(61, 61));
morphologyEx(closeImg, openImg, MORPH_OPEN, element1);
Mat element2 = getStructuringElement(MORPH_RECT, Size(3, 3));
morphologyEx(openImg, gradImg, MORPH_GRADIENT, element2);
bitwise_or(gradImg, src, dst);

3二值图像形态学算法

3.1 边界提取

前景像素集合A的边界ß(A)可按如下方式得到:首先使用合适的结构元B腐蚀A,然后求A和腐蚀结果的差集。也就是说,

边界提取

Mat src = imread("./4.png",0);
Mat thr, dst, ero;
threshold(src, thr, 128, 255, THRESH_BINARY);
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
morphologyEx(thr, ero, MORPH_ERODE, kernel);

3.2 孔洞填充

Mat I = imread("./2.png",0);
threshold(I, I, 128, 255, THRESH_BINARY);
Mat Ic,thr, dst, tmp, dilImg;
bitwise_not(I, Ic);
Mat mask = Mat::zeros(I.size(), CV_8UC1);
tmp = mask.clone();
mask.at<uchar>(0, 0) = 255;
Mat kernel = getStructuringElement(MORPH_CROSS, Size(3, 3));
int diff = -1;
while (diff!=0) {
    tmp = mask.clone();
    morphologyEx(mask, dilImg, MORPH_DILATE, kernel);
    bitwise_and(dilImg, Ic, mask);
    diff = sum(mask - tmp)[0];
}

3.3 提取连通分量

  从二值图像中提取连通分量是自动图像分析的核心步骤。冈萨雷斯《数字图像处理(第四版)》提供了一种提取连通分量的形态学算法,构造一个元素为0的阵列X0,其中对应连通分量的像素值为1,采用迭代过程可以得到所有的连通分量:

该算法与约束膨胀孔洞填充的思路相同,使用条件膨胀来限制膨胀的增长,但用I代替Ic以寻找前景点。

  对于内含多个连通分量的图像A,从仅为连通分量A1内部的某个像素B开始,用3*3的结构元不断进行膨胀。由于其它连通分量与A1之间至少有一条像素宽度的空隙,每次膨胀都不会产生位于其它连通区域内的点。用每次膨胀后的图像与原始图像A取交集,就把膨胀限制在A1内部。随着集合B的不断膨胀,B的区域不断生长,但又被限制在连通分量A1的内部,最终就会充满整个连通分量A1,从而实现对连通分量A1的提取。

Mat dilImg, tmp1, sub, I;
Mat src = imread("./5.png",0);
threshold(src, I, 200, 255, THRESH_BINARY);
Mat tmp = I.clone();
Mat markImg = Mat::zeros(I.size(), CV_8UC3);
Mat mask = Mat::zeros(I.size(), CV_8UC1);
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
vector<vector<Point>> cons;
while (countNonZero(tmp) != 0) {
    vector<Point> idx;
    findNonZero(tmp, idx);
    mask.at<uchar>(idx[0].y, idx[0].x) = 255;
    tmp1 = mask.clone();
    sub = mask.clone();
    while (countNonZero(sub) != 0)
    {
        morphologyEx(mask, dilImg, MORPH_DILATE, kernel);
        bitwise_and(dilImg, I, mask);
        sub = mask - tmp1;   
        tmp1 = mask.clone();
     }
     tmp -= mask;
}

3.4 凸壳

当且仅当数字集合A的欧式凸壳只包含于属于A的数字点,该数字集合A是凸的。一种简单的可视化方法是,用直(连续的)欧几里得线段连接其边界点,如果只有前景点包含于由这些线段形成的集合,那么集合是凸的。令Bi,i=1,2,3,4表示下图中的4个结构元。凸壳程序由如下形态学公式实现:

其中,X0i=I。分别用4个结构元做击中-击不中变换直至收敛,再求并集就是A的凸壳。为了防止凸壳增长到超出保证凸性所需的最小尺寸,设定限制凸壳不超过集合A的垂直和水平尺寸。

3.5 细化

图像细化(Image Thinning),一般指二值图像的骨架化(Image Skeletonization)的一种操作运算。所谓的细化就是经过一层层的剥离,从原来的图中去掉一些点,但仍要保持原来的形状,直到得到图像的骨架。骨架,可以理解为图象的中轴。

Mat src = imread("./6.png", 0);
Mat edge = src.clone();
Mat element = getStructuringElement(MORPH_CROSS, Size(3, 3));
Mat dst = Mat::zeros(src.size(), CV_8UC1);
Mat openImg, tmp;
int n = -1;
while (countNonZero(edge) != 0) {
    morphologyEx(edge, openImg, MORPH_OPEN, element); // 开运算
    subtract(edge, openImg, tmp); // 获得骨架子集
    bitwise_or(dst, tmp, dst); // 将删除的像素添加到骨架图
    morphologyEx(edge, edge, MORPH_ERODE, element); // 腐蚀,用于下一次迭代
}

opencv实现

#include <opencv2/opencv.hpp>
#include <algorithm>
#include <iostream>
using namespace std;
using namespace cv;
 
void ThinOnce(Mat& pSrc, Mat& pDst, int flag) {
    int rows = pSrc.rows;
    int cols = pSrc.cols;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            if (pSrc.at<float>(i, j) == 1.0f) {
                /// get 8 neighbors
                /// calculate C(p)
                int P2 = (int)pSrc.at<float>(i - 1, j);
                int P3 = (int)pSrc.at<float>(i - 1, j + 1);
                int P4 = (int)pSrc.at<float>(i, j + 1);
                int P5 = (int)pSrc.at<float>(i + 1, j + 1);
                int P6 = (int)pSrc.at<float>(i + 1, j);
                int P7 = (int)pSrc.at<float>(i + 1, j - 1);
                int P8 = (int)pSrc.at<float>(i, j - 1);
                int P9 = (int)pSrc.at<float>(i - 1, j - 1);
                int sum = P2 + P3 + P4 + P5 + P6 + P7 + P8 + P9;
                int arr[9]{ P2, P3, P4, P5, P6, P7, P8, P9, P2 };
                int count = 0;
                for (int i = 0; i < 8; i++) {
                    if (arr[i] == 0 && arr[i + 1] == 1)
                        count++;
                } 
                if (flag == 0 && sum < 7 && sum > 1 && P2 * P4 * P6 == 0 && P4 * P6 * P8 == 0 && count == 1)
                    pDst.at<float>(i, j) = 0.0f;                
                if (flag == 1 && sum < 7 && sum > 1 && P2 * P4 * P8 == 0 && P2 * P6 * P8 == 0 && count == 1)
                    pDst.at<float>(i, j) = 0.0f;
            }
        }
    }
}
void thin(Mat& src, Mat& dst) {
    bool bDone = false;
    int rows = src.rows;
    int cols = src.cols;
 
    /// pad source and dst
    Mat p_enlarged_src, p_enlarged_dst;
    copyMakeBorder(src, p_enlarged_src, 1, 1, 1, 1, BORDER_CONSTANT, Scalar(0));
    p_enlarged_src.convertTo(p_enlarged_src, CV_32F, 1/255.0);
    p_enlarged_dst = p_enlarged_src.clone();<br>
    Mat p_cmp = Mat::zeros(rows + 2, cols + 2, CV_8UC1);
    int iter = 0;
    while (bDone != true) {
        // sub-iteration
        int i = iter % 2;
        ThinOnce(p_enlarged_src, p_enlarged_dst, i);
        // compare
        compare(p_enlarged_src, p_enlarged_dst, p_cmp, CMP_EQ);
        // check
        int num_non_zero = countNonZero(p_cmp);
        if (num_non_zero == (rows + 2) * (cols + 2)) {
            bDone = true;
        }
        // copy
        p_enlarged_dst.copyTo(p_enlarged_src);
        iter++;
    }
    // copy result
    dst = p_enlarged_dst({ 1, 1, cols, rows }).clone();
}
 
int main(int argc, char* argv[])
{
    Mat src = imread("./6.png", 0);
    Mat dst;
    thin(src, dst);
    imshow("src", src);
    imshow("dst", dst);
    waitKey(0);
    return 0;
} 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1918418.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ 【 Open3D 】 点云按高程进行赋色

一、 Open3D中根据点云的高程度信息为点云中的每个点附上颜色&#xff0c;并保存颜色渲染结果&#xff01; #include<iostream> #include<open3d/Open3D.h>using namespace std;int main() {//-------------------------------读取点云--------------------------…

FastAPI 学习之路(四十三)路径操作的高级配置

在实际开发中&#xff0c;可能我们有些接口不能在接口文档中与其他业务接口一样开放给前端或者其他对接人&#xff0c;那么我们肯定会想着在接口文档中对其进行屏蔽隐藏操作&#xff0c;那么可以实现吗&#xff1f; 接口文档中隐藏接口 当然&#xff0c;还很简单&#xff0c;…

利用Hbuilder创建vue3的web项目

大体流程如下 npm install vue-router4 下载完&#xff0c;就创建完了

Redis基础教程(二十):Java使用Redis

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; &#x1f49d;&#x1f49…

springboot大学校园二手书交易APP-计算机毕业设计源码25753

摘 要 在数字化与移动互联网迅猛发展的今天&#xff0c;人们对于图书的需求与消费方式也在悄然改变。为了满足广大读者对图书的热爱与追求&#xff0c;我们倾力打造了一款基于Android平台的图书交易APP。这款APP不仅汇聚了海量的图书资源&#xff0c;提供了便捷的交易平台&…

java使用easypoi模版导出word详细步骤

文章目录 第一步、引入pom依赖第二步、新建导出工具类WordUtil第三步、创建模版word4.编写接口代码5.导出结果示例 第一步、引入pom依赖 <dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-spring-boot-starter</artifactId><…

旗晟智能巡检机器人:开启工业运维的智能化新篇章

在当今快速发展的工业领域&#xff0c;安全、效率和成本控制是企业运营的核心。旗晟科技以创新为驱动&#xff0c;推出了一站式的工业级智能巡检机器人数字化全景运维解决方案&#xff0c;为石油、天然气、化工、电力等高危行业提供了一个全新的运维模式。 一、面对挑战&#x…

在 Linux 上设置 RAID 阵列的全面指南

引言 在这篇博文中&#xff0c;我们将深入探讨如何在 Linux 上设置 RAID 阵列。本文将涵盖 RAID 的定义、架构、原理、应用场景、常见命令体系&#xff0c;并通过详细的实战模拟展示如何在 Linux 系统上实际操作。希望通过这篇文章&#xff0c;您能深入理解 RAID 技术&#xff…

初识C++ | 基本介绍、命名空间、输入输出、缺省函数、函数重载、引用、内联函数、nullptr

基本介绍 C的起源 1979年&#xff0c;当时的 Bjarne Stroustrup 正在⻉尔实验室从事计算机科学和软件⼯程的研究⼯作。⾯对项⽬中复杂的软件开 发任务&#xff0c;特别是模拟和操作系统的开发⼯作&#xff0c;他感受到了现有语⾔&#xff08;如C语⾔&#xff09;在表达能⼒、可…

获取网页logo图标

怎么获取网页logo图标的URL链接 第一种方法&#xff1a; 最常用的方法&#xff08;适用于90%的站点&#xff09;是&#xff0c;直接在访问网址首页链接后加上上/favicon.ico&#xff0c;例如&#xff1a; https://www.baidu.com/favicon.ico 第二种方法&#xff1a; 按F12&…

Photoshop套索工具使用指南:解锁自由选区的艺术

在Adobe Photoshop的强大工具箱中&#xff0c;套索工具组是每位图像处理爱好者与专业人士的得力助手。这组工具&#xff0c;包括套索工具、多边形套索工具和磁性套索工具&#xff0c;为用户提供了高度灵活的选择区域方式&#xff0c;无论是处理复杂的图像边缘还是进行精细的抠图…

Flexcel学习笔记

1.引用的单元 FlexCel.Core 始终需要使用的一个单元。 多系统运行时。{$IFDEF LINUX}SKIA.FlexCel.Core{$ELSE}{$IFDEF FIREMONKEY}FMX.FlexCel.Core{ $ELSE}VCL.FlexCel.Core{$ENDIF}{$ENDIF} FlexCel.XlsAdapter这是FlexCel的xls/x引擎。如果您正在处理xls或xlsx文件&#x…

Centos7 新增yum源

背景&#xff1a;原来的yum源&#xff0c;无法下载yum包了。新增一个阿里云的&#xff08;网易163的源失效了&#xff0c;无法使用&#xff09; Could not retrieve mirrorlist http://mirrorlist.centos.org/?release7&archx86_64&repoos&infrastock error was …

PyCharm 2023.3.2 关闭时一直显示正在关闭项目

文章目录 一、问题描述二、问题原因三、解决方法 一、问题描述 PyCharm 2023.3.2 关闭时一直显示正在关闭项目 二、问题原因 因为PyCharm还没有加载完索引导致的 三、解决方法 方法一&#xff1a; 先使用任务管理器强制关闭&#xff0c;下次关闭时注意要等待PyCharm加载完索…

AI软件测试工程师需要学习什么,怎么学习

对于已经从事软件测试工作的人来说真是需要补充的是以下部分的内容 1.AI概念和原理:了解人工智能的基本概念、发展历史、应用领域等 对应内容 https://cloud.baidu.com/article/31805742.机器学习基础知识:学习机器学习的基本概念、算法和框架&#xff0c;如线性回归、迈辑回归…

第一次参加数学建模竞赛新手小白备赛经验贴

2024年暑假已经来临&#xff0c;下半年的数学建模竞赛非常多&#xff0c;许多同学可能是第一次参赛&#xff0c;对于如何准备感到迷茫和无从下手。在这种情况下&#xff0c;我们将分享一些备赛的小技巧&#xff0c;帮助大家在这个暑假更好的入门&#xff0c;即便是零基础的小白…

计算机视觉之ShuffleNet图像分类

前言 ShuffleNetV1是一种计算高效的CNN模型&#xff0c;旨在在移动端利用有限的计算资源达到最佳的模型精度。其设计核心是引入了Pointwise Group Convolution和Channel Shuffle两种操作&#xff0c;以降低模型的计算量并保持精度。与MobileNet类似&#xff0c;ShuffleNetV1通…

【Docker系列】Docker 镜像源:优化你的容器化开发流程

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

第18章 概率潜在语义分析:课后习题

本章概要&#xff1a; 1.概率潜在语义分析是利用概率生成模型对文本集合进行话题分析的方法。概率潜在语义分析受潜在语义分析的启发提出两者可以通过矩阵分解关联起来。 给定一个文本集合&#xff0c;通过概率潜在语义分析&#xff0c;可以得到各个文本生成话题的条件概率分布…

WebDriver与浏览器通信的深度剖析与探索

在自动化测试的世界里&#xff0c;WebDriver无疑是连接测试脚本与浏览器之间的桥梁&#xff0c;它让复杂的自动化测试成为可能。本文将深入探讨WebDriver与浏览器之间的通信机制&#xff0c;揭示它们之间如何协同工作&#xff0c;以及这一过程中涉及的关键技术和挑战。 一、We…