形态学图像处理

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 dstMat 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-iterationint i = iter % 2;ThinOnce(p_enlarged_src, p_enlarged_dst, i);// comparecompare(p_enlarged_src, p_enlarged_dst, p_cmp, CMP_EQ);// checkint num_non_zero = countNonZero(p_cmp);if (num_non_zero == (rows + 2) * (cols + 2)) {bDone = true;}// copyp_enlarged_dst.copyTo(p_enlarged_src);iter++;}// copy resultdst = 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.mzph.cn/bicheng/44565.shtml

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

相关文章

react学习——24redux实现求和案例(精简版)

1、目录结构 2、count/index.js import React, {Component} from "react"; //引入store,用于获取数据 import store from ../../redux/store export default class Count extends Component {state {count:store.getState()}componentDidMount() {//监测redux中的…

传言称 iPhone 16 Pro 将支持 40W 快速充电和 20W MagSafe

目前&#xff0c;iPhone 15 和 iPhone 15 Pro 机型使用合适的 USB-C 电源适配器可实现高达 27W 的峰值充电速度&#xff0c;而 Apple 和授权第三方的官方 MagSafe 充电器可以高达 15W 的功率为 iPhone 15 机型进行无线充电。所有四款 iPhone 15 机型均可使用 20W 或更高功率的电…

PHP计件工资系统小程序源码

解锁高效管理新姿势&#xff01;全面了解计件工资系统 &#x1f525; 开篇&#xff1a;为什么计件工资系统成为企业新宠&#xff1f; 在这个效率至上的时代&#xff0c;企业如何精准激励员工&#xff0c;提升生产力成为了一大挑战。计件工资系统应运而生&#xff0c;它以其公…

【小沐学Python】在线web数据可视化Python库:Bokeh

文章目录 1、简介2、安装3、测试3.1 创建折线图3.2 添加和自定义渲染器3.3 添加图例、文本和批注3.4 自定义您的绘图3.5 矢量化字形属性3.6 合并绘图3.7 显示和导出3.8 提供和筛选数据3.9 使用小部件3.10 嵌入Bokeh图表到Flask应用程序 结语 1、简介 https://bokeh.org/ https…

算法力扣刷题记录 四十【226.翻转二叉树】

前言 继续二叉树其余操作&#xff1a; 记录 四十【226.翻转二叉树】 一、题目阅读 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1]示例…

CAS介绍

CAS是计算机科学中的一个概念&#xff0c;全称是Compare-And-Swap&#xff08;比较并交换&#xff09;&#xff0c;它是一种原子操作&#xff0c;用于多线程环境下的同步机制。在Java中&#xff0c;你可以使用java.util.concurrent.atomic包下的类&#xff0c;如AtomicInteger来…

绝对值不等式运用(C++)

货仓选址 用数学公式表达题意&#xff0c;假设有位置a1~an,假设选址在x位置处&#xff0c;则有&#xff1a; 如何让这个最小&#xff0c;我们把两个式子整合一下&#xff0c;利用绝对值不等式&#xff1a; 我们知道&#xff1a; 如下图所示&#xff1a;到A&#xff0c;B两点&…

用python生成词频云图(python实例二十一)

目录 1.认识Python 2.环境与工具 2.1 python环境 2.2 Visual Studio Code编译 3.词频云图 3.1 代码构思 3.2 代码实例 3.3 运行结果 4.总结 1.认识Python Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。 Python 的设计具有很强的可读性&a…

[ICS] Inferno(地狱) ETH/IP未授权访问,远程控制工控设备利用工具

项目地址:https://github.com/MartinxMax/Inferno Inferno $ ./Install.sh $ python Inferno.py -h 模拟服务端 $ sudo python3 -m pip install --upgrade cpppo $ $ python -m cpppo.server.enip SCADAINT[1000] ADMININT[2] -v 创建一个EtherNet/IP设备 扫描设备 $ pyth…

QT--SQLite

配置类相关的表&#xff0c;所以我使用sqlite,且QT自带该组件&#xff1b; 1.安装 sqlite-tools-win-x64-3460000、SQLiteExpert5.4.31.575 使用SQLiteExpert建好数据库.db文件&#xff0c;和对应的表后把db文件放在指定目录 ./db/program.db&#xff1b; 2.选择sql组件 3.新…

YOLOv10改进 | Conv篇 | 全新的SOATA轻量化下采样操作ADown(参数量下降百分之二十,附手撕结构图)

一、本文介绍 本文给大家带来的改进机制是利用2024/02/21号最新发布的YOLOv9其中提出的ADown模块来改进我们的Conv模块&#xff0c;其中YOLOv9针对于这个模块并没有介绍&#xff0c;只是在其项目文件中用到了&#xff0c;我将其整理出来用于我们的YOLOv10的项目&#xff0c;经…

【人工智能】-- 反向传播

个人主页&#xff1a;欢迎来到 Papicatch的博客 课设专栏 &#xff1a;学生成绩管理系统 专业知识专栏&#xff1a; 专业知识 文章目录 &#x1f349;引言 &#x1f349;反向传播 &#x1f348;定义 &#x1f348;反向传播的作用 &#x1f34d;参数优化 &#x1f34d;学…

Qt Creator仿Visual Studio黑色主题

转自本人博客&#xff1a;Qt Creator仿Visual Studio黑色主题 1.演示 配置文件和步骤在后面&#xff0c;先看成品&#xff0c;分别是QWidget和QML的代码编写界面&#xff1a; 2. 主题配置文件 下载链接&#xff1a;QtCreator _theme_VS_dark.xml 也可以自己新建一个xml文件&…

【RHCE】转发服务器实验

1.在本地主机上操作 2.在客户端操作设置主机的IP地址为dns 3.测试,客户机是否能ping通

(pyqt5)弹窗-Token验证

前言 为了保护自己的工作成果,控制在合理的范围内使用,设计一个用于Token验证的弹窗. 代码 class TokenDialog(QDialog):def __init__(self, parentNone, login_userNone, mac_addrNone, funcNone):super(TokenDialog, self).__init__(parent)self.login_user login_userself…

手撸俄罗斯方块(五)——游戏主题

手撸俄罗斯方块&#xff08;五&#xff09;——游戏主题 当确定游戏载体&#xff08;如控制台&#xff09;后&#xff0c;界面将呈现出来。但是游戏的背景色、方块的颜色、方框颜色都应该支持扩展。 当前游戏也是如此&#xff0c;引入了 Theme 的概念&#xff0c;支持主题的扩…

雨量监测站的重要性有哪些

在全球气候变化和极端天气事件频发的背景下&#xff0c;雨量监测站成为了我们理解降水模式、预测天气变化以及制定应对措施的重要工具。 雨量监测站是一种专门用于测量和记录降水量的设施。它们通过配备高精度的雨量传感器&#xff0c;能够实时监测降雨情况&#xff0c;并提供关…

【分布式系统】CephFS文件系统之MDS接口详解

目录 一.服务端操作 1.在管理节点创建 mds 服务 2.查看各个节点的 mds 服务&#xff08;可选&#xff09; 3.创建存储池&#xff0c;启用 ceph 文件系统 4.查看mds状态&#xff0c;一个up&#xff0c;其余两个待命&#xff0c;目前的工作的是node01上的mds服务 5.创建用户…

SuperCLUE最新测评发布,360智脑大模型稳居大模型第一梯队

7月9日&#xff0c;国内权威大模型评测机构SuperCLUE发布《中文大模型基准测评2024上半年报告》&#xff0c;360智脑大模型&#xff08;360gpt2-pro&#xff09;在SuperCLUE基准6月测评中&#xff0c;取得总分72分&#xff0c;超过GPT-3.5-Turbo-0125&#xff0c;位列国内大模型…

制作一个自动养号插件的必备源代码!

随着网络社交平台的日益繁荣&#xff0c;用户对于账号的维护和运营需求也日益增长&#xff0c;在这样的背景下&#xff0c;自动养号插件应运而生&#xff0c;成为了许多用户提升账号活跃度、增加曝光量的得力助手。 然而&#xff0c;制作一个高效、稳定的自动养号插件并非易事…