單應性矩陣應用-基於特徵的影像拼接

  • 2020 年 2 月 21 日
  • 筆記

前言

前面寫了一篇關於單應性矩陣的相關文章,結尾說到基於特徵的影像拼接跟對象檢測中單應性矩陣應用場景。得到很多人留言回饋,讓我繼續寫,於是就有這篇文章。這裡有兩張照片(我手機拍的),背景是我老家的平房,周圍是一片開闊地帶,都是麥子。有圖為證:

圖一:

圖二:

思路

這裡是兩張影像的拼接,多張影像與此類似。主要是應用特徵提取模組的AKAZE影像特徵點與描述子提取,當然你也可以選擇ORB、SIFT、SURF等特徵提取方法。匹配方法主要是基於暴力匹配/FLANN+KNN完成,影像對齊與配准通過RANSAC跟透視變換實現,最後通過簡單的權重影像疊加實現融合、得到拼接之後得全景影像。這個其中單應性矩陣發現是很重要的一步,如果不知道這個是什麼請看這裡:

OpenCV單應性矩陣發現參數估算方法詳解

基本流程

1.載入輸入影像

2.創建AKAZE特徵提取器

3.提取關鍵點跟描述子特徵

4.描述子匹配並提取匹配較好的關鍵點

5.單應性矩陣影像對齊

6.創建融合遮罩層,準備開始融合

7.影像透視變換與融合操作

8.輸出拼接之後的全景圖

關鍵程式碼

在具體程式碼實現步驟之前,先說一下軟體版本

-VS2015  -OpenCV4.2  -Windows 10 64位

程式碼實現:提取特徵與描述子

// 提取特徵點與描述子  vector<KeyPoint> keypoints_right, keypoints_left;  Mat descriptors_right, descriptors_left;  auto detector = AKAZE::create();  detector->detectAndCompute(left, Mat(), keypoints_left, descriptors_left);  detector->detectAndCompute(right, Mat(), keypoints_right, descriptors_right);

提取好的匹配描述子

// 暴力匹配  vector<DMatch> matches;  auto matcher = DescriptorMatcher::create(DescriptorMatcher::BRUTEFORCE);    // 發現匹配  std::vector< std::vector<DMatch> > knn_matches;  matcher->knnMatch(descriptors_left, descriptors_right, knn_matches, 2);  const float ratio_thresh = 0.7f;  std::vector<DMatch> good_matches;  for (size_t i = 0; i < knn_matches.size(); i++)  {        if (knn_matches[i][0].distance < ratio_thresh * knn_matches[i][1].distance)        {                good_matches.push_back(knn_matches[i][0]);        }  }  printf("total good match points : %dn", good_matches.size());  std::cout << std::endl;    Mat dst;  drawMatches(left, keypoints_left, right, keypoints_right, good_matches, dst);

創建mask對象

// create mask  int win_size = 800;  int h1 = left.rows;  int w1 = left.cols;  int h2 = right.rows;  int w2 = right.cols;  int h = max(h1, h2);  int w = w1 + w2;  Mat mask1 = Mat::ones(Size(w, h), CV_32FC1);  Mat mask2 = Mat::ones(Size(w, h), CV_32FC1);  Rect roi;  roi.height = h;  roi.width = win_size;  roi.y = 0;  roi.x = w1 - win_size;    // left mask  Mat temp = mask1(roi);  linspace(temp, 1, 0, win_size);    // right mask  temp = mask2(roi);  linspace(temp, 0, 1, win_size);

對齊生成全景影像

// generate panorama  Mat panorama_01 = Mat::zeros(Size(w, h), CV_8UC3);  roi.x = 0;  roi.y = 0;  roi.width = w1;  roi.height = h1;  left.copyTo(panorama_01(roi));  Mat m1;  vector<Mat> mv;  mv.push_back(mask1);  mv.push_back(mask1);  mv.push_back(mask1);  merge(mv, m1);  panorama_01.convertTo(panorama_01, CV_32F);  multiply(panorama_01, m1, panorama_01);      Mat panorama_02;  warpPerspective(right, panorama_02, H, Size(w, h));  mv.clear();  mv.push_back(mask2);  mv.push_back(mask2);  mv.push_back(mask2);  Mat m2;  merge(mv, m2);  panorama_02.convertTo(panorama_02, CV_32F);  multiply(panorama_02, m2, panorama_02);

上述程式碼中panorama_01實現對第一張影像內容提取與mask權重生成混合,panorama_02完成對第二張圖的內容透視變換與mask權重生成混合。特別注意的是順序很重要。單應性矩陣發現程式碼可以看之前文章即可,這裡不再贅述。

合併全景影像

// 合併全景圖  Mat panorama;  add(panorama_01, panorama_02, panorama);  panorama.convertTo(panorama, CV_8U);  imwrite("D:/panorama.png", panorama);

程式運行->特徵點匹配如下:

最終拼接的全景圖如下:

想知道如何改進這個輸出結果,讓輸出結果融合的根據自然與真實,請聽下回再說吧!過年了終於有點時間寫點乾貨回報一下大家!請大家多多支援!多多回饋!