图片拼接(实战项目三)
主题思路
-
读入图片
-
预处理图片
-
图片特征提取
-
特征处理
-
特征匹配
-
透视变换
-
图片再处理
-
(可选)图片特征点连线配对
具体代码
Sticher.py
引入头文件
import cv2 import numpy as np
创建类
class Sticher:
自定义函数
-
def stich:外部接口函数
-
def detectAndDescribe:用于图片的特征点提取,内部逻辑函数
-
def matchKeypoints:特征点匹配
-
def drawMatches:显示2图片的特征点匹配,用线勾勒出来,方便观察细节(可选)
展示图片函数:show()
def show(self, image, name='demo'): cv2.imshow(name, image) cv2.waitKey(0) cv2.destroyAllWindows()
外部接口函数:stich()
stich函数传入一个images的列表,里面包含2个图片,经过一系列其他成员函数后将2图片合并
参数说明:
ratio = 0.75 (先验知识,拿来主义~)文章来源:https://www.toymoban.com/news/detail-832062.html
# reprojThresh是RANSAC算法中的阈值参数,用于判断是否为内点(inlier)的阈值 # reprojThresh越低,说明受外点影响越小,图像拟合结果更精细,但可能过滤掉一些有价值的匹配点 # reprojThresh越高,说明容忍度越高,但效果可能偏低
def stich(self, images, ratio=0.75, reprojThresh=4.0, showMatches=False):#showMatches是自己定义的一个选项,没什么用 # 获取输入图片 (imageB, imageA) = images # 检测A,B图片的SIFT关键特征点,并计算特征描述子 (kpsA, featureA) = self.detectAndDescribe(imageA) (kpsB, featureB) = self.detectAndDescribe(imageB) # 经过detectAndDescribe()后,返回的kpsA和kpsB都是经过处理后的特征点集信息 # kpsA,kpsB里面只包含了特征点的坐标信息(pt),因为该成员方法已经过滤提取 # 匹配两张图片的所有特征点,返回匹配结果 M = self.matchKeypoints(kpsA, kpsB, featureA, featureB, ratio, reprojThresh) # 如果返回结果为空,则没有匹配成功的特征点,退出算法 if M is None: return None # 若匹配成功 (matches, H, status) = M # 我们处理的思路是: # 将右边图片进行透视变换,使之与左边的图片配对 # H为透视变换矩阵 # 该函数要把imageA依据关系矩阵H进行透视变换,得到一个(宽,高)尺寸的图片 # 由于H是综合imageA和imageB得到的,所以我们执行完warnPerspective后,会看到imageA中带上了imageB result = cv2.warpPerspective(imageA, H, (imageA.shape[1] + imageB.shape[1], imageA.shape[0])) self.show(result) # 参数对应位: (高,宽) result[0:imageB.shape[0], 0:imageB.shape[1]] = imageB self.show(result) # 检测是否需要显示图片匹配: if showMatches: # 生成匹配图片 vis = self.drawMatches(imageA, imageB, kpsA, kpsB, matches, status) return (result, vis) return result
特征点提取分析函数:detectAndDescribe()
核心思路:SIFT算法文章来源地址https://www.toymoban.com/news/detail-832062.html
# 检测图像的SIFT关键特征点并计算特征描述子 def detectAndDescribe(self, image): # 寻找图片的特征点先将图片转换为灰度图 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 建立一个SIFT特征扫描器(可能有的版本有专利保护) descriptor = cv2.xfeatures2d.SIFT_create() # 检测SIFT特征点,并计算描述子 # None表示检测全图,没有掩码遮罩 (kps, features) = descriptor.detectAndCompute(image, None)#唯该函数真正进行特征点分析 #kps包含一个特征点的一系列信息:一般我们需要的是 kp.pt 即图片的坐标信息(x,y) #kps的其他信息如:size(特征点尺度大小),angle(特征点的方向角度),class_id(特征点的唯一标识) # 将结果转换成Numpy数组 # 接下来只提取 kps中 pt 的信息,并将其转成浮点32位的形式,格式需服务于后面的透视变换要求 # kp.pt 这的pt 包含一个特征点的坐标位置信息,一般是一个二维向量 kps = np.float32([kp.pt for kp in kps]) # 返回特征点集(kps)和特征点局部区域的特征描述信息(features:特征描述子) return (kps, features)
特征匹配函数:matchKeypoints()
#注:此时传入的kps都是只保留了特征点的坐标信息的二维数组 def matchKeypoints(self, kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh): # 建立暴力匹配器(BF算法) matcher = cv2.BFMatcher() # 使用knn检测来自A,B图的SIFT特征匹配对,k=2 # 下面进行区域匹配,得到一个粗糙的匹配结果,还要进行过滤 rawMatches = matcher.knnMatch(featuresA, featuresB, 2) matches = [] # 该列表用于存放最终的匹配结果 for m in rawMatches: if len(m) == 2 and m[0].distance < m[1].distance * ratio: # trainIdx表示训练图像的特征点索引信息,queryIdx表示查询图像的特征点索引信息 matches.append((m[0].trainIdx, m[0].queryIdx)) # matches里面现在存放的是 二维数组,每一个单独的元素都代表一个匹配对的信息 # 当筛选后的匹配对大于4时,(我们得保证至少有4个点,才可以进行透视变换_实际上不是这样理解,底层涉及高数) if len(matches) > 4: # 获取匹配对的点的坐标 # 这个for循环表示: # A:查询出:查询图像特征点的下表索引i # 依据i找到其在A图像的特征点集的信息(此处kpsA已经被提前处理成只有kp.pt坐标位信息了 ptsA = np.float32([kpsA[i] for (_, i) in matches]) ptsB = np.float32([kpsB[i] for (i, _) in matches]) # 经过上述计算后,得到的ptsA,ptsB只是筛选后的特征点的坐标二维数组 # 计算出透视关系 # cv2.RANSAC:表示使用 RANSAC(Random Sample Consensus)算法进行鲁棒性估计和选择内点的方法 # findHomography()与cv2.getPerspectiveTransform()不同的是,前者注重图像配准,后者注重图像透视变换,虽然两者都是用来图片的变换矩阵 (H, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC, reprojThresh) # H表示单应性矩阵,表示从原始图像到目标图像的变换矩阵(到时候会拿来对right图像进行透视变换) return (matches, H, status) # matches里面存放的是二维数组,特征点的下标索引值(训练图片的下标索引值,查询图片的下标索引值) return None
(可选)绘制匹配函数:drawMatches()
# 同理,这里的kps都是处理后的 def drawMatches(self, imageA, imageB, kpsA, kpsB, matches, status): # 提取图片的尺寸信息 (hA, wA) = imageA.shape[:2] (hB, wB) = imageB.shape[:2] # 使用np.zero创建一个空白图像(也可以说是创建一个三维数组,本质一样,都是zeros方法) vis = np.zeros((max(hA, hB), wA + wB, 3), dtype="uint8") # vis 就是我们用来绘制:特征点间连线的图 vis[0:hA, 0:wA] = imageA#左边放imageA vis[0:hB, wA:] = imageB#右边放imageB for ((trainIdx, queryIdx), s) in zip(matches, status): if s == 1: # 即点对匹配成功 ptA = (int(kpsA[queryIdx][0]), int(kpsA[queryIdx][1])) ptB = (int(kpsB[trainIdx][0] + wA), int(kpsB[trainIdx][1])) cv2.line(vis, ptA, ptB, (0, 255, 0), 1) # 在图片上画出绿色线条,端点两端连接着2个匹配的特征点 return vis # vis 代表可视化结果
imageStichering.py
from Sticher import Sticher# 不能直接写import,原理同(java 静态内部类static一样) import cv2 imageA = cv2.imread("right_01.png") imageB = cv2.imread("left_01.png") #创建一个工具类 Sticher = Sticher() (result,vis) = Sticher.stich([imageB,imageA],showMatches=True) #显示所有图片 cv2.imshow('line',vis) cv2.imshow('res',result) cv2.waitKey(0) cv2.destroyAllWindows()
到了这里,关于OpenCV处理图片拼接的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!