第六章 图像的几何变换与几何校正
1. 基本坐标变换
前言
一、图像平移
图像平移 就是将图像中所有的像素点按照指定的平移量水平或垂直移动。

两点之间存在如下关系:

以矩阵形式表示平移前后的像素关系为:

注意: 超出原图像的部分统一设置为0或255
二、镜像变换:照镜子游戏
**图像的镜像(Mirror)**是指原始图像相对于某一参照面旋转180°的图像
设原始图像的宽为w,高为h,原始图像中的点为(x0,y0), 对称变换后的点为(x1,y1).
1. 垂直镜像(相对于x轴)
2.水平镜像(相对于 y 轴)

实际应用
- 手机前置摄像头自动做的镜像处理
- 制作对称图案设计
- 医学影像的左右对比分析
三、图像旋转:旋转餐桌转盘
一般图像的旋转是以图像的中心为原点 ,旋转一定的角度,即将图像上的所有像素都旋转一个相同的角度。

设原始图像的任意点A(x,y)经旋转角度β以后到新的位置 A' (x',y') ,为表示方便,采用极坐标形式表示,原始的角度为 α,如下图所示:

原始图像的点A(x,y)的坐标变换如下:

若图像旋转角β=45时,则变换关系

关键现象
- 旋转后图像尺寸可能变化(正方形除外)
- 会出现新的空白区域(用黑色/透明填充)
- 斜边会出现锯齿(需要插值处理)
四、图像缩放:放大镜与望远镜
数字图像的全比例缩放是指将给定的图像在x方向和y方向按相同的 比例a 缩放,从而获得一幅新的图像,比例缩放前后两点A0(x0, y0)、A1(x1, y1)之间的关系用矩阵形式可以表示为

当a<1时,图像缩小,当a>1时,图像放大。当a=2时,图像放大一倍,当a=1/2时,图像缩小一半。


使用最近邻插值法,更多灰度差值方法后续会讲到
五、案例代码
import cv2
import numpy as np
import matplotlib.pyplot as plt
# --------------------------
# 1. 读取并预处理图像(转为RGB格式,便于matplotlib显示)
# --------------------------
img_path = './img/dragon.png' # 🔧 替换为你的图片路径(如本地图片或网络图片)
img = cv2.imread(img_path)
if img is None:
raise FileNotFoundError("图片路径无效!请替换为有效的图片路径(如 './test.jpg')")
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # OpenCV默认BGR,转为RGB显示
h, w = img_rgb.shape[:2] # 图像高度、宽度
# --------------------------
# 2. 图像平移(右移50像素,下移30像素)
# --------------------------
tx, ty = 50, 30 # 平移量:x方向(右为正)、y方向(下为正)
M_translate = np.float32([[1, 0, tx], [0, 1, ty]]) # 平移变换矩阵
translated = cv2.warpAffine(img_rgb, M_translate, (w, h)) # 执行平移,空白处默认填充黑色
# --------------------------
# 3. 图像镜像(垂直镜像 + 水平镜像)
# --------------------------
# 垂直镜像(左右翻转,相对于y轴)
mirrored_vertical = cv2.flip(img_rgb, 1) # 参数1:垂直镜像(左右翻转)
# 水平镜像(上下翻转,相对于x轴)
mirrored_horizontal = cv2.flip(img_rgb, 0) # 参数0:水平镜像(上下翻转)
# --------------------------
# 4. 图像旋转(以中心为原点,旋转45度)
# --------------------------
angle = 45 # 旋转角度(正数为逆时针)
center = (w // 2, h // 2) # 旋转中心(图像中心)
scale = 1.0 # 缩放比例(1.0表示不变)
M_rotate = cv2.getRotationMatrix2D(center, angle, scale) # 计算旋转矩阵
rotated = cv2.warpAffine(img_rgb, M_rotate, (w, h)) # 执行旋转,空白处默认填充黑色
# --------------------------
# 5. 图像缩放(放大2倍 & 缩小0.5倍,三种插值对比)
# --------------------------
scale_up = 2.0 # 放大倍数
scale_down = 0.5 # 缩小倍数
# --- 最近邻插值(速度快,锯齿明显)
scaled_nearest_up = cv2.resize(img_rgb, None, fx=scale_up, fy=scale_up, interpolation=cv2.INTER_NEAREST)
scaled_nearest_down = cv2.resize(img_rgb, None, fx=scale_down, fy=scale_down, interpolation=cv2.INTER_NEAREST)
# --- 双线性插值(平衡速度与质量,推荐普通场景)
scaled_bilinear_up = cv2.resize(img_rgb, None, fx=scale_up, fy=scale_up, interpolation=cv2.INTER_LINEAR)
scaled_bilinear_down = cv2.resize(img_rgb, None, fx=scale_down, fy=scale_down, interpolation=cv2.INTER_LINEAR)
# --- 双三次插值(细节更丰富,速度慢,适合高清放大)
scaled_bicubic_up = cv2.resize(img_rgb, None, fx=scale_up, fy=scale_up, interpolation=cv2.INTER_CUBIC)
scaled_bicubic_down = cv2.resize(img_rgb, None, fx=scale_down, fy=scale_down, interpolation=cv2.INTER_CUBIC)
# --------------------------
# 6. 用matplotlib显示所有结果(2行4列布局)
# --------------------------
plt.figure(figsize=(16, 8))
# 第一行:平移、镜像(垂直+水平)、旋转
plt.subplot(2, 4, 1)
plt.imshow(img_rgb)
plt.title('原始图像'), plt.axis('off')
plt.subplot(2, 4, 2)
plt.imshow(translated)
plt.title(f'平移\n(右{tx},下{ty})'), plt.axis('off')
plt.subplot(2, 4, 3)
plt.imshow(mirrored_vertical)
plt.title('镜像(垂直)\n左右翻转'), plt.axis('off')
plt.subplot(2, 4, 4)
plt.imshow(mirrored_horizontal)
plt.title('镜像(水平)\n上下翻转'), plt.axis('off')
plt.subplot(2, 4, 5)
plt.imshow(rotated)
plt.title(f'旋转\n{angle}°'), plt.axis('off')
# 第二行:缩放(最近邻/双线性/双三次,放大+缩小)
plt.subplot(2, 4, 6)
plt.imshow(scaled_nearest_up)
plt.title(f'缩放(最近邻)\n放大{scale_up}x'), plt.axis('off')
plt.subplot(2, 4, 7)
plt.imshow(scaled_bilinear_up)
plt.title(f'缩放(双线性)\n放大{scale_up}x'), plt.axis('off')
plt.subplot(2, 4, 8)
plt.imshow(scaled_bicubic_up)
plt.title(f'缩放(双三次)\n放大{scale_up}x'), plt.axis('off')
# 可选:添加缩小效果的子图(若需要展示缩小,可调整布局或换行显示)
# 为简洁起见,此处仅展示放大效果,缩小效果同理(替换fx/fy为scale_down即可)
# --------------------------
下面的灰度差值运算 等下插入这里运行
# --------------------------
plt.tight_layout() # 自动调整子图间距
plt.show()
2. 灰度差值运算
灰度差值运算
几何运算还需要一个算法用于灰度级的重采样。如果一个输出像素 映射到四个输入像素之间,则其灰度值由灰度插值算法决定,如图所示 👇

1 最近邻插值算法---简单粗暴的"抄近邻"
最近邻法是将 (u0,v0)点最近的整数坐标 (u,v) 点 的灰度值取为(u0,v0)点的灰度值。
工作原理:
- 新像素点直接采用离它最近的原图像素的值
就像班级新同学直接复制最近同学的作业

适用场景:
实时性要求高的场景(如游戏纹理)
像素艺术风格图像处理
优缺点:
✅ 计算速度极快
❌ 会产生明显锯齿和马赛克
2 双线性插值算法



工作原理:
- 找到新点周围的4个最近像素(上下左右)
- 根据距离计算权重(离得越近权重越大)
- 取加权平均值
生活比喻:
- 像四个邻居开会决定小区新设施:
- 离设施越近的住户,投票权重越大
- 最终方案是各方妥协的结果
适用场景:
- 普通照片的缩放
- 需要平衡质量和速度的场合
优缺点:
✅ 双线性内插法的计算比最邻近点法复杂,计算量较大,但没有灰度不连续的缺点,结果基本令人满意
❌ 它具有低通滤波性质,使高频分量受损,图像轮廓可能会有一点模糊
3 双三次插值算法
由连续信号采样定理可知,若对采样值用插值函数S(x)=sin(πx)/πx插值,则可精确地恢复原函数,当然也就可精确得到采样点间任意点的值。
三次内插法不仅考虑(u0,v0)点的直接邻点对它的影响,还考虑到该点周围邻点的灰度值对它的影响。
工作原理:
- 召集周围16个像素(4×4区域)
- 用更复杂的三次函数计算各点影响权重
- 综合计算出新像素值
专业比喻:
- 像医学专家会诊:
- 不仅考虑最近的医生意见
- 还要参考相关领域多位专家观点
- 用专业公式整合各方意见
案例代码:
适用场景:
- 专业图像编辑
- 医学影像处理
- 需要保留细节的放大操作
优缺点:
✅ 边缘保持更好
✅ 细节更丰富
❌ 计算量是双线性的4倍
❌ 可能出现轻微"振铃效应"(边缘光环)
案例代码:👇
接上面的恐龙代码
# 第二行:缩放(最近邻/双线性/双三次,放大+缩小)
plt.subplot(2, 4, 6)
plt.imshow(scaled_nearest_up)
plt.title(f'缩放(最近邻)\n放大{scale_up}x'), plt.axis('off')
plt.subplot(2, 4, 7)
plt.imshow(scaled_bilinear_up)
plt.title(f'缩放(双线性)\n放大{scale_up}x'), plt.axis('off')
plt.subplot(2, 4, 8)
plt.imshow(scaled_bicubic_up)
plt.title(f'缩放(双三次)\n放大{scale_up}x'), plt.axis('off')双击图片插件最近邻插值算法是否有锯齿,双线是否会模糊
3. 几何变换类别
灰度差值运算
1. 刚体变换(Rigid Transformation)
如果一幅图像中的两点间的距离经变换到另一幅图像中后仍然保持不变,则这种变换称为刚体变换

特点:
- 保持物体形状和大小绝对不变
- 仅改变位置和方向
- 数学上:平移 + 旋转
生活案例:
- 将桌上的咖啡杯从左侧平移到右侧
- 旋转手机拍摄方向(不倾斜)
应用场景:
- 人脸对齐(证件照标准化)
- 工业零件位置校准
2. 仿射变换(Affine Transformation)
如果一幅图像中的直线经过映射后到另一幅图像上仍为直线,并且保持平行关系,则这种变换称为仿射变换


特点:
- 保持直线和平行线关系(平行线变换后仍平行)
- 允许拉伸、压缩、错切变形
- 数学上:平移 + 旋转 + 缩放 + 错切
生活案例:
- 倾斜拍摄的身份证(长方形→平行四边形)
- 弹簧被拉长时垂直方向变细
应用场景:
- 文档矫正(发票去倾斜)
- 医学影像配准(CT切片对齐)
3. 投影变换(Projective Transformation)
如果一幅图像中的直线经过映射后到另一幅图像上仍为直线,但平行关系基本不保持,则这种变换称为投影变换

特点:
- 模拟透视效果(近大远小)
- 平行线可能相交(如铁轨延伸)
- 数学上:仿射变换 + 透视变形
生活案例: - 从侧面拍摄黑板(长方形→梯形)
- 仰视高楼时顶部变窄
应用场景: - AR虚拟物体贴合(3D模型匹配桌面)
- 生成鸟瞰图(倾斜拍摄→俯视图)
4. 非线性变换(Nonlinear Transformation)
非线性变换又称为弯曲变换(Curved Transform)或者弹性变换,经过非线性变换,一幅图像上的直线映射到另一幅图像上不一定是直线,可能是曲线。

特点:
- 曲线也能变形(直线可能变曲线)
- 局部自由形变
- 数学上:多项式/弹性形变模型
生活案例: - 哈哈镜中的扭曲人脸
- 水波纹倒影的扭曲效果
应用场景: - 医学影像变形分析(器官形变研究)
- 特效电影中的液体变形
###5.一句话总结差异:
刚体 : 移动旋转不变形 → 搬椅子
仿射 : 拉伸压缩不破坏平行 → 压纸盒
投影 : 近大远小 → 拍建筑仰视图
非线性 :自由扭曲 → 哈哈镜效果
4. 几何校正
几何校正
在前面的学习中,我们已经掌握了图像的基础几何变换(平移、旋转、缩放)、灰度插值方法,以及四大几何变换类别(刚体、仿射、投影、非线性)。这些变换既可能是图像采集或处理过程中引入的“误差”(比如镜头畸变、拍摄角度倾斜),也可能是我们主动应用的工具(比如矫正文档、还原真实场景)。
接下来要讲的几何校正,正是针对图像中因各种原因导致的几何形变,通过逆向或补偿变换将其掰正 ,恢复或映射到预期的标准几何形态的过程
1.为什么需要几何校正?
在现实场景中,图像的几何形变无处不在,这些形变可能直接影响后续分析的准确性或视觉体验的合理性。常见的形变来源和对应的校正需求包括:
设备或拍摄问题导致的形变
- 镜头畸变(桶形/枕形失真):广角镜头会让边缘物体向外凸起(桶形),长焦镜头会让边缘物体向内凹陷(枕形),导致直线变弯,测量或识别时产生误差。
- 透视失真(梯形/倾斜):当相机与目标平面不平行时(比如俯拍桌面、斜拍黑板),矩形物体在图像中变成梯形(梯形失真),或出现近大远小的透视汇聚(如建筑顶部变窄),影响尺寸测量或文档识别的准确性。

2. 案例
- 仿射和旋转
import cv2
import numpy as np
import matplotlib.pyplot as plt
%config InlinBackend.figure_format="retina"
plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus']=False #用来正常显示负
# 创建一个示例图像(白色背景+黑色矩形)
image = np.ones((300, 300, 3), dtype=np.uint8) * 255
cv2.rectangle(image, (50, 50), (250, 250), (0, 0, 0), 2)
# 定义纯旋转(绕中心点旋转30度)
h, w = image.shape[:2]
center = (w // 2, h // 2)
rotation_matrix = cv2.getRotationMatrix2D(center, angle=30, scale=1.0)
rotated = cv2.warpAffine(image, rotation_matrix, (w, h))
# 定义仿射变换(旋转+缩放+平移)
affine_matrix = np.float32([[0.8, 0.5, 50], # 缩放+错切+平移
[-0.3, 1.2, 20]])
affine_transformed = cv2.warpAffine(image, affine_matrix, (w, h))
#plt显示
plt.figure(figsize=(12, 8))
plt.subplot(131)
plt.title("原始图像")
plt.imshow(image)
plt.subplot(132)
plt.title("旋转图像")
plt.imshow(rotated)
plt.subplot(133)
plt.title("仿射变换图像")
plt.imshow(affine_transformed)
plt.show()
- 仿射变换和仿射校正
import numpy as np
import cv2
from matplotlib import pyplot as plt
%matplotlib inline
%config InlinBackend.figure_format="retina"
plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus']=False #用来正常显示负
img0 = cv2.imread(r'./img/alphabet.jpg',0)
img1=cv2.copyMakeBorder(img0,50,50,50,50,cv2.BORDER_CONSTANT,value=0)
# 50 50 50 50 分别表示上 右 下 左 的边界宽度,BORDER_CONSTANT 表示边界为常数,value 表示常数值
(cols,rows)=img1.shape#获取图像的宽和高
pts1 = np.float32([[50,50],[200,50],[50,200]])#原图像上的三个参考点
pts2 = np.float32([[10,70],[160,50],[40,220]])#目标图像上的三个参考点
M1 = cv2.getAffineTransform(pts1,pts2) #仿射变换矩阵
dst1 = cv2.warpAffine(img1,M1,(cols,rows)) #仿射变换
M2 = cv2.getAffineTransform(pts2,pts1) #逆仿射变换矩阵
dst2 = cv2.warpAffine(dst1,M2,(cols,rows))#仿射校正
plt.figure(figsize=(8,5))
plt.subplot(131)
plt.title("原图像")
plt.axis("off")
plt.imshow(img1,cmap="gray")
plt.subplot(132)
plt.title("仿射变换")
plt.axis("off")
plt.imshow(dst1,cmap="gray")
plt.subplot(133)
plt.title("校正图像")
plt.axis("off")
plt.imshow(dst2,cmap="gray")
plt.show()
3.梯形校正了解一下

import cv2
import numpy as np
import matplotlib.pyplot as plt
# 1. 读取图像(替换为你的文档图片路径,或使用下方示例图片链接)
# 示例:使用OpenCV自带的示例图片(需联网下载,或替换为你本地的文档图片)
image_path = './img/tixing.png' # 请替换为你的文档图片路径!
# 如果没有本地图片,可以用以下代码下载一个示例(需网络):
# import urllib.request
# urllib.request.urlretrieve('https://example.com/document.jpg', 'document.jpg') # 替换为真实链接
img = cv2.imread(image_path)
if img is None:
raise FileNotFoundError("请确保图片路径正确,或替换为本地文档图片路径!")
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # OpenCV读的是BGR,转为RGB方便matplotlib显示
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
# 2. 边缘检测 + 轮廓检测
blurred = cv2.GaussianBlur(gray, (5, 5), 0) # 高斯模糊降噪
edges = cv2.Canny(blurred, 50, 150) # Canny边缘检测
contours, _ = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea, reverse=True)[:5] # 取面积最大的5个轮廓
# 3. 找到文档的近似矩形轮廓(通过多边形近似)
doc_contour = None
for cnt in contours:
peri = cv2.arcLength(cnt, True) # 计算轮廓周长
approx = cv2.approxPolyDP(cnt, 0.02 * peri, True) # 多边形近似(参数越小越精确)
if len(approx) == 4: # 如果是四边形(文档通常是四边形)
doc_contour = approx
break
if doc_contour is None:
raise ValueError("未检测到文档轮廓!请确保图片中包含清晰的矩形文档。")
# 4. 提取四个角点并排序(左上、右上、右下、左下)
def order_points(pts):
# 初始化坐标点矩阵
rect = np.zeros((4, 2), dtype="float32")
# 左上角点:x+y最小;右下角点:x+y最大
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)] # 左上
rect[2] = pts[np.argmax(s)] # 右下
# 右上角点:x-y最小;左下角点:x-y最大
diff = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(diff)] # 右上
rect[3] = pts[np.argmax(diff)] # 左下
return rect
pts = doc_contour.reshape(4, 2) # 从轮廓中提取4个点
doc_corners = order_points(pts) # 排序后的原图四个角点
# 5. 定义目标矩形的四个角点(与原图内容区域匹配的正视图)
(h, w) = img.shape[:2]
dst_corners = np.array([
[0, 0], # 左上
[w - 1, 0], # 右上
[w - 1, h - 1], # 右下
[0, h - 1] # 左下
], dtype="float32")
# 6. 计算透视变换矩阵并应用变换
M = cv2.getPerspectiveTransform(doc_corners, dst_corners) # 计算变换矩阵
warped = cv2.warpPerspective(img, M, (w, h)) # 应用透视变换
# 7. 显示结果
plt.figure(figsize=(12, 6))
plt.subplot(121), plt.imshow(img), plt.title('原始文档(倾斜)'), plt.axis('off')
plt.subplot(122), plt.imshow(warped), plt.title('校正后(正视图)'), plt.axis('off')
plt.show()
