几何变换-图像旋转

坐标变换公式如下:
x o = x i cos θ y i sin θ y o = x i sin θ + y i cos θ x_o = x_i \cos \theta - y_i\sin \theta \\ y_o = x_i\sin \theta + y_i \cos \theta

θ \theta 为旋转角度

证明:略,有空补,不知道也不妨碍用

仅截止到目前为止,对图像旋转的操作好像是挺简单的.但是这仅仅是理论部分,距离实际情况还有一段距离

首先,问题一:图像在绕着谁转?
根据推导过程可知,这个公式是基于 图像绕原点旋转 得出。而在数组中,这个原点又是索引[0,0]的位置,也就是说,图像绕着左上角旋转, θ > 0 \theta>0 逆时针; θ < 0 \theta<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()

关于这段代码有两个地方需要说明一下

  1. 在使用cv2.imshow的时候,传入数组的格式数据需要为uint8,否则会出现全白的情况
    在这里插入图片描述
    1. 旋转后坐标通常不会是整数,需要取整

最后是实验结果
在这里插入图片描述
奇怪的结果出现了!我们的代码在未知力量操控下给图像加上了奇怪的纹路!
但是百因必有果,这玩意不可能莫名其妙的上去。
究其原因,问题就出在取整上。为啥这么说呢,举个例子,现在旋转后的坐标是182.99999,取整之后变成了182;还有一个坐标是184.00001,取整之后变成了184,于是我们惊奇的发现,183没了
再者说,如果说两个坐标分别182.000001和182.99999,最后都会被认为是182,就会导致覆盖产生,所以说这个代码是有缺陷的

那么可不可以克服这个问题呢?
为了保证旋转后每一个像素块 p o p_o 都有值,我们可以根据旋转方程倒推原始图象中对应 p o p_o p i p_i ,再将 p i p_i 的值赋予 p o p_o
x i = x o cos θ + y o sin θ y i = x o sin θ + y o cos θ x_i = x_o\cos \theta + y_o\sin \theta \\ y_i = -x_o\sin \theta + y_o\cos \theta

但是我们要注意,虽然每一个像素都有值,但是对于坐标取整导致的问题仍然存在,始终有部分像素传递不过去。

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()

猜你喜欢

转载自blog.csdn.net/white_156/article/details/104417955