在這篇文章中,我將對(duì)圖像配準(zhǔn)進(jìn)行一個(gè)簡(jiǎn)單概述,展示一個(gè)最小的 OpenCV 實(shí)現(xiàn),并展示一個(gè)可以使配準(zhǔn)過(guò)程更加高效的簡(jiǎn)單技巧。
什么是圖像配準(zhǔn)
圖像配準(zhǔn)被定義為將不同成像設(shè)備或傳感器在不同時(shí)間和角度拍攝的兩幅或多幅圖像,或來(lái)自同一場(chǎng)景的兩幅或多幅圖像疊加起來(lái),以幾何方式對(duì)齊圖像以進(jìn)行分析的過(guò)程(Zitová 和 Flusser,2003 年)。
百度百科給出的解釋
圖像配準(zhǔn):圖像配準(zhǔn)(Image registration)就是將不同時(shí)間、不同傳感器(成像設(shè)備)或不同條件下(天候、照度、攝像位置和角度等)獲取的兩幅或多幅圖像進(jìn)行匹配、疊加的過(guò)程,它已經(jīng)被廣泛地應(yīng)用于遙感數(shù)據(jù)分析、計(jì)算機(jī)視覺(jué)、圖像處理等領(lǐng)域。
醫(yī)學(xué)科學(xué)、遙感和計(jì)算機(jī)視覺(jué)都使用圖像配準(zhǔn)。
有兩種主要方法:
經(jīng)典計(jì)算機(jī)視覺(jué)方法(使用 OpenCV)——我們將在本文中關(guān)注的內(nèi)容
基于深度學(xué)習(xí)的方法
雖然后者可以更好地工作,但它可能需要一些“域”適應(yīng)(在你的數(shù)據(jù)上微調(diào)神經(jīng)網(wǎng)絡(luò))并且可能計(jì)算量太大。
使用 OpenCV 進(jìn)行圖像配準(zhǔn)
基于特征的方法:由單應(yīng)變換關(guān)聯(lián)的圖像對(duì)
此操作試圖發(fā)現(xiàn)兩張照片之間的匹配區(qū)域并在空間上對(duì)齊它們以最大限度地減少錯(cuò)誤。
我們的目標(biāo)是找到一個(gè)單應(yīng)性矩陣 H,它告訴我們需要如何修改其中一張圖像,使其與另一張圖像完美對(duì)齊。
第 1 步:關(guān)鍵點(diǎn)檢測(cè)
關(guān)鍵點(diǎn)定義了圖像中一個(gè)獨(dú)特的小區(qū)域(角、邊緣、圖案)。關(guān)鍵點(diǎn)檢測(cè)器的一個(gè)重要方面是找到的區(qū)域應(yīng)該對(duì)圖像變換(例如定位、比例和亮度)具有魯棒性,因?yàn)檫@些區(qū)域很可能出現(xiàn)在我們?cè)噲D對(duì)齊的兩個(gè)圖像中。有許多執(zhí)行關(guān)鍵點(diǎn)檢測(cè)的算法,例如 SIFT、ORB、AKAZE、SURF 等。
第 2 步:特征匹配
現(xiàn)在我們必須匹配來(lái)自兩個(gè)圖像的關(guān)鍵點(diǎn),這些關(guān)鍵點(diǎn)實(shí)際上對(duì)應(yīng)于同一點(diǎn)。
第 3 步:?jiǎn)螒?yīng)性
單應(yīng)性通常由一個(gè) 3x3 矩陣表示,它描述了應(yīng)該應(yīng)用于一個(gè)圖像以與另一個(gè)圖像對(duì)齊的幾何變換。
第 4 步:圖像變形
找到單應(yīng)性矩陣后,我們可以用它來(lái)對(duì)齊圖像。下面是該過(guò)程的代碼:
?
import numpy as np import cv2 as cv import matplotlib.pyplot as plt img1 = cv.imread('image1.jpg', cv.IMREAD_GRAYSCALE) # referenceImage img2 = cv.imread('image2.jpg', cv.IMREAD_GRAYSCALE) # sensedImage # Initiate SIFT detector sift_detector = cv.SIFT_create() # Find the keypoints and descriptors with SIFT kp1, des1 = sift_detector.detectAndCompute(img1, None) kp2, des2 = sift_detector.detectAndCompute(img2, None) # BFMatcher with default params bf = cv.BFMatcher() matches = bf.knnMatch(des1, des2, k=2) # Filter out poor matches good_matches = [] for m,n in matches: if m.distance < 0.75*n.distance: good_matches.append(m) matches = good_matches points1 = np.zeros((len(matches), 2), dtype=np.float32) points2 = np.zeros((len(matches), 2), dtype=np.float32) for i, match in enumerate(matches): points1[i, :] = kp1[match.queryIdx].pt points2[i, :] = kp2[match.trainIdx].pt # Find homography H, mask = cv2.findHomography(points1, points2, cv2.RANSAC) # Warp image 1 to align with image 2 img1Reg = cv2.warpPerspective(img1, H, (img2.shape[1], img2.shape[0])) cv.imwrite('aligned_img1.jpg', img1Reg)The problem is that this matrix H is found via a compute-intensive optimization process.
?
高效的圖像配準(zhǔn)
無(wú)論您為每個(gè)步驟選擇的參數(shù)如何,對(duì)執(zhí)行時(shí)間影響最大的是圖像的分辨率。您可以大幅調(diào)整它們的大小,但如果您需要對(duì)齊的圖像具有原始分辨率,會(huì)發(fā)生什么情況?
幸運(yùn)的是,有辦法解決這個(gè)問(wèn)題。事實(shí)證明,您可以計(jì)算低分辨率圖像的變換,然后調(diào)整此變換以適用于全分辨率圖像。
詳細(xì)步驟:
調(diào)整圖像大小
在低分辨率圖像上計(jì)算矩陣 H
變換矩陣 H 使其適用于全分辨率圖像
將新矩陣應(yīng)用于原始圖像。
第 3 步可能是這里最不明顯的部分,所以讓我們看看它是如何工作的:
我們想要調(diào)整在低分辨率圖像上計(jì)算的變換以適用于高分辨率圖像。因此,我們希望高分辨率圖像中的每個(gè)像素執(zhí)行以下操作:
縮小到低分辨率 -> 應(yīng)用變換 H -> 放大到高分辨率
幸運(yùn)的是,所有這些步驟都只是矩陣乘法,我們可以將所有這些步驟組合在一個(gè)單一的轉(zhuǎn)換中。
設(shè) H 為您計(jì)算出的變換。您可以將 H 乘以另一個(gè)單應(yīng)性 A,得到 AH = H',其中 H' 是進(jìn)行兩種變換的單應(yīng)性,相當(dāng)于先應(yīng)用 H,然后應(yīng)用 A。
下面是詳細(xì)代碼:
import numpy as np import cv2 as cv import matplotlib.pyplot as plt img1 = cv.imread('image1.jpg', cv.IMREAD_GRAYSCALE) # referenceImage img2 = cv.imread('image2.jpg', cv.IMREAD_GRAYSCALE) # sensedImage # Resize the image by a factor of 8 on each side. If your images are # very high-resolution, you can try to resize even more, but if they are # already small you should set this to something less agressive. resize_factor = 1.0/8.0 img1_rs = cv.resize(img1, (0,0), fx=resize_factor, fy=resize_factor) img2_rs = cv.resize(img2, (0,0), fx=resize_factor, fy=resize_factor) # Initiate SIFT detector sift_detector = cv.SIFT_create() # Find the keypoints and descriptors with SIFT on the lower resolution images kp1, des1 = sift_detector.detectAndCompute(img1_rs, None) kp2, des2 = sift_detector.detectAndCompute(img2_rs, None) # BFMatcher with default params bf = cv.BFMatcher() matches = bf.knnMatch(des1, des2, k=2) # Filter out poor matches good_matches = [] for m,n in matches: if m.distance < 0.75*n.distance: good_matches.append(m) matches = good_matches points1 = np.zeros((len(matches), 2), dtype=np.float32) points2 = np.zeros((len(matches), 2), dtype=np.float32) for i, match in enumerate(matches): points1[i, :] = kp1[match.queryIdx].pt points2[i, :] = kp2[match.trainIdx].pt # Find homography H, mask = cv2.findHomography(points1, points2, cv2.RANSAC) # Get low-res and high-res sizes low_height, low_width = img1_rs.shape height, width = img1.shape low_size = np.float32([[0, 0], [0, low_height], [low_width, low_height], [low_width, 0]]) high_size = np.float32([[0, 0], [0, height], [width, height], [width, 0]]) # Compute scaling transformations scale_up = cv.getPerspectiveTransform(low_size, high_size) scale_down = cv.getPerspectiveTransform(high_size, low_size) # Combine the transformations. Remember that the order of the transformation # is reversed when doing matrix multiplication # so this is actualy scale_down -> H -> scale_up h_and_scale_up = np.matmul(scale_up, H) scale_down_h_scale_up = np.matmul(h_and_scale_up, scale_down) # Warp image 1 to align with image 2 img1Reg = cv2.warpPerspective( img1, scale_down_h_scale_up, (img2.shape[1], img2.shape[0]) ) cv.imwrite('aligned_img1.jpg', img1Reg)
編輯:黃飛
?
評(píng)論