4. 图像几何变换
几何变换,说白了就是让图像换个姿势。在工业视觉里,这几乎是每个项目都要面对的基础操作。我刚开始做视觉算法时,总觉得几何变换就是调个参数,后来才发现,选错变换模型,轻则定位偏差,重则整个检测流程报废。
今天咱们就把这块彻底捋清楚。从最简单的缩放平移,到最常用的仿射变换,再到透视变换,我会把每个变换的数学本质、OpenCV 实现、以及我在产线上踩过的坑,一次性讲透。
4.1 缩放与平移:最基础的坐标操作
4.1.1 图像缩放
缩放是最直观的变换。但你知道吗?缩放不仅仅是改变尺寸,它背后涉及的是采样和插值策略。
我个人习惯把缩放分为两类:
- 缩小(降采样): 从原图中挑选或合并像素。用 INTER_AREA 效果最好,能有效避免摩尔纹。
- 放大(上采样): 需要「无中生有」地填充像素。用 INTER_CUBIC 或 INTER_LINEAR 更平滑。
我曾经在一个 PCB 缺陷检测项目里,为了省时间直接用默认的线性插值去缩小图像,结果细小的划痕被直接抹掉了。嗯,从那以后,我缩小图像必用 INTER_AREA。
import cv2
import numpy as np
img = cv2.imread('pcb_defect.jpg')
# 缩小:使用 INTER_AREA 保留细节
img_small = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA)
# 放大:使用 INTER_CUBIC 更平滑
img_large = cv2.resize(img, (1024, 1024), interpolation=cv2.INTER_CUBIC)
4.1.2 图像平移
平移就是让图像整体移动。在工业视觉里,平移常用于 ROI 的偏移校正。比如,当传送带上的工件位置有固定偏移量时,我们直接平移图像来对齐检测区域。
平移的变换矩阵很简单:
# 平移矩阵:[[1, 0, tx], [0, 1, ty]]
M_translate = np.float32([[1, 0, 50], [0, 1, 100]])
shifted = cv2.warpAffine(img, M_translate, (img.shape[1], img.shape[0]))
cv2.BORDER_REPLICATE 或 cv2.BORDER_REFLECT 来填充边界。
4.2 旋转:绕哪个点转,差别很大
旋转在工业视觉里太常见了。工件在传送带上角度不一致,需要先旋转校正再检测。但很多人忽略了一个关键点:旋转中心的选择。
OpenCV 提供了 cv2.getRotationMatrix2D 来生成旋转矩阵。它的三个参数分别是:旋转中心、角度、缩放比例。
h, w = img.shape[:2]
center = (w // 2, h // 2) # 绕图像中心旋转
M_rotate = cv2.getRotationMatrix2D(center, 45, 1.0) # 顺时针45度,不缩放
rotated = cv2.warpAffine(img, M_rotate, (w, h))
你想想看,如果绕左上角 (0,0) 旋转,图像会直接飞出画面。我见过有新手工程师直接默认绕原点旋转,结果调试了半天图像都是黑的。
new_w = w * |cosθ| + h * |sinθ|。
4.3 仿射变换:工业视觉的「瑞士军刀」
仿射变换是我在工业项目中使用频率最高的几何变换。它支持平移、旋转、缩放、错切(shear)的任意组合。说白了,仿射变换就是「保持平行线依然平行」的变换。
4.3.1 仿射变换的数学本质
仿射变换用 2×3 矩阵表示。你只需要知道原图中三个不共线的点,以及它们变换后的位置,OpenCV 就能帮你算出整个变换矩阵。
# 原图中的三个点
pts1 = np.float32([[50, 50], [200, 50], [50, 200]])
# 变换后的三个点
pts2 = np.float32([[10, 100], [200, 50], [100, 250]])
M_affine = cv2.getAffineTransform(pts1, pts2)
affined = cv2.warpAffine(img, M_affine, (img.shape[1], img.shape[0]))
4.3.2 我在项目中的实战经验
我曾经做过一个手机中框尺寸测量项目。中框在夹具上的位置每次都有微小偏移,直接测量误差很大。我的解决方案是:
- 在模板图像上标记三个基准圆心的位置(pts1)
- 在实际图像中检测这三个圆心(pts2)
- 用
getAffineTransform计算变换矩阵 - 将实际图像仿射变换到模板坐标系
这样一来,所有测量都在统一坐标系下进行,精度从 ±0.3mm 提升到了 ±0.05mm。
4.4 透视变换:从二维到三维的「欺骗」
透视变换比仿射变换更强大。它允许「近大远小」的效果,能把倾斜拍摄的物体「扶正」。在工业视觉里,透视变换常用于:
- 倾斜拍摄的二维码/条形码校正
- 从侧面拍摄的工件正面视图还原
- 多相机拼接时的视角统一
4.4.1 透视变换 vs 仿射变换
| 对比维度 | 仿射变换 | 透视变换 |
|---|---|---|
| 变换矩阵 | 2×3(6个自由度) | 3×3(8个自由度) |
| 平行线 | 保持平行 | 可能不再平行 |
| 所需对应点 | 3个点 | 4个点 |
| 典型应用 | 旋转校正、平移对齐 | 倾斜校正、视角转换 |
4.4.2 透视变换实战:扶正倾斜的工件
假设你从侧面拍到一个矩形工件,它看起来是个梯形。你需要把它校正为正面视角。
# 原图中工件的四个角点(梯形)
pts1 = np.float32([[150, 200], [450, 200], [100, 400], [500, 400]])
# 目标矩形的四个角点
pts2 = np.float32([[0, 0], [400, 0], [0, 300], [400, 300]])
M_perspective = cv2.getPerspectiveTransform(pts1, pts2)
warped = cv2.warpPerspective(img, M_perspective, (400, 300))
cv2.cornerSubPix)来获取更精确的对应点。
4.5 知识体系总览
下面这张图总结了本章的核心知识结构。你可以把它当作一个快速索引,遇到几何变换问题时,先定位到对应的分支。
4.6 插值算法:像素「搬家」后的颜色填充
无论哪种几何变换,最终都会面临一个问题:变换后的像素坐标往往是小数,但图像只能存储整数坐标。这时候就需要插值算法来「猜」出这个位置的颜色。
OpenCV 提供了多种插值方法,我按实际效果排个序:
| 插值方法 | 速度 | 质量 | 推荐场景 |
|---|---|---|---|
| INTER_NEAREST | 最快 | 最差(锯齿严重) | 实时预览、像素艺术 |
| INTER_LINEAR | 快 | 中等 | 通用场景、实时处理 |
| INTER_CUBIC | 慢 | 好(平滑) | 高质量放大 |
| INTER_LANCZOS4 | 最慢 | 最好(保留边缘) | 离线高精度处理 |
| INTER_AREA | 中等 | 缩小场景最佳 | 图像缩小(降采样) |
4.7 本章小结
几何变换是工业视觉的基石。你不需要记住每个矩阵的数学推导,但一定要清楚:
- 什么时候用仿射? 当物体是平面且相机视角固定时,比如工件在传送带上的旋转校正。
- 什么时候用透视? 当相机从侧面拍摄,或者需要校正倾斜视角时,比如读倾斜的二维码。
- 插值怎么选? 缩小用 INTER_AREA,放大用 INTER_CUBIC,实时用 INTER_LINEAR。