图片拼接(实战项目三)
主题思路
-
读入图片
-
预处理图片
-
图片特征提取
-
特征处理
-
特征匹配
-
透视变换
-
图片再处理
-
(可选)图片特征点连线配对
具体代码
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()
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算法
# 检测图像的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()



发表评论