1. 程式人生 > >opencv-相機標定步驟、評估標定誤差以及標定之後影象座標到世界座標的轉換

opencv-相機標定步驟、評估標定誤差以及標定之後影象座標到世界座標的轉換

前一段時間,研究了下相機標定。關於opencv相機標定,包括標定完後,世界座標到 影象座標的轉換,以評估影象的標定誤差,這些網上有很多資源和原始碼。

可是,相機標定完之後,我們想要的是,知道了影象座標,想要得到它的世界座標,或者我們已知影象上兩個點之間的畫素距離,現在我們想知道兩個點之間的實際距離。

樓主在網上搜了很多資源,問了很多人,都沒有相關的程式碼,可以得到這樣的結論:opencv沒有提供現成的函式,滿足從影象座標到世界座標的轉換。

所以,我們最想要的這一步,是需要自己寫的。(如果我理解的不對,希望看到這篇博文的人,能夠批評指正)

相機標定的主要思路為:

一、獲取十幾張不同角度拍攝的圖片,角點檢測,得到每個角點的座標;

二、分別定義十幾張照片中,世界座標系下的角點座標,一般x、y為等間距,z為0 ;

三、開始標定,主要函式為calibrateCamera;

四、得到了相機內參和畸變係數,這是標定完後相機的屬性,還會得到外參,外參代表著每張圖片所在的平面;

五、opencv提供了世界座標到影象座標的轉換函式,主要用來評估標定的誤差;

六、我們最想要的,根據影象座標到世界座標的轉換,本質上就是矩陣的運算,需要自己寫;

下面貼出程式碼,開發環境opencv2.4.9+vs2013

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/calib3d/calib3d.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <fstream>

using namespace std;
using namespace cv;

const int imageWidth = 1600;                             //攝像頭的解析度  
const int imageHeight = 1200;
const int boardWidth = 39;                               //橫向的角點數目  
const int boardHeight = 39;                              //縱向的角點資料  
const int boardCorner = boardWidth * boardHeight;       //總的角點資料  
const int frameNumber =7;                             //相機標定時需要採用的影象幀數  
const int squareSize = 10;                              //標定板黑白格子的大小 單位mm  
const Size boardSize = Size(boardWidth, boardHeight);   //  

Mat intrinsic;                                          //相機內參數  
Mat distortion_coeff;                                   //相機畸變引數  
vector<Mat> rvecs;                                        //旋轉向量  
vector<Mat> tvecs;                                        //平移向量  
vector<vector<Point2f>> corners;                        //各個影象找到的角點的集合 和objRealPoint 一一對應  
vector<vector<Point3f>> objRealPoint;                   //各副影象的角點的實際物理座標集合  


vector<Point2f> corner;                                   //某一副影象找到的角點  

Mat rgbImage, grayImage;

/*計算標定板上模組的實際物理座標*/
void calRealPoint(vector<vector<Point3f>>& obj, int boardwidth, int boardheight, int imgNumber, int squaresize)
{
	//  Mat imgpoint(boardheight, boardwidth, CV_32FC3,Scalar(0,0,0));  
	vector<Point3f> imgpoint;
	for (int rowIndex = 0; rowIndex < boardheight; rowIndex++)
	{
		for (int colIndex = 0; colIndex < boardwidth; colIndex++)
		{
			//  imgpoint.at<Vec3f>(rowIndex, colIndex) = Vec3f(rowIndex * squaresize, colIndex*squaresize, 0);  
			imgpoint.push_back(Point3f(colIndex * squaresize, rowIndex * squaresize, 0));
		}
	}
	for (int imgIndex = 0; imgIndex < imgNumber; imgIndex++)
	{
		obj.push_back(imgpoint);
	}
}

/*設定相機的初始引數 也可以不估計*/

void CalibrationEvaluate(void)//標定結束後進行評價
{
	double err=0;
	double total_err=0;
	//calibrateCamera(objRealPoint, corners, Size(imageWidth, imageHeight), intrinsic, distortion_coeff, rvecs, tvecs, 0);
	cout << "每幅影象的定標誤差:" << endl;
	for (int i = 0; i < corners.size(); i++)
	{
		vector<Point2f> image_points2;
		vector<Point3f> tempPointSet = objRealPoint[i];
		projectPoints(tempPointSet, rvecs[i], tvecs[i], intrinsic, distortion_coeff, image_points2);




		vector<Point2f> tempImagePoint = corners[i];
		Mat tempImagePointMat = Mat(1, tempImagePoint.size(), CV_32FC2);
		Mat image_points2Mat = Mat(1, image_points2.size(), CV_32FC2);
		for (int j = 0; j < tempImagePoint.size(); j++)
		{
			image_points2Mat.at<Vec2f>(0, j) = Vec2f(image_points2[j].x, image_points2[j].y);
			tempImagePointMat.at<Vec2f>(0, j) = Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);
		}
		err = norm(image_points2Mat, tempImagePointMat, NORM_L2);
		total_err = err + total_err;
		cout << "第" << i + 1 << "幅影象的平均誤差:" << err << "畫素" << endl;
	}
	cout << "總體平均誤差:" << total_err / (corners.size() + 1) << "畫素" << endl;
}

void guessCameraParam(void)
{
	/*分配記憶體*/
	intrinsic.create(3, 3, CV_64FC1);
	distortion_coeff.create(5, 1, CV_64FC1);

	/*
	fx 0 cx
	0 fy cy
	0 0  1
	*/
	intrinsic.at<double>(0, 0) = 256.8093262;   //fx         
	intrinsic.at<double>(0, 2) = 160.2826538;   //cx  
	intrinsic.at<double>(1, 1) = 254.7511139;   //fy  
	intrinsic.at<double>(1, 2) = 127.6264572;   //cy  

	intrinsic.at<double>(0, 1) = 0;
	intrinsic.at<double>(1, 0) = 0;
	intrinsic.at<double>(2, 0) = 0;
	intrinsic.at<double>(2, 1) = 0;
	intrinsic.at<double>(2, 2) = 1;

	/*
	k1 k2 p1 p2 p3
	*/
	distortion_coeff.at<double>(0, 0) = -0.193740;  //k1  
	distortion_coeff.at<double>(1, 0) = -0.378588;  //k2  
	distortion_coeff.at<double>(2, 0) = 0.028980;   //p1  
	distortion_coeff.at<double>(3, 0) = 0.008136;   //p2  
	distortion_coeff.at<double>(4, 0) = 0;          //p3  
}

void outputCameraParam(void)
{
	/*儲存資料*/
	//cvSave("cameraMatrix.xml", &intrinsic);  
	//cvSave("cameraDistoration.xml", &distortion_coeff);  
	//cvSave("rotatoVector.xml", &rvecs);  
	//cvSave("translationVector.xml", &tvecs);  
	/*輸出資料*/
	cout << "fx :" << intrinsic.at<double>(0, 0) << endl << "fy :" << intrinsic.at<double>(1, 1) << endl;
	cout << "cx :" << intrinsic.at<double>(0, 2) << endl << "cy :" << intrinsic.at<double>(1, 2) << endl;

	cout << "k1 :" << distortion_coeff.at<double>(0, 0) << endl;
	cout << "k2 :" << distortion_coeff.at<double>(1, 0) << endl;
	cout << "p1 :" << distortion_coeff.at<double>(2, 0) << endl;
	cout << "p2 :" << distortion_coeff.at<double>(3, 0) << endl;
	cout << "p3 :" << distortion_coeff.at<double>(4, 0) << endl;
}

//int _tmain(int argc, _TCHAR* argv[])
int main()
{
	Mat img;
	int goodFrameCount = 0;
	namedWindow("chessboard");
	cout << "按Q退出 ..." << endl;
	while (goodFrameCount < frameNumber)
	{
		
		char filename[100];
		sprintf_s(filename, "chao%d.bmp", goodFrameCount);
		//sprintf_s(filename, "chess%d.jpg", goodFrameCount);
		goodFrameCount++;
		
		rgbImage = imread(filename, 1);
		cvtColor(rgbImage, grayImage, CV_BGR2GRAY);
		imshow("Camera", grayImage);

		bool isFind = findChessboardCorners(rgbImage, boardSize, corner, 0);
		//bool isFind = findChessboardCorners(rgbImage, boardSize, corner, CV_CALIB_CB_NORMALIZE_IMAGE);
		if (isFind == true) //所有角點都被找到 說明這幅影象是可行的  
		{
			/*
			Size(5,5) 搜尋視窗的一半大小
			Size(-1,-1) 死區的一半尺寸
			TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 20, 0.1)迭代終止條件
			*/
			cornerSubPix(grayImage, corner, Size(5, 5), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 20, 0.1));
			drawChessboardCorners(rgbImage, boardSize, corner, isFind);
			imshow("chessboard", rgbImage);
			corners.push_back(corner);
			//string filename = "res\\image\\calibration";  
			//filename += goodFrameCount + ".jpg";  
			//cvSaveImage(filename.c_str(), &IplImage(rgbImage));       //把合格的圖片儲存起來  

			cout << "The image is good" << endl;
		}
		else
		{
			cout << "The image is bad please try again" << endl;
		}
		//  cout << "Press any key to continue..." << endl;  
		//  waitKey(0);  

		if (waitKey(10) == 'q')
		{
			break;
		}
		//  imshow("chessboard", rgbImage);  
	}

	/*
	影象採集完畢 接下來開始攝像頭的校正
	calibrateCamera()
	輸入引數 objectPoints  角點的實際物理座標
	imagePoints   角點的影象座標
	imageSize     影象的大小
	輸出引數
	cameraMatrix  相機的內參矩陣
	distCoeffs    相機的畸變引數
	rvecs         旋轉向量(外引數)
	tvecs         平移向量(外引數)
	*/

	/*設定實際初始引數 根據calibrateCamera來 如果flag = 0 也可以不進行設定*/
	guessCameraParam();
	cout << "guess successful" << endl;
	/*計算實際的校正點的三維座標*/
	calRealPoint(objRealPoint, boardWidth, boardHeight, frameNumber, squareSize);
	cout << "cal real successful" << endl;
	/*標定攝像頭*/
	calibrateCamera(objRealPoint, corners, Size(imageWidth, imageHeight), intrinsic, distortion_coeff, rvecs, tvecs, 0);
	cout << "calibration successful" << endl;
	/*儲存並輸出引數*/
	outputCameraParam();
	CalibrationEvaluate();
	cout << "out successful" << endl;

	/*顯示畸變校正效果*/
	Mat cImage;
	undistort(rgbImage, cImage, intrinsic, distortion_coeff);
	imshow("Corret Image", cImage);
	cout << "Correct Image" << endl;
	cout << "Wait for Key" << endl;

	waitKey(0);
	system("pause");
	return 0;
}

以上只貼出了部分程式碼,完整的C++程式碼,可到 Hust平凡之路相機標定工程 下載。


相關推薦

opencv-相機標定步驟評估標定誤差以及標定之後影象座標世界座標轉換

前一段時間,研究了下相機標定。關於opencv相機標定,包括標定完後,世界座標到 影象座標的轉換,以評估影象的標定誤差,這些網上有很多資源和原始碼。 可是,相機標定完之後,我們想要的是,知道了影象座標,想要得到它的世界座標,或者我們已知影象上兩個點之間的畫素距離,現在我們想

相機標定 matlab opencv ROS三種方法標定步驟(1)

 一 、 理解攝像機模型,網上有很多講解的十分詳細,在這裡我只是記錄我的整合出來的資料和我的部分理解         計算機視覺領域中常見的三個座標系:影象座標系,相機座標系,世界座標系,實際上就是要用矩陣來表         示各個座標系下的轉換,首先在影象座標系下與相機座標系的關係            

張正友標定Opencv實現標定流程以及影象座標轉為世界座標

    使用相機以前,首先要進行相機標定,其原因是我們通過標定知道相機的內外參、得到內外參矩陣後可對相機拍攝的照片進行矯正,可以得到畸變較小的影象。而相機標定的輸入就是相機所拍的多幀圖片的角點座標,以及標定板影象上所有角點的空間座標(一般Z軸假設為Z=0)。相機標定後的輸出就

OpenCV相機標定及距離估計(單目)

相機標定基本知識 對於針孔攝像機模型,一幅檢視是通過透視變換將三維空間中的點投影到影象平面。投影公式如下: 或者 這裡(X, Y, Z)是一個點的世界座標,(u, v)是點投影在影象平面的座標,以畫素為單位。A被稱作攝像機矩陣,或者內參數矩陣。(cx, cy)是基

opencv相機標定的一些函式講解

畸變矯正是上一篇博文的遺留問題,當畸變係數和內外引數矩陣標定完成後,就應該進行畸變的矯正,以達到消除畸變的目的,此其一。 在該系列第一部分的博文中介紹的立體成像原理中提到,要通過兩幅影象估計物點的深度資訊,就必須在兩幅影象中準確的匹配到同一物點,這樣才能根據該物點在兩幅影象中的

opencv---相機標定

參考文章 座標系之間的關係 計算機視覺領域中常見的三個座標系:影象座標系,相機座標系,世界座標系 影象座標系—理想影象座標系和實際影象座標系 上圖中: 實際的影象座標系原點為 二者之間的關係式為(1)(2),(1)(2)也可以用矩陣(3)

OpenCV——相機標定

1、四個座標系 世界座標系,相機座標系,影象物理座標系,畫素座標系 2、座標系之間的轉換 2.1、世界座標系——相機座標系 假設世界座標系中點座標為[x1,y1,z1],對應的相機座標系中的點座標為[x,y,z],世界座標系轉換至相機座標系遵循如下推

opencv 相機標定與矯正

由攝像機拍取並進行標定與矯正 步驟: 1.確定基礎設定 //設定1 影象的尺寸 const int nImageW = 2592; const int nImageH

分析時序資料的三步驟:使資料平穩時序模型評估

作者: Chris St. Jeor & Sean Ankenbruck,Zencos 貪心科技編譯 時間序列預測是一個易於使用,成本較低的方案,它可以提供強大的解決問題能力。這篇文章將介紹建立一個質量模型的三個基本步驟。 這篇文

CentOS服務器上搭建Gitlab安裝步驟中文漢化詳細步驟日常管理以及異常故障排查

機器 start 自己的 sta sendmai 內網 eight 故障 /tmp 一, 服務器快速搭建gitlab方法 可以參考gitlab中文社區 的教程centos7安裝gitlab:https://www.gitlab.cc/downloads/#centos7ce

monkey實戰--測試步驟常用參數常規monkey命令

dump tmg 位置 安裝ad 解包 選擇 misc 日誌分析 cti 簡要步驟:adb devices---了解包名--adb shell monkey -p 包名 -v 運行次數(多個參數的組合形成不同的用例以求最大的覆蓋)--當崩潰或無響應時分析monkey日誌

【轉】monkey實戰--測試步驟常用參數常規monkey命令

固定 內存 err 完成後 get 都是 指點 cti 輸入 簡要步驟:adb devices---了解包名--adb shell monkey -p 包名 -v 運行次數(多個參數的組合形成不同的用例以求最大的覆蓋)--當崩潰或無響應時分析monkey日誌 常規monk

【單鏡頭反光相機】影調反差光比寬容度;光質(硬光軟光)硬調軟調高調低調中間調

部分 彩色 clas 模糊 光源 fff class 中間 blank 影調: 對攝影作品而言,“影調”,又稱為照片的基調或調子。指畫面的明暗層次、虛實對比和色彩的色相明暗等之間的關系。通過這些關系,使欣賞者感到光的流動與變化。 攝影畫面中的線條、形狀、色彩等元素是由影

五十六建立Django專案步驟Django後臺管理

1、windows下在桌面的路徑(在自己指定的路徑shift+開啟命令視窗)或者 cd 路徑 進入虛擬環境(workon 名稱)--建立專案名稱 test1 2、進入根目錄test1: 3、建立分個應用(模組)例如自己定義名字--booktest  &

OpenCV中的cvRound()cvFloor() cvCeil()函式講解

功能:cvRound(), cvFloor(), cvCeil()函式講解。 函式cvRound,cvFloor,cvCeil 都是用一種舍入的方法將輸入浮點數轉換成整數: cvRound():返回跟引數最接近的整數值,即四捨五入; cvFl

Hibernate搭建步驟 Hibernate 操作流程

1.Hibernate搭建步驟     1> 導包     2> 建表     3> 建立實體(model)     4> 填寫Hibernate.cfg.

Linux原始碼安裝步驟greptar的使用

文章目錄 原始碼安裝的步驟 命令字分類 du -sh 統計目錄佔空間大小 wc 統計檔案內容 man 幫助手冊 ls 目錄 | wc -l grep 過濾 檔案內

FE之DR之線性降維:LDA&PCA演算法相關論文主要思路關鍵步驟程式碼實現等相關配圖之詳細攻略

FE之DR之LDA:LDA演算法相關論文、主要思路、關鍵步驟、程式碼實現等相關配圖之詳細攻略 LDA LDA演算法相關論文、主要思路 1、LDA的缺點:強依賴均值。以下是LDA搞不定的四種情況   LDA演算法關鍵步驟 1、LDA演算法推導 &n

ML之Clustering之普聚類演算法:普聚類演算法的相關論文主要思路關鍵步驟程式碼實現等相關配圖之詳細攻略

ML之Clustering之普聚類演算法:普聚類演算法的相關論文、主要思路、關鍵步驟、程式碼實現等相關配圖之詳細攻略   普聚類演算法的相關論文 1、論文推薦 Clustering by fast search and find of density peak.

OpenCV使用邊緣提取腐蝕輪廓進行車牌定位

採用OpenCV249利用邊緣檢測、輪廓檢測、腐蝕實現的車牌定位,詳細為: Mat srcImage=imread("image/000.jpg"); //imshow("a",srcImage); int i,j; int cPointR,cPointG,cPointB,