前面讲了MIL的在线采集,这就引申出了如下的问题:
1.由于镜头畸变、相机内部参数不一致、环境影响等诸多因素的影响,实际相机采集到的图片会产生扭曲、变形等等,其中最常见的就是镜头畸变,这个问题如何解决?
2.不同的相机、不同的镜头针对同一现实物体采集得到的图像的大小不一样,如何确立现实物体和实际图像的对应关系(度量单位转换)?
这两个问题通过相机标定来解决。
这里只谈2D的相机标定,一般2D的相机标定使用"棋盘标定法",这个算法是华裔科学家张正友最先提出的,又叫“正友标定法”。具体的标定算法参加张正友的论文。
棋盘标定法使用标定板,如下为常见的方形标定板和圆形标定板
标定板的相邻两个方块或两个圆心之间的间距是固定的,越好的标定板这个间距精度越高。棋盘标定算法的核心就是基于相邻的两个点的实际距离是固定且已知的,我们可以用图像处理的办法获得相邻两个点的像素距离,这样实际距离就和像素距离产生了对应关系,同时将像素产生偏移的点通过对应关系恢复到偏移前 的位置。
MIL中标定的函数以Mcal开头。和MIL其他的功能一样,使用标定必须先McalAlloc分配标定对象,标定的结果保存在标定对象中。可以使用标定对象进行如下操作:
1.像素坐标和实际坐标间的转换
2.纠正图像
3.于其他的模块(测量、分析、采集)绑定使用,支持(Mblob、Mcode、Medge、Mim、Mmeas、Mmod.、Mocr、Mpat.),绑定后可使测量、分析结果直接以实际坐标呈现
void CCalibrateDlg::OnReadImage() { // TODO: Add your control notification handler code here if (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 here if (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 here if (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); //分配测量Marker MmeasAllocMarker(m_milSystem, M_STRIPE, M_DEFAULT, &m_milMarker1); MmeasAllocMarker(m_milSystem, M_STRIPE, M_DEFAULT, &m_milMarker2); //设置Marker Box MmeasSetMarker(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("测量结果")); }