cv2技术原理-仿射变换
上一篇文章 图像旋转原理以及实现-手动实现cv2.getRotationMatrix2D的功能
1、仿射变换
仿射变换(Affine Transformation)是指在向量空间中进行一次线性变换(乘以一个矩阵)和一次平移(加上一个向量),变换到另一个向量空间的过程。
仿射变换代表的是两幅图之间的映射关系,仿射变换矩阵为2x3的矩阵,如下图中的矩阵M,其中的B起着平移的作用,而A中的对角线决定缩放,反对角线决定旋转或错切。
那么M矩阵该怎么计算出来呢?先留个悬念。。。
假设得到变换矩阵M之后该怎么使用呢?
2、变换矩阵M的使用
因为图像中像素点p的坐标可以使用(x,y)表示,变换之后该像素p的位置变换到了坐标(u ,v)。
所以仿射变换是一种二维坐标(x,y)到二维坐标(u,v)之间的线性变换,其数学表达式如下:
这个矩阵是2×3的,但是这会改变原始图像的维度,为此,增加一个维度,构造齐次变换矩阵3×3。
这就保持了图像的‘平直性’和‘平行性’。
平直性:直线、圆弧不变
平行性:平行关系不变,直线相对位置不变,但是夹角可能会改变。
3、opencv实现
import numpy as np
import matplotlib.pyplot as plt
import cv2
path = "5a1672eb1027c.jpg"
img = cv2.imread(path)
def show_img(img):
plt.figure(figsize=(10, 10))
plt.imshow(img[:,:,::-1])
plt.axis('off')
plt.show()
height, width = img.shape[:2]
# 在原图像和目标图像上各选择三个点
mat_src = np.float32([[0, 0],[0, height-1],[width-1, 0]])
mat_dst = np.float32([[0, 0],[100, height-100],[width-100, 100]])
# 得到变换矩阵
mat_trans = cv2.getAffineTransform(mat_src, mat_dst)
# 进行仿射变换
dsts = cv2.warpAffine(img, mat_trans, (width,height))
# 显示
imgs = np.hstack([img,dsts])
show_img(imgs)
show_img(dsts)
print(mat_trans)
左边原图,右边结果。
同时得到了变换矩阵M:
4、主要步骤
从opencv的实现可以看出,主要有三步:
- 选取原始图像和目标图像中的各三个点(为什么是三个?稍后说) ;
- 使用cv2.getAffineTransform(mat_src, mat_dst)获得变换矩阵M;
- 使用cv2.warpAffine函数,完成变换;
为什么是这三个步骤呢?
首先介绍变换矩阵M的计算吧:
还记得这个公式吗?
5、手动实现
我们要求的就是变换矩阵M,每个方程3个未知数,所以需要三个点解三个未知数。
我们的图像高宽分别为height=1080 ,width=1920。
代码中我们在原始图像中选了三个点[0,0]、[0,1079]、[1919,0]。
代码中我们在目标图像中选了三个点[0,0]、[100,979]、[1819,100]。
原始图像上的点和目标图像上的点一一对应。
得到方程:
0=a1×0+b1×0+c1
100=a1×0+b1×1079+c1
1819=a1×1919+b1×0+c1
解得a1= 0.9479,b1=0.09267,c1=0
0=a2×0+b2×0+c2
979=a2×0+b2×1079+c2
100=a2×1919+b2×0+c2
解得a2=0.05211,b2=0.9073,c2=0
与opencv的输出结果略有差异,opencv应该是做了特殊处理。
5.1、使用M进行变换
mat_trans = np.array([[0.9479,0.09267,0],[0.05211,0.9073,0]])
out_img = np.zeros(img.shape,dtype=np.uint8)
for i in range(width):
for j in range(height):
# print(imgs[i,j,:])
ori = np.array([[i],[j],[1]])
dst = np.dot(mat_trans,ori).astype(int)
if dst[1][0]>=height:
dst[1][0] = height-1
if dst[0][0]>=width:
dst[0][0] = width-1
out_img[dst[1][0],dst[0][0],:] = img[j,i,:]
imgs = np.hstack([out_img,dsts]) #dsts为opencv输出的结果
show_img(imgs)
左边为手动实现的效果,右边为opencv结果。
输出结果对比发现,实现基本正确。
再解释一下上面三个点的直观解释就是把下图中的红点拉到绿点,然后图像的其余点跟着进行线性变换。
粗浅理解,如有问题,请批评指正。
感谢参考文献的作者。
参考:图像的仿射变换:cv2.warpAffine()