Tool for camera calibration.This example is an implementation of a camera calibration tool with a non linear method based on virtual visual servoing. It uses several images of a unique calibration grid.
The calibration grid used here is available in ViSP-images/calibration/grid2d.{fig,pdf} or in ./example/calibration/grid2d.fig (.fig files comes from Xfig tool).
The calibration grid is a 6*6 dots grid where dots centers are spaced by 0.03 meter. You can obviously use another calibration grid changing its parameters in the program. Then you have to grab some images of this grid (you can use grab examples of ViSP to do it), save them as PGM files and precise their names with the -p option.
#include <visp/vpDebug.h>
#include <visp/vpParseArgv.h>
#include <visp/vpIoTools.h>
#include <stdio.h>
#include <stdlib.h>
#include <sstream>
#include <iomanip>
#include <visp/vpImage.h>
#include <visp/vpImageIo.h>
#include <visp/vpCalibration.h>
#include <visp/vpDisplayX.h>
#include <visp/vpDisplayGDI.h>
#include <visp/vpDisplayGTK.h>
#include <visp/vpDisplayD3D.h>
#include <visp/vpMouseButton.h>
#include <visp/vpXmlParserCamera.h>
#include <visp/vpPose.h>
#include <visp/vpDot.h>
#include <visp/vpDot2.h>
#include <visp/vpPixelMeterConversion.h>
#include <visp/vpMeterPixelConversion.h>
#ifdef VISP_HAVE_OPENCV
# include <visp/vpOpenCVGrabber.h>
#elif defined(VISP_HAVE_V4L2)
# include <visp/vpV4l2Grabber.h>
#elif defined(VISP_HAVE_DIRECTSHOW)
# include <visp/vpDirectShowGrabber.h>
#elif defined(VISP_HAVE_DC1394_2)
# include <visp/vp1394TwoGrabber.h>
#endif
#define GETOPTARGS "di:p:hf:g:n:s:l:cv:"
void usage(const char *name,const char *badparam, std::string ipath, std::string ppath,
double gray, unsigned first, unsigned nimages, unsigned step, double lambda)
{
fprintf(stdout, "\n\
Read images of a calibration grid from the disk and \n\
calibrate the camera used for grabbing it.\n\
Each image corresponds to a PGM file.\n\
The calibration grid used here is available in : \n\
ViSP-images/calibration/grid2d.{fig,pdf} or \n\
./example/calibration/grid2d.fig\n\
This is a 6*6 dots calibration grid where dots centers \n\
are spaced by 0.03 meter. You can obviously use another \n\
calibration grid changing its parameters in the program.\n\
Then you have to grab some images of this grid (you can use \n\
grab examples of ViSP to do it), save them as PGM files and\n\
precise their names with the -p option.\n\
\n\
SYNOPSIS\n\
%s [-i <test image path>] [-p <personal image path>]\n\
[-g <gray level precision>] [-f <first image>] \n\
[-n <number of images>] [-s <step>] [-l lambda] \n\
[-c] [-d] [-h]\n\
", name);
fprintf(stdout, "\n\
OPTIONS: Default\n\
-i <test image path> %s\n\
Set image input path.\n\
From this path read \"ViSP-images/calibration/grid36-%%02d.pgm\"\n\
images and the calibration grid data. \n\
These images come from ViSP-images-x.y.z.tar.gz\n\
available on the ViSP website.\n\
Setting the VISP_INPUT_IMAGE_PATH environment\n\
variable produces the same behaviour than using\n\
this option.\n\
\n\
-p <personal image path> %s\n\
Specify a personal sequence containing images \n\
to process.\n\
By image sequence, we mean one file per image.\n\
The following image file formats PNM (PGM P5, PPM P6)\n\
are supported. The format is selected by analysing \n\
the filename extension.\n\
Example : \"/Temp/ViSP-images/calibration/grid36-%%02d.pgm\"\n\
%%02d is for the image numbering.\n\
\n\
-g <gray level precision> %f\n\
Specify a gray level precision to detect dots.\n\
A number between 0 and 1.\n\
precision of the gray level of the dot. \n\
It is a double precision float witch \n\
value is in ]0,1]. 1 means full precision, \n\
whereas values close to 0 show a very bad \n\
precision.\n\
\n\
-f <first image> %u\n\
First image number of the sequence.\n\
\n\
-n <number of images> %u\n\
Number of images used to compute calibration.\n\
\n\
-s <step> %u\n\
Step between two images.\n\
\n\
-l <lambda> %f\n\
Gain of the virtual visual servoing.\n\
\n\
-d \n\
Disable the image display. This can be useful \n\
for automatic tests using crontab under Unix or \n\
using the task manager under Windows.\n\
\n\
-v <generic image name> \n\
Record a serie of images using a webcam. A framegrabber (either \n\
vpOpenCVGrabber, vpDirectShowGrabber, vp1394TwoGrabber or vpV4l2Grabber) is\n\
required. The images are recorded in the disk using the generic name in \n\
parameter (for example \"/tmp/img-%%03d.pgm\").\n\
\n\
-c\n\
Disable the mouse click.\n\
If the image display is disabled (using -d)\n\
this option is without effect.\n\
\n\
-h\n\
Print the help.\n\n",
ipath.c_str(),ppath.c_str(), gray ,first, nimages, step,lambda);
if (badparam)
fprintf(stdout, "\nERROR: Bad parameter [%s]\n", badparam);
}
bool getOptions(int argc,const char **argv, std::string &ipath, std::string &ppath,
double &gray, unsigned &first, unsigned &nimages, unsigned &step,
double &lambda, bool &display, bool &click, bool& opt_video, std::string& opt_video_image_path)
{
const char *optarg;
int c;
switch (c) {
case 'd': display = false; break;
case 'i': ipath = optarg; break;
case 'p': ppath = optarg; break;
case 'g': gray = atof(optarg);break;
case 'f': first = (unsigned) atoi(optarg); break;
case 'n': nimages = (unsigned) atoi(optarg); break;
case 's': step = (unsigned) atoi(optarg); break;
case 'l': lambda = atof(optarg); break;
case 'c': click = false; break;
case 'v': opt_video = true; opt_video_image_path = optarg; break;
case 'h': usage(argv[0], NULL, ipath, ppath,gray, first, nimages, step, lambda);
return false; break;
default:
usage(argv[0], optarg, ipath, ppath, gray,first, nimages, step, lambda);
return false; break;
}
}
if ((c == 1) || (c == -1)) {
usage(argv[0], NULL, ipath, ppath,gray, first, nimages, step, lambda);
std::cerr << "ERROR: " << std::endl;
std::cerr << " Bad argument " << optarg << std::endl << std::endl;
return false;
}
return true;
}
#if (defined(VISP_HAVE_X11) || defined(VISP_HAVE_GTK) || defined(VISP_HAVE_GDI) || defined(VISP_HAVE_D3D9))
#if defined(VISP_HAVE_OPENCV) || defined(VISP_HAVE_V4L2) || defined(VISP_HAVE_DIRECTSHOW) || defined(VISP_HAVE_DC1394_2)
unsigned int recordImageSequence(const std::string& out_path, const unsigned int opt_step, const unsigned int first_image);
#endif
int main(int argc, const char ** argv)
{
double px = 600 ;
double py = 600 ;
double u0 = 0;
double v0 = 0;
double sizePrecision = 0.5 ;
double Lx = 0.03;
double Ly = 0.03;
unsigned int sizeX = 6;
unsigned int sizeY = 6;
unsigned int nbpt = sizeX*sizeY;
const unsigned int nptPose = 4;
std::list<double> LoX,LoY,LoZ;
for (unsigned int i=0 ; i < sizeX ; i++){
for(unsigned int j=0 ; j < sizeY ; j++){
LoX.push_back(i*Lx) ;
LoY.push_back(j*Ly) ;
LoZ.push_back(0) ;
}
}
std::string env_ipath;
std::string opt_ipath;
std::string ipath;
std::string opt_ppath;
std::string dirname;
std::string filename;
std::string filename_out;
char comment[FILENAME_MAX];
double opt_gray = 0.7;
unsigned opt_first = 1;
unsigned opt_nimages = 4;
unsigned opt_step = 1;
double opt_lambda = 0.5;
bool opt_display = true;
bool opt_click = true;
bool save = false;
bool opt_video = false;
std::string opt_video_image_path;
double dotSize;
char *ptenv = getenv("VISP_INPUT_IMAGE_PATH");
if (ptenv != NULL)
env_ipath = ptenv;
if (! env_ipath.empty())
ipath = env_ipath;
if (getOptions(argc, argv, opt_ipath, opt_ppath,opt_gray,opt_first, opt_nimages,
opt_step, opt_lambda, opt_display, opt_click, opt_video, opt_video_image_path) == false) {
return (-1);
}
if(opt_video){
#if (defined(VISP_HAVE_OPENCV) || defined(VISP_HAVE_V4L2) || defined(VISP_HAVE_DIRECTSHOW) || defined(VISP_HAVE_DC1394_2))
if(!opt_display){
std::cerr << std::endl
<< "ERROR:" << std::endl;
std::cerr << "Incompatible options -v and -d." << std::endl;
return -1;
}
if(!opt_click){
std::cerr << std::endl
<< "ERROR:" << std::endl;
std::cerr << "Incompatible options -v and -c." << std::endl;
return -1;
}
if(!opt_ipath.empty()){
std::cerr << std::endl
<< "ERROR:" << std::endl;
std::cerr << "Incompatible options -v and -i." << std::endl;
return -1;
}
if(!opt_ppath.empty()){
std::cerr << std::endl
<< "ERROR:" << std::endl;
std::cerr << "Incompatible options -v and -p." << std::endl;
return -1;
}
if(opt_video_image_path.empty()){
std::cerr << std::endl
<< "ERROR:" << std::endl;
std::cerr << "output image path empty." << std::endl;
return -1;
}
try{
opt_nimages = recordImageSequence(opt_video_image_path, opt_step, opt_first);
}
catch(...){
return -1;
}
opt_ipath = opt_video_image_path;
opt_ppath = opt_video_image_path;
#else
{
std::cerr << std::endl
<< "ERROR:" << std::endl;
std::cerr << "No framegrabber installed with ViSP. Cannot record images from video stream." << std::endl;
return -1;
}
#endif
}
if (!opt_display)
opt_click = false;
if (!opt_ipath.empty())
ipath = opt_ipath;
if (opt_ipath.empty() && opt_ppath.empty()) {
if (ipath != env_ipath) {
std::cout << std::endl
<< "WARNING: " << std::endl;
std::cout << " Since -i <visp image path=" << ipath << "> "
<< " is different from VISP_INPUT_IMAGE_PATH=" << env_ipath << std::endl
<< " we skip the environment variable." << std::endl;
}
}
if (opt_ipath.empty() && env_ipath.empty() && opt_ppath.empty() ){
usage(argv[0], NULL, ipath, opt_ppath, opt_gray, opt_first, opt_nimages,
opt_step, opt_lambda);
std::cerr << std::endl
<< "ERROR:" << std::endl;
std::cerr << " Use -i <visp image path> option or set VISP_INPUT_IMAGE_PATH "
<< std::endl
<< " environment variable to specify the location of the " << std::endl
<< " image path where test images are located." << std::endl
<< " Use -p <personal image path> option if you want to "<<std::endl
<< " use personal images." << std::endl
<< std::endl;
return(-1);
}
unsigned iter = opt_first;
std::ostringstream s;
char cfilename[FILENAME_MAX];
if (opt_ppath.empty()){
s.setf(std::ios::right, std::ios::adjustfield);
s << "grid36-" << std::setw(2) << std::setfill('0') << iter << ".pgm";
filename = dirname + s.str();
}
else {
sprintf(cfilename,opt_ppath.c_str(), iter) ;
filename = cfilename;
}
try{
}
catch(...)
{
std::cerr << std::endl
<< "ERROR:" << std::endl;
std::cerr << " Cannot read " << filename << std::endl;
std::cerr << " Check your -i " << ipath << " option, " << std::endl
<< " or your -p " << opt_ppath << " option " <<std::endl
<< " or VISP_INPUT_IMAGE_PATH environment variable"
<< std::endl;
return(-1);
}
unsigned int niter = 0;
char title[100];
#if defined VISP_HAVE_GDI
#elif defined VISP_HAVE_GTK
#elif defined VISP_HAVE_X11
#elif defined VISP_HAVE_D3D9
#endif
if (opt_display) {
sprintf(title, "Calibration initialization on image %s", (s.str()).c_str());
display.
init(I, 100, 100, title) ;
}
while (iter < opt_first + opt_nimages*opt_step) {
try {
if (opt_ppath.empty()){
s.str("");
s << "grid36-" << std::setw(2) << std::setfill('0') << iter<< ".pgm";
filename = dirname + s.str();
}
else {
sprintf(cfilename, opt_ppath.c_str(), iter) ;
filename = cfilename;
}
filename_out = filename + ".txt";
std::cout << "read : " << filename << std::endl;
if (opt_display) {
try{
sprintf(title, "Calibration initialization on image %s", (s.str()).c_str());
}
catch(...){
delete [] table_cal;
return(-1);
}
}
try{
for(unsigned int i=0;i<nptPose;i++) {
if (opt_click) {
std::printf("click in the dot %d of coordinates\nx=%f y=%f z=%f \n",
i+1 ,P[i].get_oX(),P[i].get_oY(),P[i].get_oZ());
std::sprintf(comment,"Click in the dot %d",i+1 );
for(unsigned int j = 0;j<i;j++)
d[j].display(I) ;
try{
}
catch(...){
}
}
else{
}
if (opt_display) {
}
}
}
delete [] table_cal;
return(-1);
}
for (unsigned int i=0 ; i < nptPose ; i++){
double x=0, y=0;
}
for (unsigned int i=0 ; i < nptPose ; i++){
calib.
addPoint(P[i].get_oX(),P[i].get_oY(),P[i].get_oZ(), cog);
}
try{
}
catch(...){
if(opt_click){
"A left click to define other dots.",
"A middle click to don't care of this pose.",
std::cout << "\nPose computation failed." << std::endl;
std::cout << "A left click to define other dots." << std::endl;
std::cout << "A middle click to don't care of this pose." << std::endl;
switch(button){
case 1 :
std::cout << "Left click has been pressed." << std::endl;
continue;
case 3 :
std::cout << "Right click has been pressed." << std::endl;
continue;
case 2 :
std::cout << "Middle click has been pressed." << std::endl;
iter += opt_step ;
niter++;
continue;
}
}
else{
iter += opt_step ;
niter++;
continue;
}
}
if (opt_display) {
for(unsigned int j = 0;j<nptPose;j++)
d[j].display(I) ;
if(opt_click){
"A left click to display grid.",
"A right click to define other dots.",
std::cout << "\nA a left click to display grid." << std::endl;
std::cout << "A right click to define other dots." << std::endl;
switch(button){
case 1 :
std::cout << "Left click has been pressed." << std::endl;
break;
case 2 :
std::cout << "Middle click has been pressed." << std::endl;
continue;
case 3 :
std::cout << "Right click has been pressed." << std::endl;
continue;
}
}
}
dotSize = 0;
for(unsigned i =0 ; i<nptPose ;i++){
}
dotSize /= nptPose;
for(unsigned int i=0;i<nbpt;i++){
}
std::list<double>::const_iterator it_LoX = LoX.begin();
std::list<double>::const_iterator it_LoY = LoY.begin();
std::list<double>::const_iterator it_LoZ = LoZ.begin();
for(unsigned int i = 0 ; i < nbpt ; i++){
++it_LoX;
++it_LoY;
++it_LoZ;
}
bool* valid = new bool[nbpt];
for (unsigned int i=0 ; i < nbpt ; i++){
valid[i] = true;
try {
valid[i] = false;
double x=0, y=0;
if (opt_display) {
if(valid[i]){
}
}
}
catch(...){
valid[i] = false;
}
}
else {valid[i] = false;}
}
if(save == true) {
table_cal[niter].
writeData(filename_out.c_str());
}
if (opt_click) {
sprintf(title, "Extracted 2D data from image %s", (s.str()).c_str());
"A left click to validate this pose.",
"A right click to retry.",
"A middle click to don't care of this pose.",
std::cout << "\nA left click to validate this pose." << std::endl;
std::cout << "A right click to retry." << std::endl;
std::cout << "A middle click to don't care of this pose." << std::endl;
switch(button){
case 1 :
std::cout << "\nLeft click has been pressed." << std::endl;
break;
case 2 :
std::cout << "Middle click has been pressed." << std::endl;
for (unsigned int i=0 ; i < nbpt ; i++)
valid[i]=false;
break;
case 3 :
std::cout << "Right click has been pressed." << std::endl;
continue;
}
}
for (unsigned int i=0 ; i < nbpt ; i++){
if(valid[i]){
table_cal[niter].
addPoint(mP[i].get_oX(),mP[i].get_oY(),mP[i].get_oZ(), cog) ;
}
}
delete [] mP;
delete [] md;
delete [] valid;
niter++ ;
}
catch(...) {
return(-1) ;
}
iter += opt_step ;
}
if(resultCalib == 0)
std::cout << cam2 << std::endl;
else
std::cout << "Calibration without distortion failed." << std::endl;
if(resultCalibDist == 0)
std::cout << cam << std::endl;
else
std::cout << "Calibration with distortion failed." << std::endl;
iter = opt_first;
niter = 0;
while (iter < opt_first + opt_nimages*opt_step) {
try {
if (opt_ppath.empty()){
s.str("");
s << "grid36-" << std::setw(2) << std::setfill('0') << iter<< ".pgm";
filename = dirname + s.str();
}
else {
sprintf(cfilename, opt_ppath.c_str(), iter) ;
filename = cfilename;
}
std::cout << "read : " << filename << std::endl;
if(table_cal[niter].get_npt()!=0){
std::cout << "\nCompute standard deviation for pose " << niter <<std::endl;
double deviation, deviation_dist ;
std::cout << "deviation for model without distortion : "
<< deviation << std::endl;
std::cout << "deviation for model with distortion : "
<< deviation_dist << std::endl;
}
else{
std::cout << "This image has not been used!" << std::endl;
}
if (opt_display) {
try{
sprintf(title, "Calibration results for image %s", (s.str()).c_str());
}
catch(...){
delete [] table_cal;
return(-1);
}
if(opt_click){
std::cout << "\nA click to continue..." << std::endl;
}
}
niter++;
}
catch(...) {
delete [] table_cal;
return(-1) ;
}
iter += opt_step ;
}
#ifdef VISP_HAVE_XML2
if(resultCalib == 0){
std::cout << "Camera parameters without distortion successfully saved in calibrate2dGrid.xml" << std::endl;
else
std::cout << "Failed to save the camera parameters without distortion in calibrate2dGrid.xml" << std::endl;
}
if(resultCalibDist == 0){
std::cout << "Camera parameters with distortion successfully saved in calibrate2dGrid.xml" << std::endl;
else
std::cout << "Failed to save the camera parameters with distortion in calibrate2dGrid.xml" << std::endl;
}
#endif
delete [] table_cal;
return(0);
}
#if defined(VISP_HAVE_OPENCV) || defined(VISP_HAVE_V4L2) || defined(VISP_HAVE_DIRECTSHOW) || defined(VISP_HAVE_DC1394_2)
unsigned int recordImageSequence(const std::string& out_path, const unsigned int opt_step, const unsigned int first_image)
{
unsigned int nbImg = first_image;
unsigned int index = 0;
#ifdef VISP_HAVE_OPENCV
#elif defined(VISP_HAVE_V4L2)
#elif defined(VISP_HAVE_DIRECTSHOW)
#elif defined(VISP_HAVE_DC1394_2)
#endif
#if defined VISP_HAVE_GDI
#elif defined VISP_HAVE_GTK
#elif defined VISP_HAVE_X11
#elif defined VISP_HAVE_D3D9
#endif
display.
init(I, 100, 100,
"record sequence for the calibration.");
bool isOver = false;
std::cout << "Left click to record the current image." << std::endl;
std::cout << "Right click to stop the acquisition." << std::endl;
while(!isOver){
char curImgName[FILENAME_MAX];
sprintf(curImgName, out_path.c_str(), nbImg);
nbImg += opt_step;
index++;
try{
std::cout << "write image : " << curImgName << std::endl;
}
catch(...){
std::cerr << std::endl
<< "ERROR." << std::endl
<< "Cannot record the image : " << curImgName << std::endl
<< "Check the path and the permissions." << std::endl;
}
}
isOver = true;
}
}
}
return index;
}
#endif
#else // (defined (VISP_HAVE_GTK) || defined(VISP_HAVE_GDI)...)
int
main()
{
vpTRACE(
"X11 or GTK or GDI or D3D functionnality is not available...") ;
}
#endif // (defined (VISP_HAVE_GTK) || defined(VISP_HAVE_GDI)...)