OpenCV 图像预处理核心技术:阈值处理与滤波去噪
图像预处理是计算机视觉任务的关键步骤,其目标是优化图像质量、突出有效信息,为后续的边缘检测、目标识别等操作奠定基础。本文将通过实战代码,系统解析图像阈值处理(全局阈值、自适应阈值、Otsu 阈值)和图像滤波去噪(均值、高斯、中值、双边滤波)两大核心技术,帮助你理解不同场景下的预处理方案选择。
一、图像阈值处理:分割前景与背景
阈值处理是通过设定像素值阈值,将灰度图像(或彩色图像的单通道)转换为二值图像(仅黑白两色),从而分割出前景目标与背景。OpenCV 提供了三类阈值处理方法,分别应对不同的图像条件(如光照均匀 / 不均、有无噪声)。
1. 全局阈值:固定阈值分割(适用于光照均匀图像)
全局阈值使用单一固定阈值对整幅图像进行分割,操作简单但对光照不均的图像效果较差。OpenCV 支持 5 种全局阈值类型,核心是通过cv2.threshold()
实现。
代码实现:5 种全局阈值类型对比
import cv2
import numpy as np
from matplotlib import pyplot as plt# 读取图像(注意:阈值处理通常基于灰度图,此处直接用彩色图会对三通道分别处理)
img = cv2.imread('ocv01.jpg')
# 转换为灰度图(推荐做法,避免彩色通道干扰)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 全局阈值处理:参数依次为(输入图,阈值,最大值,阈值类型)
ret, thresh1 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY) # 二值化:>127→255(白),<127→0(黑)
ret, thresh2 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY_INV) # 反二值化:>127→0,<127→255
ret, thresh3 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TRUNC) # 截断:>127→127,<127不变
ret, thresh4 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO) # 归零:>127不变,<127→0
ret, thresh5 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO_INV) # 反归零:>127→0,<127不变# 显示结果
titles = ['Original Gray Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
images = [img_gray, thresh1, thresh2, thresh3, thresh4, thresh5]for i in range(6):plt.subplot(2, 3, i+1)plt.imshow(images[i], cmap='gray') # 灰度模式显示plt.title(titles[i])plt.xticks([]), plt.yticks([]) # 隐藏坐标轴plt.show()
关键参数与类型解析
阈值类型 | 作用逻辑 | 适用场景 |
---|---|---|
cv2.THRESH_BINARY | 像素值 > 阈值 → 最大值(255),否则 → 0 | 分割亮前景(如白字黑底) |
cv2.THRESH_BINARY_INV | 像素值 > 阈值 → 0,否则 → 最大值(255) | 分割暗前景(如黑字白底) |
cv2.THRESH_TRUNC | 像素值 > 阈值 → 阈值(127),否则不变 | 降低亮区域对比度 |
cv2.THRESH_TOZERO | 像素值 > 阈值 → 不变,否则 → 0 | 保留亮目标,抑制暗噪声 |
cv2.THRESH_TOZERO_INV | 像素值 > 阈值 → 0,否则 → 不变 | 保留暗目标,抑制亮噪声 |
ret
:函数返回的阈值(此处为 127,自定义时与输入阈值一致;自动阈值方法中为计算出的最优阈值)。- 注意:彩色图像直接阈值处理会对 B、G、R 三通道分别分割,可能导致颜色异常,因此推荐先转换为灰度图。
2. 自适应阈值:局部动态阈值(适用于光照不均图像)
全局阈值的缺陷是:当图像光照不均(如局部阴影、明暗差异大)时,单一阈值无法兼顾所有区域。自适应阈值通过局部区域的像素均值 / 高斯加权均值动态计算每个区域的阈值,解决光照不均问题。
代码实现:自适应阈值 vs 全局阈值
import cv2
import numpy as np
from matplotlib import pyplot as plt# 读取图像并转换为灰度图
img = cv2.imread('ocv01.jpg')
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 中值滤波预处理(降低噪声对阈值的影响)
img_gray = cv2.medianBlur(img_gray, 5)# 1. 全局阈值(对比用)
ret, th_global = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)# 2. 自适应阈值:参数依次为(输入图,最大值,自适应方法,阈值类型,块大小,常数C)
# 方法1:局部均值自适应(块内像素均值 - C 作为阈值)
th_mean = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, # 自适应方法:局部均值cv2.THRESH_BINARY, # 阈值类型11, 2 # 块大小(奇数),常数C(均值减去C)
)# 方法2:局部高斯加权均值自适应(块内像素高斯加权均值 - C 作为阈值)
th_gaussian = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, # 自适应方法:高斯加权均值cv2.THRESH_BINARY, 11, 2
)# 显示对比
titles = ['Original Gray Image', 'Global Threshold (v=127)', 'Adaptive Mean Threshold', 'Adaptive Gaussian Threshold'
]
images = [img_gray, th_global, th_mean, th_gaussian]for i in range(4):plt.subplot(2, 2, i+1)plt.imshow(images[i], cmap='gray')plt.title(titles[i])plt.xticks([]), plt.yticks([])plt.show()
核心参数解析
- 块大小(11):计算局部阈值的区域大小,必须为奇数(确保中心像素存在);块越大,阈值越平滑,反之越灵敏。
- 常数 C:从局部均值 / 高斯均值中减去的数值,用于调整阈值高低(C 为正则阈值降低,更多像素被判定为白色;C 为负则阈值升高)。
- 自适应方法对比:
ADAPTIVE_THRESH_MEAN_C
:计算速度快,但对噪声敏感。ADAPTIVE_THRESH_GAUSSIAN_C
:对噪声更鲁棒(高斯加权突出中心像素),但计算稍慢,适合噪声较多的图像。
3. Otsu 阈值:自动最优阈值(适用于噪声图像)
当图像灰度直方图呈现明显双峰分布(前景与背景像素值集中在两个区间)时,Otsu 阈值能自动计算出 “分离两个峰” 的最优阈值,无需手动调整。常与高斯滤波结合,降低噪声对直方图的干扰。
代码实现:Otsu 阈值(含噪声处理)
import cv2
import numpy as np
from matplotlib import pyplot as plt# 读取含噪声的图像并转换为灰度图
img_noisy = cv2.imread('noise.jpg')
img_gray = cv2.cvtColor(img_noisy, cv2.COLOR_BGR2GRAY)# 1. 普通全局阈值(手动设127,效果差)
ret1, th1 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)# 2. Otsu阈值(自动计算最优阈值,无需手动设阈值)
# 阈值设0,通过cv2.THRESH_OTSU标志自动计算ret2(最优阈值)
ret2, th2 = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)# 3. 高斯滤波+Otsu阈值(先去噪,再优化阈值)
img_blur = cv2.GaussianBlur(img_gray, (5, 5), 0) # 5x5高斯滤波
ret3, th3 = cv2.threshold(img_blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)# 显示结果(含直方图,直观展示双峰分布)
titles = ['Original Noisy Image', 'Histogram', 'Global Threshold (v=127)','Original Noisy Image', 'Histogram', "Otsu's Thresholding",'Gaussian Filtered Image', 'Histogram', "Otsu's Thresholding (After Blur)"
]
images = [img_gray, 0, th1, img_gray, 0, th2, img_blur, 0, th3]for i in range(3):# 第1列:图像plt.subplot(3, 3, i*3 + 1)plt.imshow(images[i*3], cmap='gray')plt.title(titles[i*3]), plt.xticks([]), plt.yticks([])# 第2列:直方图(ravel()将图像展平为一维数组)plt.subplot(3, 3, i*3 + 2)plt.hist(images[i*3].ravel(), 256) # 256个灰度级plt.title(titles[i*3 + 1]), plt.xticks([]), plt.yticks([])# 标注Otsu计算的最优阈值if i > 0:plt.axvline(x=ret2 if i==1 else ret3, color='r', linestyle='--', label=f'Threshold={ret2 if i==1 else ret3:.0f}')plt.legend()# 第3列:阈值结果plt.subplot(3, 3, i*3 + 3)plt.imshow(images[i*3 + 2], cmap='gray')plt.title(titles[i*3 + 2]), plt.xticks([]), plt.yticks([])plt.show()
核心原理与优势
- Otsu 算法通过计算 “类间方差最大化” 来确定最优阈值:将像素分为前景和背景两类,当两类的方差最大时,阈值能最清晰地分割两者。
- 适用场景:灰度直方图有明显双峰的图像(如文字扫描件、细胞图像);若直方图无明显双峰(如渐变图像),Otsu 效果会下降。
- 噪声影响:噪声会导致直方图峰模糊,因此先进行高斯滤波去噪,再用 Otsu 阈值,能显著提升分割效果。
二、图像滤波去噪:平滑图像与保留细节
图像噪声(如椒盐噪声、高斯噪声)会干扰后续处理(如阈值分割、边缘检测),滤波的核心是抑制噪声的同时,尽可能保留图像细节(如边缘)。OpenCV 提供线性滤波和非线性滤波两类方法,各有优劣。
1. 线性滤波:均值滤波(基于卷积的平滑)
线性滤波通过固定卷积核对图像进行卷积运算,均值滤波是最基础的线性滤波,核心是用 “卷积核覆盖区域的像素均值” 替代中心像素值,实现平滑去噪,但会模糊边缘。
代码实现:自定义均值滤波(用cv2.filter2D
)
import cv2
import numpy as np
from matplotlib import pyplot as plt# 读取彩色图像
img = cv2.imread('ocv01.jpg')
# 转换为RGB格式(matplotlib默认RGB显示,OpenCV默认BGR)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 1. 定义均值卷积核:5x5大小,每个元素为1/25(确保卷积后像素值在0-255)
kernel = np.ones((5, 5), np.uint8) / 25 # 5x5核的总权重为1,避免亮度异常# 2. 应用滤波:cv2.filter2D(输入图,输出深度(-1表示与输入一致),卷积核)
img_blur = cv2.filter2D(img_rgb, -1, kernel)# 显示对比
plt.subplot(121), plt.imshow(img_rgb), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(img_blur), plt.title('Mean Filter (5x5)')
plt.xticks([]), plt.yticks([])
plt.show()
原理与局限性
- 卷积核大小影响:核越大,平滑效果越强,但边缘模糊越严重(如 3x3 核模糊轻,7x7 核模糊重)。
- 局限性:线性滤波对所有像素一视同仁,会同时模糊噪声和边缘,适合对边缘要求不高的场景。
2. 常用非线性滤波:兼顾去噪与边缘保留
非线性滤波通过像素值的统计特性(如中值、高斯权重)动态调整输出,能在去噪的同时更好地保留边缘,是实际应用中的首选。OpenCV 提供 3 种常用非线性滤波:高斯滤波、中值滤波、双边滤波。
代码实现:4 种滤波效果对比
import cv2
import numpy as np
from matplotlib import pyplot as plt# 读取彩色图像并转换为RGB
img = cv2.imread('ocv01.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 1. 均值滤波(线性,对比用)
blur_mean = cv2.blur(img_rgb, (5, 5)) # 5x5核,等价于filter2D的均值核# 2. 高斯滤波(非线性,基于高斯权重,去高斯噪声优)
blur_gaussian = cv2.GaussianBlur(img_rgb, (5, 5), 0 # 5x5核,sigmaX=0(自动根据核大小计算)
)# 3. 中值滤波(非线性,基于像素中值,去椒盐噪声优)
blur_median = cv2.medianBlur(img_rgb, 5) # 5x5核(仅需奇数,无权重)# 4. 双边滤波(非线性,去噪+保边缘,效果最优但最慢)
blur_bilateral = cv2.bilateralFilter(img_rgb, 9, 75, 75 # 直径=9,颜色相似度sigma=75,空间相似度sigma=75
)# 显示对比(按需选择一种滤波显示)
plt.subplot(121), plt.imshow(img_rgb), plt.title('Original')
plt.xticks([]), plt.yticks([])
# 替换下方为要对比的滤波结果(如blur_gaussian、blur_median、blur_bilateral)
plt.subplot(122), plt.imshow(blur_bilateral), plt.title('Bilateral Filter')
plt.xticks([]), plt.yticks([])
plt.show()
4 种滤波核心对比
滤波类型 | 原理 | 优势 | 劣势 | 适用场景 |
---|---|---|---|---|
均值滤波 | 局部像素均值 | 计算快 | 边缘模糊严重 | 快速平滑、对边缘无要求 |
高斯滤波 | 局部像素高斯加权均值(中心权重高) | 去高斯噪声效果好,模糊轻于均值滤波 | 仍会轻微模糊边缘 | 高斯噪声为主的图像(如低光拍摄) |
中值滤波 | 局部像素中值(替换中心像素) | 去椒盐噪声(黑白噪点)效果最优,保边缘 | 对高斯噪声效果差,处理慢于高斯滤波 | 椒盐噪声为主的图像(如压缩失真) |
双边滤波 | 结合颜色相似度 + 空间相似度(边缘处权重低) | 去噪同时最大程度保留边缘 | 计算最慢,参数调整复杂 | 需保留边缘的场景(如人脸、文档) |
实用参数调整技巧
- 高斯滤波:
sigmaX
越大,去噪越强但模糊越重;核大小需与sigmaX
匹配(如sigmaX=1
用 3x3 核,sigmaX=2
用 5x5 核)。 - 双边滤波:
sigmaColor
越大,允许更多颜色差异的像素被平滑;sigmaSpace
越大,平滑范围越广;两者需同步调整(如均设为 50-100)。
总结
本文覆盖的图像预处理技术,是计算机视觉的 “地基”,核心应用场景如下:
- 阈值处理:
- 光照均匀 → 全局阈值(简单高效)。
- 光照不均 → 自适应阈值(局部动态调整)。
- 噪声多 + 直方图双峰 → Otsu 阈值(自动最优,需先去噪)。
- 滤波去噪:
- 高斯噪声 → 高斯滤波。
- 椒盐噪声 → 中值滤波。
- 需保边缘 → 双边滤波(优先选择,容忍较慢速度)。
- 快速平滑 → 均值滤波(仅用于临时预览)。
实际项目中,预处理通常是 “滤波→阈值” 的组合流程(如先高斯滤波去噪,再自适应阈值分割),需根据图像特点(噪声类型、光照条件)灵活选择参数,才能达到最佳效果。