目录
引言
本章讨论图像之间的变换,以及一些计算变换的方法,这些变换可以用于图像扭曲变形和图像配准。
一、 单应性变换
单应性变换是将一个平面内的点映射到另一个平面内的二维投影变换,这里的平面是指图像或者三维中的平面表面。单应性变换具有很强的实用性,比如说图像的配准、图像纠正和纹理扭曲,以及创建全景图像等。本质上,单应性变换H,按照下面的方程映射二维中的点:
对于图像平面内的点,齐次坐标是一个非常有用的表达方式。点的齐次坐标是依赖于其尺度定义的,为此,表示的是同一个二维点。单应性矩阵仅依赖尺度定义,具有8个独立的自由度。
创建一个能够实现对点进行归一化和转换齐次坐标的功能:
def normalize(points):
"""
在其次坐标意义下对点集进行归一化处理
:param points: 点集
:return:
"""
for row in points:
row /= points[-1]
return points
def make_homog(points):
"""
将点集转化为齐次坐标表示
:param points: 点集
:return:
"""
return vstack((points, ones((1, points.shape[1]))))
进行点和变换的处理时,按照列优先的原则存储这些点,故n个三维点集将会依次存储为齐次坐标意义下的一个3*n数组。
在这些投影变换中存在一些特别重要的变换如:
仿射变换:
w=1,不具有投影变换所具有的强大变形能力。仿射变换包含一个可逆矩阵A和一个平移向量。
相似变换:
包含尺度变换的二维刚体变换,上式的s向量指定了变换的尺度,R是角度为的旋转矩阵,
是一个平移向量,s=1时,该变换甭管保存距离不变,此时变换为刚体变换。
1.1 直接线性变换算法
单应性矩阵可以由两幅图像中对于点计算出来,我们已经了解到一个完全射影变换具有8个自由度,而每个对应点可以写出两个方程,对应x和y坐标,所以计算单应性矩阵H要4个对应点对。计算H的方程如下:
python实现的算法如下:
def H_from_points(fp, tp):
"""
使用使用DLT方法,计算单应性矩阵H,使fp映射到tp,点自动进行归一化
:param fp:
:param tp:
:return:
"""
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_correrpondences = fp.shape[1]
A = zeros(2 * nbr_correrpondences, 9)
for i in range(nbr_correrpondences):
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]
函数的第一步操作是检查点对两个数组中点的数目是否相同,如果不相同函数将会给出错误提示,对这些点进行归一化操作,使其均值为,方差为 ,因为算法的稳定性取决于坐标的表示情况和部分数值计算的问题,因此归一化非常重要,之后利用对应点对构造矩阵A,最小二乘解即为矩阵SVD分解后得到矩阵V的最后一行。该行经过变形后得到矩阵H,并对得到的矩阵进行处理和归一化,返回输出。
1.2 仿射变换
先前提到过仿射变换是一种重要的投影变换,其具有6个自由度,因此需要3个对应点估计矩阵H,将最后两个元素设置为0,就可以使用DLT算法估计得出,下面给出python的算法实现:
def Haffine_from_points(fp, tp):
"""
计算H,仿射变换,使得tp是fp经过仿射变换H得到的
:param fp:
:param tp:
:return:
"""
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)
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_cond = dot(C2, tp)
# 因为归一化后的点均值为0,故平移量为0
A = concatenate((fp_cond[:2], tp_cond[:2]), axis=0)
U, S, A = linalg.svd(A.T)
# 创建矩阵B,C
temp = V[:2].T
B = temp[:2]
C = temp[2:4]
temp2 = concatenate((dot(C, linalg.pinv(B)), zeros((2, 1))), axis=1)
H = vstack((temp2, [0, 0, 1]))
# 反归一化
H = dot(linalg.inv(C2), dot(H,C1))
return H / H[2, 2]
二、 图像扭曲
对图像应用仿射变换就将其称为图像扭曲,扭曲操作可使用Scipy工具包里的ndimage包实现,具体命令为:
transformed_im = ndimage.affine_transform(im, A, b, size)
实验:
# 图像扭曲
from scipy import ndimage
from PIL import Image
from numpy import *
from pylab import *
img = array(Image.open('D:\\picture\\tupian.jpg').convert('L'))
gray()
H = array([[1.4, 0.05, -100], [0.05, 1.5, -100], [0, 0, 1]])
img_re = ndimage.affine_transform(img, H[:2, :2], (H[0, 2], H[1, 2]))
subplot(121)
imshow(img), title('original')
subplot(122)
imshow(img_re), title('result')
show()
从实验结果中可以看出,经过ndimage.affine_transform()函数方法处理过的图像发生了偏折,且丢失的像素使用0进行填充。
2.1 图像中的图像
将一幅图像或图像的一部分放置在另一幅图像中,使得它们能够和指定的标记物对齐,这就是仿射扭曲的简单例子。
下面就使用仿射变换将一幅图像放置到另一幅图像做出实验:
# 仿射变换
from scipy import ndimage
from PIL import Image
from numpy import *
from pylab import *
from PCV.geometry import warp
img1 = array(Image.open('D:\\picture\\tem1.jpg').convert('L'))
img2 = array(Image.open('D:\\picture\\test3.jpg').convert('L'))
gray()
# 选定一些目标点
tp1 = array([[264, 538, 540, 264], [40, 36, 605, 605], [1, 1, 1, 1]])
tp2 = array([[500,248,250,500],[100,120,300,300],[2,2,2,2]])
img3 = warp.image_in_image(img2, img1, tp1)
img4 = warp.image_in_image(img2, img1, tp2)
subplot(221)
imshow(img1), title('original1'), axis('off')
subplot(222)
imshow(img2), title('original2'), axis('off')
subplot(223)
imshow(img3), title('tp1_result'), axis('off')
subplot(224)
imshow(img4), title('tp2_result'), axis('off')
show()
实验时使用了先前下载的工具包,程序运行时出现了ModuleNotFoundError: No module named 'matplotlib.delaunay'的错误,需要在wrap.py库中将
import matplotlib.delaunay as md
改成:
from scipy.spatial import Delaunay
并将 wrap.py里的triangulate_points(x,y)函数中的内容改成:
tri = Delaunay(np.c_[x, y]).simplices
这时就能够成功运行了 ,在程序中,对选择的目标点进行了修改,可看出图片映射在另一幅图像上的状态发生了改变。当我们需要将图像放置在我们想要的位置上时,需要知道指定的目标点的坐标。
2.2 分段仿射扭曲
三角形图像块的仿射扭曲可以实现角点的精确匹配,分段仿射扭曲是对应点对集合之间常见的扭曲方式。通过给定任意图像的标记点,并将这些点进行三角剖分,然后实验仿射扭曲来扭曲每个三角形,我们可以将图像和另一幅图像的对应标记点扭曲对应。
三角化点的操作一般实验狄洛克三角剖分方法,其实验代码如下:
from numpy import *
from matplotlib.pyplot import *
from scipy.spatial import Delaunay
x, y = array(random.standard_normal((2, 100)))
tri = Delaunay(np.c_[x, y]).simplices
figure()
for t in tri:
# 将第一个点加入到最后
t_ext = [t[0], t[1], t[2], t[0]]
plot(x[t_ext], y[t_ext], 'r')
plot(x, y, '*')
axis('off')
figure()
plot(x, y, '*')
axis('off')
show()
在Matplotlib库中就含有狄洛克三角剖分,需要我们注意的是不是在Pylab库中,所以在导入包时应该为:
from matplotlib.pyplot import *
其输出结果为:
对于一幅彩色图像,当我们需要对其进行图像扭曲时就需要额外进行考虑,下面的函数提供了一个方法:
def pw_affine(fromim,toim,fp,tp,tri):
"""
从图像中扭曲矩形图像块
:param fromim: 将要扭曲的图像
:param toim: 目标图像
:param fp: 齐次坐标下,扭曲前的点
:param tp: 齐次坐标下,扭曲后的点
:param tri: 三角部分
:return:
"""
im = toim.copy()
# 检查图像是灰度图像还是彩色图像
is_color = len(fromim.shape) == 3
# 创建扭曲后的图像
im_t = zeros(im.shape, 'uint8')
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])
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
分析:这里首先检查图像是灰度图像还是彩色图像,当为彩色图像时,就要分别对每个颜色通道进行扭曲处理, 我们知道对于每个三角形而言,仿射变换是唯一确定的,因此使用了homography包中的Haffine_from_points()函数对其进行处理。
2.2 图像配准
图像配准就是将不同时间、成像设备或不同条件下(比如气候、照度、摄像位置和角度等)获取的两幅或多幅图像进行匹配、叠加的过程,它已经被广泛地应用于遥感数据分析、计算机视觉和图像处理。
由于配准图像的多样性和各种类型的退化,不能设计出适合所有配准任务的通用方法。每种方法不仅要考虑图像之间假定的几何变形类型,还要考虑辐射变形和噪声损坏,所需配准的准确率和应用数据特征。一般图像配准的流程为:
1、对两幅图像进行特征提取得到特征点;
2、通过进行相似性度量找到匹配的特征点对;
3、通过匹配的特征点对得到图像空间坐标变换参数;
4、最后由坐标变换参数进行图像配准。
在配准的过程中特征提取尤为关键,准确的特征提取为特征匹配的成功进行提供了保障。因此,寻求具有良好不变性和准确性的特征提取方法,对于匹配精度至关重要。先前我们学习到的SIRT特征提取就不失为一个很好的方法。
三、创建全景图
在同一个位置拍摄的两幅或者多福图像是单应性相关的,通常使用该约束将图像缝补起来,拼接成一个大的图像来创建全景图像。
3.1 RANSAC(随机一致性采样)
该方法是用来找到正确模型拟合带有噪声数据的迭代方法。其基本思想是,数据中包含有正确的点和噪声点,合理的模型能够在描述正确数据点的同时摒弃噪声点。
RANSAC的基本假设是:
(1)数据由“局内点”组成,例如:数据的分布可以用一些模型参数来解释;
(2)“局外点”是不能适应该模型的数据;
(3)除此之外的数据属于噪声。
局外点产生的原因有:噪声的极值;错误的测量方法;对数据的错误假设。
RANSAC也做了以下假设:给定一组(通常很小的)局内点,存在一个可以估计模型参数的过程;而该模型能够解释或者适用于局内点。
在估计参数模型时,通常使用p表示一些迭代过程中从数据集内随机选取出来的点均为局内点的概率;此时结果模型就很可能会有作用,所以p也表征了算法所产生有用结果的概率。
假设每个点是真正内群的概率为 w:
w = 内群的数目/(内群数目+外群数目)
通常我们不知道 w 是多少,是所选择的n个点都是内群的机率,
是所选择的n个点至少有一个不是内群的机率,
是表示重复 k 次都没有全部的n个点都是内群的机率, 假设算法跑 k 次以后成功的机率是p,那么
我们可以通过P反算得到抽取次数K:
所以如果希望成功机率高:
1、当n不变时,k越大,则p越大;
2、当w不变时,n越大,所需的k就越大。
3、通常w未知,所以n选小一点比较好。
下面给出实验示例:
import numpy as np
import scipy.linalg as sl
import scipy as sp
def ransac(symbol, model, n, k, t, d, debug=False, return_all=False):
"""
:param symbol: 样本点
:param model: 假设的模型
:param n: 生成模型所需的最小样本点
:param k: 最大迭代次数
:param t: 阈值
:param d: 拟合较好时,需要的样本点最少的个数,当做阈值看待
:param debug:
:param return_all:
:return:
"""
iterations = 0
bestfit = None
besterr = np.inf # 设置默认值
best_inlier_idxs = None
while iterations < k:
maybe_idxs, test_idxs = random_partition(n, symbol.shape[0])
# 获取maybe{_idxs行数据(Xi,Yi)
maybe_inliers = symbol[maybe_idxs, :]
# 若干行(xi,yi)数据点
test_points = symbol[test_idxs]
# 拟合模型
maybemodel = model.fit(maybe_inliers)
# 计算误差
test_err = model.get_error(test_points, maybemodel)
also_idxs = test_idxs[test_err < t]
also_inliers = symbol[also_idxs, :]
if debug:
print('test_err.min()', test_err.min())
print('test_err.max*(', test_err.max())
print('np.mean(test_err)', np.mean(test_err))
print('iteration %d:len(also_inliers) = %d' %(iterations, len(also_inliers)))
if len(also_inliers > d):
bettersymbol = np.concatenate((maybe_inliers, also_inliers))
bettermodel = model.fit(bettersymbol)
better_err = model.get_error(bettersymbol, bettermodel)
# 将平均误差作为新误差
this_err = np.mean(better_err)
if this_err < besterr:
bestfit = bettermodel
besterr = this_err
best_inlier_idxs = np.concatenate((maybe_idxs, also_idxs))
iterations += 1
if bestfit is None:
raise ValueError("didn't met fit criteria")
if return_all:
return bestfit, {'inliers':best_inlier_idxs}
else:
return bestfit
def random_partition(n, n_symbol):
# 获取n_symbol下的索引
all_idxs = np.arange(n_symbol)
# 打乱下标索引
np.random.shuffle(all_idxs)
idxs_1 = all_idxs[:n]
idxs_2 = all_idxs[n:]
return idxs_1, idxs_2
class LinearLeastSquaresModel:
"""
最小二乘求线性解用于ransac的输入模型
"""
def __init__(self, input_columns, output_columns, debug=False):
self.input_columns = input_columns
self.output_columns = output_columns
self.debug = debug
def fit(self, symbol):
# 第一列xi-->行xi
A = np.vstack([symbol[:, i] for i in self.input_columns]).T
# 第第二列yi-->行yi
B = np.vstack([symbol[:, i] for i in self.output_columns]).T
# residues:残差和
x, resids, rank, s = sl.lstsq(A, B)
# 返回最小平方和向量
return x
def get_error(self, symbol, model):
A = np.vstack([symbol[:, i] for i in self.input_columns]).T
B = np.vstack([symbol[:, i] for i in self.output_columns]).T
# 计算y值B_fit = model.k+A+model.b
B_fit = sp.dot(A, model)
err_per_point = np.sum((B - B_fit)**2, axis=1)
return err_per_point
def test():
# 生成理想数据
n_samples = 500
n_inputs = 1
n_output = 1
A_exact = 20 * np.random.random((n_samples, n_inputs))
perfect_fit = 60 * np.random.normal(size=(n_inputs, n_output))
B_exact = sp.dot(A_exact, perfect_fit)
# 加入高斯噪声
A_noise = A_exact + np.random.normal(size=A_exact.shape)
B_noise = B_exact + np.random.normal(size=B_exact.shape)
if 1:
# 添加局外点
n_outliers = 100
all_idxs = np.arange(A_noise.shape[0])
np.random.shuffle(all_idxs)
outlier_idxs = all_idxs[:n_outliers]
non_outlier_idxs = all_idxs[n_outliers:]
A_noise[outlier_idxs] = 20 * np.random.random((n_outliers, n_inputs))
B_noise[outlier_idxs] = 50 * np.random.normal(size=(n_outliers, n_output))
all_data = np.hstack((A_noise, B_noise))
input_columns = range(n_inputs) # the first columns of the array
output_columns = [n_inputs + i for i in range(n_output)] # the last columns of the array
debug = False
model = LinearLeastSquaresModel(input_columns, output_columns, debug=debug)
linear_fit, resids, rank, s = np.linalg.lstsq(all_data[:, input_columns], all_data[:, output_columns])
# run RANSAC algorithm
ransac_fit, ransac_data = ransac(all_data, model,
5, 5000, 7e4, 50,
debug=debug, return_all=True)
if 1:
import pylab
sort_idxs = np.argsort(A_exact[:, 0])
A_col0_sorted = A_exact[sort_idxs]
if 1:
pylab.plot(A_noise[:, 0], B_noise[:, 0], 'k.', label='data')
pylab.plot(A_noise[ransac_data['inliers'], 0], B_noise[ransac_data['inliers'], 0], 'bx',
label='RANSAC data')
else:
pylab.plot(A_noisy[non_outlier_idxs, 0], B_noisy[non_outlier_idxs, 0], 'k.', label='noisy data')
pylab.plot(A_noisy[outlier_idxs, 0], B_noisy[outlier_idxs, 0], 'r.', label='outlier data')
pylab.plot(A_col0_sorted[:, 0],
np.dot(A_col0_sorted, ransac_fit)[:, 0],
label='RANSAC fit')
pylab.plot(A_col0_sorted[:, 0],
np.dot(A_col0_sorted, perfect_fit)[:, 0],
label='exact system')
pylab.plot(A_col0_sorted[:, 0],
np.dot(A_col0_sorted, linear_fit)[:, 0],
label='linear fit')
pylab.legend()
pylab.show()
if __name__ == '__main__':
test()
分析:ransac就是用来解决样本中的噪声问题的,且最多可处理50%的噪声情况。其优点在于能够很好的估计模型参数,在运行过程中,如果出现了一种足够好的模型,就会跳出主循环,当直接从maybe_model计算this_err时虽然会节约比较两种模型的错误时间,但对于噪声会更加的敏感。不过由于其计算参数的迭代次数没有上限,所以其运行的时间没有保证,倘若强行设置迭代次数可能会导致错误的结果。
3.2 拼接图像
使用ransac算法实现将所有的图像扭曲到一个公共平面上,一般情况下公共平面就为中心图像平面,否则就需要大量的变形。因为拍摄图像均是以照相机水平旋转拍摄的,因此使用一个简单的步骤:将中心图像的左边或右边区域填充为0,以便为扭曲的图像空出位置。
函数代码如下:
def panorama(H,fromim,toim,padding=2400,delta=2400):
"""
使用单应性矩阵H协调两幅图像,创建水平全景图像
:param H:单应性矩阵,Ransac算法估计得出
:param fromim:图像
:param toim:输出图像
:param padding:指定填充像素的数目
:param delta: 指定额外的平移量
:return:
"""
# 检查是否为灰度图像
is_color = len(fromim.shape) == 3
# 用于gemoetric_transform()的单应性变化
def transf(p):
p2 = dot(H,[p[0],p[1],1])
return (p2[0]/p2[2],p2[1]/p2[2])
if H[1,2]<0: # fromim在左边
print ('warp - right')
# 变换 fromim
if is_color:
# 在目标图像右边填充0
toim_t = hstack((toim,zeros((toim.shape[0],padding,3))))
fromim_t = zeros((toim.shape[0],toim.shape[1]+padding,toim.shape[2]))
for col in range(3):
fromim_t[:,:,col] = ndimage.geometric_transform(fromim[:,:,col],
transf,(toim.shape[0],toim.shape[1]+padding))
else:
# 在目标图像的右边填充0
toim_t = hstack((toim,zeros((toim.shape[0],padding))))
fromim_t = ndimage.geometric_transform(fromim,transf,
(toim.shape[0],toim.shape[1]+padding))
else:
print ('warp - left')
# 为了补偿填充效果,在左边加入平移量
H_delta = array([[1,0,0],[0,1,-delta],[0,0,1]])
H = dot(H,H_delta)
# 变换 fromim
if is_color:
# 在目标图像左边填充0
toim_t = hstack((zeros((toim.shape[0],padding,3)),toim))
fromim_t = zeros((toim.shape[0],toim.shape[1]+padding,toim.shape[2]))
for col in range(3):
fromim_t[:,:,col] = ndimage.geometric_transform(fromim[:,:,col],
transf,(toim.shape[0],toim.shape[1]+padding))
else:
# 在目标图像左边填充0
toim_t = hstack((zeros((toim.shape[0],padding)),toim))
fromim_t = ndimage.geometric_transform(fromim,
transf,(toim.shape[0],toim.shape[1]+padding))
# 协调后返回
if is_color:
# 所有非黑色像素
alpha = ((fromim_t[:,:,0] * fromim_t[:,:,1] * fromim_t[:,:,2] ) > 0)
for col in range(3):
toim_t[:,:,col] = fromim_t[:,:,col]*alpha + toim_t[:,:,col]*(1-alpha)
else:
alpha = (fromim_t > 0)
toim_t = fromim_t*alpha + toim_t*(1-alpha)
return toim_t
对于通用的gemoetric_transform()函数,transf()被指定为能够描述像素到像素间映射的函数,函数通过将像素与H相乘,然后对齐次坐标进行归一化来实现像素之间的映射。
下面给出实验代码:
from pylab import *
from numpy import *
from PIL import Image
from PCV.geometry import homography, warp
from PCV.localdescriptors import sift
# 需要根据自己的图像地址和图像数量修改地址和循环次数
featname = ['D:\\picture\\test_img\\tupian' + str(i + 1) + '.sift' for i in range(5)]
imname = ['D:\\picture\\test_img\\tupian' + str(i + 1) + '.jpg' for i in range(5)]
l = {}
d = {}
for i in range(5): # 循环次数 = 图像数量
sift.process_image(imname[i], featname[i])
l[i], d[i] = sift.read_features_from_file(featname[i])
matches = {}
for i in range(4): # 循环次数 = 图像数量 - 1
matches[i] = sift.match(d[i + 1], d[i])
for i in range(4): # 循环次数 = 图像数量 - 1
im1 = array(Image.open(imname[i]))
im2 = array(Image.open(imname[i + 1]))
figure()
sift.plot_matches(im2, im1, l[i + 1], l[i], matches[i], show_below=True)
# 将匹配转换成齐次坐标点的函数
def convert_points(j):
ndx = matches[j].nonzero()[0]
fp = homography.make_homog(l[j + 1][ndx, :2].T)
ndx2 = [int(matches[j][i]) for i in ndx]
tp = homography.make_homog(l[j][ndx2, :2].T)
fp = vstack([fp[1], fp[0], fp[2]])
tp = vstack([tp[1], tp[0], tp[2]])
return fp, tp
# 估计单应性矩阵
model = homography.RansacModel()
fp, tp = convert_points(1)
H_12 = homography.H_from_ransac(fp, tp, model)[0] # im 1 to 2
fp, tp = convert_points(0)
H_01 = homography.H_from_ransac(fp, tp, model)[0] # im 0 to 1
tp, fp = convert_points(2) # NB: reverse order
H_32 = homography.H_from_ransac(fp, tp, model)[0] # im 3 to 2
tp, fp = convert_points(3) # NB: reverse order
H_43 = homography.H_from_ransac(fp, tp, model)[0] # im 4 to 3
# 扭曲图像
delta = 100
im1 = array(Image.open(imname[1]), "uint8")
im2 = array(Image.open(imname[2]), "uint8")
im_12 = warp.panorama(H_12, im1, im2, delta, delta)
im1 = array(Image.open(imname[0]), "f")
im_02 = warp.panorama(dot(H_12, H_01), im1, im_12, delta, delta)
im1 = array(Image.open(imname[3]), "f")
im_32 = warp.panorama(H_32, im1, im_02, delta, delta)
im1 = array(Image.open(imname[4]), "f")
im_42 = warp.panorama(dot(H_32, H_43), im1, im_32, delta, 2 * delta)
figure()
imshow(array(im_42, "uint8"))
axis('off')
show()
实验结果图:
最初的实验中使用的图片是手机中存储的一些随手拍的相关的图,经过处理后要么是报错显示没有匹配的关键点,要么就是扭曲的图像过于奇葩,上面的结果图是相对较好的结果了,这里要提醒一下选择图片最好是站在一个位置拍的同一个事物的几个面为好。
四、总结
在这次的学习里我遇到了不少困难包括库的安装与实验代码的调试,同时也发现了很多自己的不足,本章内容仍旧需要继续加强学习。