OpenCV计算机视觉实战(10)——形态学操作详解

news2025/6/8 15:06:42

OpenCV计算机视觉实战(10)——形态学操作详解

    • 0. 前言
    • 1. 腐蚀与膨胀
      • 1.1 为什么要做腐蚀与膨胀
      • 1.2 OpenCV 实现
    • 2. 开运算与闭运算
      • 2.1 开运算与闭运算原理
      • 2.2 OpenCV 实现
    • 3. 形态学梯度与骨架提取
      • 3.1 形态学梯度
      • 3.2 骨架提取
    • 小结
    • 系列链接

0. 前言

形态学操作 (Morphological Operations) 是图像预处理和特征提取的利器。通过简单的腐蚀与膨胀,我们便能去除噪点、填补孔洞;借助开闭运算,能够剔除干扰、平滑结构;而形态学梯度与骨架提取,则能够精准地捕捉边缘轮廓与中轴骨架。无论是在工业质检、文档清洗,还是道路检测与手写识别,掌握这些基本工具,都将为图像处理带来质的飞跃。

1. 腐蚀与膨胀

腐蚀 (Erosion) 是图像中的前景物体边界向内“收缩”,可去除小的噪点、分离相连物体;而膨胀 (Dilation) 则是图像中的前景物体边界向外“扩张”,可填补小的孔洞、连接分散物体。

1.1 为什么要做腐蚀与膨胀

去噪与分割:在二值化后的小颗粒噪声或连通区域中,腐蚀可以帮我们“吃掉”那些孤立的噪点,让结果更干净;膨胀则可填补目标内部的小孔,增强连通性。
形状变换:当需要放大或缩小目标形态时,腐蚀/膨胀提供了一种简单又高效的“膨胀器”或“压缩机”效果。

1.2 OpenCV 实现

要实现腐蚀与膨胀,首先需要读取二值图,然后定义结构元素 (kernel),分别调用 cv2.erodecv2.dilate,最后对比展示腐蚀后与膨胀后的效果。

import cv2
import numpy as np

# 1. 读取二值图像
img = cv2.imread('1.jpeg', cv2.IMREAD_GRAYSCALE)

# 2. 定义结构元素:3x3 矩形
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))

# 3. 腐蚀与膨胀
eroded = cv2.erode(img, kernel, iterations=1)
dilated = cv2.dilate(img, kernel, iterations=1)

# 4. 显示结果
cv2.imshow('Original', img)
cv2.imshow('Eroded (Iterations=1)', eroded)
cv2.imshow('Dilated (Iterations=1)', dilated)
cv2.waitKey(0)
cv2.destroyAllWindows()

OpenCV
关键函数解析:

  • cv2.getStructuringElement(shape, ksize):生成指定形状和大小的结构元素,用于定义腐蚀/膨胀的邻域
    • shape:决定邻域的拓扑结构,MORPH_RECT (矩形),最基础、对角方向也有效果;MORPH_ELLIPSE (椭圆),圆润过渡,更贴近自然形态;MORPH_CROSS (交叉),只在上下左右方向操作,可保留更多背景细节
    • ksize:决定邻域的影响范围,尺寸越大,对图像变化越剧烈,小尺寸适合轻微去噪,大尺寸可用于强力平滑或局部特征压缩
  • cv2.erode(src, kernel, iterations):对图像进行腐蚀操作,iterations 控制重复次数(值大则形态变化剧烈);每次腐蚀都将前景边界向内收缩一个结构元素大小
  • cv2.dilate(src, kernel, iterations):对图像进行膨胀操作,将前景边界向外扩张

2. 开运算与闭运算

2.1 开运算与闭运算原理

开运算 (Opening):先腐蚀后膨胀,常用于消除小的“白色”噪点(前景噪声),在工业检测中,常用来去除小的油污斑点或粉尘噪声。
闭运算 (Closing):先膨胀后腐蚀,常用于填充前景中的小孔、连接相邻对象,在文档处理或车牌识别中,常用来填补字符中字母、数字内部的断裂。

2.2 OpenCV 实现

要实现开运算与闭运算,首先需要读取二值图,然后定义结构元素 (kernel),调用 cv2.morphologyEx,分别指定 MORPH_OPENMORPH_CLOSE,最后对比展示开运算后与闭运算后的效果。

import cv2
import numpy as np

# 1. 读取二值图像
img = cv2.imread('1.jpeg', cv2.IMREAD_GRAYSCALE)

# 2. 定义结构元素:5x5 椭圆
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))

# 3. 开运算与闭运算
opened = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel, iterations=1)
closed = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel, iterations=1)

# 4. 显示结果
cv2.imshow('Original', img)
cv2.imshow('Opened', opened)
cv2.imshow('Closed', closed)
cv2.waitKey(0)
cv2.destroyAllWindows()

开运算与闭运算
关键函数解析:

  • cv2.morphologyEx(src, op, kernel, iterations):通用形态学函数,op 参数可选 MORPH_OPEN (开运算)、MORPH_CLOSE (闭运算)、MORPH_GRADIENT (梯度)等
  • 开运算 (MORPH_OPEN) = Erosion → Dilation;闭运算 (MORPH_CLOSE) = Dilation → Erosion

3. 形态学梯度与骨架提取

形态学梯度 (Morphological Gradient):膨胀结果与腐蚀结果之差,突出物体边缘轮廓。
骨架提取 (Skeletonization):将前景对象不断腐蚀并减去开运算结果,迭代至完全消失,得到细丝状“骨架”。

3.1 形态学梯度

形态学梯度可以定义为:梯度 = 膨胀结果 − 腐蚀结果。得到的直观效果:只保留前景的边缘像素,其余部分变黑。

import cv2
import numpy as np

# 读取并二值化
img = cv2.imread('1.jpeg', cv2.IMREAD_GRAYSCALE)
_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)

# 定义结构元素
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
# 计算梯度
gradient = cv2.morphologyEx(binary, cv2.MORPH_GRADIENT, kernel)

cv2.imshow('Binary', binary)
cv2.imshow('Morphological Gradient', gradient)
cv2.waitKey(0)
cv2.destroyAllWindows()

形态学梯度

关键函数解析:

  • cv2.MORPH_GRADIENT:在 cv2.morphologyEx 中使用,自动计算膨胀与腐蚀的差值,突出边缘信息

3.2 骨架提取

算法流程详解

初始化:复制原始二值图为 temp,创建空图 skeleton
 循环迭代:
  用同一结构元素对 temp 腐蚀,得到 eroded
  对 eroded 做开运算,得到 opened
  edge = eroded − opened,提取该层“细丝”
  将 edgeskeleton 做按位或,累加骨架
  更新 temp = eroded
 终止条件:当 temp 中前景像素消失 (countNonZero(temp) == 0) 时退出。

import cv2
import numpy as np

# 1. 读取二值图像
img = cv2.imread('7.jpeg', cv2.IMREAD_GRAYSCALE)
_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)

# 2. 准备
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
skeleton = np.zeros_like(binary)
temp = binary.copy()

# 3. 迭代提取骨架
while True:
    eroded = cv2.erode(temp, kernel)
    opened = cv2.morphologyEx(eroded, cv2.MORPH_OPEN, kernel)
    edge = cv2.subtract(eroded, opened)
    skeleton = cv2.bitwise_or(skeleton, edge)
    temp = eroded.copy()
    if cv2.countNonZero(temp) == 0:
        break

# 4. 显示结果
cv2.imshow('Original Binary', binary)
cv2.imshow('Skeleton', skeleton)
cv2.waitKey(0)
cv2.destroyAllWindows()

骨架提取

小结

在本节中,我们介绍了腐蚀与膨胀:理解了结构元素的形状与尺寸如何影响图像噪声去除与连通性增强;开运算与闭运算:掌握了“先破后立”与“先立后破”的组合套路,轻松去除斑点与填补空洞;形态学梯度与骨架提取:学会了如何从二值图中提取清晰的边缘轮廓,并将复杂形状瘦身为一像素宽的中轴线。在实际项目中,我们可以根据噪声类型和应用需求,自由组合这些操作:先用开运算去噪,再用闭运算恢复结构,或在边缘检测和形状分析前加入梯度与骨架处理。

系列链接

OpenCV计算机视觉实战(1)——计算机视觉简介
OpenCV计算机视觉实战(2)——环境搭建与OpenCV简介
OpenCV计算机视觉实战(3)——计算机图像处理基础
OpenCV计算机视觉实战(4)——计算机视觉核心技术全解析
OpenCV计算机视觉实战(5)——图像基础操作全解析
OpenCV计算机视觉实战(6)——经典计算机视觉算法
OpenCV计算机视觉实战(7)——色彩空间详解
OpenCV计算机视觉实战(8)——图像滤波详解
OpenCV计算机视觉实战(9)——阈值化技术详解

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

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

相关文章

[论文阅读] 人工智能 | 利用负信号蒸馏:用REDI框架提升LLM推理能力

【论文速读】利用负信号蒸馏:用REDI框架提升LLM推理能力 论文信息 arXiv:2505.24850 cs.LG cs.AI cs.CL Harnessing Negative Signals: Reinforcement Distillation from Teacher Data for LLM Reasoning Authors: Shuyao Xu, Cheng Peng, Jiangxuan Long, Weidi…

基于 NXP + FPGA+Debian 高可靠性工业控制器解决方案

在工业系统开发中,**“稳定”**往往比“先进”更重要。设备一旦部署,生命周期动辄 5~10 年,系统重启或异常恢复成本高昂。 这时候,一套“值得托付”的软硬件组合,就显得尤为关键。 ✅ NXP —— 提供稳定、长期供货的工…

垂起固定翼无人机应用及技术分析

一、主要应用行业 1. 能源基础设施巡检 电力巡检:适用于超高压输电线路通道的快速巡查,实时回传数据提升智能运检效率。 油田管道监测:利用长航时特性(1.5-2小时)对大范围管道进行隐患排查,减少人力巡…

vite配置@别名,以及如何让IDE智能提示路经

1.配置路径(vite.config.js) // vite.config.js import { defineConfig } from "vite"; import vue from "vitejs/plugin-vue"; import path from "path";// https://vite.dev/config/ export default defineConfig({server: {port: 8080,},plu…

【Linux】LInux下第一个程序:进度条

前言: 在前面的文章中我们学习了LInux的基础指令 【Linux】初见,基础指令-CSDN博客【Linux】初见,基础指令(续)-CSDN博客 学习了vim编辑器【Linux】vim编辑器_linux vim insert-CSDN博客 学习了gcc/g【Linux】编译器gc…

RPA+AI:自动化办公机器人开发指南

RPAAI:自动化办公机器人开发指南 系统化学习人工智能网站(收藏):https://www.captainbed.cn/flu 文章目录 RPAAI:自动化办公机器人开发指南摘要引言技术融合路径1. 传感器层:多模态数据接入2. 决策层&…

计算矩阵A和B的乘积

根据矩阵乘法规则,编程计算矩阵的乘积。函数fix_prod_ele()是基本方法编写,函数fix_prod_opt()是优化方法编写。 程序代码 #define N 3 #define M 4 typedef int fix_matrix1[N][M]; typedef int fix_matrix2[M][N]; int fix_prod_ele(f…

Houdini POP入门学习05 - 物理属性

接下来随着教程学习碰撞部分,当粒子较为复杂或者下载了一些粒子模板进行修改时,会遇到一些较奇怪问题,如粒子穿透等,这些问题实际上可以通过调节参数解决。 hip资源文件:https://download.csdn.net/download/grayrail…

每日Prompt:双重曝光

提示词 新中式,这幅图像将人体头像轮廓与山水中式建筑融为一体,双重曝光,体现了反思、内心平静以及人与自然相互联系的主题,靛蓝,水墨画,晕染,极简

【LLM】多智能体系统 Why Do Multi-Agent LLM Systems Fail?

note 构建一个成功的 MAS,不仅仅是提升底层 LLM 的智能那么简单,它更像是在构建一个组织。如果组织结构、沟通协议、权责分配、质量控制流程设计不当,即使每个成员(智能体)都很“聪明”,整个系统也可能像一…

CSS 定位:原理 + 场景 + 示例全解析

一. 什么是CSS定位? CSS中的position属性用于设置元素的定位方式,它决定了元素在页面中的"定位行为" 为什么需要定位? 常规布局(如 display: block)适用于主结构 定位适用于浮动按钮,弹出层,粘性标题等场景帮助我们精确控制元素在页面中的位置 二. 定位类型全…

如何在没有 iTunes 的情况下备份 iPhone

我可以在没有 iTunes 的情况下将 iPhone 备份到电脑吗?虽然 iTunes 曾经是备份 iPhone 的主要方法,但它并不是 iOS 用户唯一的备份选项。您可以选择多种方便的替代方案来备份 iPhone,无需使用 iTunes。您可以在这里获得更灵活、更人性化的备份…

如何把 Mac Finder 用得更顺手?——高效文件管理定制指南

系统梳理提升 Mac Finder 体验的实用设置与技巧,助你用更高效的方式管理文件。文末引出进阶选择 Path Finder。 阅读原文请转到:https://jimmysong.io/blog/customize-finder-for-efficiency/ 作为一个用 Mac 多年的用户,我始终觉得 Finder 虽…

手拉手处理RuoYi脚手架常见文问题

若依前后端分离版开发入门 基础环境:JDK1.8mysqlRedisMavenVue 取消登录验证码 后端 修改ruoyi-ui项目中的login.vue 在ruoyi-ui项目>src>views中找到login.vue文件 1、注释验证码展示及录入部分 2、 注释code必填校验,默认验证码开关为false …

使用柏林噪声生成随机地图

简单介绍柏林噪声 柏林噪声(Perlin Noise)是一种由 Ken Perlin 在1983年提出的梯度噪声(Gradient Noise)算法,用于生成自然、连续的随机值。它被广泛用于计算机图形学中模拟自然现象(如地形、云层、火焰等…

C++课设:实现简易文件加密工具(凯撒密码、异或加密、Base64编码)

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、初识文件加密:为什么需要…

H_Prj06_03 8088单板机串口读取8088ROM复位内存

1.8088CPU复位时,CSFFFFH,IP0000H,因此在ROM的逻辑地址FFFF:0000(FFF0H)处一般要防止一个长跳转指令LJMP(机器码位EAH) 2.写一个完整的8086汇编程序,通过查询方式检测串口接收符串‘r’&#x…

构建 MCP 服务器:第 3 部分 — 添加提示

这是我们构建 MCP 服务器的四部分教程的第三部分。在第一部分中,我们使用基本资源创建了第一个MCP 服务器;在第二部分中,我们添加了资源模板并改进了代码组织。现在,我们将进一步重构代码并添加提示功能。 什么是 MCP 提示&#…

基于React + FastAPI + LangChain + 通义千问的智能医疗问答系统

📌 文章摘要: 本文详细介绍了如何在前端通过 Fetch 实现与 FastAPI 后端的 流式响应通信,并支持图文多模态数据上传。通过构建 multipart/form-data 请求,配合 ReadableStream 实时读取 AI 回复内容,实现类似 ChatGPT…

C# 中替换多层级数据的 Id 和 ParentId,保持主从或父子关系不变

在C#中替换多层级数据的Id和ParentId,同时保持父子关系不变,可以通过以下步骤实现: 创建旧Id到新Id的映射:遍历所有节点,为每个旧Id生成唯一的新Id,并存储在字典中。 替换节点的Id和ParentId:…