2013年1月29日 星期二

攝像頭校正 camera calibration - part3 OpenCV programming

攝像頭校正 camera calibration - part3 OpenCV programming

在前兩篇文章  camera calibration -part1 camera model 以及 camera calibration -part2 camera calibration 中介紹了 camera 內部參數(intrinsic parameter) , 形變參數 (distortion parameter) 以及校正的方法和原理, 本文說明如何使用 OpenCV 實作camera校正 程序.
校正的程序分為兩部分;




          (1)影像的擷取,
          (2)讀取步驟 (1) 擷取的影像序列, 進行校正.
第 (1) 部分的功能是影像擷取以及存檔, 儲存的影像序列供步驟 (2) 使用, 這部分的程式代碼比較簡單:
01 #include <stdio.h>
02 #include <iostream>
03 #include <vector>
04 #include "StdAfx.h"
05 #include "CameraDS.h"
06 
07 #include <cv.h>
08 #include <highgui.h>
09 #include "opencv2/opencv.hpp"
10 
11 
12 using namespace cv;
13 using namespace std;
14 
15 
16 #define CAP_WIDTH  640
17 #define CAP_HEIGHT 480
18 int main()
19 {
20     cv::Mat img;
21     IplImage* capimg;
22     int  DeviceCamCount = 0;
23     CCameraDS *pCamDS   = NULL;
24 
25     int        CamCount = 0;
26     int        CamIndex[10];
27 
28     pCamDS  = new CCameraDS();
29     DeviceCamCount = pCamDS->CameraCount();
30     if ( DeviceCamCount == 0 )
31     {
32         printf( "No Camera detected....\n" );
33         exit(0);
34     }
35 
36     for (int i = 0, j = 0; i < DeviceCamCount; i++)
37     {
38         char szCamName[1024];
39 
40         int retval = pCamDS->CameraName(i, szCamName, sizeof(szCamName));
41 
42         if (retval >0)
43         {
44             printf("Camera #%d's Name is '%s'.\n", i, szCamName);
45             if ( !strcmp( szCamName, "USB µø°T¸Ë¸m" ) )
46             {
47                 CamIndex[ j++ ] = i;
48                 CamCount++;
49             }
50         }
51         else
52         {
53             printf("Can not get Camera #%d's name.\n", i);
54         }
55     }
56 
57     if ( pCamDS->OpenCamera( CamIndex[0], false,
58                              CAP_WIDTH, CAP_HEIGHT ) == false )
59     {
60         printf( "USB Camera 0 do not support w=%d:h=%d\n",
61                 CAP_WIDTH, CAP_HEIGHT );
62         return (-1);
63     }
64 
65     int loop  = 1;
66     int key;
67     int file_idx = 0;
68     char filename[256];
69     char fileidx[10];
70 
71     while ( loop )
72     {
73         //camera >> img;
74         capimg=pCamDS->QueryFrame();
75         img = capimg;
76         if (img.empty())
77         {
78             std::cerr << "ERROR: Could not capture image" << std::endl;
79         }
80         imshow("image", img);
81         key = cvWaitKey( 5 );
82         if ( key == 27 )
83         {
84             loop = 0;
85         }
86         else if (key =='s' || key=='S' )
87         {
88             sprintf( fileidx, "%03d", file_idx++ );
89             strcpy( filename, "SAVE_IMG" );
90             strcat( filename, fileidx );
91             strcat( filename, ".bmp" );
92 
93             imwrite( filename, img );
94             printf("Image file %s saved.\n", filename);
95         }
96         else if (key =='r' || key=='R' )
97         {
98             file_idx = 0;
99         }
100     }
101     //camera.close();
102 }
103 

代碼 86~95, 當擺放好 校正板後, 按下 S 鍵, 就可以存檔. 檔名固定為 SAVE_IMGXXX.bmp
如圖一所式 為筆者測試用的影像序列:

圖一 校正用影像序列
擺放的訣竅在於儘量放置在畫面的邊緣, 並且包含不同的深度. 基本上需要 15~20 張有效的影像.

第二部分的程式代碼如下:
01 #include <stdio.h>
02 #include <iostream>
03 #include <vector>
04 #include "StdAfx.h"
05 #include "CameraDS.h"
06 
07 #include <cv.h>
08 #include <highgui.h>
09 #include "opencv2/opencv.hpp"
10 
11 
12 using namespace cv;
13 using namespace std;
14 
15 
16 #define CAP_WIDTH  640
17 #define CAP_HEIGHT 480
18 
19 std::vector<std::vector<cv::Point3f>> objectPoints;
20 std::vector<std::vector<cv::Point2f>> imagePoints;
21 
22 cv::Mat cameraMatrix;
23 cv::Mat distCoeffs;
24 
25 cv::Mat map1,map2;
26 
27 int main()
28 {
29     cv::Mat img;
30     cv::Mat img2;
31     IplImage* capimg;
32     int  DeviceCamCount = 0;
33     CCameraDS *pCamDS   = NULL;
34 
35     int        CamCount = 0;
36     int        CamIndex[10];
37 
38     pCamDS  = new CCameraDS();
39     DeviceCamCount = pCamDS->CameraCount();
40 
41     int loop  = 1;
42     int key;
43     int file_idx = 0;
44     char filename[256];
45     char fileidx[10];
46 
47     int i;
48     cv::Size boardSize( 8, 6 );
49     cv::Size imageSize(CAP_WIDTH, CAP_HEIGHT);
50     std::vector<cv::Point2f> imageCorners;
51     std::vector<cv::Point3f> objectCorners;
52 
53     // The corners are at 3D location (X,Y,Z)= (i,j,0)
54     for (int i=0; i<boardSize.height; i++)
55     {
56         for (int j=0; j<boardSize.width; j++)
57         {
58             objectCorners.push_back(cv::Point3f(i, j, 0.0f));
59         }
60     }
61 
62     while ( loop )
63     {
64         // find file:
65         sprintf( fileidx, "%03d", file_idx++ );
66         strcpy( filename, "SAVE_IMG" );
67         strcat( filename, fileidx );
68         strcat( filename, ".bmp" );
69 
70         printf("IMG = %s\n", filename);
71 
72         img = imread( filename, 0 );
73         if ( img.empty())
74             loop = 0;
75         else
76         {
77             bool found = cv::findChessboardCorners(
78                              img, boardSize, imageCorners);
79 
80             cornerSubPix(img, imageCorners,
81                          cv::Size(5,5),
82                          cv::Size(-1,-1),
83                          cv::TermCriteria(cv::TermCriteria::MAX_ITER +
84                                           cv::TermCriteria::EPS,
85                                           30,      // max number of iterations
86                                           0.1));   // min accuracy
87             if (imageCorners.size() == boardSize.area())
88             {
89                 imagePoints.push_back(imageCorners);
90                 objectPoints.push_back(objectCorners);
91             }
92             img.copyTo( img2 );
93             drawChessboardCorners(img2, boardSize, imageCorners, found);
94 
95             imshow( "image", img2 );
96 
97             key = cvWaitKey(400);
98             //key = cvWaitKey( 5 );
99             if ( key == 27 )
100             {
101                 loop = 0;
102             }
103         }
104     }
105 
106     std::vector<cv::Mat> rvecs, tvecs;
107     // start calibration
108     calibrateCamera(objectPoints, // the 3D points
109                     imagePoints, // the image points
110                     imageSize,   // image size
111                     cameraMatrix,// output camera matrix
112                     distCoeffs,  // output distortion matrix
113                     rvecs, tvecs,// Rs, Ts
114                     0);       // set options
115 
116     FileStorage fs("test.yml", FileStorage::WRITE);
117     fs << "intrinsic" << cameraMatrix;
118     fs << "distcoeff" << distCoeffs;
119 }
120 
筆者使用的校正 pattern 長寬各有 8 以及 6 個角點. 因此代碼 48 行 設定為 boardSize(8, 6)
54~60 行定義校正 pattern 角點的 3D coordinate.
62 行的 while loop 將影像序列 一張一張的讀入, 每讀入一張, 就以 77 以及 80 行的代碼進行角點提取. 87 行判斷是否正常提取到校正 pattern  上的所有48 個角點, 若是, 則將這些角點在影像上的座標儲存起來. 代碼 93 行則是將其取到的角點加以標示. 當所有的影像都完成角點提取, 則 108 行使用 calibrationCamera 進行攝像頭校正.
在這裡, 我們感興趣的是 camera 內部參數以及型變參數,  calibrationCamera 會為我們計算這些參數並分別儲存在 cameraMatrix 以及 distCoeffs.
116~118 行, 我將 cameraMatrix以及 distCoeffs 的內容儲存到 test.yml 檔案.
圖二是其中一張影像的角點提取結果:
圖二 角點提取

校正出的內參以及型變參數如下:

%YAML:1.0
intrinsic: !!opencv-matrix
   rows: 3
   cols: 3
   dt: d
   data: [ 7.2042901435965223e+002, 0., 3.2967552840264563e+002, 0.,
             7.1972210146332782e+002, 2.6487944722387311e+002, 0., 0., 1. ]
distcoeff: !!opencv-matrix
   rows: 1
   cols: 5
   dt: d
   data: [ 1.0477467445014127e-001, -1.1727926119997072e+000,
             3.4074683728567670e-003, 4.3480547323742199e-003,
             2.2097821427154618e+000 ]


fx = 7.2042901435965223e+002
fy = 7.1972210146332782e+002
cx = 3.2967552840264563e+002
cy = 2.6487944722387311e+002

k1 = 1.0477467445014127e-001
k2 = -1.1727926119997072e+000
p1 = 3.4074683728567670e-003
p2 = 4.3480547323742199e-003
k3 = 2.2097821427154618e+000

26 則留言:

  1. 您好,請問一下
    OpenCV這相機校正,是不是校正完後會產生參數檔?之後可否只要直接讀取該檔案的數值就好?不然每一次都要執行一次感覺很花時間的說

    另外不知可否像您請教個人遇到的一些問題
    因為我做相機校正,每一次結果都會不一樣
    我不知道怎樣才是正確的結果...
    下面兩部影片是我兩次校正結果,希望您有空的話能給予解答
    http://www.youtube.com/watch?v=kPR4FoHz7C8
    http://www.youtube.com/watch?v=5x0unaA2FSc


    謝謝

    回覆刪除
  2. (1)是的, 只要把 yml 檔讀入就可以了, 不需要重複 capture image 以及 計算校正參數.

    (2)會有一些不一樣, 以形變來說, 可以使用 openCV 的 initUndistortRectifyMap 函式
    進行 undistortion. 並使用一些測試用的 pattern, 例如 line grid pattern; 網格圖, 確認undistortion 的品質.

    回覆刪除
  3. 您好:
    請問如果我想得到外部參數的話要怎麼做呢?
    rotation matrix+translation matrix應該會是3x4的一個矩陣。
    我有試著去寫出extrinsic_parameters:在yml上,但是寫出來的東西好像怪怪的一組rotation vector+translation vector只有六個數字。到底要怎麼看不太清楚,不知道能不能幫我解答一下,謝謝~

    回覆刪除
    回覆
    1. 您好,我目前也有這個困擾
      請問樓主找到答案了嗎 ? 謝謝

      刪除
    2. 透過calibrateCamera()會得到的15~20組rotation vector + translation vector,
      每組rotation vector + translation vector即是擷取的影像當下的外部參數。

      如果想得到某張擷取影像當下的外部參數,可以用Rodrigues()將rotation vector轉成rotation matrix,此時rotation matrix + translation matrix就會是一個3x4的矩陣

      刪除
  4. 你好:
    程式碼第 113 行, 的兩個參數: rvecs, tvecs 是外部參數(extrinsic parameters).
    rvecs 是 rotation matrix, tvecs 是 translation matrix.

    回覆刪除
  5. Richard您好:
    請問我依照您 [OpenCV 影像擷取 - 使用 DirectShow]方法
    也做一次,但無法顯示USB影像,ERROR如下,請幫我看看,
    感謝。

    WIN7 64bit+OPENCV246+VS2012
    有抓到筆電內CCD,但報ERROR如下

    Debug Assertion Failed

    program....ments\Visual Studio 2012\Projects\ccd_in\
    x64\Debug\ccd_in.exe
    File:C:\Program File (x86)\Microsoft Visual Studio 11.0\
    VC\atlmfc\include\atlcomcli.h
    Line:197
    Expression:p!=0

    For information on how your program can cause an assertion
    failure,see the Visual C++ documentation on asserts.

    回覆刪除
    回覆
    1. 匿名 你好
      我的平台是 Visual Studio 2010 + windows XP 32bits
      並不會有這個問題.

      刪除
    2. 此篇樓主您好,我也有同樣問題,請問您後來有解決了嗎?
      若有可否提供,謝謝

      刪除
  6. 想請問一下,我在執行程式的時候,顯示說找不到下方此標頭檔,要如何解決
    #include "CameraDS.h"

    回覆刪除
  7. build code 環境設定請參考我blog的另一篇文章, 標題是
    OpenCV 影像擷取 - 使用 DirectShow.

    回覆刪除
  8. 有沒有效正camera color matrix的方法呢?
    想進一步瞭解...
    thanks

    回覆刪除
  9. 想請問一下
    我使用OpencCV校正過一次後
    接著將失真的圖形remap成無失真圖片
    並接著再對其無失真的圖片校正一次
    結果二次校正並remap的圖片邊緣會嚴重失真
    這個是甚麼原因呢?
    是否OpenCV不能對已經校正過的相機(如手機相機)做校正呢?
    這樣我要如何得到focal length?

    回覆刪除
  10. 作者已經移除這則留言。

    回覆刪除
  11. 請問OpenCv 3.0 的intrinsics.yml 跟extrinsics.yml跟之前版本的有何不同呢?
    矩陣代表的意義是否也不同了呢?

    回覆刪除
  12. 請問整個型變參數在整個相機校正的流程當中

    是使用在世界座標轉成相機座標後

    還是使用在相機座標轉乘影像座標後使用

    因為前面介紹內外部參數矩陣,都沒有包含扭曲參數的矩陣

    那這個矩陣到底用在哪個部分呢?

    回覆刪除
  13. 請問為何54~60那段
    為何objectCorners可以直接那樣取得??謝謝!!

    回覆刪除
  14. 您好,目前我碰到了一個很大的問題,我校正完發現所使用OpenCV的function為了維持校正完影像和原始影像相同大小,所以校正完會有切除超出影像大小的部分,不知道是否有方法可以在校正完的影像上得到原始影像的所有資訊

    回覆刪除
  15. 您好,目前我碰到了一個很大的問題,我校正完發現所使用OpenCV的function為了維持校正完影像和原始影像相同大小,所以校正完會有切除超出影像大小的部分,不知道是否有方法可以在校正完的影像上得到原始影像的所有資訊

    回覆刪除
  16. 作者已經移除這則留言。

    回覆刪除