前言
图像几何变换是计算机视觉和图像处理中的基础能力之一,涵盖了图像的平移、旋转、缩放、剪切、仿射等操作。在 OpenCV 中,只需简单几行代码即可实现强大的几何变换,但很多同学对变换的底层矩阵原理、各类操作的适用场景、参数设置与常见“掉坑点”并不熟悉。本文将理论公式推导与 Python+OpenCV 代码实战相结合,全面梳理几何变换的知识体系。无论你是初学者还是进阶开发者,都能在这里找到常用速查与疑难解答!
1. NumPy 与 OpenCV 加法的区别
在图像处理时,经常会遇到NumPy 加法和OpenCV 加法的区别:
- 两个 uint8 NumPy 数组相加会模 256 回绕。不能把这一点推广到浮点数或所有混合类型运算。
- uint8 输出的
cv2.add是饱和加法,超出 255 固定为 255;输出类型不同,范围与行为也不同。
import numpy as np
import cv2
x = np.array([[250]], dtype=np.uint8)
y = np.array([[10]], dtype=np.uint8)
print('NumPy加法:', x + y) # [4]
print('OpenCV加法:', cv2.add(x, y)) # [255]
结论:图像加法建议用 cv2.add,防止溢出导致颜色异常。
2. 图像灰度化最大值法案例
常见的灰度化有加权法、最大值法和平均值法。最大值法对彩色图像每个像素取 max(R, G, B)。
import cv2
import numpy as np
img = cv2.imread('test.jpg')
if img is None:
raise FileNotFoundError('test.jpg')
gray = np.max(img, axis=2).astype(np.uint8)
cv2.imshow('Max Gray', gray)
cv2.waitKey(0)
cv2.destroyAllWindows()
用途:最大值法能让亮度感知更强,适合部分特殊场景。
3. 常见二维变换矩阵推导与公式
这里讨论的仿射变换由线性部分和平移组成;平移在二维坐标中不是线性变换,采用齐次坐标后才能统一写成矩阵乘法。
3.1 平移(Translation)
将点 平移 :
3.2 缩放(Scaling)
相对于原点缩放:
齐次坐标:
3.3 旋转(Rotation)
在 x 向右、y 向上的数学坐标中,围绕原点逆时针旋转角度 :
齐次坐标:
3.4 剪切(Shear)
x方向剪切系数为 ,y方向剪切系数为 :
齐次坐标:
3.5 综合仿射变换(Affine Transformation)
仿射变换可用6参数矩阵:
或
4. 各类变换矩阵对比速查表
| 变换类型 | 2x2部分 | 平移 | 齐次坐标3x3矩阵 |
|---|---|---|---|
| 平移 | 单位阵 | ||
| 缩放 | 0 | ||
| 旋转 | 0 | ||
| 剪切 | 0 | ||
| 仿射 |
5. OpenCV 仿射变换实战与常见问题解析
先约定坐标再检查矩阵:图像数组按行、列访问,几何点常写作 x、y;图像 y 轴通常向下。变换后图像被裁掉,也可能只是输出画布范围不足,而不是旋转矩阵算错。先用几个已知角点检查,再处理整幅图。
5.1 warpAffine 的参数
-
warpAffine 函数用于仿射变换,调用方式:
dst = cv2.warpAffine(src, M, dsize, flags=..., borderMode=..., borderValue=...)src: 输入图像M: 2x3 仿射变换矩阵dsize: 输出图像大小 (width, height)flags: 插值方法(如cv2.INTER_LINEAR)borderMode: 边界填充方式
默认将 M 理解为源点到目标点的映射,内部逆映射采样;设置 WARP_INVERSE_MAP 时,传入的 M 才直接表示目标到源的映射。点变换方向与像素采样方向不要混淆。OpenCV 几何变换文档
5.2 如何定义仿射矩阵 $M$?
-
仿射变换矩阵 可以通过三组对应点求解;源点必须不共线,若要得到可逆变换,目标点也不能共线:
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) -
或者根据具体变换手动写矩阵。
5.3 综合仿射变换实例
以绕图像中心旋转为例(矩阵中的平移项用于保持旋转中心不动):
import cv2
import numpy as np
img = cv2.imread('test.jpg')
if img is None:
raise FileNotFoundError('test.jpg')
rows, cols = img.shape[:2]
M = cv2.getRotationMatrix2D((cols/2, rows/2), 30, 1) # 以中心点旋转30度,不缩放
dst = cv2.warpAffine(img, M, (cols, rows))
cv2.imshow('rotated', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
6. 旋转与角度正负方向
- OpenCV 中,正角度代表逆时针旋转,负角度为顺时针旋转。这与数学中的单位圆、三角函数一致。
- Pillow 的
Image.rotate同样以逆时针为正,不能作为相反约定的例子。Pillow rotate 文档 - 图像坐标 y 向下。上面的数学矩阵不能原样套入图像后仍声称视觉方向相同;
getRotationMatrix2D已采用图像约定。

本轮脚本断言平移 (12, 8) 后的重叠区域与原数组一致,并验证点 (150, 50) 绕 (100, 50) 正向旋转 90° 得到 (100, 0)。图中另外展示 30° 旋转;黑色区域来自默认边界填充,不是像素丢失异常。运行记录
7. 插值方法小结
常见插值方式:
- 最近邻插值(cv2.INTER_NEAREST):速度快,马赛克感重
- 双线性插值(cv2.INTER_LINEAR):平滑常用,默认选项
- 双三次插值(cv2.INTER_CUBIC):通常更平滑,但可能振铃,不保证每种输入都更好
- Lanczos 插值(cv2.INTER_LANCZOS4):高精度场景
插值估算新采样位置的像素值,不会凭空恢复缺失的真实细节。类别掩膜应避免插入新类别值。
8. 常见问题答疑总结
8.1 仿射变换与旋转矩阵的区别
- 旋转矩阵 是仿射矩阵的特例,仅做旋转且无缩放、剪切、平移。
- 仿射变换 可以实现旋转、缩放、剪切、平移等线性组合,包含6个自由度。
8.2 warpAffine 命名含义
- “warp”意为变形/映射,“Affine”指仿射(Affine Transformation)
- 合起来即“仿射变换映射”
8.3 为什么逆时针为正、顺时针为负?
- 答案:与数学单位圆、三角函数的正方向定义一致。角度正为逆时针,负为顺时针。
8.4 剪切(Shear)怎么实现?
剪切变换矩阵为:
OpenCV无直接API,但可手动写矩阵:
import cv2
import numpy as np
img = cv2.imread('test.jpg')
if img is None:
raise FileNotFoundError('test.jpg')
rows, cols = img.shape[:2]
k = 0.3 # 剪切系数
shift_x = max(0.0, -k * (rows - 1)) # k 为负时把左侧内容移回画布
M = np.float32([[1, k, shift_x], [0, 1, 0]])
out_width = int(np.ceil(cols + abs(k) * (rows - 1)))
sheared = cv2.warpAffine(img, M, (out_width, rows))
cv2.imshow('sheared', sheared)
cv2.waitKey(0)
cv2.destroyAllWindows()
9. 旋转与仿射变换的矩阵推导
9.1 二维旋转矩阵的方阵推导
以原点为中心旋转 :
- 若绕任意点 旋转,需先平移至原点,再旋转,再平移回去。
9.2 仿射变换的齐次坐标表示
二维坐标扩展为齐次坐标(添加一维1),则任意仿射变换写成:
这便于连续组合多个变换,如“旋转+缩放+平移”可一次乘法完成。
结语
本文系统介绍了 Python + OpenCV 图像几何变换的理论与实战技巧。几何变换贯穿于所有视觉任务和 AI 场景,也是面试和工程实现的基础能力。欢迎收藏、评论、补充,如有疑问可留言交流!
参考资料
- OpenCV 官方文档: https://docs.opencv.org
- 《数字图像处理(冈萨雷斯)》
- 《计算机视觉:算法与应用》
💬 评论
这套方案在哪一步卡住了?也欢迎补充你的实现方式或踩坑经验。