2.2 图像几何变换


2.2 图像几何变换

本节摘要:几何变换改变像素的位置而非颜色,解决"摆正、对齐、矫正"三类问题。本节从平移的最小例子出发,讲透 warpAffine 与 2×3 变换矩阵、rotate 与 flip 的快捷方式、getAffineTransform 与 getPerspectiveTransform 的三对点/四对点标定法,以及仿射与透视的分界线:平行线是否保持平行。读完你能校正倾斜的文档照、把任意四边形"拉平"成正视图。

从项目需求看本节目标

阅读完本节,你应当能够:

  1. 手写平移矩阵并用 warpAffine 执行平移
  2. 解释仿射变换保持平行、透视变换不保持平行的本质差异
  3. 用 getRotationMatrix2D 实现任意角度旋转并理解其中的缩放分量
  4. 用三对点标仿射、四对点标透视,完成文档矫正类任务
  5. 说清 INTER_LINEAR 与 BORDER_REFLECT 等参数对输出边缘的影响

先跑起来:平移、旋转、透视三个最小例子

平移。把图像向右移 50 像素、向下移 30 像素:

import cv2 import numpy as np img = cv2.imread('example.jpg') h, w = img.shape[:2] M = np.float32([[1, 0, 50], [0, 1, 30]]) # 2x3 平移矩阵 shifted = cv2.warpAffine(img, M, (w, h))

矩阵怎么读?[1, 0, 50] 的意思是:输出 x = 1×x + 0×y + 50——每个像素的横坐标加 50。第二行同理处理 y。变换矩阵就是一张"输出坐标从输入坐标怎么算"的公式表。

旋转。绕画面中心转 45 度,不缩放:

center = (w // 2, h // 2) M = cv2.getRotationMatrix2D(center, 45, 1.0) # 中心、角度、缩放 rotated = cv2.warpAffine(img, M, (w, h))

透视矫正。拍歪的屏幕或文档,找到四个角点,把它"拉平":

src_pts = np.float32([[58, 92], [412, 60], [405, 350], [62, 388]]) # 原图四角 dst_pts = np.float32([[0, 0], [360, 0], [360, 420], [0, 420]]) # 目标矩形 M = cv2.getPerspectiveTransform(src_pts, dst_pts) warped = cv2.warpPerspective(img, M, (360, 420))

三段代码,一个共同结构:构造变换矩阵 → warp 系列函数执行。几何变换的全部工作就是"矩阵从哪来"。

核心原理:从刚体到投影的四级台阶

2.1 变换的层级

几何变换可以按"自由度"排成一列台阶,每上一级能做的事更多、约束更少:

层级 变换 保持什么 自由度 典型用途
一级 平移 距离、角度、平行 2 目标居中
二级 欧氏(平移+旋转) 距离、角度 3 图像摆正
三级 仿射 平行关系、直线 6 倾斜校正、剪切
四级 透视 仅直线仍是直线 8 文档/车牌矫正

平移加旋转再加各向异性的缩放与剪切,合起来是仿射变换,统一用 2×3 矩阵表示。它的核心性质是平行线变换后仍然平行——矩形会变成平行四边形,但不会变成梯形。透视变换用 3×3 矩阵(齐次坐标),允许平行线相交于消失点,正好对应"近大远小"的成像规律。手机斜拍一张 A4 纸,纸的四条边在照片里往往两两不平行(远端看起来更窄),这不是仿射能描述的形变,必须透视变换出马。

2.2 逆向映射:为什么变换是"反向算"的

一个容易被忽略的实现细节:warpAffine 并不是把输入像素"推"到输出位置,而是对输出图的每个像素,用矩阵的逆变换"回查"它该来自输入的哪里。为什么?正向推会把多个输入像素挤到同一个输出格,另一些输出格却空着,出现麻点。逆向映射保证输出图的每个像素都有确定来源;来源坐标若落在非整数位置,就用插值(默认 INTER_LINEAR)从邻域估算。

这也解释了平移后"露出的黑边":输出像素回查到了输入图像之外,没有来源,只能填边界填充值。第五个参数 borderValue 控制这个填充色,默认黑色;处理白底文档时传 255 填白色,观感好得多。

2.3 旋转矩阵里的缩放分量

getRotationMatrix2D 的第三个参数是缩放系数,设 1.0 表示不缩放。一个实用技巧:旋转 45 度时矩形的对角线会比边长更长,原图尺寸装不下旋转后的内容,四角被裁。解决方法是先算出旋转后的包围盒尺寸、把平移分量加大,或者简单粗暴地在旋转前把画布扩大一圈。做文档矫正时常用前者,做特效展示时后者即可。

2.4 标定:从对应点反推矩阵

大多数时候你不会手写矩阵,而是给"点对"让 OpenCV 反算。规则简单:仿射三对点,透视四对点。点从哪来?交互式用鼠标点击取坐标(用 mouse 回调写十行小程序),或用角点检测自动找(第 3 章)。标定的精度直接决定矫正质量——点选偏两三个像素,矫正结果就能看出歪斜。

工程实践要点

3.1 插值与边界填充的选择

warp 系列的插值参数沿用第 1 章的原则:缩小用 INTER_AREA,常规用 INTER_LINEAR,高质量放大用 INTER_CUBIC。边界填充推荐试一下 BORDER_REFLECT(反射填充,边缘内容镜像延伸),照片类处理比黑边自然;文档矫正则常用纯色填充配合 borderValue。

3.2 旋转的一行替代品

cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE) 能做 90 度的三个方向旋转,它内部是内存转置级别的操作,比 warpAffine 快且零精度损失。相机偶发 90 度方向错误、手机竖拍横存这类问题,用它一步到位。任意角度仍走 getRotationMatrix2D。

3.3 矫正实战的坑:点序必须一致

getPerspectiveTransform 的两组点必须按同样顺序排列(比如都按左上、右上、右下、左下顺时针)。一组顺时针一组乱序,矩阵就废了,输出会像被拧麻花。团队协作时把点序写进注释,是廉价的保险。另一个坑:目标尺寸写错导致输出图比例失真,A4 纸是 210×297,矫正成 297×210 之外的比例,人眼立刻看出"被拉伸"。

⚠️ 常见坑:对索引坐标与几何坐标混淆。选点时鼠标回调给的是 (x, y),写进点对前确认单位与顺序一致;混用行列会让矫正矩阵"看起来对但就是歪"。调试时可先把点画到图上确认位置:circle 一圈再 imshow。

3.4 动手实验清单

  1. 手写矩阵实现水平镜像(提示:x 分量取负加平移),与 flip 结果对比。
  2. 找一张斜拍的文档照,交互取四角点做透视矫正,观察文字是否变得横平竖直。
  3. 把旋转角度从 5 度调到 170 度,观察四角裁切变化,再实现"扩大画布"的改进版。
  4. 用三对点做仿射,故意把一对点错位 10 像素,观察矫正输出的偏移量——体会标定敏感度。

2.5 从坐标看三类变换的身位

把一张网格图分别做旋转、仿射、透视,能直观看到三者的"形变能力"梯度。旋转后的网格仍是正交网格,只是整体转了个方向;仿射后的网格变成平行四边形网格——纵横线仍各自平行,但夹角变了;透视后的网格近处格大、远处格小,平行线在远处汇于一点。理解这个梯度的价值在于反向诊断:看到矫正不理想的结果图,先判断残留的形变属于哪一级(该用透视却只用了仿射,是文档矫正最常见的错误),再决定升级变换还是修点。

工程里还有一个高频细节:变换后图像的有效区域。平移 50 像素后,原图右侧 50 像素宽的内容会被推出画布,输出画布尺寸仍是原图尺寸时这部分直接丢失。处理"不许丢内容"的场合(印章提取、票据拼接),输出尺寸要按变换后的包围盒显式计算,再在矩阵里补偿平移量,让目标落进新画布中央。

3.5 常见疑问与解答

旋转后图像被裁角怎么办?

两个方案。简单方案:旋转前把画布扩成"对角线长度"的正方形,中心对齐后再转,四角信息全保留;精细方案:用原尺寸与旋转角推算新的画布尺寸并修正矩阵里的平移分量,只补足需要的部分。文档矫正类任务用后者(避免无谓的白边),展示类任务用前者省事。

多次几何变换可以合并成一次吗?

可以且推荐。仿射矩阵乘法封闭——先旋转再平移再缩放的三次 warp,等价于三个矩阵相乘后的一次 warp。每次 warp 都有插值损失,多次变换会累积模糊,合并成一次既快又保真。写法就是把矩阵按顺序相乘,最后调用一次 warpAffine。

透视矫正后目标比例失真怎么检查?

用已知尺寸的参照物验证:A4 纸矫正后宽高比应接近 210 比 297。更通用的方法是矫正前后量目标上的同一段直线长度,比例异常说明四角点选偏或目标尺寸参数写错。测量类应用(零件尺寸检测)这个检查必做,比例错了后面全是白算。

仿射与透视的参数能互相转换吗?

仿射是透视的特例(3×3 矩阵最后一行固定为 0、0、1)。用 getPerspectiveTransform 标三对点会退化,用四对点标一个纯仿射形变,解出的矩阵最后一行自然回到仿射形态。工程上不必刻意转换,按"平行是否保持"选路线即可。

3.6 实战配方:文档矫正的标准作业流程

把本节内容组装成一个可复用的作业流程,以"手机拍的 A4 文档矫正"为例。

第一步取点。交互式取四角(鼠标回调记四组坐标)最稳;自动化路线用纸页检测(灰度化、阈值、找最大轮廓、逼近四边形顶点)适合批量处理。四点按固定顺序排好(左上、右上、右下、左下),本节 3.3 的点序铁律在此兑现。

第二步定目标尺寸。按 A4 的真实比例设定输出(比如 595 乘 842 的比例值),比例失真的话字会被拉伸,人眼对文字的拉伸极其敏感。

第三步算矩阵并矫正。getPerspectiveTransform 加 warpPerspective,插值用 INTER_CUBIC(文档要清晰、计算一次不差这点时间),边界填白色(borderValue 传 255)贴近纸面观感。

第四步质检。看四边是否笔直(残存曲率说明取点偏了)、文字行是否水平(残留旋转说明仿射成分没拉正)、对比抽查两处尺寸比例。批量化时把质检抽样比例定在十分之一左右,抽到不合格回溯该张的取点。

这套流程换到车牌、屏幕、包装盒的矫正,骨架完全不变——几何变换这节的实战价值就在于此:一次学会,处处复用。

本节要点回顾

  • 变换分级:平移 → 欧氏 → 仿射 → 透视,自由度递增、约束递减;平行线的命运是仿射与透视的分界线。
  • 统一套路:先搞到矩阵(手写、点对标定或构造函数),再交给 warpAffine 或 warpPerspective 执行。
  • 逆向映射:输出像素回查来源加插值,保证结果无空洞;边界外的黑边由 borderValue 或填充模式控制。
  • 点对标定:仿射三对、透视四对,点序两端一致是铁律,选点精度决定矫正精度。
  • 快捷路径:90 度旋转用 cv2.rotate,镜像用 flip,别动用矩阵大炮打蚊子。
  • 参数习惯:插值按缩放方向选,边界填充按内容类型选,文档类矫正优先白底填充。

常见追问:旋转后图被裁掉一角怎么办

getRotationMatrix2D 默认绕图像中心旋转且保持画布尺寸不变,旋转角度一大,四角内容就会转出画布被裁掉。工程上的做法是先算出旋转后外接矩形的宽高,再把平移分量拼进矩阵,让画布主动扩出去:先用 abs 值算出新的宽高,再在 M 的第三列加上新画布中心与原中心的偏移。矫正文档、摆正倾斜的牌照照片时,这一步几乎是必做的。

另一个高频疑问是"为什么我的旋转方向和想象相反"。OpenCV 的坐标系 y 轴向下,角度正方向是逆时针,而屏幕直觉常常相反;调不准时先拿 90 度这种已知答案的角度试一次,确认方向感再碰任意角度。同理,warpAffine 的 dsize 决定输出画布大小,与输入无关——把 dsize 设大一点再配合平移,就能实现"先腾地方再转"的宽容策略。

位置摆正了,接下来处理形状层面的毛病:小白点、断裂、空洞。下一节的形态学操作专治二值图上的这些"形状缺陷"。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U