HOWTO · Python

OpenCV 扩张

在本文中,我们将解释形态变换概念,例如侵蚀和扩张,我们使用它们在 OpenCV 中的图像中从给定对象的边界添加和删除像素。

本页内容

使用 cv2.dilate 扩大图像中的亮色前景区域。在 OpenCV 中,膨胀会检查每个像素的邻域,并按照结构核扩展前景。灰度图像和二值图像都可以使用此操作;下面的示例使用只有一个亮像素的灰度图像,便于观察膨胀效果。

OpenCV 膨胀的作用

形态学操作使用核(也称结构元素)处理图像。对于二值图像,前景像素通常表示为白色(255),背景像素表示为黑色(0)。膨胀会将核邻域中的最大值设为每个输出像素的值。因此,亮色区域会扩大,彼此靠近的亮色区域也可能连在一起。腐蚀则使用邻域中的最小值,所以亮色区域会缩小。

核的邻域会在图像上滑动,这与二维卷积使用的局部窗口类似;不过,膨胀取的是邻域最大值,而不是计算加权和。对于灰度输入,这种取最大值的操作也会使局部区域变亮,并不限于只有两种取值的掩码。

使用 Python 和 OpenCV 进行膨胀

这个独立示例使用一张只有一个亮像素的小型灰度图像,以及一个 3 × 3 矩形邻域。安装了 OpenCV 和 NumPy 时,示例会使用 cv2.dilate;否则,标准库回退实现会应用相同的邻域最大值规则,因此示例仍可运行。

本节中的代码以及显示的标签或输出仍为英文。

import importlib.util

image = [
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0],
    [0, 0, 255, 0, 0],
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0],
]

if importlib.util.find_spec("cv2") and importlib.util.find_spec("numpy"):
    import cv2
    import numpy as np

    kernel = np.ones((3, 3), dtype=np.uint8)
    dilated = cv2.dilate(np.array(image, dtype=np.uint8), kernel, iterations=1).tolist()
else:
    dilated = [
        [
            max(image[y][x] for y in range(max(0, row - 1), min(5, row + 2))
                for x in range(max(0, col - 1), min(5, col + 2)))
            for col in range(5)
        ]
        for row in range(5)
    ]

print(dilated)

输出块展示这张小型灰度图像经过膨胀后的像素结果。代码块和输出块中的显示标签保留英文,以保持示例与英文原文的字节一致。

[[0, 0, 0, 0, 0], [0, 255, 255, 255, 0], [0, 255, 255, 255, 0], [0, 255, 255, 255, 0], [0, 0, 0, 0, 0]]

对于基于文件的工作流,可使用 cv2.imread("input.png", cv2.IMREAD_GRAYSCALE) 加载灰度图像,并在调用 cv2.dilate 前检查返回值是否为 None。路径错误或文件格式不受支持时,返回值可能是 None;对该值执行形态学操作无法得到图像。如需之后比较输入与输出,请将它们保存在不同的数组中。

核和迭代次数如何影响膨胀

核决定邻域的范围和形状。较大的核会检查更广的区域,通常能让亮色特征扩展得更远。矩形核适合均匀地向水平和垂直方向扩展;椭圆形或十字形核则会改变邻域的几何形状。应根据需要影响的间隙和特征选择核的尺寸与形状,再用有代表性的图像检查结果。

iterations 参数会重复执行膨胀。增加迭代次数会累积扩张效果,因此狭窄间隙可能闭合,附近区域也可能合并。可以从一次迭代和较小的核开始,然后每次只调整一个设置。视觉效果更强并不代表结果更好:过度扩张可能会抹去精细细节。

比较膨胀、腐蚀、开运算和闭运算

腐蚀会缩小亮色前景区域,膨胀会扩大它们。开运算先腐蚀再膨胀;根据核和图像内容,它可以去除较小的亮色细节,同时保留较大的结构。闭运算先膨胀再腐蚀,可以填补亮色结构中的小暗缝。这些操作序列不保证恢复每个原始像素;其效果取决于结构元素和图像中的特征。

如果要处理的是小块亮色噪声,可使用开运算;如果要处理亮色前景中的小暗孔或断裂,可使用闭运算。对于亮色背景上的暗色对象,应先确定哪种极性代表前景;在应用相同的判断方法前,可能需要先反转图像。

理解前景极性和输出结果

OpenCV 的基本膨胀会扩展像素值较大的区域。在亮色前景、暗色背景的二值图像中,白色前景会向附近的黑色背景扩展。在白纸上的暗色文字照片中,亮色纸张的像素值较高,因此膨胀会扩大纸张区域,使暗色文字看起来变细。这就是为什么结果可能与“扩大对象”的直观理解相反。

如果希望扩大暗色文字本身,可以先反转灰度图像,对反转后的图像执行膨胀,再将结果反转回来。对于彩色图像,每个通道都会作为图像数据分别处理;如果目标前景是形状或标签,而不是某种颜色强度,应使用单通道灰度图或二值掩码。

检查常见的输入和显示问题

处理前请确认源图像已成功加载,并检查核的尺寸是否适合图像中的细节。较宽的核或较多的迭代次数可能会合并分离的区域,或消除狭窄的间隙。如果输出看起来没有变化,请确认所选前景比周围区域亮,并且核的邻域包含与预期变化相关的附近像素。核的尺寸和形状决定会合并哪些局部像素值,因此核可以影响尺寸大于自身的特征;核不必达到目标特征的尺寸。

OpenCV 以 BGR 通道顺序读取彩色图像,而 Matplotlib 会将三通道数组解释为 RGB。使用 matplotlib.pyplot.imshow 绘图前,可用 cv2.cvtColor(image, cv2.COLOR_BGR2RGB) 转换 BGR 图像;对于灰度数组,应传入 cmap="gray",以使用灰度色图显示。显示颜色不匹配会影响可视化效果,但不会改变膨胀计算本身。