3、OpenCV基础(二):图像滤波与边缘检测、形态学操作、轮廓查找与绘制、直方图均衡化

好,咱们接着往下聊。上一章我们把图像读写、颜色空间转换这些基本功过了一遍。这一章,我要带你进入真正的「图像处理核心区」——滤波、边缘检测、形态学操作、轮廓查找,还有直方图均衡化。

说实话,这些内容在YOLO实战里,你可能不会每帧都用到。但一旦遇到光线差、目标模糊、背景杂乱的情况,这些预处理手段就是你的救命稻草。我在做工业质检项目时,有次摄像头反光严重,YOLO死活检测不到缺陷。后来加了一步滤波和直方图均衡化,召回率直接从60%飙到92%。嗯,你想想看,这差距有多大。

3.1 图像滤波:让画面更干净

滤波说白了就是「去噪」。摄像头采集的图像,或多或少都有噪声——可能是传感器热噪声,也可能是环境光干扰。滤波的目的,就是把这些乱七八糟的像素点抹平,保留我们真正关心的结构信息。

3.1.1 均值滤波

最简单的滤波方式。取一个窗口(比如3x3),计算窗口内所有像素的平均值,替换中心像素。我习惯用 cv2.blur() 来实现。

import cv2
import numpy as np

img = cv2.imread('noisy.jpg')
# 均值滤波,核大小为5x5
blurred = cv2.blur(img, (5, 5))
cv2.imshow('Blurred', blurred)
cv2.waitKey(0)
我的经验:均值滤波速度快,但会丢失边缘细节。如果你只是做简单的预处理,可以用它。但追求效果的话,我建议用高斯滤波。

3.1.2 高斯滤波

高斯滤波跟均值滤波的区别在于——它给窗口内的像素分配了不同的权重。离中心越近,权重越大。这样既能去噪,又能更好地保留边缘。

# 高斯滤波,核大小5x5,sigmaX=0(自动计算)
gaussian = cv2.GaussianBlur(img, (5, 5), 0)

为什么sigmaX设成0?OpenCV会自动根据核大小计算sigma值。我个人习惯这么写,省事。如果你需要更精细的控制,可以手动指定sigma值。

3.1.3 中值滤波

中值滤波对「椒盐噪声」特别有效。什么是椒盐噪声?就是图像上随机出现的黑白点,像撒了盐和胡椒一样。中值滤波取窗口内所有像素的中位数,而不是平均值。这样一来,那些极端噪声点就被直接剔除了。

# 中值滤波,核大小5(注意是整数,不是元组)
median = cv2.medianBlur(img, 5)
注意:中值滤波的核大小必须是奇数。我曾经在项目里写了个偶数,结果程序直接报错,排查了半天才发现是这个问题。

3.2 边缘检测:找到物体的轮廓

边缘检测是计算机视觉的基石。YOLO虽然不直接依赖边缘检测,但如果你要做目标分割、轮廓提取,这一步就绕不开。

3.2.1 Canny边缘检测

Canny算法是目前最流行的边缘检测算法。它分四步:去噪、计算梯度、非极大值抑制、双阈值检测。你不需要记住全部细节,但要知道两个关键参数:低阈值和高阈值。

# Canny边缘检测
edges = cv2.Canny(gaussian, 50, 150)
cv2.imshow('Edges', edges)

阈值怎么调?我一般这样试:低阈值设50,高阈值设150。如果边缘太多(噪声多),就提高低阈值;如果边缘太少(漏检),就降低低阈值。说白了,这就是个经验活。

核心要点:Canny的输入最好是灰度图,而且先做高斯滤波效果更好。我见过有人直接对彩色图做Canny,结果边缘乱七八糟——因为三个通道的梯度互相干扰。

3.3 形态学操作:膨胀与腐蚀

形态学操作,听起来高大上,其实就是「膨胀」和「腐蚀」两个基本操作。它们通常用于二值图像,用来处理物体形状。

3.3.1 腐蚀

腐蚀会让白色区域变小。原理是:用一个结构元素(比如3x3的矩形)扫描图像,只有当结构元素完全覆盖白色区域时,中心点才保留为白色。否则变成黑色。

kernel = np.ones((5,5), np.uint8)
eroded = cv2.erode(binary_img, kernel, iterations=1)

3.3.2 膨胀

膨胀跟腐蚀相反,会让白色区域变大。只要结构元素覆盖到任何一个白色像素,中心点就变成白色。

dilated = cv2.dilate(binary_img, kernel, iterations=1)

3.3.3 开运算与闭运算

开运算 = 先腐蚀后膨胀。用来去除小白点噪声。闭运算 = 先膨胀后腐蚀。用来填充物体内部的小空洞。

# 开运算
opening = cv2.morphologyEx(binary_img, cv2.MORPH_OPEN, kernel)
# 闭运算
closing = cv2.morphologyEx(binary_img, cv2.MORPH_CLOSE, kernel)
避坑指南:我曾经在分割车牌字符时,直接用膨胀把字符连成了一片。后来改成先腐蚀再膨胀(开运算),才把粘连的字符分开。记住:形态学操作不是万能的,要根据实际效果调整核大小和迭代次数。

3.4 轮廓查找与绘制

找到边缘之后,下一步就是提取轮廓。轮廓是物体的边界点集合,可以用来计算面积、周长、形状匹配等。

3.4.1 查找轮廓

# 注意:cv2.findContours返回两个值(旧版OpenCV返回三个)
contours, hierarchy = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

参数说明:

  • cv2.RETR_EXTERNAL:只检测最外层轮廓。如果你要检测所有轮廓(包括内层),用 cv2.RETR_TREE
  • cv2.CHAIN_APPROX_SIMPLE:压缩轮廓点,只保留端点。如果你要精确轮廓,用 cv2.CHAIN_APPROX_NONE

3.4.2 绘制轮廓

# 在原图上绘制轮廓,绿色,线宽2
cv2.drawContours(img, contours, -1, (0, 255, 0), 2)

-1 表示绘制所有轮廓。如果你只想绘制第0个轮廓,就写 0

实战技巧:轮廓查找前,一定要确保图像是二值图,而且前景(白色)是你要检测的物体。如果背景是白色,记得先取反:binary = cv2.bitwise_not(binary)

3.5 直方图均衡化:让图像更清晰

直方图均衡化,说白了就是「拉伸对比度」。当图像整体偏暗或偏亮时,均衡化可以让细节更明显。

3.5.1 全局直方图均衡化

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
equalized = cv2.equalizeHist(gray)

注意:equalizeHist 只接受灰度图。如果你要对彩色图做均衡化,需要分别对三个通道处理,或者转换到HSV空间只对V通道处理。

3.5.2 自适应直方图均衡化(CLAHE)

全局均衡化有个问题:它会放大噪声。CLAHE(对比度受限自适应直方图均衡化)把图像分成小块,每个小块单独均衡化,然后拼接起来。这样既能增强对比度,又不会过度放大噪声。

clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
clahe_img = clahe.apply(gray)
我的建议:在YOLO预处理中,我强烈推荐用CLAHE代替全局均衡化。尤其是在夜间监控场景下,CLAHE能显著提升目标检测的准确率。clipLimit一般设2.0~3.0,太大容易产生伪影。

3.6 综合实战:预处理流水线

最后,我给你一个完整的预处理流水线。这是我做工业检测时常用的组合拳:

def preprocess(img):
    # 1. 转灰度
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 2. CLAHE增强
    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
    enhanced = clahe.apply(gray)
    # 3. 高斯滤波去噪
    blurred = cv2.GaussianBlur(enhanced, (5,5), 0)
    # 4. Canny边缘检测
    edges = cv2.Canny(blurred, 50, 150)
    # 5. 形态学闭运算填充空洞
    kernel = np.ones((3,3), np.uint8)
    closed = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel)
    # 6. 查找轮廓
    contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    return contours

这套流程,我用了好几年。你想想看,从原始图像到轮廓提取,每一步都有它的道理。滤波去噪、均衡化增强、边缘检测、形态学修复——环环相扣。

好了,这一章的内容就到这里。下一章我们会正式进入YOLO的世界,到时候这些预处理技巧会派上大用场。记住:好的预处理,能让你的模型事半功倍。

本章总结:
  • 滤波:均值、高斯、中值,各有适用场景
  • 边缘检测:Canny是首选,阈值要调参
  • 形态学:膨胀腐蚀、开闭运算,处理二值图
  • 轮廓:findContours + drawContours,注意输入必须是二值图
  • 直方图均衡化:CLAHE优于全局均衡化

公众号:蓝海资料掘金营,微信deep3321