计算机视觉——图像二值化和形态学操作,从全局 / 自适应阈值分割到腐蚀、膨胀及开 / 闭运算的原理与实现
摘要 本文介绍了图像处理中的二值化和形态学基础操作。二值化分为全局和自适应阈值方法:全局二值化通过固定阈值分割图像,适用于光照均匀的场景;自适应阈值则根据局部区域动态计算阈值,能有效处理光照不均的图像。形态学操作包括腐蚀和膨胀:腐蚀通过结构元素缩小前景目标,用于去噪和分离物体;膨胀则扩展前景区域,用于连接断裂部分或填充空洞。文中提供了Python代码示例及效果对比图,展示了不同参数对处理结果的影响
二值化
全局二值化
全局二值化是指:使用一个固定的阈值(threshold)对整个灰度图像进行处理,将图像中所有像素点的灰度值与该阈值比较,根据比较结果将像素点设置为两种极端值(通常是 0 和 255,即黑色和白色)。即将灰度图像转换为黑白二值图像。
它的核心思想是:假设图像中只有前景(目标)和背景两种区域,且两者的灰度值分布有明显差异,因此可以用一个全局阈值将它们分离。
import numpy as np
import cv2
#全局二值化
img = cv2.imread('lena.jpg')
new = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#参数可改变
ret,dst = cv2.threshold(new,127,255,cv2.THRESH_BINARY)
#dst = cv2.threshold(new,127,255,cv2.THRESH_BINARY)[1]
cv2.imshow('img', img)
cv2.imshow('new',new)
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
输出结果对比:
代码注释
cv2.threshold() 参数说明
该函数是 OpenCV 实现全局二值化的核心函数,参数含义如下:
new:输入图像(必须是单通道灰度图,不能直接用彩色图)。
127:阈值(threshold),用于判断像素的灰度值。
255:最大值(maxval),当像素满足阈值条件时,被赋予的值(通常设为 255,表示白色)。
cv2.THRESH_BINARY:二值化类型,决定像素与阈值比较后的处理方式。
cv2.THRESH_BINARY:若像素灰度值 > 阈值 → 设为maxval(255,白色);否则 → 设为 0(黑色)。
公式:dst(x,y) = maxval if src(x,y) > thresh else 0
cv2.THRESH_BINARY_INV:与THRESH_BINARY相反:若像素灰度值 > 阈值 → 设为 0(黑色);否则 → 设为maxval(255,白色)。
cv2.THRESH_TRUNC:若像素灰度值 > 阈值 → 设为阈值(如 127);否则 → 保持原灰度值(不转为 0)。
cv2.THRESH_TOZERO:若像素灰度值 > 阈值 → 保持原灰度值;否则 → 设为 0(黑色)。
cv2.THRESH_TOZERO_INV:与THRESH_TOZERO相反:若像素灰度值 > 阈值 → 设为 0;否则 → 保持原灰度值。
全局二值化的特点
优点:
实现简单,计算速度快(仅需一次遍历图像)。
适用于光照均匀、前景与背景灰度差异明显的图像(如文字识别中的黑白文档)。
缺点:
阈值是固定的,无法适应图像中光照不均匀的区域(例如:同一图像中有的地方亮、有的地方暗)。
若前景与背景的灰度值分布重叠较多(无明显分界),二值化效果会很差(目标与背景难以分离)。
总结
全局二值化是一种简单高效的图像分割方法,通过固定阈值将灰度图转为黑白二值图,适合处理光照均匀、目标与背景差异明显的图像。你的代码正确实现了这一过程,通过调整阈值(如 127)和二值化类型,可以得到不同的黑白分割效果。
自适应阈值二值化
自适应阈值二值化(Adaptive Thresholding),这是一种针对光照不均匀图像的二值化方法。与全局二值化使用固定阈值不同,它会根据图像局部区域的灰度特征自动计算每个区域的阈值,从而在复杂光照下获得更优的二值化效果。
什么是自适应阈值二值化?
自适应阈值二值化的核心思想是:将图像分割成多个局部区域,每个区域使用自己的阈值进行二值化。这种方法解决了全局二值化的局限性 —— 当图像存在光照不均(如部分区域亮、部分区域暗)时,固定阈值无法兼顾所有区域,而自适应阈值能让每个局部区域根据自身灰度分布确定阈值,从而更准确地分离前景与背景。
#自适应阈值二值化
import numpy as np
import cv2
#文字照片
img = cv2.imread('lena.jpg')
new = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#参数可改变
dst = cv2.adaptiveThreshold(new,255,cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY,3,0)
#dst = cv2.threshold(new,127,255,cv2.THRESH_BINARY)[1]
cv2.imshow('img', img)
cv2.imshow('new',new)
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
输出结果对比:
代码注释
# 先将彩色图转为灰度图(二值化前提)
new = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 自适应阈值二值化
dst = cv2.adaptiveThreshold(
new, # 输入:单通道灰度图
255, # 最大值(满足条件时赋予的像素值,通常为255)
cv2.ADAPTIVE_THRESH_MEAN_C, # 阈值计算方法(均值法)
cv2.THRESH_BINARY, # 二值化类型(与全局二值化的类型一致)
3, # 块大小(局部区域的尺寸,必须是奇数)
0 # 常数C(阈值的调整值,计算出的局部均值减去C作为最终阈值)
)
cv2.adaptiveThreshold() 参数详解
1),输入图像(第一个参数):
必须是单通道灰度图(如代码中的new)。
2),最大值(第二个参数):
当像素满足阈值条件时,被赋予的值(通常设为 255,即白色)。
3),阈值计算方法(第三个参数):
决定如何计算局部区域的阈值,有两种常用方式:
cv2.ADAPTIVE_THRESH_MEAN_C:
阈值 = 局部区域内所有像素的平均值 - 常数 C。
cv2.ADAPTIVE_THRESH_GAUSSIAN_C:
阈值 = 局部区域内所有像素的高斯加权平均值 - 常数 C(高斯加权会让区域中心像素的权重更高,边缘像素权重更低,对噪声更鲁棒)。
4),二值化类型(第四个参数):
与全局二值化的类型一致,如cv2.THRESH_BINARY(大于阈值则为 255,否则为 0)、cv2.THRESH_BINARY_INV(反转结果)等。
5),块大小(第五个参数):
定义 “局部区域” 的尺寸(如 3、5、7…),必须是奇数。
块大小越小:对局部细节越敏感,可能保留更多细节,但易受噪声影响。
块大小越大:对光照变化的平滑性越好,但可能模糊小目标(如细线条文字)。
6),常数 C(第六个参数):
对计算出的局部均值(或高斯均值)进行微调,公式为:最终阈值 = 局部均值(或高斯均值) - C
C 为正数时:阈值降低,更多像素会被判定为前景(白色)。
C 为负数时:阈值升高,更多像素会被判定为背景(黑色)。
通常根据图像噪声情况调整,默认可设为 0。
总结
自适应阈值二值化通过 “局部区域自适应阈值” 解决了光照不均的问题,特别适合处理文字、文档扫描件(常存在阴影)或复杂光照下的图像。你的代码中使用了均值法计算局部阈值,通过调整块大小(如 3→11)和常数 C(如 0→5),可以适配不同的图像场景,得到更清晰的黑白分割效果。
二,基础操作
1,腐蚀
在形态学图像处理中,腐蚀是最基础的操作之一,其作用类似于 “侵蚀” 图像中的前景区域,通过指定的结构元素(核)对图像进行遍历,最终使前景目标 “收缩” 或 “细化”,常用于去除细小噪声、分离相邻目标或削弱目标边缘。
腐蚀的原理
腐蚀操作的核心逻辑是:
用一个预设的结构元素(核,Kernel)在图像上滑动,只有当核完全覆盖前景区域(即核内所有像素均为前景)时,核的中心像素才被保留为前景,否则被置为背景。
形象理解:可以将核想象成一个 “滑动窗口”,它会 “吞噬” 前景区域中无法完全容纳它的部分,最终使前景目标的边缘被侵蚀,尺寸缩小。
import cv2
import numpy as np
# 读取图像(通常用灰度图,前景为白色,背景为黑色)
img = cv2.imread('image.jpg', 0) # 0表示以灰度图读取
# 定义结构元素(核),形状和大小决定腐蚀效果
kernel = np.ones((3, 3), np.uint8) # 3x3矩形核(也可用cv2.getStructuringElement定义)
# 例如:十字形核 cv2.getStructuringElement(cv2.MORPH_CROSS, (5,5))
# 执行腐蚀操作(iterations为迭代次数,次数越多腐蚀越彻底)
eroded = cv2.erode(img, kernel, iterations=1)
# 显示结果
cv2.imshow('Original', img)
cv2.imshow('Eroded', eroded)
cv2.waitKey(0)
cv2.destroyAllWindows()
输出结果对比:
关键参数说明
1,结构元素(核):
大小:核越大,腐蚀作用越强(目标收缩越明显)。例如 3x3 核腐蚀较弱,10x10 核腐蚀剧烈。
形状:常用矩形(MORPH_RECT)、十字形(MORPH_CROSS)、椭圆形(MORPH_ELLIPSE),形状会影响腐蚀的方向特性(如十字形核对线性特征的腐蚀更敏感)。
2,迭代次数(iterations):默认值为 1,迭代次数越多,腐蚀效果叠加越明显(相当于多次执行腐蚀)。例如,用 3x3 核迭代 2 次,效果接近用 5x5 核迭代 1 次。
总结
腐蚀是通过结构元素 “侵蚀” 前景目标的操作,核心是 “收缩” 前景、去除细小成分。它是形态学处理的基础,常与膨胀结合使用(如开运算、闭运算),在图像去噪、目标分割等任务中应用广泛。
2,膨胀
在形态学图像处理中,膨胀 是与腐蚀相对的基础操作,其作用是 “扩张” 或 “加粗” 图像中的前景区域。通过指定的结构元素(核)对图像进行遍历,最终使前景目标的边缘向外扩展,常用于连接断裂的目标、填充细小空洞或增强目标的整体形态。
膨胀的原理
膨胀操作的核心逻辑是:
用一个预设的结构元素(核,Kernel)在图像上滑动,只要核与前景区域有任何重叠(即核内至少有一个像素为前景),核的中心像素就被置为前景。
形象理解:可以将核想象成一个 “刷子”,它会沿着前景区域的边缘 “向外涂抹”,使前景目标的边界扩张,尺寸增大。
import cv2
import numpy as np
# 读取图像(通常用灰度图,前景为白色,背景为黑色)
img = cv2.imread('image.jpg', 0) # 0表示以灰度图读取
# 定义结构元素(核),形状和大小决定膨胀效果
kernel = np.ones((3, 3), np.uint8) # 3x3矩形核(也可用cv2.getStructuringElement定义)
# 例如:椭圆形核 cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
# 执行膨胀操作(iterations为迭代次数,次数越多膨胀越彻底)
dilated = cv2.dilate(img, kernel, iterations=1)
# 显示结果
cv2.imshow('Original', img)
cv2.imshow('Dilated', dilated)
cv2.waitKey(0)
cv2.destroyAllWindows()
输出结果对比:
关键参数说明
1,结构元素(核):
大小:核越大,膨胀作用越强(目标扩张越明显)。例如 3x3 核膨胀较弱,10x10 核膨胀剧烈。
形状:常用矩形(MORPH_RECT)、十字形(MORPH_CROSS)、椭圆形(MORPH_ELLIPSE),形状会影响膨胀的方向特性(如十字形核对线性特征的扩张更明显)。
2,**迭代次数(iterations):**默认值为 1,迭代次数越多,膨胀效果叠加越明显(相当于多次执行膨胀)。例如,用 3x3 核迭代 2 次,效果接近用 5x5 核迭代 1 次。
3,开运算
在形态学图像处理中,开运算 是一种基础组合形态学操作,由先腐蚀后膨胀 两个步骤组成,主要用于去除图像中的细小噪声、分离相邻物体、平滑目标边缘,同时基本保持目标的整体形状和大小不变。
即:
首先用结构元素(核,Kernel)对图像进行腐蚀操作,去除细小噪声和目标边缘的凸起部分;
然后对腐蚀后的结果用相同的核进行膨胀操作,恢复目标的主体形状(抵消腐蚀对目标主体的收缩影响)。
开运算的作用(去除噪点,黑底白噪点)
去除细小噪声:
腐蚀会 “吞噬” 小于核尺寸的噪声点,后续的膨胀会恢复目标主体,而噪声点因已被腐蚀去除,无法再被膨胀恢复,从而实现去噪。
分离相邻目标:
若两个目标因距离过近而粘连,腐蚀会先断开连接,再通过膨胀恢复各自的形状,实现分离。
平滑目标边缘:
腐蚀会消除边缘的尖锐凸起,膨胀会平滑边缘的凹陷,最终使目标边缘更规整(但可能丢失一些细节)。
保持目标整体形态:
由于腐蚀和膨胀使用相同的核,目标的主体大小和形状基本不变(仅去除细小部分)。
import cv2
import numpy as np
# 读取图像
img = cv2.imread('42.jpg')
if img is None:
print("无法加载图像,请检查文件路径!")
exit()
# ----------------------
# 给图像添加白噪点
# ----------------------
# 复制原图用于添加噪声(避免修改原图)
noise_img = img.copy()
# 获取图像尺寸
h, w, c = noise_img.shape
# 设置噪声比例(总像素的5%)
noise_ratio = 0.05
# 计算噪声像素数量
noise_count = int(h * w * noise_ratio)
# 随机生成白噪点(白色:BGR=255,255,255)
for _ in range(noise_count):
# 随机选择像素位置
x = np.random.randint(0, w)
y = np.random.randint(0, h)
# 设置为白色噪点
noise_img[y, x] = [255, 255, 255]
# ----------------------
# 对含噪图像进行开运算
# ----------------------
# 定义结构元素(矩形核,3x3)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
# 开运算(先腐蚀去除噪点,再膨胀恢复目标)
opening = cv2.morphologyEx(noise_img, cv2.MORPH_OPEN, kernel)
# ----------------------
# 显示结果
# ----------------------
cv2.imshow('Original', img) # 原图
cv2.imshow('With White Noise', noise_img) # 加白噪点图
cv2.imshow('After Opening', opening) # 开运算结果
cv2.waitKey(0)
cv2.destroyAllWindows()
输出结果对比:
4.闭运算
在形态学图像处理中,闭运算是与开运算对应的基础组合操作,由先膨胀后腐蚀两个步骤组成,主要用于填充图像中前景区域的细小空洞、连接前景内部的断裂部分,同时基本保持目标的整体形状和大小不变。
即:
首先用结构元素(核,Kernel)对图像进行膨胀操作,填充前景中的细小空洞、连接断裂部分;
然后对膨胀后的结果用相同的核进行腐蚀操作,恢复目标的主体形状(抵消膨胀对目标主体的扩张影响)。
闭运算的作用
填充细小空洞:
膨胀会先 “扩张” 前景,填充内部小于核尺寸的空洞(背景区域),后续的腐蚀会收缩目标边缘,使空洞被永久填充,同时不显著改变目标的整体大小。
连接前景内部的断裂:
若前景区域内部有细小裂缝或断裂,膨胀会先连接这些断裂,再通过腐蚀恢复目标的原始轮廓,实现内部结构的修复。
平滑目标内部边缘:
膨胀会消除前景内部边缘的凹陷(空洞边缘),腐蚀会平滑外部边缘的凸起,使目标整体更完整、边缘更规整。
保持目标整体形态:
由于膨胀和腐蚀使用相同的核,目标的主体大小和形状基本不变(仅修复内部缺陷)。
关键参数:结构元素(核)
核的大小和形状直接影响闭运算效果:
核越大:能填充的空洞越大、连接的断裂越宽,但可能过度模糊目标细节。
核越小:仅能填充微小空洞和连接细裂缝,保留更多细节,但修复能力有限。
常用核形状:与开运算一致(矩形、椭圆、十字形),根据目标形状选择(如椭圆核适合填充圆形空洞)。
import cv2
import numpy as np
# 读取图像(确保图像有明显的白色前景区域)
img = cv2.imread('42.jpg')
if img is None:
print("无法加载图像,请检查文件路径!")
exit()
# 复制原图用于添加噪声(避免修改原图)
noise_img = img.copy()
# 转为灰度图用于判断白色区域(便于后续筛选白色像素)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化:将灰度值>200的区域视为“白色区域”(可根据图像调整阈值)
_, white_mask = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)
# 获取白色区域的所有像素坐标
white_pixels = np.where(white_mask == 255) # 返回(行坐标数组, 列坐标数组)
total_white = len(white_pixels[0])
if total_white == 0:
print("图像中未检测到明显白色区域,无法添加内部噪点!")
exit()
# ----------------------
# 在白色区域内部添加黑噪点(黑色:BGR=0,0,0)
# ----------------------
noise_ratio = 0.03 # 黑噪点占白色区域像素的3%(可调整密度)
noise_count = int(total_white * noise_ratio)
# 随机选择白色区域内的像素,设置为黑色噪点
indices = np.random.choice(total_white, noise_count, replace=False)
for i in indices:
y = white_pixels[0][i] # 行坐标
x = white_pixels[1][i] # 列坐标
noise_img[y, x] = [0, 0, 0] # 黑色噪点
# ----------------------
# 对含黑噪点的图像进行闭运算
# ----------------------
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) # 3x3矩形核
closing = cv2.morphologyEx(noise_img, cv2.MORPH_CLOSE, kernel)
# ----------------------
# 显示结果
# ----------------------
cv2.imshow('Original', img) # 原图
cv2.imshow('White Area with Black Noise', noise_img) # 白色区域含黑噪点图
cv2.imshow('After Closing', closing) # 闭运算修复结果
cv2.waitKey(0)
cv2.destroyAllWindows()
代码说明:
1,识别白色区域:通过灰度化和二值化(阈值 200)提取图像中的白色区域(可根据实际图像亮度调整阈值,确保准确识别白色区域)。
2,添加黑噪点:仅在白色区域内部随机选择 3% 的像素点,将其设为黑色(模拟白色区域内的空洞、污渍等缺陷),避免在背景区域添加噪声(确保噪声位置符合 “内部缺陷” 的场景)。
3,闭运算修复原理:
第一步膨胀:白色区域会 “扩张”,填充内部的黑色噪点(黑噪点尺寸小于核时会被白色覆盖)。
第二步腐蚀:膨胀后的白色区域会收缩回原来的大小,最终黑色噪点被填充,白色区域恢复完整。
4,效果对比:运行后可观察到:
含噪图像中白色区域有许多黑色小点(模拟缺陷)。
闭运算结果中,大部分黑色噪点被填充,白色区域更完整(核尺寸越大,能填充的黑噪点越大)。
输出结果对比;
如果输出结果效果不好,可以更改核大小。
在 OpenCV 形态学操作(如腐蚀、膨胀、开运算、闭运算)中,iterations(迭代次数)是控制操作强度的关键参数,指同一形态学操作重复执行的次数。
5,iterations核心作用
iterations 决定操作的 “叠加效果”:
1)迭代次数越多,形态学效果越显著(如腐蚀更彻底、膨胀更明显)。
2)1 次迭代是基础效果,多次迭代相当于连续执行相同操作(用相同核)
关键特点
1,效果等价性:小核多次迭代 ≈ 大核单次迭代(但不完全相同)。
例:3x3 核迭代 2 次,效果接近 5x5 核迭代 1 次,但边缘平滑度不同。
2,对不同操作的影响:
腐蚀 / 膨胀:迭代次数越多,目标收缩 / 扩张越剧烈(可能导致目标变形、消失或粘连)。
开 / 闭运算:迭代次数越多,去噪 / 填充效果越强(但可能模糊细节)。
3,注意事项:迭代次数并非越多越好,需根据目标尺寸和需求调整(如处理细小噪声用 1-2 次,修复大缺陷用 3-5 次)。
更多推荐
所有评论(0)