單應(yīng)性矩陣應(yīng)用-基于特征的圖像拼接
點擊上方“小白學(xué)視覺”,選擇加"星標(biāo)"或“置頂”
重磅干貨,第一時間送達
本文轉(zhuǎn)自:深度學(xué)習(xí)這件小事
前面寫了一篇關(guān)于單應(yīng)性矩陣的相關(guān)文章,結(jié)尾說到基于特征的圖像拼接跟對象檢測中單應(yīng)性矩陣應(yīng)用場景。得到很多人留言反饋,讓我繼續(xù)寫,于是就有這篇文章。這里有兩張照片(我手機拍的),背景是我老家的平房,周圍是一片開闊地帶,都是麥子。有圖為證:
圖一:

圖二:

這里是兩張圖像的拼接,多張圖像與此類似。主要是應(yīng)用特征提取模塊的AKAZE圖像特征點與描述子提取,當(dāng)然你也可以選擇ORB、SIFT、SURF等特征提取方法。匹配方法主要是基于暴力匹配/FLANN+KNN完成,圖像對齊與配準通過RANSAC跟透視變換實現(xiàn),最后通過簡單的權(quán)重圖像疊加實現(xiàn)融合、得到拼接之后得全景圖像。這個其中單應(yīng)性矩陣發(fā)現(xiàn)是很重要的一步,如果不知道這個是什么請看這里:
OpenCV單應(yīng)性矩陣發(fā)現(xiàn)參數(shù)估算方法詳解
1.加載輸入圖像
2.創(chuàng)建AKAZE特征提取器
3.提取關(guān)鍵點跟描述子特征
4.描述子匹配并提取匹配較好的關(guān)鍵點
5.單應(yīng)性矩陣圖像對齊
6.創(chuàng)建融合遮罩層,準備開始融合
7.圖像透視變換與融合操作
8.輸出拼接之后的全景圖
在具體代碼實現(xiàn)步驟之前,先說一下軟件版本
-VS2015-OpenCV4.2-Windows 10 64位
代碼實現(xiàn):提取特征與描述子
1// 提取特征點與描述子
2vector<KeyPoint> keypoints_right, keypoints_left;
3Mat descriptors_right, descriptors_left;
4auto detector = AKAZE::create();
5detector->detectAndCompute(left, Mat(), keypoints_left, descriptors_left);
6detector->detectAndCompute(right, Mat(), keypoints_right, descriptors_right);提取好的匹配描述子
1// 暴力匹配
2vector<DMatch> matches;
3auto matcher = DescriptorMatcher::create(DescriptorMatcher::BRUTEFORCE);
4
5// 發(fā)現(xiàn)匹配
6std::vector< std::vector<DMatch> > knn_matches;
7matcher->knnMatch(descriptors_left, descriptors_right, knn_matches, 2);
8const float ratio_thresh = 0.7f;
9std::vector<DMatch> good_matches;
10for (size_t i = 0; i < knn_matches.size(); i++)
11{
12 if (knn_matches[i][0].distance < ratio_thresh * knn_matches[i][1].distance)
13 {
14 good_matches.push_back(knn_matches[i][0]);
15 }
16}
17printf("total good match points : %d\n", good_matches.size());
18std::cout << std::endl;
19
20Mat dst;
21drawMatches(left, keypoints_left, right, keypoints_right, good_matches, dst);創(chuàng)建mask對象
1// create mask
2int win_size = 800;
3int h1 = left.rows;
4int w1 = left.cols;
5int h2 = right.rows;
6int w2 = right.cols;
7int h = max(h1, h2);
8int w = w1 + w2;
9Mat mask1 = Mat::ones(Size(w, h), CV_32FC1);
10Mat mask2 = Mat::ones(Size(w, h), CV_32FC1);
11Rect roi;
12roi.height = h;
13roi.width = win_size;
14roi.y = 0;
15roi.x = w1 - win_size;
16
17// left mask
18Mat temp = mask1(roi);
19linspace(temp, 1, 0, win_size);
20
21// right mask
22temp = mask2(roi);
23linspace(temp, 0, 1, win_size);對齊生成全景圖像
1// generate panorama
2Mat panorama_01 = Mat::zeros(Size(w, h), CV_8UC3);
3roi.x = 0;
4roi.y = 0;
5roi.width = w1;
6roi.height = h1;
7left.copyTo(panorama_01(roi));
8Mat m1;
9vector<Mat> mv;
10mv.push_back(mask1);
11mv.push_back(mask1);
12mv.push_back(mask1);
13merge(mv, m1);
14panorama_01.convertTo(panorama_01, CV_32F);
15multiply(panorama_01, m1, panorama_01);
16
17
18Mat panorama_02;
19warpPerspective(right, panorama_02, H, Size(w, h));
20mv.clear();
21mv.push_back(mask2);
22mv.push_back(mask2);
23mv.push_back(mask2);
24Mat m2;
25merge(mv, m2);
26panorama_02.convertTo(panorama_02, CV_32F);
27multiply(panorama_02, m2, panorama_02);上述代碼中panorama_01實現(xiàn)對第一張圖像內(nèi)容提取與mask權(quán)重生成混合,panorama_02完成對第二張圖的內(nèi)容透視變換與mask權(quán)重生成混合。特別注意的是順序很重要。單應(yīng)性矩陣發(fā)現(xiàn)代碼可以看之前文章即可,這里不再贅述。
合并全景圖像
1// 合并全景圖
2Mat panorama;
3add(panorama_01, panorama_02, panorama);
4panorama.convertTo(panorama, CV_8U);
5imwrite("D:/panorama.png", panorama);程序運行->特征點匹配如下:

最終拼接的全景圖如下:

想知道如何改進這個輸出結(jié)果,讓輸出結(jié)果融合的根據(jù)自然與真實,請聽下回再說吧!過年了終于有點時間寫點干貨回報一下大家!請大家多多支持!多多反饋!
交流群
歡迎加入公眾號讀者群一起和同行交流,目前有SLAM、三維視覺、傳感器、自動駕駛、計算攝影、檢測、分割、識別、醫(yī)學(xué)影像、GAN、算法競賽等微信群(以后會逐漸細分),請掃描下面微信號加群,備注:”昵稱+學(xué)校/公司+研究方向“,例如:”張三 + 上海交大 + 視覺SLAM“。請按照格式備注,否則不予通過。添加成功后會根據(jù)研究方向邀請進入相關(guān)微信群。請勿在群內(nèi)發(fā)送廣告,否則會請出群,謝謝理解~

