摄像机的追踪标定

本文是我第一次在csdn上写的博客,有不详之处,望大家见谅,也希望大家多多支持。

废话不多说,直接进入正题。对于摄像机标定,是学习图像处理和机器视觉不可回避的话题,这方面的现有理论已相对成熟,国内外学者所发的相关文章不少。摄像机标定主要分为传统相机标定,摄像机自标定,主动视觉标定。在这三类标定中,高精度标定用传统摄像机标定,但标定算法复杂,需知道高精度标定块结构信息;主动视觉摄像机标定方法,通常可以线性求解,鲁棒性比较高,但 不能使用于摄像机运动未知和无法控制的场合;摄像机自标定方法, 仅需要建立图像之间的对应,灵活性强,潜在 应用范围广,不足之处是非线性标定,鲁棒性不高。

在我的实验项目中,我采用介于传统标定和自标定之间的张正友棋盘格追踪标定,该标定方法易于操作,标定精度高,还有一点opencv中的标定算法也采用张正友标定法。在实验项目开始之前,我看了《计算机视觉》马颂德,张正友这本书,还看了张正友的棋盘标定法论文(英文不好懂),理论知识就足以。接下来,我用matlab中的工具箱进行了摄像机标定,之所以先采用matlab是因为可以让我们先了解相机标定原理和过程,同时标定精度高,在角点提取和畸变优化方面,功能强大,对计算出来的参数还可以进行迭代优化,关于matlab的相机标定大家可参考http://blog.csdn.net/zshtang/article/details/5523287和http://www.vision.caltech.edu/bouguetj/calib_doc/ ,同时matlab采用的是tasi两部标定算法。

接下来,我将重点放在相机标定中MFC界面的设计,opencv代码的编写上,在这之中遇到了许多小问题,希望对大家以后的开发有所借鉴。我的开发换进是VC6.0+opencv1.0(这种搭配应该是上个世纪的产物)。我将代码的实现分为两大模块,分别是实现摄像机打开关闭截图模块和标定模块(计算出内外参数,结果显示在edit box中),总体的界面设计如下图:

因为opencv1.0版本在win7系统中会出现一些问题,包括线程的创建,这些我会一一解答。

首先,对于摄像机的打开关闭,我没有采用opencv提供的函数,如cvCreateCameraCapture,cvQueryImage的函数,因为opencv1.0默认的是XP系统,win7上会出现黑屏。原因是老版本的opencv1.0采用的是Video for Windows (VFW)的视频读写框架,而新版本的opencv采用DirectShow这一通用读写框架。而现在用的视频采集卡支持通用框架(如DirectShow,V4L),但可能不支持VFW了,所以才遇到我之前遇到的情况,摄像头内容无法被软件读取到!去网上下载CameraDS包(基于DirectShow),利用类CCameraDS实现摄像头的读取,详见:

摄像机的打开代码如下:

void CImageShowDlg::OnButton1()
{// TODO: Add your control notification handler code here  if (!camera.OpenCamera(0,false))  {  AfxMessageBox("无法打开摄像头");  return;  }  IplImage* m_Frame;  m_Frame=camera.QueryFrame();  CvvImage m_CvvImage;  m_CvvImage.CopyOf(m_Frame,1);     if (true)  {  m_CvvImage.DrawToHDC(hDC, &rect);  } SetTimer(1,10,NULL);  // 设置计时器,每10ms触发一次事件
}

在上述代码中,我设置了timer消息,因为如果采用在while循环里面读取每一帧图像,是可以将摄像机视频流显示出来,但会发现其他按钮没有任何反应,原因在于处理器被while()循环函数完全占有,没办法处理其他进程。所以我采用了类似于创建一个线程概念(有点时间片轮转的意思),每个10秒触发一次计时器,去读取视频帧。

void CImageShowDlg::OnTimer(UINT nIDEvent)
{// TODO: Add your message handler code here and/or call default     m_Frame=camera.QueryFrame();CvvImage m_CvvImage;  m_CvvImage.CopyOf(m_Frame,1);     if (true)  {      m_CvvImage.DrawToHDC(hDC, &rect);  }    
}

接下来,就是摄像机的关闭,比较简单。

void CImageShowDlg::OnButton2()
{// TODO: Add your control notification handler code herecamera.CloseCamera();KillTimer(1);
}

建议大家如果要将图片显示在界面上,可用CvvImage这个类,非常方便,否则还要写设置图片大小和显示两个函数。

接下来就是,截取符合要求的棋盘格图片,就是要判断读取到的帧图片角点提取数量是否符合要求,如果是,则保存图片,代码如下。

void CImageShowDlg::OnButton3()
{// TODO: Add your control notification handler code herechar *FileName="wujiang";char tmpfile[100]={'\0'};static int count=1;sprintf(tmpfile,"%s\\%d.bmp",FileName,count++);Sleep(2000);CvSize board_size = cvSize(4,6);    /* 定标板上每行、列的角点数 */int board_n=board_size.width*board_size.height;//标定板上的内角点总数CvPoint2D32f* image_points_buf = new CvPoint2D32f[board_n];   /* 缓存每幅图像上检测到的角点 */int num= -1 ;//用于存储角点个数。 所有变量定义后必须初始化,这是一个编程优良习惯int found=cvFindChessboardCorners( m_Frame, board_size,image_points_buf, &num, CV_CALIB_CB_ADAPTIVE_THRESH);if (found==0){MessageBox("角点读取失败");} else{cvSaveImage(tmpfile,m_Frame);}
}

下来是,摄像机标定算法的代码实现和将计算结果显示在edit box中。

void CImageShowDlg::OnImageShow()
{// TODO: Add your control notification handler code hereifstream fin("calibdata.txt"); /* 定标所用图像文件的路径 */ofstream fout("caliberation_result.txt");  /* 保存定标结果的文件 */ofstream fout1("Distortion1.txt");ofstream fout2("Intrinsic1.txt");int image_count=0;  /* 图像数量 */int sucesses=0;CvSize image_size;  /* 图像的尺寸 */CvSize board_size = cvSize(4,6);    /* 定标板上每行、列的角点数 */int board_n=board_size.width*board_size.height;//标定板上的内角点总数CvPoint2D32f* image_points_buf = new CvPoint2D32f[board_n];   /* 缓存每幅图像上检测到的角点 */Seq<CvPoint2D32f> image_points_seq;  /* 保存检测到的所有角点 */ //Seq是一个队列模板类string filename;int count= -1 ;//用于存储角点个数。 所有变量定义后必须初始化,这是一个编程优良习惯while (getline(fin,filename)){image_count++;Image<uchar> view(filename);    //Image是个类if (image_count == 1) {image_size.width = view.size().width;   //传递图像的宽和高image_size.height = view.size().height;}int found=cvFindChessboardCorners( view.cvimage, board_size,image_points_buf, &count, CV_CALIB_CB_ADAPTIVE_THRESH);if (found==0)//cvFindChessboardCorners(要检测的棋盘图,图中每行和每列的角点个数,检测到的角点,输出——角点个数,操作标志)//CV_CALIB_CB_ADAPTIVE_THRESH - 使用自适应阈值(通过平均图像亮度计算得到)将图像转换为黑白图,而不是一个固定的阈值。//CV_CALIB_CB_FILTER_QUADS - 使用其他的准则(如轮廓面积,周长,方形形状)来去除在轮廓检测阶段检测到的错误方块。//返回值:如果所有角点都被检测到且它们都被以一定顺序排布(一行一行地,每行从左到右),函数返回非零值,//否则在函数不能发现所有角点或者记录它们地情况下,函数返回0{    MessageBox("角点读取失败");} else {sucesses++;Image<uchar> view_gray(view.size(),8,1);//Image(CvSize size, int depth, int channels )//rgb2gray(view,view_gray);cvCvtColor(view.cvimage, view_gray.cvimage, CV_BGR2GRAY);/* 亚像素精确化 */cvFindCornerSubPix( view_gray.cvimage, image_points_buf, count, cvSize(11,11),cvSize(-1,-1), cvTermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 30, 0.1 ));//对粗提取的角点进行精确化image_points_seq.push_back(image_points_buf,count);//add some elements at rear把精确化的角点坐标传递给image_points_seq/* 在图像上显示角点位置 */cvDrawChessboardCorners( view.cvimage, board_size, image_points_buf, count, 1);//用于在图片中标记角点//cvDrawChessboardCorners(结果图像<必须是8位彩色图像>,每行和每列的内角点数目,//检测到的角点数组,角点数目,指示完整地棋盘被发现(≠0)还是没有发现(=0))view.show("calib");//显示图片cvWaitKey(500);//用于暂停,单位是毫秒view.close();}fout<<"图片:"<<image_count<<"  ";fout<<"找到角点数:"<<count<<endl;}/************************************************************************//* //以下是利用包装后的数据结构(seq类)进行操作,比基于openCV的数据结构更方便,结构清晰。*/int total = image_points_seq.length();//所有检测出的角点数delete []image_points_buf;/************************************************************************摄像机定标*************************************************************************//*棋盘三维信息*/CvSize square_size = cvSize(10,10);  /* 实际测量得到的定标板上每个棋盘格的大小 */Matrix<double> object_points(1,board_size.width*board_size.height*sucesses,3); /* 保存定标板上角点的三维坐标 *///参数(row,col,channel)Matrix<double> image_points(1,image_points_seq.cvseq->total,2); /* 保存提取的所有角点 */Matrix<int> point_counts(1,sucesses,1); /* 每幅图像中角点的数量 *//*内外参数*/Matrix<double> intrinsic_matrix(3,3,1); /* 摄像机内参数矩阵 */Matrix<double> distortion_coeffs(1,4,1); /* 摄像机的4个畸变系数:k1,k2,p1,p2 */Matrix<double> rotation_vectors(1,sucesses,3); /* 每幅图像的旋转行向量 */Matrix<double> translation_vectors(1,sucesses,3); /* 每幅图像的平移行向量 *//* 初始化定标板上角点的三维坐标 */int i,j,t;for (t=0;t<sucesses;t++) {for (i=0;i<board_size.height;i++) {for (j=0;j<board_size.width;j++) {/* 假设定标板放在世界坐标系中z=0的平面上 */object_points(0,t*board_size.height*board_size.width+i*board_size.width+j,0) = i*square_size.width;object_points(0,t*board_size.height*board_size.width+i*board_size.width+j,1) = j*square_size.height;object_points(0,t*board_size.height*board_size.width+i*board_size.width+j,2) = 0;}}}/* 将角点的存储结构转换成矩阵形式 */for (i=0;i<image_points_seq.cvseq->total;i++) {image_points(0,i,0) = image_points_seq[i].x;image_points(0,i,1) = image_points_seq[i].y;}/* 初始化每幅图像中的角点数量,这里我们假设每幅图像中都可以看到完整的定标板 */for (i=0;i<sucesses;i++)point_counts(0,i) = board_size.width*board_size.height;/* 开始定标 */cvCalibrateCamera2(object_points.cvmat,image_points.cvmat,point_counts.cvmat,image_size,intrinsic_matrix.cvmat,distortion_coeffs.cvmat,rotation_vectors.cvmat,translation_vectors.cvmat,0);cvSave("Intrinsics.txt",intrinsic_matrix.cvmat);cvSave("Distortion.txt",distortion_coeffs.cvmat);cvSave("rotation_vectors.txt",rotation_vectors.cvmat);cvSave("translation_vectors.txt",translation_vectors.cvmat);/************************************************************************对定标结果进行评价*************************************************************************/double total_err = 0.0; /* 所有图像的平均误差的总和 */double err = 0.0; /* 每幅图像的平均误差 */Matrix<double> image_points2(1,point_counts(0,0,0),2); /* 保存重新计算得到的投影点 */fout<<endl<<"每幅图像的定标误差:\n";for (i=0;i<sucesses;i++) {/* 通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点 */cvProjectPoints2(object_points.get_cols(i*point_counts(0,0,0),(i+1)*point_counts(0,0,0)-1).cvmat,rotation_vectors.get_col(i).cvmat,translation_vectors.get_col(i).cvmat,intrinsic_matrix.cvmat,distortion_coeffs.cvmat,image_points2.cvmat,0,0,0,0);/* 计算新的投影点和旧的投影点之间的误差*/err = cvNorm(image_points.get_cols(i*point_counts(0,0,0),(i+1)*point_counts(0,0,0)-1).cvmat,image_points2.cvmat,CV_L1);cvSave("image_points3.txt",image_points.get_cols(i*point_counts(0,0,0),(i+1)*point_counts(0,0,0)-1).cvmat);cvSave("each_image_points2.txt",image_points2.cvmat);total_err += err/=point_counts(0,0,0);fout<<"\t第"<<i+1<<"幅图像的平均误差:"<<err<<"像素"<<'\n';}fout<<"总体平均误差:"<<total_err/sucesses<<"像素"<<'\n'<<'\n';/************************************************************************保存定标结果*************************************************************************/Matrix<double> rotation_vector(3,1); /* 保存每幅图像的旋转向量 */Matrix<double> rotation_matrix(3,3); /* 保存每幅图像的旋转矩阵 */fout<<"相机内参数矩阵:\n";fout<<intrinsic_matrix<<'\n';fout<<"畸变系数:\n";fout<<distortion_coeffs<<'\n';//    fout1<<"相机内参数矩阵:\n";
//  fout1<<intrinsic_matrix<<'\n';
//  fout1<<"畸变系数:\n";fout1<<distortion_coeffs<<'\n';fout2<<intrinsic_matrix<<'\n';for (i=0;i<sucesses;i++) {fout<<"第"<<i+1<<"幅图像的旋转向量:\n";fout<<rotation_vectors.get_col(i);/* 对旋转向量进行存储格式转换 */for (j=0;j<3;j++) {rotation_vector(j,0,0) = rotation_vectors(0,i,j);}/* 将旋转向量转换为相对应的旋转矩阵 */cvRodrigues2(rotation_vector.cvmat,rotation_matrix.cvmat);fout<<"第"<<i+1<<"幅图像的旋转矩阵:\n";fout<<rotation_matrix;fout<<"第"<<i+1<<"幅图像的平移向量:\n";fout<<translation_vectors.get_col(i)<<'\n';}
}

以上代码是将所保存的图片,计算出摄像机内外参数,并将结果保存在txt文件中,这主要方便后续将结果显示在编辑框中。还有一点,虽然将标定结果存入了txt文件中,但是若想在上述代码中直接实现将标定结果显示在编辑框中,是不可能的,你会发现编辑框中将出现乱码(烫烫烫烫烫烫烫烫烫烫烫),解决方法是另写一个button控件,究其原因是windows未将标定结果写入外存,只是存入缓冲区中,故你将在txt文件中看不到任何结果,但若关闭VC6.0,结果将出现在txt文本中。

接下来就是将标定结果显示在编辑框中。

void CImageShowDlg::OnReadIntrinsic()
{// TODO: Add your control notification handler code hereCFileDialog fileDlg(TRUE);  CString str;  CFile f;  wof.Open("caliberation_result.txt",CFile::modeReadWrite);  f.Read(str.GetBuffer(f.GetLength()),f.GetLength());  f.Close();  GetDlgItem(IDC_INTRINSIC )->SetWindowText( str);
}

以上就是我对相机标定的理解,详细代码可看我的代码http://download.csdn.net/detail/u013025764/7225051

MFC+OPENCV摄像机标定相关推荐

  1. 十五天掌握OpenCV——摄像机标定和3D重构!—摄像机标定

    魏老师学生--Cecil:学习OpenCV-机器视觉之旅 基础 代码 设置 标定 畸变校正 反向投影误差 代码演示 Aim: 学习摄像机畸变以及摄像机的内部参数和外部参数: 对畸变图像进行修复. 基础 ...

  2. 2019-9-29 opencv摄像机标定与三维重构4-Depth Map from Stereo Images立体图像中的深度图(视差图)

    官网参见https://docs.opencv.org/3.4.1/dd/d53/tutorial_py_depthmap.html 上一节中,我们学习了极线约束的概念和相关术语.主要包含:如果我们有 ...

  3. Python+OpenCV:摄像机标定(Camera Calibration)

    Python+OpenCV:摄像机标定(Camera Calibration) 理论 Some pinhole cameras introduce significant distortion to ...

  4. 用OpenCV进行摄像机标定

    用OpenCV进行摄像机标定 照相机已经存在很长时间了.然而,随着廉价针孔相机在20世纪末的引入,日常生活中变得司空见惯.不幸的是,这种廉价伴随着它的代价:显著的扭曲.幸运的是,这些常数,通过校准和一 ...

  5. (转)OpenCV版本的摄像机标定

    摄像机的标定问题是机器视觉领域的入门问题,可以分为传统的摄像机定标方法和摄像机自定标方法.定标的方法有很多中常见的有:Tsai(传统)和张正友(介于传统和自定标)等, 摄像机成像模型和四个坐标系(通用 ...

  6. Python+OpenCV学习(17)---摄像机标定

    Python+OpenCV学习(17)---摄像机标定 原文:http://blog.csdn.net/firemicrocosm/article/details/48594897 利用python学 ...

  7. 张正友摄像机标定的研究(MATLAB+OpenCV)

    张正友 本科浙大,本来以为是中国人论文是中文呢,哎 张正友的主页: http://research.microsoft.com/en-us/um/people/zhang/Calib/ 不过里面的棋盘 ...

  8. matlab张正友摄像机标定算法应用,张正友摄像机标定的研究(MATLAB+OpenCV)

    张正友 本科浙大,本来以为是中国人论文是中文呢,哎 不过里面的棋盘格跟我的不一样啊,why???,我决定先看看中文的论文吧,我的首要任务是弄清楚输入输出,流程,怎么用吧 matlab 跟 opencv ...

  9. 基于opencv的摄像机标定

    原理简述: 三维世界中的点的位置与其对应的二维投影,遵从以下公式:   其中,  M表示三维世界中的点:  [R|T]表示欧氏变换,是一个3*4矩阵  A表示相机参数矩阵,存放相机内部参数  P表示M ...

最新文章

  1. Floodlight 处理交换机增加/移除过程
  2. 工具使用 - Quartus II 管脚分配方法
  3. oracle 日期排序_日期居然用字符串保存?我笑了
  4. 国家发钱了!研究生补贴一览表!
  5. Jmeter生成html格式测试报告
  6. 51nod1134最长递增子序列(dp)
  7. 荣耀30 Pro+价格曝光:顶配真的高攀不起!
  8. 通用mapper版+SpringBoot+MyBatis框架+mysql数据库的整合
  9. php中execute函数,PHP:调用布尔值上的成员函数execute()
  10. 数据库、C#、Java生成唯一GUID 方法
  11. win7计算机管理没有用户模块,Win7系统下安装ipx协议提示找不到相应的模块如何解决...
  12. 如何下载城通网盘的东西?
  13. 通俗理解逻辑删除和物理删除的区别
  14. VS2017报错 class “Cxxxx“没有成员“GetContextMenuManager“ “GetContextMenuManager“:不是“Cxxxx“的成员
  15. 学医后才知道的小知识...
  16. excel在一个单元格输入内容,在其他单元格同步显示
  17. FakeSMC 修改
  18. C、C++、Java到Python,编程入门学习什么语言好?
  19. “希希敬敬对”团队——敏捷冲刺Alpha过程总结
  20. 【091】肖邦《降B小调第一夜曲》

热门文章

  1. CentOS 7 安装Steam Dota2
  2. angular.js自定义指令
  3. Kafka的Streams
  4. 链上存储未来,CESS 如何促进 Web3 的大规模采用?
  5. Web颜色对照表大全
  6. 简单消息,异步消息,同步消息辨析
  7. C语言模块化开发,深入多文件编程
  8. 51汇编中DATA和EQU
  9. 角门小学一年级新生家长会
  10. Android碎片知识(十).开发中的gps定位(转)