OpenCV 图像拼接实战:SIFT 特征匹配 + 透视变换实现全景图
文章目录完整代码导入库与辅助函数特征提取函数 detectAndDescribe提取两张图的特征特征匹配 —— 暴力匹配器与比值筛选透视变换矩阵透视变换与图像拼接完整代码A图B图import cv2 import numpy as np import sys defcv_show(name,image):cv2.imshow(name,image)cv2.waitKey(0)defdetectAndDescribe(image):graycv2.cvtColor(image,cv2.COLOR_BGR2GRAY)siftcv2.SIFT_create()(kps,des)sift.detectAndCompute(gray,None)kps_floatnp.float32([kp.ptforkp in kps])return(kps,kps_float,des)imageAcv2.imread(A.jpg)cv_show(imageA,imageA)imageBcv2.imread(B.jpg)cv_show(imageB,imageB)(kpsA,kpsA_float,desA)detectAndDescribe(imageA)(kpsB,kpsB_float,desB)detectAndDescribe(imageB)matchercv2.BFMatcher()rawMatchesmatcher.knnMatch(desB,desA,2)good[]matches[]form in rawMatches:iflen(m)2and m[0].distance0.65*m[1].distance:good.append(m)matches.append((m[0].queryIdx,m[0].trainIdx))print(len(good))print(matches)viscv2.drawMatchesKnn(imageB,kpsB,imageA,kpsA,good,None,flagscv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)cv_show(keypoint Matcher,vis)iflen(matches)4:ptsBnp.float32([kpsB_float[i]for(i,_)in matches])ptsAnp.float32([kpsA_float[i]for(_,i)in matches])(H,mask)cv2.findHomography(ptsB,ptsA,cv2.RANSAC,10)else:print(图片未找到4个匹配点)sys.exit()resultcv2.warpPerspective(imageB,H,(imageB.shape[1]imageA.shape[1],imageB.shape[0]))cv_show(resultB,result)result[0:imageA.shape[0],0:imageA.shape[1]]imageAcv_show(result,result)cv2.imwrite(ping_jie.jpg,result)导入库与辅助函数import cv2 import numpy as np import sys defcv_show(name,image):cv2.imshow(name,image)cv2.waitKey(0)自定义的显示函数显示图像并等待按键方便观察每一步结果。特征提取函数 detectAndDescribedefdetectAndDescribe(image):graycv2.cvtColor(image,cv2.COLOR_BGR2GRAY)siftcv2.SIFT_create()(kps,des)sift.detectAndCompute(gray,None)kps_floatnp.float32([kp.ptforkp in kps])return(kps,kps_float,des)首先将彩色图转为灰度图因为特征检测不需要颜色信息灰度图计算更快再创建 SIFT 检测器找出图像中稳定的关键点比如角点。保存三个值kps原始关键点对象用于绘图kps_float:是关键点的坐标 (x, y) 提取出来转为 float 类型的数组des:描述子矩阵提取两张图的特征imageAcv2.imread(A.jpg)cv_show(imageA,imageA)imageBcv2.imread(B.jpg)cv_show(imageB,imageB)(kpsA,kpsA_float,desA)detectAndDescribe(imageA)(kpsB,kpsB_float,desB)detectAndDescribe(imageB)读取两张图片 A.jpg 和 B.jpg它们有重叠区域。分别调用 detectAndDescribe 得到两幅图的关键点、坐标数组和描述子。kps数据类型是keypoint存放的是关键点坐标。kps_float:将坐标点信息转换成float形式的为数组。特征匹配 —— 暴力匹配器与比值筛选matchercv2.BFMatcher()rawMatchesmatcher.knnMatch(desB,desA,2)good[]matches[]form in rawMatches:iflen(m)2and m[0].distance0.65*m[1].distance:good.append(m)matches.append((m[0].queryIdx,m[0].trainIdx))print(len(good))print(matches)viscv2.drawMatchesKnn(imageB,kpsB,imageA,kpsA,good,None,flagscv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)cv_show(keypoint Matcher,vis)创建暴力匹配器它会计算 B 图中每个描述子与 A 图中所有描述子的距离然后找出最近的两个。让m循环遍历两个点之间距离的信息如果如果最近距离 m[0].distance 小于 0.65 倍的第二近距离 m[1].distance就认为这个点的信息是有效的。queryIdx是 B 图中特征点的索引trainIdx 是 A 图中特征点的索引打印匹配数量和AB图的对应索引号输出一共有31个有效匹配31[(14,76),(36,105),(39,105),(63,118),(65,121),(66,122),(74,130),(83,128),(87,136),(93,140),(105,147),(118,172),(138,176),(154,191),(155,192),(158,198),(164,213),(165,206),(176,217),(185,227),(201,242),(202,243),(204,246),(207,250),(209,255),(212,257),(217,7),(228,275),(229,276),(231,275),(233,276)]可以看到有很多连线如果匹配大多平行且一致说明找得不错。如果效果不好就得修改阈值。透视变换矩阵iflen(matches)4:ptsBnp.float32([kpsB_float[i]for(i,_)in matches])ptsAnp.float32([kpsA_float[i]for(_,i)in matches])(H,mask)cv2.findHomography(ptsB,ptsA,cv2.RANSAC,10)else:print(图片未找到4个匹配点)sys.exit()从 matches 中提取 B 图中的点坐标ptsB和 A 图中对应的点坐标ptsA。cv2.findHomography利用匹配点对求解从 B 图到 A 图的透视变换矩阵 H。使用 RANSAC 算法随机抽样一致性来剔除错误的匹配外点提高鲁棒性。10 是重投影误差阈值表示如果某对点变换后的误差大于 10 像素则视为外点。如果匹配点不足 4 个程序输出提示并退出。透视变换与图像拼接resultcv2.warpPerspective(imageB,H,(imageB.shape[1]imageA.shape[1],imageB.shape[0]))cv_show(resultB,result)result[0:imageA.shape[0],0:imageA.shape[1]]imageAcv_show(result,result)cv2.imwrite(ping_jie.jpg,result)图像 imageB 按照透视变换矩阵 H 进行透视变换输出到目标画布画布的尺寸设置为 宽度 B 的宽度 A 的宽度高度 B 的高度。变换后B 图被“拉”到 A 图的坐标系中但由于画布大小B 会出现在右侧然后将 A 图直接复制到画布的左上角result[0:hA, 0:wA] imageA。因为 A 图本身已经在正确位置所以直接覆盖即可。