抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

计算机视觉学习 morphing

原理和示例来自 xjtu 苏有歧老师的课程ppt
项目代码地址: https://gitee.com/a9ia/cv-practice (不保证是否能保持公开,如有需求可以通过我的qq联系我)

前置知识

  • 清楚图像存储是用rgb数组矩阵
  • 初等数学
  • 矩阵知识

什么是morphing

简单来说就是对图像的形状和颜色进行平均,通过对形状、颜色平均系数的调整实现两张图片互相变形。

例如:

morphingEx

用处

可以用于图像变换,如将一张图片1转换为另一张图片1,只需要将图像2形状比重设为1,颜色设为0,图像1形状比重设为0,颜色设为1。

也可以用于生成常见的人们从小到大相片变换的视频

原理

morphing

  1. 设置对应标注点,用于标注两个图片相同的特征
    • 怎么获得对应点?暂时手动标注,存在自动生成的算法,后续可能添加
  2. 特征点连接成三角形数组
  3. 根据形状权重,对标注点进行形状平均变换,获得平均图形
  4. 平均图形上分别对两个图像进行后向插值
    • 什么叫后向插值?即用平均图形的每一个点投影到原图像的对应点,取对应点的值,这样可以避免原图像对应到生成图像的同一个点,最终生成图像中有一部分没有颜色值。
    • 如何进行插值?通过重心坐标插值。
    • 什么叫重心坐标?根据数学原理三角形的内部每一个点(point)都可以用三个顶点(A, B, C)的向量和表示:point = aA + bB + cC; a + b + c = 1
    • 怎么获得重心坐标?上一条的公式 point = aA + bB + cC 实际上相当于两个方程式,所以可以通过求(加上和为1在内)的三个方程式获得(a, b, c)
    • 将求方程式的解转换为解方程组 [Ax, Bx, Cx; Ay, By, Cy; 1, 1, 1]*[a;b;c;] = [point_x, point_y, 1] 的特征值
    • 综上,平均图形和三角形数组生成重心坐标,然后用原图像对应的三角形顶点获取平均图形这个点的颜色,最终得到两个填满颜色的平均图形
  5. 用生成的填满颜色的两个平均图形,通过颜色权重对两个图像进行加权

代码部分

调整图片尺寸

这部分是为了进行点对应时不会出现在图片之外的问题

1
2
3
4
5
import cv2
cover_path = './example1.jpg'
im1 = cv2.imread(cover_path)
im2 = cv2.resize(im1, (300,300))
cv2.imwrite('./p1.jpg', im2)

手动设置标注点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from PIL import Image
from pylab import *
import numpy as np
import matplotlib.pyplot as plt

im1 = array(Image.open('p1.jpg'))
im2 = array(Image.open('p2.jpg'))
figure()
subplot(2, 1, 1)
imshow(im1)
subplot(2, 1, 2)
imshow(im2)
# 获取点集坐标
print ('Please click 40 points on 2 pictures in sequence')
x =ginput(60, 400)
print ('you clicked:',x)
plt.close()
# 处理<依次>点击两张图片对应点生成的集合
p1 = np.array(x[:: 2]) # 图像 1 点集
p2 = np.array(x[1:: 2]) # 图像 2 点集

# 写入文件保存
np.savetxt(fname="p1.csv", X=p1, fmt="%f", delimiter=",")
np.savetxt(fname="p2.csv", X=p2, fmt="%f", delimiter=",")

进行morphing变换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
'''
Date: 2022-06-27 14:57:55
LastEditTime: 2022-06-27 16:06:43
'''
from PIL import Image
from pylab import *
import numpy as np
import cv2
from scipy.spatial import Delaunay
import matplotlib.pyplot as plt

def morphed_im(im1, im2, im1_pts, im2_pts, warp_func, dissolve_func):
"""生成 morphing 图像

Args:
im1 (ndarray): 原图像1的像素数组
im2 (ndarray): 原图像2的像素数组
im1_pts (ndarray): 原图像1上标注的点
im2_pts (ndarray): 原图像2上标注的点
warp_func (float): 向图像2变形(warping)的权重
dissolve_func (float): 向图像2取色的权重

Return:
morphed_im(ndarray): 生成的图像
"""

if (np.size(im1, 2) != 3 or np.size(im2, 2) != 3 or np.size(im1, 1) != np.size(im2, 1) or np.size(im1, 2) != np.size(im2, 2)):
return

xsz = np.size(im1, 0)
ysz = np.size(im1, 1)
pts_mean = (1 - warp_func) * im1_pts + warp_func * im2_pts
tri = Delaunay(pts_mean)

# 显示三角分割
figure()
# 图形1的三角分割
sub1 = subplot(2, 2, 1)
plt.imshow(im1)
print(im1_pts)
sub1.triplot(im1_pts[:, 0], im1_pts[:, 1], tri.simplices.copy())
sub1.plot(im1_pts[:, 0], im1_pts[:, 1], 'o')

# 图形2的三角分割
sub2 = subplot(2, 2, 2)
plt.imshow(im2)
sub2.triplot(im2_pts[:, 0], im2_pts[:, 1], tri.simplices.copy())
sub2.plot(im2_pts[:, 0], im2_pts[:, 1], 'o')

# 平均三角分割的形状图形
sub_mean = subplot(2, 2, 3)

sub_mean.triplot(pts_mean[:, 0], ysz - pts_mean[:, 1], tri.simplices.copy())
sub_mean.plot(pts_mean[:, 0], ysz - pts_mean[:, 1], 'o')

# 关闭三角分割图片
plt.pause(10)
plt.close()

# 获取重心坐标参数
barycentric = zeros((xsz * ysz, 3), dtype=float32)
tris = tri.simplices
xy_mesh = np.array(list((x,y) for x in range(xsz) for y in range(ysz))) # 生成网格

# 网格点对应像素点所在三角形在平均图形坐标点集中的索引
triang = tri.find_simplex([xy_mesh])[0]
for i in arange(0, xsz * ysz):
xp,yp = xy_mesh[i]
trindex = tri.find_simplex([xp, yp])

if trindex == -1:
# 当前像素点不在三角分割内时不计算入生成图形内
barycentric[i, :] = [float('nan'), float('nan'), float('nan')]
continue

ax = pts_mean[tris[trindex, 0], 0]
bx = pts_mean[tris[trindex, 1], 0]
cx = pts_mean[tris[trindex, 2], 0]
ay = pts_mean[tris[trindex, 0], 1]
by = pts_mean[tris[trindex, 1], 1]
cy = pts_mean[tris[trindex, 2], 1]


A = np.mat([[ax, bx, cx], [ay, by, cy], [1, 1, 1]])
b = np.mat([[xp], [yp], [1]])
mul = inv(A) * b
barycentric[i] = [mul[0,0], mul[1,0], mul[2,0]]

# 用重心坐标计算生成图像每个像素对应的原图像像素的坐标
im1_crsp = zeros((xsz * ysz, 3), dtype=float32)
for i in arange(0, xsz * ysz):
trindex = triang[i]
if trindex == -1:
im1_crsp[i] = [255,255,255]
continue

ax = im1_pts[tris[trindex, 0], 0]
bx = im1_pts[tris[trindex, 1], 0]
cx = im1_pts[tris[trindex, 2], 0]
ay = im1_pts[tris[trindex, 0], 1]
by = im1_pts[tris[trindex, 1], 1]
cy = im1_pts[tris[trindex, 2], 1]

A = np.mat([[ax, bx, cx], [ay, by, cy], [1, 1, 1]])

X = A * np.mat(barycentric[i]).reshape(3, 1)
X = uint8(around(X))
im1_crsp[i] = im1[X[1, 0], X[0, 0]] # 这个地方xy是反着的,因为不知名原因im1的像素点的索引和颜色值是y=x的直线对称的

# 用重心坐标计算生成图像每个像素对应的原图像像素的坐标
im2_crsp = zeros((xsz * ysz, 3), dtype=float32)
for i in arange(0, xsz * ysz):
trindex = triang[i]
if trindex == -1:
im2_crsp[i] = [255,255,255]
continue

ax = im2_pts[tris[trindex, 0], 0]
bx = im2_pts[tris[trindex, 1], 0]
cx = im2_pts[tris[trindex, 2], 0]
ay = im2_pts[tris[trindex, 0], 1]
by = im2_pts[tris[trindex, 1], 1]
cy = im2_pts[tris[trindex, 2], 1]

A = [[ax, bx, cx], [ay, by, cy], [1, 1, 1]]

X = A * np.mat(barycentric[i]).reshape(3, 1)
X = uint8(around(X))
im2_crsp[i] = im2[X[1,0], X[0,0]]

print("finish warping")

morphed_im = zeros((xsz * ysz, 3), dtype=float32).reshape(xsz, ysz, 3)
morphed_im1 = zeros((xsz * ysz, 3), dtype=uint8).reshape(xsz, ysz, 3)
morphed_im2 = zeros((xsz * ysz, 3), dtype=uint8).reshape(xsz, ysz, 3)

# 将前面计算好的平均图像的像素值填充到数组中
for i in arange(0, xsz * ysz):
xp,yp = xy_mesh[i]
morphed_im1[yp, xp] = im1_crsp[i]
morphed_im2[yp, xp] = im2_crsp[i]


morphed_im = (1 - dissolve_func) * morphed_im1 + dissolve_func * morphed_im2
morphed_im = uint8(around(morphed_im))
print("finish morphing")
figure()
sub1 = subplot(2, 2, 1)
plt.imshow(morphed_im1)
sub2 = subplot(2, 2, 2)
plt.imshow(morphed_im2)
sub2 = subplot(2, 2, 3)
plt.imshow(morphed_im)
sub_mean = subplot(2, 2, 4)
plt.imshow(morphed_im)
plt.pause(10)
plt.close()
return morphed_im

if __name__ == '__main__':
# 展示两张图片
im1 = array(Image.open('p1.jpg'))
im2 = array(Image.open('p2.jpg'))
p1 = np.array(np.loadtxt(fname="p1.csv", dtype=np.float32, delimiter=","))
p2 = np.array(np.loadtxt(fname="p2.csv", dtype=np.float32, delimiter=","))
im = morphed_im(im1, im2, p1, p2, 0.5, 0.5)
cv2.cvtColor(im, cv2.COLOR_BGR2RGB, im); # 转换色域
cv2.imwrite('./res.jpg', im)

结果

0.5倍率下

标注完成生成的三角形图像

image-20220627161221200

morphing变换后的平均图像,以及合成的图像

image-20220627161239822

平均图像序列

1
2
3
4
5
6
7
8
9
10
11
12
13
if __name__ == '__main__':
# 展示两张图片
im1 = array(Image.open('p1.jpg'))
im2 = array(Image.open('p2.jpg'))
p1 = np.array(np.loadtxt(fname="p1.csv", dtype=np.float32, delimiter=","))
p2 = np.array(np.loadtxt(fname="p2.csv", dtype=np.float32, delimiter=","))
# im = morphed_im(im1, im2, p1, p2, 0.5, 0.5)
# cv2.cvtColor(im, cv2.COLOR_BGR2RGB, im); # 转换色域
# cv2.imwrite('./res.jpg', im)
for i in range(10):
im = morphed_im(im1, im2, p1, p2, i/10.0, i/10.0)
cv2.cvtColor(im, cv2.COLOR_BGR2RGB, im); # 转换色域
cv2.imwrite('./res/res_no'+str(i)+'.jpg', im)

image-hechengList

评论