形态学图像处理
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;
}
相关文章:
形态学图像处理
1 工具 1.1 灰度腐蚀和膨胀 当平坦结构元b的原点是(x,y)时,它在(x,y)处对图像f的灰度腐蚀定义为,图像f与b重合区域中的最小值。结构元b在位置(x,y)处对图像f的腐蚀写为: 类似地,当b的反射的原点是(x,y)时,平坦结构元…...
安泰电压放大器的选型方案是什么
电压放大器是一种常见的电路元件,广泛应用于各种电子设备中。在选择电压放大器的时候,我们需要考虑一系列因素,以确保选型方案能够满足实际需求。下面安泰电子将详细介绍电压放大器选型的主要考虑因素,包括应用需求、技术性能、成…...
ARMV8安全特性:Pointer Authentication
文章目录 前言一、Introduction二、Problem Definition三、Pointer Authentication3.1 Instructions3.2 Cryptography3.3 Key Management 四、Sample Use Cases4.1 Software Stack Protection4.2 Control Flow Integrity (CFI)4.3 Binding Pointers to Addresses 五、Security …...
MySQL和Redis更新一致性问题
1. 先更新数据库,再更新缓存 适用场景:适用于对数据一致性要求不是特别高,且缓存更新失败对 系统影响较小的场景。例如,某些非关键数据的缓存更新。 风险:如果缓存更新失败,会导致数据库和缓存数据不一致。…...
(19)夹钳(用于送货)
文章目录 前言 1 常见的抓手参数 2 参数说明 前言 Copter 支持许多不同的抓取器,这对送货应用和落瓶很有用。 按照下面的链接(或侧边栏),根据你的设置了解配置信息。 Electro Permanent Magnet v3 (EPMv3)Electro Permanent M…...
安装lap和cython_bbox失败了很多次!!!终于被我发现了!
先说 lap 试了很多种方式,pip install lap / conda install -c conda-forge lap … 全失败了后面发现 lap 不支持 python > 3.9 的版本使用 pip install lapx 成功! cython_bbox 更难了 一直提示缺少MicroSoft C 14.0 … 大家有需要自行下载&#x…...
异业联盟整合各大行业门店,共享资源
异业联盟系统是一种将不同行业的企业或商家整合在一起,通过资源共享、优势互补、合作推广等方式,实现共同发展和互利共赢的商业合作模式的数字化管理和运营系统。 其具有以下显著优势: 1.拓展客户群体:不同行业的企业联合起来&am…...
前端如何去看蓝湖
首先加入团队,在内容中我们可以看到点击图片,右边出现的图 包含了像素甚至有代码,我们可以参考这个代码。 那么在使用之前我们需要调整好像素,例如我们的像素宽为375,不用去管高,然后这个宽度我们可以去自…...
HTML+CSS+JS 实现3D风吹草动效果(B站视频)
效果: 代码: <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>3D effect&…...
常用网络概念
📑打牌 : da pai ge的个人主页 🌤️个人专栏 : da pai ge的博客专栏 ☁️宝剑锋从磨砺出,梅花香自苦寒来 ☁️运维工程师的职责:监…...
图鸟UI框架在uni-app多端应用开发中的实践与应用
摘要: 随着移动互联网的蓬勃发展,跨平台应用开发已成为行业趋势。本文将探讨图鸟UI框架如何在uni-app开发环境下助力开发者高效构建多端应用,并通过具体案例展示其在实际项目中的应用效果。 一、引言 在移动应用开发领域,跨平台…...
特征值究竟体现了矩阵的什么特征?
特征值究竟体现了矩阵的什么特征? 简单来说就是x经过矩阵A映射后和自己平行 希尔伯特第一次提出eigenvalue,这里的eigen就是自己的。所以eigenvalue也称作本征值 特征值和特征向量刻画了矩阵变换空间的特征 对平面上的任意向量可以如法炮制,把他在特征…...
C语言-顺序表
🎯引言 欢迎来到HanLop博客的C语言数据结构初阶系列。在这个系列中,我们将深入探讨各种基本的数据结构和算法,帮助您打下坚实的编程基础。本次我将为你讲解。顺序表(也称为数组)是一种线性表,因其简单易用…...
OpenCV漫水填充函数floodFill函数的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 功能描述 ffloodFill函数是OpenCV库中用于图像处理的一个功能,它用于填充与种子点颜色相近的连通区域。这个函数在很多场景下都非常有用&#x…...
redis内存分析
阿里云redis集群对key进行hash后保存在对应的后端节点,使每个节点的key数量大致相同,但是如果存在大key,就会导致单个节点内存用满,可以使用redis-rdb-tools 或rdr来对内存进行分析。 redis-rdb-tools redis-rdb-tools通过对dum…...
redis批量删除keys,用lua脚本。
文章目录 现象解决方法 现象 系统报错: misconf redis is configured to save ....后查看机器内存。 是内存满了,需要删除其中的key 解决方法 (1) 编写一个脚本,放在redis-cli.exe同一个目录 (2) 脚本内容如下: -- 使用Lua脚…...
Python-找客户软件
软件功能 请求代码: 填充表格: 可以search全国各个区县的所有企业信息,过滤手机号、查看是否续存/在业状态。方便找客户。 支持定-制-其他引-留-阮*件(XHSS,DYY,KS,Bi-li*Bi-li) V*…...
STM32 - PWR 笔记
PWR(Power Control)电源控制 PWR 负责管理 STM32 内部的电源供电部分,可以实现 可编程电压监测器 和 低功耗模式 的功能 可编程电压监测器(PVD)可以监控VDD电源电压,当VDD下降到PVD阀值以下或上升到PVD…...
标准盒模型和怪异盒子模型的区别
在 CSS 中,标准盒模型和怪异盒模型是两种不同的盒子模型计算方式,主要区别如下: 一、标准盒模型(content-box) 1. 定义与组成 - 标准盒模型是 CSS 中默认的盒模型。 - 它由内容区域(content)、…...
推荐算法——MRR
定义: MRR计算的是第一个正确答案的排名的倒数,并对所有查询取平均值。它衡量了模型在排序结果中快速找到正确答案的能力。 其中: Q 是查询的总数。ranki 是第 i 个查询中第一个正确答案的排名(位置)。如果第一个正…...
国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
深度学习水论文:mamba+图像增强
🧀当前视觉领域对高效长序列建模需求激增,对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模,以及动态计算优势,在图像质量提升和细节恢复方面有难以替代的作用。 🧀因此短时间内,就有不…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...

