- 1 圖像拼接基礎知識
- 1.1 特征匹配 原理及代碼示例
- 1.2 單應性矩陣 原理及代碼示例
- 2 圖像拼接(一)(直接拼接)
- 3 圖像拼接(二)(單應性矩陣 + 圖像變換 + 拼接)
- 3.1 單應性矩陣函數
- 3.2 拼接函數 實現 及細節測試驗證
- 3.3 圖像拼接
- 4 后續完善(拼接縫隙過度、裁剪)
- 4.1 輸入圖像大小一致 優化
- 4.2 后續完善
- 5 進階實戰--圖像拼接 (實戰項目)
P87 11
1 圖像拼接基礎知識
圖像關系,有重疊部分
第二張圖像的左上角是原點(0,0),左邊和上邊的都是負值,不顯示
左邊的圖片變換后,超出尺寸的就不顯示了,
事實上顯示出來的那一部分,是與第二張圖重疊的部分
從左上往右下拉
之后將右邊的圖,平移過來
1.1 特征匹配 原理及代碼示例
超詳細教程:特征點檢測與匹配(Harris角點檢測、Shi-Tomasi角點檢測、SIFT關鍵點檢測、SURF特征檢測、 ORB特征檢測、暴力特征匹配、FLANN特)
1.2 單應性矩陣 原理及代碼示例
教程:圖像查找(特征匹配 + 單應性矩陣)
2 圖像拼接(一)(直接拼接)
import cv2
import numpy as np #第一步,讀取文件,將圖片設置成一樣大小640*480
#第二步,找特征點,描述子,計算單應性矩陣
#第三部,根據單應性矩陣對圖像進行變換,然后平移
#第四部,拼接并輸出結果img1=cv2.imread('map1.png')
img2=cv2.imread('map2.png')#設置成一樣大小640*480
img1=cv2.resize(img1,(640,480))
img2=cv2.resize(img2,(640,480))#將兩圖橫向壓入棧中,即直接拼接
inputs=np.hstack((img1,img2))
cv2.imshow('input',inputs)cv2.waitKey(0)
可見,直接拼接只是簡單的把兩個圖像拼在一起,不可用
3 圖像拼接(二)(單應性矩陣 + 圖像變換 + 拼接)
3.1 單應性矩陣函數
def get_homo(img1,img2):#1 創建特征轉換對象#2 通過特征轉換獲得特征點和描述子#3 創建特征匹配器#4 進行特征匹配#5 驗證過濾特征,找出有效的特征匹配點sift = cv2.xfeatures2d.SIFT_create()k1,d1=sift.detectAndCompute(img1,None)k2,d2=sift.detectAndCompute(img2,None)#創建特征匹配器bf=cv2.BFMatcher()matches=bf.knnMatch(d1,d2,k=2)verify_matches=[]verify_ratio = 0.8 #過濾器閾值 for m1,m2 in matches:if m1.destance < 0.8*m2.distance:verify_matches.append(m1)min_matches=8if len(verify_matches)>=min_matches:img1_pts=[] #img1特征坐標點img2_pts=[]for m in verify_matches:img1_pts.append(k1[m.queryIdx].pt)img2_pts.append(k2[m.trainIdx].pt)#img_pt數組格式 [(x1,y2),(x2,y2)....]#findHomography需要的數組坐標[[x1,y1],[x2,y2]...]img1_pts=np.float(img1_pts).reshape(-1,1,2)img2_pts=np.float(img2_pts).reshape(-1,1,2)H,mask=cv2.findHomography(img1_pts,img2_pts,cv2.RANSAC,5.0)return Helse :print('err: Not enough matches')exit()
3.2 拼接函數 實現 及細節測試驗證
#定義拼接函數
def stitch_image(img1,img2,H):#1 獲得每張圖片的四個角點#2 對圖片進行變換(單應性矩陣使圖進行旋轉,平移)#3 創建一張大圖,將兩張圖拼接到一起#4 輸出結果w1,h1=img1.shape[:2]#shape有3個值(高,寬,通道數),這里只取前兩個值w2,h2=img2.shape[:2]#獲取第一張圖的四個角點,OpenCV里圖像四個點順序通常喜歡逆時針#獲取的角點要變稱浮點型,數組不能是二維的,要變成能三維img1_dism=np.float32([[0,0],[0,h1-1],[w1-1,h1-1],[w1-1,0]]).reshape(-1,1,2)img2_dism=np.float32([[0,0],[0,h2-1],[w2-1,h2-1],[w2-1,0]]).reshape(-1,1,2)img1_transform=cv2.perspectiveTransform(img1_dism,H)print(img1_dism)print(img2_dism)print(img1_transform)
輸出img1,img2和變換后img1的四個角點
print(img1_dism)print(img2_dism)print(img1_transform)
變換后img1_transform
的四個角點,坐標有負值,原因超出了邊界,超出的部分不顯示;
result_dism=np.concatenate((img2_dism,img1_transform),axis=0)aa=result_dism.min()#獲取最小值print(aa)
只輸出了一個數,是所有數據的最小值;
要輸出最小的x值和最小y值,axis=0表示按x軸獲取數據
aa=result_dism.min(axis=0)#axis=0表示按x軸獲取數據
ravel()將二維數組轉換成一維
可以看到雙括號,變成了單括號
轉換為整形,
aa=np.int32(result_dism.min(axis=0).ravel())
#最小值,向下取整-0.5,最大值向上取整,+0.5a=np.int32(result_dism.min(axis=0).ravel()-0.5)b=np.int32(result_dism.max(axis=0).ravel()+0.5)print(a)print(b)
變換之后的圖,需要平移
#單應性矩陣變換,未平移
result_img=cv2.warpPerspective(img1,H,(max_x-min_x,max_y-min_y))#min是負值,減號,就相當于加
#平移,即乘以一個齊次坐標
#[1,0,dx]
#[0,1,dy]
#[0,0,1 ]
transform_array=np.array([[1,0,transform_dist[0]],[0,1,transform_dist[1]],[0,0,1]])#單應性矩陣變換result_img=cv2.warpPerspective(img1,transform_array.dot(H),(max_x-min_x,max_y-min_y))#min是負值,減號,就相當于加return result_img
至此,圖像一的變換與平移完成
3.3 圖像拼接
#找到合適的位置把圖二拼接過來result_img[transform_dist[1]:transform_dist[1]+h2,transform_dist[0]:transform_dist[0]+w2]=img2
到此處,拼過工作已經完后,證情況下運行出拼接結果。
但是結果卻出錯了,圖像的寬高顛倒了。
跳轉到45行,是最后寫的一個拼接位置,,好像也沒有什么毛病。
反復的全文檢查了好幾遍,也沒發現問題。
最后發現這里,不一致;
shape數據存儲順序為:高,寬,通道數;
而寫的是w,h,顛倒了,調換過來問題解決!
#獲取原始圖像的高寬h1,w1=img1.shape[:2]#shape有3個值(高,寬,通道數),這里只取前兩個值h2,w2=img2.shape[:2]
運行結果;
完整代碼:
import cv2
import numpy as np #根據單應性矩陣對圖像進行變換,及拼接
def stitch_image(img1,img2,H):#1 獲得每張圖片的四個角點#2 對第二張圖片進行變換(單應性矩陣使圖進行旋轉,平移)#3 創建一張大圖,將兩張圖拼接到一起#4 輸出結果#獲取原始圖像的高寬h1,w1=img1.shape[:2]#shape有3個值(高,寬,通道數),這里只取前兩個值h2,w2=img2.shape[:2]#獲取第一張圖的四個角點,OpenCV里圖像四個點順序通常喜歡逆時針#獲取的角點要變稱浮點型,數組不能是二維的,要變成能三維img1_dism=np.float32([[0,0],[0,h1],[w1,h1],[w1,0]]).reshape(-1,1,2)img2_dism=np.float32([[0,0],[0,h2],[w2,h2],[w2,0]]).reshape(-1,1,2)img1_transform=cv2.perspectiveTransform(img1_dism,H)#print(img1_dism)#print(img2_dism)#print(img1_transform)result_dism=np.concatenate((img2_dism,img1_transform),axis=0)print(result_dism)#axis=0表示按x軸獲取數據,獲得最小x值y值,ravel()將二維數組轉換成一維,轉換為整形,#最小值,向下取整-0.5,最大值向上取整,+0.5[min_x,min_y]=np.int32(result_dism.min(axis=0).ravel()-0.5)[max_x,max_y]=np.int32(result_dism.max(axis=0).ravel()+0.5)#圖像變換之后,部分數均已經超出顯示范圍,需要平移到大窗口中#平移的距離transform_dist = [-min_x,-min_y] #加負號,變成正值#平移,即乘以一個齊次坐標#[1,0,dx]#[0,1,dy]#[0,0,1 ]transform_array=np.array([[1,0,transform_dist[0]],[0,1,transform_dist[1]],[0,0,1]])#單應性矩陣變換#到此處圖像一的變換與平移完成result_img=cv2.warpPerspective(img1,transform_array.dot(H),(max_x-min_x,max_y-min_y))#min是負值,減號,就相當于加#到此處圖像一的變換與平移完成#找到合適的位置把圖二拼接過來result_img[transform_dist[1]:transform_dist[1]+h2,transform_dist[0]:transform_dist[0]+w2]=img2return result_img#定義單應性矩陣函數
def get_homo(img1,img2):#1 創建特征轉換對象#2 通過特征轉換獲得特征點和描述子#3 創建特征匹配器#4 進行特征匹配#5 驗證過濾特征,找出有效的特征匹配點sift = cv2.xfeatures2d.SIFT_create()k1,d1=sift.detectAndCompute(img1,None)k2,d2=sift.detectAndCompute(img2,None)#創建特征匹配器bf=cv2.BFMatcher()matches=bf.knnMatch(d1,d2,k=2)#過濾特征,找出有效的特征匹配點verify_matches=[]verify_ratio = 0.8 #過濾器閾值 for m1,m2 in matches:if m1.distance < 0.8 * m2.distance:verify_matches.append(m1)min_matches=8if len(verify_matches)>min_matches:img1_pts=[] #img1特征坐標點img2_pts=[]for m in verify_matches:img1_pts.append(k1[m.queryIdx].pt)img2_pts.append(k2[m.trainIdx].pt)#img_pt數組格式 [(x1,y2),(x2,y2)....]#findHomography需要的數組坐標[[x1,y1],[x2,y2]...]img1_pts=np.float32(img1_pts).reshape(-1,1,2)img2_pts=np.float32(img2_pts).reshape(-1,1,2)H,mask=cv2.findHomography(img1_pts,img2_pts,cv2.RANSAC,5.0)return Helse :print('err: Not enough matches')exit()#第一步,讀取文件,將圖片設置成一樣大小640*480
#第二步,找特征點,描述子,計算單應性矩陣
#第三部,根據單應性矩陣對圖像進行變換,然后平移
#第四部,拼接并輸出結果img1=cv2.imread('map1.png')
img2=cv2.imread('map2.png')#設置成一樣大小640*480
img1=cv2.resize(img1,(640,480))
img2=cv2.resize(img2,(640,480))#將兩圖橫向壓入棧中,即直接拼接
inputs=np.hstack((img1,img2))#獲得單應性矩陣
H=get_homo(img1,img2)#根據單應性矩陣對圖像進行變換,及拼接
result_image=stitch_image(img1,img2,H)cv2.imshow('input',result_image)
cv2.waitKey(0)
4 后續完善(拼接縫隙過度、裁剪)
4.1 輸入圖像大小一致 優化
上面的代碼示例,手動設置img1,和img的尺寸
#設置成一樣大小640*480
img1=cv2.resize(img1,(640,480))
img2=cv2.resize(img2,(640,480))
現在改為,自動確定尺寸。
判斷圖片尺寸是否一致,
如果 一樣大,不做resize;
如果不一樣大,就要resize,選擇兩幅圖中最小的寬高作為resize后的尺寸。
#判斷圖片尺寸是否一致,如果不一樣大,就要resize,這里選擇兩幅圖中最小的寬高
if (imageA.shape[0]==imageB.shape[0] and imageA.shape[1]==imageB.shape[1])!=1:h=min(imageA.shape[1],imageB.shape[1])w=min(imageA.shape[0],imageB.shape[0])imageA=cv2.resize(imageA,(h,w))#注意這里尺寸(高,寬),和平時的習慣寬高有點不一樣imageB=cv2.resize(imageB,(h,w))print('修改后尺寸:',imageA.shape,imageB.shape)#輸出調整后的尺寸
下圖輸出信息分別為:
兩張圖片原始尺寸;
resize后的尺寸;
拼接后的尺寸;
4.2 后續完善
5 進階實戰–圖像拼接 (實戰項目)
上面的圖像拼接,重在展示基本原理,但存在拼接縫隙過度等一些問題。只用于實驗,不能滿足實際項目要求。
下面是,實戰項目,實現。
實戰項目:進階實戰–圖像拼接(二) (實戰一:圖像拼接 附完整代碼、實戰二:圖像拼接 附完整代碼)