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 我在项目中的实战经验

我曾经做过一个手机中框尺寸测量项目。中框在夹具上的位置每次都有微小偏移,直接测量误差很大。我的解决方案是:

  1. 在模板图像上标记三个基准圆心的位置(pts1)
  2. 在实际图像中检测这三个圆心(pts2)
  3. 用 getAffineTransform 计算变换矩阵
  4. 将实际图像仿射变换到模板坐标系

这样一来,所有测量都在统一坐标系下进行,精度从 ±0.3mm 提升到了 ±0.05mm。

核心公式: 仿射变换矩阵 M = [[a, b, tx], [c, d, ty]]。其中 a,b,c,d 控制旋转和缩放,tx,ty 控制平移。你只需要 3 组对应点就能解出这 6 个参数。

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))
我的经验: 透视变换对点位的精度非常敏感。我曾经因为一个角点偏差了 2 个像素,导致校正后的图像边缘出现明显的锯齿。建议使用亚像素级别的角点检测(如 cv2.cornerSubPix)来获取更精确的对应点。

4.5 知识体系总览

下面这张图总结了本章的核心知识结构。你可以把它当作一个快速索引,遇到几何变换问题时,先定位到对应的分支。

图像几何变换知识体系 几何变换 基础变换(2×3矩阵) 缩放 (resize) 平移 (warpAffine) 旋转 (getRotationMatrix2D) 高级变换(3×3矩阵) 仿射变换 (getAffineTransform) 透视变换 (getPerspectiveTransform) 核心原则 仿射变换保持平行线平行(3点求解) | 透视变换允许近大远小(4点求解) 工业视觉典型应用场景 缩放:多分辨率检测金字塔 | 旋转:工件角度校正 | 仿射:坐标系对齐 | 透视:倾斜拍摄扶正 插值选择:缩小用 INTER_AREA,放大用 INTER_CUBIC,实时用 INTER_LINEAR

4.6 插值算法:像素「搬家」后的颜色填充

无论哪种几何变换,最终都会面临一个问题:变换后的像素坐标往往是小数,但图像只能存储整数坐标。这时候就需要插值算法来「猜」出这个位置的颜色。

OpenCV 提供了多种插值方法,我按实际效果排个序:

插值方法 速度 质量 推荐场景
INTER_NEAREST 最快 最差(锯齿严重) 实时预览、像素艺术
INTER_LINEAR 快 中等 通用场景、实时处理
INTER_CUBIC 慢 好(平滑) 高质量放大
INTER_LANCZOS4 最慢 最好(保留边缘) 离线高精度处理
INTER_AREA 中等 缩小场景最佳 图像缩小(降采样)
性能警告: 在工业实时检测中(比如 60fps 的产线),千万不要用 INTER_LANCZOS4。我曾经在一个项目中用了它,结果处理一帧图像花了 80ms,直接导致产线停摆。后来换成 INTER_LINEAR,速度提升到 5ms,效果完全满足需求。

4.7 本章小结

几何变换是工业视觉的基石。你不需要记住每个矩阵的数学推导,但一定要清楚:

  • 什么时候用仿射? 当物体是平面且相机视角固定时,比如工件在传送带上的旋转校正。
  • 什么时候用透视? 当相机从侧面拍摄,或者需要校正倾斜视角时,比如读倾斜的二维码。
  • 插值怎么选? 缩小用 INTER_AREA,放大用 INTER_CUBIC,实时用 INTER_LINEAR。