圖片拼接(實(shí)戰(zhàn)項(xiàng)目三)
主題思路
-
讀入圖片
-
預(yù)處理圖片
-
圖片特征提取
-
特征處理
-
特征匹配
-
透視變換
-
圖片再處理
-
(可選)圖片特征點(diǎn)連線配對(duì)
具體代碼
Sticher.py
引入頭文件
import cv2 import numpy as np
創(chuàng)建類
class Sticher:
自定義函數(shù)
-
def stich:外部接口函數(shù)
-
def detectAndDescribe:用于圖片的特征點(diǎn)提取,內(nèi)部邏輯函數(shù)
-
def matchKeypoints:特征點(diǎn)匹配
-
def drawMatches:顯示2圖片的特征點(diǎn)匹配,用線勾勒出來(lái),方便觀察細(xì)節(jié)(可選)
展示圖片函數(shù):show()
? ?def show(self, image, name='demo'): ? ? ? ?cv2.imshow(name, image) ? ? ? ?cv2.waitKey(0) ? ? ? ?cv2.destroyAllWindows()
外部接口函數(shù):stich()
stich函數(shù)傳入一個(gè)images的列表,里面包含2個(gè)圖片,經(jīng)過(guò)一系列其他成員函數(shù)后將2圖片合并
參數(shù)說(shuō)明:
ratio = 0.75 (先驗(yàn)知識(shí),拿來(lái)主義~)文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-832062.html
# reprojThresh是RANSAC算法中的閾值參數(shù),用于判斷是否為內(nèi)點(diǎn)(inlier)的閾值 # reprojThresh越低,說(shuō)明受外點(diǎn)影響越小,圖像擬合結(jié)果更精細(xì),但可能過(guò)濾掉一些有價(jià)值的匹配點(diǎn) # reprojThresh越高,說(shuō)明容忍度越高,但效果可能偏低
? ?def stich(self, images, ratio=0.75, reprojThresh=4.0, showMatches=False):#showMatches是自己定義的一個(gè)選項(xiàng),沒(méi)什么用 ? ? ? ?# 獲取輸入圖片 ? ? ? (imageB, imageA) = images ? ? ? ?# 檢測(cè)A,B圖片的SIFT關(guān)鍵特征點(diǎn),并計(jì)算特征描述子 ? ? ? (kpsA, featureA) = self.detectAndDescribe(imageA) ? ? ? (kpsB, featureB) = self.detectAndDescribe(imageB) ? ? ? ?# 經(jīng)過(guò)detectAndDescribe()后,返回的kpsA和kpsB都是經(jīng)過(guò)處理后的特征點(diǎn)集信息 ? ? ? ?# kpsA,kpsB里面只包含了特征點(diǎn)的坐標(biāo)信息(pt),因?yàn)樵摮蓡T方法已經(jīng)過(guò)濾提取 ? ? ? ?# 匹配兩張圖片的所有特征點(diǎn),返回匹配結(jié)果 ? ? ? ?M = self.matchKeypoints(kpsA, kpsB, featureA, featureB, ratio, reprojThresh) ? ? ? ? ?# 如果返回結(jié)果為空,則沒(méi)有匹配成功的特征點(diǎn),退出算法 ? ? ? ?if M is None: ? ? ? ? ? ?return None ? ? ? ? ?# 若匹配成功 ? ? ? (matches, H, status) = M ? ? ? ?# 我們處理的思路是: ? ? ? ?# 將右邊圖片進(jìn)行透視變換,使之與左邊的圖片配對(duì) ? ? ? ?# H為透視變換矩陣 ? ? ? ?# 該函數(shù)要把imageA依據(jù)關(guān)系矩陣H進(jìn)行透視變換,得到一個(gè)(寬,高)尺寸的圖片 ? ? ? ?# 由于H是綜合imageA和imageB得到的,所以我們執(zhí)行完warnPerspective后,會(huì)看到imageA中帶上了imageB ? ? ? ?result = cv2.warpPerspective(imageA, H, (imageA.shape[1] + imageB.shape[1], imageA.shape[0])) ? ? ? ?self.show(result) ? ? ? ?# 參數(shù)對(duì)應(yīng)位: (高,寬) ? ? ? ?result[0:imageB.shape[0], 0:imageB.shape[1]] = imageB ? ? ? ?self.show(result) ? ? ? ?# 檢測(cè)是否需要顯示圖片匹配: ? ? ? ?if showMatches: ? ? ? ? ? ?# 生成匹配圖片 ? ? ? ? ? ?vis = self.drawMatches(imageA, imageB, kpsA, kpsB, matches, status) ? ? ? ? ? ?return (result, vis) ? ? ? ?return result
特征點(diǎn)提取分析函數(shù):detectAndDescribe()
核心思路:SIFT算法文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-832062.html
# 檢測(cè)圖像的SIFT關(guān)鍵特征點(diǎn)并計(jì)算特征描述子 def detectAndDescribe(self, image): ? ?# 尋找圖片的特征點(diǎn)先將圖片轉(zhuǎn)換為灰度圖 ? ?gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) ? ?# 建立一個(gè)SIFT特征掃描器(可能有的版本有專利保護(hù)) ? ?descriptor = cv2.xfeatures2d.SIFT_create() ? ?# 檢測(cè)SIFT特征點(diǎn),并計(jì)算描述子 ? ?# None表示檢測(cè)全圖,沒(méi)有掩碼遮罩 ? (kps, features) = descriptor.detectAndCompute(image, None)#唯該函數(shù)真正進(jìn)行特征點(diǎn)分析 ? ?#kps包含一個(gè)特征點(diǎn)的一系列信息:一般我們需要的是 kp.pt 即圖片的坐標(biāo)信息(x,y) ? ?#kps的其他信息如:size(特征點(diǎn)尺度大小),angle(特征點(diǎn)的方向角度),class_id(特征點(diǎn)的唯一標(biāo)識(shí)) ? ? ?# 將結(jié)果轉(zhuǎn)換成Numpy數(shù)組 ? ?# 接下來(lái)只提取 kps中 pt 的信息,并將其轉(zhuǎn)成浮點(diǎn)32位的形式,格式需服務(wù)于后面的透視變換要求 ? ?# kp.pt 這的pt 包含一個(gè)特征點(diǎn)的坐標(biāo)位置信息,一般是一個(gè)二維向量 ? ?kps = np.float32([kp.pt for kp in kps]) ? ? ?# 返回特征點(diǎn)集(kps)和特征點(diǎn)局部區(qū)域的特征描述信息(features:特征描述子) ? ?return (kps, features)
特征匹配函數(shù):matchKeypoints()
#注:此時(shí)傳入的kps都是只保留了特征點(diǎn)的坐標(biāo)信息的二維數(shù)組 def matchKeypoints(self, kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh): ? ?# 建立暴力匹配器(BF算法) ? ?matcher = cv2.BFMatcher() ? ?# 使用knn檢測(cè)來(lái)自A,B圖的SIFT特征匹配對(duì),k=2 ? ?# 下面進(jìn)行區(qū)域匹配,得到一個(gè)粗糙的匹配結(jié)果,還要進(jìn)行過(guò)濾 ? ?rawMatches = matcher.knnMatch(featuresA, featuresB, 2) ? ?matches = [] ?# 該列表用于存放最終的匹配結(jié)果 ? ?for m in rawMatches: ? ? ? ?if len(m) == 2 and m[0].distance < m[1].distance * ratio: ? ? ? ? ? ?# trainIdx表示訓(xùn)練圖像的特征點(diǎn)索引信息,queryIdx表示查詢圖像的特征點(diǎn)索引信息 ? ? ? ? ? ?matches.append((m[0].trainIdx, m[0].queryIdx)) ? ?# matches里面現(xiàn)在存放的是 二維數(shù)組,每一個(gè)單獨(dú)的元素都代表一個(gè)匹配對(duì)的信息 ? ? ?# 當(dāng)篩選后的匹配對(duì)大于4時(shí),(我們得保證至少有4個(gè)點(diǎn),才可以進(jìn)行透視變換_實(shí)際上不是這樣理解,底層涉及高數(shù)) ? ?if len(matches) > 4: ? ? ? ?# 獲取匹配對(duì)的點(diǎn)的坐標(biāo) ? ? ? ?# 這個(gè)for循環(huán)表示: ? ? ? ?# A:查詢出:查詢圖像特征點(diǎn)的下表索引i ? ? ? ?# 依據(jù)i找到其在A圖像的特征點(diǎn)集的信息(此處kpsA已經(jīng)被提前處理成只有kp.pt坐標(biāo)位信息了 ? ? ? ?ptsA = np.float32([kpsA[i] for (_, i) in matches]) ? ? ? ?ptsB = np.float32([kpsB[i] for (i, _) in matches]) ? ? ? ?# 經(jīng)過(guò)上述計(jì)算后,得到的ptsA,ptsB只是篩選后的特征點(diǎn)的坐標(biāo)二維數(shù)組 ? ? ? ?# 計(jì)算出透視關(guān)系 ? ? ? ?# cv2.RANSAC:表示使用 RANSAC(Random Sample Consensus)算法進(jìn)行魯棒性估計(jì)和選擇內(nèi)點(diǎn)的方法 ? ? ? ?# findHomography()與cv2.getPerspectiveTransform()不同的是,前者注重圖像配準(zhǔn),后者注重圖像透視變換,雖然兩者都是用來(lái)圖片的變換矩陣 ? ? ? (H, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC, reprojThresh) ? ? ? ?# H表示單應(yīng)性矩陣,表示從原始圖像到目標(biāo)圖像的變換矩陣(到時(shí)候會(huì)拿來(lái)對(duì)right圖像進(jìn)行透視變換) ? ? ? ?return (matches, H, status) ? ? ? ?# matches里面存放的是二維數(shù)組,特征點(diǎn)的下標(biāo)索引值(訓(xùn)練圖片的下標(biāo)索引值,查詢圖片的下標(biāo)索引值) ? ?return None
(可選)繪制匹配函數(shù):drawMatches()
# 同理,這里的kps都是處理后的 def drawMatches(self, imageA, imageB, kpsA, kpsB, matches, status): ? ?# 提取圖片的尺寸信息 ? (hA, wA) = imageA.shape[:2] ? (hB, wB) = imageB.shape[:2] ? ?# 使用np.zero創(chuàng)建一個(gè)空白圖像(也可以說(shuō)是創(chuàng)建一個(gè)三維數(shù)組,本質(zhì)一樣,都是zeros方法) ? ?vis = np.zeros((max(hA, hB), wA + wB, 3), dtype="uint8") ? ?# vis 就是我們用來(lái)繪制:特征點(diǎn)間連線的圖 ? ?vis[0:hA, 0:wA] = imageA#左邊放imageA ? ?vis[0:hB, wA:] = imageB#右邊放imageB ? ? ?for ((trainIdx, queryIdx), s) in zip(matches, status): ? ? ? ?if s == 1: ?# 即點(diǎn)對(duì)匹配成功 ? ? ? ? ? ?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) ?# 在圖片上畫(huà)出綠色線條,端點(diǎn)兩端連接著2個(gè)匹配的特征點(diǎn) ? ? ?return vis ?# vis 代表可視化結(jié)果
imageStichering.py
from Sticher import Sticher# 不能直接寫(xiě)import,原理同(java 靜態(tài)內(nèi)部類static一樣) import cv2 ? imageA = cv2.imread("right_01.png") imageB = cv2.imread("left_01.png") ? #創(chuàng)建一個(gè)工具類 Sticher = Sticher() (result,vis) = Sticher.stich([imageB,imageA],showMatches=True) ? #顯示所有圖片 cv2.imshow('line',vis) cv2.imshow('res',result) cv2.waitKey(0) cv2.destroyAllWindows()
到了這里,關(guān)于OpenCV處理圖片拼接的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!