Sunday, December 3, 2017

opencv - camera calibration 相機校正

---

這邊以相機校正的實作為主

就不談細節了

在做相機校正之前要先準備最好15張以上拍好的棋盤格圖片

如果不想自己照的朋友

可以先用我照好的,取其中15張左右就可以了



全部都使用也可以,只是運行時間當然地會花比較久

下載連結

而使用的照片越多,結果也會越精準

---

其中這個部分

#define n_corner_cols 8 //棋盤點行數
#define n_corner_rows 6 //棋盤點列數
#define corner_size 25  //棋盤格大小 (mm)


棋盤點的行數和列數是要看交界點的數量

而不是看棋盤的格子數

所以下面這張範例圖的棋盤行數和列數是8和6



---

程式碼:


#include "opencv2\opencv.hpp"

#include <windows.h>
#include <iostream>
#include <string>
#include <cstring>
#include <vector>
#include <fstream>
#include <iostream>
#include <direct.h>

using namespace std;
using namespace cv;

#define n_corner_cols 8 //棋盤點行數
#define n_corner_rows 6 //棋盤點列數
#define corner_size 25  //棋盤格大小 (mm)


void fprintMatrix(Mat matrix, string name) //輸出矩陣
{
 FILE * fp;
 fp = fopen(name.c_str(), "w");
 int i, j;
 printf("%s size %d %d\n", name.c_str(), matrix.cols, matrix.rows);
 for (i = 0; i< matrix.rows; ++i)
 {
  for (j = 0; j< matrix.cols; ++j)
  {
   fprintf(fp, "%lf ", matrix.at<  double >(i, j));
  }
  fprintf(fp, "\n");
 }

 fclose(fp);
}

vector <string> getAllFilesName(string inputFolder) //取得資料夾內所有的檔案
{
 vector <string> allFileName;

 int n_files = 0;
 wstring inputFolder_wchar = wstring(inputFolder.begin(), inputFolder.end());
 const wchar_t* lookForFile = inputFolder_wchar.c_str();
 WIN32_FIND_DATA findFileFullName;

 HANDLE hFind;
 hFind = FindFirstFile(lookForFile, &findFileFullName);

 if (hFind != INVALID_HANDLE_VALUE) 
 {

  do
  {
   wstring ws(findFileFullName.cFileName); //利用wstring 將wchar型態的檔案名稱轉成string型態
   string findFileFullName_str(ws.begin(), ws.end());

   allFileName.push_back(findFileFullName_str);

   n_files++;
  } while (FindNextFile(hFind, &findFileFullName) == TRUE);
 }

 cout << endl << "Number of files: " << n_files << endl;

 return allFileName;
}

void calibration(string folder_colorImg, string folder_calibration)
{
 vector <string> fileNames;
 fileNames = getAllFilesName(folder_colorImg + "*.png"); //設定要找的資料為.png檔

 vector<vector<Point2f>> corner_all_2D;
 vector<vector<Point3f>> corner_all_3D;

 Size image_size = imread(folder_colorImg + fileNames[0], CV_LOAD_IMAGE_COLOR).size();

 for (int index_file = 0; index_file < fileNames.size(); index_file++)
 {
  cout << fileNames[index_file] << endl;

  //read files
  Mat img_gray = imread(folder_colorImg + fileNames[index_file], CV_LOAD_IMAGE_GRAYSCALE);
  Mat img_color = imread(folder_colorImg + fileNames[index_file], CV_LOAD_IMAGE_COLOR);
  imshow("img gray", img_gray);

  //find corners 
  vector <Point2f> corner_per_2D;
  vector <Point3f> corner_per_3D;

  bool isFindCorner = findChessboardCorners(img_gray, Size(n_corner_cols, n_corner_rows), corner_per_2D);
  if (isFindCorner && (corner_per_2D.size() == n_corner_rows*n_corner_cols))
  {

   //2D corners
   cv::TermCriteria param(cv::TermCriteria::MAX_ITER + cv::TermCriteria::EPS, 30, 0.1);
   cornerSubPix(img_gray, corner_per_2D, cv::Size(5, 5), cv::Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));

   drawChessboardCorners(img_color, Size(n_corner_cols, n_corner_rows), corner_per_2D, true);

   imshow("img color show", img_color);
   corner_all_2D.push_back(corner_per_2D);


   //3D corners
   for (int r = 0; r < n_corner_rows; r++)
   {
    for (int c = 0; c < n_corner_cols; c++)
    {
     corner_per_3D.push_back(Point3f(c*corner_size, r*corner_size, 0));
    }
   }
   corner_all_3D.push_back(corner_per_3D);

  }
  else
  {
   putText(img_color, "NO FOUND", Point(0, img_color.rows / 2), FONT_HERSHEY_DUPLEX, 3, Scalar(0, 0, 255));
   imshow("img color show", img_color);
  }
  //cv::waitKey(0); //把這個註解取消掉可以看單張找到棋盤格的狀況
 }

 //camera calibration
 vector< Mat> rvecs, tvecs;
 Mat intrinsic_Matrix(3, 3, CV_64F);
 Mat distortion_coeffs(8, 1, CV_64F);

 calibrateCamera(corner_all_3D, corner_all_2D, image_size, intrinsic_Matrix, distortion_coeffs, rvecs, tvecs);
 cout << "Finished IMAGE Camera calibration" << endl;

 //save part
 fprintMatrix(intrinsic_Matrix, folder_calibration + "intrinsic.txt");
 fprintMatrix(distortion_coeffs, folder_calibration + "distortion.txt");

}


int main()
{             
 string folder_colorImg = ".//data//color//";    //圖片的資料夾

 string folder_calibration = ".//calibration//"; //要放置相機矯正資料的資料夾
 _mkdir((folder_calibration).c_str());           //建立相機校正資料的資料夾

 calibration(folder_colorImg, folder_calibration);

 std::system("pause");
 return 0;
}

---

運行之後應該就可以在資料夾 calibration 內找到校正的矩陣資料了



---

以下是使用狀態比較不好的圖片所做的實驗

如圖找不到應有的棋盤點數的話會在圖片上顯示NO FOUND字樣

也不會將該圖片的棋盤點拿去做相機校正






1 comment:

  1. 你好 我照這上面步驟 編譯執行後,並沒有出現兩個參數的txt檔,是否有什麼額外的步驟要做呢

    ReplyDelete