2. 编程实现多张图片的自动拼接
姓名:许展风 学号:3210100658
电子邮箱:zhanfeng_xu@outlook.com 联系电话:15224131655
老师:潘纲老师 报告日期:2023年12月4日
2.1. 一、功能简述及运行说明
2.1.1. 1.1 功能简述
对输入的多张彩色图像,通过算法实现多张图片的拼接。
2.1.2. 1.2 运行说明
程序运行后,第一步根据提示输入待拼接的图片数量,第二步依次输入图片路径,第三步输入输出图片名称。回车后程序运行,并输出最后一步图片拼接的中间过程,以及最终所有图片的拼接结果。
2.2. 二、开发与运行环境
编程语言:python 3.10.6
编程环境:VScode+Jupyter Notebook
运行环境:Windows
2.3. 三、算法原理
2.3.1. 3.1 算法流程图
graph LR
A[彩色图片1]
B[彩色图片2]
A --> C[特征点提取]
B --> C
C --> D[图像配准]
D --> E[计算透视变换矩阵H]
E --> F[图像变形]
F --> G[图像融合]
2.3.2. 3.2 具体原理介绍
2.3.2.1. 1. 特征点提取
通过特殊算法检测输入图像的特征点,这些特征可能包括某些方向的极值点,它们不受图像的尺度缩放、亮度变化所影响,是一个稳定的特征。
2.3.2.2. 2. 特征点匹配
利用图像的特征点建立图像特征点之间的对应,匹配两张图片相同的部分。
2.3.2.3. 3. 计算透视变换矩阵H
利用匹配的特征点,建立图像之间的几何对应关系,使它们可以在一个共同参照系中进行变换、比较和分析。
2.3.2.4. 4. 图像变形
利用变换矩阵H对其中一张图像作透视变换,使得两张图像变为同一参照系。
2.3.2.5. 5. 图像融合
变形后的图像可以直接拼接,也可以通过改变边界附近图像的灰度,使得图像在缝隙处平滑过渡。
2.4. 四、具体实现
2.4.1. 4.1 特征点提取
# sift特征点计算
sift = cv2.SIFT_create()
kp1, des1 = sift.detectAndCompute(imgGray1, None)
kp2, des2 = sift.detectAndCompute(imgGray2, None)
使用SIFT算法提取图片特征点,SIFT的全称是Scale Invariant Feature Transform,尺度不变特征变换,SIFT特征对旋转、尺度缩放、亮度变化等保持不变性,是一种非常稳定的局部特征。[2]
2.4.2. 4.2 特征点匹配
# 对应特征点配对
bf = cv2.BFMatcher(cv2.NORM_L2)
matches = bf.knnMatch(des1, des2, k=2)
goodMatch = [] # 配对点集合,用于画图
good = [] # 配对点坐标序号集合, 用于后续求变换矩阵
for m, n in matches:
if m.distance < 0.75 * n.distance:
goodMatch.append(m)
good.append((m.trainIdx, m.queryIdx))
cv2.BFMatcher是openCV库中的一种匹配器,Brute-Force蛮力匹配器,该匹配器将两组中的所有特征点进行匹配,返回距离最近的匹配项。再将蛮力匹配得到的结果进行筛选,当最近距离与次近距离的比值小于ratio值时的配对保留。存储保留配对的索引值,便于后续索引。
另一种匹配器为FLANN匹配器,它利用最近邻搜索的优化算法,可以在大型数据集中拥有比BF匹配器更快的运算速度,在本实验中没有必要使用。
2.4.3. 4.3 计算透视变换矩阵H
# 求变换矩阵
pts1 = np.float32([kp1[i].pt for (_, i) in good])
pts2 = np.float32([kp2[i].pt for (i, _) in good])
H, status = cv2.findHomography(pts1, pts2, cv2.RANSAC, 4.0)
利用保留匹配点的索引提取匹配点的坐标,利用cv2.findHomography函数计算单应性矩阵,使用RANSAC方法。
2.4.4. 4.4 图像变形与融合
# 用变换矩阵对imga作透视变换
tranRes = cv2.warpPerspective(img1, H, (img1.shape[1], img1.shape[0]))
# 透视变换后的图片拼接上imgb
tranRes[th:imgb.shape[0] + th, tw:imgb.shape[1] + tw] = imgb
利用计算得到的矩阵H作为透视变换矩阵,使用透视变换函数对图片1作透视变换。
在算法实现过程中,可以发现对图片1作透视变换后,图片1会发生包括平移在内的变换,其结果是图片1中与图片2特征对应的位置得到重合,此时直接将图片2覆盖在图片1,即可直接得到混合图片。当使用Yosemite图片作为样本时,会发现实际上图片1会变换后左移,而移动出画布的范围造成变换后信息的缺失。如下图中绿色的区域被丢失。[3]为了解决这个问题,可以扩大初始图片的画布,
# 由于透视变换将imga图片进行平移、旋转等等操作,所以需要扩大图像画布,避免信息丢失
tw = np.int16(np.max([imga.shape[1], imgb.shape[1]])) # 确定宽度平移量
th = np.int16(np.max([imga.shape[0], imgb.shape[0]])) # 确定高度平移量
M = np.float32([[1, 0, tw], [0, 1, th]]) # 构造平移变换矩阵
img1 = cv2.warpAffine(imga, M,
(imga.shape[1] + 2 * tw,
imga.shape[0] + 2 * th)) # 变换后,保证了图像的最大尺度变换下信息不丢失
img2 = cv2.warpAffine(imgb, M,
(imga.shape[1] + 2 * tw,
imga.shape[0] + 2 * th)) # 对imgb作同样处理,便于后续图片直接原位置拼接
因此在程序中对输入图片使用平移变换,扩大画布的同时平移图片至中央,此时考虑到图片最大尺度的透视变换,只要两个方向都留足原本图像的大小,就能保证图像不丢失。此时图片混合方法需要将原大小的图片2放入变换后图片1的中央。此时得到的最终结果将保留大片区域的黑边,因为扩大了画布,因此可以再通过截取的方法出去黑边。算法如下:
def removeBlack(blackim):
'''去除图像黑边
:blackim: 输入图像
:res_image: 输出图像
'''
blackimgGray = cv2.cvtColor(blackim, cv2.COLOR_BGR2GRAY) # 转为灰度图像
edges_y, edges_x = np.where(blackimgGray != 0) # 求非黑的有效区域
bottom = min(edges_y)
top = max(edges_y)
height = top - bottom # 求有效区域的最大高度
left = min(edges_x)
right = max(edges_x)
width = right - left # 求有效区域的最小宽度
res_image = blackim[bottom:bottom + height, left:left + width] # 裁剪出有效区域
return res_image
2.5. 五、实验结果与分析
2.5.1. 5.1 特征点提取结果


使用了扩大画布的方法后,提取特征点时计算会收到一定的影响,但是可以看到大部分的特征点是能提取到目标图片内部的,因此对最终结果影响较小。
2.5.2. 5.2 特征点匹配结果

图中显示了前100个匹配点结果,依然能看到有部分的特征点匹配收到影响,但是由于匹配到的有效配对点有很多,依然对最终的变换矩阵影响较小。
2.5.3. 5.3 图片变换与混合

图片1 变换后与图片2叠加,去黑边处理后,可以发现相同的部分能够充分重叠,只是分界处有灰度级的区别,因此有分界线痕迹。
2.5.3.1. 5.4 多张图片拼接


最终得到多张图片拼接结果,可以发现在位置上能够得到充分混合,而在灰度级上还有较明显的区别。
2.5.4. 5.5 使用个人图片效果





待拼接的5张图片原图如上,是在西教用手机拍摄的五张照片。拼接的效果如下。

可以看到拼接结果是正确的,通过墙与地面的接缝的笔直情况判断出拼接的效果是好的,它以第3张图片视角为主视角,将其他图片实现了变换拼接。由于是近景,拍摄时视角的转向比较大,所以拼接时变换的程度也大,对应视角的剧烈变化。同时可以发现,由于手机摄影亮度自动调节的关系,第一张图片与其他图片有明显不一样的亮度,从拼接结果也能明显的看出来。
2.6. 六、结论与心得体会
2.6.1. 结论
该程序能够基本完成图像拼接的任务。
在两方面有一定的瑕疵,其一是扩大画布避免信息丢失的方法,对特征点的检测和匹配存在一定的影响。且处理大图像时将消耗大量的空间存储图像信息。如果对于输入的图像有先验的透视变换的尺度和方向的估计,可以特定的对画布进行合适大小的扩大,一定程度上节省空间。
另一方面是由于图像噪声、光照曝光度、模型匹配误差等因素,直接的图像拼接后,重叠区域的拼接处会出现比较明显的边痕迹[4],这个问题一般通过multi-band bleing算法来解决。采用的方法是直接对带拼接的两个图片进行拉普拉斯金字塔分解,后一半对前一半进行融合。由于时间及复杂度问题没能完成。
2.6.2. 心得体会
图像拼接原理涉及到的算法很多很复杂,例如包括sirf特征点提取算法、单应性矩阵求解算法等等,在这个程序里都直接用openCV中的函数实现了。但是对于图像拼接的一般流程有了充分的熟悉与掌握。
实验时最大困难是变换后信息丢失的问题,其实解决这个问题角度考虑了三个,一是从一开始改变画布,也就是程序最终采用的方法;二是在透视变换时增大显示范围,但是这个操作是直接用一个函数实现的,难以直接调整;三是改变矩阵H,但不清楚改变H后,对于重叠部分是否还重叠的影响,也没有采用。
编程时参考了较多CSDN上的文章与程序,但没有对程序完全照搬,也没有任何程序能够直接搬进来就可以用,都得在理解的基础上进行修改调试,有些文献参考的是原理介绍,有些参考了问题解决的思路,编程的方法与思路。
2.7. 七、参考文献
[1], 纸箱里的猫咪, Opencv实战——图像拼接, [OL], CSDN, 2022-06-06, [2023-12-04], https://blog.csdn.net/Thousand_drive/article/details/125084810
[2], 阿飞大魔王, 图像特征点提取(SIFT,SURF,ORB), [OL], CSDN, 2019-03-26, [2023-12-04], https://blog.csdn.net/lucifer_24/article/details/88823448
[3], 牛牛牛叶, 解决透视变换后图片信息丢失的问题,附程序, [OL], CSDN,2020-08-13,[2023-12-04], https://blog.csdn.net/xiaoyeer666/article/details/107973505
[4], LiaoNanan,Python计算机视觉(三)—— 全景图像拼接, [OL], CSDN,2022-04-27,[2023-12-04], https://blog.csdn.net/LIAO_0312/article/details/124460671