1. 程式人生 > >Opencv249和Opencv3.0以上的 SolvePnp函式詳解(附帶程式、算例)

Opencv249和Opencv3.0以上的 SolvePnp函式詳解(附帶程式、算例)

最近要做一個演算法,用到了位姿估計。位姿估計的使用範圍非常廣泛。主要解決的問題為:在給出2D-3D若干點對以及相片的內參資訊,如何求得相機中心在世界座標系下的座標以及相機的方向(旋轉矩陣)。為此筆者做了大量研究,看了許多主流的文章,也是用了許多相關的函式庫。主要有OpenMVG、OpenGV、OpenCV這三種。這三個庫雖然都集成了EPnp、Upnp、P3P等多種演算法,但實際差別還是很大。這一篇部落格主要對opencv中的SolvePnp演算法做一個總結以及各類實驗。

PnP的具體原理我就不過多解釋了,這裡放幾個連結供大家學習:

首先是Opencv的官方文件:https://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html#solvepnp

然後是一篇解釋Pnp的很有用的一篇文章:http://blog.csdn.net/cocoaqin/article/details/77841261

然後是Epnp的英文論文:https://pdfs.semanticscholar.org/6ed0/083ff42ac966a6f37710e0b5555b98fd7565.pdf

論文記得用谷歌開啟。

我們就直接上實驗吧。先看原始資料

333.965515 110.721138 45.893448 164.000000  1909.000000
327.870117 110.772079 45.835598 2578.000000 1970.000000
327.908630 115.816376 43.036915 2858.000000 3087.000000
333.731628 115.862755 43.031918 95.000000 3051.000000
330.019196 110.630211 45.871151 1727.000000 1942.000000
330.043457 115.823494 43.027847 1855.000000 3077.000000
331.949371 115.881104 43.020267 943.000000 3072.000000
331.943970 110.598534 45.909332 962.000000 1926.000000

沒錯,用空格分開。前三列依次為x,y,z座標,後兩列為影象的畫素座標。


然後我們看看SovePnp的原始碼都需要些什麼

我們發現除了函式本身定義之外上面還有許多引數解釋。嗯,Opencv果然是專業。

然後我們再看Opencv原始碼呼叫:

bool solvePnP( InputArray _opoints, InputArray _ipoints,
               InputArray _cameraMatrix, InputArray _distCoeffs,
               OutputArray _rvec, OutputArray _tvec, bool useExtrinsicGuess, int flags )
{
    CV_INSTRUMENT_REGION()

    Mat opoints = _opoints.getMat(), ipoints = _ipoints.getMat();
    int npoints = std::max(opoints.checkVector(3, CV_32F), opoints.checkVector(3, CV_64F));
    CV_Assert( npoints >= 0 && npoints == std::max(ipoints.checkVector(2, CV_32F), ipoints.checkVector(2, CV_64F)) );

    Mat rvec, tvec;
    if( flags != SOLVEPNP_ITERATIVE )
        useExtrinsicGuess = false;

    if( useExtrinsicGuess )
    {
        int rtype = _rvec.type(), ttype = _tvec.type();
        Size rsize = _rvec.size(), tsize = _tvec.size();
        CV_Assert( (rtype == CV_32F || rtype == CV_64F) &&
                   (ttype == CV_32F || ttype == CV_64F) );
        CV_Assert( (rsize == Size(1, 3) || rsize == Size(3, 1)) &&
                   (tsize == Size(1, 3) || tsize == Size(3, 1)) );
    }
    else
    {
        _rvec.create(3, 1, CV_64F);
        _tvec.create(3, 1, CV_64F);
    }
    rvec = _rvec.getMat();
    tvec = _tvec.getMat();

    Mat cameraMatrix0 = _cameraMatrix.getMat();
    Mat distCoeffs0 = _distCoeffs.getMat();
    Mat cameraMatrix = Mat_<double>(cameraMatrix0);
    Mat distCoeffs = Mat_<double>(distCoeffs0);
    bool result = false;

    if (flags == SOLVEPNP_EPNP || flags == SOLVEPNP_DLS || flags == SOLVEPNP_UPNP)
    {
        Mat undistortedPoints;
        undistortPoints(ipoints, undistortedPoints, cameraMatrix, distCoeffs);
        epnp PnP(cameraMatrix, opoints, undistortedPoints);

        Mat R;
        PnP.compute_pose(R, tvec);
        Rodrigues(R, rvec);
        result = true;
    }
    else if (flags == SOLVEPNP_P3P)
    {
        CV_Assert( npoints == 4);
        Mat undistortedPoints;
        undistortPoints(ipoints, undistortedPoints, cameraMatrix, distCoeffs);
        p3p P3Psolver(cameraMatrix);

        Mat R;
        result = P3Psolver.solve(R, tvec, opoints, undistortedPoints);
        if (result)
            Rodrigues(R, rvec);
    }
    else if (flags == SOLVEPNP_ITERATIVE)
    {
        CvMat c_objectPoints = opoints, c_imagePoints = ipoints;
        CvMat c_cameraMatrix = cameraMatrix, c_distCoeffs = distCoeffs;
        CvMat c_rvec = rvec, c_tvec = tvec;
        cvFindExtrinsicCameraParams2(&c_objectPoints, &c_imagePoints, &c_cameraMatrix,
                                     c_distCoeffs.rows*c_distCoeffs.cols ? &c_distCoeffs : 0,
                                     &c_rvec, &c_tvec, useExtrinsicGuess );
        result = true;
    }
    else
        CV_Error(CV_StsBadArg, "The flags argument must be one of SOLVEPNP_ITERATIVE, SOLVEPNP_P3P, SOLVEPNP_EPNP or SOLVEPNP_DLS");
    return result;
}
我們發現Epnp、Upnp以及DLS呼叫的是同一個介面。喂喂,這也太敷衍了吧。。。按理說應該是不一樣才對。也許以後會改吧。

然後P3P演算法要求輸入的控制點個數只能是4對。

好,沒問題了,我們準備好影象,然後開始呼叫。

我們首先構造好K矩陣,fx、fy都是以畫素為單位的焦距。我們必須事先知道焦距,或者能從Exif中解析出焦距資訊。好,我們直接上核心程式碼。至於貼圖函式

void MappingCloud(VertexPositionColor*&CloudPtr, int PointNum,cv::Mat IMG, cv::Mat K, cv::Mat nin_R, cv::Mat T, bool ifout , std::string OutColorCloudFileName)
{
cout << "Coloring Cloud..." << endl;
std::ofstream CloudOuter;
if (ifout == true)
CloudOuter.open(OutColorCloudFileName);
double reProjection = 0;
for (int i = 0; i < PointNum; i++)
{
//計算3D點雲在影像上的投影位置(畫素)
cv::Mat SingleP(cv::Matx31d(//這裡必須是Matx31d,如果是f則會報錯。opencv還真是嬌氣
CloudPtr[i].x,
CloudPtr[i].y,
CloudPtr[i].z));
cv::Mat change = K*(nin_R*SingleP + T);
change /= change.at<double>(2, 0);
//cout << change.t() << endl;
int xPixel = cvRound(change.at<double>(0, 0));
int yPixel = cvRound(change.at<double>(1, 0));
//取得顏色
uchar* data = IMG.ptr<uchar>(yPixel);
int Blue =data[xPixel * 3+0]; //第row行的第col個畫素點的第一個通道值 Blue
int Green = data[xPixel * 3 + 1]; // Green
int Red = data[xPixel * 3 + 2]; // Red
//顏色賦值
CloudPtr[i].R = Red;
CloudPtr[i].G = Green;
CloudPtr[i].B = Blue;
if (ifout == true)
CloudOuter << CloudPtr[i].x << " " << CloudPtr[i].y << " " << CloudPtr[i].z << " " << CloudPtr[i].R << " " << CloudPtr[i].G << " " << CloudPtr[i].B << endl;
}
if (ifout == true)
CloudOuter.close();
cout << "done!" << endl;
}
std::string intToStdtring(int Num)
{
std::stringstream strStream;
strStream << Num;
std::string s = std::string(strStream.str());
return s;
}
void OpencvPnPTest()//測試opencv的Pnp演算法
{
	std::ifstream reader(".\\TextureMappingData\\楊芳8點座標.txt");
	if (!reader)
	{
		std::cout << "開啟錯誤!";
		system("pause");
		exit(0);
	}
	string imgName = std::string(".\\TextureMappingData\\IMG_0828.JPG");
	cv::Mat IMG = cv::imread(".\\TextureMappingData\\IMG_0828.JPG");
	double IMGW = IMG.cols;//楊芳8點座標.txt
	double IMGH = IMG.rows;
	double RealWidth = 35.9;
	double CCDWidth = RealWidth / (IMGW >= IMGH ? IMGW : IMGH);//一定要取較長的那條邊
	double f = 100.0;
	double fpixel = f / CCDWidth;
	cv::Mat K_intrinsic(cv::Matx33d(
		fpixel, 0, IMGH / 2.0,
		0, fpixel, IMGW / 2.0,
		0, 0, 1));
	int UsePointCont = 5;
	vector<cv::Point3f> ConP3DVec;
	vector<cv::Point2f> ConP2DVec;
	cv::Point3f ConP3D;
	cv::Point2f ConP2D;
	cout << "AllPoints: " << endl;
	while (reader >> ConP3D.x)
	{
		reader >> ConP3D.y;
		reader >> ConP3D.z;
		reader >> ConP2D.x;
		reader >> ConP2D.y;
		ConP3DVec.push_back(ConP3D);
		ConP2DVec.push_back(ConP2D);
		cout << setprecision(10)
			<< ConP3D.x << " " << ConP3D.y << " " << ConP3D.z << " "
			<< ConP2D.x << " " << ConP2D.y << endl;
		if (ConP3DVec.size() == UsePointCont)
			break;
	}
	reader.close();
	cout << "BaseInformation: " << endl;
	cout << "imgName: " << imgName<< endl;
	cout << "width: " << IMGW << endl;
	cout << "height: " << IMGH << endl;
	cout << "UsePointCont: " << UsePointCont << endl;
	cout << "RealWidth: " << RealWidth << endl;
	cout << "camerapixel: " << CCDWidth << endl;
	cout << "f: " << f << endl;
	cout << "fpixel: " << fpixel << endl;
	cout << "KMatrix: " << K_intrinsic << endl;

	//對於後母戊鼎opencv原始幾種解法報告:
	//對於8個點的情況:EPnP表現良好,DLS表現良好,EPnp與Upnp呼叫的函式介面相同
	//對於4個點的情況:P3P表現良好,EPnp表現良好,然而P3P實際輸入點數為4個,那麼最後一個點用於檢核。
	//Epnp在4點的時候表現時好時壞,和控制點的選取狀況相關。倘若增加為5個點,則精度有著明顯提升
	//EPnP在4點的情況下貼圖完全錯誤,比後方交會的效果還要更加扭曲
	//4點DLS的方法取得了和EPnp同樣的貼圖結果,從結果來看非常扭曲
	//4點的ITERATOR方法取得了很好的貼圖效果,甚至比P3P還要好,因為P3P只用三個點參與計算
	//也就是說,在控制點對數量較少的情況下(4點),只有P3P可以給出正確結果
	//增加一對控制點(變為5對)之後,Epnp的魯棒性迅速提升,可以得到正確結果,DLS也同樣取得了正確結果
	//增加到5對控制點後P3P失效,因為只讓用4個點,不多不少.
	//同樣的,5點的ITERATOR方法結果正確。
	//----------結論:使用多於4點的EPnP最為穩妥.所求得的矩陣為計算機視覺矩陣
	//int flag = cv::SOLVEPNP_EPNP;std::string OutCloudPath = std::string(".\\Output\\EPNP_") + intToStdtring(UsePointCont) + std::string("Point_HMwuding.txt");
	//int flag = cv::SOLVEPNP_DLS;std::string OutCloudPath = std::string(".\\Output\\DLS_") + intToStdtring(UsePointCont) + std::string("Point_HMwuding.txt");  
	//int flag = cv::SOLVEPNP_P3P;std::string OutCloudPath = std::string(".\\Output\\P3P_") + intToStdtring(UsePointCont) + std::string("Point_HMwuding.txt");
	int flag = cv::SOLVEPNP_ITERATIVE; std::string OutCloudPath = std::string(".\\Output\\ITERATIVE_") + intToStdtring(UsePointCont) + std::string("Point_HMwuding.txt");
	cout << endl << "Soving Pnp Using Method" << flag<<"..."<< endl;
	cv::Mat Rod_r ,TransMatrix ,RotationR;
	bool success = cv::solvePnP(ConP3DVec, ConP2DVec, K_intrinsic, cv::noArray(), Rod_r, TransMatrix,false, flag);
	Rodrigues(Rod_r, RotationR);//將旋轉向量轉換為羅德里格旋轉矩陣
	cout << "r:" << endl << Rod_r << endl;
	cout << "R:" << endl << RotationR << endl;
	cout << "T:" << endl << TransMatrix << endl;
	cout << "C(Camera center:):" << endl << -RotationR.inv()*TransMatrix << endl;//這個C果然是相機中心,十分準確
	Comput_Reprojection(ConP3DVec, ConP2DVec, K_intrinsic, RotationR, TransMatrix);

	//=====下面讀取點雲並進行紋理對映
	cout << "Reading Cloud..." << endl;
	std::string CloudPath = std::string(".\\PointCloud\\HMwuding.txt");
	ScarletCloudIO CloudReader(CloudPath);
	CloudReader.Read();
	CloudReader.PrintStationStatus();
	VertexPositionColor *CloudPtr = CloudReader.PtCloud().PointCloud;
	int CloudNum = CloudReader.PtCloud().PointNum;
	MappingCloud(CloudPtr,CloudNum, IMG, K_intrinsic,//點雲、數量、內參
		RotationR, TransMatrix,true, OutCloudPath);//旋轉、平移(並非是相機中心)、點雲最終的放置路徑

	//下面可以繼續研究旋轉平移以及比例縮放的影響
}

實驗的報告以及調整的引數都已經在註釋中寫清楚了。下面我給出這幾個貼圖效果。我們採用的命名規則為:方法_控制點個數

首先是原始


------------------DLS_4Point_HMwuding:(錯誤)


------------------DLS_5Point_HMwuding:(正確)


-----------------------EPNP_4Point_HMwuding.txt(錯誤)


-----------------------EPNP_5Point_HMwuding.txt(正確)


---------------------ITERATIVE_4Point_HMwuding.txt(正確)


-----------------P3P_4Point_HMwuding.txt(正確)


基本上就這麼多了

相關推薦

Opencv249Opencv3.0以上SolvePnp函式附帶程式

最近要做一個演算法,用到了位姿估計。位姿估計的使用範圍非常廣泛。主要解決的問題為:在給出2D-3D若干點對以及相片的內參資訊,如何求得相機中心在世界座標系下的座標以及相機的方向(旋轉矩陣)。為此筆者做了大量研究,看了許多主流的文章,也是用了許多相關的函式庫。主要有OpenM

STM32庫函式----外部中斷/事件控制器 EXTI

1.void EXTI_DeInit  (void) 函式解釋:將EXTI外設暫存器重置為默註釋。RCC_APB2PeriphResetCmd引數中沒有EXTI外設的的巨集,該外設重置採取的是直接向暫存器賦預設值的操作。 例子:EXTI_DeInit ( );  

linux中fork函式原創!!例項講解

    所以打印出結果:    0 parent 2043 3224 3225    0 child  3224 3225    0    第二步:假設父程序p3224先執行,當進入下一個迴圈時,i=1,接著執行fork,系統中又新增一個程序p3226,對於此時的父程序,p2043->p3224(當前程

socket函式 有了新的認識

       我們先來看一下socket函式的原型: SOCKET PASCAL FAR socket (int af, int type, int protocol);       典型的呼叫方式為:  unsigned int sockSrv = socket(AF_I

[轉載]SourceTree安裝教程GitLab配置附帶報錯解決辦法

連結:http://www.cnblogs.com/Lam7/p/6004737.html 補充:1.沒有vpn的支援,atlassian只註冊不了的,就算打得開頁面也沒辦法進行人機驗證。 2.安裝git,直接進行下一步,中間可以不用操作 3.安裝sour

Feature Selection附帶ReliefRelief-FLVM

Feature Selection詳解 第二十六次寫部落格,本人數學基礎不是太好,如果有幸能得到讀者指正,感激不盡,希望能借此機會向大家學習。這一篇承接上一篇《Feature Selection詳解(附帶Relief、Relief-F、LVM詳解)(一)》的內容,仍然是針對特

Feature Selection附帶ReliefRelief-FLVM

Feature Selection詳解 第二十五次寫部落格,本人數學基礎不是太好,如果有幸能得到讀者指正,感激不盡,希望能借此機會向大家學習。這一篇主要是針對特徵選擇問題的幾種常見方法進行闡述,並介紹其中幾種比較經典的特徵選擇演算法(Relief、Relief-F、LVM)。

iCheck表單美化外掛使用方法含引數事件等

iCheck 特色: 1、在不同瀏覽器(包括ie6+)和裝置上都有相同的表現 — 包括 桌面和移動裝置 2、支援觸控裝置 — iOS、Android、BlackBerry、Windows Phone等系統 4、方便定製 — 用HTML 和 CSS 即可為其設定樣式 (多套面板) 5、體積小巧 — gzi

asp.net打包過程WEB程式也能打包

{  base.Install(stateSaver);  StreamWriter sw2=System.IO.File.CreateText(Context.Parameters["des"].ToString()+"WebSystem.url"); //Context.Parameters["des"]

Hadoop RCFile儲存格式原始碼分析程式碼示例

RCFile RCFile全稱Record Columnar File,列式記錄檔案,是一種類似於SequenceFile的鍵值對(Key/Value Pairs)資料檔案。 關鍵詞:Record、Columnar、Key、Value。 RCFile的優勢在哪

elasticsearch系列四:搜尋搜尋APIQuery DSL

{  "took": 60,  "timed_out": false,  "_shards": {    "total": 5,    "successful": 5,    "skipped": 0,    "failed": 0  },  "hits": {    "total": 1000,    "m

mallocfree函式轉載只是為了查閱方便,若侵權立刪

malloc和free函式詳解   本文介紹malloc和free函式的內容。   在C中,對記憶體的管理是相當重要。下面開始介紹這兩個函式:     一、malloc()和free()的基本概念以及基本用法: 1、函式原型及說明: void *malloc(lon

%date~0,4% %time~0,2%等用法

在windows中,有個原始並且功能強大的批處理,好像是被人遺忘了,比如博主最近在一個專案中就用到它,非常好用。今天就和博主一直來看看用批處理生動生成每日的資料夾。 為了能正確地生成每天的日期資料夾,請先將本機時間的短日期格式設定為yyyy-MM-dd。   然後就開始寫bat批處理檔案了,新

C語言是什麼vc6.0的安裝步驟及第一個c程式

從今天開始,我每天會分享一些關於計算機的知識,包括C語言、Python、資料庫、網路、Linux、網路安全等相關知識;今天我們就以C語言來開始我們的交流、學習吧。 C語言是一門通用計算機程式語言,廣泛應用於底層開發。C語言的設計目標是提供一種能以簡易的方式編譯、

oracle之percent_rank() over()函式PERCENTILE_CONT() within group()over()函式

建立一個臨時表 create table EMP ( EMPNO NUMBER(4) not null, ENAME VARCHAR2(10), JOB VARCHAR2(9), MGR NUMBER(4), HIREDATE DATE, SAL

Android6.0 影象合成過程 doComposition函式

上篇部落格分析到setUpHWComposer函式,這裡我們繼續分析影象合成的過程從doComposition函式開始,以及在這過程中解答一些上篇部落格提出的疑問。 一、doComposition合成圖層 doComposition這個函式就是合成所有層的影象 void

ajax error 函式jquery

文章來自:原始碼線上https://www.shengli.me/jquery/186.html error函式 返回的引數有三個: 1.5版本以後返回的是jqXHR物件,該物件不僅包括XMLHttpRequest物件,還包含其他更多詳細屬性和資訊:   這裡主

H 264的兩個概念 DC係數AC係數 MV預測過程附圖

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

python 學習彙總27:itertools函式 tcy

itertools函式 2018/11/14 2.1.建立新iter: count(start=0, step=1)#無限迴圈數;按Ctrl + C退出 # 返回均勻間隔值無限流;通常用作map()生成連續資料點的引數。此外,用於zip()新增序列號 g = itertools.count

Nginx反向代理配置正向代理反向代理負載均衡原理Nginx反向代理原理配置講解

nginx概述 nginx是一款自由的、開源的、高效能的HTTP伺服器和反向代理伺服器;同時也是一個IMAP、POP3、SMTP代理伺服器;nginx可以作為一個HTTP伺服器進行網站的釋出處理,另外nginx可以作為反向代理進行負載均衡的實現。 Nginx是一款開原始碼的高效能HT