一. 前言
1. 最近在做身份证OCR项目中,需要对倾斜扭曲的图像做矫正,透视变换正可以解决这个问题,在这里记录对变换方法的理解。
2. 本文主要介绍一下仿射变换和透视变换的原理,特点以及其在opencv中实现的一些注意点。
3. 首先看下透视变换后的例图:
二. 从仿射变换说起
透视变换和仿射变换在原理上比较相似,而仿射变换更加简单。
1. 定义:仿射变换(Affine Transformation或 Affine Map),又称为仿射映射,是指在几何中,图像进行从一个向量空间进行一次线性变换和一次平移,变换为到另一个向量空间的过程。
2. 特性:仿射变换保持了二维图形的平直性和平行性。平直性:变换是直线的,变换后还是直线;平行性:二维图形之间的相对位置关系保持不变。
3. 公式:如图所示
其中,
- M表示变换矩阵,它包括线性变换矩阵A和平移矩阵B;
- [x, y]T代表原始图像矩阵,是一个二维数组,x代表像素的横座标,y代表像素的纵座标;
- T代表经过仿射变换后的图像矩阵。
4. 理解:
-
在几何中,任何变换都可以以矩阵乘法(线性变换)的形式表示,图像中的平移操作可以以矢量加法(平移)的形式表示;
- 一个任意的仿射变换都可以表示为乘以一个矩阵再加上一个向量的形式;[x, y]T代表原始图像矩阵,是一个二维数组,x代表像素的横座标,y代表像素的纵座标;
- 如果线性变换矩阵A=[[1, 0], [0,1]],那么A点乘矩阵[x, y]T 就不会改变[x, y]T的信息,此时,仿射变换就变成了平移操作,移动的行列值就是矩阵B中的元素值;
- 如果线性矩阵A=[[cosθ, -sinθ], [sinθ, cosθ]],那么线性变换就变成了图片的旋转操作,旋转角度θ。矩阵B代表旋转中心的座标便宜量。两个矩阵的计算方式如下图:
5. 应用:
- opencv-python中,使用cv.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])函数进行仿射变换。
- 参数:
- src表示输入图像
- M为转换矩阵2*3,需要手动给出
- dsize表示输出图像的size,用来截取从原点到size的大小的图片
- 官方API:https://docs.opencv.org/3.4.3/da/d54/group__imgproc__transform.html#ga0203d9ee5fcd28d40dbc4a1ea4451983
- cv2.getAffineTransform(src, dst)函数提供了计算转换矩阵的方法。
- src和dst分别表示输出座标和输出座标,注意,这里座标数量应该为3个;
- 返回值为期望的转换矩阵M;
- 官方API:https://docs.opencv.org/3.4.3/da/d54/group__imgproc__transform.html#ga47069038267385913c61334e3d6af2e0
- cv2.getRotationMatrix2D(center, angle, scale)函数提供了计算转换矩阵的方法。
- 参数:
- center为旋转中心
- angle为旋转角度
- scale为缩放比例
- 官方API:https://docs.opencv.org/3.4.3/da/d54/group__imgproc__transform.html#gafbbc470ce83812914a70abfb604f4326
三. 再说透视变换
有了仿射变换的理解,下面说透视变换就简单了,因为它是仿射变换的一种非线性扩展。
1. 定义:透视变换(Perspective Transformation)就是将图片投影到一个新的视平面,也称为投影映射。
2. 特性:相对仿射变换来说,改变了直线之间的平行关系。
3. 直接看公式:通用的变换公式如下
其中,
- u, v代表原始图片座标,x, y为经过透视变换的图片座标;
- 变换矩阵为3*3,它可以拆分为四个部分
- [a11, a12, a21, a22]为线性变换的矩阵,[a31, a32]为平移矩阵,这两个矩阵等价于仿射变换的变换矩阵;
- [a13,a23]为产生透视变换的矩阵;
- 因此变换公式也可以表示为:
4. 理解:
- 在3*3的变换矩阵M中,包含了线性变换矩阵和平移矩阵,因此,仿射变换可以看作透视变换的一种特殊形式;
- 经过透视变换后的图片通常不是平行四边形(除非映射平面和原平面平行);
- 求取转换矩阵时,需要四个点的座标,同时要保证至少三个不在同一直线;
- 已知4个点的座标和想要变换成的矩阵座标,即可求出3*3的变换矩阵。
5. 应用:
- opencv-python中,使用cv2.warpPerspective(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])函数进行透视变换。
- 参数:
- src表示输入图像
- M为转换矩阵3*3,需要手动给出
- dsize表示输出图像的size,用来截取从原点到size的大小的图片
- 官方API:https://docs.opencv.org/3.4.3/da/d54/group__imgproc__transform.html#gaf73673a7e8e18ec6963e3774e6a94b87
- 获取透视变换矩阵的函数:cv2.getPerspectiveTransform(src, dst)
6. 代码:
# 身份证的模版技术
import cv2
import numpy as np
import os
from matplotlib import pyplot as plt
from math import *
img_path = ""
img = cv2.imread(img_path, 0)
print("原图")
plt.imshow(img, cmap='gray', interpolation='bicubic')
plt.show()
txt_path = img_path.split('.')[0] + '.txt'
# Roi顺时针座标
with open(txt_path, encoding='utf-8') as f:
rec = list(map(float, f.readline().strip().split(',')))
# print(rec)
xDim, yDim = img.shape[1], img.shape[0]
pt1 = (max(1, rec[0]), max(1, rec[1]))
pt2 = (rec[2], rec[3])
pt3 = (rec[4], rec[5])
pt4 = (min(rec[6], xDim - 2), min(yDim - 2, rec[7]))
pts = np.array([pt1, pt2, pt3, pt4], np.int32)
H_rows, W_cols= 400, 600
# print(H_rows, W_cols)
# 原图中书本的四个角点(左上、右上、左下、右下),与变换后矩阵位置
pts1 = np.float32([pt1, pt2, pt3, pt4])
pts2 = np.float32([[0, 0],[W_cols,0],[W_cols, H_rows],[0, H_rows],])
# 生成透视变换矩阵;进行透视变换
M = cv2.getPerspectiveTransform(pts1, pts2)
# print(M)
dst = cv2.warpPerspective(img, M, dsize=(W_cols,H_rows))
print("校正之后的图")
plt.imshow(dst, cmap='gray', interpolation='bicubic')
plt.show()