手机号码:15920139670

 

座       机:   020-31605853

海康威视VisionMaster算法软件如何通过二次开发进行手眼标定

海康威视VM算法软件如何通过二次开发进行手眼标定

一、什么是手眼标定

在做手眼标定之前,我们一定要弄清楚什么是标定。

1、标定的本质

标定的目的是统一坐标系,将图像坐标系中的点转换到物理坐标系中,然后在物理坐标系中进行数值处理。从数学的角度来看标定:

1.png

标定:已知像素坐标系A有一系列点Px1,Py1;物理坐标系B有一系列点Wx1,Wy1;通过标定运算获得坐标系A到坐标系B的转换关系Matrix,即我们生成的标定文件。

生产:已知坐标系A的一个点currentPtA;求解该点在坐标系B中的对应点currentPtAMapB。

求解:currentPtAMapB = currentPtA*Matrix

  1. 标定的分类

标定的目的是统一坐标系,但是标定的过程不一样,标定的流程也会有所不同。下图是外参标定的常用方法,本文我们以手眼标定中12点标定为例讲解。

2.png

  1. 12点相对标定

12点标定是9点标定和旋转标定的总和。主要用于处理不共轴的问题。不共轴:即运行点和基准点不重合,当基准点发生旋转时,运行点会发送变化的现象。常见的不共轴原因有①图像处理引起,②机构引起,③图像定位点与机构定位点不重合引起。

3.png

9点标定的结果是个3×3的矩阵Matrix,旋转标定的结果是点Center,而12点标定则是将前者统一起来,做了原点归一化,将旋转中心当做原点(零点)。

4.png

将旋转中心(CenterX,CenterY)与12点标定矩阵相乘,即做标定转换,可发现输出结果为(0,0),由此可见12点标定是将原点定在了旋转中心上,旋转中心即为零点。

注意:由上面的关系可知,当基准拍照位置变化后,需要重新做12点标定,因为旋转中心发生了移动。所以12点标定的拍照点必须与生产时的拍照点完全一致。

  1. 一键标定流程中标定与通讯的配合

5.png

一键标定可分为三个阶段:

  • 开始标定:机构移动到拍照位,如上图0的位置(和后续的5,10位置相同),吸取标定对象(可以是产品),然后给视觉软件发送开始标定指令
  • XY平移标定:机构按设定的偏移值依次移动到上图9个位置,然后给视觉发送到位信号。
  • 旋转标定:机构依次移动到上图的3个位置(此时XY要保持不动,只能选择R轴),然后给视觉发送到位信号。

二、VM软件二次开发进行一键手眼标定

  1. 标定方案的搭建

这里我们以VisionMaster例程中的N点标定方案为例,方案默认路径为C:\Program Files\VisionMaster3.4.0\Applications\Samples\CH\软件功能展示\标定\N点标定

通过特征匹配模块获取图像Mark点作为图像点传给N点标定模块。

6.png

  1. 二次开发的一键标定流程。

首先获取到N点标定模块的基本参数。

                    //清空N点标定数据点
                    int nRet = ImvsPlatformSDK_API.IMVS_PF_SetParamValue_CS(m_handle, 3, "ClearPoint", "null");

                    //标定结束标志位
                    CalibEnd = false;

                    //获取参数偏移量X
                    string MoveX = string.Empty;
                    string MoveY = string.Empty;
                    string MoveAngle = string.Empty;

                    nRet = ImvsPlatformSDK_API.IMVS_PF_GetParamValue_CS(m_handle, 3, "MoveAlignX", 1024, ref MoveX);
                    nRet = ImvsPlatformSDK_API.IMVS_PF_GetParamValue_CS(m_handle, 3, "MoveAlignY", 1024, ref MoveY);
                    nRet = ImvsPlatformSDK_API.IMVS_PF_GetParamValue_CS(m_handle, 3, "MoveAngle", 1024, ref MoveAngle);

                    //获取参数基准点
                    string BasicX = string.Empty;
                    string BasicY = string.Empty;
                    nRet = ImvsPlatformSDK_API.IMVS_PF_GetParamValue_CS(m_handle, 3, "BasePointX", 1024, ref BasicX);
                    nRet = ImvsPlatformSDK_API.IMVS_PF_GetParamValue_CS(m_handle, 3, "BasePointY", 1024, ref BasicY);

 

然后根据获取的基准点,偏移值,计算出要走的12点点位,然后开始走9宫格。

                    //计算要走到的工位
                    PointF[] absoluteFList = new PointF[9];
                    for (int i = 0 ; i < 9 ; i++)
                    {
                        absoluteFList[i] = new PointF();

                        if (i == 0 || i == 5 || i == 6)
                        {
                            absoluteFList[i].fX = Convert.ToSingle(BasicX) - Convert.ToSingle(MoveX);
                        }
                        else if (i == 1 || i == 4 || i == 7)
                        {
                            absoluteFList[i].fX = Convert.ToSingle(BasicX);
                        }
                        else
                        {
                            absoluteFList[i].fX = Convert.ToSingle(BasicX) + Convert.ToSingle(MoveX);
                        }

                        if (i < 3)
                        {
                            absoluteFList[i].fY = Convert.ToSingle(BasicY) - Convert.ToSingle(MoveY);
                        }
                        else if (i < 6)
                        {
                            absoluteFList[i].fY = Convert.ToSingle(BasicY);
                        }
                        else if (i < 9)
                        {
                            absoluteFList[i].fY = Convert.ToSingle(BasicY) + Convert.ToSingle(MoveY);
                        }
                    }

                    float fDeltaX = 0F;
                    float fDeltaY = 0F;
                    float fDeltaTheta = 0F;
                    float fPreX = 0F;
                    float fPreY = 0F;
                    float fPreTheta = 0F;

                    //开始走九宫格
                    for (int i = 0 ; i < 9 ; i++)
                    {
                        fDeltaX = absoluteFList[i].fX - fPreX;
                        fDeltaY = absoluteFList[i].fY - fPreY;

                        //走到对应位置
                        GoOnePos(fDeltaX, fDeltaY, 0F);
                        fPreX = absoluteFList[i].fX;
                        fPreY = absoluteFList[i].fY;
                        this.AddToMessage(string.Format("第" + (i + 1) + "点"+ absoluteFList[i].fX+ "," +absoluteFList[i].fY + "," +0));
                        //睡眠300ms
                        Thread.Sleep(300);

                        //触发相机拍照
                        nRet = ImvsPlatformSDK_API.IMVS_PF_ExecuteOnce_V30_CS(m_handle, 10000, "calib");
                        Thread.Sleep(20);

                        //循环得到返回结果
                        int waitForReceived = 0;
                        while (true)
                        {
                            if (waitForReceived > 150)
                            {
                                throw new Exception();
                            }

                            if (!IsRecData)
                            {
                                waitForReceived++;
                                Thread.Sleep(100);
                                continue;
                            }
                            else
                            {
                                IsRecData = false;
                                waitForReceived = 0;
                                break;
                            }
                        }
                    }

注意:1)上面代码中的GoOnePos(fDeltaX, fDeltaY, 0F)为通讯的函数,可使用自己习惯的通讯库开发,其中需要有机构运动到位信号,即发送给机构偏移坐标后,需收到到位回复再进行下一步动作。2)fDeltaX, fDeltaY, 0F为即将运动到的下一点坐标与当前坐标的偏移值。

走位9点平移标定后,再回到原点,开始旋转标定。

                    //回到初始位置
                    fDeltaX = absoluteFList[4].fX - absoluteFList[8].fX;
                    fDeltaY = absoluteFList[4].fY - absoluteFList[8].fY;
                    GoOnePos(fDeltaX, fDeltaY, 0F);

                    float[] fAngle = new float[3];
                    fAngle[0] = 0F - Convert.ToSingle(MoveAngle);
                    fAngle[1] = 0F;
                    fAngle[2] = 0F + Convert.ToSingle(MoveAngle);

                    for (int i = 0 ; i < 3 ; i++)
                    {
                        fDeltaTheta = fAngle[i] - fPreTheta;
                        fPreTheta = fAngle[i];
                        //移动到位
                        GoOnePos(0F, 0F, fDeltaTheta);
                        this.AddToMessage(string.Format("第" + (i + 10) + "点" + 0 + "," + 0+ ","+fDeltaTheta));

                        //睡眠300ms
                        Thread.Sleep(300);

                        IsRecData = false;
                        //触发相机拍照
                        nRet = ImvsPlatformSDK_API.IMVS_PF_ExecuteOnce_V30_CS(m_handle, 10000, "calib");
                        Thread.Sleep(20);

                        //循环得到返回结果
                        int waitForReceived = 0;
                        while (true)
                        {
                            if (waitForReceived > 150)
                            {
                                throw new Exception();
                            }
                            if (!IsRecData)
                            {
                                waitForReceived++;
                                Thread.Sleep(100);
                                continue;
                            }
                            else
                            {
                                IsRecData = false;
                                waitForReceived = 0;
                                break;
                            }
                        }
                    }

最后回到原点,开始生成标定矩阵。

                    //回到初始位置
                    fDeltaTheta = fAngle[1] - fAngle[2];
                    GoOnePos(0F, 0F, fDeltaTheta);
                    Thread.Sleep(500);
                    Thread.Sleep(20);
                    nRet = ImvsPlatformSDK_API.IMVS_PF_ExecuteOnce_V30_CS(m_handle, 10000, "calib");
                    Thread.Sleep(200);
                    // 判断结果是否可用
                    if (!CalibEnd)
                        throw new Exception("标定失败!");

                    try
                    {
                        //生成标定文件
                        string strMatPath = AppDomain.CurrentDomain.BaseDirectory + @"\Calib\" + DateTime.Now.ToString("yyyyMMddhhmmss_") + "CalibFile.iwcal";

                        //生成标定文件
                         nRet = ImvsPlatformSDK_API.IMVS_PF_SetParamValue_CS(m_handle, 3, "SaveCalibPath", strMatPath);
                        if (ImvsSdkPFDefine.IMVS_EC_OK == nRet)
                        {
                            AddToMessage("标定成功!");
                        }
                    }
                    catch (Exception ex)
                    {
                        throw new Exception(ex.Message);
                    }

上面代码中的IsRecData和CalibEnd为是否收到标准图像坐标和是否标定完成的bool标志位,IsRecData主要为确认执行一次后,N点标定模块成功收到本次的图像坐标点;CalibEnd为N点标定的标定状态,当标定点不满12点时每次返回0,满12点后返回1,因此可以通过判断该状态来确定N点标定模块是否成功接收到12组数据。这两个标志位可在回调中如下面设置。

 private void UpdateDataModuResultOutput(ImvsSdkPFDefine.IMVS_PF_MODU_RES_INFO struResultInfo)
        {
            if (null == struResultInfo.pData)
            {
                return;
            }
            switch (struResultInfo.strModuleName)
            {
                case ImvsSdkPFDefine.MODU_NAME_ALIGNCALIBMODU:
                    ImvsSdkPFDefine.IMVS_PF_ALIGNCALIB_MODU_INFO stCalibInfo = (ImvsSdkPFDefine.
                        IMVS_PF_ALIGNCALIB_MODU_INFO)Marshal.PtrToStructure(struResultInfo.pData,
                        typeof(ImvsSdkPFDefine.IMVS_PF_ALIGNCALIB_MODU_INFO));
                    if (1 == stCalibInfo.iModuStatu)
                    {
                        CalibEnd = true;
                    }

                    if(stCalibInfo.pstImagePt[stCalibInfo.iCalibIndex-1].fPtX !=0 || stCalibInfo.pstImagePt[stCalibInfo.iCalibIndex - 1].fPtY != 0)
                    {
                        IsRecData = true;
                    }
                    break;

                default: break;
            }
        }

三、效果展示

这里本文使用调试助手模拟PLC和软件通讯

7.png

最终生成标定文件:

8.png