前言
在讲述图像的仿射变换(即几何变换)之前,我们需要先了解一下图像的插值算法(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个未知方程求出,核心思想是在水平和垂直两个方向上分别进行一次线性插值。
![](https://img-blog.csdnimg.cn/direct/7ce14bc4337d4d28b831d39bac80052e.png)
如上图,双线性插值法根据点
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
α=x0−x
β
=
y
0
−
y
\beta=y_0-y
β=y0−y
L
t
1
=
L
11
+
β
(
L
21
−
L
11
)
L_{t1}=L_{11}+\beta(L_{21}-L_{11})
Lt1=L11+β(L21−L11)
L
t
2
=
L
11
+
β
(
L
22
−
L
12
)
L_{t2}=L_{11}+\beta(L_{22}-L_{12})
Lt2=L11+β(L22−L12)
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=α(Lt2−Lt1)=(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等图像编辑工具中)
![](https://img-blog.csdnimg.cn/direct/6963e619187a422db92c005029092e20.png)
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)∣x∣3−(α+3)∣x∣2+1,∣x∣≤1α∣x∣3−5α∣x∣2+8α∣x∣−4α,1<∣x∣<20,∣x∣≥2
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")
结语
虽然本次实验中选用的彩色图经过双三次内插法效果很怪,但不难发现,被我们放大后的灰度图,经过双三次内插法后,轮廓更加明显了。