opencv相机标定原理与步骤

嵌入式设计应用

132人已加入

描述

  我们首先介绍下计算机视觉领域中常见的三个坐标系:图像坐标系,相机坐标系,世界坐标系。以及他们之间的关系。然后介绍如何使用张正友相机标定法标定相机。

  总体原理:

  摄像机标定(Camera calibration)简单来说是从世界坐标系换到图像坐标系的过程,也就是求最终的投影矩阵的过程。

  基本的坐标系:

  世界坐标系;

  相机坐标系;

  成像平面坐标系;

  像素坐标系

OpenCV

  一般来说,标定的过程分为两个部分:

  第一步是从世界坐标系转为相机坐标系,这一步是三维点到三维点的转换,包括R,t(相机外参,确定了相机在某个三维空间中的位置和朝向)等参数;

  第二部是从相机坐标系转为成像平面坐标系(像素坐标系),这一步是三维点到二维点的转换,包括K(相机内参,是对相机物理特性的近似)等参数;

  投影矩阵 : P=K [ R | t ] 是一个3×4矩阵,混合了内参和外参而成。

  图像坐标系:

  理想的图像坐标系原点O1和真实的O0有一定的偏差,由此我们建立了等式(1)和(2),可以用矩阵形式(3)表示。

  相机坐标系(C)和世界坐标系(W):

  OpenCV

  通过相机与图像的投影关系,我们得到了等式(4)和等式(5),可以用矩阵形式(6)表示。我们又知道相机坐标系和世界坐标的关系可以用等式(7)表示:

  OpenCV

  由等式(3),等式(6)和等式(7)我们可以推导出图像坐标系和世界坐标系的关系:

  OpenCV

  其中M1称为相机的内参矩阵,包含内参(fx,fy,u0,v0)。M2称为相机的外参矩阵,包含外参(R:旋转矩阵,T:平移矩阵)。

  众所周知,相机镜头存在一些畸变,主要是径向畸变(下图dr),也包括切向畸变(下图dt)等。

  OpenCV

  上图右侧等式中,k1,k2,k3,k4,k5,k6为径向畸变,p1,p2为切向畸变。在OpenCV中我们使用张正友相机标定法通过10幅不同角度的棋盘图像来标定相机获得相机内参和畸变系数。函数为calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs,flag)

  objectPoints: 一组世界坐标系中的3D

  imagePoints: 超过10张图片的角点集合

  imageSize: 每张图片的大小

  cameraMatrix: 内参矩阵

  distCoeffs: 畸变矩阵(默认获得5个即便参数k1,k2,p1,p2,k3,可修改)

  rvecs: 外参:旋转向量

  tvecs: 外参:平移向量

  flag: 标定时的一些选项:

  CV_CALIB_USE_INTRINSIC_GUESS:使用该参数时,在cameraMatrix矩阵中应该有fx,fy,u0,v0的估计值。否则的话,将初始化(u0,v0)图像的中心点,使用最小二乘估算出fx,fy。

  CV_CALIB_FIX_PRINCIPAL_POINT:在进行优化时会固定光轴点。当CV_CALIB_USE_INTRINSIC_GUESS参数被设置,光轴点将保持在中心或者某个输入的值。

  CV_CALIB_FIX_ASPECT_RATIO:固定fx/fy的比值,只将fy作为可变量,进行优化计算。当CV_CALIB_USE_INTRINSIC_GUESS没有被设置,fx和fy将会被忽略。只有fx/fy的比值在计算中会被用到。

  CV_CALIB_ZERO_TANGENT_DIST:设定切向畸变参数(p1,p2)为零。

  CV_CALIB_FIX_K1,。。。,CV_CALIB_FIX_K6:对应的径向畸变在优化中保持不变。

  CV_CALIB_RATIONAL_MODEL:计算k4,k5,k6三个畸变参数。如果没有设置,则只计算其它5个畸变参数。

  首先我们打开摄像头并按下‘g’键开始标定:

  [cpp] view plain copy print?

  VideoCapture cap(1);

  cap.set(CV_CAP_PROP_FRAME_WIDTH,640);

  cap.set(CV_CAP_PROP_FRAME_HEIGHT,480);

  if(!cap.isOpened()){

  std::cout《《“打开摄像头失败,退出”;

  exit(-1);

  }

  namedWindow(“Calibration”);

  std::cout《《“Press ‘g’ to start capturing images!”《《endl;

  VideoCapture cap(1);

  cap.set(CV_CAP_PROP_FRAME_WIDTH,640);

  cap.set(CV_CAP_PROP_FRAME_HEIGHT,480);

  if(!cap.isOpened()){

  std::cout《《“打开摄像头失败,退出”;

  exit(-1);

  }

  namedWindow(“Calibration”);

  std::cout《《“Press ‘g’ to start capturing images!”《《endl;

  [cpp] view plain copy print?

  if( cap.isOpened() && key == ‘g’ )

  {

  《span style=“white-space:pre”》 《/span》mode = CAPTURING;

  }

  if( cap.isOpened() && key == ‘g’ )

  {

  《span style=“white-space:pre”》 《/span》mode = CAPTURING;

  }

  按下空格键(SPACE)后使用findChessboardCorners函数在当前帧寻找是否存在可用于标定的角点,如果存在将其提取出来后亚像素化并压入角点集合,保存当前图像:

  [cpp] view plain copy print?

  if( (key & 255) == 32 )

  {

  image_size = frame.size();

  /* 提取角点 */

  Mat imageGray;

  cvtColor(frame, imageGray , CV_RGB2GRAY);

  bool patternfound = findChessboardCorners(frame, board_size, corners,CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE + CALIB_CB_FAST_CHECK );

  if (patternfound)

  {

  n++;

  tempname《《n;

  tempname》》filename;

  filename+=“.jpg”;

  /* 亚像素精确化 */

  cornerSubPix(imageGray, corners, Size(11, 11), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));

  count += corners.size();

  corners_Seq.push_back(corners);

  imwrite(filename,frame);

  tempname.clear();

  filename.clear();

  }

  else

  {

  std::cout《《“Detect Failed.\n”;

  }

  }

  if( (key & 255) == 32 )

  {

  image_size = frame.size();

  /* 提取角点 */

  Mat imageGray;

  cvtColor(frame, imageGray , CV_RGB2GRAY);

  bool patternfound = findChessboardCorners(frame, board_size, corners,CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE + CALIB_CB_FAST_CHECK );

  if (patternfound)

  {

  n++;

  tempname《《n;

  tempname》》filename;

  filename+=“.jpg”;

  /* 亚像素精确化 */

  cornerSubPix(imageGray, corners, Size(11, 11), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));

  count += corners.size();

  corners_Seq.push_back(corners);

  imwrite(filename,frame);

  tempname.clear();

  filename.clear();

  }

  else

  {

  std::cout《《“Detect Failed.\n”;

  }

  }

  角点提取完成后开始标定,首先初始化定标板上角点的三维坐标:

  [cpp] view plain copy print?

  for (int t=0;t《image_count;t++)

  {

  《span style=“white-space:pre”》 《/span》vector《Point3f》 tempPointSet;

  for (int i=0;i《board_size.height;i++)

  {

  《span style=“white-space:pre”》 《/span》for (int j=0;j《board_size.width;j++)

  {

  /* 假设定标板放在世界坐标系中z=0的平面上 */

  Point3f tempPoint;

  tempPoint.x = i*square_size.width;

  tempPoint.y = j*square_size.height;

  tempPoint.z = 0;

  tempPointSet.push_back(tempPoint);

  《span style=“white-space:pre”》 《/span》}

  }

  object_Points.push_back(tempPointSet);

  }

  for (int t=0;t《image_count;t++)

  {

  《span style=“white-space:pre”》 《/span》vector《Point3f》 tempPointSet;

  for (int i=0;i《board_size.height;i++)

  {

  《span style=“white-space:pre”》 《/span》for (int j=0;j《board_size.width;j++)

  {

  /* 假设定标板放在世界坐标系中z=0的平面上 */

  Point3f tempPoint;

  tempPoint.x = i*square_size.width;

  tempPoint.y = j*square_size.height;

  tempPoint.z = 0;

  tempPointSet.push_back(tempPoint);

  《span style=“white-space:pre”》 《/span》}

  }

  object_Points.push_back(tempPointSet);

  }

  使用calibrateCamera函数开始标定:

  [cpp] view plain copy print?

  calibrateCamera(object_Points, corners_Seq, image_size, intrinsic_matrix ,distortion_coeffs, rotation_vectors, translation_vectors);

  calibrateCamera(object_Points, corners_Seq, image_size, intrinsic_matrix ,distortion_coeffs, rotation_vectors, translation_vectors);

  完成定标后对标定进行评价,计算标定误差并写入文件:

  [cpp] view plain copy print?

  std::cout《《“每幅图像的定标误差:”《《endl;

  fout《《“每幅图像的定标误差:”《《endl《《endl;

  for (int i=0; i《image_count; i++)

  {

  vector《Point3f》 tempPointSet = object_Points[i];

  /**** 通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点 ****/

  projectPoints(tempPointSet, rotation_vectors[i], translation_vectors[i], intrinsic_matrix, distortion_coeffs, image_points2);

  /* 计算新的投影点和旧的投影点之间的误差*/

  vector《Point2f》 tempImagePoint = corners_Seq[i];

  Mat tempImagePointMat = Mat(1,tempImagePoint.size(),CV_32FC2);

  Mat image_points2Mat = Mat(1,image_points2.size(), CV_32FC2);

  for (int j = 0 ; j 《 tempImagePoint.size(); j++)

  {

  image_points2Mat.at《Vec2f》(0,j) = Vec2f(image_points2[j].x, image_points2[j].y);

  tempImagePointMat.at《Vec2f》(0,j) = Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);

  }

  err = norm(image_points2Mat, tempImagePointMat, NORM_L2);

  total_err += err/= point_counts[i];

  std::cout《《“第”《《i+1《《“幅图像的平均误差:”《《err《《“像素”《《endl;

  fout《《“第”《《i+1《《“幅图像的平均误差:”《《err《《“像素”《《endl;

  }

  std::cout《《“总体平均误差:”《《total_err/image_count《《“像素”《《endl;

  fout《《“总体平均误差:”《《total_err/image_count《《“像素”《《endl《《endl;

  std::cout《《“评价完成!”《《endl;

  std::cout《《“每幅图像的定标误差:”《《endl;

  fout《《“每幅图像的定标误差:”《《endl《《endl;

  for (int i=0; i《image_count; i++)

  {

  vector《Point3f》 tempPointSet = object_Points[i];

  /**** 通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点 ****/

  projectPoints(tempPointSet, rotation_vectors[i], translation_vectors[i], intrinsic_matrix, distortion_coeffs, image_points2);

  /* 计算新的投影点和旧的投影点之间的误差*/

  vector《Point2f》 tempImagePoint = corners_Seq[i];

  Mat tempImagePointMat = Mat(1,tempImagePoint.size(),CV_32FC2);

  Mat image_points2Mat = Mat(1,image_points2.size(), CV_32FC2);

  for (int j = 0 ; j 《 tempImagePoint.size(); j++)

  {

  image_points2Mat.at《Vec2f》(0,j) = Vec2f(image_points2[j].x, image_points2[j].y);

  tempImagePointMat.at《Vec2f》(0,j) = Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);

  }

  err = norm(image_points2Mat, tempImagePointMat, NORM_L2);

  total_err += err/= point_counts[i];

  std::cout《《“第”《《i+1《《“幅图像的平均误差:”《《err《《“像素”《《endl;

  fout《《“第”《《i+1《《“幅图像的平均误差:”《《err《《“像素”《《endl;

  }

  std::cout《《“总体平均误差:”《《total_err/image_count《《“像素”《《endl;

  fout《《“总体平均误差:”《《total_err/image_count《《“像素”《《endl《《endl;

  std::cout《《“评价完成!”《《endl;

  显示标定结果并写入文件:

  [cpp] view plain copy print?

  std::cout《《“开始保存定标结果………………”《《endl;

  Mat rotation_matrix = Mat(3,3,CV_32FC1, Scalar::all(0)); /* 保存每幅图像的旋转矩阵 */

  fout《《“相机内参数矩阵:”《《endl;

  fout《《intrinsic_matrix《《endl《《endl;

  fout《《“畸变系数:\n”;

  fout《《distortion_coeffs《《endl《《endl《《endl;

  for (int i=0; i《image_count; i++)

  {

  fout《《“第”《《i+1《《“幅图像的旋转向量:”《《endl;

  fout《《rotation_vectors[i]《《endl;

  /* 将旋转向量转换为相对应的旋转矩阵 */

  Rodrigues(rotation_vectors[i],rotation_matrix);

  fout《《“第”《《i+1《《“幅图像的旋转矩阵:”《《endl;

  fout《《rotation_matrix《《endl;

  fout《《“第”《《i+1《《“幅图像的平移向量:”《《endl;

  fout《《translation_vectors[i]《《endl《《endl;

  }

  std::cout《《“完成保存”《《endl;

  fout《《endl;

  std::cout《《“开始保存定标结果………………”《《endl;

  Mat rotation_matrix = Mat(3,3,CV_32FC1, Scalar::all(0)); /* 保存每幅图像的旋转矩阵 */

  fout《《“相机内参数矩阵:”《《endl;

  fout《《intrinsic_matrix《《endl《《endl;

  fout《《“畸变系数:\n”;

  fout《《distortion_coeffs《《endl《《endl《《endl;

  for (int i=0; i《image_count; i++)

  {

  fout《《“第”《《i+1《《“幅图像的旋转向量:”《《endl;

  fout《《rotation_vectors[i]《《endl;

  /* 将旋转向量转换为相对应的旋转矩阵 */

  Rodrigues(rotation_vectors[i],rotation_matrix);

  fout《《“第”《《i+1《《“幅图像的旋转矩阵:”《《endl;

  fout《《rotation_matrix《《endl;

  fout《《“第”《《i+1《《“幅图像的平移向量:”《《endl;

  fout《《translation_vectors[i]《《endl《《endl;

  }

  std::cout《《“完成保存”《《endl;

  fout《《endl;

  具体的代码实现和工程详见:Calibration

  运行截图:

  OpenCV

  下一节我们将使用RPP相机姿态算法得到相机的外部参数:旋转和平移。

  2015/11/14补充:

  所有分辨率下的畸变(k1,k2,p1,p2)相同,但内参不同(fx,fy,u0,v0),不同分辨率下需要重新标定相机内参。以下是罗技C920在1920*1080下的内参:

  OpenCV

  2016/08/20补充:

  findChessboardCorners函数的第二个参数是定义棋盘格的横纵内角点个数,要设置正确,不然函数找不到合适的角点,返回false。如下图中的横内角点是12,纵内角点是7,则Size board_size = Size(12, 7);

  OpenCV

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分