3、图像预处理(一):图像缩放、裁剪、旋转、仿射变换、透视变换
各位同学,欢迎来到图像预处理的第一讲。
说实话,在车牌识别这个领域摸爬滚打这么多年,我最大的体会就是:预处理做得好,识别成功一半。你想想看,摄像头拍回来的车牌,角度歪的、光线暗的、尺寸不对的,啥情况都有。如果不先把这些「毛刺」处理掉,后面的字符分割和识别根本没法搞。
今天这一章,咱们就把图像预处理中最基础、也最常用的几个操作讲透:缩放、裁剪、旋转、仿射变换、透视变换。这些都是 OpenCV 里的基本功,但也是车牌识别流水线上不可或缺的环节。
3.1 图像缩放:让车牌尺寸统一
车牌识别的第一步,往往是把图像缩放到一个固定尺寸。为什么?因为后续的算法(比如模板匹配、深度学习模型)通常要求输入尺寸一致。
OpenCV 里用 cv2.resize() 搞定。我个人习惯用 INTER_LINEAR 做放大,INTER_AREA 做缩小,效果比较稳。
import cv2
img = cv2.imread('car.jpg')
# 缩放到 640x480
resized = cv2.resize(img, (640, 480))
# 按比例缩放,宽度设为 400,高度自动计算
h, w = img.shape[:2]
ratio = 400 / w
new_h = int(h * ratio)
resized_ratio = cv2.resize(img, (400, new_h))
3.2 图像裁剪:只取感兴趣的区域
裁剪说白了就是「切图」。在车牌识别里,我们经常先检测出车牌的大概位置,然后把这个区域裁出来,再做后续处理。
OpenCV 里裁剪特别简单——直接用 NumPy 的切片操作就行。
# 假设我们检测到车牌区域在 (x, y, w, h)
x, y, w, h = 100, 200, 300, 100
plate_roi = img[y:y+h, x:x+w]
cv2.imshow('Plate ROI', plate_roi)
嗯,这里要注意:切片顺序是 [行, 列],也就是先 y 后 x。我刚开始学的时候老搞反,结果裁出来的图都是歪的。
img[x:x+w, y:y+h],结果调试了半天才发现坐标轴搞反了。记住:OpenCV 里图像是 NumPy 数组,shape 是 (height, width, channels)。
3.3 图像旋转:纠正倾斜的车牌
实际拍摄的车牌,很少有完全水平的。尤其是路边停车抓拍,角度往往偏个 5 到 10 度。这时候就需要旋转校正。
OpenCV 的旋转需要两步走:先算旋转矩阵,再做仿射变换。
def rotate_image(img, angle):
h, w = img.shape[:2]
center = (w // 2, h // 2)
# 计算旋转矩阵
M = cv2.getRotationMatrix2D(center, angle, 1.0)
# 执行旋转
rotated = cv2.warpAffine(img, M, (w, h))
return rotated
# 顺时针旋转 5 度
corrected = rotate_image(plate_img, -5)
你可能会问:为什么角度是负的?因为 OpenCV 里正角度是逆时针旋转。要顺时针校正,就得传负值。
warpAffine 里把输出尺寸设大一些,或者用 cv2.BORDER_REPLICATE 填充边缘。
3.4 仿射变换:更灵活的几何变换
旋转其实是仿射变换的一种特例。仿射变换可以完成:平移、旋转、缩放、错切。它保证变换后直线还是直线,平行线还是平行线。
在车牌识别里,仿射变换常用于校正轻微透视变形。比如车牌左右高度不一致,可以用仿射变换拉平。
# 原图中的三个点
src_pts = np.float32([[50, 50], [200, 50], [50, 200]])
# 目标图中的三个点
dst_pts = np.float32([[10, 100], [200, 50], [100, 250]])
# 计算仿射变换矩阵
M = cv2.getAffineTransform(src_pts, dst_pts)
# 应用变换
affined = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))
说白了,仿射变换就是「三点定乾坤」。你只需要告诉 OpenCV:原图上三个点,要变到目标图的哪三个位置,剩下的它帮你算。
3.5 透视变换:搞定大角度拍摄
仿射变换搞不定的是「近大远小」的透视效果。比如你从侧面拍一辆车,车牌看起来是梯形的。这时候就得用透视变换。
透视变换需要 4 个点,而且这 4 个点不能共线。它可以把任意四边形映射成矩形。
# 原图中的四个角点(比如车牌四个角)
src_pts = np.float32([
[141, 131], [480, 159],
[493, 630], [64, 601]
])
# 目标图中的四个角点(标准矩形)
dst_pts = np.float32([
[0, 0], [300, 0],
[300, 100], [0, 100]
])
# 计算透视变换矩阵
M = cv2.getPerspectiveTransform(src_pts, dst_pts)
# 应用变换
warped = cv2.warpPerspective(img, M, (300, 100))
你看,代码跟仿射变换很像,只是函数名从 getAffineTransform 变成了 getPerspectiveTransform,而且需要 4 个点。
3.6 实战:车牌校正流水线
好了,理论讲完了。咱们来串一个完整的车牌校正流程。这是我实际项目中用到的套路。
def correct_plate(img, corners):
"""
img: 原始图像
corners: 车牌四个角点,顺序为左上、右上、右下、左下
"""
# 1. 计算目标矩形的宽高
width = 300
height = 100
# 2. 定义目标点
dst_pts = np.float32([
[0, 0],
[width, 0],
[width, height],
[0, height]
])
# 3. 透视变换
M = cv2.getPerspectiveTransform(corners, dst_pts)
warped = cv2.warpPerspective(img, M, (width, height))
# 4. 灰度化 + 二值化(为后续字符分割做准备)
gray = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
return warped, binary
这个流程我用了好几年,效果很稳定。核心思路就是:先校正几何变形,再处理图像内容。顺序不能乱。
3.7 本章小结
今天咱们聊了五个基础操作:
- 缩放:统一尺寸,注意插值方法的选择
- 裁剪:提取 ROI,注意坐标顺序
- 旋转:校正倾斜,注意角度正负
- 仿射变换:三点变换,保持平行性
- 透视变换:四点变换,校正透视变形
说实话,这些操作单独看都不难。但组合起来,就能解决车牌识别中 80% 的几何变形问题。下一章咱们会继续聊图像预处理的其他内容——滤波、边缘检测、二值化。到时候你会看到,这些基础操作是怎么一步步把一张「脏乱差」的车牌照片,变成干净清晰的识别素材的。
好,今天就到这儿。动手试试吧,把代码跑起来,你会有更深的体会。