OpenCV图像处理笔记[15]
一、全景图像拼接调试日志
1. 在Pycharm中导入项目代码
报错:module 'cv2.cv2' has no attribute 'xfeatures2d'
解决:该算法被申请了专利,将opencv版本退到3.4.2,
-
卸载之前的包
pip uninstall opencv-python
pip uninstall opencv-contrib-python
-
安装3.4.2的版本
pip install opencv-python == 3.4.2.16
pip install opencv-contrib-python == 3.4.2.16
2. Python引用同级目录报错
报错:发现在import Stitcher 包报错
解决:pycharm不会将当前文件目录自动加入自己的sourse_path。
右键make_directory as-->Sources Root
将当前工作的文件夹加入source_path
3. 运行结果
二、 代码理解
1. 拼接函数--stitch(self, images, ratio=0.75, reprojThresh=4.0,showMatches=False)
#拼接函数
def stitch(self, images, ratio=0.75, reprojThresh=4.0,showMatches=False):
#获取输入图片
(imageB, imageA) = images
#检测A、B图片的SIFT关键特征点,并计算特征描述子
(kpsA, featuresA) = self.detectAndDescribe(imageA)
(kpsB, featuresB) = self.detectAndDescribe(imageB)
# 匹配两张图片的所有特征点,返回匹配结果
M = self.matchKeypoints(kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh)
# 如果返回结果为空,没有匹配成功的特征点,退出算法
if M is None:
return None
# 否则,提取匹配结果
# H是3x3视角变换矩阵
(matches, H, status) = M
# 将图片A进行视角变换,result是变换后图片
#cv2.warpPespective(imageA, H, (imageA.shape[1] + imageB.shape[1], imageA.shape[0]))
# 获得根据单应性矩阵变化后的图像
#参数说明:image表示输入图像,H表示单应性的矩阵,
#(imageA.shape[1] + imageB.shape[1], imageA.shape[0])表示矩阵变化后的维度
result = cv2.warpPerspective(imageA, H, (imageA.shape[1] + imageB.shape[1], imageA.shape[0]))
self.cv_show('result222', result)
# 将图片B传入result图片最左端
result[0:imageB.shape[0], 0:imageB.shape[1]] = imageB
self.cv_show('result', result)
# 检测是否需要显示图片匹配
if showMatches:
# 生成匹配图片
vis = self.drawMatches(imageA, imageB, kpsA, kpsB, matches, status)
# 返回结果
return (result, vis)
# 返回匹配结果
return result
-
cv2.warpPerspective
:对图像进行透视变换,就是变形 -
透视变换(Perspective Transformation)是将图片投影到一个新的视平面(Viewing Plane),也称作投影映射(Projective Mapping)
-
OpenCV中warpPerspective( )函数:实现图片的透视变换
输入梯形四个顶点的坐标和目标画布四个角的坐标,即可自动完成转换
核心代码只有两行:
首先读取两个坐标数组,计算变换矩阵;
然后根据变换矩阵对原图进行透视变换,并输出到目标画布。
2. 检测图片SIFT特征点,计算特征描述子--detectAndDescribe(self, image)
def detectAndDescribe(self, image):
# 将彩色图片转换成灰度图
gray= cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 建立SIFT生成器
descriptor = cv2.xfeatures2d.SIFT_create()
# 检测SIFT特征点,并计算描述子
(kps, features) = descriptor.detectAndCompute(image, None)
# 将结果转换成NumPy数组
kps = np.float32([kp.pt for kp in kps])
# 返回特征点集,及对应的描述特征
return (kps, features)
-
尺度不变特征变换SIFT(Scale-invariant feature transform)
-
算法要点
- 降采样
- 高斯差分算子DOG(Difference of Gaussians)
- 关键点检测
- 关键点描述
- 关键点匹配
-
参考
-
3. 匹配图片特征点--matchKeypoints(self, kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh)
def matchKeypoints(self, kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh):
# 建立暴力匹配器
matcher = cv2.BFMatcher()
# 使用KNN检测来自A、B图的SIFT特征匹配对,K=2
rawMatches = matcher.knnMatch(featuresA, featuresB, 2)
matches = []
for m in rawMatches:
# 当最近距离跟次近距离的比值小于ratio值时,保留此匹配对#如果第一个邻近距离比第二个邻近距离的0.75倍小,则保留
if len(m) == 2 and m[0].distance < m[1].distance * ratio:
# 存储两个点在featuresA, featuresB中的索引值
matches.append((m[0].trainIdx, m[0].queryIdx))
# 当筛选后的匹配对大于4时,计算视角变换矩阵
if len(matches) > 4:
# 获取匹配对的点坐标
ptsA = np.float32([kpsA[i] for (_, i) in matches])
ptsB = np.float32([kpsB[i] for (i, _) in matches])
#有时候单个独立下划线是用作一个名字,来表示某个变量是临时的或无关紧要的。
# 计算视角变换矩阵
#cv2.findHomography(kpA, kpB, cv2.RANSAC, reproThresh) # 计算出单应性矩阵
#参数说明:kpA表示图像A关键点的坐标, kpB图像B关键点的坐标, 使用随机抽样一致性算法来进行迭代,reproThresh表示每次抽取样本的个数
(H, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC, reprojThresh)
# 返回结果
return (matches, H, status)
# 如果匹配对小于4时,返回None
return None
-
matcher = cv2.BFMatcher() # 使用KNN检测来自A、B图的SIFT特征匹配对,K=2 rawMatches = matcher.knnMatch(featuresA, featuresB, 2)
Brute-Force匹配非常简单,首先在第一幅图像中选取一个关键点然后依次与第二幅图像的每个关键点进行(描述符)距离测试,最后返回距离最近的关键点.
对于BF匹配器,必须使用
cv2.BFMatcher()
创建BFMatcher对象。它需要两个可选的参数.
- 第一个是
normType
,它指定要使用的距离测量,默认情况下,它是cv2.NORM_L2
.它适用于SIFT,SURF等(cv2.NORM_L1
也在那里).对于基于二进制字符串的描述符,如ORB,BRIEF,BRISK等,应使用cv2.NORM_HAMMING
,使用汉明距离作为度量,如果ORB使用WTA_K == 3or4,则应使用cv2.NORM_HAMMING2
.crossCheck
:默认值为False。如果设置为True,匹配条件就会更加严格,只有到A中的第i个特征点与B中的第j个特征点距离最近,并且B中的第j个特征点到A中的第i个特征点也是最近时才会返回最佳匹配,即这两个特征点要互相匹配才行.两个重要的方法是
BFMatcher.match()
和BFMatcher.knnMatch()
, 第一个返回最佳匹配, 第二种方法返回k个最佳匹配,其中k由用户指定.使用
cv2.drawMatches()
来绘制匹配的点,它会将两幅图像先水平排列,然后在最佳匹配的点之间绘制直线。如果前面使用的BFMatcher.knnMatch()
,现在可以使用函数cv2.drawMatchsKnn
为每个关键点和它的个最佳匹配点绘制匹配线。如果要选择性绘制就要给函数传入一个掩模
-
单应性矩阵Homography Matrix
H, status = cv2.findHomography(ptsA,ptsB,cv2.RANSAC,ransacReprojThreshold) #其中H为求得的单应性矩阵矩阵 #status则返回一个列表来表征匹配成功的特征点。 #ptsA,ptsB为关键点 #cv2.RANSAC, ransacReprojThreshold这两个参数与RANSAC有关
-
随机抽样一致算法RANSAC(Random sample consensus)
-
参考
4. 画出拼接图像
def drawMatches(self, imageA, imageB, kpsA, kpsB, matches, status):
# 初始化可视化图片,将A、B图左右连接到一起
(hA, wA) = imageA.shape[:2]
(hB, wB) = imageB.shape[:2]
vis = np.zeros((max(hA, hB), wA + wB, 3), dtype="uint8")
vis[0:hA, 0:wA] = imageA
vis[0:hB, wA:] = imageB
# 联合遍历,画出匹配对
# zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。
#如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表。
for ((trainIdx, queryIdx), s) in zip(matches, status):
# 当点对匹配成功时,画到可视化图上
if s == 1:
# 画出匹配对 ,query是要匹配的描述子,train是被匹配的描述子
ptA = (int(kpsA[queryIdx][0]), int(kpsA[queryIdx][1]))
ptB = (int(kpsB[trainIdx][0]) + wA, int(kpsB[trainIdx][1]))
#cv2.line(imageA, kpsA, imageB, kpsB, (0,0,255), 2) 进行画出直线的操作
#参数说明:imageA和imageB表示输入图片, kpsA和kpsB表示关键点的坐标(x, y) ,(0, 0, 255)表示颜色, 2表示直线的宽度
cv2.line(vis, ptA, ptB, (0, 255, 0), 1)
# 返回可视化结果
return vis