使用OpenCV和Python進行圖像拼接

麼是圖像拼接呢?簡單來說,對於輸入應該有一組圖像,輸出是合成圖像。同時,必須保留圖像之間的邏輯流。

首先讓我們瞭解圖像拼接的概念。基本上,如果你想捕捉一個大的場景,你的相機只能提供一個特定分辨率的圖像(如:640×480),這當然不足以捕捉大的全景。所以,我們可以做的是捕捉整個場景的多個圖像,然後把所有的碎片放在一起,形成一個大的圖像。這些有序的照片被稱為全景。獲取多幅圖像並將其轉換成全景圖的整個過程稱為圖像拼接。

首先,需要安裝opencv 3.4.2.16。

接下來我們將導入我們將在Python代碼中使用的庫:

使用OpenCV和Python進行圖像拼接

在我們的教程中,我們將拍攝這張精美的照片,我們會將其分成兩張左右兩張照片,然後我們會嘗試拍攝相同或非常相似的照片。

使用OpenCV和Python進行圖像拼接

因此,我將此圖像切成兩個圖像,它們會有某種重疊區域:

使用OpenCV和Python進行圖像拼接

在此,我們將列出我們應採取的步驟,以取得最終的結果:

  • 計算左右圖像的篩選關鍵點和描述符。
  • 計算一個圖像中的每個描述符與另一個圖像中的每個描述符之間的距離。
  • 為圖像的每個描述符選擇最佳匹配項。
  • 運行RANSAC以估計單應性。
  • Warp對齊以便拼接。
  • 最後將它們拼接在一起。

因此,從第一步開始,我們將導入這兩個圖像並將它們轉換為灰度,如果您使用的是大圖像,我建議您使用cv2.resize,因為如果您使用較舊的計算機,它可能會非常慢並且需要很長時間。如果要調整圖像大小,即調整50%,只需將fx = 1更改為fx = 0.5即可。

使用OpenCV和Python進行圖像拼接

我們還需要找出兩幅圖像中匹配的特徵。我們將使用opencv_contrib的SIFT描述符。SIFT (Scale constant Feature Transform)是一種非常強大的OpenCV算法。這些最匹配的特徵作為拼接的基礎。我們提取兩幅圖像的關鍵點和sift描述符如下:

使用OpenCV和Python進行圖像拼接

kp1和kp2是關鍵點,des1和des2是圖像的描述符。如果我們用特徵來畫這幅圖,它會是這樣的:

cv2.imshow('original_image_left_keypoints',cv2.drawKeypoints(img_,kp1,None))

左邊的圖像顯示實際圖像。右側的圖像使用SIFT檢測到的特徵進行註釋:

使用OpenCV和Python進行圖像拼接

一旦你有了兩個圖像的描述符和關鍵點,我們就會發現它們之間的對應關係。我們為什麼要這麼做?為了將任意兩個圖像連接成一個更大的圖像,我們必須找到重疊的點。這些重疊的點會讓我們根據第一幅圖像瞭解第二幅圖像的方向。根據這些公共點,我們就能知道第二幅圖像是大是小還是旋轉後重疊,或者縮小/放大後再fitted。所有此類信息的產生是通過建立對應關係來實現的。這個過程稱為registration。

對於匹配圖像,可以使用opencv提供的FLANN或BFMatcher方法。我會寫兩個例子證明我們會得到相同的結果。兩個示例都匹配兩張照片中更相似的特徵。當我們設置參數k = 2時,這樣我們就要求knnMatcher為每個描述符給出2個最佳匹配。“matches”是列表的列表,其中每個子列表由“k”個對象組成。以下是Python代碼:

FLANN匹配代碼:

使用OpenCV和Python進行圖像拼接

BFMatcher匹配代碼:

使用OpenCV和Python進行圖像拼接

通常在圖像中,圖像的許多地方可能存在許多特徵。所以我們過濾掉所有的匹配來得到最好的。因此我們使用上面得到的前2個匹配項進行比值檢驗。如果下面定義的比值大於指定的比值,則考慮匹配。

使用OpenCV和Python進行圖像拼接

現在我們定義在圖像上繪製線條的參數,並給出輸出以查看當我們在圖像上找到所有匹配時的樣子:

使用OpenCV和Python進行圖像拼接

這是輸出的匹配圖像:

使用OpenCV和Python進行圖像拼接

這部分完整Python代碼:

import cv2
import numpy as np
img_ = cv2.imread('original_image_left.jpg')
#img_ = cv2.resize(img_, (0,0), fx=1, fy=1)
img1 = cv2.cvtColor(img_,cv2.COLOR_BGR2GRAY)
img = cv2.imread('original_image_right.jpg')
#img = cv2.resize(img, (0,0), fx=1, fy=1)
img2 = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
sift = cv2.xfeatures2d.SIFT_create()
# find the key points and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
#cv2.imshow('original_image_left_keypoints',cv2.drawKeypoints(img_,kp1,None))
#FLANN_INDEX_KDTREE = 0
#index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
#search_params = dict(checks = 50)
#match = cv2.FlannBasedMatcher(index_params, search_params)
match = cv2.BFMatcher()
matches = match.knnMatch(des1,des2,k=2)
good = []
for m,n in matches:
if m.distance < 0.03*n.distance:
good.append(m)
draw_params = dict(matchColor = (0,255,0), # draw matches in green color
singlePointColor = None,
flags = 2)
img3 = cv2.drawMatches(img_,kp1,img,kp2,good,None,**draw_params)
cv2.imshow("original_image_drawMatches.jpg", img3)
使用OpenCV和Python進行圖像拼接

因此,一旦我們獲得了圖像之間的最佳匹配,我們的下一步就是計算單應矩陣。如前所述,單應矩陣將與最佳匹配點一起使用,以估計兩個圖像內的相對方向變換。

在OpenCV中估計單應性是一項簡單的任務,只需一行代碼:

H, __ = cv2.findHomography(srcPoints, dstPoints, cv2.RANSAC, 5)

在開始編碼拼接算法之前,我們需要交換圖像輸入。所以img_現在會取右圖像img會取左圖像。

那麼讓我們進入拼接編碼:

使用OpenCV和Python進行圖像拼接

因此,首先,我們將最小匹配條件count設置為10(由MIN_MATCH_COUNT定義),並且只有在匹配良好的匹配超出所需匹配時才進行拼接。否則,只需顯示一條消息,說明匹配不夠。

因此,在if語句中,我們將關鍵點(從匹配列表)轉換為findHomography()函數的參數。

只需在這段代碼中討論cv2.imshow(“original_image_overlapping.jpg”,img2),我們就會顯示我們收到的圖像重疊區域:

使用OpenCV和Python進行圖像拼接

因此,一旦我們建立了單應性,我們需要扭曲視角,我們將以下單應矩陣應用於圖像:

warped_image = cv2.warpPerspective(image, homography_matrix, dimension_of_warped_image)

所以我們使用如下:

使用OpenCV和Python進行圖像拼接

在上面兩行Python代碼中,我們從兩個給定的圖像中獲取重疊區域。然後在“dst”中我們只接收到沒有重疊的圖像的右側,因此在第二行代碼中我們將左側圖像放置到最終圖像。所以在這一點上我們完全拼接了圖像:

使用OpenCV和Python進行圖像拼接

剩下的就是去除圖像的黑色,所以我們將編寫以下代碼來從所有圖像邊框中刪除黑邊:

使用OpenCV和Python進行圖像拼接

這是我們調用修剪邊界的最終定義函數,同時我們在屏幕上顯示該圖像。如果您願意,也可以將其寫入磁盤:

使用OpenCV和Python進行圖像拼接

使用上面的Python代碼,我們將首先收到原始圖片:

使用OpenCV和Python進行圖像拼接

這是完整的最終代碼:

import cv2
import numpy as np
img_ = cv2.imread('original_image_right.jpg')
#img_ = cv2.imread('original_image_left.jpg')
#img_ = cv2.resize(img_, (0,0), fx=1, fy=1)
img1 = cv2.cvtColor(img_,cv2.COLOR_BGR2GRAY)
img = cv2.imread('original_image_left.jpg')
#img = cv2.imread('original_image_right.jpg')
#img = cv2.resize(img, (0,0), fx=1, fy=1)
img2 = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
sift = cv2.xfeatures2d.SIFT_create()
# find key points
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
#cv2.imshow('original_image_left_keypoints',cv2.drawKeypoints(img_,kp1,None))
#FLANN_INDEX_KDTREE = 0
#index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
#search_params = dict(checks = 50)
#match = cv2.FlannBasedMatcher(index_params, search_params)
match = cv2.BFMatcher()
matches = match.knnMatch(des1,des2,k=2)
good = []
for m,n in matches:
if m.distance < 0.03*n.distance:
good.append(m)
draw_params = dict(matchColor=(0,255,0),
singlePointColor=None,
flags=2)
img3 = cv2.drawMatches(img_,kp1,img,kp2,good,None,**draw_params)
#cv2.imshow("original_image_drawMatches.jpg", img3)
MIN_MATCH_COUNT = 10
if len(good) > MIN_MATCH_COUNT:
src_pts = np.float32([ kp1[m.queryIdx].pt for m in good ]).reshape(-1,1,2)
dst_pts = np.float32([ kp2[m.trainIdx].pt for m in good ]).reshape(-1,1,2)
M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
h,w = img1.shape
pts = np.float32([ [0,0],[0,h-1],[w-1,h-1],[w-1,0] ]).reshape(-1,1,2)
dst = cv2.perspectiveTransform(pts, M)
img2 = cv2.polylines(img2,[np.int32(dst)],True,255,3, cv2.LINE_AA)
#cv2.imshow("original_image_overlapping.jpg", img2)
else:
print("Not enought matches are found - %d/%d", (len(good)/MIN_MATCH_COUNT))
dst = cv2.warpPerspective(img_,M,(img.shape[1] + img_.shape[1], img.shape[0]))
dst[0:img.shape[0],0:img.shape[1]] = img
cv2.imshow("original_image_stitched.jpg", dst)

def trim(frame):
#crop top
if not np.sum(frame[0]):
return trim(frame[1:])
#crop top
if not np.sum(frame[-1]):
return trim(frame[:-2])
#crop top
if not np.sum(frame[:,0]):
return trim(frame[:,1:])
#crop top
if not np.sum(frame[:,-1]):
return trim(frame[:,:-2])
return frame
cv2.imshow("original_image_stitched_crop.jpg", trim(dst))
#cv2.imsave("original_image_stitched_crop.jpg", trim(dst))
使用OpenCV和Python進行圖像拼接

在本教程中,我們學習瞭如何使用OpenCV執行圖像拼接和全景構造,並編寫了最終的圖像拼接代碼。

我們的圖像拼接算法需要四個主要步驟:檢測關鍵點和提取局部不變描述符; 獲得圖像之間的匹配描述符; 應用RANSAC估計單應矩陣; 使用單應矩陣應用warping transformation。

當僅為兩個圖像構建全景圖時,該算法在實踐中工作良好。


分享到:


相關文章: