图像的拼接融合
一、图像拼接原理介绍
- 图像拼接技术简单的理解就是将数张有重叠部分的图像拼成一幅无缝的全景图或高分辨率图像的技术。这些有重叠部分的图像可能是不同时间、不同视角或者不同传感器获得的图像。目前,图像拼接在医学成像、计算机视觉、卫星数据、军事目标自动识别等领域具有重要意义。图像拼接的输出是两个输入图像的并集。
- 图像配准和图像融合是图像拼接的两个关键技术。图像配准是图像融合的基础,而且图像配准算法的计算量一般非常大,因此图像拼接技术的发展很大程度上取决于图像配准技术的创新。早期的图像配准技术主要采用点匹配法,这类方法速度慢、精度低,而且常常需要人工选取初始匹配点,无法适应大数据量图像的融合。图像拼接的方法很多,不同的算法步骤会有一定差异,但大致的过程是相同的。
- 在图像拼接中首先利用SIFT算法提取图像特征进而进行特征匹配,继而使用RANSAC算法对特征匹配的结果进行优化,接着利用图像变换结构进行图像映射,最终进行图像融合。
- 在图像拼接过程中,运用SIFT局部描述算子检测图像中的关键点和特征,SIFT特征是基于物体上的一些局部外观的兴趣点而与影像的大小和旋转无关。对于光线、噪声、些微视角改变的容忍度也相当高,所以用来检测要拼接图像的特征及关键点就很有优势。而接下来即步骤三是找到重叠的图片部分,连接所有图片之后就可以形成一个基本的全景图了。匹配图片最常用的方式是采用RANSAC( 随机抽样一致),用此排除掉不符合大部分几何变换的匹配。之后利用这些匹配的点来估算单应矩阵”,也就是将其中一张图像通过关联性和另一张匹配。
二、RANSAC算法讲解
- RANSAC算法便是用来找到正确模型来拟合带有噪声数据的迭代方法。此基础上便可以分离内群与离群数据。简单来说就是一般来讲观测的数据里经常会出现很多噪音,比如说像SIFT匹配有时就会因为不同地方有类似的图案导致匹配错误。RANSAC通过反复取样,也就是从整个观测数据中随机抽一些数据估算模型参数之后看和所有数据误差有多大,然后取误差最小视为最好以及分离内群与离群数据。基本的思想是,数据中包含正确的点和噪声点,合理的模型应该能够在描述正确数据点的同时摒弃噪声点。
三、图像拼接步骤
- 对每幅图进行特征点提取
- 对对特征点进行匹配
- 进行图像配准
- 把图像拷贝到另一幅图像的特定位置
- 对重叠边界进行特殊处理,采用APAP之类的算法,对齐特征点,即就是图像配准。
- 通过图割方法,自动选取拼接缝。
- 根据blending策略实现融合。
四、图像融合(去裂缝处理)
- 图像融合是图像拼接的两个关键技术。图像配准是图像融合的基础,而且图像配准算法的计算量一般非常大,因此图像拼接技术的发展很大程度上取决于图像配准技术的创新。早期的图像配准技术主要采用点匹配法,这类方法速度慢、精度低,而且常常需要人工选取初始匹配点,无法适应大数据量图像的融合。图像拼接的方法很多,不同的算法步骤会有一定差异,但大致的过程是相同的。图像拼接完会发现在拼接的交界处有明显的衔接痕迹,存在边缘效应,因为光照色泽的原因使得图片交界处的过渡很糟糕,所以需要特定的处理解决这种不自然。那么这时候可以采用multi-band blending方法。multi-band blending是目前图像融和方面比较好的方法,拼接之后的图像进行融合的效果是很好的。
五、实验内容
1.视差变化小的场景
- 针对固定点位拍摄多张图片,以中间图片为中心,实现图像的拼接融合
成功案例
数据集:
特征匹配结果:
全景拼接结果:
分析:
- 从全景拼接结果中可以看出拼接图片没有缝隙,很平滑,也没有明显的曝光,和原景对比,位置是正确的。
- 在拍摄图片的时候,楼房前面的树枝是没有拍整齐的,但是在通过拼接后也拼接上了。
- 但是图片有被拉伸,左侧也有明显的向右倾斜。可能是因为位移量不够,所以拉伸了图片进行填充。左侧向右倾斜,主要是与我粗糙的拍摄手法有关。
- 图片旁出现了黑色部分,是因为图片无法填充满,可以试试拍摄时角度变化大一些。效果会更好。
失败案例
数据集:
全景拼接结果:
分析:
- 运行的结果很不理想,扭曲拉伸严重。
- 拼接结果出现了歪斜,缝隙和黑幕。
- 失败的原因可能是我拍摄的角度太单一。
- 拍摄图片有大量相同的特征点,而且重叠部分过少,范围过小。导致每张图片之间的重叠部分过多,导致每张图片都有大量的特征点匹配,图片重叠严重,从而造成缺失。建议图片范围取大一些,不易失败。
2. 视差变化大的场景
- 即 针对同一场景,选取视差变化大的场景,也就是有近景目标,更换拍摄位置,分析拼接结果
数据集:
特征匹配结果:
全景拼接结果:
分析:
- 从以上全景拼接图中可以看出远景中的建筑物也被拼接进了近景中的图片,位置是正确的,但是拼接效果并不好!
- 两图的拼接并不自然,原因就在于拼接图的交界处,两图因为光照色泽的原因使得两图交界处的过渡也很糟糕。这里的处理思路是加权融合,在重叠部分由前一幅图像慢慢过渡到第二幅图像,即将图像的重叠区域的像素值按一定的权值相加合成新的图像。
- 存在有曝光的痕迹,也存有缝隙,左侧存在大块黑幕
- 出现黑幕的主要原因是拍摄视角小,导致拼接后无法填充满。
- 拼接的过程中有较明显的拼接缝。可能是因为图片的曝光率相差过大,因此拼接缝会更明显。
- 导致该拼接结果不够好的原因除了与算法本身有关的情况外,我觉得与拍摄角度也是有很大的关系。
四、实验总结
- 所选图像有比较多的重叠部分是拼接的基本要求。
- 拍摄时曝光不均很容易导致画面分割明显
- 正确的特征点对全景拼接十分关键,只有寻找到正确且数量较多的特征点,才能更好的完成后续的拼接,让图像拼接的更自然。
五、代码展示
# -*- 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 = ['D:\\计算机视觉\\SIFT特征匹配图片库\\' + str(i + 1) + '.sift' for i in range(5)] imname = ['D:\\计算机视觉\\SIFT特征匹配图片库\\' + 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()