一、旋转
旋转的数学原理:
经过计算,可以得到以下关系:
x′=xcosθ−ysinθ
y′=xsinθ+ycosθ
写成矩阵的形式是:
或者是:(注意坐标按行的方式和按列的方式排列时,变换矩阵的左右位置不一样,矩阵中元素的位置也不一样。可以根据计算的实际过程来确定)
因此,如果知道旋转的角度,可以直接构造矩阵进行变换,代码如下:(当角度为正时,逆时针旋转)。
src = np.array([[5, 5], [5, 10], [10, 5], [10, 10]])
angle = 10
angle_pi = angle * np.pi / 180
a = np.cos(angle_pi)
b = np.sin(angle_pi)
A =np.array([[a, b], [-b, a]])
dest = np.dot(src, A)
如果是多边形的话,旋转后会形变,如矩形会变形为平行四边形,变换前后如下图所示:
对于图像的变换,可以使用opencv等工具进行变换:
rotate_matrix = cv2.getRotationMatrix2D(center=(img.shape[0]/2, img.shape[1]/2), angle=90, scale=1)
rot_img = cv2.warpAffine(img, rotate_matrix, (img.shape[0], img.shape[1]))
上述过程是绕原点旋转,如果想绕任意一点(x0, y0)旋转,可以先把(x0, y0)平移到原点(例如把所有点都减去(x0, y0)),再进行旋转。
二、位移
位移比较简单,就是简单坐标加减过程
用变换矩阵进行计算就是:
图像的平移:需构造变换矩阵
trans_matrix = np.array([[1, 0, 10],[0, 1, 5]], dtype=np.float32)
trans_img = cv2.warpAffine(img, trans_matrix, (img.shape[0] + 10, img.shape[1] + 5))
三、缩放
直接对原坐标进行缩放,图像的中心点会偏移
如果想保持中心点不变,可以先把中心点平移到原点,再进行缩放,然后再平移回来
或者利用矩阵来计算:同样的过程,先平移,再缩放,再平移:(注意:根据矩阵乘法结合律,离原坐标近的矩阵先执行)
图像的缩放:
resize_img = cv2.resize(src=img, dsize=(img.shape[1]*2, img.shape[0]*2), interpolation=cv2.INTER_LINEAR)
四、仿射变换
把旋转、平移、缩放、对称、错切等结合起来,就是仿射变换,仿射变换不会改变图形的相对关系,平行线变换后还是平行线,圆变换后变成椭圆。其中的变换矩阵就是按照变换的顺序,把各个过程的变换乘起来(先执行的离原坐标较近)。
图形的仿射变换:需要知道三对对应点,求变换矩阵M
rows, cols, ch = img.shape
pts1 = np.float32([[0, 0], [cols - 1, 0], [0, rows - 1]])
pts2 = np.float32([[cols * 0.2, rows * 0.1], [cols * 0.9, rows * 0.2], [cols * 0.1, rows * 0.9]])
M = cv2.getAffineTransform(pts1, pts2)
dst_img = cv2.warpAffine(img, M, (cols, rows))
反过来,利用上面的过程,可以在知道旋转前、后的坐标的情况下,求变换矩阵。例如需要把图像进行变换,使图像中的某些点与目标位置的点进行匹配,从而对原图像的位置进行调整。如根据人脸图像中的眼睛、鼻子、嘴巴等关键点,对人脸进行对齐。
def bestFit(destination, source):
destMean = np.mean(destination, axis=0)
srcMean = np.mean(source, axis=0)
srcVec = (source - srcMean).flatten()
destVec = (destination - destMean).flatten()
a = np.dot(srcVec, destVec) / np.linalg.norm(srcVec)**2 #dot(a, b) = |a|*|b|*cos
b = 0
for i in range(destination.shape[0]):
b += srcVec[2*i] * destVec[2*i+1] - srcVec[2*i+1] * destVec[2*i] #|a x b| = |a||b|sin, |a x b| = x1y2 - x2y1
b = b / np.linalg.norm(srcVec)**2 #sin
A = np.array([[a, b], [-b, a]]) #[[cos, sin], [-sin, cos]]
srcMean = np.dot(srcMean, T)
t = destMean - srcMean
return A, t
其中,destination是一组目标点,source是原图像的对应点,上面代码求的是平均的变换。返回的A是旋转矩阵,t 是平移矩阵。
得到A和t之后,如果是对其他的坐标点进行变换,方法如下:
dest = np.dot(src, A) + t
如果是对图像进行变换,方法如下:
A2 = np.linalg.inv(A) #逆矩阵
t2 = np.dot(-t, A2) #位移矩阵也需要进行旋转和转置
outImg = ndimage.interpolation.affine_transform(img, A2, t2[[1, 0]], output_shape=self.imgSize)
五、透视变换
透视变换相当于将图像投影到一个新的平面(可能与原平面不平行),得到的是从原图到目标平面的映射,直线变换后还是直线,但平行线变换后不一定还保持平行。透视变换需要确定四对坐标。
points1 = np.float32([ [480, 140], [0,140], [0,0], [480,0] ]) # 原图象四个角坐标
points2 = np.float32([ [480, 200], [0, 150], [0, 50], [480, 0]]) # 目标图像四个角坐标
M = cv2.getPerspectiveTransform(points1, points2)
perspective_img = cv2.warpPerspective(img_plate, M, (480, 200))
变换的结果如下: