【Numpy】图像处理——插值算法


前言

  在讲述图像的仿射变换(即几何变换)之前,我们需要先了解一下图像的插值算法(image interpolation)。比如在我们对图像进行旋转或者裁剪时,图像的部分像素信息会丢失过多。


最近邻内插

  当我们准备对图像进行缩放、旋转、几何校正时,我们就会用到图像内插的概念。
  比如,我们要把一个500500大小的图像放大到10001000的大小,如果使用numpy,我们首先要获取原图数据到数组中,再创建一个10001000的新数组用以存放放大后图像的数据。但是原图的像素点一共就是500500个,放大后的像素点个数时1000*1000,该如何把这儿大的图像所有像素都填充数据呢?
  这个时候我们可以先使用简单的最近邻插值法,按照图像放大的比例,将原图每一个像素点的颜色数值加到放大后的像素点里。

#最近邻内插(将原图像中最近邻的灰度赋给了每一个新位置)

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

# 加载图像(此处原图尺寸是180*180*3)
img = Image.open("Tom_Jerry.jpg")

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

# 将图像转化为NumPy数组
img_array = np.array(img)

print(img_array.shape)

# print(img_array)
# print(img_array.shape)
# print(img_array[0].shape)

#需要注意的是,numpy数组的形状表示的“行、列···”,因此,此处的1920对应的是行数,对应图像的高,而非宽。
#1080对应的是列数,也就是宽。
img_arr = np.zeros((1920,1080,3))
rows,cols,channels = img_arr.shape  #row指的是行数,也就是图像的高;col指的是列数,也就是图像的宽
# print(rows,cols)
# print(img_array[0][0])

for i in range(rows):
    for j in range(cols):
        img_arr[i][j] = img_array[int(i/rows*180)][int(j/cols*180)]

# print(img_arr)

img_arr = img_arr.astype(np.uint8)

#注意,plt显示灰色图时,无需改变图像格式为unit8,但是输出彩色图时,需要将numpy表示的图像数据数组转换成unit8的格式。
# uint8是无符号八位整型,表示范围是[0, 255]的整数。
plt.title("image_bigger")
plt.imshow(img_arr,cmap="brg")
plt.show()

在这里插入图片描述
(180, 180, 3)
在这里插入图片描述


双线性内插

  最近邻插值法虽然简单,但在一些图像变换中容易产生图像失真,比如严重的直边失真。我们可以选择其他方法,比如双线性内插。
  双线性内插:使用4个最近邻的灰度来计算给定位置的灰度。
  令 ( x , y ) (x,y) (x,y)表示待赋灰度值的位置坐标,令 v ( x , y ) v(x,y) v(x,y)表示灰度值。公式如下:
V ( x , y ) = a x + b y + c x y + d V(x,y)=ax+by+cxy+d V(x,y)=ax+by+cxy+d
  双线性内插:4个系数可由点 ( x , y ) (x,y) (x,y)的4个最邻近点写出的4个未知方程求出,核心思想是在水平和垂直两个方向上分别进行一次线性插值。

  如上图,双线性插值法根据点 P ( x 0 , y 0 ) P(x_0,y_0) P(x0,y0)的四个相邻点 ( x , y ) (x,y) (x,y) ( x + 1 , y ) (x+1,y) (x+1,y) ( x , y + 1 ) (x,y+1) (x,y+1) ( x + 1 , y + 1 ) (x+1,y+1) (x+1,y+1)的灰度值 L 11 L_{11} L11 L 12 L_{12} L12 L 21 L_{21} L21 L 22 L_{22} L22,通过两次插值计算得出点 P ( x 0 , y 0 ) P(x_0,y_0) P(x0,y0)的灰度值 L 0 L_0 L0
  其中,α与β分别代表与P点相关的两个距离比重。我们暂且称之为权重系数。计算公式大致如下:
α = x 0 − x \alpha = x_0-x α=x0x β = y 0 − y \beta=y_0-y β=y0y L t 1 = L 11 + β ( L 21 − L 11 ) L_{t1}=L_{11}+\beta(L_{21}-L_{11}) Lt1=L11+β(L21L11) L t 2 = L 11 + β ( L 22 − L 12 ) L_{t2}=L_{11}+\beta(L_{22}-L_{12}) Lt2=L11+β(L22L12) L 0 = α ( L t 2 − L t 1 ) = ( 1 − α ) ( 1 − β ) L 11 + ( 1 − α ) β L 21 + α ( 1 − β ) L 12 + α β L 22 L_0=\alpha(L_{t2}-L{t1})=(1-\alpha)(1-\beta)L_{11}+(1-\alpha)\beta L_{21}+\alpha(1-\beta)L_{12}+\alpha\beta L_{22} L0=α(Lt2Lt1)=(1α)(1β)L11+(1α)βL21+α(1β)L12+αβL22 L 0 = A B C L_0=ABC L0=ABC A = [ ( 1 − β ) β ] A=\begin{bmatrix}(1-\beta)\beta \end{bmatrix} A=[(1β)β] B = [ L 11 L 12 L 21 L 22 ] B=\begin{bmatrix}L_{11}&L_{12}\\L_{21}&L_{22} \end{bmatrix} B=[L11L21L12L22] C = [ ( 1 − α ) α ] T C=\begin{bmatrix}(1-\alpha)\alpha \end{bmatrix}^T C=[(1α)α]T
  公式自上而下依次是获取α与β的数值(数值范围:0~1),再利用α与β表示P点所在位置左右两点的灰度值(此处使用β在纵向上的比例表示的),最后利用α再横向上的比例表示P点的灰度值。
  整体而言,可以将P点的灰度值由三个矩阵表示(向量是特殊的矩阵),这三矩阵A、B、C分别表示纵向关系、与P点对角相邻的四个点的灰度值、横向关系。
  可以看出,这里面就是涉及到双线性内插法的实质:最邻近四个点的灰度值以及两个方向的关系(所谓双线性指的就是x与y这两个方向的空间几何关系)

#双线性内插法(此处只对灰度图/灰度值作考虑,因此,图像的通道数都是1,可以近似地将三维一通道的图片看成是二维的图片了)

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

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

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

gray_image = img.convert('L')
gray_img = np.array(gray_image)

r,c = gray_img.shape

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

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

#右侧扩展一列,直接拷贝最后一列
gray_img_new2 = np.zeros((r+1,c+1))
gray_img_new2[:,:c] =  gray_img_new1[:,:]
gray_img_new2[:,c:c+1] =  gray_img_new1[:,c-1:c]

plt.title("gray_image")
plt.imshow(gray_img,cmap="gray")
plt.show()

plt.title("gray_image_new1")
plt.imshow(gray_img_new1,cmap="gray")
plt.show()

plt.title("gray_image_new2")
plt.imshow(gray_img_new2,cmap="gray")
plt.show()

img_arr = np.zeros((1920,1080))
rows,cols = img_arr.shape

x_ratio = r/rows
y_ratio = c/cols

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

# print(img_arr)

img_arr = img_arr.astype(np.uint8)

#注意,plt显示灰色图时,无需改变图像格式为unit8,但是输出彩色图时,需要将numpy表示的图像数据数组转换成unit8的格式。
# uint8是无符号八位整型,表示范围是[0, 255]的整数。
plt.title("image_bigger")
plt.imshow(img_arr,cmap="gray")
plt.show()

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

#双线性内插法(彩色图尝试)

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

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

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

# 将图像转化为NumPy数组
img_array = np.array(img)

r,c,ch = img_array.shape

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

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


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

img_arr_new1 = img_new1.astype(np.uint8)
plt.title("img_new1")
plt.imshow(img_arr_new1,cmap="brg")
plt.show()

img_arr_new2 = img_new2.astype(np.uint8)
plt.title("img_new2")
plt.imshow(img_arr_new2,cmap="brg")
plt.show()

img_arr = np.zeros((360,360,3))
rows,cols,channels = img_arr.shape

x_ratio = r/rows
y_ratio = c/cols

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

# print(img_arr)

img_arr = img_arr.astype(np.uint8)


plt.title("image_bigger")
plt.imshow(img_arr,cmap="brg")
plt.show()

# 将numpy数组转换为PIL图像
img = Image.fromarray(img_array)

# 显示图像
img.show()

# 如果需要,可以保存图像
img.save("new_Tom.png")

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


双三次内插法

  双三次内插法考虑中心点周围16个点对它的影响(其实更高阶的插值算法本质上就是让图像在变换过程中,尤其是缩放过程中,将更大范围内的像素点的影响力辐射到每一个像素上,使得图像的像素点到像素点灰度值变化有一个递进的过程。而双三次内插法多用于ps等图像编辑工具中)

W ( x ) = { ( α + 2 ) ∣ x ∣ 3 − ( α + 3 ) ∣ x ∣ 2 + 1 , ∣ x ∣ ≤ 1 α ∣ x ∣ 3 − 5 α ∣ x ∣ 2 + 8 α ∣ x ∣ − 4 α , 1 < ∣ x ∣ < 2 0 , ∣ x ∣ ≥ 2 W(x)=\begin{cases}(\alpha+2)|x|^3-(\alpha+3)|x|^2+1,|x|\leq1 \\ \alpha|x|^3-5\alpha|x|^2+8\alpha|x|-4\alpha,1<|x|<2 \\ 0,|x|\geq2 \end{cases} W(x)= (α+2)x3(α+3)x2+1,x1αx35αx2+8αx4α,1<x<20,x2 L t 1 = W ( 1 + β ) L 11 + W ( β ) L 21 + W ( 1 − β ) L 31 + W ( 2 − β ) L 41 L_{t1}=W(1+\beta)L_{11}+W(\beta)L_{21}+W(1-\beta)L_{31}+W(2-\beta)L_{41} Lt1=W(1+β)L11+W(β)L21+W(1β)L31+W(2β)L41 L t 2 = W ( 1 + β ) L 12 + W ( β ) L 22 + W ( 1 − β ) L 32 + W ( 2 − β ) L 42 L_{t2}=W(1+\beta)L_{12}+W(\beta)L_{22}+W(1-\beta)L_{32}+W(2-\beta)L_{42} Lt2=W(1+β)L12+W(β)L22+W(1β)L32+W(2β)L42 L t 3 = W ( 1 + β ) L 13 + W ( β ) L 23 + W ( 1 − β ) L 33 + W ( 2 − β ) L 43 L_{t3}=W(1+\beta)L_{13}+W(\beta)L_{23}+W(1-\beta)L_{33}+W(2-\beta)L_{43} Lt3=W(1+β)L13+W(β)L23+W(1β)L33+W(2β)L43 L t 4 = W ( 1 + β ) L 14 + W ( β ) L 24 + W ( 1 − β ) L 34 + W ( 2 − β ) L 44 L_{t4}=W(1+\beta)L_{14}+W(\beta)L_{24}+W(1-\beta)L_{34}+W(2-\beta)L_{44} Lt4=W(1+β)L14+W(β)L24+W(1β)L34+W(2β)L44 L 0 = W ( 1 + α ) L t 1 + W ( α ) L t 2 + W ( 1 − α ) L t 3 + W ( 2 − α ) L t 4 L_0=W(1+\alpha)L_{t1}+W(\alpha)L_{t2}+W(1-\alpha)L_{t3}+W(2-\alpha)L_{t4} L0=W(1+α)Lt1+W(α)Lt2+W(1α)Lt3+W(2α)Lt4 A = [ W ( 1 + β ) W ( β ) W ( 1 − β ) W ( 2 − β ) ] A=\begin{bmatrix} W(1+\beta)W(\beta)W(1-\beta)W(2-\beta) \end{bmatrix} A=[W(1+β)W(β)W(1β)W(2β)] A = [ L 11 L 12 L 13 L 14 L 21 L 22 L 23 L 24 L 31 L 32 L 33 L 34 L 41 L 42 L 43 L 44 ] A=\begin{bmatrix} L_{11}&L_{12}&L_{13}&L_{14} \\ L_{21}&L_{22}&L_{23}&L_{24} \\ L_{31}&L_{32}&L_{33}&L_{34} \\ L_{41}&L_{42}&L_{43}&L_{44} \end{bmatrix} A= L11L21L31L41L12L22L32L42L13L23L33L43L14L24L34L44 A = [ W ( 1 + α ) W ( α ) W ( 1 − α ) W ( 2 − α ) ] T A=\begin{bmatrix} W(1+\alpha)W(\alpha)W(1-\alpha)W(2-\alpha) \end{bmatrix}^T A=[W(1+α)W(α)W(1α)W(2α)]T
  计算公式基本上和双线性差不多,除了多了些像素点以及一个Bicubic(双三次内插)基函数,其中a是一个自由参数,一般取-0.5即可。那我们直接上代码吧!

#双三次内插法

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

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

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

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

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

x_ratio = r/rows
y_ratio = c/cols

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

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

def w(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

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

#底部扩展两行,直接拷贝最后一行
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,:]

img_new4_arr = img_new4.astype(np.uint8)
plt.title("img_new4")
plt.imshow(img_new4_arr,cmap="brg")
plt.show()
    
#此处需注意,根据上述公式编写代码,双线性内插法实现从纵向上进行运算,再从横向上运算。
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(1+beita),w(beita),w(1-beita),w(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(1+alpha),w(alpha),w(1-alpha),w(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_arr[i][j] = (1-alpha)*(1-beita)*gray_img[x,y] + (1-alpha)*beita*gray_img[x,y+1] + alpha*(1-beita)*gray_img[x+1,y] + alpha*beita*gray_img[x+1,y+1]

# 图像的数据类型应为np.uint8,其值的范围为0-255。
img_array = img_array.astype(np.uint8)


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

# 将numpy数组转换为PIL图像
img = Image.fromarray(img_array)

# 显示图像
img.show()

# 如果需要,可以保存图像
img.save("new_Jerry.png")

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

在这里插入图片描述

#双三次内插法(我们再看看灰度图有什么效果)

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

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

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

gray_image = img.convert('L')
gray_img = np.array(gray_image)

gray = gray_img.astype(np.uint8)
plt.title("image_gray")
plt.imshow(gray,cmap="gray")
plt.show()


r,c = gray_img.shape

img_array = np.zeros((360,360))
rows,cols = img_array.shape

x_ratio = r/rows
y_ratio = c/cols

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

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

def w(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

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

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

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


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

#左侧扩展两列,直接拷贝第一列
img_new4 = np.zeros((r+4,c+4))
img_new4[:,2:c+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="gray")
plt.show()
    
#此处需注意,根据上述公式编写代码,双线性内插法实现从纵向上进行运算,再从横向上运算。
for j in range(cols):
    y = math.floor(j * y_ratio) + 2
    beita = j * y_ratio - math.floor(j * y_ratio)
    A = np.array([w(1+beita),w(beita),w(1-beita),w(2-beita)]).reshape((1,4))
    for i in range(rows):
        x = math.floor(i * x_ratio) + 2
        alpha = i * x_ratio - math.floor(i * x_ratio)
        C = np.array([w(1+alpha),w(alpha),w(1-alpha),w(2-alpha)]).reshape((4,1))
        B = img_new4[x-1:x+3,y-1:y+3]
        img_array[i][j] = np.dot(np.dot(A,B),C)
#         img_arr[i][j] = (1-alpha)*(1-beita)*gray_img[x,y] + (1-alpha)*beita*gray_img[x,y+1] + alpha*(1-beita)*gray_img[x+1,y] + alpha*beita*gray_img[x+1,y+1]

# 图像的数据类型应为np.uint8,其值的范围为0-255。
img_array = img_array.astype(np.uint8)


plt.title("image_bigger")
plt.imshow(img_array,cmap="gray")
plt.show()

# 将numpy数组转换为PIL图像
img = Image.fromarray(img_array)

# 显示图像
img.show()

# 如果需要,可以保存图像
img.save("new_bilibili.png")

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


结语

  虽然本次实验中选用的彩色图经过双三次内插法效果很怪,但不难发现,被我们放大后的灰度图,经过双三次内插法后,轮廓更加明显了。

  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Cherry Yuan

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

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

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

打赏作者

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

抵扣说明:

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

余额充值