海康威视VisionMaster算法软件如何通过二次开发进行手眼标定
海康威视VM算法软件如何通过二次开发进行手眼标定
一、什么是手眼标定
在做手眼标定之前,我们一定要弄清楚什么是标定。
1、标定的本质
标定的目的是统一坐标系,将图像坐标系中的点转换到物理坐标系中,然后在物理坐标系中进行数值处理。从数学的角度来看标定:
标定:已知像素坐标系A有一系列点Px1,Py1;物理坐标系B有一系列点Wx1,Wy1;通过标定运算获得坐标系A到坐标系B的转换关系Matrix,即我们生成的标定文件。
生产:已知坐标系A的一个点currentPtA;求解该点在坐标系B中的对应点currentPtAMapB。
求解:currentPtAMapB = currentPtA*Matrix
- 标定的分类
标定的目的是统一坐标系,但是标定的过程不一样,标定的流程也会有所不同。下图是外参标定的常用方法,本文我们以手眼标定中12点标定为例讲解。
- 12点相对标定
12点标定是9点标定和旋转标定的总和。主要用于处理不共轴的问题。不共轴:即运行点和基准点不重合,当基准点发生旋转时,运行点会发送变化的现象。常见的不共轴原因有①图像处理引起,②机构引起,③图像定位点与机构定位点不重合引起。
9点标定的结果是个3×3的矩阵Matrix,旋转标定的结果是点Center,而12点标定则是将前者统一起来,做了原点归一化,将旋转中心当做原点(零点)。
将旋转中心(CenterX,CenterY)与12点标定矩阵相乘,即做标定转换,可发现输出结果为(0,0),由此可见12点标定是将原点定在了旋转中心上,旋转中心即为零点。
注意:由上面的关系可知,当基准拍照位置变化后,需要重新做12点标定,因为旋转中心发生了移动。所以12点标定的拍照点必须与生产时的拍照点完全一致。
- 一键标定流程中标定与通讯的配合
一键标定可分为三个阶段:
- 开始标定:机构移动到拍照位,如上图0的位置(和后续的5,10位置相同),吸取标定对象(可以是产品),然后给视觉软件发送开始标定指令
- XY平移标定:机构按设定的偏移值依次移动到上图9个位置,然后给视觉发送到位信号。
- 旋转标定:机构依次移动到上图的3个位置(此时XY要保持不动,只能选择R轴),然后给视觉发送到位信号。
二、VM软件二次开发进行一键手眼标定
- 标定方案的搭建
这里我们以VisionMaster例程中的N点标定方案为例,方案默认路径为C:\Program Files\VisionMaster3.4.0\Applications\Samples\CH\软件功能展示\标定\N点标定
通过特征匹配模块获取图像Mark点作为图像点传给N点标定模块。
- 二次开发的一键标定流程。
首先获取到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和软件通讯
最终生成标定文件: