4、图像预处理技术:灰度化、去噪、二值化、倾斜校正、透视变换
各位同学,欢迎来到第四讲。上一章我们聊了图像采集的硬件选型,今天咱们正式进入「让图像变干净」的环节。
说实话,在货架标签识别这个场景里,原始图像的质量往往惨不忍睹。光照不均、角度歪斜、背景杂乱……这些问题如果不处理,后面的OCR识别基本就是白费力气。我最早做这个项目时,就吃过这个亏——采集的图像直接扔进识别模型,结果准确率不到30%。后来老老实实把预处理做扎实,准确率直接飙到95%以上。
好,咱们一个一个来。
4.1 灰度化:去掉颜色,保留结构
为什么要灰度化?说白了,颜色信息对识别标签上的文字和条码来说,基本是冗余的。你想想看,货架标签上的文字是黑白的,条码也是黑白的,保留RGB三个通道只会增加计算量,还容易引入噪声。
常用的灰度化方法有三种:
| 方法 | 公式 | 特点 |
|---|---|---|
| 最大值法 | Gray = max(R, G, B) | 亮度偏高,细节保留一般 |
| 平均值法 | Gray = (R + G + B) / 3 | 均衡,但对比度可能下降 |
| 加权法(推荐) | Gray = 0.299R + 0.587G + 0.114B | 符合人眼感知,效果最好 |
我个人习惯用加权法。OpenCV里一行代码搞定:
import cv2
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
4.2 去噪:把「雪花」扫干净
灰度化之后,下一步就是去噪。货架场景下,噪声主要来自两个地方:一是光照反射产生的亮点,二是摄像头传感器本身的热噪声。
常用的去噪方法有:
- 高斯滤波: 适合去除高斯噪声,对边缘保留较好。我一般用5x5的核。
- 中值滤波: 对椒盐噪声(就是那种黑白点)效果奇好。我在超市货架项目里遇到过标签上有污渍的情况,中值滤波直接搞定。
- 双边滤波: 既能去噪又能保边,但速度慢。适合对质量要求极高的场景。
代码示例:
# 高斯滤波
img_gaussian = cv2.GaussianBlur(img_gray, (5, 5), 0)
# 中值滤波
img_median = cv2.medianBlur(img_gray, 5)
# 双边滤波
img_bilateral = cv2.bilateralFilter(img_gray, 9, 75, 75)
4.3 二值化:让文字「跳」出来
二值化的目标很简单——把图像变成纯黑和纯白。这样文字区域就是黑色,背景就是白色,OCR识别起来就轻松多了。
但这里有个坑:货架标签的光照往往不均匀。比如标签上半部分被灯光直射,下半部分在阴影里。如果用全局阈值,要么上半部分过曝,要么下半部分全黑。
怎么办?用自适应阈值。
# 全局阈值(不推荐用于光照不均场景)
_, img_binary = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)
# 自适应阈值(推荐)
img_adaptive = cv2.adaptiveThreshold(img_gray, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2)
参数说明:11是邻域大小,2是常数修正值。这两个值需要根据实际图像微调。我在项目中一般从11和2开始试,效果不行再调。
4.4 倾斜校正:把歪的标签「掰正」
货架标签很少是正对着摄像头拍的。要么是员工随手一拍,要么是标签贴歪了。倾斜角度一大,OCR识别率直线下降。
怎么做?核心思路是找到图像中的直线,计算角度,然后旋转回来。
具体步骤:
- 用Canny边缘检测提取边缘
- 用霍夫变换检测直线
- 计算直线的平均角度
- 用仿射变换旋转图像
代码示例:
import numpy as np
# 边缘检测
edges = cv2.Canny(img_binary, 50, 150, apertureSize=3)
# 霍夫变换检测直线
lines = cv2.HoughLines(edges, 1, np.pi/180, 200)
# 计算平均角度
angles = []
for rho, theta in lines[:, 0]:
angle = theta * 180 / np.pi - 90
angles.append(angle)
avg_angle = np.mean(angles)
# 旋转校正
(h, w) = img.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, avg_angle, 1.0)
img_rotated = cv2.warpAffine(img, M, (w, h),
flags=cv2.INTER_CUBIC,
borderMode=cv2.BORDER_REPLICATE)
4.5 透视变换:从「斜着拍」到「正着看」
倾斜校正只能处理旋转,但解决不了透视变形。什么叫透视变形?就是你从侧面拍一个标签,它看起来是梯形的,而不是矩形。这时候就需要透视变换。
透视变换的原理很简单:找到原始图像中四个角点,映射到目标矩形的四个角点。
步骤:
- 找到标签的四个角点(可以用轮廓检测)
- 定义目标矩形的四个角点
- 计算透视变换矩阵
- 应用变换
# 假设我们已经找到了四个角点 pts_src
# 目标矩形的四个角点
pts_dst = np.array([
[0, 0],
[width-1, 0],
[width-1, height-1],
[0, height-1]
], dtype='float32')
# 计算透视变换矩阵
M = cv2.getPerspectiveTransform(pts_src, pts_dst)
# 应用变换
img_warped = cv2.warpPerspective(img, M, (width, height))
4.6 预处理流程总结
好了,五个步骤都讲完了。在实际项目中,我一般按这个流程走:
| 步骤 | 操作 | 目的 |
|---|---|---|
| 1 | 灰度化 | 去除颜色冗余,降低计算量 |
| 2 | 去噪 | 消除光照反射和传感器噪声 |
| 3 | 二值化 | 分离文字和背景 |
| 4 | 倾斜校正 | 纠正旋转角度 |
| 5 | 透视变换 | 纠正透视变形 |
当然,这个顺序不是死的。比如有些场景下,先做透视变换再做二值化效果更好。我建议你们多试几种组合,找到最适合自己数据的那一套。
下一章,咱们聊聊特征提取和匹配。到时候你会看到,预处理做得好,后面的工作有多轻松。