精华内容
下载资源
问答
  • 这是填坑篇,之前写的图片旋转程序把图片变成了桌布,几个世纪后,在一个月黑风高的夜晚,我灵光乍现,何不试试双线性插值?先上代码和效果图。1 #!/usr/bin/env python32 #-*-coding:utf-8-*-3 """4 双线性插值参考...

    这是填坑篇,之前写的图片旋转程序把图片变成了桌布,几个世纪后,在一个月黑风高的夜晚,我灵光乍现,何不试试双线性插值?

    先上代码和效果图。

    1 #!/usr/bin/env python3

    2 #-*-coding:utf-8-*-

    3 """

    4 双线性插值参考资料: 双线性插值原理及Python实现 - Jinglever https://www.jianshu.com/p/29e5c84ea5395

    6 如果出现错误:...If you are on Ubuntu or Debian, install libgtk2.0-dev and pkg-config7 执行 pip3 install opencv-contrib-python8 """

    9 importnumpy as np10 #np.set_printoptions(suppress=True) # 关闭科学计数法

    11 importcv212 importos13

    14

    15 #旋转矩阵R

    16 ANGLE = 30 #(dim=°)

    17 assert 0 < ANGLE < 90 #目前限制这个旋转范围,原因是y1, y2, y3, y4上下关系根据角度变化

    18 alpha = ANGLE/360*2*np.pi19 R_rev = np.matrix([[np.cos(alpha), np.sin(alpha)], #逆向映射推导的旋转矩阵

    20 [-np.sin(alpha), np.cos(alpha)]])21 print(R_rev)22

    23 #重设图片大小

    24 WIDTH, HEIGHT = 640, 480

    25

    26 img = cv2.imread("timg.jpg")27 img =cv2.resize(img, (WIDTH, HEIGHT))28 #img_gray = np.float32(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY))

    29 img =np.float32(img)30 print(img.shape)31

    32 #假设已经得到旋转后的图片,利用图片边框画出图片的矩形,在矩形内遍历坐标就是图片各个像素点的坐标

    33 #注意旋转角度超过90度后边框线的上下关系会发生变化,待改进……

    34 x = np.arange(np.abs(WIDTH*np.cos(alpha)) + np.abs(HEIGHT*np.sin(alpha)), dtype=np.int32)35 y1 = lambda x: (- x*np.tan(alpha)).astype(np.int32)36 y2 = lambda x: (y1(x) + HEIGHT/np.cos(alpha)).astype(np.int32)37 y3 = lambda x: (x/np.tan(alpha)).astype(np.int32)38 y4 = lambda x: (y3(x) - WIDTH/np.sin(alpha)).astype(np.int32)39 #用矩形下面2条线(的最大值)确定y坐标最小值,上面2条线(的最小值)确定y坐标最大值

    40 y_min = np.max(np.concatenate((y1(x).reshape(1, -1), y4(x).reshape(1, -1))), axis=0)41 y_max = np.min(np.concatenate((y2(x).reshape(1, -1), y3(x).reshape(1, -1))), axis=0)42 #计算旋转后图片各像素点坐标

    43 pre_index = [np.array((yi, xi)).reshape(-1, 1) for xi in x for yi in range(y_min[xi], y_max[xi]+1)]44

    45 ori_index = np.array(list(map(R_rev.dot, pre_index))).reshape(-1, 2) #坐标变换到原图

    46 hs_p, ws_p = np.hsplit(ori_index, 2) #分离y, x坐标

    47

    48 ws_p = np.clip(ws_p, 0, WIDTH-1) #限制坐标最值防止越界

    49 hs_p = np.clip(hs_p, 0, HEIGHT-1)50

    51 ws_0 = np.clip(np.floor(ws_p), 0, WIDTH - 2).astype(np.int) #找出每个投影点在原图的近邻点坐标

    52 hs_0 = np.clip(np.floor(hs_p), 0, HEIGHT - 2).astype(np.int)53 ws_1 = ws_0 + 1

    54 hs_1 = hs_0 + 1

    55

    56 f_00 = img[hs_0, ws_0, :].T #四个临近点的像素值

    57 f_01 =img[hs_0, ws_1, :].T58 f_10 =img[hs_1, ws_0, :].T59 f_11 =img[hs_1, ws_1, :].T60

    61 w_00 = ((hs_1 - hs_p) * (ws_1 - ws_p)).T #计算权重

    62 w_01 = ((hs_1 - hs_p) * (ws_p -ws_0)).T63 w_10 = ((hs_p - hs_0) * (ws_1 -ws_p)).T64 w_11 = ((hs_p - hs_0) * (ws_p -ws_0)).T65

    66 pixels = (f_00 * w_00).T + (f_01 * w_01).T + (f_10 * w_10).T + (f_11 * w_11).T #计算目标像素值

    67

    68 y_new, x_new = np.hsplit(np.array(pre_index).reshape(-1, 2), 2) ## 分离y, x坐标

    69 y_new = y_new - np.min(y_new) #y坐标平移,防止图片旋转后被窗口切分

    70

    71 h, w = np.max(y_new), np.max(x_new) #旋转后画布大小

    72 #像素映射 原始→新图

    73 new_img = np.zeros((h+1, w+1, img.shape[2])) #(H, W, C)

    74 new_img[y_new, x_new, :] = pixels #填充像素

    75

    76 cv2.imwrite('./AffinedImg.jpg', new_img, [int(cv2.IMWRITE_JPEG_QUALITY),95])77 #显示图片

    78 cv2.imshow('img', np.array(new_img, dtype=np.uint8))79 cv2.waitKey(0)80 cv2.destroyAllWindows()

    原图见入坑篇。

    下面是运行结果,这次我换成了彩色的:

    c9b4cecc805732cac0a8bddfc05a5fb2.png

    双线性插值常用于图像的比例缩放,基本原理很容易搜索到,这里就不多说了,重点讲一下怎么把它应用到图像旋转上来。

    假设输入图片是 input image,输出图片是 output image,首先回顾一下双线性插值的思路:坐标的变换是反着来的,从 output image 到 input image。即 output image 对应的整数坐标,缩放变换到 input image 后,变成浮点数坐标,然后取它4个角上的点,计算浮点数坐标的颜色,填充到 output image 对应的坐标那里。

    再说回图像旋转,之前出现黑点就是因为图像的变换是从 input image 到 output image,即每个 input image 的像素坐标用旋转矩阵算到 output image 上,然后把浮点数直接量化成整数,这样就引入了量化误差,个别输出的坐标就错位了,导致有黑点(本来在黑点位置的像素因为坐标错位到别的地方去了,黑点那里就没有颜色数据了)。

    应用双线性插值的解决思路:先得到 output image 对应的整数坐标,变换到 input image 后,变成浮点数坐标,然后取它4个角上的点,计算浮点数坐标的颜色,填充到 output image 对应的坐标那里。(跟上面那句一样)

    那么,实现过程就分为以下几步:

    1. 获取 output image 对应各个像素点坐标。

    1) 假设已经得到 output image,这张图片是旋转一定角度的,俗话说就是斜着的,但是坐标系是正着的,怎么得到像素坐标?

    2. 坐标映射:使用反着转的旋转矩阵(R_rev)把 output image 的坐标转到 input image 上,这个结果算出来是浮点数。

    3. 双线性插值:取浮点数坐标4个角上的点,计算浮点数坐标的颜色,然后填充回 output image。大功告成!

    首先回答问题 1) :

    我使用了一个很简单的方法,就是靠图像边框作为边界,框出图像的矩形区域,遍历里面的所有点。

    6d9b3c895754e3d6f7f8b7d2b1236479.png

    $y_{1}=-x\cdot \tan \left ( \alpha  \right )$

    $y_{2}=-x\cdot \tan \left ( \alpha  \right ) + \frac {HEIGHT}{\cos \left ( \alpha  \right )}$

    $y_{3}=\frac{x}{\tan \left ( \alpha  \right )}$

    $y_{4}=\frac{x}{\tan \left ( \alpha  \right )}-\frac{WIDTH}{\sin \left ( \alpha  \right )}$

    上图绘制了y1, y2, y3, y4四条直线,注意图片显示的坐标,y轴正方向朝下。如图所示,y1, y2, y3, y4是图片的边框线,标号是我自己随便标的,如果旋转角度在90度内,边框线的上下关系不变(y2, y3在上,y1, y4在下,注意y轴正方向朝下)。这也就是现在这个程序只能实现90度以内旋转的原因,如果要继续旋转,例如旋转120度时,就变成 y1, y3 在上,y2, y4 在下,需要修改程序。

    然后是遍历图片坐标:

    1d71a0a77340b5557d1755010396e488.png

    如图所示,从点 (0, 0) 开始,按照箭头方向逐列遍历图片坐标,保存到 pre_index 中。 对应代码:(理解注释里的上下关系的时候,仍然要记得y轴正方向朝下!)

    #假设已经得到旋转后的图片,利用图片边框画出图片的矩形,在矩形内遍历坐标就是图片各个像素点的坐标#注意旋转角度超过90度后边框线的上下关系会发生变化,待改进……

    x = np.arange(np.abs(WIDTH*np.cos(alpha)) + np.abs(HEIGHT*np.sin(alpha)), dtype=np.int32)

    y1= lambda x: (- x*np.tan(alpha)).astype(np.int32)

    y2= lambda x: (y1(x) + HEIGHT/np.cos(alpha)).astype(np.int32)

    y3= lambda x: (x/np.tan(alpha)).astype(np.int32)

    y4= lambda x: (y3(x) - WIDTH/np.sin(alpha)).astype(np.int32)#用矩形下面2条线(的最大值)确定y坐标最小值,上面2条线(的最小值)确定y坐标最大值

    y_min = np.max(np.concatenate((y1(x).reshape(1, -1), y4(x).reshape(1, -1))), axis=0)

    y_max= np.min(np.concatenate((y2(x).reshape(1, -1), y3(x).reshape(1, -1))), axis=0)#计算旋转后图片各像素点坐标

    pre_index = [np.array((yi, xi)).reshape(-1, 1) for xi in x for yi in range(y_min[xi], y_max[xi]+1)]

    到这里第1步就完成了。

    然后是第2步,坐标映射。

    #R = np.matrix([[np.cos(alpha), -np.sin(alpha)],#[np.sin(alpha), np.cos(alpha)]])

    R_rev = np.matrix([[np.cos(alpha), np.sin(alpha)], #逆向映射推导的旋转矩阵

    [-np.sin(alpha), np.cos(alpha)]])

    按照推导正向旋转矩阵的方法反推逆向旋转矩阵,就可以得到上面的结果。如果仍然难以理解,就当做反转(alpha = -alpha)

    ori_index = np.array(list(map(R_rev.dot, pre_index))).reshape(-1, 2) #坐标变换到原图

    ori_index 里的坐标全部是根据 pre_index 计算来的,并不是从原图上面取点。这里计算出来的 ori_index 数据类型是浮点数。

    然后是第3步,双线性插值和像素填充。

    从 ori_index 开始直到计算出来 pixels 就是双线性插值的过程了,实现原理可以参考一下参考资料。

    之后是像素填充:

    y_new, x_new = np.hsplit(np.array(pre_index).reshape(-1, 2), 2) ## 分离y, x坐标

    y_new = y_new - np.min(y_new) #y坐标平移,防止图片旋转后被窗口切分

    h, w= np.max(y_new), np.max(x_new) #旋转后画布大小#像素映射 原始→新图

    new_img = np.zeros((h+1, w+1, img.shape[2])) #(H, W, C)

    new_img[y_new, x_new, :] = pixels #填充像素

    需要注意的是,旋转后的图片有一部分的坐标值是负值,实际显示的时候如果输入负坐标,图片会被分开显示,所以把旋转后的图片朝y轴正方向平移,移到所有点坐标值都大于0的地方。

    现在 pixels 里已经计算出来旋转后图片所有点的像素值,像素点数据的排列方向和 pre_index 是相同的,所以直接把对应的点赋值就可以了。

    最后的图片就是 new_img,插值效果还是很不错的。;-)

    参考资料:

    展开全文
  • 实现图像旋转

    2021-09-18 17:50:25
    图像旋转原理 OpenCV中的图像旋转 OpenCV主要使用getRotationMatrix2D()来得到变换矩阵(getRotationMatrix2D的计算方式与上一节的推导一致,大家可以参看函数解释推导一下),再使用warpAffine()来实现图像旋转...

    在这里插入图片描述

    图像旋转原理

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

    OpenCV中的图像旋转

    OpenCV主要使用getRotationMatrix2D()来得到变换矩阵(getRotationMatrix2D的计算方式与上一节的推导一致,大家可以参看函数解释推导一下),再使用warpAffine()来实现图像旋转。代码如下

    def rotate(image, angle, center=None, scale=1.0):
        # grab the dimensions of the image
        (h, w) = image.shape[:2]
    
        # if the center is None, initialize it as the center of
        # the image
        if center is None:
            center = (w // 2, h // 2)
    
        # perform the rotation
        M = cv2.getRotationMatrix2D(center, angle, scale)
        rotated = cv2.warpAffine(image, M, (w, h))
    
        # return the rotated image
        return rotated
    

    现在我们来旋转一只小鸟。
    在这里插入图片描述

    使用OpenCV的方法旋转结果如下所示:
    在这里插入图片描述
    在这里插入图片描述
    可以看到当旋转矩形涂向师,旋转后原图大量信息丢失了。在有些时候我们并不想要这种信息的丢失(比如在深度学习数据增强的时候)。

    现在我改写一下上面的代码,来使矩形图片可以正确的旋转,不丢失信息。代码如下:

    def rotate_bound(image, angle):
        # grab the dimensions of the image and then determine the
        # center
        (h, w) = image.shape[:2]
        (cX, cY) = (w / 2, h / 2)
    
        # grab the rotation matrix (applying the negative of the
        # angle to rotate clockwise), then grab the sine and cosine
        # (i.e., the rotation components of the matrix)
        M = cv2.getRotationMatrix2D((cX, cY), angle, 1.0)
        cos = np.abs(M[0, 0])
        sin = np.abs(M[0, 1])
    
        # compute the new bounding dimensions of the image
        nW = int((h * sin) + (w * cos))
        nH = int((h * cos) + (w * sin))
    
        # adjust the rotation matrix to take into account translation
        M[0, 2] += (nW / 2) - cX
        M[1, 2] += (nH / 2) - cY
    
        # perform the actual rotation and return the image
        return cv2.warpAffine(image, M, (nW, nH))
    

    在计算出旋转变换矩阵M后,计算一下可以正常包含旋转后图像的外接矩形框的长和宽,然后计算外接矩形框的中心和原矩形框中心的距离,最后将旋转后的图像中心移到新的外接矩形框的中心。

    结果如下:

    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • 图像旋转的插值算法

    2021-01-14 16:48:17
    图像处理的过程中,常常出现由于对应点的缺失而在变换后产生一些有规律的空缺点。为了弥补这种缺陷,我们使用几种不同的插值算法来改进。本文注重于双线性插值、双三次插值和样条插值的具体推出过程,由于代码水平...

    1.图像旋转的原理

    考虑直角坐标系 ( x , y ) (x,y) (x,y) 中一点 ,用极坐标来表示:

    { x = R cos ⁡ θ y = R sin ⁡ θ \begin{cases}x=R\cos\theta\\y=R\sin\theta\end{cases} {x=Rcosθy=Rsinθ

    将(x,y)按顺时针方向旋转角度 α \alpha α得到 ( x ′ y ′ ) (x'y') (xy),则有:

    { x ′ = R cos ⁡ ( θ − α ) = R cos ⁡ θ cos ⁡ α + R sin ⁡ θ sin ⁡ α = x cos ⁡ α + y sin ⁡ α y ′ = R sin ⁡ ( θ − α ) = − R cos ⁡ θ sin ⁡ α + R sin ⁡ θ cos ⁡ α = − x sin ⁡ α + y cos ⁡ α \begin{cases}x'=R\cos(\theta-\alpha)=R\cos\theta\cos\alpha+R\sin\theta\sin\alpha=x\cos\alpha+y\sin\alpha\\y'=R\sin(\theta-\alpha)=-R\cos\theta\sin\alpha+R\sin\theta\cos\alpha=-x\sin\alpha+y\cos\alpha\end{cases} {x=Rcos(θα)=Rcosθcosα+Rsinθsinα=xcosα+ysinαy=Rsin(θα)=Rcosθsinα+Rsinθcosα=xsinα+ycosα

    将上述变换写成矩阵形式:

    ( x ′ y ′ 1 ) = ( x y 1 ) ( cos ⁡ α − sin ⁡ α 0 sin ⁡ α cos ⁡ α 0 0 0 1 ) \begin{array}{cc}(x'&y'&1)\end{array}=\begin{array}{cc}(x&y&1)\end{array}\left(\begin{array}{cc}\cos\alpha&-\sin\alpha&0\\\sin\alpha&\cos\alpha&0\\0&0&1\end{array}\right) (xy1)=(xy1)cosαsinα0sinαcosα0001

    在计算机中,图像的原点一般位于图片的左上角,且y轴向下,而为了进行旋转,需要坐标原点移至图像中心,并使y轴向上,故需要一个变换:

    ( x ′ y ′ 1 ) = ( x y 1 ) ( 1 0 0 0 − 1 0 − 0.5 m 0.5 n 1 ) \begin{array}{cc}(x'&y'&1)\end{array}=\begin{array}{cc}(x&y&1)\end{array}\left(\begin{array}{cc}1&0&0\\0&-1&0\\-0.5m&0.5n&1\end{array}\right) (xy1)=(xy1)100.5m010.5n001

    其中m、n分别为图像的宽度和高度。
    同样在旋转后,需要将坐标系变回图像坐标,故还需:

    ( x ′ y ′ 1 ) = ( x y 1 ) ( 1 0 0 0 − 1 0 0.5 m ′ 0.5 n ′ 1 ) \begin{array}{cc}(x'&y'&1)\end{array}=\begin{array}{cc}(x&y&1)\end{array}\left(\begin{array}{cc}1&0&0\\0&-1&0\\0.5m'&0.5n'&1\end{array}\right) (xy1)=(xy1)100.5m010.5n001

    其中m、n分别为旋转后新图像的宽度和高度。
    综上,图像旋转的矩阵描述为:

    ( x ′ y ′ 1 ) = ( x y 1 ) ( 1 0 0 0 − 1 0 − 0.5 m 0.5 n 1 ) ( cos ⁡ α − sin ⁡ α 0 sin ⁡ α cos ⁡ α 0 0 0 1 ) ( 1 0 0 0 − 1 0 0.5 m ′ 0.5 n ′ 1 ) \begin{array}{cc}(x'&y'&1)\end{array}=\begin{array}{cc}(x&y&1)\end{array}\left(\begin{array}{cc}1&0&0\\0&-1&0\\-0.5m&0.5n&1\end{array}\right)\left(\begin{array}{cc}\cos\alpha&-\sin\alpha&0\\\sin\alpha&\cos\alpha&0\\0&0&1\end{array}\right)\left(\begin{array}{cc}1&0&0\\0&-1&0\\0.5m'&0.5n'&1\end{array}\right) (xy1)=(xy1)100.5m010.5n001cosαsinα0sinαcosα0001100.5m010.5n001

    同理,可以得到其逆运算的矩阵描述为:

    ( x y 1 ) = ( x ′ y ′ 1 ) ( 1 0 0 0 − 1 0 − 0.5 m ′ 0.5 n ′ 1 ) ( cos ⁡ α sin ⁡ α 0 − sin ⁡ α cos ⁡ α 0 0 0 1 ) ( 1 0 0 0 − 1 0 0.5 m 0.5 n 1 ) \begin{array}{cc}(x&y&1)\end{array}=\begin{array}{cc}(x'&y'&1)\end{array}\left(\begin{array}{cc}1&0&0\\0&-1&0\\-0.5m'&0.5n'&1\end{array}\right)\left(\begin{array}{cc}\cos\alpha&\sin\alpha&0\\-\sin\alpha&\cos\alpha&0\\0&0&1\end{array}\right)\left(\begin{array}{cc}1&0&0\\0&-1&0\\0.5m&0.5n&1\end{array}\right) (xy1)=(xy1)100.5m010.5n001cosαsinα0sinαcosα0001100.5m010.5n001

    这就是后续算法中要使用的旋转公式。

    2.前向映射

    前向是通过原图像一点的坐标计算出旋转后该点所在位置,并将其灰度值传给旋转后的点,以顺时针旋转 π 6 \frac\pi6 6π为例,用matlab实现

    degree = pi/6;
    
    img = imread('image.jpg');
    % 获得原图像尺寸和灰度值
    [m,n,o] = size(img);         
    %计算旋转后图像的大小
    m2 = ceil(abs(m*cos(degree))+abs(n*sin(degree)));
    n2 = ceil(abs(n*cos(degree))+abs(m*sin(degree)));      
    
    new_img = zeros(m2,n2,o);
    %旋转的矩阵描述
    mat_1 = [1 0 0;0 -1 0;-0.5*n 0.5*m 1];
    mat_2 = [cos(degree) -sin(degree) 0;sin(degree) cos(degree) 0;0 0 1];
    mat_3 = [1 0 0;0 -1 0;0.5*n2 0.5 *m2 1];                     
    
    for i = 1:n
        for j=1:m
            %计算新坐标
            new_coordinate = [i j 1]*mat_1*mat_2*mat_3;
            col = ceil(new_coordinate(1));
            row = ceil(new_coordinate(2));                         
            %传递灰度值
            new_img(row,col,:) = img(j,i,:);       
        end
    end
    
    figure,imshow(uint8(img));title('原图');
    figure,imshow(uint8(new_img));title('经旋转后的图像');
    

    得到的结果如下:


    可以看到图像中出现了一些有规律的噪声。这是因为算法中对变换后的坐标不一定是整数,而我们对其进行了取整操作,故部分像素就没有对应的灰度值,就显示为初始化的黑色。为了克服这样的缺点,我们将使用反向映射的方法并通过几种插值算法来弥补这些空缺。

    3.反向映射

    反向映射是从旋转后的图像出发,去寻找原图中对应的灰度值。当然原图中对应的点的坐标也不一定为整数,所以我们通过对其周围点的灰度值进行插值运算的处理来获得这一点的灰度值并赋予旋转后的图像。

    3.1双线性插值

    双线性插值利用旋转后图像中的点在原图所对应点周围四个点的数值,在两个方向分别进行线性插值来得到。
    双线性插值算法的原理示意
    记这四个点分别为 Q i j Q_{ij} Qij, ( i , j = 1 , 2 ) (i,j=1,2) (i,j=1,2),先对x轴方向做两次线性插值得到函数在 R 1 R_1 R1 R 2 R_2 R2上的值,再对 R 1 R_1 R1 R 2 R_2 R2做y轴方向的线性插值得到函数在所求点的值。有:
    在这里插入图片描述
    f ( R 1 ) ≈ x 2 − x x 2 − x 1 f ( Q 11 ) + x − x 1 x 2 − x 1 f ( Q 21 ) f(R_1)\approx\frac{x_2-x}{x_2-x_1}f(Q_{11})+\frac{x-x_1}{x_2-x_1}f(Q_{21}) f(R1)x2x1x2xf(Q11)+x2x1xx1f(Q21)

    f ( R 2 ) ≈ x 2 − x x 2 − x 1 f ( Q 12 ) + x − x 1 x 2 − x 1 f ( Q 22 ) f(R_2)\approx\frac{x_2-x}{x_2-x_1}f(Q_{12})+\frac{x-x_1}{x_2-x_1}f(Q_{22}) f(R2)x2x1x2xf(Q12)+x2x1xx1f(Q22)

    f ( P ) ≈ y 2 − y y 2 − y 1 f ( R 1 ) + y − y 1 y 2 − y 1 f ( R 2 ) ≈ ( y 2 − y ) ( x 2 − x ) ( y 2 − y 1 ) ( x 2 − x 1 ) f ( Q 11 ) + ( y 2 − y ) ( x − x 1 ) ( y 2 − y 1 ) ( x 2 − x 1 ) f ( Q 21 ) + ( y − y 1 ) ( x 2 − x ) ( y 2 − y 1 ) ( x 2 − x 1 ) f ( Q 12 ) + ( y − y 1 ) ( x − x 1 ) ( y 2 − y 1 ) ( x 2 − x 1 ) f ( Q 22 ) f(P)\approx\frac{y_2-y}{y_2-y_1}f(R_{1})+\frac{y-y_1}{y_2-y_1}f(R_{2})\approx\frac{(y_2-y)(x_2-x)}{(y_2-y_1)(x_2-x_1)}f(Q_{11})+\frac{(y_2-y)(x-x_1)}{(y_2-y_1)(x_2-x_1)}f(Q_{21})+\frac{(y-y_1)(x_2-x)}{(y_2-y_1)(x_2-x_1)}f(Q_{12})+\frac{(y-y_1)(x-x_1)}{(y_2-y_1)(x_2-x_1)}f(Q_{22}) f(P)y2y1y2yf(R1)+y2y1yy1f(R2)(y2y1)(x2x1)(y2y)(x2x)f(Q11)+(y2y1)(x2x1)(y2y)(xx1)f(Q21)+(y2y1)(x2x1)(yy1)(x2x)f(Q12)+(y2y1)(x2x1)(yy1)(xx1)f(Q22)

    具体的matlab实现如下:

    degree = pi/6;
    
    img = imread('image.jpg');
    % 获得原图像尺寸和灰度值
    [m,n,o] = size(img); 
    %计算旋转后图像的大小
    m2 = ceil(abs(m*cos(degree))+abs(n*sin(degree)));
    n2 = ceil(abs(n*cos(degree))+abs(m*sin(degree)));      
    new_img = zeros(m2,n2,o);
    %旋转逆运算的矩阵描述
    mat_1 = [1 0 0;0 -1 0;-0.5*n2 0.5*m2 1];
    mat_2 = [cos(degree) sin(degree) 0;-sin(degree) cos(degree) 0;0 0 1];
    mat_3 = [1 0 0;0 -1 0;0.5*n 0.5 *m 1];
    
    for i = 1:n2
        for j=1:m2
            %计算原图坐标
            old_coordinate = [i j 1]*mat_1*mat_2*mat_3;
            col = old_coordinate(1);
            row = old_coordinate(2);
            %防止数组越界
            if row<1 || col<1 || row>m || col>n
                new_img(j,i,:) = 0;
            else
                %取周围四点
                left = floor(col);
                right = ceil(col);
                top = floor(row);
                bottom = ceil(row);
                a = col - left;
                b = row - top;
                %利用双线性插值计算灰度值
                new_img(j,i,:) = (1-a)*(1-b)*img(top,left,:)+a*(1-b)*img(top,right,:)+...
                    (1-a)*b*img(bottom,left,:)+a*b*img(bottom,right,:);
            end        
        end
    end
    
    figure,imshow(uint8(img));title('原图');
    figure,imshow(uint8(new_img));title('经旋转后的图像');
    

    结果如下:
    在这里插入图片描述
    可以看到图像没有噪声了。但通过放大细节的对比可以看出,这样的处理让图像中的色块边界变得模糊了,为此,我们尝试通过双三次插值和样条插值的方式来处理图像。

    3.2双三次插值

    双三次插值是一种更为复杂的二维空间插值法,它的思想和双线性插值是类似的,即在两个方向分别进行插值计算,但双三次插值需要选取图像原坐标周围16个点进行来加权平均。
    由于这是一个等距结点的插值,插值函数 g ( x ) g(x) g(x)可以写成如下形式:

    g ( x ) = ∑ k c k u ( x − x k h ) g(x)=\sum_k c_ku\left(\frac{x-x_k}h\right) g(x)=kcku(hxxk)

    其中 x k x_k xk是插值结点,h是结点间距,u是我们要求的双三次插值核函数,而 c k c_k ck是依赖于样本数据的系数,我们选取适当的 c k c_k ck的值使得 g ( x ) g(x) g(x)对被插函数 f ( x ) f(x) f(x)在所有插值结点 x k x_k xk满足 g ( x k ) = f ( x k ) g(x_k)=f(x_k) g(xk)=f(xk)
    显然,插值核函数u应有如下性质:

    1. u是定义在区间 ( − 2 , − 1 ) , ( − 1 , 0 ) , ( 0 , 1 ) , ( 1 , 2 ) (-2,-1),(-1,0),(0,1),(1,2) (2,1),(1,0),(0,1),(1,2)上的分段三次函数,且在 ( − 2 , 2 ) (-2,2) (2,2)外恒为零
    2. u连续且具有连续的一阶导数
    3. u是偶函数,即u关于x=0对称
    4. u ( 0 ) = 1 u(0)=1 u(0)=1而对其余非零的整数结点n, u ( n ) = 0 u(n)=0 u(n)=0

    由性质1和性质3,u可以写成如下形式:

    u ( x ) = { A 1 ∣ x ∣ 3 + B 1 ∣ x ∣ 2 + C 1 ∣ x ∣ + D 1 , 0 < ∣ x ∣ < 1 A 2 ∣ x ∣ 3 + B 2 ∣ x ∣ 2 + C 2 ∣ x ∣ + D 2 , 1 < ∣ x ∣ < 2 0 , ∣ x ∣ > 2 u(x)=\begin{cases}A_1|x|^3+B_1|x|^2+C_1|x|+D_1,&0<|x|<1\\ A_2|x|^3+B_2|x|^2+C_2|x|+D_2,&1<|x|<2\\ 0,&|x|>2\end{cases} u(x)=A1x3+B1x2+C1x+D1,A2x3+B2x2+C2x+D2,0,0<x<11<x<2x>2

    再由性质2中连续性的要求,应有:

    { 1 = u ( 0 ) = D 1 0 = u ( 1 − ) = A 1 + B 1 + C 1 + D 1 0 = u ( 1 + ) = A 2 + B 2 + C 2 + D 2 0 = u ( 2 − ) = 8 A 2 + 4 B 2 + 2 C 2 + D 2 − C 1 = u ′ ( 0 − ) = u ′ ( 0 + ) = C 1 3 A 1 + 2 B 1 + C 1 = u ′ ( 1 − ) = u ′ ( 1 + ) = 3 A 2 + 2 B 2 + C 2 12 A 2 + 4 B 2 + C 2 = u ′ ( 2 + ) = 0 \begin{cases}1=u(0)=D_1\\ 0=u(1^-)=A_1+B_1+C_1+D_1\\ 0=u(1^+)=A_2+B_2+C_2+D_2\\ 0=u(2^-)=8A_2+4B_2+2C_2+D_2\\ -C_1=u'(0^-)=u'(0^+)=C_1\\ 3A_1+2B_1+C_1=u'(1^-)=u'(1^+)=3A_2+2B_2+C_2\\ 12A_2+4B_2+C_2=u'(2^+)=0 \end{cases} 1=u(0)=D10=u(1)=A1+B1+C1+D10=u(1+)=A2+B2+C2+D20=u(2)=8A2+4B2+2C2+D2C1=u(0)=u(0+)=C13A1+2B1+C1=u(1)=u(1+)=3A2+2B2+C212A2+4B2+C2=u(2+)=0

    这里只有七个约束条件,而有八个未知系数,这里我们设 A 2 = a A_2=a A2=a(后面我们会看到 a a a的值的选取会影响到 f ( x ) f(x) f(x) g ( x ) g(x) g(x)的Taylor展式的接近程度),解出u得

    u ( x ) = { ( a + 2 ) ∣ x ∣ 3 − ( a + 3 ) ∣ x ∣ 2 + 1 , 0 < ∣ x ∣ < 1 a ∣ x ∣ 3 − 5 a ∣ x ∣ 2 + 8 a ∣ x ∣ − 4 a , 1 < ∣ x ∣ < 2 0 , ∣ x ∣ > 2 u(x)=\begin{cases}(a+2)|x|^3-(a+3)|x|^2+1,&0<|x|<1\\ a|x|^3-5a|x|^2+8a|x|-4a,&1<|x|<2\\ 0,&|x|>2\end{cases} u(x)=(a+2)x3(a+3)x2+1,ax35ax2+8ax4a,0,0<x<11<x<2x>2

    再考虑性质4及 x j − x k = ( j − k ) h x_j-x_k=(j-k)h xjxk=(jk)h,故对 g ( x ) g(x) g(x)的上述形式有:

    g ( x j ) = ∑ k c k u ( j − k ) = c j g(x_j)=\sum_kc_ku(j-k)=c_j g(xj)=kcku(jk)=cj

    而由插值条件 g ( x j ) = f ( x j ) g(x_j)=f(x_j) g(xj)=f(xj),故 f ( x j ) = c j f(x_j)=c_j f(xj)=cj c k c_k ck的值就应取为 f ( x k ) f(x_k) f(xk)

    x x x位于两个连续的结点 x j x_j xj x j + 1 x_{j+1} xj+1之间时,记 s = x − x j h , 0 < s < 1 s=\frac{x-x_j}h,0<s<1 s=hxxj,0<s<1,

    x − x k h = x − x j + x j − x k h = s + j − k \frac{x-x_k}h=\frac{x-x_j+x_j-x_k}h=s+j-k hxxk=hxxj+xjxk=s+jk,故

    g ( x ) = ∑ k c k u ( s + j − k ) g(x)=\sum_kc_ku(s+j-k) g(x)=kcku(s+jk)

    而u在 ( − 2 , 2 ) (-2,2) (2,2)之外为0,故

    g ( x ) = c j − 1 u ( s + 1 ) + c j u ( s ) + c j + 1 u ( s − 1 ) + c j + 2 u ( s − 2 ) g(x)=c_{j-1}u(s+1)+c_ju(s)+c_{j+1}u(s-1)+c_{j+2}u(s-2) g(x)=cj1u(s+1)+cju(s)+cj+1u(s1)+cj+2u(s2)

    代入u(x)上述的表达式得

    { u ( s + 1 ) = a s 3 − 2 a s 2 + a s u ( s ) = ( a + 2 ) s 3 − ( a + 3 ) s 2 + 1 u ( s − 1 ) = − ( a + 2 ) s 3 + ( 2 a + 3 ) s 2 − a s u ( s − 2 ) = − a s 3 + a s 2 \begin{cases}u(s+1)=as^3-2as^2+as\\u(s)=(a+2)s^3-(a+3)s^2+1\\u(s-1)=-(a+2)s^3+(2a+3)s^2-as\\u(s-2)=-as^3+as^2\end{cases} u(s+1)=as32as2+asu(s)=(a+2)s3(a+3)s2+1u(s1)=(a+2)s3+(2a+3)s2asu(s2)=as3+as2

    ⇒ g ( x ) = − [ a ( c j + 2 − c j − 1 ) + ( a + 2 ) ( c j + 1 − c j ) ] s 3 + [ 2 a ( c j + 1 − c j − 1 ) + 3 ( c j + 1 − c j ) + a ( c j + 2 − c j ) ] s 2 − a ( c j + 1 − c j − 1 ) s + c j \Rightarrow g(x)=-[a(c_{j+2}-c_{j-1})+(a+2)(c_{j+1}-c_j)]s^3+[2a(c_{j+1}-c{j-1})+3(c_{j+1}-c_j)+a(c_j+2-c_j)]s^2-a(c_{j+1}-c_{j-1})s+c_j g(x)=[a(cj+2cj1)+(a+2)(cj+1cj)]s3+[2a(cj+1cj1)+3(cj+1cj)+a(cj+2cj)]s2a(cj+1cj1)s+cj

    假设 f ( x ) f(x) f(x) [ x j , x j + 1 ] [x_j,x_{j+1}] [xj,xj+1]内有连续至三阶的导数,则由Taylor展式有

    c j + 1 = f ( x j + 1 ) = f ( x j ) + f ′ ( x j ) h + f ′ ′ ( x j ) h 2 / 2 + 0 ( h 3 ) c j + 2 = f ( x j + 2 ) = f ( x j ) + 2 h f ′ ( x j ) + 2 h 2 f ′ ′ ( x j ) + 0 ( h 3 ) c j − 1 = f ( x j + 1 ) = f ( x j ) − f ′ ( x j ) h + f ′ ′ ( x j ) h 2 / 2 + 0 ( h 3 ) c_{j+1}=f(x_{j+1})=f(x_j)+f'(x_j)h+f''(x_j)h^2 /2+0(h^3)\\ c_{j+2}=f(x_{j+2})=f(x_j)+2hf'(x_j)+2h^{2} f''(x_j)+0(h^3)\\ c_{j-1}=f(x_{j+1})=f(x_j)-f'(x_j)h+f''(x_j)h^2 /2+0(h^3) cj+1=f(xj+1)=f(xj)+f(xj)h+f(xj)h2/2+0(h3)cj+2=f(xj+2)=f(xj)+2hf(xj)+2h2f(xj)+0(h3)cj1=f(xj+1)=f(xj)f(xj)h+f(xj)h2/2+0(h3)

    代入 g ( x ) g(x) g(x)的上述表达式得:

    g ( x ) = − ( 2 a + 1 ) [ 2 h f ′ ( x j ) + h 2 f ′ ′ ( x j ) ] s 3 + [ ( 6 a + 3 ) h f ′ ( x j ) + ( 4 a + 3 ) h 2 f ′ ′ ( x j ) / 2 ] s 2 − 2 a h f ′ ( x j ) s + f ( x j ) + 0 ( h 3 ) g(x)=-(2a+1)[2hf'(x_j)+h^2 f''(x_j)]s^3+[(6a+3)hf'(x_j)+(4a+3)h^2 f''(x_j)/2]s^2-2ahf'(x_j)s+f(x_j)+0(h^3) g(x)=(2a+1)[2hf(xj)+h2f(xj)]s3+[(6a+3)hf(xj)+(4a+3)h2f(xj)/2]s22ahf(xj)s+f(xj)+0(h3)

    而又 x − x j = s h x-x_j=sh xxj=sh,对 f ( x ) f(x) f(x)做Taylor展开有

    f ( x ) = f ( x j ) + s h f ′ ( x j ) + s 2 h 2 f ′ ′ ( x j ) / 2 + 0 ( h 3 ) f(x)=f(x_j)+shf'(x_j)+s^2h^2f''(x_j)/2+0(h^3) f(x)=f(xj)+shf(xj)+s2h2f(xj)/2+0(h3)

    两式做差得

    f ( x ) − g ( x ) = ( 2 a + 1 ) [ 2 h f ′ ( x j ) + h 2 f ′ ′ ( x j ) ] s 3 − ( 2 a + 1 ) [ 3 h f ′ ( x j ) + h 2 f ′ ′ ( x j ) ] s 2 + ( 2 a + 1 ) s h f ′ ( x j ) + 0 ( h 3 ) f(x)-g(x)=(2a+1)[2hf'(x_j)+h^2f''(x_j)]s^3-(2a+1)[3hf'(x_j)+h^2f''(x_j)]s^2+(2a+1)shf'(x_j)+0(h^3) f(x)g(x)=(2a+1)[2hf(xj)+h2f(xj)]s3(2a+1)[3hf(xj)+h2f(xj)]s2+(2a+1)shf(xj)+0(h3)

    可以看出,如果我们取 a = − 1 2 a=-\frac12 a=21,则插值误差以 h 3 h^3 h3阶趋于零,而取其他值都没有这么好的逼近效果,故就令 A 2 = a = − 1 2 A_2=a=-\frac12 A2=a=21

    代入得插值核函数u为:

    u ( x ) = { 3 2 ∣ x ∣ 3 − 5 2 ∣ x ∣ 2 + 1 , 0 < ∣ x ∣ < 1 − 1 2 ∣ x ∣ 3 + 5 2 ∣ x ∣ 2 − 4 ∣ x ∣ + 2 , 1 < ∣ x ∣ < 2 0 , ∣ x ∣ > 2 u(x)=\begin{cases}\frac32|x|^3-\frac52|x|^2+1,&0<|x|<1\\ -\frac12|x|^3+\frac52|x|^2-4|x|+2,&1<|x|<2\\ 0,&|x|>2\end{cases} u(x)=23x325x2+1,21x3+25x24x+2,0,0<x<11<x<2x>2

    具体的matlab实现如下:

    degree = pi/6;
    
    img = imread('image.jpg');
    img = double(img);
    % 获得原图像尺寸和灰度值
    [m,n,o] = size(img); 
    %计算旋转后图像的大小
    m2 = ceil(abs(m*cos(degree))+abs(n*sin(degree)));
    n2 = ceil(abs(n*cos(degree))+abs(m*sin(degree)));      
    new_img = zeros(m2,n2,o);
    %旋转逆运算的矩阵描述
    mat_1 = [1 0 0;0 -1 0;-0.5*n2 0.5*m2 1];
    mat_2 = [cos(degree) sin(degree) 0;-sin(degree) cos(degree) 0;0 0 1];
    mat_3 = [1 0 0;0 -1 0;0.5*n 0.5 *m 1];
    
    for w = 1:n2
        for h=1:m2
            %计算原图坐标
            old_coordinate = [w h 1]*mat_1*mat_2*mat_3;
            col = old_coordinate(1);
            row = old_coordinate(2);
            %防止数组越界
            if row<2 || col<2 || row>m-2 || col>n-2
                new_img(h,w,:) = 0;
            else
                %取周围点,从左上角开始
                i = floor(col) - 1;
                j = floor(row) - 1;
                u = col - i;
                v = row - j;
                %利用插值核函数计算权值
                A = [weight(v),weight(v-1),weight(v-2),weight(v-3)];
                C = [weight(u);weight(u-1);weight(u-2);weight(u-3)];
                %对三个颜色通道分别进行赋值
                for d = 1:o 
                   B = img(j:j+3,i:i+3,d); 
                   new_img(h,w,d) = A*B*C;    
                end
                
            end        
        end
    end
    
    figure,imshow(uint8(img));title('原图');
    figure,imshow(uint8(new_img));title('经旋转后的图像');
    

    其中weight是插值核函数:

    function [w] = weight(a)
    x = abs(a);
    if x <= 1
        w = 1-2.5*x^2+1.5*x^3;
    elseif 1 < x && x < 2
        w = 2-4*x+2.5*x^2-0.5*x^3;
    else
        w = 0;
    end
    end
    

    结果如图:
    在这里插入图片描述
    与双线性插值的结果相比,图像的灰度连续性更好,色块边界更加清晰。

    3.3样条插值

    按照双线性插值的思想,利用样条插值法也可以计算出旋转后图片中每个点在原图中对应的周围16个点的权重,并加权计算出该点的灰度值。
    我们利用三弯矩法来构造样条函数。
    记样条函数为 S 3 ( x ) , S ′ ′ ( x i ) = M i , i = 1 , 2 , 3 , 4 S_3(x),S''(x_i)=M_i,i=1,2,3,4 S3(x),S(xi)=Mi,i=1,2,3,4
    注意到 S 3 ′ ′ ( x ) S_3''(x) S3(x)是分段函数,则有

    S 3 ′ ′ ( x ) = M i − 1 x i − x Δ x i − 1 + M i x − x i − 1 Δ x i − 1 , x i − 1 < x < x i S_3''(x)=M_{i-1}\frac{x_i-x}{\Delta x_{i-1}}+M_{i}\frac{x-x_{i-1}}{\Delta x_{i-1}},x_{i-1}<x<x_i S3(x)=Mi1Δxi1xix+MiΔxi1xxi1,xi1<x<xi

    对上式积分两次,并由插值条件 S 3 ( x i ) = y i S_3(x_i)=y_i S3(xi)=yi

    S 3 ( x ) = M i − 1 ( x i − x ) 3 6 Δ x i − 1 + M i ( x − x i − 1 ) 3 6 Δ x i − 1 + ( y i − 1 − M i − 1 Δ x i − 1 2 6 ) x i − x Δ x i − 1 + ( y i − M i Δ x i − 1 2 6 ) x − x i − 1 Δ x i − 1 , x i − 1 < x < x i S_3(x)=M_{i-1}\frac{(x_i-x)^3}{6\Delta x_{i-1}}+M_{i}\frac{(x-x_{i-1})^3}{6\Delta x_{i-1}}+\left(y_{i-1}-\frac{M_{i-1}\Delta x_{i-1}^2}6\right)\frac{x_i-x}{\Delta x_{i-1}}+\left(y_{i}-\frac{M_{i}\Delta x_{i-1}^2}6\right)\frac{x-x_{i-1}}{\Delta x_{i-1}},x_{i-1}<x<x_i S3(x)=Mi16Δxi1(xix)3+Mi6Δxi1(xxi1)3+(yi16Mi1Δxi12)Δxi1xix+(yi6MiΔxi12)Δxi1xxi1,xi1<x<xi
    对上式求导得
    S 3 ′ ( x ) = − M i − 1 ( x i − x ) 2 2 Δ x i − 1 + M i ( x − x i − 1 ) 2 2 Δ x i − 1 + y i − y i − 1 Δ x i − 1 − M i − m i − 1 6 Δ x i − 1 , x i − 1 < x < x i S_3'(x)=-M_{i-1}\frac{(x_i-x)^2}{2\Delta x_{i-1}}+M_{i}\frac{(x-x_{i-1})^2}{2\Delta x_{i-1}}+\frac{y_i-y_{i-1}}{\Delta x_{i-1}}-\frac{M_i-m_{i-1}}6\Delta x_{i-1},x_{i-1}<x<x_i S3(x)=Mi12Δxi1(xix)2+Mi2Δxi1(xxi1)2+Δxi1yiyi16Mimi1Δxi1,xi1<x<xi
    由一阶导数连续的要求应有 S 3 ′ ( x i − ) = S 3 ′ ( x i + ) , i = 1 , 2 , 3 , 4 S_3'(x_i^-)=S_3'(x_i^+),i=1,2,3,4 S3(xi)=S3(xi+),i=1,2,3,4,有
    λ i M i − 1 + 2 M i + ( 1 − λ i ) M i + 1 = d i , i = 1 , 2 , 3 , 4 \lambda_iM_{i-1}+2M_i+(1-\lambda_i)M_{i+1}=d_i,i=1,2,3,4 λiMi1+2Mi+(1λi)Mi+1=di,i=1,2,3,4
    其中
    d i = 6 ( y i + 1 − y i Δ x i − y i − y i − 1 Δ x i − 1 ) 1 Δ x i − 1 + Δ x i = 6 f [ x i − 1 , x i , x i + 1 ] , λ i = Δ x i − 1 Δ x i − 1 + Δ x i d_i=6\left(\frac{y_{i+1}-y_i}{\Delta x_i}-\frac{y_i-y_{i-1}}{\Delta x_{i-1}}\right)\frac1{\Delta x_{i-1}+\Delta x_{i}}=6f[x_{i-1},x_i,x_{i+1}],\lambda_i=\frac{\Delta x_{i-1}}{\Delta x_{i-1}+\Delta x_{i}} di=6(Δxiyi+1yiΔxi1yiyi1)Δxi1+Δxi1=6f[xi1,xi,xi+1],λi=Δxi1+ΔxiΔxi1
    我们考虑自然样条,即要求 M 1 = M 4 = 0 M_1=M_4=0 M1=M4=0,再利用方程组求解即可。具体的代码实现为:

    function [M] = bspline(X,Y)
    n = length(X);
    %差商表A,系数矩阵D和lambda的取值g
    A = zeros(n,n);A(:,1) = Y';D = zeros(n-2,n-2);g = zeros(n-2,1);
    %计算差商
    for j=2:n
        for i=j:n
            A(i,j) = (A(i,j-1)-A(i-1,j-1))/(X(i)-X(i-j+1));
        end
    end
    %计算结点间距
    h = zeros(n-1,1);
    for i = 1:n-1
        h(i) = X(i+1)-X(i);
    end
    %计算系数矩阵和lambda
    for i = 1:n-2
        D(i,i) = 2;
        g(i,1) = (6/(h(i+1)+h(i)))*(A(i+2,2)-A(i+1,2));
    end
    for i = 2:n-2
        D(i-1,i) = h(i)/(h(i-1)+h(i));
        D(i,i-1) = h(i)/(h(i)+h(i+1));
    end
    %计算M的取值
    M = D\g;
    M = [0;M;0];
    end
    

    由于我们考虑的旋转后图片中每个点在原图中对应的点总是位于第二个和第三个插值结点之间,且是间距为1的等距结点,故有
    S 3 ( x ) = M 2 ( x 3 − x ) 3 6 + M 3 ( x − x 2 ) 3 6 + ( y 2 − M 2 / 6 ) ( x 3 − x ) + ( y 3 − M 3 / 6 ) ( x − x 2 ) S_3(x)=M_2\frac{(x_3-x)^3}6+M_3\frac{(x-x_2)^3}6+(y_2-M_2/6)(x_3-x)+(y_3-M_3/6)(x-x_2) S3(x)=M26(x3x)3+M36(xx2)3+(y2M2/6)(x3x)+(y3M3/6)(xx2)
    这就是我们要找的计算权值的函数。
    具体的matlab实现如下:

    degree = pi/6;
    
    img = imread('image.jpg');
    img = double(img);
    % 获得原图像尺寸和灰度值
    [m,n,o] = size(img); 
    %计算旋转后图像的大小
    m2 = ceil(abs(m*cos(degree))+abs(n*sin(degree)));
    n2 = ceil(abs(n*cos(degree))+abs(m*sin(degree)));      
    new_img = zeros(m2,n2,o);
    %旋转逆运算的矩阵描述
    mat_1 = [1 0 0;0 -1 0;-0.5*n2 0.5*m2 1];
    mat_2 = [cos(degree) sin(degree) 0;-sin(degree) cos(degree) 0;0 0 1];
    mat_3 = [1 0 0;0 -1 0;0.5*n 0.5 *m 1];
    
    for w = 1:n2
        for h=1:m2
            %计算原图坐标
            old_coordinate = [w h 1]*mat_1*mat_2*mat_3;
            col = old_coordinate(1);
            row = old_coordinate(2);
            %防止数组越界
            if row<2 || col<2 || row>m-2 || col>n-2
                new_img(h,w,:) = 0;
            else
                %取周围点,从左上角开始
                i = floor(col)-1;
                j = floor(row)-1;
                u = col-i-1;
                v = row-j-1;
                %对每行四个点进行一次样条插值
                for d = 1:o  
                    Y = zeros(1,4);
                    for k = 0:3
                       M = bspline([1 2 3 4],[img(j+k,i,d) img(j+k,i+1,d) img(j+k,i+2,d) img(j+k,i+3,d)]);
                       Y(k+1) = M(2)/6*(1-u)^3+M(3)/6*u^3+(img(j+k,i+1,d)-M(2)/6)*(1-u)+(img(j+k,i+2,d)-M(3)/6)*u;
                    end
                    %对上一步得到的四个点再进行一次插值
                    N = bspline([1 2 3 4],Y);
                    new_img(h,w,d) = N(2)/6*(1-v)^3+N(3)/6*v^3+(Y(2)-N(2)/6)*(1-v)+(Y(3)-M(3)/6)*v;    
                end
                
            end        
        end
    end
    
    figure,imshow(uint8(img));title('原图');
    figure,imshow(uint8(new_img));title('经旋转后的图像');
    

    结果如下:
    在这里插入图片描述

    4.总结

    从上述结果可以看出,双三次插值和样条插值的效果比双三次结点更好,这是由于它们考虑了原图中的点周围更多的点的灰度值,进而保证了旋转后图像的灰度值具有更好的连续性,当然,这样造成了计算量的显著增大,这点反映在了不同算法的计算耗时上:
    双线性插值法的耗时:
    在这里插入图片描述

    双三次插值法的耗时:

    样条插值法耗时:
    在这里插入图片描述
    可以看出双三次插值法在效果更好的同时计算速度也较快,所以在图像处理的实际应用中一般都采用双三次插值法。
    在其他的图像效果实现上,往往也是通过某种方式计算出变换后图片上的点对应的原坐标点,再对其原坐标周围的点的灰度值进行插值运算得到这一点的灰度值。可见插值算法在图像处理中确实有着非常广泛而重要的应用。

    参考文献:
    [1].Chensonglu.图像旋转原理及实现.blog.csdn.net/lkj345/article/details/50555870
    [2].R.Keys(1981).Cubic convolution interpolation for digital image processing. IEEE TRANSACTIONS ON ACOUSTICS, SPEECH, AND SIGNAL PROCESSING, VOL. ASSP-29, NO. 6, DECEMBER 1981

    展开全文
  • 图像旋转 裁剪 缩放操作1.旋转矩形2.图像的裁剪:3.图像缩放最近邻算法Bilinear算法(双线性插值)Bicubic算法(双三次插值) 1.旋转矩形 首先建议阅读 图像旋转算法原理-旋转矩阵 ...

    1.旋转矩形

    首先建议阅读 图像旋转算法原理-旋转矩阵 https://blog.csdn.net/liyuan02/article/details/6750828,这篇博客可以让你很好地理解图像中的每一个点是如何进行旋转操作的。其中涉及到了图像原点与笛卡尔坐标原点之间的相互转换以及点旋转的一些公式推导。

    建议阅读该博客的朋友最好对插值、matlab编程、数字图像有一些了解,另外所有代码和测试图片都可以到GitHub去下载。
    在一次数字图像处理课中,我接触到了图片旋转的一些原理,当时没完全想明白,课后通过一段时间的学习,终于完成了图片旋转的matlab程序。开始觉得应该挺简单的,但终究是纸上谈兵,在实现的过程中遇到很多问题。
    1 旋转矩形首先建议阅读 图像旋转算法原理-旋转矩阵,这篇博客可以让你很好地理解图像中的每一个点是如何进行旋转操作的。其中涉及到了图像原点与笛卡尔坐标原点之间的相互转换以及点旋转的一些公式推导。
    在这里插入图片描述

    2.图像的裁剪:

    在这里插入图片描述
    程序设计思路:

    这里不需要按像素遍历,因为目标图像的每行像素在原图像中是按相同顺序连续存放的,所以只需要遍历目标图像每行,将其在原图像中的对应内存数据复制过来即可。
    在这里插入图片描述

    3.图像缩放

    图像缩放的基本原理就是根据原图像的像素值通过一定的规则计算得到目标图像的像素值。
    图像缩放的两个关键问题是应该选取原图像中哪些像素值,以及如何确定选取的像素值的权重。
    图像缩放的常见问题是产生锯齿(缩小)或者模糊(放大)。

    缩放图像是二维的尺度伸缩变换,目标图像的像素值从原图像得到。
    令h_step = H/h, w_step = W/w,目标图中相邻像素的距离就转换为了原图中的h_step和w_step(通常不是整数)。
    此外,原图和目标图的第一个像素重合。
    在这里插入图片描述

    最近邻算法

    在待求像素的(待插值图的)四邻像素中,将距离待求像素最近的邻像素灰度赋给待求像素。
    见下图,设u为待求像素与四邻像素的左上点的水平坐标差,v为待求像素与四邻像素的左上点的垂直坐标差。将待求像素在待插值图中的坐标位置进行四舍五入处理,对应坐标的像素值即为待求像素的值。
    在这里插入图片描述
    优点:
    最常见,最通用的算法之一,计算量很小,算法简单,因此运算速度较快。

    缺点:
    效果不好,放大图像时会出现严重的马赛克,缩小图像则会严重失真。它仅使用离待测采样点最近的像素的灰度值作为该采样点的灰度值,而没考虑其他相邻像素点的影响,因而重新采样后灰度值有明显的不连续性,图像质量损失较大,会产生明显的马赛克和锯齿现象。

    Bilinear算法(双线性插值)

    双线性插值即在两个方向分别进行一次线性插值,通过四个相邻像素插值得到待求像素。
    见下图,已知Q11,Q12,Q21,Q22为原图中的四邻像素,P点为待求像素。
    图中所示的双线性插值步骤:
    (1)通过Q12,Q22线性插值得到R2,通过Q11,Q21线性插值得到R1;
    (2)通过R1,R2线性插值得到P.
    图中所示的双线性插值先进行了水平方向的插值,再进行垂直方向上的插值,也可以先垂直插值再水平插值。插值方向的顺序不影响最终的结果。
    双线性插值的公式为:
    f(x,y)=f(0,0)(1-x)(1-y)+f(1,0)x(1-y)+f(0,1)(1-x)y+f(1,1)xy
    在这里插入图片描述
    优点:
    最常见,最通用的算法之一,效果比最近邻插值法好。计算量较小,运算速度较快。图像连续性较好。双线性插值法效果要好于最近邻插值,只是计算量稍大一些,算法复杂些,程序运行时间也稍长些,但缩放后图像质量高,基本克服了最近邻插值灰度值不连续的特点,因为它考虑了待测采样点周围四个直接邻点对该采样点的相关性影响。

    缺点:
    放大时图像较为模糊,细节损失较严重。它仅考虑待测样点周围四个直接邻点灰度值的影响, 而未考虑到各邻点间灰度值变化率的影响, 因此具有低通滤波器的性质, 从而导致缩放后图像的高频分量受到损失, 图像边缘在一定程度上变得较为模糊。用此方法缩放后的输出图像与输入图像相比, 存在由于插值函数设计考虑不周而产生的图像质量受损与计算精度不高的问题。

    Bicubic算法(双三次插值)

    在数值分析这个数学分支中,双三次插值(英语:Bicubic interpolation)是二维空间中最常用的插值方法。在这种方法中,函数f在点 (x,y) 的值可以通过矩形网格中最近的十六个采样点的加权平均得到,在这里需要使用两个多项式插值三次函数,每个方向使用一个。

    在这里插入图片描述
    通过双三次插值可以得到一个连续的插值函数,它的一阶偏导数连续,并且交叉导数处处连续。

    双三次插值过程类似于双线性插值,分水平插值和垂直插值两个步骤,但双三次插值涉及的像素数更多(4x4)。

    计算系数 的过程依赖于插值数据的特性。如果已知插值函数的导数,常用的方法就是使用四个顶点的高度以及每个顶点的三个导数。一阶导数h’x与h’y表示 x 与 y 方向的表面斜率,二阶相互导数h’’xy表示同时在 x 与 y 方向的斜率。这些值可以通过分别连续对 x 与 y 向量取微分得到。对于网格单元的每个顶点,将局部坐标(0,0, 1,0, 0,1 和 1,1) 带入这些方程,再解这 16 个方程。

    展开全文
  • OpenCV图像旋转的原理与技巧

    千次阅读 2020-12-18 10:08:00
    点击上方“小白学视觉”,选择加"星标"或“置顶”重磅干货,第一时间送达转自|OpenCV学堂01引言初学图像处理,很多人遇到的第一关就是图像旋转图像旋转图像几何变换中...
  • 图像旋转的python代码

    2021-07-16 20:09:18
    图像旋转通过opencv进行旋转矩阵转置方法旋转图片 通过opencv进行旋转 通过opencv的函数进行旋转 示例图像: 代码: from matplotlib import pyplot as plt import cv2 import numpy as np # 这个成功的代码只能...
  • #1 使用pillow对图像进行旋转 1、下面使用如下这张图作为原图: 2、逆时针旋转90度 使用rotate(rotate_angle)函数使用: rotate_angle取值范围为:0~360 rotate_angle为正值,表示逆时针旋转 rotate_angle为负值...
  • =摘要=图像处理老师的课很...当然掩盖不了心中的好奇心----图像旋转是怎么实现的?于是经过一定的探索(主要是matlab的用法)...终于实现了这个"旋转":)本文就是关于这个"探索"过程的一点文字记录啦.=预览一下=matlab...
  • OpenCV实现图像旋转

    2020-12-18 02:44:05
    不知道是不是因为太简单,OpenCV不屑于去封装还是怎么回事,OpenCV中并没有一个API是专门用于实现图像任意角度的旋转的。我最近由于想要实现一下带旋转的模板匹配,因而必须对图像进行旋转。因此自己写了一个函数。...
  • 旋转一般是指将图像围绕某一指定点旋转一定的角度,图像旋转后会有一部分图像转出显示区域,可以截图那部分,也可以改变图像的尺寸使得图像显示完全。图像旋转原理所谓图像旋转是指图像以某一点为中心旋转一定的角度...
  • 基于matlab的图像旋转

    2021-04-18 10:37:21
    用matlab 的imrotate 函数对二值图像旋转时,不能实现功能,自己写了个旋转程序。旋转的基本原理如下:对原图中的点A(X0,Y0),旋转到(X1,Y1), 旋转角度为alpha,则 X1=X0*cos(alpha) +Y0*sin(alpha); Y1=-X0*sin...
  • 图像旋转

    2021-07-28 05:46:39
    图像旋转是指图像以某一点为中心旋转一定的角度,形成一幅新的图像的过程。当然这个点通常就是图像的中心。既然是按照中心旋转,自然会有这样一个属性:旋转前和旋转后的点离中心的位置不变。中文名图像旋转外文名...
  • 1图像转换OpenCV提供了两个转换函数cv2.warpAffine和cv2.warpPerspective,可以使用它们进行各种转换。 cv2.warpAffine采用2x3变换矩阵,而cv2.warpPerspective采用3x3变换矩阵作为输入。2图像缩放缩放只是调整图像...
  • 目录 使用PIL库 实现图片旋转 使用PIL库 在Python中, 实现turtle的图片旋转需要使用PIL库, 该库可通过pip安装。 使用PIL的Image.open函数可以实现加载图片; 使用ImageTk.PhotoImage可将PIL.Image对象转换为tkinter...
  • 本文实例为大家分享了opencv实现图像旋转效果的具体代码,供大家参考,具体内容如下 图像旋转: 在opencv中首先根据旋转角度和中心获取旋转矩阵,然后根据旋转矩阵进行变换 参数: 实现代码: 1 2 ...
  • 双线性插值(Bilinear interpolation)的图像旋转在mobile上面的...然后我们要扩展一下因为我们不是在原点做旋转,我们要围绕原来的图片中心做旋转,那么我们假定原来的图像中心是oldCenterX, oldCenterY.旋转完成以...
  • 图像旋转MATLAB代码实现插值时采用了最近点法和双线性插值,结果与imrotate函数进行了比较。myImrotate.m%实现Imrotate函数.clc;clearall;closeall;Img=imread('E:\eye.bmp');Img=double(Img);[hw]=size(Img);alpha=...
  • HTML5图片旋转

    2021-06-26 11:15:39
    HTML5图片旋转#n{margin:10px auto; width:920px; border:1px solid #CCC;font-size:12px; line-height:30px;}#n a{ padding:0 4px; color:#333}function startup() {var canvas = document.getElementById('canvas'...
  • } 运行示例 旋转5°后的图片图像的白点是因为计算时 float 到 int 截断误差导致 版本2 void Rotate(Matrix &dst, Matrix &src, int angle) { double theta = angle * 3.141592 / 180.0; float c = cos(theta); ...
  • 我正在使用RotateAnimation旋转要用作Android中自定义循环微调器的图像。这是我的rotate_indefinitely.xml文件,放置在res/anim/:xmlns:android=...
  • Matlab中完成图像旋转(坐标旋转)

    千次阅读 2021-03-06 16:43:37
    Matlab中完成图像旋转(坐标旋转) 通过极坐标的转化,将点阵进行旋转,得到旋转后点阵的坐标并绘制出来,以下为代码。 y=xlsread("D:\temp\meigu\tianran\unit_coordinates - 副本.csv","unit_coordinates - 副本",...
  • Qt 图片旋转

    2021-09-11 09:36:27
    Qt 图片旋转 第一次实现Qt的图片旋转功能,网上说有两种方法,用QMatrix 方法旋转得到的图片周围是黑色的,暂时没有解决,于是用了另外一种方法QPainter,但这坐标系的变换和旋转属实给我绕蒙了,理解了其实不难。...
  • 图像旋转算法原理

    2021-07-19 13:44:37
  • QT图片旋转

    2020-12-19 19:07:20
    目前发现有两种方法,如下:1、使用QPixmap的transformed函数旋转,这个函数默认是以图片中心为旋转点,不能随意设置旋转点,使用如下:QMatrix leftmatrix;leftmatrix.rotate(180);QLabel *pLabel=newQLabel();...
  • %图像旋转%X,Y为其行列数Image=imread('test.png');Image=rgb2gray(Image);angle=30;%角度任意的一个数 表示30度pai=3.14;Angle=pai*angle/180;%转换一下角度的表示方法。[X,Y]=size(Image);%原图显示subplot(2,1,1)...
  • 1.背景 因为数据集很少,项目还需要进行...接下来介绍的这种是图像旋转 2.实现方法 import cv2 from math import * import os def remote(img, degree): height, width = img.shape[:2] radians = float(degre
  • 给定一张图片,如何在不使用PIL、OpenCV这些库的情况下,单独使用Numpy库实现图片旋转。 “talk is easy, show me the code!” 常见旋转有:上下翻转、左右翻转、旋转180度、逆时针旋转90度、顺时针旋转90度。 ...
  • 下面我们来看看更通常一点的做法:以图像的中心为圆心进行旋转。这里涉及到一个坐标系的转换问题。看下图:在矩阵中我们的坐标系通常是AB和AC方向的,而传统的笛卡尔直角坐标系是DE和DF方向的。令图像表示为M×N的...
  • 图片旋转,本质上是对旋转后的图片中每个像素点计算在原图的位置。然后照搬过来就好。(多说一句,如果计算出来在原图中的位置不是整数而是小数,因为像素点个数都是整数,就需要小数到整数的转换。这个转换过程是有...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 265,844
精华内容 106,337
关键字:

图像旋转