概述
图像到图像的映射
- 单应性变换
- 线性变换
- 仿射变换
- 图像扭曲
- 图像中的图像
- 使用三角形仿射弯曲效果
- 分段仿射扭曲
单应性变换
1、单应性变换时将一个平面内的点映射到另一个平面内的二维投影变换。下图中的两幅图片,存在对应的点,如第一幅桥顶的A点对应第二幅图桥顶的A’点,但是这两幅图桥顶的点在图像中的左边都不一样,要找到这两个坐标的对应关系,就用到了单应性变换。单应性变换就是一个平面到另一个平面的变换关系
2、假设A(x, y)、A’(x’, y’),那么x和x’、y和y’就存在一种变换关系,定义变换关系矩阵为H,则A点和A’点的变换关系公式定义为下图公式。从下图的关系可以看出,x、y、x’、y’是可以比较容易得到的,所以关键问题是如何去求解H矩阵。由于不同的图像变换方式,H矩阵的取值会有所不同。下面主要讲讲线性变换和仿射变换
线性变换
1、线性变换需要满足一下三点要求:
- 变换前是直线,变换后还是直线
- 直线保持比例不变
- 变换前是原点的,变换后依然是原点
例如,一个矩形,经过线性变换后(旋转90度),原点(红色点)还是在原点,位置没有偏移,直线还是直线,且直线的比例没有发生变化
2、将上面的单应性变换矩阵展开后得到x和x’、y和y’的关系如下
3、再将其转成矩阵后可以表示为下图的形式,我们可以使用SVD算法找到H矩阵的最小二乘解
4、直接线性变换算法(SVD),将下面的方法放入homography.py文件中
def H_from_points(fp, tp):
"""使用线性DLT方法,计算单应性矩阵H,使fp映射到tp。点自动进行归一化"""
if fp.shape != tp.shape:
raise RuntimeError("number of points do not match")
# 对点进行归一化(对数值计算很重要)
# --- 映射起始点 ---
m = mean(fp[:2], axis=1)
maxstd = max(std(fp[:2], axis=1)) + 1e-9
C1 = diag([1 / maxstd, 1 / maxstd, 1])
C1[0][2] = -m[0] / maxstd
C1[1][2] = -m[1] / maxstd
fp = dot(C1, fp)
# --- 映射对应点 ---
m = mean(tp[:2], axis=1)
maxstd = max(std(tp[:2], axis=1)) + 1e-9
C2 = diag([1 / maxstd, 1 / maxstd, 1])
C2[0][2] = -m[0] / maxstd
C2[1][2] = -m[1] / maxstd
tp = dot(C2, tp)
# 创建用于线性方法的矩阵,对于每个对应对,在矩阵中会出现两行数值
nbr_correspondences = fp.shape[1]
A = zeros((2 * nbr_correspondences, 9))
for i in range(nbr_correspondences):
A[2 * i] = [-fp[0][i], -fp[1][i], -1, 0, 0, 0,
tp[0][i] * fp[0][i], tp[0][i] * fp[1][i], tp[0][i]]
A[2 * i + 1] = [0, 0, 0, -fp[0][i], -fp[1][i], -1,
tp[1][i] * fp[0][i], tp[1][i] * fp[1][i], tp[1][i]]
U, S, V = linalg.svd(A)
H = V[8].reshape((3, 3))
# 反归一化
H = dot(linalg.inv(C2), dot(H, C1))
# 归一化,然后返回
return H / H[2, 2]
仿射变换
1、仿射变换需要满足一下两点条件:
- 变换前是直线,变换后还是直线
- 直线的比例保持不变
从上面的条件可以发现,仿射变换和线性变换相比少了一个原点不变的条件,所以仿射变换相当于线性变换加平移。例如:(图片来源网络)
2、仿射变换的变换矩阵定义为下图公式,H矩阵最后一排h7=h8=0,h9=1,且权重w=1。c和 f 表示x和y的平移量
3、计算仿射矩阵H的方法,将下面的方法放到homography.py文件中。其中tp是变换后的坐标,fp是变换前的坐标,通过计算H,使得tp是fp通过仿射变换矩阵H得到的,然后返回H
def Haffine_from_points(fp, tp):
"""计算H仿射变换,使得tp是fp经过仿射变换H得到的"""
if fp.shape != tp.shape:
raise RuntimeError('number of points do not match')
# 对点进行归一化(对数值计算很重要)
# --- 映射起始点 ---
m = mean(fp[:2], axis=1)
maxstd = max(std(fp[:2], axis=1)) + 1e-9
C1 = diag([1 / maxstd, 1 / maxstd, 1])
C1[0][2] = -m[0] / maxstd
C1[1][2] = -m[1] / maxstd
fp_cond = dot(C1, fp)
# --- 映射对应点 ---
m = mean(tp[:2], axis=1)
C2 = C1.copy() # 两个点集,必须都进行相同的缩放
C2[0][2] = -m[0] / maxstd
C2[1][2] = -m[1] / maxstd
tp_cond = dot(C2, tp)
# 因为归一化后点的均值为0,所以平移量为0
A = concatenate((fp_cond[:2], tp_cond[:2]), axis=0)
U, S, V = linalg.svd(A.T)
# 如Hartley和Zisserman著的Multiplr View Geometry In Computer,Scond Edition所示,
# 创建矩阵B和C
tmp = V[:2].T
B = tmp[:2]
C = tmp[2:4]
tmp2 = concatenate((dot(C, linalg.pinv(B)), zeros((2, 1))), axis=1)
H = vstack((tmp2, [0, 0, 1]))
# 反归一化
H = dot(linalg.inv(C2), dot(H, C1))
return H / H[2, 2]
图像扭曲
1、仿射变换可以用于很多的应用,图像扭曲就是其中一个应用。当仿射变换应用于图像扭曲时,变换矩阵定义下图公式,其中向量s指定了变换的尺度,如果s=1,那么该变换能够保持距离不变,此时变换为刚体变换,θ表示旋转(扭曲)的角度
2、图像扭曲代码
from numpy import *
from matplotlib.pyplot import *
from scipy import ndimage
from PIL import Image
im = array(Image.open('img/jmu01.jpg').convert('L'))
H = array([[1.4, 0.05, -100], [0.05, 1.5, -100], [0, 0, 1]])
# 图像扭曲
im2 = ndimage.affine_transform(im, H[:2, :2], (H[0, 2], H[1, 2]))
gray()
subplot(121)
imshow(im)
axis('off')
subplot(122)
imshow(im2)
axis('off')
show()
运行结果
根据代码,设置了H矩阵值为[[1.4, 0.05, -100], [0.05, 1.5, -100], [0, 0, 1]],则s cosθ=1.4,-s sinθ=0.05,tx=-100,s sinθ=0.05,scosθ=1.5,ty=-100。从上图可以看到扭曲区域边界之外的地方用0像素来填充,来创建一个二值的alpha图像。
图像中的图像
1、将一张图像放到另一张图像中,这是仿射扭曲一个比较简单的应用。通过图像扭曲的特点,它能够使一张图像的4个顶点和另一幅图像的指定位置对齐。
2、下面定义方法image_in_image:将一副图像放到另一幅图像中。将该方法放到warp.py文件中。
def image_in_image(im1, im2, tp):
"""使用仿射变换将im1放置在im2上,使im1图像的角和tp尽可能的靠近
tp是齐次表示的,并且是按照从左上角逆时针计算的"""
# 扭曲的点
m, n = im1.shape[:2]
fp = array([[0, m, m, 0], [0, 0, n, n], [1, 1, 1, 1]])
# 计算仿射变换,并且将其应用于图像im1中
# 计算仿射变换矩阵H
H = homography.Haffine_from_points(tp, fp)
im1_t = ndimage.affine_transform(im1, H[:2, :2],
(H[0, 2], H[1, 2]), im2.shape[:2])
alpha = (im1_t > 0)
return (1 - alpha) * im2 + alpha * im1_t
3、调用image_in_image方法,将陈嘉庚雕像放到中山纪念馆前面
from numpy import *
from matplotlib.pyplot import *
from scipy import ndimage
from PIL import Image
import warp
im1 = array(Image.open('img/jmu01.jpg').convert('L'))
im2 = array(Image.open('img/jmu12.jpg').convert('L'))
gray()
subplot(131)
imshow(im1)
axis('equal')
axis('off')
subplot(132)
imshow(im2)
axis('equal')
axis('off')
# 选定4个点的y,x坐标
tp = array([[264, 800, 800, 264], [40, 40, 400, 400], [1, 1, 1, 1]])
im3 = warp.image_in_image(im1, im2, tp)
subplot(133)
imshow(im3)
axis('equal')
axis('off')
show()
在这个案例中,在中山纪念馆图像中设定了4个顶点的位置,存放到变量tp。然后传入imge_in_image方法计算陈嘉庚图像的仿射变换矩阵H,接着使用图像扭曲方法将两幅图像组合在一起
使用三角形仿射弯曲效果
1、为什么需要三角形仿射?如果不使用三角形仿射,我们把中山纪念馆贴到一张公告牌上,那么会出现贴不满的情况,如下图。
2、这里简单说两个三角形的情况。如果使用三角形仿射,将中山纪念馆图像从左上角到右下角分割成两个三角形,然后先将左下角的三角形先贴到公告牌上,再将右上角的三角形贴到公告牌上,这样就能够使得公告牌被贴满。
3、三角形仿射代码
from numpy import *
from matplotlib.pyplot import *
from scipy import ndimage
from PIL import Image
import warp
import homography
im1 = array(Image.open('img/jmu12.jpg').convert('L'))
im2 = array(Image.open('img/announce.jpg').convert('L'))
# 选定im1角上的一些点
print(im1.shape)
m, n = im1.shape[:2]
# 中山纪念馆的4个顶点坐标
fp = array([[0, m, m, 0], [0, 0, n, n], [1, 1, 1, 1]])
# 映射倒公告牌中的坐标
tp = array([[50, 188, 175, 33], [60, 50, 375, 370], [1, 1, 1, 1]])
# 第一个三角形
tp2 = tp[:, :3] # 前三个点
fp2 = fp[:, :3]
# 计算H
H = homography.Haffine_from_points(tp2, fp2)
im1_t = ndimage.affine_transform(im1, H[:2, :2],
(H[0, 2], H[1, 2]), im2.shape[:2])
# 三角形的alpha
alpha = warp.alpha_for_triangle(tp2, im2.shape[0], im2.shape[1])
im3 = (1 - alpha) * im2 + alpha * im1_t
# 第二个三角形
tp2 = tp[:, [0, 2, 3]] # 第1、3、4个坐标
fp2 = fp[:, [0, 2, 3]]
# 计算H
H = homography.Haffine_from_points(tp2, fp2)
im1_t = ndimage.affine_transform(im1, H[:2, :2],
(H[0, 2], H[0, 2]), im2.shape[:2])
# 三角形的alpha图像
alpha = warp.alpha_for_triangle(tp2, im2.shape[0], im2.shape[1])
im4 = (1 - alpha) * im3 + alpha * im1_t
figure()
gray()
imshow(im4)
axis("equal")
# axis("off")
show()
这里简单的为每个三角形创建了alpha图像,然后将所有图像(三角形)合并起来,三角形alpha图像能够简单的通过检查像素的坐标是否能够写成三角形顶点坐标的凸组合来计算得出。通过alpha_for_triangle函数来完成上述工作,代码如下。
def alpha_for_triangle(points, m, n):
"""对于带有由points定义角点的三角形,创建大小为(m,n)的alpha图
(在归一化的齐次坐标意义下)"""
alpha = zeros((m, n))
for i in range(min(points[0]), max(points[0])):
for j in range(min(points[1]), max(points[1])):
x = linalg.solve(points, [i, j, 1])
if min(x) > 0:
alpha[i, j] = 1
return alpha
4、三角形仿射运行结果
从图像上可以看到,相同的坐标,使用三角仿射后,使得图像能够贴满公告牌。我们可以看到图像从左上角到右下角被分割成了两块三角形,由于坐标设置的不好,使得两个三角形拼接后发生了位置偏差的问题。
分段仿射扭曲
从上面的结果可以知道,将一张图像分成若干个三角形,可以实现角点的精确匹配,分的三角形越多,匹配越精确。
1、首先需要三角剖分的函数triangulate_points,获得三角形,将其加入warp.py文件中
# 三角剖分的函数
def triangulate_points(x, y):
"""二维点的 Delaunay 三角剖分"""
tri = Delaunay(np.c_[x, y]).simplices
return tri
2、然后是通过得到的三角形对图像进行扭曲拼接,该函数为pw_affine,将其放入warp.py文件中
def pw_affine(fromim, toim, fp, tp, tri):
""" 从一幅图像中扭曲矩形图像块
fromim= 将要扭曲的图像
toim= 目标图像
fp= 齐次坐标表示下,扭曲前的点
tp= 齐次坐标表示下,扭曲后的点
tri= 三角剖分 """
im = toim.copy()
# 检查图像是灰度图像还是彩色图象
is_color = len(fromim.shape) == 3
# 创建扭曲后的图像(如果需要对彩色图像的每个颜色通道进行迭代操作,那么有必要这样做)
im_t = zeros(im.shape, 'u8')
for t in tri:
# 计算仿射变换
H = homography.Haffine_from_points(tp[:, t], fp[:, t])
if is_color:
for col in range(fromim.shape[2]):
im_t[:, :, col] = ndimage.affine_transform(
fromim[:, :, col], H[:2, :2], (H[0, 2], H[1, 2]), im.shape[:2])
# im1_t = ndimage.affine_transform(im1, H[:2, :2],
# (H[0, 2], H[1, 2]), im2.shape[:2])
else:
im_t = ndimage.affine_transform(
fromim, H[:2, :2], (H[0, 2], H[1, 2]), im.shape[:2])
# 三角形的 alpha
alpha = alpha_for_triangle(tp[:, t], im.shape[0], im.shape[1])
# 将三角形加入到图像中
im[alpha > 0] = im_t[alpha > 0]
return im
3、然后在拼接后的图片中绘制三角形的函数plot_mesh,将其放入warp.py文件中
# 绘制三角形
def plot_mesh(x, y, tri):
""" 绘制三角形 """
for t in tri:
t_ext = [t[0], t[1], t[2], t[0]] # 将第一个点加入到最后
plot(x[t_ext], y[t_ext], 'r')
4、最后是主程序调用上面的函数
from numpy import *
from matplotlib.pyplot import *
from PIL import Image
import warp
# 打开图像,并将其扭曲
fromim = array(Image.open('img/jmu12.jpg').convert('L'))
x, y = meshgrid(range(5), range(6))
x = (fromim.shape[1] / 4) * x.flatten()
y = (fromim.shape[0] / 5) * y.flatten()
# 三角剖分
tri = warp.triangulate_points(x, y)
# 打开图像和目标点
im = array(Image.open('img/announce.jpg').convert('L'))
tp = loadtxt('turningtorso1_points.txt') # destination points
# 将点转换成齐次坐标
fp = vstack((y, x, ones((1, len(x)))))
tp = vstack((tp[:, 1], tp[:, 0], ones((1, len(tp)))))
# 扭曲三角形
im = warp.pw_affine(fromim, im, fp, tp, tri)
# 绘制图像
figure()
gray()
imshow(im)
warp.plot_mesh(tp[1], tp[0], tri)
axis('off')
show()
运行结果
通过上面的结果可以看到,多个三角形的拼接精确度,比两个三角形的精确度要高,拼接后发生错位的地方看起来没有两个三角形那么别扭。
完成实验存在的一个问题是,如果传入的是一幅彩色图像,pw_affine函数处理彩色图像的时候发生了too many indices for array错误,随后查看了图像扭曲affine_transform函数的调用方式,发现图像扭曲的处理的是灰度图像,处理彩色图像和灰度图像的方式不一样,于是将图像改成了灰度便可以运行成功,但是彩色图像的错误目前还无法弄清楚是什么原因
需要说明的是,运行结果的右图中的所有目标点需要通过ginput函数去手动选取,然后将这些点写到turningtorso1_points.txt文件中,至少选择30个点(因为triangulate_points函数得到的三角形的点最大下标是29),而且手动选取这些点的时候,需要从左向右、从上到下的顺序进行采点,顺序不能错误,否则拼接出来的三角形就会发生很大的错位。同时选取的目标点不能随意的乱点,需要按照上面的运行结果那样有规律、整齐的选取,如果随意乱取目标点,也会造成图片错位混乱,以下就是因为随意选取目标点而导致将中山纪念馆放到广告牌上的时候变得很混乱
以下是选取目标点的代码。选取目标点的时候ginput函数应该是会等待选取完30个点才会继续运行代码,但是实际上在我取完30个点之前,代码就自动运行然后结束了,这个目前还不知道是什么原因,只能快速选完点,然后写入txt文件,最后通过txt文件进行坐标调整
im = array(Image.open('img/announce.jpg').convert('L'))
gray()
imshow(im)
tp = plt.ginput(30)
for i in range(0, len(tp)):
tp[i] = list(tp[i])
tp[i][0] = int(tp[i][0])
tp[i][1] = int(tp[i][1])
tp = array(tp)
print(tp)
最后
以上就是欢呼火龙果为你收集整理的图像到图像的映射——实验3单应性变换图像扭曲的全部内容,希望文章能够帮你解决图像到图像的映射——实验3单应性变换图像扭曲所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复