【Numpy】图像处理——仿射变换


前言

  图像的空间运算是一个比较大的范畴,主要包括:单像素运算、邻域运算、几何变换。
  1.单像素变换,就是灰度变换,利用函数改变图像各个像素的灰度值。
  2.邻域运算,最主要的就是图像滤波,包括线性滤波和非线性滤波。输出图像中的每个像素值由对应的输入像素及其邻域内像素共同决定的图像运算。一个邻域是围绕任意一点(x,y)为中心、选定范围了的点的集合。
  3.几何空间变换,改变了图像各像素点的空间排列。

参考冈萨雷斯的《数字图像处理第四版》

  几何变换改变图像中像素的空间排列,但是任何变换都会涉及到图像像素值赋值(插值)这一概念。
  我们把图像的几何变换近似地看成是一个平移变换与线性变换的结合,也就是仿射变换(Affine Transformation)。
  仿射变换一般包括缩放变换、旋转变换、平移变换和剪切变换。二维放射变换的关键性质是:保留点、线、面。
  所以几何变换变的是什么呢?就是点、线、面的位置,当我们改变图像中某一像素点的位置时,如平移、旋转等等,周围的像素点是不是也要变(牵一发而动全身),总不可能只有一个像素点从空间中脱离吧!点变了,其所在的线也就变了,紧接着,面也就变了。不过变的也只是位置(像素坐标),图像本身的完整性还是在的。
  简单的坐标变换可以表示成:一个22的变换矩阵与x、y两个方向的坐标构成的向量(即21的矩阵)相乘。
[ x ′ y ′ ] = T [ x y ] = [ t 11 t 21 t 21 t 22 ] [ x y ] \begin{bmatrix} x' \\ y' \end{bmatrix}=T \begin{bmatrix} x \\ y \end{bmatrix}=\begin{bmatrix} t_{11}&t_{21} \\ t_{21}&t_{22} \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} [xy]=T[xy]=[t11t21t21t22][xy] [ x ′ y ′ ] = A [ x y 1 ] = [ a 11 a 21 a 13 a 21 a 22 a 23 0 0 1 ] [ x y 1 ] \begin{bmatrix} x' \\ y' \end{bmatrix}=A \begin{bmatrix} x \\ y \\ 1 \end{bmatrix}=\begin{bmatrix} a_{11}&a_{21}&a_{13} \\ a_{21}&a_{22}&a_{23} \\ 0&0&1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} [xy]=A xy1 = a11a210a21a220a13a231 xy1 [ x ′ y ′ ] = B [ x y ] = [ b 11 b 21 b 13 b 21 b 22 b 23 ] [ x y ] \begin{bmatrix} x' \\ y' \end{bmatrix}=B \begin{bmatrix} x \\ y \end{bmatrix}=\begin{bmatrix} b_{11}&b_{21}&b_{13} \\ b_{21}&b_{22}&b_{23} \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} [xy]=B[xy]=[b11b21b21b22b13b23][xy]
  其中, ( x , y ) (x,y) (x,y)是原图像中像素的坐标, ( x ′ , y ′ ) (x',y') (x,y)是变换后图像中像素的坐标,矩阵T是坐标变换矩阵,一律放在原图像坐标矩阵前面。
  但是, 2 × 2 2\times2 2×2变换矩阵只能表示平移变换,若要实现其他变换,还需要扩展变换矩阵。我们设计一个23或者33的变换矩阵即可。
  如上所述两个变换矩阵都是仿射变换矩阵,我们选择两种当中任意一个都可以。但是为了参考书籍一致,接下来的部分还是以3*3的变化矩阵为主。下面的表格展示九种仿射变换:
在这里插入图片描述


图像的平移

import numpy as np
import matplotlib.pyplot as plt
from PIL import Image

img = Image.open("erha.jpg")

img_arr = np.array(img)

plt.title("origin_img")
plt.imshow(img,cmap="brg")
plt.show()


height,width,channels = img_arr.shape
#print(height,width,channels)
# 宽、高、通道数分别是500、627、3

img_new1 = np.zeros((height,width,channels))

#平移变换矩阵,此处设置水平向右平移100个像素点,垂直向下平移50个像素点
t_x,t_y = 100,50
trans_arr = np.matrix([[1,0,t_x],[0,1,t_y],[0,0,1]])

#由于变换过程中会涉及到图像数据溢出图像边界的情况,因此需要考虑对变换后的图像做一些修改。
#溢出的像素值数据不做考虑,没有被像素值覆盖/填充的位置需要考虑填充一些特殊值,比如表示黑色或者表示白色。

#此实验没有被像素值覆盖/填充的位置填充的值设置为(255,255,255),本次是对3通道RGB彩色图的变换操作。
for c in range(channels):
    for i in range(height):
        for j in range(width):
            if i < t_x  or j <t_y:
                img_new1[i,j,c] = 255
            else:
                img_new1[i,j,c] = img_arr[i-t_x,j-t_y,c]

img_new1 = img_new1.astype(np.uint8)

plt.title("pingyi")
plt.imshow(img_new1,cmap="brg")
plt.show()

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


图像的旋转

第一次尝试

#尝试一
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
import math

img = Image.open("erha.jpg")

img_arr = np.array(img)

plt.title("origin_img")
plt.imshow(img,cmap="brg")
plt.show()


height,width,channels = img_arr.shape
#print(height,width,channels)
# 宽、高、通道数分别是500、627、3

img_new1 = np.ones((1000,1200,channels)) * 255

#旋转变换,此处选择逆时针旋转30度,即Π/20。
theta = np.pi / 20
trans_arr = np.matrix([[np.cos(theta),(-1) * np.sin(theta),0],[np.sin(theta),np.cos(theta),0],[0,0,1]])
#print(trans_arr)

#由于变换过程中会涉及到图像数据溢出图像边界的情况,因此需要考虑对变换后的图像做一些修改。
#溢出的像素值数据不做考虑,没有被像素值覆盖/填充的位置需要考虑填充一些特殊值,比如表示黑色或者表示白色。

#此实验没有被像素值覆盖/填充的位置填充的值设置为(255,255,255),本次是对3通道RGB彩色图的变换操作。
for c in range(channels):
    for i in range(height):
        for j in range(width):
                img_new1[math.floor(i*np.cos(theta)-j*np.sin(theta)),math.floor(i*np.sin(theta)+j*np.cos(theta)),c] = img_arr[i,j,c]

img_new1 = img_new1.astype(np.uint8)

plt.title("yuandian_xuanzhuan")
plt.imshow(img_new1,cmap="brg")
plt.show()

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

绕中心旋转

  在刚刚的尝试中,我是简单粗暴地将输出图像设置成 ( 1000 , 1200 , 3 ) (1000,1200,3) (1000,1200,3)的大小,而且默认旋转中心是左上角的 ( 0 , 0 ) (0,0) (0,0)坐标。
  接下来我准备一图像的中心为旋转中心点,但是输出图像还是依然粗暴的设置为大一些的。
  由于图像的高为偶数——500,不利于我们取中心像素点,因此我们可以对原图像进行扩展一行或者删除一行。图像中我们感兴趣的是二哈的脸,而图像上下边缘的信息并不重要,所以此处我就删除图像的第一行。

#尝试二:中心点旋转
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image


img = Image.open("erha.jpg")

img_arr = np.array(img)

plt.title("origin_img")
plt.imshow(img,cmap="brg")
plt.show()


height,width,channels = img_arr.shape
#print(height,width,channels)
# 宽、高、通道数分别是500、627、3

img_arr_new = np.zeros((height-1,width,channels))
img_arr_new[:,:,:] = img_arr[1:,:,:]
rows,cols,ch = img_arr_new.shape  #建议行后的高、宽、通道数(也可以读作行、列、通道数)

img_arr_new1 = img_arr_new.astype(np.uint8)

plt.title("erha_xin")
plt.imshow(img_arr_new1,cmap="brg")
plt.show()

h,w,c = 599,699,ch
img_new1 = np.ones((h,w,c)) * 255
# img_new1 = np.ones((height-1,width,channels))

#旋转变换,此处选择逆时针旋转30度,即Π/20。
theta = np.pi / 20
# trans_arr = np.matrix([[np.cos(theta),(-1) * np.sin(theta),0],[np.sin(theta),np.cos(theta),0],[0,0,1]])
# #print(trans_arr)


#此实验没有被像素值覆盖/填充的位置填充的值设置为(255,255,255),本次是对3通道RGB彩色图的变换操作。
for k in range(ch):
    for i in range(rows):
        for j in range(cols):
            img_new1[int((i-int(rows/2))*np.cos(theta)-(j-int(cols/2))*np.sin(theta))-int(h/2),int((i-int(rows/2))*np.sin(theta)+(j-int(cols/2))*np.cos(theta))-int(w/2),k] = img_arr_new[i,j,k]


img_new1 = img_new1.astype(np.uint8)

plt.title("mid_xuanzhuan")
plt.imshow(img_new1,cmap="brg")
plt.show()

img_save = Image.fromarray(img_new1)

img_save.show()

img_save.save("new_dog_erha.jpg")

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

加入插值算法(先插值后旋转)

  终于正常了,哈哈!不过问题来了,图像质量变差了,我们可以很明显地看出输出图像相较于原图,不够平滑了,出现了奇怪的斑点!怎么办呢?
  接下来,我吧双三次内插法加进来试试!

#尝试三:中心点旋转,先双三次内插,再旋转变换
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image


img = Image.open("erha.jpg")

img_arr = np.array(img)

plt.title("origin_img")
plt.imshow(img,cmap="brg")
plt.show()


height,width,channels = img_arr.shape
#print(height,width,channels)
# 宽、高、通道数分别是500、627、3

img_arr_new = np.zeros((height-1,width,channels))
img_arr_new[:,:,:] = img_arr[1:,:,:]
rows,cols,ch = img_arr_new.shape  #建议行后的高、宽、通道数(也可以读作行、列、通道数)

img_arr_new1 = img_arr_new.astype(np.uint8)

plt.title("erha_xin")
plt.imshow(img_arr_new1,cmap="brg")
plt.show()

#扩展图像,为了后面插值时避免越界

#底部扩展两行,直接拷贝最后一行
img_new_1 = np.zeros((rows+2,cols,ch))
img_new_1[:rows,:,:] =  img_arr_new1
img_new_1[rows:rows+2,:,:] =  img_arr_new1[rows-1:rows,:,:]

#顶部扩展两行,直接拷贝第一行
img_new2 = np.zeros((rows+4,cols,ch))
img_new2[2:rows+4,:,:] =  img_new_1
img_new2[0:2,:,:] =  img_new_1[0:1,:,:]


#右侧扩展两列,直接拷贝最后两列
img_new3 = np.zeros((rows+4,cols+2,ch))
img_new3[:,:cols,:] =  img_new2
img_new3[:,cols:cols+2,:] =  img_new2[:,cols-1:cols,:]

#左侧扩展两列,直接拷贝第一列
img_new4 = np.zeros((rows+4,cols+4,ch))
img_new4[:,2:cols+4,:] =  img_new3
img_new4[:,0:2,:] =  img_new3[:,0:1,:]

img_new4_arr = img_new4.astype(np.uint8)
plt.title("img_new4")
plt.imshow(img_new4_arr,cmap="brg")
plt.show()


h,w,c = 599,699,ch
img_new1 = np.ones((h,w,ch)) * 255
# img_new1 = np.ones((height-1,width,channels))

#旋转变换,此处选择逆时针旋转30度,即Π/20。
theta = np.pi / 20
# trans_arr = np.matrix([[np.cos(theta),(-1) * np.sin(theta),0],[np.sin(theta),np.cos(theta),0],[0,0,1]])
# #print(trans_arr)


x_ratio = rows/h
y_ratio = cols/w

a = -0.5 #此处选择如此特殊的自由参数,是因为所选的原图很小,试了半天参数。

#另外,自由参数的数值不是固定的,就像深度学习算法的学习率一样,需要微调/

#双三次内插法_基函数
def w_func(x):
    if abs(x) <= 1:
        return ((a + 2) * (abs(x) ** 3) - (a + 3) * (abs(x) ** 2) + 1)
    elif (abs(x)>1 and abs(x) < 2):
        return (a * (abs(x) ** 3) - 5 * a * (abs(x) ** 2) + 8 * a * abs(x) - 4 * a)
    else:
        return 0




#此实验没有被像素值覆盖/填充的位置填充的值设置为(255,255,255),本次是对3通道RGB彩色图的变换操作。
for k in range(ch):
    for i in range(h):
        for j in range(w):
            y = int(j * y_ratio) + 2
            beita = j * y_ratio - int(j * y_ratio)
            A = np.array([w_func(1+beita),w_func(beita),w_func(1-beita),w_func(2-beita)]).reshape((1,4))
            
            x = int(i * x_ratio) + 2
            alpha = i * x_ratio - int(i * x_ratio)
            C = np.array([w_func(1+alpha),w_func(alpha),w_func(1-alpha),w_func(2-alpha)]).reshape((4,1))
            B = img_new4[x-1:x+3,y-1:y+3,k]
            
            img_new1[int((x-int(rows/2))*np.cos(theta)-(y-int(cols/2))*np.sin(theta))-int(h/2),int((x-int(rows/2))*np.sin(theta)+(y-int(cols/2))*np.cos(theta))-int(w/2),k] = np.dot(np.dot(A,B),C)


img_new1 = img_new1.astype(np.uint8)

plt.title("mid_Bicubic_xuanzhuan")
plt.imshow(img_new1,cmap="brg")
plt.show()

img_save = Image.fromarray(img_new1)

img_save.show()

img_save.save("new_dog.jpg")

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

加入插值算法(先旋转后插值)

#尝试四:中心点旋转,先旋转变换,再双三次内插
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image


img = Image.open("erha.jpg")

img_arr = np.array(img)

plt.title("origin_img")
plt.imshow(img,cmap="brg")
plt.show()


height,width,channels = img_arr.shape
#print(height,width,channels)
# 宽、高、通道数分别是500、627、3

img_arr_new = np.zeros((height-1,width,channels))
img_arr_new[:,:,:] = img_arr[1:,:,:]
rows,cols,ch = img_arr_new.shape  #建议行后的高、宽、通道数(也可以读作行、列、通道数)

img_arr_new1 = img_arr_new.astype(np.uint8)

plt.title("erha_xin")
plt.imshow(img_arr_new1,cmap="brg")
plt.show()

h,w,c = 599,699,channels
img_new1 = np.ones((h,w,channels)) * 255
# img_new1 = np.ones((height-1,width,channels))

#旋转变换,此处选择逆时针旋转30度,即Π/20。
theta = np.pi / 20
# trans_arr = np.matrix([[np.cos(theta),(-1) * np.sin(theta),0],[np.sin(theta),np.cos(theta),0],[0,0,1]])
# #print(trans_arr)


#此实验没有被像素值覆盖/填充的位置填充的值设置为(255,255,255),本次是对3通道RGB彩色图的变换操作。
for k in range(ch):
    for i in range(rows):
        for j in range(cols):
            img_new1[int((i-int(rows/2))*np.cos(theta)-(j-int(cols/2))*np.sin(theta))-int(h/2),int((i-int(rows/2))*np.sin(theta)+(j-int(cols/2))*np.cos(theta))-int(w/2),k] = img_arr_new[i,j,k]


img_new1 = img_new1.astype(np.uint8)

plt.title("mid_xuanzhuan")
plt.imshow(img_new1,cmap="brg")
plt.show()


#扩展图像,为了后面插值时避免越界

#底部扩展两行,直接拷贝最后一行
img_new_1 = np.zeros((h+2,w,c))
img_new_1[:h,:,:] =  img_new1
img_new_1[h:h+2,:,:] =  img_new1[h-1:h,:,:]

#顶部扩展两行,直接拷贝第一行
img_new2 = np.zeros((h+4,w,c))
img_new2[2:h+4,:,:] =  img_new_1
img_new2[0:2,:,:] =  img_new_1[0:1,:,:]


#右侧扩展两列,直接拷贝最后两列
img_new3 = np.zeros((h+4,w+2,c))
img_new3[:,:w,:] =  img_new2
img_new3[:,w:w+2,:] =  img_new2[:,w-1:w,:]

#左侧扩展两列,直接拷贝第一列
img_new4 = np.zeros((h+4,w+4,c))
img_new4[:,2:w+4,:] =  img_new3
img_new4[:,0:2,:] =  img_new3[:,0:1,:]

img_new4_arr = img_new4.astype(np.uint8)
# plt.title("img_new4")
# plt.imshow(img_new4_arr,cmap="brg")
# plt.show()

img_array = np.zeros((h,w,c))
x_ratio = 1
y_ratio = 1

a = -0.5 #此处选择如此特殊的自由参数,是因为所选的原图很小,试了半天参数。

#另外,自由参数的数值不是固定的,就像深度学习算法的学习率一样,需要微调/

#双三次内插法_基函数
def w_func(x):
    if abs(x) <= 1:
        return ((a + 2) * (abs(x) ** 3) - (a + 3) * (abs(x) ** 2) + 1)
    elif (abs(x)>1 and abs(x) < 2):
        return (a * (abs(x) ** 3) - 5 * a * (abs(x) ** 2) + 8 * a * abs(x) - 4 * a)
    else:
        return 0
    
#此处需注意,根据上述公式编写代码,双线性内插法实现从纵向上进行运算,再从横向上运算。
for k in range(c):
    for j in range(w):
        y = int(j * y_ratio) + 2
        beita = j * y_ratio - int(j * y_ratio)
        A = np.array([w_func(1+beita),w_func(beita),w_func(1-beita),w_func(2-beita)]).reshape((1,4))
        for i in range(h):
            x = int(i * x_ratio) + 2
            alpha = i * x_ratio - int(i * x_ratio)
            C = np.array([w_func(1+alpha),w_func(alpha),w_func(1-alpha),w_func(2-alpha)]).reshape((4,1))
            B = img_new4[x-1:x+3,y-1:y+3,k]
            img_array[i][j][k] = np.dot(np.dot(A,B),C)

img_array = img_array.astype(np.uint8)

plt.title("mid_xuanzhuan_Bicubic")
plt.imshow(img_array,cmap="brg")
plt.show()


img_save = Image.fromarray(img_array)

img_save.show()

img_save.save("new_dog2.jpg")

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

快乐双拼

#拓展尝试一,numpy扩展列或行(快乐双拼)

import numpy as np
import matplotlib.pyplot as plt
from PIL import Image

img = Image.open("new_dog2.jpg")

img_arr = np.array(img)

plt.title("origin_img")
plt.imshow(img,cmap="brg")
plt.show()


height,width,channels = img_arr.shape
#print(height,width,channels)
# 宽、高、通道数分别是500、627、3

img_new1 = np.zeros((height,width,channels))

#平移变换矩阵,此处设置水平向右平移350个像素点
t_x,t_y = 0,350
trans_arr = np.matrix([[1,0,t_x],[0,1,t_y],[0,0,1]])

#由于变换过程中会涉及到图像数据溢出图像边界的情况,因此需要考虑对变换后的图像做一些修改。
#溢出的像素值数据不做考虑,没有被像素值覆盖/填充的位置需要考虑填充一些特殊值,比如表示黑色或者表示白色。

#此实验没有被像素值覆盖/填充的位置填充的值设置为(255,255,255),本次是对3通道RGB彩色图的变换操作。
for c in range(channels):
    for i in range(height):
        for j in range(width):
            if i < t_x  or j <t_y:
                img_new1[i,j,c] = 255
            else:
                img_new1[i,j,c] = img_arr[i-t_x,j-t_y,c]

img_new1 = img_new1.astype(np.uint8)

plt.title("pingyi_y+350")
plt.imshow(img_new1,cmap="brg")
plt.show()

img_new2 = np.zeros((height,width,channels))

#平移变换矩阵,此处设置水平向左平移350个像素点
t_x1,t_y1 = 0,350
trans_arr = np.matrix([[1,0,t_x1],[0,1,t_y1],[0,0,1]])

#由于变换过程中会涉及到图像数据溢出图像边界的情况,因此需要考虑对变换后的图像做一些修改。
#溢出的像素值数据不做考虑,没有被像素值覆盖/填充的位置需要考虑填充一些特殊值,比如表示黑色或者表示白色。

#此实验没有被像素值覆盖/填充的位置填充的值设置为(255,255,255),本次是对3通道RGB彩色图的变换操作。
for c in range(channels):
    for i in range(height):
        for j in range(width):
            if j < t_y1 - 1:
                img_new2[i,j,c] = img_arr[i-t_x1,j+t_y1,c]
            else:
                img_new2[i,j,c] = 255

img_new2 = img_new2.astype(np.uint8)

plt.title("pingyi_y-350")
plt.imshow(img_new2,cmap="brg")
plt.show()

 
img_new3 = np.hstack((img_new1,img_new2))


img_new3 = np.array(img_new3)
print(img_new3.shape)

plt.title("pingyi_double")
plt.imshow(img_new3,cmap="brg")
plt.show()

img_save = Image.fromarray(img_new3)

img_save.show()

img_save.save("new_dog_double.jpg")

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

快乐对称

#拓展尝试二,numpy扩展列或行(快乐对称)

import numpy as np
import matplotlib.pyplot as plt
from PIL import Image

img = Image.open("new_dog2.jpg")

img_arr = np.array(img)

plt.title("origin_img")
plt.imshow(img,cmap="brg")
plt.show()


height,width,channels = img_arr.shape
#print(height,width,channels)
# 宽、高、通道数分别是500、627、3

img_new1 = np.zeros((height,width,channels))

#平移变换矩阵,此处设置水平向右平移350个像素点
t_x,t_y = 0,350
trans_arr = np.matrix([[1,0,t_x],[0,1,t_y],[0,0,1]])

#由于变换过程中会涉及到图像数据溢出图像边界的情况,因此需要考虑对变换后的图像做一些修改。
#溢出的像素值数据不做考虑,没有被像素值覆盖/填充的位置需要考虑填充一些特殊值,比如表示黑色或者表示白色。

#此实验没有被像素值覆盖/填充的位置填充的值设置为(255,255,255),本次是对3通道RGB彩色图的变换操作。
for c in range(channels):
    for i in range(height):
        for j in range(width):
            if i < t_x  or j <t_y:
                img_new1[i,j,c] = 255
            else:
                img_new1[i,j,c] = img_arr[i-t_x,j-t_y,c]

img_new1 = img_new1.astype(np.uint8)

plt.title("pingyi_y+350")
plt.imshow(img_new1,cmap="brg")
plt.show()

img_new2 = np.zeros((height,width,channels))


#由于变换过程中会涉及到图像数据溢出图像边界的情况,因此需要考虑对变换后的图像做一些修改。
#溢出的像素值数据不做考虑,没有被像素值覆盖/填充的位置需要考虑填充一些特殊值,比如表示黑色或者表示白色。

#此实验没有被像素值覆盖/填充的位置填充的值设置为(255,255,255),本次是对3通道RGB彩色图的变换操作。

#图像翻转(Flip)
# 翻转矩阵是[[-1,0,width],[0,1,0],[0,0,1]],其中width为原图宽度,水平翻转就是遍历过程中,将x坐标按照width - x取值赋值给变换后的图像x'坐标,
for c in range(channels):
    for i in range(height):
        for j in range(width):
            img_new2[i,j,c] = img_new1[i,width - j - 1,c]
img_new2 = img_new2.astype(np.uint8)

plt.title("pingyi_y+350_fanzhuan")
plt.imshow(img_new2,cmap="brg")
plt.show()

 
img_new3 = np.hstack((img_new1,img_new2))


img_new3 = np.array(img_new3)
print(img_new3.shape)

plt.title("pingyi_double")
plt.imshow(img_new3,cmap="brg")
plt.show()

img_save = Image.fromarray(img_new3)

img_save.show()

img_save.save("new_dog_double.jpg")

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

快乐旋转

#拓展尝试三,PIL图像叠加(快乐旋转+函数创建)

#尝试四:中心点旋转,先旋转变换,再双三次内插
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
import math
import time
import imageio

#双三次内插法_基函数
def w_func(x):
    a = -0.5 #自由参数,
    if abs(x) <= 1:
        return ((a + 2) * (abs(x) ** 3) - (a + 3) * (abs(x) ** 2) + 1)
    elif (abs(x)>1 and abs(x) < 2):
        return (a * (abs(x) ** 3) - 5 * a * (abs(x) ** 2) + 8 * a * abs(x) - 4 * a)
    else:
        return 0


#图像缩放
def suofang(img,height,width):

    if type(img) == "str":
        img = Image.open(img)
    elif type(img) == "numpy.ndarray":
        img = img

    # 将图像转化为NumPy数组
    img_arr = np.array(img)
    r,c,ch = img_arr.shape 

    img_array = np.zeros((height,width,3))
    rows,cols,channels = img_array.shape

    x_ratio = r/rows
    y_ratio = c/cols

    #扩展图像,为了后面插值时避免越界

    #底部扩展两行,直接拷贝最后一行
    img_new1 = np.zeros((r+2,c,ch))
    img_new1[:r,:,:] =  img_arr
    img_new1[r:r+2,:,:] =  img_arr[r-1:r,:,:]

    #顶部扩展两行,直接拷贝第一行
    img_new2 = np.zeros((r+4,c,ch))
    img_new2[2:r+4,:,:] =  img_new1
    img_new2[0:2,:,:] =  img_new1[0:1,:,:]


    #右侧扩展两列,直接拷贝最后两列
    img_new3 = np.zeros((r+4,c+2,ch))
    img_new3[:,:c,:] =  img_new2
    img_new3[:,c:c+2,:] =  img_new2[:,c-1:c,:]

    #左侧扩展两列,直接拷贝第一列
    img_new4 = np.zeros((r+4,c+4,ch))
    img_new4[:,2:c+4,:] =  img_new3
    img_new4[:,0:2,:] =  img_new3[:,0:1,:]

    #此处需注意,根据上述公式编写代码,双线性内插法实现从纵向上进行运算,再从横向上运算。
    for k in range(channels):
        for j in range(cols):
            y = int(j * y_ratio) + 2
            beita = j * y_ratio - int(j * y_ratio)
            A = np.array([w_func(1+beita),w_func(beita),w_func(1-beita),w_func(2-beita)]).reshape((1,4))
            for i in range(rows):
                x = int(i * x_ratio) + 2
                alpha = i * x_ratio - int(i * x_ratio)
                C = np.array([w_func(1+alpha),w_func(alpha),w_func(1-alpha),w_func(2-alpha)]).reshape((4,1))
                B = img_new4[x-1:x+3,y-1:y+3,k]
                img_array[i][j][k] = np.dot(np.dot(A,B),C)
                
    return img_array

#旋转所选图像尽可能在(500*600)的高、宽范围内,如果实在太大,可以先进行缩放。
def xuanzhuan(img,theta):
    
    img = Image.open(img)
    img_arr = np.array(img)
    height,width,channels = img_arr.shape
    
    if height > 500 or width > 600:
        img_arr = suofang(img_arr,500,600)
    else:
        img_arr = img_arr
        
    height,width,channels = img_arr.shape
    
    #找中心点,需要被旋转的图像高和宽都是奇数,若出现奇数的高和宽,需要改变图像大小,减去一行或一列,或者增加一行或一列。
    #此处选择减去一行或一列
    
    if height % 2 == 0:
        height -= 1
        h1 = 1
    else:
        height = height
        h1 = 0
    if width % 2 == 0:
        width -= 1
        w1 = 1
    else:
        width = width
        w1 = 0
    
    img_arr_new = np.zeros((height,width,channels))
    img_arr_new[:,:,:] = img_arr[h1:,w1:,:]
    rows,cols,ch = img_arr_new.shape  #建议行后的高、宽、通道数(也可以读作行、列、通道数)

    #取图像“对角线长度+1”作为输出图像的高和宽,这样可以保证任意角度旋转都足以存放进去
    diagonal_len = int(math.sqrt(rows ** 2 + cols ** 2))
    
    
    #“对角线长度+1”中的+1,是因为勾股定理开跟会遇到小数的情况,小数不能作为高和宽。
    #当然也可以选择math内部的ceil()来进行向上取整。
    
#     diagonal_len = math.ceil(math.sqrt(rows ** 2 + cols ** 2))
#     h,w,c = diagonal_len,diagonal_len,channels
    
    h,w,c = diagonal_len+1,diagonal_len+1,channels
    img_new1 = np.ones((h,w,channels)) * 255

    #此实验没有被像素值覆盖/填充的位置填充的值设置为(255,255,255),本次是对3通道RGB彩色图的变换操作。
    for k in range(ch):
        for i in range(rows):
            for j in range(cols):
                img_new1[int((i-int(rows/2))*np.cos(theta)-(j-int(cols/2))*np.sin(theta))-int(h/2),int((i-int(rows/2))*np.sin(theta)+(j-int(cols/2))*np.cos(theta))-int(w/2),k] = img_arr_new[i,j,k]
    
    img_new1 = suofang(img_new1,diagonal_len+1,diagonal_len+1)
    
    return img_new1


def auto_xuanzhuan(angle):
    image = xuanzhuan("erha.jpg",angle)  #angle:角度,此处为旋转的角度
    return image
    
if __name__ == "__main__":
    
    images = []
    
    for i in range(0,40):
        
        # 获取代码执行前时间戳
        start_time = time.time()
        
        #此处必须注意数据格式必须转换为uint8格式,否则Image.fromarray无法识别.
        #另外,必须转化为图像格式,而不应该是numpy.ndarray格式
        images.append(Image.fromarray(np.uint8(auto_xuanzhuan(np.pi / 20 * i))))
        
        # 获取代码执行后时间戳
        end_time = time.time()
        # 计算执行时间
        run_time = end_time - start_time
        print("第{}次循环代码执行时间为:{}秒".format(i+1,run_time))
第1次循环代码执行时间为:24.325934171676636秒
第2次循环代码执行时间为:24.42971444129944秒
第3次循环代码执行时间为:24.219058990478516秒
第4次循环代码执行时间为:24.76055121421814秒
第5次循环代码执行时间为:24.93599224090576秒
第6次循环代码执行时间为:24.05968928337097秒
第7次循环代码执行时间为:24.292383193969727秒
第8次循环代码执行时间为:24.577701807022095秒
第9次循环代码执行时间为:24.300209283828735秒
第10次循环代码执行时间为:24.229836463928223秒
第11次循环代码执行时间为:25.340418577194214秒
第12次循环代码执行时间为:24.492943286895752秒
第13次循环代码执行时间为:24.931776762008667秒
第14次循环代码执行时间为:24.633894205093384秒
第15次循环代码执行时间为:24.458670616149902秒
第16次循环代码执行时间为:24.337478637695312秒
第17次循环代码执行时间为:24.506762742996216秒
第18次循环代码执行时间为:24.68997073173523秒
第19次循环代码执行时间为:24.45765495300293秒
第20次循环代码执行时间为:24.497570037841797秒
第21次循环代码执行时间为:24.624290227890015秒
第22次循环代码执行时间为:24.285611867904663秒
第23次循环代码执行时间为:24.528175830841064秒
第24次循环代码执行时间为:24.427589893341064秒
第25次循环代码执行时间为:24.5060133934021秒
第26次循环代码执行时间为:24.185490608215332秒
第27次循环代码执行时间为:24.770081281661987秒
第28次循环代码执行时间为:24.470173358917236秒
第29次循环代码执行时间为:24.503557920455933秒
第30次循环代码执行时间为:24.654132843017578秒
第31次循环代码执行时间为:24.86076831817627秒
第32次循环代码执行时间为:24.75024437904358秒
第33次循环代码执行时间为:24.231610774993896秒
第34次循环代码执行时间为:24.553200483322144秒
第35次循环代码执行时间为:25.199523448944092秒
第36次循环代码执行时间为:24.524871349334717秒
第37次循环代码执行时间为:24.6987783908844秒
第38次循环代码执行时间为:24.48868703842163秒
第39次循环代码执行时间为:24.672356367111206秒
第40次循环代码执行时间为:24.763872146606445秒
#保存 GIF 动画
imageio.mimsave("gif.gif", images, duration=0.001)
  图像翻转和缩放在前面已经旋转部分已经写好了,接下来在写一个更灵活的图像缩放函数吧!注意:每一个变换都自带一个双三次内插法!

图像缩放

#图像缩放
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
import math


#双三次内插法_基函数
def w_func(x):
    a = -0.5 #自由参数,
    if abs(x) <= 1:
        return ((a + 2) * (abs(x) ** 3) - (a + 3) * (abs(x) ** 2) + 1)
    elif (abs(x)>1 and abs(x) < 2):
        return (a * (abs(x) ** 3) - 5 * a * (abs(x) ** 2) + 8 * a * abs(x) - 4 * a)
    else:
        return 0


def suofang(img,height,width):

    img = Image.open(img)

    # 将图像转化为NumPy数组
    img_arr = np.array(img)
    r,c,ch = img_arr.shape 

    img_array = np.zeros((height,width,ch))
    rows,cols,channels = img_array.shape
    
    x_ratio = r/rows
    y_ratio = c/cols


    #扩展图像,为了后面插值时避免越界

    #底部扩展两行,直接拷贝最后一行
    img_new1 = np.zeros((r+2,c,ch))
    img_new1[:r,:,:] =  img_arr
    img_new1[r:r+2,:,:] =  img_arr[r-1:r,:,:]

    #顶部扩展两行,直接拷贝第一行
    img_new2 = np.zeros((r+4,c,ch))
    img_new2[2:r+4,:,:] =  img_new1
    img_new2[0:2,:,:] =  img_new1[0:1,:,:]


    #右侧扩展两列,直接拷贝最后两列
    img_new3 = np.zeros((r+4,c+2,ch))
    img_new3[:,:c,:] =  img_new2
    img_new3[:,c:c+2,:] =  img_new2[:,c-1:c,:]

    #左侧扩展两列,直接拷贝第一列
    img_new4 = np.zeros((r+4,c+4,ch))
    img_new4[:,2:c+4,:] =  img_new3
    img_new4[:,0:2,:] =  img_new3[:,0:1,:]

    #此处需注意,根据上述公式编写代码,双线性内插法实现从纵向上进行运算,再从横向上运算。
    for k in range(channels):
        for j in range(cols):
            y = int(j * y_ratio) + 2
            beita = j * y_ratio - int(j * y_ratio)
            A = np.array([w_func(1+beita),w_func(beita),w_func(1-beita),w_func(2-beita)]).reshape((1,4))
            for i in range(rows):
                x = int(i * x_ratio) + 2
                alpha = i * x_ratio - int(i * x_ratio)
                C = np.array([w_func(1+alpha),w_func(alpha),w_func(1-alpha),w_func(2-alpha)]).reshape((4,1))
                B = img_new4[x-1:x+3,y-1:y+3,k]
                img_array[i][j][k] = np.dot(np.dot(A,B),C)
                
    return img_array

image = suofang("erha.jpg",2000,2000)

image = np.uint8(image)

plt.title("After_changing")
plt.imshow(image,cmap="brg")
plt.show
<function matplotlib.pyplot.show(close=None, block=None)>

在这里插入图片描述


图像剪切(水平和垂直剪切)

  图像的剪切(斜切),又称扭变,是指平面景物在投影平面上的非垂直投影,使图像中的图形在水平方向或垂直方向产生扭变。
  由扭变角度 θ \theta θ可以构造斜切变换矩阵M。

  如上图所示,剪切后的图像宽和高分别由W和H表示,此时图像的尺寸也变了。

#图像剪切
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
import math

def clipping(img,angle_x,angle_y):
    
    img = Image.open(img)
    
    img_arr = np.array(img)
    height,width,ch = img_arr.shape
    
    W = math.ceil(width + height * abs(np.tan(angle_x)))
    H = math.ceil(height + width * abs(np.tan(angle_y)))
    
    image = np.zeros((H,W,ch))
    
    for i in range(height):
        for j in range(width):
            image[int(i + j * np.tan(angle_y))][int(j+i * np.tan(angle_x))] = img_arr[i][j]
    return image

image = clipping("erha.jpg",np.pi/4,0)

image = np.uint8(image)

plt.title("clipped_img_shuiping")
plt.imshow(image,cmap="brg")
plt.show()

image_1 = clipping("erha.jpg",0,np.pi/4)

image_1 = np.uint8(image_1)

plt.title("clipped_img_chuizhi")
plt.imshow(image_1,cmap="brg")
plt.show()

  水平剪切:
在这里插入图片描述
  垂直剪切:
在这里插入图片描述


结语

  灰度变换和仿射变换是常见的图像增广技术,后续我会在numpy系列出完之后出一个pytorch系列的图像处理。

  • 30
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
numpy 是一个优秀的数值计算库,可以实现各种数学运算和数据处理。通过 numpy 可以方便地实现仿变换。 首先,要进行仿变换,需要定义一个仿矩阵。这个矩阵可以用来描述平移、旋转、缩放和剪切等操作。在 numpy 中,可以通过二维数组来表示这个矩阵。 对于一张二维图片,可以将其表示为一个二维数组。假设图片的大小是 m 行 n 列,则可以创建一个大小为 (m, n, 2) 的三维数组,其中最后一维度表示了每个像素点的坐标。同样,可以使用一个大小为 (2, 3) 的仿矩阵来对图片进行仿变换。 在 numpy 中,可以使用函数 numpy.dot() 来进行矩阵乘法运算。对于图片的仿变换,可以先将仿矩阵与每个像素点的坐标矩阵进行乘法运算,得到变换后的坐标矩阵。然后,可以将变换后的坐标矩阵转化为整数坐标,以便在新的图片中找到对应的像素点。 具体的步骤如下: 1. 定义一个二维数组表示图片,大小为 (m, n, 2)。 2. 定义一个大小为 (2, 3) 的仿矩阵,表示平移、旋转、缩放和剪切等变换操作。 3. 对于每个像素点的坐标矩阵 (x, y),将其与仿矩阵进行乘法运算,得到变换后的坐标矩阵 (x', y')。 4. 将变换后的坐标矩阵 (x', y') 转化为整数坐标 (xi, yi)。 5. 根据转化后的整数坐标,在新的图片中找到对应的像素点,并将其填充到新的图片中。 通过以上步骤,可以实现图片的仿变换。总的来说,numpy 提供了矩阵运算的功能,可以方便地进行各种数值计算和数据处理,包括仿变换

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cherry Yuan

再多的奖励也换不回失去的头发

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值