00001 
00002 
00003 
00004 
00005 
00006 
00007 
00008 
00009 
00010 
00011 
00012 
00013 
00014 
00015 
00016 
00017 
00018 
00019 
00020 
00048 #include <iostream>
00049 #include <fstream>
00050 
00051 #include <QVMatrix>
00052 #include <QVApplication>
00053 #include <QVImageCanvas>
00054 #include <QVVideoReaderBlock>
00055 #include <QVDefaultGUI>
00056 
00057 #include <qvip.h>
00058 #include <qvimageio.h>
00059 #include <qvprojective.h>
00060 
00061 #ifndef DOXYGEN_IGNORE_THIS
00062 #include <cv.h>
00063 #include <highgui.h>
00064 
00065 #define NUM_CHESSBOARDS 16
00066 
00067 
00068 
00069 
00070 
00071 
00072 
00073 
00074 
00075 
00076 
00077 
00078 
00079 
00080 
00081 
00082 
00083 
00084 
00085 
00086 
00087 
00088 
00089 
00090 
00091 
00092 
00093 
00094 
00095 
00096 
00097 
00098 
00099 
00100 
00101 
00102 
00103 
00104 
00105 
00106 
00107 
00108 
00109 double  error(const double u, const double v, const double x, const double y, const QVVector k)
00110         {
00111         const double    r2 = x*x+y*y, r4 = r2*r2, r6 = r4*r2, r8 = r4*r4,
00112                         d = 1 + k[0] * r2 + k[1] * r4 + k[2] * r6 + k[3] * r8;
00113         const double    errorU = u - x * d, errorV = v - y * d;
00114 
00115         return errorU * errorU + errorV * errorV;
00116         }
00117 
00118 QPointF findMinimum(const QPointF &orig, const QVVector k)
00119         {
00120         const int       u = orig.x(), v = orig.y();
00121         QPointF actual = orig;
00122 
00123         while(true)
00124                 {
00125                 QPointF best = actual;
00126                 double bestError = std::numeric_limits<double>::max();
00127         
00128                 for (double i = -0.01; i <= 0.01; i+=0.0025)
00129                         for (double j = -0.01; j <= 0.01; j+=0.0025)
00130                                 if (error(u, v, actual.x()+i, actual.y()+j, k) < bestError)
00131                                         best = actual + QPoint(i, j);
00132                 if (actual == best)
00133                         break;
00134 
00135                 actual = best;
00136                 }
00137         return actual;
00138         }
00139 
00140 class CameraCalibration
00141         {
00142         public:
00143                 int cols, rows;
00144                 QVMatrix K, distortionCoeffs;
00145 
00146                 QVMatrix Kinv;
00147                 IplImage *dx, *dy;
00148 
00149                 bool initCacheMap(IplImage *&dx_, IplImage *&dy_, const QVMatrix &A2 = QVMatrix::identity(3))
00150                         {
00151                         Kinv = pseudoInverse(K);
00152 
00153                         CvMat   * camera_matrix = K.toCvMat(CV_32F),
00154                                 * dist_coeffs = distortionCoeffs.toCvMat(CV_32FC1),
00155                                 * new_camera_matrix = A2.toCvMat(CV_32F);
00156 
00157                         if (dx_ != NULL)
00158                                 cvReleaseImage(&dx);
00159                         if (dy_ != NULL)
00160                                 cvReleaseImage(&dy);
00161 
00162                         if (cols == 0 or rows == 0)
00163                                 return false;
00164 
00165                         dx_ = cvCreateImage(cvSize(cols, rows), IPL_DEPTH_32F, 1);
00166                         dy_ = cvCreateImage(cvSize(cols, rows), IPL_DEPTH_32F, 1);
00167 
00168                         cvInitUndistortRectifyMap(camera_matrix, dist_coeffs, NULL , new_camera_matrix, dx_, dy_);
00169 
00170                         cvReleaseMat(&camera_matrix);
00171                         cvReleaseMat(&dist_coeffs);
00172                         cvReleaseMat(&new_camera_matrix);
00173 
00174                         return true;
00175                         }
00176 
00177         public:
00178                 CameraCalibration(const CameraCalibration &other):
00179                         cols(other.cols), rows(other.rows), K(other.K), distortionCoeffs(other.distortionCoeffs), dx(NULL), dy(NULL)
00180                         {
00181                         initCacheMap(dx, dy);
00182                         }
00183 
00184                 CameraCalibration(      const int cols = 0, const int rows = 0,
00185                                         const QVMatrix &K = QVMatrix::identity(3),
00186                                         const QVMatrix &distortionCoeffs = QVMatrix(4, 1, 0.0)):
00187                         cols(cols), rows(rows), K(K), distortionCoeffs(distortionCoeffs), dx(NULL), dy(NULL)
00188                         {
00189                         initCacheMap(dx,dy);
00190                         }
00191 
00192                 ~CameraCalibration()
00193                         {
00194                         if (dx != NULL)
00195                                 cvReleaseImage(&dx);
00196                         if (dy != NULL)
00197                                 cvReleaseImage(&dy);
00198                         }
00199 
00200                 CameraCalibration & operator = (const CameraCalibration &other)
00201                         {
00202                         cols = other.cols;
00203                         rows = other.rows;
00204                         K = other.K;
00205                         distortionCoeffs = other.distortionCoeffs;
00206                         initCacheMap(dx,dy);
00207                         return (*this);
00208                         }
00209 
00210                 bool inited() const
00211                         {
00212                         return (cols != 0 and rows != 0);
00213                         }
00214 
00215                 bool loadFromFile(const QString &fileName)
00216                         {
00217                         std::ifstream stream;
00218                         stream.open(qPrintable(fileName));
00219 
00220                         if ( stream.fail() )
00221                                 return false;
00222 
00223                         stream >> cols;
00224                         stream >> rows;
00225                         stream >> K;
00226                         stream >> distortionCoeffs;
00227 
00228                         stream.close();
00229 
00230                         return initCacheMap(dx,dy);
00231                         }
00232 
00233                 bool saveToFile(const QString &fileName) const
00234                         {
00235                         std::ofstream stream;
00236                         stream.open(qPrintable(fileName));
00237 
00238                         if ( stream.fail() )
00239                                 return false;
00240 
00241                         stream << cols << std::endl;
00242                         stream << rows << std::endl;
00243                         stream << K;
00244                         stream << distortionCoeffs;
00245 
00246                         stream.close();
00247 
00248                         return true;
00249                         }
00250 
00251                 QPointF map(const int i, const int j) const
00252                         {
00253                         
00254                         return QPointF(CV_IMAGE_ELEM(dx, float, j, i), CV_IMAGE_ELEM(dy, float, j, i));
00255                         }
00256 
00257                 QVImage<uChar, 1> radialUndistort(const QVImage<uChar, 1> &image)
00258                         {
00259                         if (cols == 0 or rows == 0)
00260                                 return QVImage<uChar, 1>();
00261 
00262                         
00263                         QVMatrix A2 = K;
00264                         A2(0,0) = 0.75*A2(0,0);
00265                         A2(1,1) = 0.75*A2(1,1);
00266                         A2(0,2) = 160.0;
00267                         A2(1,2) = 100.0;
00268 
00269                         IplImage *dx_ = NULL, *dy_ = NULL;
00270 
00271                         initCacheMap(dx_, dy_, A2);
00272 
00273                         
00274                         IplImage *src = image, *dst = image;
00275  
00276                         cvRemap(src, dst, dx_, dy_, CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS, cvScalarAll(0));
00277                         QVImage<uChar, 1> outputImage = QVImage<uChar, 1>(dst);
00278 
00279                         cvReleaseImage(&dx_);
00280                         cvReleaseImage(&dy_);
00281                         cvReleaseImage(&src);
00282                         cvReleaseImage(&dst);
00283 
00284                         return outputImage;
00285                         }
00286         };
00287 
00288 QHash<QV3DPointF, QPointF> detectBoard(const QVImage<uChar, 3> &actualImage, const int board_w = 6, const int board_h = 9)
00289         {
00290         const int board_n = board_w * board_h;
00291         const CvSize board_sz = cvSize(board_w, board_h);
00292         CvPoint2D32f* corners = new CvPoint2D32f[ board_n ];
00293 
00294         IplImage *image = actualImage;
00295         IplImage *gray_image = cvCreateImage(cvGetSize(image), 8, 1);
00296 
00297         CvSize image_sz = cvGetSize(image);
00298 
00299         int corner_count, found = cvFindChessboardCorners(
00300                                                 image, board_sz, corners, &corner_count,
00301                                                 CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FILTER_QUADS);
00302         
00303         
00304         cvCvtColor(image, gray_image, CV_BGR2GRAY);
00305         cvFindCornerSubPix(     gray_image, corners, corner_count, cvSize(11,11),cvSize(-1,-1),
00306                                 cvTermCriteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 30, 0.1));
00307 
00308         cvReleaseImage(&image);
00309         cvReleaseImage(&gray_image);
00310 
00311         QHash<QV3DPointF, QPointF> boardCorners;
00312         if(corner_count == board_n)
00313                 for(int j=0; j<board_n; ++j)
00314                         boardCorners[QV3DPointF(j/board_w, j%board_w, 0.0f)] = QPointF(corners[j].x, corners[j].y);
00315 
00316         delete corners;
00317         return boardCorners;
00318         }
00319 
00320 CameraCalibration calibrateIntrinsicsFrom3DTo2DCorrespondences(const QList< QHash<QV3DPointF, QPointF> > &pointCorrespondences, const int cols, const int rows)
00321         {
00322         
00323         int num_points = 0;
00324         for(int i = 0, index = 0; i < pointCorrespondences.size(); i++)
00325                 num_points += pointCorrespondences[index].count();
00326 
00327         
00328         CvMat* object_points  = cvCreateMat(num_points,3,CV_32FC1);
00329         CvMat* image_points   = cvCreateMat(num_points,2,CV_32FC1);
00330         CvMat* point_counts   = cvCreateMat(pointCorrespondences.size(),1,CV_32SC1);
00331 
00332         
00333         for(int i = 0, index = 0; i < pointCorrespondences.size(); i++)
00334                 {
00335                 foreach(QV3DPointF point3D, pointCorrespondences[i].keys())
00336                         {
00337                         CV_MAT_ELEM( *image_points, float, index, 0)    = pointCorrespondences[i][point3D].x();
00338                         CV_MAT_ELEM( *image_points, float, index, 1)    = pointCorrespondences[i][point3D].y();
00339                         CV_MAT_ELEM( *object_points, float, index, 0)   = point3D.x();
00340                         CV_MAT_ELEM( *object_points, float, index, 1)   = point3D.y();
00341                         CV_MAT_ELEM( *object_points, float, index, 2)   = point3D.z();
00342 
00343                         index++;
00344                         }
00345                 CV_MAT_ELEM(*point_counts, int, i, 0) = pointCorrespondences[i].count();
00346                 }
00347 
00348         
00349         CvMat* intrinsic_matrix  = cvCreateMat(3,3,CV_32FC1);
00350         CvMat* distortion_coeffs = cvCreateMat(4,1,CV_32FC1);
00351 
00352         CV_MAT_ELEM( *intrinsic_matrix, float, 0, 0 ) = 1.0f;
00353         CV_MAT_ELEM( *intrinsic_matrix, float, 1, 1 ) = 1.0f;
00354 
00355         
00356         cvCalibrateCamera2(object_points, image_points, point_counts, cvSize(cols,rows), intrinsic_matrix, distortion_coeffs, NULL, NULL,0);
00357 
00358         
00359         CameraCalibration result = CameraCalibration(cols, rows, intrinsic_matrix, distortion_coeffs);
00360 
00361         
00362         cvReleaseMat(&object_points);
00363         cvReleaseMat(&image_points);
00364         cvReleaseMat(&point_counts);
00365         cvReleaseMat(&intrinsic_matrix);
00366         cvReleaseMat(&distortion_coeffs);
00367 
00368         return result;
00369         }
00370 
00371 class CameraCalibratorBlock: public QVProcessingBlock
00372         {
00373         private:
00374                 QVImage<uChar, 3> actualImage;
00375 
00376                 QList< QHash<QV3DPointF, QPointF> > grabbedBoards;
00377 
00378                 CameraCalibration calibration;
00379 
00380         public:
00381                 CameraCalibratorBlock(QString name): QVProcessingBlock(name)
00382                         {
00383                         addProperty< QVImage<uChar,3> >("Input image", inputFlag|outputFlag);
00384                         addProperty< QVImage<uChar,3> >("Output image", outputFlag);
00385                         addProperty< QString >("Camera file name", inputFlag|outputFlag, "calibrated.camera");
00386 
00387                         addProperty< QList<QPointF> >("Board corners", outputFlag);
00388                         addTrigger("Grab chessboard");
00389                         addTrigger("Calibrate");
00390 
00391                         if (calibration.loadFromFile(getPropertyValue< QString >("Camera file name")))
00392                                 std::cout << "Camera loaded" << std::endl;
00393                         else
00394                                 std::cout << "Could not load camera" << std::endl;
00395                         }
00396 
00397                 void processTrigger(const QString triggerName)
00398                         {
00399                         if (triggerName == "Grab chessboard")
00400                                 {
00401                                 const QHash<QV3DPointF, QPointF> grabbedBoard = detectBoard(actualImage);
00402                                 if (grabbedBoard.count() > 0)
00403                                         {
00404                                         grabbedBoards << grabbedBoard;
00405                                         writeQVImageToFile(     QString("temp/frame-") + QString::number(getIteration()).QString::rightJustified(8, '0') + ".png",
00406                                                                 actualImage);
00407                                         }
00408                                 std::cout << "Captured " << grabbedBoards.size() << " boards"<< std::endl;
00409                                 }
00410                         else if (triggerName == "Calibrate")
00411                                 {
00412                                 std::cout << "Proceeding to camera calibration." << std::endl;
00413                                 calibration = calibrateIntrinsicsFrom3DTo2DCorrespondences(grabbedBoards, 320, 240);
00414 
00415                                 
00416                                 
00417 
00418 
00419 
00420 
00421 
00422 
00423 
00424 
00425 
00426 
00427 
00428 
00429 
00430                                 
00431 
00432                                 if (calibration.saveToFile("calibrated.camera"))
00433                                         std::cout << "Camera calibrated and stored." << std::endl;
00434                                 else
00435                                         std::cout << "Error storing camera." << std::endl;
00436                                 }
00437                         std::cout << "End of trigger processing" << std::endl;
00438                         }
00439 
00440                 void iterate()
00441                         {
00442                         actualImage = getPropertyValue<QVImage<uChar, 3> >("Input image");
00443 
00444                         const QHash<QV3DPointF, QPointF> boardCorners = detectBoard(actualImage);
00445                         setPropertyValue< QList<QPointF> >("Board corners", boardCorners.values());
00446 
00447                         if (not calibration.inited())
00448                                 return;
00449 
00450                         if (boardCorners.count() > 0)
00451                                 {
00452                                 QList<QPointFMatching> matchings;
00453                                 foreach(QV3DPointF point3D, boardCorners.keys())
00454                                         {
00455                                         const QPointF p = boardCorners[point3D];
00456                                         matchings << QPointFMatching(QPointF(point3D.x(), point3D.y()), applyHomography(calibration.Kinv,p));
00457                                         }
00458                                 const QVMatrix  H = computeProjectiveHomography(matchings),
00459                                                 errorM = H.transpose()*H,
00460                                                 normalizedErrorM = errorM * (2.0 / (errorM(0,0) + errorM(1,1)));
00461 
00462                                 const double a = errorM(0,0), b = errorM(0,1), c = errorM(1,0), d = errorM(1,1);
00463 
00464                                 std::cout << ( ABS(1-normalizedErrorM(0,0)) + ABS(1- normalizedErrorM(1,1)) + 2*ABS(normalizedErrorM(1,0)) ) << std::endl;
00465                                 std::cout << "H^t * H = " << normalizedErrorM << std::endl;
00466                                 }
00467 
00468                         
00469                         }
00470         };
00471 
00472 #include <QVVector>
00473 #include <QVMatrix>
00474 int main(int argc, char *argv[])
00475         {
00476         QVApplication app(argc, argv, "Example program for QVision library. Displays the contents of a video source.");
00477 
00478         QVVideoReaderBlock camera("Video");
00479 
00480         CameraCalibratorBlock player("Video player");
00481         camera.linkProperty(&player,"Input image");
00482 
00483         QVDefaultGUI interface;
00484 
00485         QVImageCanvas inputImageCanvas("Input image");
00486         player.linkProperty("Input image", inputImageCanvas);
00487         player.linkProperty("Board corners", inputImageCanvas);
00488 
00489         QVImageCanvas outputImageCanvas("Output image");
00490         player.linkProperty("Output image", outputImageCanvas);
00491 
00492 
00493         return app.exec();
00494         }
00495 #endif  // DOXYGEN_IGNORE_THIS
00496