影象拼接演算法總結(二)
2、特徵提取與匹配
OpenCV中關於SURF演算法的部分,常常涉及到的是SURF、SurfFeatureDetector、SurfDescriptorExtractor這三個類;
features2d.hpp標頭檔案中,有:typedef SURF SurfFeatureDetector;和typedef SURF SurfDescriptorExtractor;typedef宣告是為現有型別建立一個新的名字,類型別名,即SURF類有了兩個新名字SurfFeatureDetector以及SurfDescriptorExtractor。
也就是說,SurfFeatureDetector類和SurfDescriptorExtractor類,其實就是SURF類,他們三者等價。
因此包含必要的標頭檔案:
#include<iostream>
#include<stdio.h>
#include"highgui/highgui.hpp"
#include"opencv2/nonfree/nonfree.hpp"
#include"opencv2/legacy/legacy.hpp"
usingnamespace cv;
usingnamespace std;
2.1 拼接影象的載入:
例項:
MatimageRgbLeft = imread("img_left_1.JPG");
MatimageRgbRight = imread("img_right_1.JPG");
//灰度圖轉換,在特徵提取與匹配模組使用灰度影象,因此需要將影象轉為灰度圖
MatimageGrayLeft, imageGrayRight;
cvtColor(imageRgbLeft,imageGrayLeft, CV_RGB2GRAY);
cvtColor(imageRgbRight,imageGrayRight, CV_RGB2GRAY);
2.2 提取特徵點
例項:
intminHessian = 800; //設定閾值minHessian,關係到最後提取的特徵點,這個是surf演算法的一個引數;
SurfFeatureDetectorsurfDetector(minHessian); // 海塞矩陣閾值
vector<KeyPoint>keyPointLeft, keyPointRight;// 定義兩個KeyPoint 向keypoints_object, keypoints_scene來存放檢測出來的特徵點;
surfDetector.detect(imageGrayLeft,keyPointLeft);
surfDetector.detect(imageGrayRight,keyPointRight);
2.3 drawKeypoints()
作用:繪製關鍵點。
形式:void drawKeypoints(const Mat&image, constvector<KeyPoint>& keypoints, Mat& outImage, constScalar&color=Scalar::all(-1), int flags=DrawMatchesFlags::DEFAULT );
引數:
image:const Mat&型別的src,輸入影象;
keypoints:const vector<KeyPoint>&型別的keypoints,根據源影象得到的特徵點,它是一個輸出引數;
outImage:Mat&型別的outImage,輸出影象,其內容取決於第五個引數識別符號falgs;
color:const Scalar&型別的color,關鍵點的顏色,有預設值Scalar::all(-1);
flags:int型別的flags,繪製關鍵點是的特徵識別符號,有預設值DrawMatchesFlags::DEFAULT;
例項:
MatimageLeftPoint, imageRightPoint;
drawKeypoints(imageRgbLeft,keyPointLeft, imageLeftPoint, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
imshow("imageLeftPoint",imageLeftPoint);
drawKeypoints(imageRgbRight,keyPointRight, imageRightPoint, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
imshow("imageRightPoint",imageRightPoint);
waitKey(0);
特徵點提取的效果圖如下:
圖9-3:左圖影象特徵點提取結果
圖9-4:右圖影象特徵點提取結果
2.4特徵點描述,為特徵點匹配做準備
例項:
SurfDescriptorExtractorsurfDescriptor;
MatimageDescLeft, imageDescRight;
surfDescriptor.compute(imageGrayLeft,keyPointLeft, imageDescLeft);
surfDescriptor.compute(imageRgbRight,keyPointRight, imageDescRight);
2.5獲得匹配特徵點,並提取最優配對
用BruteForceMatcher類中的函式match來匹配兩幅影象的特徵向量
例項:
BruteForceMatchermatcher;
vector<DMatch>matchePoints;
matcher.match(imageDescLeft,imageDescRight, matchePoints, Mat());
BruteForceMatcher是由DescriptorMatcher派生出來的一個類,而DescriptorMatcher定義了不同的匹配策略的共同介面。呼叫match方法後,在其第三個引數輸出一個cv::DMatch向量。於是我們定義一個std::vector<DMatch>型別的matches。
用FlannBasedMatcher類中的函式match來匹配兩幅影象的特徵向量
例項:
FlannBasedMatchermatcher;
vector<DMatch>matchePoints;
matcher.match(imageDescLeft,imageDescRight, matchePoints, Mat());
FlannBasedMatcher演算法更快但是找到的是最近鄰近似匹配,所以當我們需要找到一個相對好的匹配但是不需要最佳匹配的時候往往使用FlannBasedMatcher。當然也可以通過調整FlannBasedMatcher的引數來提高匹配的精度或者提高演算法速度,但是相應地演算法速度或者演算法精度會受到影響。
2.6 drawMatches()
//作用:從兩幅圖片中畫出發現的匹配的關鍵點。
形式:void drawMatches(const Mat& img1, constvector<KeyPoint>& keypoints1, const Mat& img2, constvector<KeyPoint>& keypoints2, const vector<DMatch>&matches1to2, Mat& outImg, const Scalar& matchColor=Scalar::all(-1),const Scalar& singlePointColor=Scalar::all(-1), constvector<char>& matchesMask=vector<char>(), int flags=DrawMatchesFlags::DEFAULT);
引數:
img1、img2:兩幅源影象;
keypoints1、keypoints2:兩幅源影象中的關鍵點;
matches1to2:從第一幅影象來匹配第二幅影象,即從keypoints1[i]中尋找與keypoints2[i]的對應點;
outImg:輸出影象;它的值取決於flags--在影象中繪製的內容;
matchColor:匹配顏色(線和點的顏色),如果matchColor==Scalar::all(-1),顏色隨機生成;
singlePointColor:單個關鍵點的顏色,即沒有關鍵點與之匹配,如果matchColor==Scalar::all(-1),顏色隨機生成;
matchesMask:掩碼決定畫哪個匹配的關鍵點,如果掩碼為空,畫出所有的匹配的關鍵點;
flags:設定繪圖功能,可能標誌位的值由“DrawMatchesFlags”確定;
例項:
Matfirstmatches;
drawMatches(imageRgbLeft,keyPointLeft, imageRgbRight, keyPointRight,
matchePoints, firstmatches,Scalar::all(-1), Scalar::all(-1),
vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
imshow("first_matches",firstmatches);
waitKey(0);
畫出匹配效果圖如下:
圖9-5:特徵匹配結果
2.7匹配特徵點sort排序
sort方法可以對匹配點進行從小到大的排序,用sort排序之前,每個匹配點對間的距離(即匹配穩健性程度)是隨機分佈的,排序之後,距離按由小到大的順序排列,越靠前的,匹配度越高,可以通過排序後把靠前的匹配提取出來。本例中提取前20個最優匹配。
例項:
sort(matchePoints.begin(),matchePoints.end()); //特徵點排序
//獲取排在前N個的最優匹配特徵點
vector<Point2f>imagePointsLeft, imagePointsRight;
intj = 0;
for(int i = 0; i<matchePoints.size(); i++)
{
j++;
imagePointsLeft.push_back(keyPointLeft[matchePoints[i].queryIdx].pt);
imagePointsRight.push_back(keyPointRight[matchePoints[i].trainIdx].pt);
if (j > 20)
break;
}
3、影象配準
3.1 findHomography()和RANSAC去除誤匹配
作用:尋找兩個平面匹配上的關鍵點的變換。
形式:Mat findHomography(InputArray srcPoints, InputArray dstPoints, intmethod=0, double ransacReprojThreshold=3, OutputArray mask=noArray() );
引數:
srcPoints:原始平面中點的座標,即一個CV_32FC2 or vector<Point2f> 型矩陣;
dstPoints:目標平面中點的座標,即一個CV_32FC2 or vector<Point2f> 型矩陣;
method:用於計算單應矩陣的方法--0:使用所有點的常規方法;
CV_RANSAC:基於RANSAC的魯棒方法;
CV_LMEDS:最不平均方法;
ransacReprojThreshold:僅當使用CV_RANSAC方法時,把一雙點作為內窗的最大投影誤差;
mask:魯棒方法的可選輸出掩碼設定;
//獲取影象1到影象2的投影對映矩陣,尺寸為3*3
vector<unsignedchar> inliersMask(imagePointsLeft.size());
Mathomo = findHomography(imagePointsLeft, imagePointsRight, CV_RANSAC, 5, inliersMask);//使用CV_RANSAC來去除誤匹配
vector<DMatch>matches_ransac;
//手動的保留RANSAC過濾後的匹配點對
for(int i = 0; i<inliersMask.size(); i++)
{
//cout<<inliersMask[i]<<endl;
cout << (int)(inliersMask[i])<< endl;
if (inliersMask[i])
{
matches_ransac.push_back(matchePoints[i]);
}
}
Matsecondmatches;
drawMatches(imageRgbLeft,keyPointLeft, imageRgbRight, keyPointRight,
matches_ransac, secondmatches,Scalar::all(-1), Scalar::all(-1),
vector<char>(),DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);//使用drawMatches畫出RANSAC之後的匹配點
imshow("secondmatches",secondmatches);
waitKey(0);
匹配效果圖如下:
圖9-6:RANSAC去除誤匹配後特徵匹配結果
H矩陣修正:如果直接使用H矩陣進行影象的拼接將使得在左圖沒有重疊區域的畫素點的座標變化為負值,這是由於左圖是按照右圖的座標系進行配準,所以左右相對於右圖的橫座標在非重疊區域都是負值,所以需要引用校正矩陣,其中H矩陣的第一行資料影響水平(x)方向的偏移,第二行資料影響垂直(y)方向的偏移。所以水平偏移需要修正H矩陣的第一行資料,垂直偏移需要修正H矩陣的第二行資料。H矩陣數值的調整可以通過用一個3*3的矩陣相乘實現,水平方向的修正矩陣:
MatadjustMat=(Mat_<double>(3,3)<<1.0,0,adjustValue,0,1.0,0,0,0,1.0);
垂直方向上的修正矩陣:
MatadjustMat=(Mat_<double>(3,3)<<1.0,0,0,0,1.0,adjustValue,0,0,1.0);
水平和垂直兩個方向上的修正矩陣:
MatadjustMat=(Mat_<double>(3,3)<<1.0,0,adjustValue1,0,1.0,adjustValue2,0,0,1.0);
例項:
//計算修正矩陣,首先定義原始影象矩形的4個頂點座標,然後進行放射變化操作,計算配準後頂點座標,由於左圖非重疊區域座標會變為負值,所以取(0,0)點的變化後橫座標絕對值即為非重疊區域的寬度。
vector<Point2f>obj_corners(4);
obj_corners[0]= Point(0, 0); obj_corners[1] = Point(imageRgbLeft.cols, 0);
obj_corners[2]= Point(imageRgbLeft.cols, imageRgbLeft.rows); obj_corners[3] = Point(0,imageRgbLeft.rows);
vector<Point2f>scene_corners(4);
perspectiveTransform(obj_corners,scene_corners, homo);
MatadjustMat = (Mat_<double>(3, 3) << 1.0, 0, abs(scene_corners[0].x),0, 1.0, 0, 0, 0, 1.0);
MatadjustHomo = adjustMat*homo;
3.2透視變化
voidwarpPerspective( InputArray src, OutputArray dst,
InputArrayM, Size dsize,
intflags=INTER_LINEAR,
intborderMode=BORDER_CONSTANT,
constScalar& borderValue=Scalar());
src:輸入2通道或3通道的浮點陣列,每個元素是一個將被轉換的2D/3D向量;
dst:與輸入有相同大小和型別的輸出矩陣;
M:3x3 或4x4的浮點轉換矩陣;
Dsize:拼接影象完成影象大小;
MatimageTransformLeft;
warpPerspective(imageRgbLeft,imageTransformLeft, adjustHomo, Size(imageRgbRight.cols +abs(scene_corners[0].x), imageRgbRight.rows));
效果圖如下:
圖9-7:影象配準前原圖
圖9-8:影象配準結果圖
4、影象融合
4.1 定位重疊區域
//在最強匹配點左側的重疊區域進行累加,是銜接穩定過渡,消除突變
Matimage1Overlap, image2Overlap; //圖1和圖2的重疊部分
image1Overlap= imageTransformLeft(Rect(Point(abs(scene_corners[0].x), 0), Point(abs(scene_corners[0].x)+ (scene_corners[2].x), imageRgbRight.rows)));
image2Overlap= imageRgbRight(Rect(0, 0, image1Overlap.cols, image1Overlap.rows));
Matimage1ROICopy = image1Overlap.clone(); //複製一份圖1的重疊部分
4.2重疊區域加權融合
for(int i = 0; i<image1Overlap.rows; i++)
{
for (int j = 0; j<image1Overlap.cols;j++)
{
double weight;
weight = (double)j /image1Overlap.cols; //隨距離改變而改變的疊加係數
image1Overlap.at<Vec3b>(i, j)[0]= (1 - weight)*image1ROICopy.at<Vec3b>(i, j)[0] +weight*image2Overlap.at<Vec3b>(i, j)[0];
image1Overlap.at<Vec3b>(i, j)[1]= (1 - weight)*image1ROICopy.at<Vec3b>(i, j)[1] +weight*image2Overlap.at<Vec3b>(i, j)[1];
image1Overlap.at<Vec3b>(i, j)[2]= (1 - weight)*image1ROICopy.at<Vec3b>(i, j)[2] +weight*image2Overlap.at<Vec3b>(i, j)[2];
}
}
4.3 非重疊區域融合
MatROIMat = imageRgbRight(Rect(Point(image1Overlap.cols, 0),Point(imageRgbRight.cols, imageRgbRight.rows))); //圖2中不重合的部分
ROIMat.copyTo(Mat(imageTransformLeft,Rect(abs(scene_corners[0].x) + (scene_corners[2].x), 0, ROIMat.cols,imageRgbRight.rows))); //不重合的部分直接銜接上去
namedWindow("拼接結果", 0);
imshow("拼接結果", imageTransformLeft);
//imwrite("D:\\拼接結果.jpg", imageTransform1);
waitKey(0);
影象融合後效果圖如下:
圖9-9:影象融合結果