1 | #include "opencv2/core/core.hpp"
|
2 | #include "opencv2/imgproc/imgproc.hpp"
|
3 | #include "opencv2/calib3d/calib3d.hpp"
|
4 | #include "opencv2/highgui/highgui.hpp"
|
5 | #include <stdio.h>
|
6 | #include <string.h>
|
7 | #include <time.h>
|
8 |
|
9 | using namespace cv;
|
10 | using namespace std;
|
11 |
|
12 | |
13 | example command line (for copy-n-paste):
|
14 | calibration -w 6 -h 8 -s 2 -o camera.yml -op -oe image_list.xml
|
15 |
|
16 | where image_list.xml is the standard OpenCV XML/YAML
|
17 | file consisting of the list of strings, e.g.:
|
18 |
|
19 | <?xml version="1.0"?>
|
20 | <opencv_storage>
|
21 | <images>
|
22 | "view000.png"
|
23 | "view001.png"
|
24 | <!-- view002.png -->
|
25 | "view003.png"
|
26 | "view010.png"
|
27 | "one_extra_view.jpg"
|
28 | </images>
|
29 | </opencv_storage>
|
30 |
|
31 | you can also use a video file or live camera input to calibrate the camera
|
32 |
|
33 | */
|
34 |
|
35 | enum { DETECTION = 0, CAPTURING = 1, CALIBRATED = 2 };
|
36 |
|
37 | static double computeReprojectionErrors(
|
38 | const vector<vector<Point3f> >& objectPoints,
|
39 | const vector<vector<Point2f> >& imagePoints,
|
40 | const vector<Mat>& rvecs, const vector<Mat>& tvecs,
|
41 | const Mat& cameraMatrix, const Mat& distCoeffs,
|
42 | vector<float>& perViewErrors )
|
43 | {
|
44 | vector<Point2f> imagePoints2;
|
45 | int i, totalPoints = 0;
|
46 | double totalErr = 0, err;
|
47 | perViewErrors.resize(objectPoints.size());
|
48 |
|
49 | for( i = 0; i < (int)objectPoints.size(); i++ )
|
50 | {
|
51 | projectPoints(Mat(objectPoints[i]), rvecs[i], tvecs[i],
|
52 | cameraMatrix, distCoeffs, imagePoints2);
|
53 | err = norm(Mat(imagePoints[i]), Mat(imagePoints2), CV_L1 );
|
54 | int n = (int)objectPoints[i].size();
|
55 | perViewErrors[i] = err/n;
|
56 | totalErr += err;
|
57 | totalPoints += n;
|
58 | }
|
59 |
|
60 | return totalErr/totalPoints;
|
61 | }
|
62 |
|
63 | static void calcChessboardCorners(Size boardSize, float squareSize, vector<Point3f>& corners)
|
64 | {
|
65 | corners.resize(0);
|
66 |
|
67 | for( int i = 0; i < boardSize.height; i++ )
|
68 | for( int j = 0; j < boardSize.width; j++ )
|
69 | corners.push_back(Point3f(float(j*squareSize),
|
70 | float(i*squareSize), 0));
|
71 | }
|
72 |
|
73 | static bool runCalibration( vector<vector<Point2f> > imagePoints,
|
74 | Size imageSize, Size boardSize,
|
75 | float squareSize, float aspectRatio,
|
76 | int flags, Mat& cameraMatrix, Mat& distCoeffs,
|
77 | vector<Mat>& rvecs, vector<Mat>& tvecs,
|
78 | vector<float>& reprojErrs,
|
79 | double& totalAvgErr)
|
80 | {
|
81 | cameraMatrix = Mat::eye(3, 3, CV_64F);
|
82 | if( flags & CV_CALIB_FIX_ASPECT_RATIO )
|
83 | cameraMatrix.at<double>(0,0) = aspectRatio;
|
84 |
|
85 | distCoeffs = Mat::zeros(5, 1, CV_64F);
|
86 |
|
87 | vector<vector<Point3f> > objectPoints(1);
|
88 | calcChessboardCorners(boardSize, squareSize, objectPoints[0]);
|
89 | for( size_t i = 1; i < imagePoints.size(); i++ )
|
90 | objectPoints.push_back(objectPoints[0]);
|
91 |
|
92 | calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix,
|
93 | distCoeffs, rvecs, tvecs, flags);
|
94 |
|
95 | bool ok = checkRange( cameraMatrix, CV_CHECK_QUIET ) &&
|
96 | checkRange( distCoeffs, CV_CHECK_QUIET );
|
97 |
|
98 | totalAvgErr = computeReprojectionErrors(objectPoints, imagePoints,
|
99 | rvecs, tvecs, cameraMatrix, distCoeffs, reprojErrs);
|
100 |
|
101 | return ok;
|
102 | }
|
103 |
|
104 |
|
105 | void saveCameraParams( const string& filename,
|
106 | Size imageSize, Size boardSize,
|
107 | float squareSize, float aspectRatio, int flags,
|
108 | const Mat& cameraMatrix, const Mat& distCoeffs,
|
109 | const vector<Mat>& rvecs, const vector<Mat>& tvecs,
|
110 | const vector<float>& reprojErrs,
|
111 | const vector<vector<Point2f> >& imagePoints,
|
112 | double totalAvgErr )
|
113 | {
|
114 | FileStorage fs( filename, FileStorage::WRITE );
|
115 |
|
116 | time_t t;
|
117 | time( &t );
|
118 | struct tm *t2 = localtime( &t );
|
119 | char buf[1024];
|
120 | strftime( buf, sizeof(buf)-1, "%c", t2 );
|
121 |
|
122 | fs << "calibration_time" << buf;
|
123 |
|
124 | if( !rvecs.empty() || !reprojErrs.empty() )
|
125 | fs << "nframes" << (int)std::max(rvecs.size(), reprojErrs.size());
|
126 | fs << "image_width" << imageSize.width;
|
127 | fs << "image_height" << imageSize.height;
|
128 | fs << "board_width" << boardSize.width;
|
129 | fs << "board_height" << boardSize.height;
|
130 | fs << "squareSize" << squareSize;
|
131 |
|
132 | if( flags & CV_CALIB_FIX_ASPECT_RATIO )
|
133 | fs << "aspectRatio" << aspectRatio;
|
134 |
|
135 | if( flags != 0 )
|
136 | {
|
137 | sprintf( buf, "flags: %s%s%s%s",
|
138 | flags & CV_CALIB_USE_INTRINSIC_GUESS ? "+use_intrinsic_guess" : "",
|
139 | flags & CV_CALIB_FIX_ASPECT_RATIO ? "+fix_aspectRatio" : "",
|
140 | flags & CV_CALIB_FIX_PRINCIPAL_POINT ? "+fix_principal_point" : "",
|
141 | flags & CV_CALIB_ZERO_TANGENT_DIST ? "+zero_tangent_dist" : "" );
|
142 | cvWriteComment( *fs, buf, 0 );
|
143 | }
|
144 |
|
145 | fs << "flags" << flags;
|
146 |
|
147 | fs << "camera_matrix" << cameraMatrix;
|
148 | fs << "distortion_coefficients" << distCoeffs;
|
149 |
|
150 | fs << "avg_reprojection_error" << totalAvgErr;
|
151 | if( !reprojErrs.empty() )
|
152 | fs << "per_view_reprojection_errors" << Mat(reprojErrs);
|
153 |
|
154 | if( !rvecs.empty() && !tvecs.empty() )
|
155 | {
|
156 | Mat bigmat(rvecs.size(), 6, CV_32F);
|
157 | for( size_t i = 0; i < rvecs.size(); i++ )
|
158 | {
|
159 | Mat r = bigmat(Range(i, i+1), Range(0,3));
|
160 | Mat t = bigmat(Range(i, i+1), Range(3,6));
|
161 | rvecs[i].copyTo(r);
|
162 | tvecs[i].copyTo(t);
|
163 | }
|
164 | cvWriteComment( *fs, "a set of 6-tuples (rotation vector + translation vector) for each view", 0 );
|
165 | fs << "extrinsic_parameters" << bigmat;
|
166 | }
|
167 |
|
168 | if( !imagePoints.empty() )
|
169 | {
|
170 | Mat imagePtMat(imagePoints.size(), imagePoints[0].size(), CV_32FC2);
|
171 | for( size_t i = 0; i < imagePoints.size(); i++ )
|
172 | {
|
173 | Mat r = imagePtMat.row(i).reshape(2, imagePtMat.cols);
|
174 | Mat(imagePoints[i]).copyTo(r);
|
175 | }
|
176 | fs << "image_points" << imagePtMat;
|
177 | }
|
178 | }
|
179 |
|
180 | static bool readStringList( const string& filename, vector<string>& l )
|
181 | {
|
182 | l.resize(0);
|
183 | FileStorage fs(filename, FileStorage::READ);
|
184 | if( !fs.isOpened() )
|
185 | return false;
|
186 | FileNode n = fs.getFirstTopLevelNode();
|
187 | if( n.type() != FileNode::SEQ )
|
188 | return false;
|
189 | FileNodeIterator it = n.begin(), it_end = n.end();
|
190 | for( ; it != it_end; ++it )
|
191 | l.push_back((string)*it);
|
192 | return true;
|
193 | }
|
194 |
|
195 |
|
196 | bool runAndSave(const string& outputFilename,
|
197 | const vector<vector<Point2f> >& imagePoints,
|
198 | Size imageSize, Size boardSize, float squareSize,
|
199 | float aspectRatio, int flags, Mat& cameraMatrix,
|
200 | Mat& distCoeffs, bool writeExtrinsics, bool writePoints )
|
201 | {
|
202 | vector<Mat> rvecs, tvecs;
|
203 | vector<float> reprojErrs;
|
204 | double totalAvgErr = 0;
|
205 |
|
206 | bool ok = runCalibration(imagePoints, imageSize, boardSize, squareSize,
|
207 | aspectRatio, flags, cameraMatrix, distCoeffs,
|
208 | rvecs, tvecs, reprojErrs, totalAvgErr);
|
209 | printf("%s. avg reprojection error = %.2f\n",
|
210 | ok ? "Calibration succeeded" : "Calibration failed",
|
211 | totalAvgErr);
|
212 |
|
213 | if( ok )
|
214 | saveCameraParams( outputFilename, imageSize,
|
215 | boardSize, squareSize, aspectRatio,
|
216 | flags, cameraMatrix, distCoeffs,
|
217 | writeExtrinsics ? rvecs : vector<Mat>(),
|
218 | writeExtrinsics ? tvecs : vector<Mat>(),
|
219 | writeExtrinsics ? reprojErrs : vector<float>(),
|
220 | writePoints ? imagePoints : vector<vector<Point2f> >(),
|
221 | totalAvgErr );
|
222 | return ok;
|
223 | }
|
224 |
|
225 |
|
226 | int main( int argc, char** argv )
|
227 | {
|
228 | Size boardSize, imageSize;
|
229 | float squareSize = 1.f, aspectRatio = 1.f;
|
230 | Mat cameraMatrix, distCoeffs;
|
231 | const char* outputFilename = "out_camera_data.yml";
|
232 | const char* inputFilename = 0;
|
233 |
|
234 | int i, nframes = 10;
|
235 | bool writeExtrinsics = false, writePoints = false;
|
236 | bool undistortImage = false;
|
237 | int flags = 0;
|
238 | VideoCapture capture;
|
239 | bool flipVertical = false;
|
240 | int delay = 1000;
|
241 | clock_t prevTimestamp = 0;
|
242 | int mode = DETECTION;
|
243 | int cameraId = 0;
|
244 | vector<vector<Point2f> > imagePoints;
|
245 | vector<string> imageList;
|
246 | bool videofile = false;
|
247 |
|
248 | const char* liveCaptureHelp =
|
249 | "When the live video from camera is used as input, the following hot-keys may be used:\n"
|
250 | " <ESC>, 'q' - quit the program\n"
|
251 | " 'g' - start capturing images\n"
|
252 | " 'u' - switch undistortion on/off\n";
|
253 |
|
254 | if( argc < 2 )
|
255 | {
|
256 | printf( "This is a camera calibration sample.\n"
|
257 | "Usage: calibration\n"
|
258 | " -w <board_width> # the number of inner corners per one of board dimension\n"
|
259 | " -h <board_height> # the number of inner corners per another board dimension\n"
|
260 | " [-n <number_of_frames>] # the number of frames to use for calibration\n"
|
261 | " # (if not specified, it will be set to the number\n"
|
262 | " # of board views actually available)\n"
|
263 | " [-d <delay>] # a minimum delay in ms between subsequent attempts to capture a next view\n"
|
264 | " # (used only for video capturing)\n"
|
265 | " [-s <squareSize>] # square size in some user-defined units (1 by default)\n"
|
266 | " [-o <out_camera_params>] # the output filename for intrinsic [and extrinsic] parameters\n"
|
267 | " [-op] # write detected feature points\n"
|
268 | " [-oe] # write extrinsic parameters\n"
|
269 | " [-zt] # assume zero tangential distortion\n"
|
270 | " [-a <aspectRatio>] # fix aspect ratio (fx/fy)\n"
|
271 | " [-p] # fix the principal point at the center\n"
|
272 | " [-v] # flip the captured images around the horizontal axis\n"
|
273 | " [-V] # use a video file, and not an image list, uses\n"
|
274 | " # [input_data] string for the video file name\n"
|
275 | " [input_data] # input data, one of the following:\n"
|
276 | " # - text file with a list of the images of the board\n"
|
277 | " # - name of video file with a video of the board\n"
|
278 | " # if input_data not specified, a live view from the camera is used\n"
|
279 | "\n" );
|
280 | printf( "%s", liveCaptureHelp );
|
281 | return 0;
|
282 | }
|
283 |
|
284 | for( i = 1; i < argc; i++ )
|
285 | {
|
286 | const char* s = argv[i];
|
287 | if( strcmp( s, "-w" ) == 0 )
|
288 | {
|
289 | if( sscanf( argv[++i], "%u", &boardSize.width ) != 1 || boardSize.width <= 0 )
|
290 | return fprintf( stderr, "Invalid board width\n" ), -1;
|
291 | }
|
292 | else if( strcmp( s, "-h" ) == 0 )
|
293 | {
|
294 | if( sscanf( argv[++i], "%u", &boardSize.height ) != 1 || boardSize.height <= 0 )
|
295 | return fprintf( stderr, "Invalid board height\n" ), -1;
|
296 | }
|
297 | else if( strcmp( s, "-s" ) == 0 )
|
298 | {
|
299 | if( sscanf( argv[++i], "%f", &squareSize ) != 1 || squareSize <= 0 )
|
300 | return fprintf( stderr, "Invalid board square width\n" ), -1;
|
301 | }
|
302 | else if( strcmp( s, "-n" ) == 0 )
|
303 | {
|
304 | if( sscanf( argv[++i], "%u", &nframes ) != 1 || nframes <= 3 )
|
305 | return printf("Invalid number of images\n" ), -1;
|
306 | }
|
307 | else if( strcmp( s, "-a" ) == 0 )
|
308 | {
|
309 | if( sscanf( argv[++i], "%f", &aspectRatio ) != 1 || aspectRatio <= 0 )
|
310 | return printf("Invalid aspect ratio\n" ), -1;
|
311 | }
|
312 | else if( strcmp( s, "-d" ) == 0 )
|
313 | {
|
314 | if( sscanf( argv[++i], "%u", &delay ) != 1 || delay <= 0 )
|
315 | return printf("Invalid delay\n" ), -1;
|
316 | }
|
317 | else if( strcmp( s, "-op" ) == 0 )
|
318 | {
|
319 | writePoints = 1;
|
320 | }
|
321 | else if( strcmp( s, "-oe" ) == 0 )
|
322 | {
|
323 | writeExtrinsics = 1;
|
324 | }
|
325 | else if( strcmp( s, "-zt" ) == 0 )
|
326 | {
|
327 | flags |= CV_CALIB_ZERO_TANGENT_DIST;
|
328 | }
|
329 | else if( strcmp( s, "-p" ) == 0 )
|
330 | {
|
331 | flags |= CV_CALIB_FIX_PRINCIPAL_POINT;
|
332 | }
|
333 | else if( strcmp( s, "-v" ) == 0 )
|
334 | {
|
335 | flipVertical = 1;
|
336 | }
|
337 | else if( strcmp( s, "-o" ) == 0 )
|
338 | {
|
339 | outputFilename = argv[++i];
|
340 | }
|
341 | else if( strcmp( s, "-V" ) == 0 )
|
342 | {
|
343 | videofile = true;
|
344 | }
|
345 | else if( s[0] != '-' )
|
346 | {
|
347 | if( isdigit(s[0]) )
|
348 | sscanf(s, "%d", &cameraId);
|
349 | else
|
350 | inputFilename = s;
|
351 | }
|
352 | else
|
353 | return fprintf( stderr, "Unknown option %s", s ), -1;
|
354 | }
|
355 |
|
356 | if( inputFilename )
|
357 | {
|
358 | if(!videofile && readStringList(inputFilename, imageList) )
|
359 | mode = CAPTURING;
|
360 | else
|
361 | capture.open(inputFilename);
|
362 | }
|
363 | else
|
364 | capture.open(cameraId);
|
365 |
|
366 | if( !capture.isOpened() && imageList.empty() )
|
367 | return fprintf( stderr, "Could not initialize video capture\n" ), -2;
|
368 |
|
369 | if( !imageList.empty() )
|
370 | nframes = (int)imageList.size();
|
371 |
|
372 | if( capture.isOpened() )
|
373 | printf( "%s", liveCaptureHelp );
|
374 |
|
375 | namedWindow( "Image View", 1 );
|
376 |
|
377 | for(i = 0;;i++)
|
378 | {
|
379 | Mat view, viewGray;
|
380 | bool blink = false;
|
381 |
|
382 | if( capture.isOpened() )
|
383 | {
|
384 | Mat view0;
|
385 | capture >> view0;
|
386 | view0.copyTo(view);
|
387 | }
|
388 | else if( i < (int)imageList.size() )
|
389 | view = imread(imageList[i], 1);
|
390 |
|
391 | if(!view.data)
|
392 | {
|
393 | if( imagePoints.size() > 0 )
|
394 | runAndSave(outputFilename, imagePoints, imageSize,
|
395 | boardSize, squareSize, aspectRatio,
|
396 | flags, cameraMatrix, distCoeffs,
|
397 | writeExtrinsics, writePoints);
|
398 | break;
|
399 | }
|
400 |
|
401 | imageSize = view.size();
|
402 |
|
403 | if( flipVertical )
|
404 | flip( view, view, 0 );
|
405 |
|
406 | vector<Point2f> pointbuf;
|
407 | bool found = findChessboardCorners( view, boardSize, pointbuf, CV_CALIB_CB_ADAPTIVE_THRESH );
|
408 |
|
409 |
|
410 | cvtColor(view, viewGray, CV_BGR2GRAY);
|
411 | cornerSubPix( viewGray, pointbuf, Size(11,11),
|
412 | Size(-1,-1), TermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 30, 0.1 ));
|
413 |
|
414 | if( mode == CAPTURING && found &&
|
415 | (!capture.isOpened() || clock() - prevTimestamp > delay*1e-3*CLOCKS_PER_SEC) )
|
416 | {
|
417 | imagePoints.push_back(pointbuf);
|
418 | prevTimestamp = clock();
|
419 | blink = capture.isOpened();
|
420 | }
|
421 |
|
422 | drawChessboardCorners( view, boardSize, Mat(pointbuf), found );
|
423 |
|
424 | string msg = mode == CAPTURING ? "100/100" :
|
425 | mode == CALIBRATED ? "Calibrated" : "Press 'g' to start";
|
426 | int baseLine = 0;
|
427 | Size textSize = getTextSize(msg, 1, 1, 1, &baseLine);
|
428 | Point textOrigin(view.cols - textSize.width - 10, view.rows - baseLine - 10);
|
429 |
|
430 | if( mode == CAPTURING )
|
431 | msg = format( "%d/%d", (int)imagePoints.size(), nframes );
|
432 |
|
433 | putText( view, msg, textOrigin, 1, 1,
|
434 | mode != CALIBRATED ? Scalar(0,0,255) : Scalar(0,255,0));
|
435 |
|
436 | if( blink )
|
437 | bitwise_not(view, view);
|
438 |
|
439 | if( mode == CALIBRATED && undistortImage )
|
440 | {
|
441 | Mat temp = view.clone();
|
442 | undistort(temp, view, cameraMatrix, distCoeffs);
|
443 | }
|
444 |
|
445 | imshow("Image View", view);
|
446 | int key = waitKey(capture.isOpened() ? 50 : 500);
|
447 |
|
448 | if( (key & 255) == 27 )
|
449 | break;
|
450 |
|
451 | if( key == 'u' && mode == CALIBRATED )
|
452 | undistortImage = !undistortImage;
|
453 |
|
454 | if( capture.isOpened() && key == 'g' )
|
455 | {
|
456 | mode = CAPTURING;
|
457 | imagePoints.clear();
|
458 | }
|
459 |
|
460 | if( mode == CAPTURING && imagePoints.size() >= (unsigned)nframes )
|
461 | {
|
462 | if( runAndSave(outputFilename, imagePoints, imageSize,
|
463 | boardSize, squareSize, aspectRatio,
|
464 | flags, cameraMatrix, distCoeffs,
|
465 | writeExtrinsics, writePoints))
|
466 | mode = CALIBRATED;
|
467 | else
|
468 | mode = DETECTION;
|
469 | if( !capture.isOpened() )
|
470 | break;
|
471 | }
|
472 | }
|
473 | return 0;
|
474 | }
|