1. 程式人生 > >OpenCV 標定和畸變校正

OpenCV 標定和畸變校正

                                        <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-e2445db1a8.css">
                    <div class="htmledit_views">

1.攝像機成像原理

成像的過程實質上是4個座標系的轉換。首先空間中的一點由 世界座標系 轉換到 攝像機座標系 ,然後再將其投影到成像平面 (影象物理座標系 ) ,最後再將成像平面上的資料轉換到影象平面 (影象畫素座標系

) 。

 

下文對4個座標系的 變換做了詳細的解釋:

http://blog.csdn.net/humanking7/article/details/44756073

2.畸變模型

影象畫素座標系(uOv座標系) 下的無畸變座標 (U, V),經過 徑向畸變 和 切向畸變 後落在了uOv座標系 的 (Ud, Vd) 上。即就是說,真實影象 imgR 與 畸變影象 imgD 之間的關係為: imgR(U, V) = imgD(Ud, Vd)

OpenCV的document中介紹如下:

http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/calib3d/camera_calibration/camera_calibration.html#cameracalibrationopencv

所以標定的目標是就是確定這5個引數的值。

3.標定板

標定的最開始階段最需要的肯定是標定板。可以直接從opencv官網上能下載到: 

http://docs.opencv.org/2.4/_downloads/pattern.png

4.影象採集

儘量覆蓋攝像機的各個角度,多拍幾張照片(必須大於1張)

5.標定

OpenCV的例程來進行標定,在你的opencv目錄下  sources\samples\cpp\tutorial_code\calib3d\camera_calibration  有3個檔案 :

camera_calibration.cpp  VID5.xml  in_VID5.xml

第一個是標定程式的原始碼。  第二個是配置檔案,你可以更改標定圖片獲取的方式以及標定板的一些引數。  第三個裡面可以修改標定圖片序列的檔名。

程式碼:
  1. #include <iostream>
  2. #include <sstream>
  3. #include <time.h>
  4. #include <stdio.h>
  5. #include <opencv2/core/core.hpp>
  6. #include <opencv2/imgproc/imgproc.hpp>
  7. #include <opencv2/calib3d/calib3d.hpp>
  8. #include <opencv2/highgui/highgui.hpp>
  9. #ifndef _CRT_SECURE_NO_WARNINGS
  10. # define _CRT_SECURE_NO_WARNINGS
  11. #endif
  12. using namespace cv;
  13. using namespace std;
  14. static void help()
  15. {
  16. cout << “This is a camera calibration sample.” << endl
  17. << “Usage: calibration configurationFile” << endl
  18. << “Near the sample file you’ll find the configuration file, which has detailed help of “
  19. “how to edit it. It may be any OpenCV supported file format XML/YAML.” << endl;
  20. }
  21. class Settings
  22. {
  23. public:
  24. Settings() : goodInput(false) {}
  25. enum Pattern { NOT_EXISTING, CHESSBOARD, CIRCLES_GRID, ASYMMETRIC_CIRCLES_GRID };
  26. enum InputType {INVALID, CAMERA, VIDEO_FILE, IMAGE_LIST};
  27. void write(FileStorage& fs) const //Write serialization for this class
  28. {
  29. fs << “{“ << “BoardSize_Width” << boardSize.width
  30. << “BoardSize_Height” << boardSize.height
  31. << “Square_Size” << squareSize
  32. << “Calibrate_Pattern” << patternToUse
  33. << “Calibrate_NrOfFrameToUse” << nrFrames
  34. << “Calibrate_FixAspectRatio” << aspectRatio
  35. << “Calibrate_AssumeZeroTangentialDistortion” << calibZeroTangentDist
  36. << “Calibrate_FixPrincipalPointAtTheCenter” << calibFixPrincipalPoint
  37. << “Write_DetectedFeaturePoints” << bwritePoints
  38. << “Write_extrinsicParameters” << bwriteExtrinsics
  39. << “Write_outputFileName” << outputFileName
  40. << “Show_UndistortedImage” << showUndistorsed
  41. << “Input_FlipAroundHorizontalAxis” << flipVertical
  42. << “Input_Delay” << delay
  43. << “Input” << input
  44. << “}”;
  45. }
  46. void read(const FileNode& node) //Read serialization for this class
  47. {
  48. node[“BoardSize_Width” ] >> boardSize.width;
  49. node[“BoardSize_Height”] >> boardSize.height;
  50. node[“Calibrate_Pattern”] >> patternToUse;
  51. node[“Square_Size”] >> squareSize;
  52. node[“Calibrate_NrOfFrameToUse”] >> nrFrames;
  53. node[“Calibrate_FixAspectRatio”] >> aspectRatio;
  54. node[“Write_DetectedFeaturePoints”] >> bwritePoints;
  55. node[“Write_extrinsicParameters”] >> bwriteExtrinsics;
  56. node[“Write_outputFileName”] >> outputFileName;
  57. node[“Calibrate_AssumeZeroTangentialDistortion”] >> calibZeroTangentDist;
  58. node[“Calibrate_FixPrincipalPointAtTheCenter”] >> calibFixPrincipalPoint;
  59. node[“Input_FlipAroundHorizontalAxis”] >> flipVertical;
  60. node[“Show_UndistortedImage”] >> showUndistorsed;
  61. node[“Input”] >> input;
  62. node[“Input_Delay”] >> delay;
  63. interprate();
  64. }
  65. void interprate()
  66. {
  67. goodInput = true;
  68. if (boardSize.width <= 0 || boardSize.height <= 0)
  69. {
  70. cerr << “Invalid Board size: “ << boardSize.width << ” “ << boardSize.height << endl;
  71. goodInput = false;
  72. }
  73. if (squareSize <= 10e-6)
  74. {
  75. cerr << “Invalid square size “ << squareSize << endl;
  76. goodInput = false;
  77. }
  78. if (nrFrames <= 0)
  79. {
  80. cerr << “Invalid number of frames “ << nrFrames << endl;
  81. goodInput = false;
  82. }
  83. if (input.empty()) // Check for valid input
  84. inputType = INVALID;
  85. else
  86. {
  87. if (input[0] >= ‘0’ && input[0] <= ‘9’)
  88. {
  89. stringstream ss(input);
  90. ss >> cameraID;
  91. inputType = CAMERA;
  92. }
  93. else
  94. {
  95. if (readStringList(input, imageList))
  96. {
  97. inputType = IMAGE_LIST;
  98. nrFrames = (nrFrames < (int)imageList.size()) ? nrFrames : (int)imageList.size();
  99. }
  100. else
  101. inputType = VIDEO_FILE;
  102. }
  103. if (inputType == CAMERA)
  104. inputCapture.open(cameraID);
  105. if (inputType == VIDEO_FILE)
  106. inputCapture.open(input);
  107. if (inputType != IMAGE_LIST && !inputCapture.isOpened())
  108. inputType = INVALID;
  109. }
  110. if (inputType == INVALID)
  111. {
  112. cerr << ” Inexistent input: “ << input;
  113. goodInput = false;
  114. }
  115. flag = 0;
  116. if(calibFixPrincipalPoint) flag |= CV_CALIB_FIX_PRINCIPAL_POINT;
  117. if(calibZeroTangentDist) flag |= CV_CALIB_ZERO_TANGENT_DIST;
  118. if(aspectRatio) flag |= CV_CALIB_FIX_ASPECT_RATIO;
  119. calibrationPattern = NOT_EXISTING;
  120. if (!patternToUse.compare(“CHESSBOARD”)) calibrationPattern = CHESSBOARD;
  121. if (!patternToUse.compare(“CIRCLES_GRID”)) calibrationPattern = CIRCLES_GRID;
  122. if (!patternToUse.compare(“ASYMMETRIC_CIRCLES_GRID”)) calibrationPattern = ASYMMETRIC_CIRCLES_GRID;
  123. if (calibrationPattern == NOT_EXISTING)
  124. {
  125. cerr << ” Inexistent camera calibration mode: “ << patternToUse << endl;
  126. goodInput = false;
  127. }
  128. atImageList = 0;
  129. }
  130. Mat nextImage()
  131. {
  132. Mat result;
  133. if( inputCapture.isOpened() )
  134. {
  135. Mat view0;
  136. inputCapture >> view0;
  137. view0.copyTo(result);
  138. }
  139. else if( atImageList < (int)imageList.size() )
  140. result = imread(imageList[atImageList++], CV_LOAD_IMAGE_COLOR);
  141. return result;
  142. }
  143. static bool readStringList( const string& filename, vector<string>& l )
  144. {
  145. l.clear();
  146. FileStorage fs(filename, FileStorage::READ);
  147. if( !fs.isOpened() )
  148. return false;
  149. FileNode n = fs.getFirstTopLevelNode();
  150. if( n.type() != FileNode::SEQ )
  151. return false;
  152. FileNodeIterator it = n.begin(), it_end = n.end();
  153. for( ; it != it_end; ++it )
  154. l.push_back((string)*it);
  155. return true;
  156. }
  157. public:
  158. Size boardSize; // The size of the board -> Number of items by width and height
  159. Pattern calibrationPattern;// One of the Chessboard, circles, or asymmetric circle pattern
  160. float squareSize; // The size of a square in your defined unit (point, millimeter,etc).
  161. int nrFrames; // The number of frames to use from the input for calibration
  162. float aspectRatio; // The aspect ratio
  163. int delay; // In case of a video input
  164. bool bwritePoints; // Write detected feature points
  165. bool bwriteExtrinsics; // Write extrinsic parameters
  166. bool calibZeroTangentDist; // Assume zero tangential distortion
  167. bool calibFixPrincipalPoint;// Fix the principal point at the center
  168. bool flipVertical; // Flip the captured images around the horizontal axis
  169. string outputFileName; // The name of the file where to write
  170. bool showUndistorsed; // Show undistorted images after calibration
  171. string input; // The input ->
  172. int cameraID;
  173. vector<string> imageList;
  174. int atImageList;
  175. VideoCapture inputCapture;
  176. InputType inputType;
  177. bool goodInput;
  178. int flag;
  179. private:
  180. string patternToUse;
  181. };
  182. static void read(const FileNode& node, Settings& x, const Settings& default_value = Settings())
  183. {
  184. if(node.empty())
  185. x = default_value;
  186. else
  187. x.read(node);
  188. }
  189. enum { DETECTION = 0, CAPTURING = 1, CALIBRATED = 2 };
  190. bool runCalibrationAndSave(Settings& s, Size imageSize, Mat& cameraMatrix, Mat& distCoeffs,
  191. vector<vector<Point2f> > imagePoints );
  192. int main(int argc, char* argv[])
  193. {
  194. help();
  195. Settings s;
  196. const string inputSettingsFile = argc > 1 ? argv[1] : “default.xml”;
  197. FileStorage fs(inputSettingsFile, FileStorage::READ); // Read the settings
  198. if (!fs.isOpened())
  199. {
  200. cout << “Could not open the configuration file: \”“ << inputSettingsFile << “\”“ << endl;
  201. return -1;
  202. }
  203. fs[“Settings”] >> s;
  204. fs.release(); // close Settings file
  205. if (!s.goodInput)
  206. {
  207. cout << “Invalid input detected. Application stopping. “ << endl;
  208. return -1;
  209. }
  210. vector<vector<Point2f> > imagePoints;
  211. Mat cameraMatrix, distCoeffs;
  212. Size imageSize;
  213. int mode = s.inputType == Settings::IMAGE_LIST ? CAPTURING : DETECTION;
  214. clock_t prevTimestamp = 0;
  215. const Scalar RED(0,0,255), GREEN(0,255,0);
  216. const char ESC_KEY = 27;
  217. for(int i = 0;;++i)
  218. {
  219. Mat view;
  220. bool blinkOutput = false;
  221. view = s.nextImage();
  222. //—– If no more image, or got enough, then stop calibration and show result ————-
  223. if( mode == CAPTURING && imagePoints.size() >= (unsigned)s.nrFrames )
  224. {
  225. if( runCalibrationAndSave(s, imageSize, cameraMatrix, distCoeffs, imagePoints))
  226. mode = CALIBRATED;
  227. else
  228. mode = DETECTION;
  229. }
  230. if(view.empty()) // If no more images then run calibration, save and stop loop.
  231. {
  232. if( imagePoints.size() > 0 )
  233. runCalibrationAndSave(s, imageSize, cameraMatrix, distCoeffs, imagePoints);
  234. break;
  235. }
  236. imageSize = view.size(); // Format input image.
  237. if( s.flipVertical ) flip( view, view, 0 );
  238. vector<Point2f> pointBuf;
  239. bool found;
  240. switch( s.calibrationPattern ) // Find feature points on the input format
  241. {
  242. case Settings::CHESSBOARD:
  243. found = findChessboardCorners( view, s.boardSize, pointBuf,
  244. CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FAST_CHECK | CV_CALIB_CB_NORMALIZE_IMAGE);
  245. break;
  246. case Settings::CIRCLES_GRID:
  247. found = findCirclesGrid( view, s.boardSize, pointBuf );
  248. break;
  249. case Settings::ASYMMETRIC_CIRCLES_GRID:
  250. found = findCirclesGrid( view, s.boardSize, pointBuf, CALIB_CB_ASYMMETRIC_GRID );
  251. break;
  252. default:
  253. found = false;
  254. break;
  255. }
  256. if (found) // If done with success,
  257. {
  258. // improve the found corners’ coordinate accuracy for chessboard
  259. if( s.calibrationPattern == Settings::CHESSBOARD)
  260. {
  261. Mat viewGray;
  262. cvtColor(view, viewGray, COLOR_BGR2GRAY);
  263. cornerSubPix( viewGray, pointBuf, Size(11,11),
  264. Size(-1,-1), TermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 30, 0.1 ));
  265. }
  266. if( mode == CAPTURING && // For camera only take new samples after delay time
  267. (!s.inputCapture.isOpened() || clock() - prevTimestamp > s.delay*1e-3*CLOCKS_PER_SEC) )
  268. {
  269. imagePoints.push_back(pointBuf);
  270. prevTimestamp = clock();
  271. blinkOutput = s.inputCapture.isOpened();
  272. }
  273. // Draw the corners.
  274. drawChessboardCorners( view, s.boardSize, Mat(pointBuf), found );
  275. }
  276. //—————————– Output Text ————————————————
  277. string msg = (mode == CAPTURING) ? “100/100” :
  278. mode == CALIBRATED ? “Calibrated” : “Press ‘g’ to start”;
  279. int baseLine = 0;
  280. Size textSize = getTextSize(msg, 1, 1, 1, &baseLine);
  281. Point textOrigin(view.cols - 2*textSize.width - 10, view.rows - 2*baseLine - 10);
  282. if( mode == CAPTURING )
  283. {
  284. if(s.showUndistorsed)
  285. msg = format( “%d/%d Undist”, (int)imagePoints.size(), s.nrFrames );
  286. else
  287. msg = format( “%d/%d”, (int)imagePoints.size(), s.nrFrames );
  288. }
  289. putText( view, msg, textOrigin, 1, 1, mode == CALIBRATED ? GREEN : RED);
  290. if( blinkOutput )
  291. bitwise_not(view, view);
  292. //————————- Video capture output undistorted ——————————
  293. if( mode == CALIBRATED && s.showUndistorsed )
  294. {
  295. Mat temp = view.clone();
  296. undistort(temp, view, cameraMatrix, distCoeffs);
  297. }
  298. //—————————— Show image and check for input commands ——————-
  299. imshow(“Image View”, view);
  300. char key = (char)waitKey(s.inputCapture.isOpened() ? 50 : s.delay);
  301. if( key == ESC_KEY )
  302. break;
  303. if( key == ‘u’ && mode == CALIBRATED )
  304. s.showUndistorsed = !s.showUndistorsed;
  305. if( s.inputCapture.isOpened() && key == ‘g’ )
  306. {
  307. mode = CAPTURING;
  308. imagePoints.clear();
  309. }
  310. }
  311. // ———————–Show and save the undistorted image for the image list ————————
  312. if( s.inputType == Settings::IMAGE_LIST && s.showUndistorsed )
  313. {
  314. Mat view, rview, map1, map2;
  315. initUndistortRectifyMap(cameraMatrix, distCoeffs, Mat(),
  316. getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, 1, imageSize, 0),
  317. imageSize, CV_16SC2, map1, map2);
  318. for(int i = 0; i < (int)s.imageList.size(); i++ )
  319. {
  320. view = imread(s.imageList[i], 1);
  321. if(view.empty())
  322. continue;
  323. remap(view, rview, map1, map2, INTER_LINEAR);
  324. imshow(“Image View”, rview);
  325. string imageName = format( “undistorted_%d.jpg”, i);
  326. imwrite(imageName,rview);
  327. char c = (char)waitKey();
  328. if( c == ESC_KEY || c == ‘q’ || c == ‘Q’ )
  329. break;
  330. }
  331. }
  332. return 0;
  333. }
  334. static double computeReprojectionErrors( const vector<vector<Point3f> >& objectPoints,
  335. const vector<vector<Point2f> >& imagePoints,
  336. const vector<Mat>& rvecs, const vector<Mat>& tvecs,
  337. const Mat& cameraMatrix , const Mat& distCoeffs,
  338. vector<float>& perViewErrors)
  339. {
  340. vector<Point2f> imagePoints2;
  341. int i, totalPoints = 0;
  342. double totalErr = 0, err;
  343. perViewErrors.resize(objectPoints.size());
  344. for( i = 0; i < (int)objectPoints.size(); ++i )
  345. {
  346. projectPoints( Mat(objectPoints[i]), rvecs[i], tvecs[i], cameraMatrix,
  347. distCoeffs, imagePoints2);
  348. err = norm(Mat(imagePoints[i]), Mat(imagePoints2), CV_L2);
  349. int n = (int)objectPoints[i].size();
  350. perViewErrors[i] = (float) std::sqrt(err*err/n);
  351. totalErr += err*err;
  352. totalPoints += n;
  353. }
  354. return std::sqrt(totalErr/totalPoints);
  355. }
  356. static void calcBoardCornerPositions(Size boardSize, float squareSize, vector<Point3f>& corners,
  357. Settings::Pattern patternType /*= Settings::CHESSBOARD*/)
  358. {
  359. corners.clear();
  360. switch(patternType)
  361. {
  362. case Settings::CHESSBOARD:
  363. case Settings::CIRCLES_GRID:
  364. for( int i = 0; i < boardSize.height; ++i )
  365. for( int j = 0; j < boardSize.width; ++j )
  366. corners.push_back(Point3f(float( j*squareSize ), float( i*squareSize ), 0));
  367. break;
  368. case Settings::ASYMMETRIC_CIRCLES_GRID:
  369. for( int i = 0; i < boardSize.height; i++ )
  370. for( int j = 0; j < boardSize.width; j++ )
  371. corners.push_back(Point3f(float((2*j + i % 2)*squareSize), float(i*squareSize), 0));
  372. break;
  373. default:
  374. break;
  375. }
  376. }
  377. static bool runCalibration( Settings& s, Size& imageSize, Mat& cameraMatrix