SLAM之特征匹配(一)————RANSAC-------OpenCV中findFundamentalMat函数使用的模型
目錄
1.RANSAC原理??
2. RANSAC算法步驟:?
3. RANSAC源碼解析
step one niters最初的值為2000,這就是初始時的RANSAC算法的循環次數,getSubset()函數是從一組對應的序列中隨機的選出4組(因為要想計算出一個3X3的矩陣,至少需要4組對應的坐標),m1和m2是我們輸入序列,ms1和ms2是隨機選出的對應的4組匹配。
參考https://blog.csdn.net/laobai1015/article/details/51683076
cv::findFundamentalMat
4. 針孔相機模型和變形
5.照相機定標
5.1 ProjectPoints2
5.2 FindHomographyFindHomography
5.3 CalibrateCamera2
5.4 FindExtrinsicCameraParams2
計算指定視圖的攝像機外參數
5.5 Rodrigues2
5.6 Undistort2
5.7 InitUndistortMap
5. 8 FindChessboardCorners
DrawChessBoardCorners
6. 姿態估計
6.1 CreatePOSITObject
6.2 POSIT
6.3 ReleasePOSITObject
6.4 CalcImageHomography
6.5 FindFundamentalMat
6.6 ComputeCorrespondEpilines
隨機抽樣一致性(RANSAC)算法,可以在一組包含“外點”的數據集中,采用不斷迭代的方法,尋找最優參數模型,不符合最優模型的點,被定義為“外點”。在圖像配準以及拼接上得到廣泛的應用,本文將對RANSAC算法在OpenCV中角點誤匹配對的檢測中進行解析。
1.RANSAC原理??
? OpenCV中濾除誤匹配對采用RANSAC算法尋找一個最佳單應性矩陣H,矩陣大小為3×3。RANSAC目的是找到最優的參數矩陣使得滿足該矩陣的數據點個數最多,通常令h33=1來歸一化矩陣。由于單應性矩陣有8個未知參數,至少需要8個線性方程求解,對應到點位置信息上,一組點對可以列出兩個方程,則至少包含4組匹配點對。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ?其中(x,y)表示目標圖像角點位置,(x',y')為場景圖像角點位置,s為尺度參數。
? RANSAC算法從匹配數據集中隨機抽出4個樣本并保證這4個樣本之間不共線,計算出單應性矩陣,然后利用這個模型測試所有數據,并計算滿足這個模型數據點的個數與投影誤差(即代價函數),若此模型為最優模型,則對應的代價函數最小。
-----------------------------------------------------------------------------------------------------------------
2. RANSAC算法步驟:?
? ? ? ? ? 1. 隨機從數據集中隨機抽出4個樣本數據 (此4個樣本之間不能共線),計算出變換矩陣H,記為模型M;
? ? ? ? ? 2. 計算數據集中所有數據與模型M的投影誤差,若誤差小于閾值,加入內點集 I ;
? ? ? ? ? 3. 如果當前內點集 I 元素個數大于最優內點集 I_best , 則更新 I_best = I,同時更新迭代次數k ;
? ? ? ? ? 4. 如果迭代次數大于k,則退出 ; 否則迭代次數加1,并重復上述步驟;
? 注:迭代次數k在不大于最大迭代次數的情況下,是在不斷更新而不是固定的;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?其中,p為置信度,一般取0.995;w為"內點"的比例 ; m為計算模型所需要的最少樣本數=4;
-----------------------------------------------------------------------------------------------------------------
2.例程
OpenCV中此功能通過調用findHomography函數調用,下面是個例程:
[cpp]?view plain?copy- #include?<iostream>??
- #include?"opencv2/opencv.hpp"??
- #include?"opencv2/core/core.hpp"??
- #include?"opencv2/features2d/features2d.hpp"??
- #include?"opencv2/highgui/highgui.hpp"??
- using?namespace?cv;??
- using?namespace?std;??
- int?main(int?argc,?char**?argv)??
- {??
- ????Mat?obj=imread("F:\\Picture\\obj.jpg");???//載入目標圖像??
- ????Mat?scene=imread("F:\\Picture\\scene.jpg");?//載入場景圖像??
- ????if?(obj.empty()?||?scene.empty()?)??
- ????{??
- ????????cout<<"Can't?open?the?picture!\n";??
- ????????return?0;??
- ????}??
- ????vector<KeyPoint>?obj_keypoints,scene_keypoints;??
- ????Mat?obj_descriptors,scene_descriptors;??
- ????ORB?detector;?????//采用ORB算法提取特征點??
- ????detector.detect(obj,obj_keypoints);??
- ????detector.detect(scene,scene_keypoints);??
- ????detector.compute(obj,obj_keypoints,obj_descriptors);??
- ????detector.compute(scene,scene_keypoints,scene_descriptors);??
- ????BFMatcher?matcher(NORM_HAMMING,true);?//漢明距離做為相似度度量??
- ????vector<DMatch>?matches;??
- ????matcher.match(obj_descriptors,?scene_descriptors,?matches);??
- ????Mat?match_img;??
- ????drawMatches(obj,obj_keypoints,scene,scene_keypoints,matches,match_img);??
- ????imshow("濾除誤匹配前",match_img);??
- ??
- ????//保存匹配對序號??
- ????vector<int>?queryIdxs(?matches.size()?),?trainIdxs(?matches.size()?);??
- ????for(?size_t?i?=?0;?i?<?matches.size();?i++?)??
- ????{??
- ????????queryIdxs[i]?=?matches[i].queryIdx;??
- ????????trainIdxs[i]?=?matches[i].trainIdx;??
- ????}?????
- ??
- ????Mat?H12;???//變換矩陣??
- ??
- ????vector<Point2f>?points1;?KeyPoint::convert(obj_keypoints,?points1,?queryIdxs);??
- ????vector<Point2f>?points2;?KeyPoint::convert(scene_keypoints,?points2,?trainIdxs);??
- ????int?ransacReprojThreshold?=?5;??//拒絕閾值??
- ??
- ??
- ????H12?=?findHomography(?Mat(points1),?Mat(points2),?CV_RANSAC,?ransacReprojThreshold?);??
- ????vector<char>?matchesMask(?matches.size(),?0?);????
- ????Mat?points1t;??
- ????perspectiveTransform(Mat(points1),?points1t,?H12);??
- ????for(?size_t?i1?=?0;?i1?<?points1.size();?i1++?)??//保存‘內點’??
- ????{??
- ????????if(?norm(points2[i1]?-?points1t.at<Point2f>((int)i1,0))?<=?ransacReprojThreshold?)?//給內點做標記??
- ????????{??
- ????????????matchesMask[i1]?=?1;??
- ????????}?????
- ????}??
- ????Mat?match_img2;???//濾除‘外點’后??
- ????drawMatches(obj,obj_keypoints,scene,scene_keypoints,matches,match_img2,Scalar(0,0,255),Scalar::all(-1),matchesMask);??
- ??
- ????//畫出目標位置??
- ????std::vector<Point2f>?obj_corners(4);??
- ????obj_corners[0]?=?cvPoint(0,0);?obj_corners[1]?=?cvPoint(?obj.cols,?0?);??
- ????obj_corners[2]?=?cvPoint(?obj.cols,?obj.rows?);?obj_corners[3]?=?cvPoint(?0,?obj.rows?);??
- ????std::vector<Point2f>?scene_corners(4);??
- ????perspectiveTransform(?obj_corners,?scene_corners,?H12);??
- ????line(?match_img2,?scene_corners[0]?+?Point2f(static_cast<float>(obj.cols),?0),???
- ????????scene_corners[1]?+?Point2f(static_cast<float>(obj.cols),?0),Scalar(0,0,255),2);??
- ????line(?match_img2,?scene_corners[1]?+?Point2f(static_cast<float>(obj.cols),?0),???
- ????????scene_corners[2]?+?Point2f(static_cast<float>(obj.cols),?0),Scalar(0,0,255),2);??
- ????line(?match_img2,?scene_corners[2]?+?Point2f(static_cast<float>(obj.cols),?0),???
- ????????scene_corners[3]?+?Point2f(static_cast<float>(obj.cols),?0),Scalar(0,0,255),2);??
- ????line(?match_img2,?scene_corners[3]?+?Point2f(static_cast<float>(obj.cols),?0),??
- ????????scene_corners[0]?+?Point2f(static_cast<float>(obj.cols),?0),Scalar(0,0,255),2);??
- ??
- ????imshow("濾除誤匹配后",match_img2);??
- ????waitKey(0);??
- ??????
- ????return?0;??
- }??
3. RANSAC源碼解析
(1)findHomography內部調用cvFindHomography函數
srcPoints:目標圖像點集
dstPoints:場景圖像點集
method: 最小中值法、RANSAC方法、最小二乘法
ransacReprojTheshold:投影誤差閾值
mask:掩碼
[cpp]?view plain?copy- cvFindHomography(?const?CvMat*?objectPoints,?const?CvMat*?imagePoints,??
- ??????????????????CvMat*?__H,?int?method,?double?ransacReprojThreshold,??
- ??????????????????CvMat*?mask?)??
- {??
- ????const?double?confidence?=?0.995;??//置信度??
- ????const?int?maxIters?=?2000;????//迭代最大次數??
- ????const?double?defaultRANSACReprojThreshold?=?3;?//默認拒絕閾值??
- ????bool?result?=?false;??
- ????Ptr<CvMat>?m,?M,?tempMask;??
- ??
- ????double?H[9];??
- ????CvMat?matH?=?cvMat(?3,?3,?CV_64FC1,?H?);??//變換矩陣??
- ????int?count;??
- ??
- ????CV_Assert(?CV_IS_MAT(imagePoints)?&&?CV_IS_MAT(objectPoints)?);??
- ??
- ????count?=?MAX(imagePoints->cols,?imagePoints->rows);??
- ????CV_Assert(?count?>=?4?);???????????//至少有4個樣本??
- ????if(?ransacReprojThreshold?<=?0?)??
- ????????ransacReprojThreshold?=?defaultRANSACReprojThreshold;??
- ??
- ????m?=?cvCreateMat(?1,?count,?CV_64FC2?);??//轉換為齊次坐標??
- ????cvConvertPointsHomogeneous(?imagePoints,?m?);??
- ??
- ????M?=?cvCreateMat(?1,?count,?CV_64FC2?);//轉換為齊次坐標??
- ????cvConvertPointsHomogeneous(?objectPoints,?M?);??
- ??
- ????if(?mask?)??
- ????{??
- ????????CV_Assert(?CV_IS_MASK_ARR(mask)?&&?CV_IS_MAT_CONT(mask->type)?&&??
- ????????????(mask->rows?==?1?||?mask->cols?==?1)?&&??
- ????????????mask->rows*mask->cols?==?count?);??
- ????}??
- ????if(?mask?||?count?>?4?)??
- ????????tempMask?=?cvCreateMat(?1,?count,?CV_8U?);??
- ????if(?!tempMask.empty()?)??
- ????????cvSet(?tempMask,?cvScalarAll(1.)?);??
- ??
- ????CvHomographyEstimator?estimator(4);??
- ????if(?count?==?4?)??
- ????????method?=?0;??
- ????if(?method?==?CV_LMEDS?)??//最小中值法??
- ????????result?=?estimator.runLMeDS(?M,?m,?&matH,?tempMask,?confidence,?maxIters?);??
- ????else?if(?method?==?CV_RANSAC?)????//采用RANSAC算法??
- ????????result?=?estimator.runRANSAC(?M,?m,?&matH,?tempMask,?ransacReprojThreshold,?confidence,?maxIters);//(2)解析??
- ????else??
- ????????result?=?estimator.runKernel(?M,?m,?&matH?)?>?0;?//最小二乘法??
- ??
- ????if(?result?&&?count?>?4?)??
- ????{??
- ????????icvCompressPoints(?(CvPoint2D64f*)M->data.ptr,?tempMask->data.ptr,?1,?count?);??//保存內點集??
- ????????count?=?icvCompressPoints(?(CvPoint2D64f*)m->data.ptr,?tempMask->data.ptr,?1,?count?);??
- ????????M->cols?=?m->cols?=?count;??
- ????????if(?method?==?CV_RANSAC?)??//??
- ????????????estimator.runKernel(?M,?m,?&matH?);??//內點集上采用最小二乘法重新估算變換矩陣??
- ????????estimator.refine(?M,?m,?&matH,?10?);//???
- ????}??
- ??
- ????if(?result?)??
- ????????cvConvert(?&matH,?__H?);??//保存變換矩陣??
- ??
- ????if(?mask?&&?tempMask?)??
- ????{??
- ????????if(?CV_ARE_SIZES_EQ(mask,?tempMask)?)??
- ???????????cvCopy(?tempMask,?mask?);??
- ????????else??
- ???????????cvTranspose(?tempMask,?mask?);??
- ????}??
- ??
- ????return?(int)result;??
- }??
(2) runRANSAC
maxIters:最大迭代次數
m1,m2 :數據樣本
model:變換矩陣
reprojThreshold:投影誤差閾值
confidence:置信度 ?0.995
[cpp]?view plain?copy- bool?CvModelEstimator2::runRANSAC(?const?CvMat*?m1,?const?CvMat*?m2,?CvMat*?model,??
- ????????????????????????????????????CvMat*?mask0,?double?reprojThreshold,??
- ????????????????????????????????????double?confidence,?int?maxIters?)??
- {??
- ????bool?result?=?false;??
- ????cv::Ptr<CvMat>?mask?=?cvCloneMat(mask0);??
- ????cv::Ptr<CvMat>?models,?err,?tmask;??
- ????cv::Ptr<CvMat>?ms1,?ms2;??
- ??
- ????int?iter,?niters?=?maxIters;??
- ????int?count?=?m1->rows*m1->cols,?maxGoodCount?=?0;??
- ????CV_Assert(?CV_ARE_SIZES_EQ(m1,?m2)?&&?CV_ARE_SIZES_EQ(m1,?mask)?);??
- ??
- ????if(?count?<?modelPoints?)??
- ????????return?false;??
- ??
- ????models?=?cvCreateMat(?modelSize.height*maxBasicSolutions,?modelSize.width,?CV_64FC1?);??
- ????err?=?cvCreateMat(?1,?count,?CV_32FC1?);//保存每組點對應的投影誤差??
- ????tmask?=?cvCreateMat(?1,?count,?CV_8UC1?);??
- ??
- ????if(?count?>?modelPoints?)??//多于4個點??
- ????{??
- ????????ms1?=?cvCreateMat(?1,?modelPoints,?m1->type?);??
- ????????ms2?=?cvCreateMat(?1,?modelPoints,?m2->type?);??
- ????}??
- ????else??//等于4個點??
- ????{??
- ????????niters?=?1;?//迭代一次??
- ????????ms1?=?cvCloneMat(m1);??//保存每次隨機找到的樣本點??
- ????????ms2?=?cvCloneMat(m2);??
- ????}??
- ??
- ????for(?iter?=?0;?iter?<?niters;?iter++?)??//不斷迭代??
- ????{??
- ????????int?i,?goodCount,?nmodels;??
- ????????if(?count?>?modelPoints?)??
- ????????{??
- ???????????//在(3)解析getSubset??
- ????????????bool?found?=?getSubset(?m1,?m2,?ms1,?ms2,?300?);?//隨機選擇4組點,且三點之間不共線,(3)解析??
- ????????????if(?!found?)??
- ????????????{??
- ????????????????if(?iter?==?0?)??
- ????????????????????return?false;??
- ????????????????break;??
- ????????????}??
- ????????}??
- ??
- ????????nmodels?=?runKernel(?ms1,?ms2,?models?);??//估算近似變換矩陣,返回1??
- ????????if(?nmodels?<=?0?)??
- ????????????continue;??
- ????????for(?i?=?0;?i?<?nmodels;?i++?)//執行一次???
- ????????{??
- ????????????CvMat?model_i;??
- ????????????cvGetRows(?models,?&model_i,?i*modelSize.height,?(i+1)*modelSize.height?);//獲取3×3矩陣元素??
- ????????????goodCount?=?findInliers(?m1,?m2,?&model_i,?err,?tmask,?reprojThreshold?);??//找出內點,(4)解析??
- ??
- ????????????if(?goodCount?>?MAX(maxGoodCount,?modelPoints-1)?)??//當前內點集元素個數大于最優內點集元素個數??
- ????????????{??
- ????????????????std::swap(tmask,?mask);??
- ????????????????cvCopy(?&model_i,?model?);??//更新最優模型??
- ????????????????maxGoodCount?=?goodCount;??
- ????????????????//confidence?為置信度默認0.995,modelPoints為最少所需樣本數=4,niters迭代次數??
- ????????????????niters?=?cvRANSACUpdateNumIters(?confidence,??//更新迭代次數,(5)解析??
- ????????????????????(double)(count?-?goodCount)/count,?modelPoints,?niters?);??
- ????????????}??
- ????????}??
- ????}??
- ??
- ????if(?maxGoodCount?>?0?)??
- ????{??
- ????????if(?mask?!=?mask0?)??
- ????????????cvCopy(?mask,?mask0?);??
- ????????result?=?true;??
- ????}??
- ??
- ????return?result;??
- }??
step one niters最初的值為2000,這就是初始時的RANSAC算法的循環次數,getSubset()函數是從一組對應的序列中隨機的選出4組(因為要想計算出一個3X3的矩陣,至少需要4組對應的坐標),m1和m2是我們輸入序列,ms1和ms2是隨機選出的對應的4組匹配。
?隨機的選出4組匹配后,就應該根據這4個匹配計算相應的矩陣,所以函數runKernel()就是根據4組匹配計算矩陣,參數里的models就是得到的矩陣。
stept wo?這個矩陣只是一個假設,為了驗證這個假設,需要用其他的點去計算,看看其他的點是內點還是外點。
findInliers()函數就是用來計算內點的。利用前面得到的矩陣,把所有的序列帶入,計算得出哪些是內點,哪些是外點,函數的返回值為goodCount,就是此次計算的內點的個數。
step three ?函數中還有一個值為maxGoodCount,每次循環的內點個數的最大值保存在這個值中,一個估計的矩陣如果有越多的內點,那么這個矩陣就越有可能是正確的。
step four ??所以計算內點個數以后,緊接著判斷一下goodCount和maxGoodCount的大小關系,如果goodCount>maxGoodCount,則把goodCount賦值給maxGoodCount。賦值之后的一行代碼非常關鍵,我們單獨拿出來說一下,代碼如下:
- niters?=?cvRANSACUpdateNumIters(?confidence,??
- ???????????????????(double)(count?-?goodCount)/count,?modelPoints,?niters?); ?
niters本來是迭代的次數,也就是循環的次數。但是通過這行代碼我們發現,每次循環后,都會對niters這個值進行更新,也就是每次循環后都會改變循環的總次數。
cvRANSACUpdateNumIters()函數利用confidence(置信度)count(總匹配個數)goodCount(當前內點個數)niters(當前的總迭代次數)這幾個參數,來動態的改變總迭代次數的大小。該函數的中心思想就是當內點占的比例較多時,那么很有可能已經找到了正確的估計,所以就適當的減少迭代次數來節省時間。這個迭代次數的減少是以指數形式減少的,所以節省的時間開銷也是非常的可觀。因此最初設計的2000的迭代次數,可能最終的迭代次數只有幾十。同樣的,如果你自己一開始把迭代次數設置成10000或者更大,進過幾次迭代后,niters又會變得非常小了。所以初始時的niters設置的再大,其實對最終的運行時間也沒什么影響。
所以,們現在應該清楚為什么輸入數據增加,而算法運行時間不會增加了。openCV的RANSAC算法首先把迭代的次數設置為2000,然后再迭代的過程中,動態的改變總迭代次數,無論輸入數據有多少,總的迭代次數不會增加,并且通過4個匹配計算出估計的矩陣這個時間是不變的,通過估計矩陣來計算內點,這方面的增加的時間開銷基本上可以忽略。所以導致的最終結果就是,無論輸入點有多少,運算時間基本不會有太大變化。
以上就是RANSAC算法的核心代碼,其中用到的一些函數,下面一一給出。
(3)getSubset
ms1,ms2:保存隨機找到的4個樣本
maxAttempts:最大迭代次數,300
[cpp]?view plain?copy- bool?CvModelEstimator2::getSubset(?const?CvMat*?m1,?const?CvMat*?m2,??
- ???????????????????????????????????CvMat*?ms1,?CvMat*?ms2,?int?maxAttempts?)??
- {??
- ????cv::AutoBuffer<int>?_idx(modelPoints);?//modelPoints?所需要最少的樣本點個數??
- ????int*?idx?=?_idx;??
- ????int?i?=?0,?j,?k,?idx_i,?iters?=?0;??
- ????int?type?=?CV_MAT_TYPE(m1->type),?elemSize?=?CV_ELEM_SIZE(type);??
- ????const?int?*m1ptr?=?m1->data.i,?*m2ptr?=?m2->data.i;??
- ????int?*ms1ptr?=?ms1->data.i,?*ms2ptr?=?ms2->data.i;??
- ????int?count?=?m1->cols*m1->rows;??
- ??
- ????assert(?CV_IS_MAT_CONT(m1->type?&?m2->type)?&&?(elemSize?%?sizeof(int)?==?0)?);??
- ????elemSize?/=?sizeof(int);?//每個數據占用字節數??
- ??
- ????for(;?iters?<?maxAttempts;?iters++)??
- ????{??
- ????????for(?i?=?0;?i?<?modelPoints?&&?iters?<?maxAttempts;?)??
- ????????{??
- ????????????idx[i]?=?idx_i?=?cvRandInt(&rng)?%?count;??//?隨機選取1組點??
- ????????????for(?j?=?0;?j?<?i;?j++?)??//?檢測是否重復選擇??
- ????????????????if(?idx_i?==?idx[j]?)??
- ????????????????????break;??
- ????????????if(?j?<?i?)??
- ????????????????continue;??//重新選擇??
- ????????????for(?k?=?0;?k?<?elemSize;?k++?)??//拷貝點數據??
- ????????????{??
- ????????????????ms1ptr[i*elemSize?+?k]?=?m1ptr[idx_i*elemSize?+?k];??
- ????????????????ms2ptr[i*elemSize?+?k]?=?m2ptr[idx_i*elemSize?+?k];??
- ????????????}??
- ????????????if(?checkPartialSubsets?&&?(!checkSubset(?ms1,?i+1?)?||?!checkSubset(?ms2,?i+1?)))//檢測點之間是否共線??
- ????????????{??
- ????????????????iters++;??//若共線,重新選擇一組??
- ????????????????continue;??
- ????????????}??
- ????????????i++;??
- ????????}??
- ????????if(?!checkPartialSubsets?&&?i?==?modelPoints?&&??
- ????????????(!checkSubset(?ms1,?i?)?||?!checkSubset(?ms2,?i?)))??
- ????????????continue;??
- ????????break;??
- ????}??
- ??
- ????return?i?==?modelPoints?&&?iters?<?maxAttempts;??//返回找到的樣本點個數??
- }??
(4) findInliers & computerReprojError
[cpp]?view plain?copy- int?CvModelEstimator2::findInliers(?const?CvMat*?m1,?const?CvMat*?m2,??
- ????????????????????????????????????const?CvMat*?model,?CvMat*?_err,??
- ????????????????????????????????????CvMat*?_mask,?double?threshold?)??
- {??
- ????int?i,?count?=?_err->rows*_err->cols,?goodCount?=?0;??
- ????const?float*?err?=?_err->data.fl;??
- ????uchar*?mask?=?_mask->data.ptr;??
- ??
- ????computeReprojError(?m1,?m2,?model,?_err?);??//計算每組點的投影誤差??
- ????threshold?*=?threshold;??
- ????for(?i?=?0;?i?<?count;?i++?)??
- ????????goodCount?+=?mask[i]?=?err[i]?<=?threshold;//誤差在限定范圍內,加入‘內點集’??
- ????return?goodCount;??
- }??
- //--------------------------------------??
- void?CvHomographyEstimator::computeReprojError(?const?CvMat*?m1,?const?CvMat*?m2,const?CvMat*?model,?CvMat*?_err?)??
- {??
- ????int?i,?count?=?m1->rows*m1->cols;??
- ????const?CvPoint2D64f*?M?=?(const?CvPoint2D64f*)m1->data.ptr;??
- ????const?CvPoint2D64f*?m?=?(const?CvPoint2D64f*)m2->data.ptr;??
- ????const?double*?H?=?model->data.db;??
- ????float*?err?=?_err->data.fl;??
- ??
- ????for(?i?=?0;?i?<?count;?i++?)????????//保存每組點的投影誤差,對應上述變換公式??
- ????{??
- ????????double?ww?=?1./(H[6]*M[i].x?+?H[7]*M[i].y?+?1.);??????
- ????????double?dx?=?(H[0]*M[i].x?+?H[1]*M[i].y?+?H[2])*ww?-?m[i].x;??
- ????????double?dy?=?(H[3]*M[i].x?+?H[4]*M[i].y?+?H[5])*ww?-?m[i].y;??
- ????????err[i]?=?(float)(dx*dx?+?dy*dy);??
- ????}??
- }??
(5)cvRANSACUpdateNumIters
對應上述k的計算公式
p:置信度
ep:外點比例
[cpp]?view plain?copy- cvRANSACUpdateNumIters(?double?p,?double?ep,??
- ????????????????????????int?model_points,?int?max_iters?)??
- {??
- ????if(?model_points?<=?0?)??
- ????????CV_Error(?CV_StsOutOfRange,?"the?number?of?model?points?should?be?positive"?);??
- ??
- ????p?=?MAX(p,?0.);??
- ????p?=?MIN(p,?1.);??
- ????ep?=?MAX(ep,?0.);??
- ????ep?=?MIN(ep,?1.);??
- ??
- ????//?avoid?inf's?&?nan's??
- ????double?num?=?MAX(1.?-?p,?DBL_MIN);??//num=1-p,做分子??
- ????double?denom?=?1.?-?pow(1.?-?ep,model_points);//做分母??
- ????if(?denom?<?DBL_MIN?)??
- ????????return?0;??
- ??
- ????num?=?log(num);??
- ????denom?=?log(denom);??
- ??
- ????return?denom?>=?0?||?-num?>=?max_iters*(-denom)????
- ????????max_iters?:?cvRound(num/denom);??
- }??
參考https://blog.csdn.net/laobai1015/article/details/51683076
cv::findFundamentalMat
在處理立體圖像對的時候經常會用到對極幾何的知識,計算基礎矩陣也是很常見的事。OpenCV實現了基本矩陣的算法。對于老版本的C代碼,計算基本矩陣的RANSAC方法中有一個迭代次數不收斂的bug,可能導致每次計算的采樣次數都達到最大限制的次數,其根源在于計算采樣可信度的公式有誤,新版本的代碼還沒有仔細看過,不敢確定是否已經修正了這個bug。但是這個bug并不會對最后的結果造成多大影響,只是會影響計算的效率——原本幾次采樣即可結束迭代,在這個bug的影響下可能要采樣數百次。
????新版本的計算基本矩陣的函數的使用也有一些問題,下面來看看cv::findFundamentalMat函數:
//! the algorithm for finding fundamental matrix
enum
{
????FM_7POINT = CV_FM_7POINT, //!< 7-point algorithm
????FM_8POINT = CV_FM_8POINT, //!< 8-point algorithm
????FM_LMEDS = CV_FM_LMEDS,??//!< least-median algorithm
????FM_RANSAC = CV_FM_RANSAC??//!< RANSAC algorithm
};
//! finds fundamental matrix from a set of corresponding 2D points
CV_EXPORTS Mat findFundamentalMat( const Mat& points1, const Mat& points2,
?????????????????????????????????????CV_OUT vector<uchar>& mask, int method=FM_RANSAC,
?????????????????????????????????????double param1=3., double param2=0.99 );
//! finds fundamental matrix from a set of corresponding 2D points
CV_EXPORTS_W Mat findFundamentalMat( const Mat& points1, const Mat& points2,
?????????????????????????????????????int method=FM_RANSAC,
?????????????????????????????????????double param1=3., double param2=0.99 );
????上面是OpenCV計算基本矩陣的C++接口,其內部實現仍然是調用的C代碼函數,僅僅是在上層進行了一次封裝。網上有些文檔中說const Mat& points1和points2這兩個參數可以直接由vector<Point2f>類型作為傳入參數,其實這是錯誤的。直接用vector<Point2f>傳遞,在編譯時可能不會報錯,但是運行時會直接崩潰。因為cv::Mat的構造函數并不會把Point2f轉化為兩個浮點數存于Mat的兩個元素中,而是仍然以Point2f存于Mat的一個元素中,于是findFundamentalMat一讀取這個Mat就會出錯。
????因此我們最好老老實實地去構建Mat points1和Mat points2。該矩陣的維度可以是2xN,也可以是Nx2的,其中N是特征點的數目。另一個要注意的地方就是該矩陣的類型不能是CV_64F,也就是說findFundamentalMat內部解析points1和points2時是按照float類型去解析的,設為CV_64F將導致讀取數據失敗,程序崩潰。最好設為CV_32F。下面是從匹配好的特征點計算F的代碼示例:
// vector<KeyPoint> m_LeftKey;
//?vector<KeyPoint> m_RightKey;
// vector<DMatch> m_Matches;
// 以上三個變量已經被計算出來,分別是提取的關鍵點及其匹配,下面直接計算F
// 分配空間
int ptCount = (int)m_Matches.size();
Mat p1(ptCount, 2, CV_32F);
Mat p2(ptCount, 2, CV_32F);
// 把Keypoint轉換為Mat
Point2f pt;
for (int i=0; i<ptCount; i++)
{
?????pt = m_LeftKey[m_Matches[i].queryIdx].pt;
?????p1.at<float>(i, 0) = pt.x;
?????p1.at<float>(i, 1) = pt.y;
??
?????pt = m_RightKey[m_Matches[i].trainIdx].pt;
?????p2.at<float>(i, 0) = pt.x;
?????p2.at<float>(i, 1) = pt.y;
}
// 用RANSAC方法計算F
// Mat m_Fundamental;
// 上面這個變量是基本矩陣
// vector<uchar> m_RANSACStatus;
// 上面這個變量已經定義過,用于存儲RANSAC后每個點的狀態
m_Fundamental = findFundamentalMat(p1, p2, m_RANSACStatus, FM_RANSAC);
// 計算野點個數
int OutlinerCount = 0;
for (int i=0; i<ptCount; i++)
{
?????if (m_RANSACStatus[i] == 0) // 狀態為0表示野點
?????{
??????????OutlinerCount++;
?????}
}
// 計算內點
// vector<Point2f> m_LeftInlier;
//?vector<Point2f> m_RightInlier;
//?vector<DMatch> m_InlierMatches;
// 上面三個變量用于保存內點和匹配關系
int InlinerCount = ptCount - OutlinerCount;
m_InlierMatches.resize(InlinerCount);
m_LeftInlier.resize(InlinerCount);
m_RightInlier.resize(InlinerCount);
InlinerCount = 0;
for (int i=0; i<ptCount; i++)
{
?????if (m_RANSACStatus[i] != 0)
?????{
??????????m_LeftInlier[InlinerCount].x = p1.at<float>(i, 0);
??????????m_LeftInlier[InlinerCount].y = p1.at<float>(i, 1);
??????????m_RightInlier[InlinerCount].x = p2.at<float>(i, 0);
??????????m_RightInlier[InlinerCount].y = p2.at<float>(i, 1);
??????????m_InlierMatches[InlinerCount].queryIdx = InlinerCount;
??????????m_InlierMatches[InlinerCount].trainIdx = InlinerCount;
??????????InlinerCount++;
?????}
}
// 把內點轉換為drawMatches可以使用的格式
vector<KeyPoint> key1(InlinerCount);
vector<KeyPoint> key2(InlinerCount);
KeyPoint::convert(m_LeftInlier, key1);
KeyPoint::convert(m_RightInlier, key2);
// 顯示計算F過后的內點匹配
// Mat m_matLeftImage;
// Mat m_matRightImage;
// 以上兩個變量保存的是左右兩幅圖像
Mat OutImage;
drawMatches(m_matLeftImage, key1, m_matRightImage, key2, m_InlierMatches, OutImage);
cvNamedWindow( "Match features", 1);
cvShowImage("Match features", &(IplImage(OutImage)));
cvWaitKey( 0 );
cvDestroyWindow( "Match features" );
4. 針孔相機模型和變形
這一節里的函數都使用針孔攝像機模型,這就是說,一幅視圖是通過透視變換將三維空間中的點投影到圖像平面。投影公式如下:
或者
這里(X, Y, Z)是一個點的世界坐標,(u, v)是點投影在圖像平面的坐標,以像素為單位。A被稱作攝像機矩陣,或者內參數矩陣。(cx, cy)是基準點(通常在圖像的中心),fx, fy是以像素為單位的焦距。所以如果因為某些因素對來自于攝像機的一幅圖像升采樣或者降采樣,所有這些參數(fx, fy, cx和cy)都將被縮放(乘或者除)同樣的尺度。內參數矩陣不依賴場景的視圖,一旦計算出,可以被重復使用(只要焦距固定)。旋轉-平移矩陣[R|t]被稱作外參數矩陣,它用來描述相機相對于一個固定場景的運動,或者相反,物體圍繞相機的的剛性運動。也就是[R|t]將點(X, Y, Z)的坐標變換到某個坐標系,這個坐標系相對于攝像機來說是固定不變的。上面的變換等價與下面的形式(z≠0):
x' =?x?/?z
y' =?y?/?z
真正的鏡頭通常有一些形變,主要的變形為徑向形變,也會有輕微的切向形變。所以上面的模型可以擴展為:
x' =?x?/?z
y' =?y?/?z
這里?r2?=?x'2?+?y'2
k1和k2是徑向形變系數,p1和p1是切向形變系數。OpenCV中沒有考慮高階系數。形變系數跟拍攝的場景無關,因此它們是內參數,而且與拍攝圖像的分辨率無關。
后面的函數使用上面提到的模型來做如下事情:
- 給定內參數和外參數,投影三維點到圖像平面。
- 給定內參數、幾個三維點坐標和其對應的圖像坐標,來計算外參數。
- 根據已知的定標模式,從幾個角度(每個角度都有幾個對應好的3D-2D點對)的照片來計算相機的外參數和內參數。
5.照相機定標
5.1 ProjectPoints2
投影三維點到圖像平面
void cvProjectPoints2( const CvMat* object_points, const CvMat* rotation_vector,const CvMat* translation_vector, const CvMat* intrinsic_matrix,const CvMat* distortion_coeffs, CvMat* image_points,CvMat* dpdrot=NULL, CvMat* dpdt=NULL, CvMat* dpdf=NULL,CvMat* dpdc=NULL, CvMat* dpddist=NULL );
object_points
物體點的坐標,為3xN或者Nx3的矩陣,這兒N是視圖中的所有所有點的數目。
rotation_vector
旋轉向量,1x3或者3x1。
translation_vector
平移向量,1x3或者3x1。
intrinsic_matrix
攝像機內參數矩陣A:
distortion_coeffs
形變參數向量,4x1或者1x4,為[k1,k2,p1,p2]。如果是NULL,所有形變系數都設為0。
image_points
輸出數組,存儲圖像點坐標。大小為2xN或者Nx2,這兒N是視圖中的所有點的數目。
dpdrot
可選參數,關于旋轉向量部分的圖像上點的導數,Nx3矩陣。
dpdt
可選參數,關于平移向量部分的圖像上點的導數,Nx3矩陣。
dpdf
可選參數,關于fx和fy的圖像上點的導數,Nx2矩陣。
dpdc
可選參數,關于cx和cy的圖像上點的導數,Nx2矩陣。
dpddist
可選參數,關于形變系數的圖像上點的導數,Nx4矩陣。
函數cvProjectPoints2通過給定的內參數和外參數計算三維點投影到二維圖像平面上的坐標。另外,這個函數可以計算關于投影參數的圖像點偏導數的雅可比矩陣。雅可比矩陣可以用在cvCalibrateCamera2和cvFindExtrinsicCameraParams2函數的全局優化中。這個函數也可以用來計算內參數和外參數的反投影誤差。注意,將內參數和(或)外參數設置為特定值,這個函數可以用來計算外變換(或內變換)。
5.2 FindHomographyFindHomography
計算兩個平面之間的透視變換
void cvFindHomography( const CvMat* src_points,const CvMat* dst_points,CvMat* homography );
src_points
原始平面的點坐標,大小為2xN,Nx2,3xN或者 Nx3矩陣(后兩個表示齊次坐標),這兒N表示點的數目。
dst_points
目標平面的點坐標大小為2xN,Nx2,3xN或者 Nx3矩陣(后兩個表示齊次坐標)。
homography
輸出的3x3的homography矩陣。
函數cvFindHomography計算源平面和目標平面之間的透視變換.
使得反投影錯誤最小:
這個函數可以用來計算初始的內參數和外參數矩陣。由于Homography矩陣的尺度可變,所以它被規一化使得h33= 1
5.3 CalibrateCamera2
利用定標來計算攝像機的內參數和外參數
void cvCalibrateCamera2( const CvMat* object_points, const CvMat* image_points,const CvMat* point_counts, CvSize image_size,CvMat* intrinsic_matrix, CvMat* distortion_coeffs,CvMat* rotation_vectors=NULL,CvMat* translation_vectors=NULL,int flags=0 );
object_points
定標點的世界坐標,為3xN或者Nx3的矩陣,這里N是所有視圖中點的總數。
image_points
定標點的圖像坐標,為2xN或者Nx2的矩陣,這里N是所有視圖中點的總數。
point_counts
向量,指定不同視圖里點的數目,1xM或者Mx1向量,M是視圖數目。
image_size
圖像大小,只用在初始化內參數時。
intrinsic_matrix
輸出內參矩陣(A)?,如果指定CV_CALIB_USE_INTRINSIC_GUESS和(或)CV_CALIB_FIX_ASPECT_RATION,fx、 fy、 cx和cy部分或者全部必須被初始化。
distortion_coeffs
輸出大小為4x1或者1x4的向量,里面為形變參數[k1, k2, p1, p2]。
rotation_vectors
輸出大小為3xM或者Mx3的矩陣,里面為旋轉向量(旋轉矩陣的緊湊表示方式,具體參考函數cvRodrigues2)
translation_vectors
輸出大小為3xM或Mx3的矩陣,里面為平移向量。
flags
不同的標志,可以是0,或者下面值的組合:
- CV_CALIB_USE_INTRINSIC_GUESS - 內參數矩陣包含fx,fy,cx和cy的初始值。否則,(cx, cy)被初始化到圖像中心(這兒用到圖像大小),焦距用最小平方差方式計算得到。注意,如果內部參數已知,沒有必要使用這個函數,使用cvFindExtrinsicCameraParams2則可。
- CV_CALIB_FIX_PRINCIPAL_POINT - 主點在全局優化過程中不變,一直在中心位置或者在其他指定的位置(當CV_CALIB_USE_INTRINSIC_GUESS設置的時候)。
- CV_CALIB_FIX_ASPECT_RATIO - 優化過程中認為fx和fy中只有一個獨立變量,保持比例fx/fy不變,fx/fy的值跟內參數矩陣初始化時的值一樣。在這種情況下, (fx, fy)的實際初始值或者從輸入內存矩陣中讀取(當CV_CALIB_USE_INTRINSIC_GUESS被指定時),或者采用估計值(后者情況中fx和fy可能被設置為任意值,只有比值被使用)。
- CV_CALIB_ZERO_TANGENT_DIST – 切向形變參數(p1, p2)被設置為0,其值在優化過程中保持為0。
函數cvCalibrateCamera2從每個視圖中估計相機的內參數和外參數。3維物體上的點和它們對應的在每個視圖的2維投影必須被指定。這些可以通過使用一個已知幾何形狀且具有容易檢測的特征點的物體來實現。這樣的一個物體被稱作定標設備或者定標模式,OpenCV有內建的把棋盤當作定標設備方法(參考cvFindChessboardCorners)。目前,傳入初始化的內參數(當CV_CALIB_USE_INTRINSIC_GUESS不被設置時)只支持平面定標設備(物體點的Z坐標必須為全0或者全1)。不過3維定標設備依然可以用在提供初始內參數矩陣情況。在內參數和外參數矩陣的初始值都計算出之后,它們會被優化用來減小反投影誤差(圖像上的實際坐標跟cvProjectPoints2計算出的圖像坐標的差的平方和)。
5.4 FindExtrinsicCameraParams2
計算指定視圖的攝像機外參數
void cvFindExtrinsicCameraParams2( const CvMat* object_points,const CvMat* image_points,const CvMat* intrinsic_matrix,const CvMat* distortion_coeffs,CvMat* rotation_vector,CvMat* translation_vector );
object_points
定標點的坐標,為3xN或者Nx3的矩陣,這里N是視圖中的個數。
image_points
定標點在圖像內的坐標,為2xN或者Nx2的矩陣,這里N是視圖中的個數。
intrinsic_matrix
內參矩陣(A)?。
distortion_coeffs
大小為4x1或者1x4的向量,里面為形變參數[k1,k2,p1,p2]。如果是NULL,所有的形變系數都為0。
rotation_vector
輸出大小為3x1或者1x3的矩陣,里面為旋轉向量(旋轉矩陣的緊湊表示方式,具體參考函數cvRodrigues2)。
translation_vector
大小為3x1或1x3的矩陣,里面為平移向量。
函數cvFindExtrinsicCameraParams2使用已知的內參數和某個視圖的外參數來估計相機的外參數。3維物體上的點坐標和相應的2維投影必須被指定。這個函數也可以用來最小化反投影誤差。
5.5 Rodrigues2
進行旋轉矩陣和旋轉向量間的轉換
int cvRodrigues2( const CvMat* src, CvMat* dst, CvMat* jacobian=0 );
src
輸入的旋轉向量(3x1或者1x3)或者旋轉矩陣(3x3)。
dst
輸出的旋轉矩陣(3x3)或者旋轉向量(3x1或者1x3)
jacobian
可選的輸出雅可比矩陣(3x9或者9x3),關于輸入部分的輸出數組的偏導數。
函數轉換旋轉向量到旋轉矩陣,或者相反。旋轉向量是旋轉矩陣的緊湊表示形式。旋轉向量的方向是旋轉軸,向量的長度是圍繞旋轉軸的旋轉角。旋轉矩陣R,與其對應的旋轉向量r,通過下面公式轉換:
反變換也可以很容易的通過如下公式實現:
旋轉向量是只有3個自由度的旋轉矩陣一個方便的表示,這種表示方式被用在函數cvFindExtrinsicCameraParams2和cvCalibrateCamera2內部的全局最優化中。
5.6 Undistort2
校正圖像因相機鏡頭引起的變形
void cvUndistort2( const CvArr* src, CvArr* dst,const CvMat* intrinsic_matrix,const CvMat* distortion_coeffs );
src
原始圖像(已經變形的圖像)。只能變換32fC1的圖像。
dst
結果圖像(已經校正的圖像)。
intrinsic_matrix
相機內參數矩陣,格式為?。
distortion_coeffs
四個變形系數組成的向量,大小為4x1或者1x4,格式為[k1,k2,p1,p2]。
函數cvUndistort2對圖像進行變換來抵消徑向和切向鏡頭變形。相機參數和變形參數可以通過函數cvCalibrateCamera2取得。使用本節開始時提到的公式,對每個輸出圖像像素計算其在輸入圖像中的位置,然后輸出圖像的像素值通過雙線性插值來計算。如果圖像得分辨率跟定標時用得圖像分辨率不一樣,fx、fy、cx和cy需要相應調整,因為形變并沒有變化。
5.7 InitUndistortMap
計算形變和非形變圖像的對應(map)
void cvInitUndistortMap( const CvMat* intrinsic_matrix,const CvMat* distortion_coeffs,CvArr* mapx, CvArr* mapy );
intrinsic_matrix
攝像機內參數矩陣(A) [fx 0 cx; 0 fy cy; 0 0 1].
distortion_coeffs
形變系數向量[k1, k2, p1, p2],大小為4x1或者1x4。
mapx
x坐標的對應矩陣。
mapy
y坐標的對應矩陣。
函數cvInitUndistortMap預先計算非形變對應-正確圖像的每個像素在形變圖像里的坐標。這個對應可以傳遞給cvRemap函數(跟輸入和輸出圖像一起)。
5. 8 FindChessboardCorners
尋找棋盤圖的內角點位置
int cvFindChessboardCorners( const void* image, CvSize pattern_size,CvPoint2D32f* corners, int* corner_count=NULL,int flags=CV_CALIB_CB_ADAPTIVE_THRESH );
image
輸入的棋盤圖,必須是8位的灰度或者彩色圖像。
pattern_size
棋盤圖中每行和每列角點的個數。
corners
檢測到的角點
corner_count
輸出,角點的個數。如果不是NULL,函數將檢測到的角點的個數存儲于此變量。
flags
各種操作標志,可以是0或者下面值的組合:
- CV_CALIB_CB_ADAPTIVE_THRESH - 使用自適應閾值(通過平均圖像亮度計算得到)將圖像轉換為黑白圖,而不是一個固定的閾值。
- CV_CALIB_CB_NORMALIZE_IMAGE - 在利用固定閾值或者自適應的閾值進行二值化之前,先使用cvNormalizeHist來均衡化圖像亮度。
- CV_CALIB_CB_FILTER_QUADS - 使用其他的準則(如輪廓面積,周長,方形形狀)來去除在輪廓檢測階段檢測到的錯誤方塊。
函數cvFindChessboardCorners試圖確定輸入圖像是否是棋盤模式,并確定角點的位置。如果所有角點都被檢測到且它們都被以一定順序排布(一行一行地,每行從左到右),函數返回非零值,否則在函數不能發現所有角點或者記錄它們地情況下,函數返回0。例如一個正常地棋盤圖右8x8個方塊和7x7個內角點,內角點是黑色方塊相互聯通地位置。這個函數檢測到地坐標只是一個大約地值,如果要精確地確定它們的位置,可以使用函數cvFindCornerSubPix。
DrawChessBoardCorners
繪制檢測到的棋盤角點
void cvDrawChessboardCorners( CvArr* image, CvSize pattern_size,CvPoint2D32f* corners, int count,int pattern_was_found );
image
結果圖像,必須是8位彩色圖像。
pattern_size
每行和每列地內角點數目。
corners
檢測到地角點數組。
count
角點數目。
pattern_was_found
指示完整地棋盤被發現(≠0)還是沒有發現(=0)。可以傳輸cvFindChessboardCorners函數的返回值。
當棋盤沒有完全檢測出時,函數cvDrawChessboardCorners以紅色圓圈繪制檢測到的棋盤角點;如果整個棋盤都檢測到,則用直線連接所有的角點。
6. 姿態估計
6.1 CreatePOSITObject
初始化包含對象信息的結構
CvPOSITObject* cvCreatePOSITObject( CvPoint3D32f* points, int point_count );
points
指向三維對象模型的指針
point_count
對象的點數
函數 cvCreatePOSITObject 為對象結構分配內存并計算對象的逆矩陣。
預處理的對象數據存儲在結構CvPOSITObject中,只能在OpenCV內部被調用,即用戶不能直接讀寫數據結構。用戶只可以創建這個結構并將指針傳遞給函數。
對象是在某坐標系內的一系列點的集合,函數 cvPOSIT計算從照相機坐標系中心到目標點points[0] 之間的向量。
一旦完成對給定對象的所有操作,必須使用函數cvReleasePOSITObject釋放內存。
6.2 POSIT
執行POSIT算法
void cvPOSIT( CvPOSITObject* posit_object, CvPoint2D32f* image_points, double focal_length,CvTermCriteria criteria, CvMatr32f rotation_matrix, CvVect32f translation_vector );
posit_object
指向對象結構的指針
image_points
指針,指向目標像素點在二維平面圖上的投影。
focal_length
使用的攝像機的焦距
criteria
POSIT迭代算法程序終止的條件
rotation_matrix
旋轉矩陣
translation_vector
平移矩陣.
函數 cvPOSIT 執行POSIT算法。圖像坐標在攝像機坐標系統中給出。焦距可以通過攝像機標定得到。算法每一次迭代都會重新計算在估計位置的透視投影。
兩次投影之間的范式差值是對應點中的最大距離。如果差值過小,參數criteria.epsilon就會終止程序。
6.3 ReleasePOSITObject
釋放3D對象結構
void cvReleasePOSITObject( CvPOSITObject** posit_object );
posit_object
指向 CvPOSIT 結構指針的指針。
函數 cvReleasePOSITObject 釋放函數 cvCreatePOSITObject分配的內存。
6.4 CalcImageHomography
計算長方形或橢圓形平面對象(例如胳膊)的Homography矩陣
void cvCalcImageHomography( float* line, CvPoint3D32f* center,float* intrinsic, float* homography );
line
對象的主要軸方向,為向量(dx,dy,dz).
center
對象坐標中心 ((cx,cy,cz)).
intrinsic
攝像機內參數 (3x3 matrix).
homography
輸出的Homography矩陣(3x3).
函數 cvCalcImageHomography 為從圖像平面到圖像平面的初始圖像變化(defined by 3D oblong object line)計算Homography矩陣。
對極幾何(雙視幾何)
6.5 FindFundamentalMat
由兩幅圖像中對應點計算出基本矩陣
int cvFindFundamentalMat( const CvMat* points1,const CvMat* points2,CvMat* fundamental_matrix,int method=CV_FM_RANSAC,double param1=1.,double param2=0.99,CvMat* status=NULL);
points1
第一幅圖像點的數組,大小為2xN/Nx2 或 3xN/Nx3 (N 點的個數),多通道的1xN或Nx1也可以。點坐標應該是浮點數(雙精度或單精度)。:
points2
第二副圖像的點的數組,格式、大小與第一幅圖像相同。
fundamental_matrix
輸出的基本矩陣。大小是 3x3 或者 9x3 ,(7-點法最多可返回三個矩陣).
method
計算基本矩陣的方法
- CV_FM_7POINT – 7-點算法,點數目= 7
- CV_FM_8POINT – 8-點算法,點數目 >= 8
- CV_FM_RANSAC – RANSAC 算法,點數目 >= 8
- CV_FM_LMEDS - LMedS 算法,點數目 >= 8
param1
這個參數只用于方法RANSAC 或 LMedS 。它是點到對極線的最大距離,超過這個值的點將被舍棄,不用于后面的計算。通常這個值的設定是0.5 or 1.0 。
param2
這個參數只用于方法RANSAC 或 LMedS 。 它表示矩陣正確的可信度。例如可以被設為0.99 。
status
具有N個元素的輸出數組,在計算過程中沒有被舍棄的點,元素被被置為1;否則置為0。這個數組只可以在方法RANSAC and LMedS 情況下使用;在其它方法的情況下,status一律被置為1。這個參數是可選參數。
對極幾何可以用下面的等式描述:
其中 F 是基本矩陣,p1?和?p2?分別是兩幅圖上的對應點。
函數 FindFundamentalMat 利用上面列出的四種方法之一計算基本矩陣,并返回基本矩陣的值:沒有找到矩陣,返回0,找到一個矩陣返回1,多個矩陣返回3。 計算出的基本矩陣可以傳遞給函數cvComputeCorrespondEpilines來計算指定點的對極線。
例子1:使用 RANSAC 算法估算基本矩陣。
int numPoints = 100;
CvMat* points1;
CvMat* points2;
CvMat* status;
CvMat* fundMatr;
points1 = cvCreateMat(2,numPoints,CV_32F);
points2 = cvCreateMat(2,numPoints,CV_32F);
status = cvCreateMat(1,numPoints,CV_32F);/* 在這里裝入對應點的數據... */fundMatr = cvCreateMat(3,3,CV_32F);
int num = cvFindFundamentalMat(points1,points2,fundMatr,CV_FM_RANSAC,1.0,0.99,status);
if( num == 1 )printf("Fundamental matrix was found\n");
elseprintf("Fundamental matrix was not found\n");例子2:7點算法(3個矩陣)的情況。
CvMat* points1;
CvMat* points2;
CvMat* fundMatr;
points1 = cvCreateMat(2,7,CV_32F);
points2 = cvCreateMat(2,7,CV_32F);/* 在這里裝入對應點的數據... */fundMatr = cvCreateMat(9,3,CV_32F);
int num = cvFindFundamentalMat(points1,points2,fundMatr,CV_FM_7POINT,0,0,0);
printf("Found %d matrixes\n",num);?
6.6 ComputeCorrespondEpilines
為一幅圖像中的點計算其在另一幅圖像中對應的對極線。
void cvComputeCorrespondEpilines( const CvMat* points,int which_image,const CvMat* fundamental_matrix,CvMat* correspondent_lines);
points
輸入點,是2xN 或者 3xN 數組 (N為點的個數)
which_image
包含點的圖像指數(1 or 2)
fundamental_matrix
基本矩陣
correspondent_lines
計算對極點, 3xN數組
函數 ComputeCorrespondEpilines 根據外級線幾何的基本方程計算每個輸入點的對應外級線。如果點位于第一幅圖像(which_image=1),對應的對極線可以如下計算?:
其中F是基本矩陣,p1?是第一幅圖像中的點,?l2?- 是與第二幅對應的對極線。如果點位于第二副圖像中 which_image=2),計算如下:
其中p2?是第二幅圖像中的點,l1?是對應于第一幅圖像的對極線,每條對極線都可以用三個系數表示 a, b, c:
歸一化后的對極線系數存儲在correspondent_lines 中。?
Freak特征描述+BruteForceMatcher匹配+RANSAC剔除誤匹配
時間一晃飛快,前幾日的廣州之行讓人難忘,置身炎熱的戶外仿佛蒸桑拿一樣。這一段時間一直在研究一種從一幅圖片中找到給定目標的方法,而基于局部特征提取并且進行特征的匹配可以實現。?
目前局部特征描述子有以下幾種,
- 2004年發展成熟的SIFT
- 后來改進的SURF,它們的描述符是高維度整型的數字。
- 2010年出現的BRIEF是一種二進制的描述符,這種描述符在提高計算速度方面給出了不錯的思路,但是不具備旋轉不變,尺度不變,對噪聲也比較敏感(雖然它采用了高斯平滑來抗噪聲)
- 2011年出現的ORB算法同樣是二進制,但是解決了旋轉不變和噪聲敏感的問題,但是不具備尺度不變性。
- 2011年同時出現了BRISK算法,解決旋轉不變,尺度不變和抗噪聲問題,網上有視頻,實時性也不錯,先有個直觀認識,爽死。鏈接[(http://v.youku.com/v_show/id_XMTI5MzI3Mzk0OA==.html)]
- 2012年提出FREAK算法,其實是在BRISK上的改進。具備BRISK的優點。這篇論文中作者表示FREAK算法可以outperform以前所有的描述子。(注意:這里是特征描述子,因為論文中沒有給出特征點檢測方法,只有特征點的描述子)?
看完了綜述,我決定用FREAK來實現。足足耗了我15天,今天我這一段所得寫下,勉勵自己,服務他人。?
先貼上代碼
/************************************************************************
* Copyright(c) 2016 唯瘋
* All rights reserved.
*
* Brief: FAST特征點提取以及FREAK描述子的圖像匹配,基于OpenCV2.4.8
* Version: 1.0
* Author: 唯瘋
* Date: 2016/07/21
* Address: 廣州&北京
************************************************************************/
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/nonfree/features2d.hpp>
#include <opencv2/legacy/legacy.hpp>
#include <iostream>
#include <vector>using namespace std;
using namespace cv;int main()
{Mat img1_src = imread("im5.jpg",0);Mat img2_src = imread("im6.jpg",0);//FastFeatureDetector fast(40);SurfFeatureDetector fast(2000,4);FREAK extractor;vector<KeyPoint> keypoints1,keypoints2;Mat descriptor1,descriptor2;vector<DMatch> final_matches; vector<DMatch> matches;double t = (double)getTickCount();fast.detect(img1_src,keypoints1);fast.detect(img2_src,keypoints2);//drawKeypoints(img1_src,keypoints1,img1_src,Scalar(0,255,0));//drawKeypoints(img2_src,keypoints2,img2_src,Scalar(0,255,0));//問題在這里!!!醉了,這里的問題,浪費了我5天,歐耶,就是整整5天,由于我想在這里看一看檢測出來的特征點是啥樣的,就跟父母想第一眼就立刻看到剛出生的嬰兒是一個心情的。結果,后來特征匹配就幾乎沒有什么正確匹配,害我還以為是FREAK這個描述子有問題呢。于是就各種找問題,看論文,翻墻逛論壇。最后一個不經意間發現了。問題就是:這倆個語句是把特征點又原封不動的畫到了img1_src中,也就是原圖像里面,而后來我進行特征點描述的時候,就直接在畫滿了特征點的圖片下進行描述,而不是原圖!不是原圖啊!是充滿了特征點的圖片!所以后期再進行匹配的時候,顯然,各種亂匹配,就跟隔壁家小狗似的,見了貓都想干壞事。于是乎,我直接注釋掉了這兩句!extractor.compute(img1_src,keypoints1,descriptor1);extractor.compute(img2_src,keypoints2,descriptor2);BFMatcher matcher(NORM_HAMMING,true);//暴力匹配,并且進行crosscheck,就是說第二個參數選擇true。matcher.match(descriptor1,descriptor2,matches);final_matches=matches;cout<<"number of total_matches : "<<final_matches.size()<<endl;//接下來是RANSAC剔除誤匹配vector<Point2f> querymatches, trainmatches;vector<KeyPoint> p1,p2;for(int i=0;i<final_matches.size();i++){p1.push_back(keypoints1[final_matches[i].queryIdx]);p2.push_back(keypoints2[final_matches[i].trainIdx]);}for(int i=0;i<p1.size();i++){querymatches.push_back(p1[i].pt);trainmatches.push_back(p2[i].pt);}cout<<querymatches[1]<<" and "<<trainmatches[1]<<endl;vector<uchar> status;Mat h = findHomography(querymatches,trainmatches,status,CV_FM_RANSAC,10);int index=0;vector<DMatch> super_final_matches;for (int i=0;i<final_matches.size();i++){cout<<status[i];if (status[i] != 0){super_final_matches.push_back(final_matches[i]);index++;}}cout<<"number of inlier_matches : "<<index<<endl;Mat imgMatch;drawMatches(img1_src,keypoints1,img2_src,keypoints2,super_final_matches,imgMatch);imshow("imgMatch",imgMatch);t = ((double)getTickCount()-t)/getTickFrequency();cout<<" total time [s] : "<<t<<endl;waitKey(0);return 0;
}
一:?
首先說說特征點檢測,為什么我用surf,而不用速度很快的fast或者Brisk,我自己做了測試,發現用fast和brisk的話,再用freak描述,最終匹配效果并不好,比sift要差…我其實不太明白為什么,因為有一些后期的論文對比效果說freak綜合效果最好,然而我卻得不到這樣的結果。要繼續鉆研了,大家有想法歡迎交流,哦不,歡迎指點!?
二:?
RANSAC的這個算法,請用opencv庫中的cvFindHomography,不要用cvFindFundamentalMat,因為這兩個是不一樣的,字面上簡單看一個是找到單應性矩陣,一個是找到基礎矩陣,這兩個到底有神馬區別????
《計算機視覺中的多視圖幾何》中這樣描述:
- 2D單應:一幅2D圖像中點集到另一幅圖像中點集的射影變換。
- 基本矩陣:它使一個隊所有的點都是的x`Fx=0成立的奇異矩陣F。這是一個3*3的奇異矩陣,而且秩為2,不可逆。它是2維圖像上的點通過對極線束約束的映射,是2維到1維的映射。
具體的大家可以看書或者百度,或者等以后我有空了,再具體描述。其實我也沒有太懂哈哈哈!還要再看看!慎言慎言!?
之前,我曾經自己編寫過一個RANSAC的算法啊,但是效果不是特別理想,由于RANSAC算法本身也比較耗時,各路大神們也都提出了很多改進方法,不知道opencv跟進這些改進算法沒有?推薦綜述型文章《A Universal Framework for Random?
Sample Consensus》,里面提到的prosac,lo-ransac速度提升很多!大家自行汲取,我想以后再寫一些關于RANSAC的博文。?
三:?
代碼中用到BFMatcher這個類,對象的參數有兩個BFMatcher::BFMatcher(int normType=NORM_L2, bool crossCheck=false )。第一個指的是距離,如果是sift或者surf描述的話,就選擇NORM_L2和NORM_L2;ORB,BRISK,FREAK這種二進制的描述子就用漢明距離,也就是NORM_HAMMING;NORM_HAMMING2是用作ORB的。第二個參數是是否crossCheck,最好選擇true,這個也經過我實驗驗證,選擇true相當于knn匹配中將k置1,也就是只返回最好的那一組匹配,而不是多個近鄰。(注意:如果要用knnmatch的話,這個crossCheck的參數就選擇false)。這種只返回最好的一組匹配的方式可以作為ratio test的一種替代選擇。實驗證明,如果選擇false,那么將會返回一對多匹配,和多對一的匹配,后期用ransac的效果可能非常不理想。crossCheck就是交叉匹配,剔除不好的匹配。有論文支撐,大家感興趣可以去搜。
https://blog.csdn.net/luoshixian099/article/details/50217655
https://blog.csdn.net/lcb_coconut/article/details/52145293?locationNum=4
隨機抽樣一致性算法(RANSAC) - Wuya - 博客園
總結
以上是生活随笔為你收集整理的SLAM之特征匹配(一)————RANSAC-------OpenCV中findFundamentalMat函数使用的模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 哈利波特魔法觉醒社团活动怎么玩?
- 下一篇: 黄山风景区日落时间