图像扭曲/逆扭曲
- 0. 前言
- 1. 使用 scikit-image warp() 函数执行图像变换
- 1.1 scikit-image warp() 函数原理
- 1.2 利用 warp() 函数实现图像变换
- 2. 漩涡变换详解
- 2.1 旋涡变换原理
- 2.2 使用 scikit-image warp() 实现旋涡变换
- 2.3 使用 scipy.ndimage 实现漩涡变换
- 3. 使用 scikit-image 实现弹性变形
- 3.1 弹性变形原理
- 3.2 实现弹性变形
- 小结
- 系列链接
0. 前言
我们已经知道,可以将图像变换分为线性变换与非线性变换两类,在《图像线性变换》一节中,我们介绍了如何在图像上应用欧几里得变换(例如,旋转、反射)和仿射转换。在本节中,我们将进一步学习如何利用 scikit-image
库执行图像变换,除了线性变换外,本节着重讲解了如何执行旋涡变换、弹性变换等非线性变换。
1. 使用 scikit-image warp() 函数执行图像变换
2D
线性几何变换是点转换,它们会被应用于图像中的每个像素,以得到变换后的输出图像。本节中,我们将介绍如何使用 scikit-image
库中的 warp()
函数来执行图像变换。
1.1 scikit-image warp() 函数原理
对于变换后的输出图像中的像素值 f ( x ′ , y ′ ) f(x',y') f(x′,y′) 可以使用以下方程式在输入图像对应位置 ( u , v ) (u, v) (u,v) 处获取,将得到的像素值赋值给输出图像即可得到结果:
f ( x ′ , y ′ ) = g ( u , v ) f(x',y')=g(u,v) f(x′,y′)=g(u,v)
其中,
x
′
x'
x′ 和
y
′
y'
y′ 分别表示沿
x
x
x 和
y
y
y 轴的变换
x
(
u
,
v
)
=
x
′
x(u,v)= x'
x(u,v)=x′,
y
(
u
,
v
)
=
y
’
y(u,v)=y’
y(u,v)=y’,我们也可以使用反扭曲来实现图像变换。使用 warp()
函数是更通用的图像变换方式,可用于实现线性(例如,使用矩阵乘法来实现的变换)和非线性图像变换。使用 warp()
函数执行图像变换,我们需要提供(反向)变换函数,而非变换矩阵。
1.2 利用 warp() 函数实现图像变换
根据 scikit-image
文档,我们得知 warp()
函数可以通过使用如下输入参数调用,利用该函数我们可以以更通用的方式实现图像变换(函数的输入参数如下):
skimage.transform.warp(image, inverse_map, map_args={}, output_shape= None, order=1, mode='constant', cval=0.0, clip=True, preserve_range=False)
warp()
函数根据给定的坐标变换函数 inverse_map
扭曲图像 image
。接下来,我们实际应用 warp()
函数实现图像变换。
(1) 首先,导入所有所需的库,从 scikit-image
库的 transform
模块中导入 warp
函数:
from skimage.io import imread
from skimage.transform import warp
import matplotlib.pylab as plt
(2) 接下来,定义 translate()
函数,该函数用于在 warp()
函数中作为 inverse_map
参数实现像素坐标变换:
def translate(xy, t_x, t_y):
xy[:, 0] -= t_y
xy[:, 1] -= t_x
return xy
(3) 读取输入图像,并将输入图像与变换函数一起传入 warp()
函数作为参数:
im = imread('1.png')
im = warp(im, translate, map_args={'t_x':-250, 't_y':200}) # 创建字典用于变换参数
plt.imshow(im)
plt.title('Translated image', size=20)
plt.show()
下图显示了程序执行后的输出,即将变换函数应用于输入图像后得到的输出图像:
2. 漩涡变换详解
在上一小节中,我们学习了如何执行图像的线性变换,在本节中,我们将学习如何实现图像的非线性变换——漩涡变换 (swirl transform
)。
2.1 旋涡变换原理
我们假设输出图像中的坐标为
(
x
,
y
)
(x,y)
(x,y),漩涡变换的反向映射首先应计算其相对于中心
(
x
0
,
y
0
)
(x_0,y_0)
(x0,y0) 的极坐标:
θ
=
a
r
c
t
a
n
(
y
x
)
ρ
=
(
x
−
x
0
)
2
+
(
y
−
y
0
)
2
\theta=arctan(\frac y x) \\ ρ=\sqrt {(x-x_0)^2+(y-y_0)^2}
θ=arctan(xy)ρ=(x−x0)2+(y−y0)2
然后,根据以下公式对其进行变换:
r
=
l
n
2
⋅
r
a
d
i
o
u
s
5
θ
′
=
ψ
+
s
⋅
e
−
ρ
r
+
θ
r=ln2\cdot \frac {radious} 5 \\ \theta'=ψ+s\cdot e^{-\frac ρ r+\theta}
r=ln2⋅5radiousθ′=ψ+s⋅e−rρ+θ
其中
ψ
ψ
ψ 表示旋转角度,
s
s
s 表示像素值强度。接下来,我们利用 warp
() 函数实现漩涡变换。
2.2 使用 scikit-image warp() 实现旋涡变换
(1) 首先,导入所需库,并定义 swirl()
函数,该函数根据上述数学公式实现像素漩涡变换:
from skimage.io import imread
from skimage.transform import warp
import matplotlib.pylab as plt
import numpy as np
def swirl(xy, x0, y0, R):
r = np.sqrt((xy[:,1]-x0)**2 + (xy[:,0]-y0)**2)
a = np.pi*r / R
xy[:, 1] = (xy[:, 1]-x0)*np.cos(a) + (xy[:, 0]-y0)*np.sin(a) + x0
xy[:, 0] = -(xy[:, 1]-x0)*np.sin(a) + (xy[:, 0]-y0)*np.cos(a) + y0
return xy
(2) 读取输入图像,然后将图像与 swirl()
函数输入参数调用 warp()
函数,以将非线性变换应用于输入图像:
im = imread('1.png')
print(im.shape)
im1 = warp(im, swirl, map_args={'x0':250, 'y0':350, 'R':600})
plt.figure(figsize=(20,10))
plt.subplot(121), plt.imshow(im), plt.axis('off'), plt.title('Input image', size=10)
plt.subplot(122), plt.imshow(im1), plt.axis('off'), plt.title('Output image', size=10)
plt.show()
函数 swirl()
接受参数 xy
、x0
、y0
和 r
,并根据 warp()
函数的 map_args
参数进行设定。
执行以上代码,可以得到以下结果图像:
需要注意的是,x0
、y0
和 R
参数传递给 swirl()
函数,通过修改这些参数值,可以观察到不同参数对输出图像的影响。
2.3 使用 scipy.ndimage 实现漩涡变换
在上一小节中,我们学习了如何使用 scikit-image warp()
实现旋涡变换;在本小节中,我们将继续学习如何使用 scipy.ndimage
的 geometric_transform()
函数实现非线性漩涡变换。
(1) 首先,导入所需库,并定义实现漩涡变换所需的函数 apply_swirl()
:
from scipy import ndimage as ndi
from skimage.io import imread
from skimage.color import rgb2gray
import matplotlib.pylab as plt, numpy as np
def apply_swirl(xy, x0, y0, R):
r = np.sqrt((xy[1]-x0)**2 + (xy[0]-y0)**2)
a = np.pi*r / R
return ((xy[1]-x0)*np.cos(a) + (xy[0]-y0)*np.sin(a) + x0, -(xy[1]-x0)*np.sin(a) + (xy[0]-y0)*np.cos(a) + y0)
函数 apply_swirl()
接受参数 xy
、x0
、y0
和 r
,并根据 geometric_transform()
函数的 extra_arguments
参数进行设定。
(2) 接下来,读取输入图像,并将其转换为灰度图像,从 scipy.ndimage
模块中调用 geometric_transform()
函数,然后将 apply_swirl()
函数作为参数传递给 geometric_transform()
函数,用于执行漩涡变换。
im = rgb2gray(imread('1.png'))
print(im.shape)
im1 = ndi.geometric_transform(im, apply_swirl, extra_arguments=(300, 200, 620))
(3) 最后,绘制原始图像和变换后的图像:
plt.figure(figsize=(20,10))
plt.gray()
plt.subplot(121), plt.imshow(im), plt.axis('off'), plt.title('Input image', size=10)
plt.subplot(122), plt.imshow(im1), plt.axis('off'), plt.title('Output image', size=10)
plt.show()
3. 使用 scikit-image 实现弹性变形
3.1 弹性变形原理
对图像应用位移场( displacement fields
)可以产生图像畸变,通过位移场可以计算每个像素原始位置的新目标位置。位置
(
x
,
y
)
(x,y)
(x,y) 处的新目标位置根据原位置得出,例如,如果
Δ
X
(
x
,
y
)
=
1
ΔX(x,y)=1
ΔX(x,y)=1、
Δ
y
(
x
,
y
)
=
0
Δy(x,y)=0
Δy(x,y)=0 这意味着每个像素的新位置都向右移动 1
。我们可以通过以下步骤创建图像弹性变形:
- 首先,生成随机位移场,即
Δ
y
(
x
,
y
)
=
r
a
n
d
(
−
1
,
+
1
)
Δy(x,y)= rand(-1,+1)
Δy(x,y)=rand(−1,+1)、
Δ
x
(
x
,
y
)
=
r
a
n
d
(
−
1
,
+
1
)
Δx(x,y)= rand(-1,+1)
Δx(x,y)=rand(−1,+1),其中
r
a
n
d
(
−
1
,
+
1
)
rand(-1,+1)
rand(−1,+1) 用于生成一个在
-1
和1
之间的随机数 - 然后用标准偏差为 σ σ σ 的高斯函数(以像素为单位)与 Δ x Δx Δx、 Δ y Δy Δy 执行卷积
- 如果
σ
σ
σ 很大,因为随机值的平均值为
0
,则将导致结果值非常小,如果我们将位移场进行归一化,则位移场将接近具有随机方向的常数;如果 σ σ σ 很小,则位移场在归一化后近似于完全随机的场。如果 σ σ σ 值大小合适,位移场则类似弹性变形,其中 σ σ σ 为弹性系数 - 然后将位移场乘以控制变形强度的缩放因子 α α α
3.2 实现弹性变形
(1) 首先导入所需的库,然后自定义函数实现弹性变形函数 elastic_transform()
:
import numpy as np
import matplotlib.pylab as plt
from skimage.color import rgb2gray
from scipy.ndimage import gaussian_filter, map_coordinates
def elastic_transform(image, alpha, sigma):
random_state = np.random.RandomState(None)
h, w = image.shape
dx = gaussian_filter((random_state.rand(*image.shape) * 2 - 1), sigma, mode="constant", cval=0) * alpha
dy = gaussian_filter((random_state.rand(*image.shape) * 2 - 1), sigma, mode="constant", cval=0) * alpha
x, y = np.meshgrid(np.arange(w), np.arange(h))
indices = np.reshape(y+dy, (-1, 1)), np.reshape(x+dx, (-1, 1))
distored_image = map_coordinates(image, indices, order=1, mode='reflect')
return distored_image.reshape(image.shape)
函数 map_coordinates()
通过插值将输入阵列映射到新的坐标,坐标的数组用于为输出中的每个点找到输入中的相应坐标,这些坐标处的输入值由调用函数时所用的插值参数 order
确定。
(2) 使用 matplotlib
的 imread()
函数读取输入图像,并使用 skimage.color
的 rgb2gray()
函数将其转换为灰度图像:
img = rgb2gray(plt.imread('2.png'))
(3) 调用 elastic_transform()
函数将弹性变形应用于图像:
img1 = elastic_transform(img, 100, 4)
(4) 绘制原始输入和应用弹性形变后的输出图像:
plt.figure(figsize=(20,10))
plt.subplot(121)
plt.imshow(img, cmap='gray')
plt.axis('off'), plt.title('Original', size=10)
plt.subplot(122)
plt.imshow(img1, cmap='gray')
plt.axis('off'), plt.title('Deformed', size=10)
plt.tight_layout()
小结
在本节中,我们学习了如何使用 scikit-image
库中的通用图像变换函数 warp()
,该函数不仅可以实现常见的图像线性变换,而且能够实现包括旋涡变换、弹性变换等在内的非线性变换。在实际应用 warp()
函数之前,我们还学习了不同非线性变换的基本数学原理,以分辨其与线性变换之间的差别。
系列链接
Python图像处理【1】图像与视频处理基础
Python图像处理【2】探索Python图像处理库
Python图像处理【3】Python图像处理库应用
Python图像处理【4】图像线性变换