提起线性代数,老师似乎只会告诉我们矩阵的应用范围很广,很重要。但是
一学期学下来,大家似乎也只会做题并不知道矩阵是如何运用的。一开始的我也是很疑惑,所学的知识似乎和实际的运用丝毫挂不上边。比如,以前听人说图像矩阵可以旋转。但是如何旋转,怎么旋转一直都不了解。虽然上网百度网上只是给出了图像旋转的公式,但却没有给出具体的解释。经过一番钻研还是了解了图像旋转的全过程,这里分享给大家,希望对大家有所帮助。
图像旋转的步骤
首先,大家要先了解图像旋转的步骤有哪些,只有知道自己每一步在做什么才不致于迷茫:
我们先读取一张图片,想象将图片放入一个坐标系中,图片的左上角与坐标原点重合,这时候的图片矩阵的索引与坐标系中的坐标(x,y)相吻合。再接着以图片对应的每一个(x,y)坐标进行一定角度的旋转得到(x1,y1),这时候的坐标x1,y1有的可能为负值,这样的负值在矩阵的索引中是不科学的。所以我们需要将负值变为正值,这相当于平移到正值的象限,平移后的坐标为(x2,y2)。最后将原图像矩阵根据索引(x,y)得到像素值,并将其赋值给相对应的矩阵索引(x2,y2)。这样就完成了。
图文并茂才能更好的理解,下面看图:
实现难点
下面我们就来说旋转矩阵是怎么来的以及它是什么样子的。
假设一个点为(x,y),旋转角度为 θ \theta θ。在直角坐标系中难以表示旋转,我们以极坐标系来表示: x 2 + y 2 = r 2 x = r cos α y = r sin α x^2+y^2 = r^2\qquad x = r\cos\alpha \qquad y = r\sin\alpha x2+y2=r2x=rcosαy=rsinα
则旋转后的坐标为: x = r cos ( α − θ ) y = r sin ( α − θ ) x = r\cos(\alpha-\theta) \qquad y = r\sin(\alpha-\theta) x=rcos(α−θ)y=rsin(α−θ)
化简后为: x 1 = x cos θ + y sin θ y 1 = y cos θ − x sin θ x1 = x\cos\theta+y\sin\theta \qquad y1 = y\cos\theta-x\sin\theta x1=xcosθ+ysinθy1=ycosθ−xsinθ
根据化简后的公式可得出矩阵计算公式:
[ x y ] ∗ [ cos θ − sin θ sin θ cos θ ] = [ x 1 y 1 ] \begin{bmatrix}x & y\\ \end{bmatrix}*\begin{bmatrix}\cos\theta&-\sin\theta\\ \sin\theta&\cos\theta\\ \end{bmatrix}=\begin{bmatrix}x1 & y1\\ \end{bmatrix} [xy]∗[cosθsinθ−sinθcosθ]=[x1y1]
上面的旋转公式只是理论上的,但在实际运用的时候还需要对其进行进一步的改造。一般的旋转都是默认进行图像中心位置的旋转,这样我们需要先将图像中心移动到原点,在旋转,最后将其移动到原位置。(这里的w,h代表了图像的长和宽)
[ x y 1 ] ∗ [ 1 0 0 0 1 0 − w / 2 − h / 2 1 ] ∗ [ cos θ − sin θ 0 sin θ cos θ 0 0 0 1 ] ∗ [ 1 0 0 0 1 0 w / 2 h / 2 1 ] = [ x 1 y 1 1 ] \begin{bmatrix}x & y&1\\ \end{bmatrix}*\begin{bmatrix}1&0&0\\0&1&0\\-w/2&-h/2&1\\ \end{bmatrix}*\begin{bmatrix}\cos\theta&-\sin\theta&0\\ \sin\theta&\cos\theta&0\\0&0&1\\ \end{bmatrix}*\begin{bmatrix}1&0&0\\0&1&0\\w/2&h/2&1\\ \end{bmatrix}=\begin{bmatrix}x1 & y1&1\\ \end{bmatrix} [xy1]∗
10−w/201−h/2001
∗
cosθsinθ0−sinθcosθ0001
∗
10w/201h/2001
=[x1y11]
再对其进行化简得
[ x y 1 ] ∗ [ cos θ − sin θ 0 sin θ cos θ 0 w / 2 − w / 2 ∗ cos − h / 2 ∗ sin w / 2 ∗ sin − h / 2 ∗ cos + h / 2 1 ] = [ x 1 y 1 1 ] \begin{bmatrix}x & y&1\\ \end{bmatrix}*\begin{bmatrix}\cos\theta&-\sin\theta&0\\ \sin\theta&\cos\theta&0\\w/2-w/2*\cos-h/2*\sin&w/2*\sin-h/2*\cos+h/2&1\\ \end{bmatrix}=\begin{bmatrix}x1 & y1&1\\ \end{bmatrix} [xy1]∗
cosθsinθw/2−w/2∗cos−h/2∗sin−sinθcosθw/2∗sin−h/2∗cos+h/2001
=[x1y11]
这个就是最终我们在编程是要使用的公式
到这为止我们的核心工作就完成了,下面就可以用代码来实现了。
代码实现
在实现过程中会有一些关键点,我也都做了注释
import cv2
import numpy as np
#创建用于图像坐标转换的矩阵
def create_rotation_matrix(angle,w,h):
cos = np.cos(np.deg2rad(angle))
sin = np.sin(np.deg2rad(angle))
matrix = np.array([[cos,-sin,0],[sin,cos,0],[w-w*cos-h*sin,w*sin-h*cos+h,1]])
return matrix
#图像旋转
def rotation_image(image,matrix,background = 0):
#获取图像的大小
w,h,z = image.shape
#创建旋转图像的矩阵的变量名
rotation_image = None
#创建图像坐标矩阵,这里新创建的矩阵相当于图纸,而旋转后的坐标代表要在纸上画图的位置
coordinate = np.array([[i,j,1] for i in range(w) for j in range(h)],dtype = np.int16)
#矩阵旋转
rotation_coordinate = np.dot(coordinate,matrix)
#将矩阵由浮点型转换为整数型,矩阵的索引需要整数。
rotation_coordinate = rotation_coordinate.astype(np.int16)
#将对应坐标像素赋值,此方法是将超出图像背景的部分进行了裁剪
if background == 0:
#创建旋转图像的矩阵
rotation_image = np.zeros((w,h,z),dtype = np.uint8)
#循环将相对应的像素进行赋值
for i in range(coordinate.shape[0]):
x,y,_ = coordinate[i]
rot_x,rot_y,_ = rotation_coordinate[i]
if rot_x<0 or rot_x>=w or rot_y<0 or rot_y>=h:
#对于超出旋转矩阵范围的坐标点,不做处理
pass
else:
rotation_image[rot_x,rot_y,:] = image[x,y,:]
#非零代表不裁剪
else:
#找出坐标矩阵中最小的x和y
min_coor = np.min(rotation_coordinate,axis = 0)
#让坐标矩阵的所有x和y减去对应的最小x和y,这是坐标位于第一象限(都为正直)
rotation_coordinate = rotation_coordinate-min_coor
#再找出坐标矩阵的最大的x和y,作为rotation_image矩阵的大小
max_coor = np.max(rotation_coordinate,axis = 0)
rotation_image = np.zeros((max_coor[0]+1,max_coor[1]+1,z),dtype = np.uint8)
#像素赋值
for i in range(coordinate.shape[0]):
x,y,_ = coordinate[i]
rot_x,rot_y,_ = rotation_coordinate[i]
rotation_image[rot_x,rot_y,:] = image[x,y,:]
return rotation_image
if __name__ == '__main__':
#读取图像
image = cv2.imread(r'C:\Users\Administrator\Desktop\1.png')
#image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
w,h,_= image.shape
#注意这里传入的旋转点w,h不能直接传入的是w,h = image.shape
#传入的应该是0至(w-1)和0至(h-1)
matrix = create_rotation_matrix(angle=-45,w =w/2,h = h/2)
#background为0时代表的是旋转后的图像与原图像大小相同相当于裁剪了图像,
#传入其他数值则为显示完整的旋转图像
rotation_image = rotation_image(image,matrix,background = 1)
cv2.imshow('image',image)
cv2.imshow('rotation_image',rotation_image)
cv2.waitKey()
cv2.destroyAllWindows()
效果截图:
左边是原图,右边是旋转之后的图像。
总结
相信大家看见了旋转之后的图像中存在小黑点,这是因为图像在旋转之后的坐标值一般都为小数,但我们将其直接转换为了整数,这会导致旋转后的位置没有像素点。可能也会是其他原因。
这种旋转方式是前向的像素传值,还有一种是后向的像素传值(按照旋转之后的坐标到原图像中寻找对应的像素值)加上一种像素的插值法就可以解决上面的问题了。
这篇文章主要是想讲一下矩阵的运用,对于解决上面的旋转之后的小黑点问题,等到下一篇文章再讲解吧。