calibration.cpp

calibration.cpp with a flag for using video, with a hint in the usage printout - Ethan Rublee, 2010-06-21 03:33 am

Download (16.3 kB)

 
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
        // improve the found corners' coordinate accuracy
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
}