The program I wrote is adapted from a tutorial I found to do camera calibration from live input. My version instead looks for a sequence of files name 00.jpg, 01.jpg, etc and calibrates from those. So the way I used it was to take several pictures of the checkerboard pattern from my iPad, upload them to my computer, edit out the rest of the stuff on my desktop in Photoshop so finding the corners was more likely to be correct, and rename them. The output of the program is two XML files which include the camera intrinsic parameters and distortion coefficients. The code for the program is attached after the jump.
And results:
f_x = 786.42938232
f_y = 786.42938232
c_x = 311.25384521 // See update below
c_y = 217.01358032 // See update below
And distortion coefficients were: -0.10786291, 1.23078966, -4.54779295e-03, -3.28966696e-03, -5.54199600
I hope I did that right. The center is slightly off from where it ideally should be (320, 240).
Note: I found this precompiled private framework of OpenCV built for OSX rather handy. It is only built with 32-bit support, so set your target in XCode accordingly.
UPDATE: The primary point obtained from this calibration was wrong! It was throwing off the pose estimates at glancing angles. I set it to 320,240 and everything works better now...
// main.mm
// CameraCalibration
//
// Adapted from http://dasl.mem.drexel.edu/~noahKuntz/openCVTut10.html
//
#import <cocoa cocoa.h>
#import <opencv opencv.h>
#import <opencv highgui.h>
int main(int argc, char *argv[])
{
const int NUMBER_OF_PICTURES = 23;
int board_w = 5; // Board width in squares
int board_h = 8; // Board height
int n_boards = NUMBER_OF_PICTURES; // Maximum number of boards
int board_n = board_w * board_h;
CvSize board_sz = cvSize( board_w, board_h );
cvNamedWindow( "Calibration", 1);
// Allocate Sotrage
CvMat* image_points = cvCreateMat( n_boards*board_n, 2, CV_32FC1 );
CvMat* object_points = cvCreateMat( n_boards*board_n, 3, CV_32FC1 );
CvMat* point_counts = cvCreateMat( n_boards, 1, CV_32SC1 );
CvMat* intrinsic_matrix = cvCreateMat( 3, 3, CV_32FC1 );
CvMat* distortion_coeffs = cvCreateMat( 5, 1, CV_32FC1 );
CvPoint2D32f* corners = new CvPoint2D32f[ board_n ];
int corner_count;
int successes = 0;
int step, frame = 0;
const CFIndex FILENAME_LEN = 2048;
char filename[FILENAME_LEN] = "";
// (this is the mac way to package application resources)
CFBundleRef mainBundle = CFBundleGetMainBundle();
assert (mainBundle);
CFURLRef image_url = CFBundleCopyResourceURL (mainBundle, CFSTR("00"), CFSTR("jpg"), NULL);
assert (image_url);
Boolean got_it = CFURLGetFileSystemRepresentation (image_url, true,
reinterpret_cast<uint8 *>(filename), FILENAME_LEN);
if (! got_it) abort ();
IplImage *image = cvLoadImage(filename, 1);
IplImage *gray_image = cvCreateImage( cvGetSize( image ), 8, 1 );
NSLog(@"Loaded image.");
// Capture Corner views loop until we've got n_boards
// succesful captures (all corners on the board are found)
int pictureNumber = 0;
while( successes < n_boards && pictureNumber < NUMBER_OF_PICTURES-1){
// Find chessboard corners:
int found = cvFindChessboardCorners( image, board_sz, corners,
&corner_count, CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FILTER_QUADS );
// Get subpixel accuracy on those corners
cvCvtColor( image, gray_image, CV_BGR2GRAY );
cvFindCornerSubPix( gray_image, corners, corner_count, cvSize( 11, 11 ),
cvSize( -1, -1 ), cvTermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 30, 0.1 ));
// Draw it
cvDrawChessboardCorners( image, board_sz, corners, corner_count, found );
cvShowImage( "Calibration", image );
// If we got a good board, add it to our data
if( corner_count == board_n ){
step = successes*board_n;
for( int i=step, j=0; j < board_n; ++i, ++j ){
CV_MAT_ELEM( *image_points, float, i, 0 ) = corners[j].x;
CV_MAT_ELEM( *image_points, float, i, 1 ) = corners[j].y;
CV_MAT_ELEM( *object_points, float, i, 0 ) = j/board_w;
CV_MAT_ELEM( *object_points, float, i, 1 ) = j%board_w;
CV_MAT_ELEM( *object_points, float, i, 2 ) = 0.0f;
}
CV_MAT_ELEM( *point_counts, int, successes, 0 ) = board_n;
successes++;
}
pictureNumber++;
NSLog(@"%i chessboards found in %i pictures", successes, pictureNumber);
CFStringRef number = CFStringCreateWithFormat(NULL, NULL, CFSTR("%02i"), pictureNumber);
CFURLRef image_url = CFBundleCopyResourceURL (mainBundle, (CFStringRef)number, CFSTR("jpg"), NULL);
assert (image_url);
CFURLGetFileSystemRepresentation (image_url, true, reinterpret_cast(filename), FILENAME_LEN);
image = cvLoadImage(filename, 1); // Get next image
} // End collection while loop
// Allocate matrices according to how many chessboards found
CvMat* object_points2 = cvCreateMat( successes*board_n, 3, CV_32FC1 );
CvMat* image_points2 = cvCreateMat( successes*board_n, 2, CV_32FC1 );
CvMat* point_counts2 = cvCreateMat( successes, 1, CV_32SC1 );
// Transfer the points into the correct size matrices
for( int i = 0; i < successes*board_n; ++i ){
CV_MAT_ELEM( *image_points2, float, i, 0) = CV_MAT_ELEM( *image_points, float, i, 0 );
CV_MAT_ELEM( *image_points2, float, i, 1) = CV_MAT_ELEM( *image_points, float, i, 1 );
CV_MAT_ELEM( *object_points2, float, i, 0) = CV_MAT_ELEM( *object_points, float, i, 0 );
CV_MAT_ELEM( *object_points2, float, i, 1) = CV_MAT_ELEM( *object_points, float, i, 1 );
CV_MAT_ELEM( *object_points2, float, i, 2) = CV_MAT_ELEM( *object_points, float, i, 2 );
}
for( int i=0; i < successes; ++i ){
CV_MAT_ELEM( *point_counts2, int, i, 0 ) = CV_MAT_ELEM( *point_counts, int, i, 0 );
}
cvReleaseMat( &object_points );
cvReleaseMat( &image_points );
cvReleaseMat( &point_counts );
// At this point we have all the chessboard corners we need
// Initiliazie the intrinsic matrix such that the two focal lengths
// have a ratio of 1.0
CV_MAT_ELEM( *intrinsic_matrix, float, 0, 0 ) = 1.0;
CV_MAT_ELEM( *intrinsic_matrix, float, 1, 1 ) = 1.0;
// Calibrate the camera
cvCalibrateCamera2( object_points2, image_points2, point_counts2, cvGetSize( image ),
intrinsic_matrix, distortion_coeffs, NULL, NULL, CV_CALIB_FIX_ASPECT_RATIO );
// Save the intrinsics and distortions
cvSave( "Intrinsics.xml", intrinsic_matrix );
cvSave( "Distortion.xml", distortion_coeffs );
// Example of loading these matrices back in
CvMat *intrinsic = (CvMat*)cvLoad( "Intrinsics.xml" );
CvMat *distortion = (CvMat*)cvLoad( "Distortion.xml" );
// Build the undistort map that we will use for all subsequent frames
IplImage* mapx = cvCreateImage( cvGetSize( image ), IPL_DEPTH_32F, 1 );
IplImage* mapy = cvCreateImage( cvGetSize( image ), IPL_DEPTH_32F, 1 );
cvInitUndistortMap( intrinsic, distortion, mapx, mapy );
return 0;
}
My cousin recommended this blog and she was totally right keep up the fantastic work!
ReplyDeletewow nice blog
ReplyDelete