基于RANSAC的图像全景拼接
基于RANSAC的图像全景拼接
RANSAC算法
RANSAC是一种迭代算法,用来从观测数据中估算出数学模型的参数,此基础上便可以分离内群与离群数据。简单来说就是一般来讲观测的数据里经常会出现很多噪音,比如说像SIFT匹配有时就会因为不同地方有类似的图案导致匹配错误。而RANSAC就是通过反复取样,也就是从整个观测数据中随机抽一些数据估算模型参数之后看和所有数据误差有多大,然后取误差最小视为最好以及分离内群与离群数据。
全景拼接原理介绍
- 针对某个场景拍摄多张/序列图像
- 通过匹配特征(sift匹配)计算下一张图像与上一张图像之间的变换结构。
- 图像映射,将下一张图像叠加到上一张图像的坐标系中
- 变换后的融合/合成
- 重复上述步骤
全景图像拼接最重要的两个步骤就是:
- 特征点匹配
这部分主要采用SIFT算法实现,之前的博客有介绍就不再详细介绍了,主要是为了找到两幅图像相同的特征点并将其进行匹配。
- 图片匹配
图片匹配就是找到图像之间所有重叠的部分,将其拼接后就能得到一幅全景图。
图像配准
图像配准是对图像进行变换,使变换后的图像能够在很好的拼接在上一张图片的坐标系。为了能够进行图像对比和更精细的图像分析,图像配准是一步非常重要的操作因为图片存在歪斜或两张图片的平面与平面之间景深不同(近大远小),直接将两张图片进行映射变换会导致图片中部分物体有重影现象(鬼影)。为了尽量减小这种情况,图像配准算法将图片划分成小块的区域,分别在小块区域中进行图片的匹配和映射
全局优化和无缝衔接
- 全景图矫直:矫正拍摄图片时相机的相对3D旋转,主要原因是拍摄图片时相机很可能并不在同一水平线上,并且存在不同程度的倾斜,略过这一步可能导致全景图变成波浪形状。
- 图像均衡补偿:全局平衡所有图片的光照和色调。
- 图像频段融合:步骤4之后仍然会存在图像之间衔接边缘、晕影效果(图像的外围部分的亮度或饱和度比中心区域低)、视差效果(因为相机透镜移动导致)。
测试代码:
# -*- coding: utf-8 -*- from pylab import * from numpy import * from PIL import Image # If you have PCV installed, these imports should work from PCV.geometry import homography, warp from PCV.localdescriptors import sift """ This is the panorama example from section 3.3. """ # 设置数据文件夹的路径 featname = ['E:\\test_pic\\pinjie\\' + str(i + 1) + '.sift' for i in range(5)] imname = ['E:\\test_pic\\pinjie\\' + str(i + 1) + '.jpg' for i in range(5)] # 提取特征并匹配使用sift算法 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): matches[i] = sift.match(d[i + 1], d[i]) # 可视化匹配 for i in range(4): 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) # switch x and y - TODO this should move elsewhere 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 = 2000 # for padding and translation用于填充和平移 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()
数据集合图片(5张)
特征匹配结果:
结果出现了歪斜,缝隙和黑幕,第一次运行的结果不理想
可以发现,图片的拼接顺序出现了错误,最右侧的图片与左侧的图片直接相连接,调过来中间的图片,导致了三张图片堆积在左边,而右侧缺失了两张图片
大致猜想是因为5张图片的范围过小,导致每张图片之间的重叠部分过多,导致每张图片都有大量的特征点匹配,导致图片重叠,造成缺失。
建议图片范围取大一些,不要有太多的重叠部分。
调整照片后再次进行拼接:
图片没有缝隙,很平滑,也没有明显的曝光,对比原图,位置是正确的,广告牌也拼接上了,但是左侧图片被拉伸了,虽然拉伸程度不大。可能是因为位移量不够,所以拉伸了图片进行填充
重新拍照后测试:
没有发生图片错位,但是扭曲拉伸严重,大体上完成了拼接,不过效果一般,存才鬼影现象,比较模糊,提升原图清晰度可以有效改善此结果
远近不同图片的拼接
测试图片(2张)
远景:
近景:
运行结果:
远景中的建筑物也被拼接进了近景中的图片,位置也是正确的,且没有明显的曝光的痕迹,相较于上一组实验,这组实验的照片较为稳定和清晰,特征点匹配的准确,拼接的顺滑,没有缝隙
总结
- 图像配准虽然能够较好地完成配准,但非常依赖于特征点对。若图像高频信息较少,特征点对过少,配准将完全失效,并且对大尺度的图像进行配准,其效果也不是很好,特征点对的数量对拼接效果有很大的影响。
- 拍摄时曝光不均导致画面分割明显
- 拼接图像部分歪斜,可能是因为拍摄时角度的偏差,导致当该图像填充时目标图像中点的坐标也变化了。
- 原始图片的像素值很大程度上决定了特征点的寻找,像素值太低,将导致寻找到的特征点过少,不利于后续的拼接效果,而像素值过高会导致代码的运行时间过长,加重算法的负荷,运行时间长达5分钟...个人建议别超过1M,不然真的运行太慢了,当然要是电脑好就当我没说
-
全景拼接的一个关键点就是sift特征点的匹配,只有寻找到正确且数量较多的特征点,在后续的拼接中才能更好的完成,拼接的更自然。
-
基于RANSAC算法的图像拼接存在的问题可以从上面的拼接结果看出来,不同图像之间的连接部分出现不连贯以及由于图像曝光不同而出现的边缘效应的情况。而且还可以看出RANSAC算法只是将图像进行简单的扭曲(从矩形变成四边形),导致图像拼接得非常生硬
- 最好不要拍摄对称性很高的建筑物,以为两边的的特征点长的几乎一样的,这样会使算法的匹配出现失误,第一组实验就是间隔太小,导致最右侧的图片拼接到了最左边。
报错处理
若代码出现次错误,进入到PCV\geometry\warp.py文件中,
1.将第一行的
import matplotlib.delaunay as md
替换成
from scipy.spatial import Delaunay
2.把以下代码
triangulate_points(x,y)
里面的代码替换成
tri = Delaunay(np.c_[x,y]).simplices
这样就能运行了