坐标变换公式如下:
为旋转角度
证明:略,有空补,不知道也不妨碍用
仅截止到目前为止,对图像旋转的操作好像是挺简单的.但是这仅仅是理论部分,距离实际情况还有一段距离
首先,问题一:图像在绕着谁转?
根据推导过程可知,这个公式是基于 图像绕原点旋转 得出。而在数组中,这个原点又是索引[0,0]的位置,也就是说,图像绕着左上角旋转,
逆时针;
顺时针
然后我们再考虑这样一个问题:旋转之后会不会导致信息缺失?
按照日常生活的理解,旋转就是坐标的一一映射,通过一定的运算法则由原始图像的坐标得到旋转后的位置,再把对应的像素值传过去。但是在实际情况下,我们可以认为图像是在二维平面上有定义域的,也就是每一个图像都有大小限制,所以说做旋转变换后的坐标可能超出了这个图像的定义域,造成信息缺失。所以,我们在进行旋转前应当预留足够的位置,防止出现缺失
这里引申以下,如果不想要绕左上角旋转,而是图像中心旋转怎么办?
这个问题还是比较好解决的,因为只需要在旋转之前,将旋转中心通过平移操作移动到原点就可以了,旋转之后通过平移操作适当调整位置将图像现实出来
具体见代码
下面开始进行程序实现:以python为例
实验使用图像如下
实验内容:围绕点(200,150)逆时针旋转30
import numpy as np
import cv2 as cv
img = cv.imread('sample.jpg', cv.IMREAD_GRAYSCALE)
cv.imshow('ori', img)
(H, W) = (700, 700) # 设定画布大小
ans = np.zeros((H, W), dtype=np.uint8) # 默认数组类型为float64,在imshow无法正常识别
alpha = 30/180*np.pi # 弧度非角度
(cos, sin) = (np.cos(alpha), np.sin(alpha))
(adjust_x,adjust_y) = (200, 150) # 设置旋转中心
(move_x, move_y) = (300, 250) # 旋转后位移参数,使居中
for x in range(0, 454):
for y in range(0, 375):
(amx, amy) = (x-adjust_x, y-adjust_y)
(x_new, y_new) = (np.int(amx*cos-amy*sin)+move_x, np.int(amx*sin+amy*cos)+move_y)
if x_new>=700 or x_new<0 or y_new>=700 or y_new<0 :
continue
ans[x_new,y_new] = img[x, y]
cv.imshow('test', ans)
cv.waitKey(0)
cv.destroyAllWindows()
关于这段代码有两个地方需要说明一下
- 在使用cv2.imshow的时候,传入数组的格式数据需要为uint8,否则会出现全白的情况
- 旋转后坐标通常不会是整数,需要取整
最后是实验结果
奇怪的结果出现了!我们的代码在未知力量操控下给图像加上了奇怪的纹路!
但是百因必有果,这玩意不可能莫名其妙的上去。
究其原因,问题就出在取整上。为啥这么说呢,举个例子,现在旋转后的坐标是182.99999,取整之后变成了182;还有一个坐标是184.00001,取整之后变成了184,于是我们惊奇的发现,183没了
再者说,如果说两个坐标分别182.000001和182.99999,最后都会被认为是182,就会导致覆盖产生,所以说这个代码是有缺陷的
那么可不可以克服这个问题呢?
为了保证旋转后每一个像素块
都有值,我们可以根据旋转方程倒推原始图象中对应
的
,再将
的值赋予
但是我们要注意,虽然每一个像素都有值,但是对于坐标取整导致的问题仍然存在,始终有部分像素传递不过去。
import numpy as np
import cv2 as cv
img = cv.imread('sample.jpg', cv.IMREAD_GRAYSCALE)
(len_x, len_y) = img.shape
# print(len_x, len_y)
cv.imshow('ori', img)
(H, W) = (700, 700) # 设定画布大小
ans = np.zeros((H, W), dtype=np.uint8) # 默认数组类型为float64,在imshow无法正常识别
alpha = 30/180*np.pi # 弧度非角度
(cos, sin) = (np.cos(alpha), np.sin(alpha))
(adjust_x, adjust_y) = (0, 0)
(move_x, move_y) = (250, -75)
for x in range(0,H-1):
for y in range(0, W-1):
(ori_x, ori_y) = (np.int(x*cos+y*sin)-move_x, np.int(y*cos-x*sin)-move_y)
if ori_x < 0 or ori_y < 0 or ori_x >= len_x or ori_y >= len_y:
continue
ans[x,y] = img[ori_x, ori_y]
cv.imshow('test', ans)
cv.waitKey(0)
cv.destroyAllWindows()
实验效果如下 可以看出产生类似毛玻璃的效果,线条边缘有明显锯齿化
为了解决这一问题,引入插值
这里应该有个连接 但是现在还没写完
代码如下
alpha = 30/180*np.pi
(cos, sin) = (np.cos(alpha), np.sin(alpha))
(adjust_x, adjust_y) = (0, 0)
(move_x, move_y) = (250, -75)
for x in range(0,H-1):
for y in range(0, W-1):
(ori_x, ori_y) = (np.int(x*cos+y*sin)-move_x, np.int(y*cos-x*sin)-move_y)
if ori_x < 0 or ori_y < 0 or ori_x >= len_x or ori_y >= len_y:
continue
(sum_pix, Un) = (np.int(img[ori_x, ori_y]), 1)
if ori_x - 1 >= 0:
sum_pix += img[ori_x-1, ori_y]
Un += 1
if ori_x + 1 < len_x:
sum_pix += img[ori_x+1, ori_y]
Un += 1
if ori_y - 1 >= 0:
sum_pix += img[ori_x, ori_y-1]
Un += 1
if ori_y + 1 < len_y:
sum_pix += img[ori_x, ori_y+1]
Un += 1
ans[x, y] = np.uint8(sum_pix / Un)
cv.imshow('direct', ans)
与无插值方法对比
两图像做差结果
import numpy as np
import cv2 as cv
img = cv.imread('sample.jpg', cv.IMREAD_GRAYSCALE)
(len_x, len_y) = img.shape
(H, W) = (700, 700) # 设定画布大小
ans = np.zeros((H, W), dtype=np.uint8) # 默认数组类型为float64,在imshow无法正常识别
alpha = 30/180*np.pi # 弧度非角度
(cos, sin) = (np.cos(alpha), np.sin(alpha))
(adjust_x, adjust_y) = (0, 0)
(move_x, move_y) = (250, -75)
for x in range(0,H-1):
for y in range(0, W-1):
(ori_x, ori_y) = (np.int(x*cos+y*sin)-move_x, np.int(y*cos-x*sin)-move_y)
if ori_x < 0 or ori_y < 0 or ori_x >= len_x or ori_y >= len_y:
continue
(sum_pix, Un) = (np.int(img[ori_x, ori_y]), 1)
if ori_x - 1 >= 0:
sum_pix += img[ori_x-1, ori_y]
Un += 1
if ori_x + 1 < len_x:
sum_pix += img[ori_x+1, ori_y]
Un += 1
if ori_y - 1 >= 0:
sum_pix += img[ori_x, ori_y-1]
Un += 1
if ori_y + 1 < len_y:
sum_pix += img[ori_x, ori_y+1]
Un += 1
ans[x, y] = np.uint8(sum_pix / Un)
cv.imshow('direct', ans)
ans1 = np.zeros((H, W), dtype=np.uint8)
for x in range(0,H-1):
for y in range(0, W-1):
(ori_x, ori_y) = (np.int(x*cos+y*sin)-move_x, np.int(y*cos-x*sin)-move_y)
if ori_x < 0 or ori_y < 0 or ori_x >= len_x or ori_y >= len_y:
continue
ans1[x, y] = img[ori_x, ori_y]
cv.imshow('insert', ans1)
cv.imshow('dif', np.uint8(ans - ans1))
cv.waitKey(0)
cv.destroyAllWindows()