Friday, March 5, 2010

Camera Calibration using OpenCV

Here is a simple test by which you can look into the parameters of your camera and correct any distortions.


The entire program is as it is from the O'Reilly book [link].


The method used is based on pin-hole camera model, correcting intrinsic deviations and also distortions like radial and tangential.


Some of the links which you might be interested in:
Wikipedia     [link]
Zhengyou Zhang: A Flexible New Technique for Camera Calibration    [link]

Jean-Yves Bouguet : (Matlab)    [link]

Download the source code along with the calibration patterns here:

#include <cv.h>
#include <highgui.h>
#include <stdio.h>
#include <stdlib.h>

int n_boards = 0;  //Number of snapshots of the chessboard
int frame_step;   //Frames to be skipped
int board_w;   //Enclosed corners horizontally on the chessboard
int board_h;   //Enclosed corners vertically on the chessboard
int main() 
{
 CvCapture* capture;

 printf("Enter the numbers of spanspots = ");
 scanf("%d",&n_boards);
 
 printf("\rEnter the numbers of frames to skip = ");
 scanf("%d",&frame_step);

 board_w  = 9;
 board_h  = 6;
  
 int board_total  = board_w * board_h;          //Total enclosed corners on the board
 CvSize board_sz = cvSize( board_w, board_h );

 capture = cvCreateCameraCapture( 0 );
 if(!capture) 
 { 
  printf("\nCouldn't open the camera\n"); 
  return -1;
 }

 cvNamedWindow( "Snapshot" );
 cvNamedWindow( "Raw Video");

 //Allocate storage for the parameters according to total number of 
        //corners and number of snapshots
 CvMat* image_points      = cvCreateMat(n_boards*board_total,2,CV_32FC1);
 CvMat* object_points     = cvCreateMat(n_boards*board_total,3,CV_32FC1);
 CvMat* point_counts      = cvCreateMat(n_boards,1,CV_32SC1);
 CvMat* intrinsic_matrix  = cvCreateMat(3,3,CV_32FC1);
 CvMat* distortion_coeffs = cvCreateMat(4,1,CV_32FC1);

 //Note:
 //Intrinsic Matrix - 3x3 Lens Distorstion Matrix - 4x1
 // [fx 0 cx]              [k1 k2 p1 p2   k3(optional)]
 // [0 fy cy]
 // [0  0  1]


 CvPoint2D32f* corners = new CvPoint2D32f[ board_total ];
 int corner_count;
 int successes = 0;
 int step, frame = 0;

 IplImage *image = cvQueryFrame( capture );
 IplImage *gray_image = cvCreateImage(cvGetSize(image),8,1);    //subpixel

 //Loop while successful captures equals total snapshots
 //Successful captures implies when all the enclosed corners 
        //are detected from a snapshot

 while(successes < n_boards)     
 {
  if((frame++ % frame_step) == 0)                             
                //Skip frames
  {
   //Find chessboard corners:
   int found = cvFindChessboardCorners(image, 
                                     board_sz, corners,&corner_count,
                               CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FILTER_QUADS );

   cvCvtColor(image, gray_image, CV_BGR2GRAY);           
                        
                        //Get Subpixel accuracy on those corners
   cvFindCornerSubPix(gray_image, corners, corner_count, 
                                             cvSize(11,11),cvSize(-1,-1), 
                               cvTermCriteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 30, 0.1 ));

   cvDrawChessboardCorners(image, board_sz, corners, corner_count, found);   
                        //Draw it
   
   // If we got a good board, add it to our data
   if(corner_count == board_total) 
   {
    cvShowImage( "Snapshot", image );            
                                //show in color if we did collect the image
    step = successes*board_total;
    for( int i=step, j=0; j<board_total; ++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) = (float) j/board_w;
    CV_MAT_ELEM(*object_points,float,i,1) = (float) (j%board_w);
    CV_MAT_ELEM(*object_points,float,i,2) = 0.0f;
   }
   CV_MAT_ELEM(*point_counts, int,successes,0) = board_total;    
   successes++;
   printf("\r%d successful Snapshots out of %d 
                                        collected.",successes,n_boards);
   }
   else
   cvShowImage( "Snapshot", gray_image );          
                        //Show Gray if we didn't collect the image
  } 
  
  //Handle pause/unpause and ESC
  int c = cvWaitKey(15);
  if(c == 'p')
  {  
   c = 0;
   while(c != 'p' && c != 27)
   {
    c = cvWaitKey(250);
   }
  }
  if(c == 27)
   return 0;

  image = cvQueryFrame( capture );        
                //Get next image
  cvShowImage("Raw Video", image);
 } 

 //End WHILE loop with enough successful captures

 cvDestroyWindow("Snapshot");

 printf("\n\n *** Calbrating the camera now...\n");
 
 //Allocate matrices according to successful number of captures
 CvMat* object_points2  = cvCreateMat(successes*board_total,3,CV_32FC1);
 CvMat* image_points2   = cvCreateMat(successes*board_total,2,CV_32FC1);
 CvMat* point_counts2   = cvCreateMat(successes,1,CV_32SC1);

 //Tranfer the points to matrices
 for(int i = 0; i<successes*board_total; ++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);   
             //These are all the same number
 }
 cvReleaseMat(&object_points);
 cvReleaseMat(ℑ_points);
 cvReleaseMat(&point_counts);

 // Initialize the intrinsic matrix with both the two focal lengths in a ratio of 1.0

 CV_MAT_ELEM( *intrinsic_matrix, float, 0, 0 ) = 1.0f;
 CV_MAT_ELEM( *intrinsic_matrix, float, 1, 1 ) = 1.0f;

 //Calibrate the camera
 //______________________________________________________
 
 cvCalibrateCamera2(object_points2, image_points2, point_counts2,  
                           cvGetSize( image ), 
                           intrinsic_matrix, distortion_coeffs, NULL, NULL,0 );
                                           
//CV_CALIB_FIX_ASPECT_RATIO
 //______________________________________________________

 //Save values to file
 printf(" *** Calibration Done!\n\n");
 printf("Storing Intrinsics.xml and Distortions.xml files...\n");
 cvSave("Intrinsics.xml",intrinsic_matrix);
 cvSave("Distortion.xml",distortion_coeffs);
 printf("Files saved.\n\n");

 printf("Starting corrected display....");

 //Sample: load the matrices from the file
 CvMat *intrinsic = (CvMat*)cvLoad("Intrinsics.xml");
 CvMat *distortion = (CvMat*)cvLoad("Distortion.xml");

 // Build the undistort map used 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);

 // Run the camera to the screen, showing the raw and the undistorted image.

 cvNamedWindow( "Undistort" );
 while(image) 
 {
  IplImage *t = cvCloneImage(image);
  cvShowImage( "Raw Video", image );  // Show raw image
  cvRemap( t, image, mapx, mapy );  // Undistort image
  cvReleaseImage(&t);
  cvShowImage("Undistort", image);  // Show corrected image

  //Handle pause/unpause and ESC
  int c = cvWaitKey(15);
  if(c == 'p')
  { 
   c = 0;
   while(c != 'p' && c != 27)
   {
    c = cvWaitKey(250);
   }
  }
  if(c == 27)
   break;

  image = cvQueryFrame( capture );
 } 
 
 return 0;
}
Pattern [Size A4, 3508x2480px, PNG]:












Images:

27 comments:

anep said...

nicely done!
thank you for sharing..
keep it up!

geezer said...

Hi, I dont know how to make these programs, can you send me the compiled binaries as a favour, please?
Best,
Colin

geezer said...

OK, I compiled it, now how do I use it? I take photos of the calibration target, then what?

Bharath said...

Hi geezer,

Run the code and the camera starts to take snapshots (..say 12).. Now you have to hold the calibration pattern infront of the camera in various positions until it registers 12 VALID snapshots (ie all 54 points should be recognized in each shot). Then it calibrates accordingly and displays the corrected image in a new window. (Code also saves the distortion parameters in the .xml file)

geezer said...

It starts up my webcam and shows two windows with video in. I want to calibrate my Android phone camera. Perhaps Droidcam will work?
If you make an Android app for camera calibration, you will be famous!

Colin

Bharath said...

Hi Colin,
I'm still learning Android programming as of now, i'll see what i can do. :)

Richards said...

Hi Bharath,
I tried running the above code in open cv and ran in to a lot of errors. Is this code specific to any version of opencv? i am using opencv version 1.1. Should i use an update version to get this code running? Or is there any settings that i have to modify in my project to get it running?

Bharath said...

Hi Richards,

No.. code works on all OpenCV versions..

The program was compiled in debug mode. Create a new project and add only the code to it and compile a fresh.

ray1claw said...

Hi Bharath,
I'm past the stage of calibration for now, I used one of the samples for that, it gave me an intrinsics.yml and an extrinsics.yml. I also made a program that finds the features points in consecutive frames, and calculates the z value for each of those points, but i assumed focus x baseline = 1000 for that(which gave me decent values but not that nice). Now that I've calibrated my stereo setup i need to find the actual f and b from the yml files, can you help me here?

Thanks
Ravish

John said...

You just copy and pasted this from the OpenCV book...

Alonso said...

Hi Bharath,
I'm a very beginner of computer vision system. I've just received new project from my professor which it related to camera calibration. I've just found this site talking and sharing camera calibration by using openCV library. I am trying to compile and run your code but it was not succeeded. I got some errors as below :
1 error LNK2019: unresolved external symbol _cvCalibrateCamera2 referenced in function _main
2 error LNK2019: unresolved external symbol _cvDrawChessboardCorners referenced in function _main
3 error LNK2019: unresolved external symbol _cvFindChessboardCorners referenced in function _main
4 fatal error LNK1120: 3 unresolved externals

Can you help me to solve this problem ?
I really appreciate anyone who solve this problem.

Thank you.
From Alonso

ishwar lal said...

In that code format can any one tell give me code for stereoscopy. I mean to calibrate more than one cameras and make 3d.

Regards
Ishwar lal

SystemCoder said...

Alonso: if you are using visual studio 2010, you are missing a lib file I suspect. In your linker >> input >> additional dependencies, click on the dropdown arrow and then do edit.. make sure you have opencv_calib3d231d.lib defined in there (the full path if you dare).

ilkay atıl said...

Thanks for the post, by the way there are some missing release functions for a proper exit, always release what you got. Add the following lines to the end of the function before return statement:
/////////////////////////////////
// Release the capture device & windows
cvDestroyWindow( "Undistort" );
cvDestroyWindow( "Raw Video" ); cvReleaseCapture( &capture );
//////////////////////////////////

karakuri said...

I have a little problem. I think that everything goes good until remapping. Program returns xml files (they look fine) and it crush at a moment of remapping( when I remove this line program doesn't crush). Any ideas why this can be happening?

Nimantha said...

i want to know how to reconstruct a 3d using openCV.Can you give me a tutorials for this.
i am doing a project that is a 3d face recognition system.but i am a beginner to do this.
please help me.

Rita said...

Hi, Can this code be used if im using 2 cameras forming a stereoscopic system?

thomas gi said...
This comment has been removed by the author.
hissou said...

hi, me i want just to know how can ii be sure that the calibration process i good enough.

Elmi said...

Hm, with current OpenCV version 2.4.3 this code does not work, there is nearly no difference between rwa and undistorted image. Any ideas what could be wrong?

Seif Sher said...

hi how would i deal with partial occlusions thanks

fraud said...
This comment has been removed by the author.
fraud said...

Bharath thanx again , you have done grate work , i only have one question for you...! Any ideas why my final undistort image shows up in random shape or a plain black screen each time??

venky said...

hey there,
I had a project based on this calibration. I have a very silly question... what does the entry number of frames to skip mean ?
thanks,
venky

Saipol Hadi Hasim said...

I'm using opencv 2.4.5 and VS2010. so after I convert your source code, all code is red lineup, why......what can I do

Huy Phan said...

Hi, would you tell me what's ℑ_points for? That ℑ character can't compile! i'm sorry if it was a stupid question

Dida said...

I think there is mistake in line 92 and 93:
CV_MAT_ELEM(*object_points,float,i,0) = (float) j/board_w;
CV_MAT_ELEM(*object_points,float,i,1) = (float) (j%board_w);
"float" cast should not be there, otherwise, it will cause the object points skewed.

Post a Comment