python+OpenCV 实现image-stitching 图像拼接
1.实验环境及数据:
实验环境:
- python3.7+OpenCV3.4
python高于2.7版本的要注意更改所用demo的print的格式,从print “XXX”
-> print("XXX")
数据设置:
- 准备了三个大类的数据:室内,室外,景深差距大的图片。
- 每组数据的设置要从左到右的顺序标号。
- 对拍摄的照片进行一定程度的压缩来进行算法的加速。
2.图像拼接的基本步骤:
概括来说就是:
- 读取图像(需要按顺序)
- 找到图像间的逻辑一致性(单应性)
- 拼接图像
接下来将从第二点开始对图像拼接的原理进行阐述。
3.图像拼接的原理:
- Homography 单应性
因为图片拼接的过程中,图片需要进行一些单应性变换才可以进行较好的拼接。是发生在投影平面上的点和线的投影映射。关于单应变化的内容可以参考点我的上一篇文章图像和图像的映射。使用在图像拼接中,通过单应性变化,把图片投影成拼接需要的形状。
在计算单应性之前,需要进行特征提取和特征匹配:
-
特征提取
特征提取使用是SIFT算子,具体的原理及计算方法可以参考图片特征值匹配。 -
图片的匹配
图片的匹配可以采用OpenCV提供的FLANN或BFMatcher
# FLANN parameters
FLANN_INDEX_KDTREE = 0
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50) # or pass empty dictionary
flann = cv2.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1,des2,k=2)
img3 = cv2.drawMatchesKnn(img1c,kp1,img2c,kp2,matches,None,**draw_params)
cv2.imshow("correspondences", img3)
cv2.waitKey()
匹配完可以得到类似下图的图片
在完成这两个步骤后,就可以进行单应性计算了。
- Warping & Stitching 翘曲和拼接
1.翘曲
在建立完单应性之后,我们就会知道拼接图片在被拼接图片的视角上看应该是怎么样的情况。在这种情况下,我们需要对图片进行一定程度的变形,即翘曲。
翘曲有三种类型:Planar(平面,即旋转和平移)、Cylindrical(圆柱,即把图像绘制在一个圆柱表面上)、Spherical (球形,和圆柱是同类概念)。
我们可以使用单应性矩阵来完成,在代码中表示为:
warped_image = cv2.warpPerspective(image, homography_matrix, dimension_of_warped_image)
图片经过翘曲变换后呈如下效果:
2.拼接
在翘曲完成后,通过重复对扭曲的图像向左向右缝合即可完成图像的拼接。
对于图像拼接部分的原理大致可以表示为如下过程:如果一个图像开始的坐标点为
(
0
,
0
)
(0,0)
(0,0),结束的坐标点为
(
r
e
,
c
e
)
(r_{e},c_{e})
(re,ce),那我们可以通过翘曲得到一个新的图片矩阵,从开始点start:
H
×
[
0
,
0
]
H×[0,0]
H×[0,0]到结束点end:
H
×
[
r
e
,
c
e
]
H×[r_{e},c_{e}]
H×[re,ce],如果开始点为负数,就对它进行平移。并且还要确保单应性矩阵的归一化。
然后再使用basic for looping constructs和覆盖两个图像的方法对图像进行拼接,方法的输入将是stableimage和warpedImage。迭代两个图像对其进行处理,如果像素相等,则将像素作为该值。否则优先考虑非黑色像素。具体代码如下:
def mix_and_match(self, leftImage, warpedImage):
i1y, i1x = leftImage.shape[:2]
i2y, i2x = warpedImage.shape[:2]
print (leftImage[-1,-1])
t = time.time()
black_l = np.where(leftImage == np.array([0,0,0]))
black_wi = np.where(warpedImage == np.array([0,0,0]))
print (time.time() - t)
print (black_l[-1])
for i in range(0, i1x):
for j in range(0, i1y):
try:
if(np.array_equal(leftImage[j,i],np.array([0,0,0])) and np.array_equal(warpedImage[j,i],np.array([0,0,0]))):
# print "BLACK"
# instead of just putting it with black,
# take average of all nearby values and avg it.
warpedImage[j,i] = [0, 0, 0]
else:
if(np.array_equal(warpedImage[j,i],[0,0,0])):
# print "PIXEL"
warpedImage[j,i] = leftImage[j,i]
else:
if not np.array_equal(leftImage[j,i], [0,0,0]):
bw, gw, rw = warpedImage[j,i]
bl,gl,rl = leftImage[j,i]
# b = (bl+bw)/2
# g = (gl+gw)/2
# r = (rl+rw)/2
warpedImage[j, i] = [bl,gl,rl]
except:
pass
# cv2.imshow("waRPED mix", warpedImage)
# cv2.waitKey()
return warpedImage
3.实验结果及分析
test1:
首先进行最简单的图片图片拼接,我把下图下图(集美大学延奎图书馆)裁剪成了三部分,来测试其的拼接效果。
左边三张为剪切后的图片,每张图都与相邻右边的图片有所重复,最右为原图:
拼接结果图:
整张图整体和原图没有什么差别,只有楼梯部分变斜了,至于尺寸的问题是因为resize()的原因。
test2
对于外景图片的拼接,使用的是集美大学的尚大楼的图片:
拼接结果:
基本拼接上是没有什么大问题的,只是第二张和第三张图片拼接之后的亮度差异表明显,离镜头较近的石头的拼接上出现了一定的偏移。
test3
现在进行室外景深不同的景物的拼接,使用的图片是集美大学美玲楼附近的三张图片。
三张图片如下:
拼接的效果图:
可以看出来在第一张图片和第二张图片的拼接上并没有完成的很好,有明显的间隔感,楼房之间的连接也出现了差错。
但是在第二张和第三张的部分就完成的比较好。
test4
对于室内照片的拼接,使用的是我自己桌子的照片(小乱= =)
拼接结果:
其中蓝色部分圈出的是拼接的比较好的,红色部分圈出的拼接的比较不好的地方。在拼接的交界处都出现了一定程度的偏移。可见对于一些细节比较多的,距离镜头比较近的图片的拼接效果就比较差了。
如果再在上面的基础上增加一张桌子右侧的图片:
拼接的效果就变得非常,极其不理想起来:
??????
导致这样的原因大致是因为最后一张图片和第三张图片之间的关联点确实太少了(只有几本书是一样的),并且最后一张图还存在着干扰项。可以看出来处理的过程中,算法想把左边的木质墙面和右边的木质墙面当成一个平面处理,所以整个图像变得很扭曲。
- 实验总结
从实验的结果上来看,对于室外的远处景物,该算法都可以进行比较好的拼接,基本上看不出明显的拼接痕迹。而对于室外近处的景物,分两种情况:一是,如果近处的景物很杂乱并且很相似(例如草堆,树群,人群等)拼接效果就会很差;而是如果近处景物是比较规整和稀少的(例如下水道盖,道道路瓷砖等)拼接效果就会变得很好。最后对于室内的拼接,该算法遭遇了滑铁卢。对于细节特别多并且相似部分也特别多的图像拼接,该算法的表现就变得时好时坏,有的时候甚至极其糟糕。
当然对于图像拼接的方法和算法还有很多,例如最大值切割法等等。
参考文章:https://kushalvyas.github.io/stitching.html
参考代码:https://github.com/kushalvyas/Python-Multiple-Image-Stitching