一. 前言
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()