OpenCV常用圖像拼接方法將分為四個部分與大家共享,這里是第三種方法,歡迎關注后續。
OpenCV的常用圖像拼接方法(三):基於特征匹配的圖像拼接,本次介紹SIFT特征匹配拼接方法,OpenCV版本為4.4.0。特點和適用范圍:圖像有足夠重合相同特征區域,且待拼接圖像之間無明顯尺度變換和畸變。
優點:適應部分傾斜變化情況。缺點:需要有足夠的相同特征區域進行匹配,速度較慢,拼接較大圖片容易崩潰。
如下是待拼接的兩張圖片:
特征匹配圖:
拼接結果圖:
拼接縫處理后(拼接處過渡更自然):
核心代碼:
1 /********************直接圖像拼接函數*************************/
2 bool ImageOverlap0(Mat &img1, Mat &img2) 3 { 4 Mat g1(img1, Rect(0, 0, img1.cols, img1.rows)); // init roi
5 Mat g2(img2, Rect(0, 0, img2.cols, img2.rows)); 6
7 cvtColor(g1, g1, COLOR_BGR2GRAY); 8 cvtColor(g2, g2, COLOR_BGR2GRAY); 9
10 vector<cv::KeyPoint> keypoints_roi, keypoints_img; /* keypoints found using SIFT */
11 Mat descriptor_roi, descriptor_img; /* Descriptors for SIFT */
12 FlannBasedMatcher matcher; /* FLANN based matcher to match keypoints */
13
14 vector<cv::DMatch> matches, good_matches; 15 cv::Ptr<cv::SIFT> sift = cv::SIFT::create(); 16 int i, dist = 80; 17
18 sift->detectAndCompute(g1, cv::Mat(), keypoints_roi, descriptor_roi); /* get keypoints of ROI image */
19 sift->detectAndCompute(g2, cv::Mat(), keypoints_img, descriptor_img); /* get keypoints of the image */
20 matcher.match(descriptor_roi, descriptor_img, matches); //實現描述符之間的匹配
21
22 double max_dist = 0; double min_dist = 5000; 23 //-- Quick calculation of max and min distances between keypoints
24 for (int i = 0; i < descriptor_roi.rows; i++) 25 { 26 double dist = matches[i].distance; 27 if (dist < min_dist) min_dist = dist; 28 if (dist > max_dist) max_dist = dist; 29 } 30 // 特征點篩選
31 for (i = 0; i < descriptor_roi.rows; i++) 32 { 33 if (matches[i].distance < 3 * min_dist) 34 { 35 good_matches.push_back(matches[i]); 36 } 37 } 38
39 printf("%ld no. of matched keypoints in right image\n", good_matches.size()); 40 /* Draw matched keypoints */
41
42 Mat img_matches; 43 //繪制匹配
44 drawMatches(img1, keypoints_roi, img2, keypoints_img, 45 good_matches, img_matches, Scalar::all(-1), 46 Scalar::all(-1), vector<char>(), 47 DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS); 48 imshow("matches", img_matches); 49
50 vector<Point2f> keypoints1, keypoints2; 51 for (i = 0; i < good_matches.size(); i++) 52 { 53 keypoints1.push_back(keypoints_img[good_matches[i].trainIdx].pt); 54 keypoints2.push_back(keypoints_roi[good_matches[i].queryIdx].pt); 55 } 56 //計算單應矩陣(仿射變換矩陣)
57 Mat H = findHomography(keypoints1, keypoints2, RANSAC); 58 Mat H2 = findHomography(keypoints2, keypoints1, RANSAC); 59
60
61 Mat stitchedImage; //定義仿射變換后的圖像(也是拼接結果圖像)
62 Mat stitchedImage2; //定義仿射變換后的圖像(也是拼接結果圖像)
63 int mRows = img2.rows; 64 if (img1.rows > img2.rows) 65 { 66 mRows = img1.rows; 67 } 68
69 int count = 0; 70 for (int i = 0; i < keypoints2.size(); i++) 71 { 72 if (keypoints2[i].x >= img2.cols / 2) 73 count++; 74 } 75 //判斷匹配點位置來決定圖片是左還是右
76 if (count / float(keypoints2.size()) >= 0.5) //待拼接img2圖像在右邊
77 { 78 cout << "img1 should be left" << endl; 79 vector<Point2f>corners(4); 80 vector<Point2f>corners2(4); 81 corners[0] = Point(0, 0); 82 corners[1] = Point(0, img2.rows); 83 corners[2] = Point(img2.cols, img2.rows); 84 corners[3] = Point(img2.cols, 0); 85 stitchedImage = Mat::zeros(img2.cols + img1.cols, mRows, CV_8UC3); 86 warpPerspective(img2, stitchedImage, H, Size(img2.cols + img1.cols, mRows)); 87
88 perspectiveTransform(corners, corners2, H); 89 /*
90 circle(stitchedImage, corners2[0], 5, Scalar(0, 255, 0), 2, 8); 91 circle(stitchedImage, corners2[1], 5, Scalar(0, 255, 255), 2, 8); 92 circle(stitchedImage, corners2[2], 5, Scalar(0, 255, 0), 2, 8); 93 circle(stitchedImage, corners2[3], 5, Scalar(0, 255, 0), 2, 8); */
94 cout << corners2[0].x << ", " << corners2[0].y << endl; 95 cout << corners2[1].x << ", " << corners2[1].y << endl; 96 imshow("temp", stitchedImage); 97 //imwrite("temp.jpg", stitchedImage);
98
99 Mat half(stitchedImage, Rect(0, 0, img1.cols, img1.rows)); 100 img1.copyTo(half); 101 imshow("result", stitchedImage); 102 } 103 else //待拼接圖像img2在左邊
104 { 105 cout << "img2 should be left" << endl; 106 stitchedImage = Mat::zeros(img2.cols + img1.cols, mRows, CV_8UC3); 107 warpPerspective(img1, stitchedImage, H2, Size(img1.cols + img2.cols, mRows)); 108 imshow("temp", stitchedImage); 109
110 //計算仿射變換后的四個端點
111 vector<Point2f>corners(4); 112 vector<Point2f>corners2(4); 113 corners[0] = Point(0, 0); 114 corners[1] = Point(0, img1.rows); 115 corners[2] = Point(img1.cols, img1.rows); 116 corners[3] = Point(img1.cols, 0); 117
118 perspectiveTransform(corners, corners2, H2); //仿射變換對應端點
119 /*
120 circle(stitchedImage, corners2[0], 5, Scalar(0, 255, 0), 2, 8); 121 circle(stitchedImage, corners2[1], 5, Scalar(0, 255, 255), 2, 8); 122 circle(stitchedImage, corners2[2], 5, Scalar(0, 255, 0), 2, 8); 123 circle(stitchedImage, corners2[3], 5, Scalar(0, 255, 0), 2, 8); */
124 cout << corners2[0].x << ", " << corners2[0].y << endl; 125 cout << corners2[1].x << ", " << corners2[1].y << endl; 126
127 Mat half(stitchedImage, Rect(0, 0, img2.cols, img2.rows)); 128 img2.copyTo(half); 129 imshow("result", stitchedImage); 130
131 } 132 imwrite("result.bmp", stitchedImage); 133 return true; 134 }