8.MIL中相机标定(Calibration)

来源:互联网 发布:手机应用编程 编辑:程序博客网 时间:2024/06/09 20:11

前面讲了MIL的在线采集,这就引申出了如下的问题:

1.由于镜头畸变、相机内部参数不一致、环境影响等诸多因素的影响,实际相机采集到的图片会产生扭曲、变形等等,其中最常见的就是镜头畸变,这个问题如何解决?

2.不同的相机、不同的镜头针对同一现实物体采集得到的图像的大小不一样,如何确立现实物体和实际图像的对应关系(度量单位转换)?

这两个问题通过相机标定来解决。


这里只谈2D的相机标定,一般2D的相机标定使用"棋盘标定法",这个算法是华裔科学家张正友最先提出的,又叫“正友标定法”。具体的标定算法参加张正友的论文。


棋盘标定法使用标定板,如下为常见的方形标定板和圆形标定板


标定板的相邻两个方块或两个圆心之间的间距是固定的,越好的标定板这个间距精度越高。棋盘标定算法的核心就是基于相邻的两个点的实际距离是固定且已知的,我们可以用图像处理的办法获得相邻两个点的像素距离,这样实际距离就和像素距离产生了对应关系,同时将像素产生偏移的点通过对应关系恢复到偏移前 的位置。


MIL中标定的函数以Mcal开头。和MIL其他的功能一样,使用标定必须先McalAlloc分配标定对象,标定的结果保存在标定对象中。可以使用标定对象进行如下操作:

1.像素坐标和实际坐标间的转换

2.纠正图像

3.于其他的模块(测量、分析、采集)绑定使用,支持(Mblob、Mcode、Medge、Mim、Mmeas、Mmod.、Mocr、Mpat.),绑定后可使测量、分析结果直接以实际坐标呈现


1.标定

MIL中标定一般使用McalGrid函数,如下图

前面说过,标定过程就将现实世界的各个点和图像中图像处理的点建立对应关系,如上图,现实世界中的Grid Image采集后得到图像Real-world grid。
默认标定的时候,现实世界的(0,0)和图像的左上角圆心重合。

2.标定对象操作

标定时找出像素坐标和实际坐标之间的关系,我们要修弄清楚标定的各个坐标系概念,如图

1.绝对世界坐标(Absolute world coordinate system):顾名思义,就是现实世界的绝对坐标,但是这个概念是最容易让人产生误解的。这里的绝对世界坐标实际上是为了对应现实世界和图像世界而创造的一个概念,实际上这里的绝对世界坐标就是说当你的相机标定时,默认左上角的圆心对应的现实世界的点就是绝对世界坐标原点,也就是说实际上这个现实世界坐标原点是你拿着相机移动到哪里,那左上角的圆心对应的现实世界的点就是绝对坐标原点。度量单位为现实单位如mm等。
2.相对相机坐标(Relative camera position):如图,这个概念是为了单相机多视场拍摄大区域图像而设置的。当我们选定绝对世界坐标原点的时候,相机的位置即为相对相机坐标原点,而后机械臂移动一定的距离,这时候的相对相机坐标改变,如上图。度量单位为现实单位如mm等。默认的单相机单视场采集时使用默认值。上图示意的单相机多视场在每次采集前使用McalControl(参数M_CAMERA_POSITION_X和M_CAMERA_POSITION_Y)调整标定对象的相对相机位置,这一相对位置最终会保存在采集到的图像中,这样单相机采集到了多幅图像,每幅图像中包含了各自的相对相机位置,利用这一信息就可以把多幅图像拼接起来。
3.相对世界坐标(Relative world coordinate system):默认相对世界坐标原点和绝对世界坐标原点重合。

这个概念是为了测量方便而引入的。如上图,采集到的图像返回的测量结果(现实世界度量)默认是相对于左上角的世界坐标原点,如果希望返回的测量结果(现实世界度量)是相对于物体的左上角,那么只需要将相对世界坐标原点移到物体左上角即可,测量的结果(现实世界度量)都是相对于相对世界坐标原点的。使用McalRelativeOrigin函数来操作标定对象来改变相对世界坐标。

前面也提到过,实际上猜测MIL的各个对象就是保存各种参数的结构体,为了描述更清楚,我们假设相机Image的内存结构至少包括:相对世界坐标(Rel Cood)、相对相机坐标(Rel Cood)、标定对象索引(Cal Ref)和实际图像数据Buf
那么标定相关示意流程和内存改变情况如下图

接下来按照上图示意图吧标定流程走一遍:
1.相机采集棋盘标定图像Grid Image,使用函数McalGrid标定得到包含标定数据的标定对象,可以使用McalControl调整相对相机坐标,使用McalRelativeOrigin调整相对世界坐标。
2.使用Cal对象对图像做纠正(McalTransformImage)和坐标转换时(McalTransformCoordinate, McalTransformCoordinateList, McalTransformResult),第一次调用时会生成一个标定缓存(Cal Cache),这会加快计算速度。缓存中保存的是Source Image中各个点的实际坐标和像素坐标对应关系。
3.注意标定图像(Calibrated Image)和纠正图像(Corrected Image)的差别,前者只是和Cal对象绑定,后者在和Cal对象绑定还利用Cal对象对原来的Buf进行了修正操作,因此两者前者显示不会改变,后者显示会改变。
4.可使用McalAssociate将Dig对象和Cal对象绑定,这时候的Dig对象就是Calibrated Dig
5.经由Calibrated Dig采集得到的图像Capture Image默认为已标定图像(Calibrated),注意这时候的标定对象索引(Cal Ref)指向和Dig对象绑定的Cal对象。注意这时候的Capture Image的相对世界坐标(Rel Cood)和相对相机坐标(Rel Cood)是由和Dig对象绑定的Cal对象复制而来,但是一旦采集完成,改变和Dig对象绑定的Cal对象的相对世界坐标(Rel Cood)和相对相机坐标(Rel Cood)不会再改变Capture Image的相对世界坐标(Rel Cood)和相对相机坐标(Rel Cood),这一原因从图中看的很清楚,但是改变Cal对象的其他特性还是会影响Capture Image的相关特性因为Capture Image引用该Cal对象。

3.演示程序

这个演示程序是从MIL手册提供的改写而来
如下左图是相机采集的标定板的图像,有很明显的镜头畸变,下图右图为同一相机镜头采集的PCB板的图像,在该程序中我们首先读入标定图像,输入实际参数,产生标定对象,然后使用该标定对象纠正图像和获得PCB板的实际宽度。该程序测量使用MIL Measure模块,如果不清楚,只看标定和纠正程序即可。


测试界面如下

即标定需要输入标定板的实际行列间距和实际行列个数,这都是McalGrid的参数,其他参数参见手册。

读入图像
void CCalibrateDlg::OnReadImage() {// TODO: Add your control notification handler code hereif (M_NULL != m_milImage){MbufFree(m_milImage);m_milImage = M_NULL;}//选择打开文件CFileDialog fileDialog(TRUE);fileDialog.m_ofn.lpstrFilter = TEXT("(MIL 格式图像)\0*.mim\0\0");fileDialog.m_ofn.lpstrInitialDir = TEXT(".\\Image");if (IDOK == fileDialog.DoModal()){MbufRestore(fileDialog.GetPathName().GetBuffer(MAX_PATH), m_milSystem, &m_milImage);//显示MdispZoom(m_milDisplay, 0.9, 0.9);MdispControl(m_milDisplay,  M_CENTER_DISPLAY, M_ENABLE);MdispSelectWindow(m_milDisplay, m_milImage, GetDlgItem(IDP_DISP_IMAGE)->GetSafeHwnd());}}
标定图像
void CCalibrateDlg::OnCal() {// TODO: Add your control notification handler code hereif (M_NULL != m_milCalibration){McalFree(m_milCalibration);m_milCalibration = M_NULL;}//分配Cal对象McalAlloc(m_milSystem, M_DEFAULT, M_DEFAULT, &m_milCalibration);//标定UpdateData(TRUE);McalGrid(m_milCalibration, m_milImage, 0, 0, 0, m_nRowNum, m_nColomnNum, m_dbRowSpace, m_dbColomnSpace, M_DEFAULT, M_DEFAULT);}
图像纠偏
void CCalibrateDlg::OnModify() {// TODO: Add your control notification handler code here//这里可能是MIL 8.0的一个Bug, 对自身Buf操作再写入到自身Buf中, //Display不会自动刷新, 必须先取消显示再显示MdispSelectWindow(m_milDisplay, M_NULL, M_NULL);    McalTransformImage(m_milImage, m_milImage, m_milCalibration,                      M_BILINEAR|M_OVERSCAN_CLEAR, M_DEFAULT, M_DEFAULT);    //刷新显示    MdispSelectWindow(m_milDisplay, m_milImage, GetDlgItem(IDP_DISP_IMAGE)->GetSafeHwnd());}
图像测量
void CCalibrateDlg::OnMeasure() {// TODO: Add your control notification handler code hereif (M_NULL != m_milMarker1){MmeasFree(m_milMarker1);m_milMarker1 = M_NULL;}if (M_NULL != m_milMarker2){MmeasFree(m_milMarker2);m_milMarker2 = M_NULL;}//绑定图像和标定对象,这里并没有修正图像,只是将图像和标定对象绑定,//测量的实际结果是经由标定对象修正后的实际测量值McalAssociate(m_milCalibration, m_milImage, M_DEFAULT);//分配测量MarkerMmeasAllocMarker(m_milSystem, M_STRIPE, M_DEFAULT, &m_milMarker1);MmeasAllocMarker(m_milSystem, M_STRIPE, M_DEFAULT, &m_milMarker2);//设置Marker BoxMmeasSetMarker(m_milMarker1, M_BOX_ORIGIN, MEAS_BOX_POS_X1, MEAS_BOX_POS_Y1);MmeasSetMarker(m_milMarker1, M_BOX_SIZE, MEAS_BOX_WIDTH1, MEAS_BOX_HEIGHT1);MmeasSetMarker(m_milMarker2, M_BOX_ORIGIN, MEAS_BOX_POS_X2, MEAS_BOX_POS_Y2);MmeasSetMarker(m_milMarker2, M_BOX_SIZE, MEAS_BOX_WIDTH2, MEAS_BOX_HEIGHT2);//设置Stripe方向为水平方向MmeasSetMarker(m_milMarker1, M_ORIENTATION, M_HORIZONTAL, M_NULL);MmeasSetMarker(m_milMarker2, M_ORIENTATION, M_HORIZONTAL, M_NULL);//设置Marker 宽度和权重(占有整个宽度的百分比)MmeasSetMarker(m_milMarker1, M_WIDTH, WIDTH_APPROXIMATION, M_NULL);MmeasSetMarker(m_milMarker2, M_WIDTH, WIDTH_APPROXIMATION, M_NULL);MmeasSetMarker(m_milMarker1, M_WEIGHT_FACTOR+M_WIDTH, WIDTH_WEIGHT_FACTOR, M_NULL);MmeasSetMarker(m_milMarker2, M_WEIGHT_FACTOR+M_WIDTH, WIDTH_WEIGHT_FACTOR, M_NULL);//开始测量MmeasFindMarker(M_DEFAULT, m_milImage, m_milMarker1, M_WIDTH+M_POSITION);MmeasFindMarker(M_DEFAULT, m_milImage, m_milMarker2, M_WIDTH+M_POSITION);//获得实际单位的测量结果    double  WorldDistance1,  WorldDistance2;MmeasGetResult(m_milMarker1, M_WIDTH, &WorldDistance1, M_NULL);MmeasGetResult(m_milMarker2, M_WIDTH, &WorldDistance2, M_NULL);//获得像素单位的测量结果double  PixelDistance1,  PixelDistance2;McalControl(m_milCalibration, M_OUTPUT_COORDINATE_SYSTEM, M_PIXEL);MmeasGetResult(m_milMarker1, M_WIDTH, &PixelDistance1, M_NULL);MmeasGetResult(m_milMarker2, M_WIDTH, &PixelDistance2, M_NULL);//获得两个Marker位置double  PosX1, PosY1, PosX2, PosY2, PosX3, PosY3, PosX4, PosY4;MmeasGetResult(m_milMarker1, M_POSITION+M_EDGE_FIRST,  &PosX1, &PosY1);MmeasGetResult(m_milMarker1, M_POSITION+M_EDGE_SECOND, &PosX2, &PosY2);MmeasGetResult(m_milMarker2, M_POSITION+M_EDGE_FIRST,  &PosX3, &PosY3);MmeasGetResult(m_milMarker2, M_POSITION+M_EDGE_SECOND, &PosX4, &PosY4);//绘制测量结果MdispInquire(m_milDisplay, M_OVERLAY_ID, &m_milOverlayImage);MdispControl(m_milDisplay, M_OVERLAY_CLEAR, M_DEFAULT);MgraColor(M_DEFAULT, M_COLOR_YELLOW);MmeasDraw(M_DEFAULT, m_milMarker1, m_milOverlayImage, M_DRAW_WIDTH, M_DEFAULT, M_RESULT);MmeasDraw(M_DEFAULT, m_milMarker2, m_milOverlayImage, M_DRAW_WIDTH, M_DEFAULT, M_RESULT);MgraBackColor(M_DEFAULT, M_COLOR_BLACK);MgraText(M_DEFAULT, m_milOverlayImage, (long)(PosX1+0.5-40),(long)((PosY1+0.5)+((PosY2 - PosY1)/2.0)), MIL_TEXT(" Distance 1 "));MgraText(M_DEFAULT, m_milOverlayImage, (long)(PosX3+0.5-40),(long)((PosY3+0.5)+((PosY4 - PosY3)/2.0)), MIL_TEXT(" Distance 2 "));//弹出结果显示框CString csResult;csResult.Format(TEXT("标定的测量结果为Distance1:%8.2lf cm,Distance2:%6.2lf cm\n未标定的测量结果为Distance1:%8.2lf pixels,Distance2:%6.2lf pixels"),WorldDistance1, WorldDistance2,PixelDistance1, PixelDistance2);MessageBox(csResult, TEXT("测量结果"));}

完整测试程序下载链接
原创,转载请注明来自http://blog.csdn.net/wenzhou1219
1 0
原创粉丝点击