第四章:图像几何变换——让图像动起来
各位同学,欢迎来到第四章。这一章我们聊聊图像的几何变换。
说白了,几何变换就是让图像在空间里「动一动」——放大缩小、平移、旋转、甚至扭曲。听起来简单,但这里面的门道可不少。我刚开始做图像处理时,以为缩放就是resize一下完事,结果在项目中吃了大亏。嗯,咱们今天就把这些坑一个个填上。
4.1 图像缩放——不只是变大变小
图像缩放,最基础的操作。OpenCV里用 cv2.resize() 搞定。
import cv2
import numpy as np
img = cv2.imread('lena.jpg')
# 方法一:指定目标尺寸
resized = cv2.resize(img, (300, 300))
# 方法二:指定缩放因子
resized_by_factor = cv2.resize(img, None, fx=0.5, fy=0.5)
# 方法三:指定插值方式
resized_interp = cv2.resize(img, (300, 300), interpolation=cv2.INTER_CUBIC)
这里有个关键点——插值方式。你想想看,图像缩小的时候,像素变少了,怎么决定哪些像素保留?图像放大的时候,像素变多了,多出来的像素怎么填?
| 插值方法 | 适用场景 | 特点 |
|---|---|---|
| INTER_NEAREST | 快速预览 | 速度最快,但锯齿明显 |
| INTER_LINEAR | 默认选项,通用场景 | 速度与质量平衡 |
| INTER_CUBIC | 放大图像 | 质量好,但计算量大 |
| INTER_LANCZOS4 | 高质量缩放 | 效果最好,速度最慢 |
4.2 图像平移——让图像「走两步」
平移其实就是一个矩阵乘法的事。OpenCV里用仿射变换来实现。
import cv2
import numpy as np
img = cv2.imread('lena.jpg')
rows, cols = img.shape[:2]
# 定义平移矩阵:向右平移100像素,向下平移50像素
M = np.float32([[1, 0, 100],
[0, 1, 50]])
# 执行平移
translated = cv2.warpAffine(img, M, (cols, rows))
这里要注意,warpAffine 的第三个参数是输出图像的尺寸。如果平移后图像超出了边界,超出的部分会被裁掉。我曾经在做一个拼接项目时,平移后图像边缘信息丢失了,找了半天bug才发现是尺寸没设对。
(cols + 100, rows + 50),这样平移后的图像就能完整显示。
4.3 图像旋转——绕哪个点转?
旋转也是仿射变换的一种。OpenCV提供了 cv2.getRotationMatrix2D() 来生成旋转矩阵。
import cv2
import numpy as np
img = cv2.imread('lena.jpg')
rows, cols = img.shape[:2]
# 绕图像中心旋转45度,缩放因子为1
center = (cols // 2, rows // 2)
M = cv2.getRotationMatrix2D(center, 45, 1.0)
# 执行旋转
rotated = cv2.warpAffine(img, M, (cols, rows))
为什么需要指定旋转中心?你想想看,绕左上角旋转和绕中心旋转,结果完全不一样。我刚开始学的时候,默认绕(0,0)点旋转,结果图像直接飞出去了,画面一片黑。嗯,这个坑我替你们踩过了。
cv2.getRotationMatrix2D 返回的矩阵可以配合 cv2.warpAffine 的边界填充模式来处理。
4.4 仿射变换——三个点定天下
仿射变换的本质是:直线还是直线,平行线还是平行线。它由三个点唯一确定。
import cv2
import numpy as np
img = cv2.imread('lena.jpg')
rows, cols = img.shape[:2]
# 原图中的三个点
pts1 = np.float32([[50, 50],
[200, 50],
[50, 200]])
# 目标图中的三个点
pts2 = np.float32([[10, 100],
[200, 50],
[100, 250]])
# 计算仿射变换矩阵
M = cv2.getAffineTransform(pts1, pts2)
# 执行仿射变换
affined = cv2.warpAffine(img, M, (cols, rows))
为什么是三个点?因为仿射变换有6个自由度(2x3矩阵),三个点提供6个方程,刚好解出来。我在做文档矫正时,经常用仿射变换来校正倾斜的扫描件。选三个角点,映射到矩形位置,搞定。
4.5 透视变换——四个点改变世界
透视变换比仿射变换更强大。它允许直线变成直线,但平行线不一定保持平行。说白了,就是模拟人眼从不同角度观察的效果。
import cv2
import numpy as np
img = cv2.imread('card.jpg')
rows, cols = img.shape[:2]
# 原图中的四个点(比如一张名片四个角)
pts1 = np.float32([[56, 65],
[368, 52],
[28, 387],
[389, 390]])
# 目标图中的四个点(矫正后的矩形)
pts2 = np.float32([[0, 0],
[300, 0],
[0, 400],
[300, 400]])
# 计算透视变换矩阵
M = cv2.getPerspectiveTransform(pts1, pts2)
# 执行透视变换
warped = cv2.warpPerspective(img, M, (300, 400))
透视变换需要4个点,因为它的自由度是8(3x3矩阵,最后一个元素固定为1)。四个点提供8个方程,刚好求解。
4.6 实战:综合应用——文档矫正
咱们来一个完整的例子。假设你拍了一张倾斜的文档照片,怎么把它矫正成正面视图?
import cv2
import numpy as np
def correct_document(image_path):
img = cv2.imread(image_path)
orig = img.copy()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 边缘检测
edges = cv2.Canny(gray, 50, 150)
# 找轮廓
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 找到最大的四边形轮廓
for c in sorted(contours, key=cv2.contourArea, reverse=True)[:5]:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
if len(approx) == 4:
doc_pts = approx.reshape(4, 2).astype(np.float32)
break
# 排序四个角点:左上、右上、右下、左下
doc_pts = order_points(doc_pts)
# 目标尺寸
width = 500
height = 700
dst_pts = np.float32([[0, 0],
[width, 0],
[width, height],
[0, height]])
# 透视变换
M = cv2.getPerspectiveTransform(doc_pts, dst_pts)
warped = cv2.warpPerspective(orig, M, (width, height))
return warped
def order_points(pts):
# 按左上、右上、右下、左下排序
rect = np.zeros((4, 2), dtype=np.float32)
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
diff = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
return rect
# 使用
result = correct_document('skewed_doc.jpg')
cv2.imwrite('corrected_doc.jpg', result)
这个例子把今天讲的内容串起来了:边缘检测找轮廓、轮廓近似找四边形、透视变换矫正。我在实际项目中,这个流程的成功率能达到90%以上。剩下的10%?嗯,光照太差或者文档背景太复杂的时候,需要加一些预处理。
4.7 总结与对比
| 变换类型 | 所需点数 | 变换矩阵 | 特性 | 典型应用 |
|---|---|---|---|---|
| 缩放 | 无 | 无(直接resize) | 改变尺寸 | 图像预处理 |
| 平移 | 无 | 2x3 | 保持形状 | 图像对齐 |
| 旋转 | 无 | 2x3 | 绕点旋转 | 图像校正 |
| 仿射变换 | 3个点 | 2x3 | 保持平行线 | 文档矫正、图像配准 |
| 透视变换 | 4个点 | 3x3 | 改变视角 | 文档扫描、车牌识别 |
好了,这一章的内容就到这里。几何变换是图像处理的基础,也是很多高级应用(比如图像拼接、SLAM)的基石。我个人建议,把今天这几个函数多练几遍,尤其是仿射变换和透视变换,面试时经常被问到。
下一章我们聊聊图像滤波,到时候见。