MIL开发实践(3)——MIL触发采图
目录
-
引言
- 结果展示部分
- 正文内容如下:
- 首先将系统进入采集状态。
- 然后启动图像采集程序。
- 接着关闭与图像采集相关的设备。
- 最后释放图像采集权限并完成数据处理流程。
- 总结
前言
在完成了上一阶段的功能之后,在我们的实现中核心调用的是这个回调函数。具体来说,在我们的实现架构中,默认情况下会调用此回调机制来处理事件响应。随后就需要完成触发采样操作的具体实施步骤了。这一触发采样的耗时明显长于之前的操作,并且主要原因是由于对所使用的函数机制理解得不够透彻。在接下来的时间里我会向您详细说明整个流程的具体工作原理以及相关的技术细节。
MdigHookFunction(MilDigitizer, M_GRAB_FRAME_START, ProcessingFunction, &UserHookData);
MdigControl( MilDigitizer, M_GRAB_TRIGGER, M_ENABLE);
MdigControl( MilDigitizer, M_GRAB_TRIGGER_SOURCE, M_SOFTWARE);
该ProcessingFunction会在程序执行采图操作并触发特定事件时被激活。采用MdigContinuous作为图像采集的核心模块,并具备连续捕获图像的能力。其显著特点是在捕获过程中,图像直接送到显存中,在采集过程中是直接送到显存中的MdigGrabContinuous为异步采集模式,在采集过程中是直接送到显存中的不存储于MilBufferImage变量中。只有当采集结束时才会保存最后一帧到 MilBufferImage 中这一特性主要用于实现实时监控功能。若希望实现即时图像捕获与处理功能,则需使用MdigGrab和MdigProcess这两个模块。
效果图

因为这里的相机没有加光源和镜头,所以整体的效果看起来就是这样。
正文
打开回调采图模式
为了实现图像采集功能的触发,请先打开TriggerMode。这相当于上一步骤中所提到的操作。该操作会调用下面提到的功能模块:GrabThreadStart。请关闭图像采集触发模式,并在不再需要图像采集时关闭此功能。
qint32 MDeviceE2v::GrabThreadStart()
{
qint32 ret = RETURN_FAIL;
#ifdef WIN32_E2v
MIL_INT WidthVal = 0;
//下面的这个语句是获得相机参数的语句,但是对于CL接口的相机是不能使用的,CXP接口的才能使用
//MdigInquireFeature(MilDigitizer, M_FEATURE_VALUE, MIL_TEXT("Width"), M_TYPE_INT64, &WidthVal);
// MdigControl(MilDigitizer, M_GRAB_TRIGGER_SOURCE, M_SOFTWARE);
// MdigControl(MilDigitizer, M_GRAB_TRIGGER_STATE, M_ENABLE);
MdigGrabContinuous(MilDigitizer, MilImage);
MdigHookFunction(MilDigitizer, M_GRAB_FRAME_END, ProcessingFunction, &UserHookData);
MdigControl( MilDigitizer, M_GRAB_TRIGGER, M_ENABLE );
MdigControl( MilDigitizer, M_GRAB_TRIGGER_SOURCE, M_SOFTWARE);
MdigControl( MilDigitizer, M_GRAB_MODE, M_ASYNCHRONOUS );
MIL_INT GrabContinuousEndTrigger = 0;
// MIL_INT GrabTriggerActivation = 0;
// MIL_INT GrabTriggerSource = 0;
// MIL_INT GrabTriggerState = 0;
MdigInquire(MilDigitizer, M_GRAB_CONTINUOUS_END_TRIGGER, &GrabContinuousEndTrigger);
// MdigInquire(MilDigitizer, M_GRAB_TRIGGER_ACTIVATION, &GrabTriggerActivation);
// MdigInquire(MilDigitizer, M_GRAB_TRIGGER_SOURCE, &GrabTriggerSource);
// MdigInquire(MilDigitizer, M_GRAB_TRIGGER_STATE, &GrabTriggerState);
qDebug()<<"GrabContinuous is"<<GrabContinuousEndTrigger;
if (GrabContinuousEndTrigger == M_DEFAULT) // Specifies the default value.
{
qDebug()<<"GrabContinuousEndTrigger is M_DEFAULT";
}
else if (GrabContinuousEndTrigger == M_DISABLE) // Specifies not to generate the trigger automatically.
{
qDebug()<<"GrabContinuousEndTrigger is M_DISABLE";
}
else if (GrabContinuousEndTrigger == M_ENABLE) // Specifies to generate the trigger automatically.
{
qDebug()<<"GrabContinuousEndTrigger is M_ENABLE";
}
MIL_INT AllocationOverscan = 0;
MsysInquire(MilSystem, M_ALLOCATION_OVERSCAN, &AllocationOverscan);
if (AllocationOverscan == M_DEFAULT) // M_DEFAULT.
{
qDebug()<<"AllocationOverscan M_DEFAULT";
}
else if (AllocationOverscan == M_DISABLE) // Specifies that image buffers allocated on the system will have no overscan region.
{
qDebug()<<"AllocationOverscan M_DISABLE";
}
else if (AllocationOverscan == M_ENABLE) // Specifies that image buffers are allocated on the system with an overscan region.
{
qDebug()<<"AllocationOverscan M_ENABLE";
}
ret = RETURN_OK;
#endif
return ret;
}
下面稍微解释一下这个函数:
首先强调的是,在开始前必须先执行回调函数。否则无法确保后续操作中能够采集到目标图像。
MdigHookFunction(MilDigitizer, M_GRAB_FRAME_START, ProcessingFunction, &UserHookData);
在后续步骤中, 需要对配置好的触发模式进行详细设置, 并明确设定该系统的触发类型(软 Trigger vs 硬 Trigger), 同时确保参数中包含相关的启动选项(如立即启动或开启相应的Trigger机制)。
MdigControl( MilDigitizer, M_GRAB_TRIGGER, M_ENABLE );#触发使能命令
MdigControl( MilDigitizer, M_GRAB_TRIGGER_SOURCE, M_SOFTWARE);#软触发还是硬触发
MdigControl( MilDigitizer, M_GRAB_MODE, M_ASYNCHRONOUS );# 同步命令
这些命令我是从哪里知道的呢?你需要去看文档或者去看

这个地方可能有很多关于这种操作该执行哪些命令的信息。相比而言,这可能显得比你查阅文档更为方便。
-
此时,在此开启连续采图操作。否则的话,则会导致触发失去作用。所采用的捕获函数为连续捕获函数。
MdigGrabContinuous(MilDigitizer, MilImage); -
下面内容基本上属于扩展性质,在我的情况下,并没有实际应用价值。或许对你会有帮助我可以简单介绍一下里面这个语句:
MdigInquireFeature(MilDigitizer, M_FEATURE_VALUE, MIL_TEXT("Width"), M_TYPE_INT64, &WidthVal);
我已对此语句进行了注释处理,并附加了相关说明。在查阅文档时,我以为该指令旨在实现相机参数读取功能。然而,在深入分析后发现该指令无法实现预期功能。当运行此指令时会导致系统崩溃,请参考我的第四篇文章以获取相机参数读取的具体操作。
CoaXPress (CXP) 是一种专为机器视觉行业设计的同轴电缆不对称高速串行通信规范。它特别适用于需要高分辨率成像并快速将图像传输至主机的应用场景,在这一领域中被视为高效可靠的解决方案。
但若未采用该接口则会面临诸多不便。因此,在使用CXP接口时,请考虑采用这一技术方案:无需按照较为复杂的方式从我的第四篇文章中提取所需参数并配置串口设置。
- 接下来的这个函数是这个:
MdigInquire(MilDigitizer, M_GRAB_CONTINUOUS_END_TRIGGER, &GrabContinuousEndTrigger);
该函数的作用是获取MIL采集卡的相关参数。请注意这一点:它的返回值与常规使用情况有所不同。尽管它是基于MIL_INT进行声明……但与Int之间的差异并非微不足道——它返回的数据大多对应于ComBox中的各项数值。因此,在需要调用该函数时,请确保正确理解其用途。对于该函数的第二个参数,请参考上文所述相关内容。

关闭回调采图模式
先给出完整的关闭函数:
qint32 MDeviceE2v::GrabThreadStop()
{
qint32 ret = RETURN_FAIL;
#ifdef WIN32_E2v
qDebug()<<"MDeviceE2v::GrabThreadStop123";
if(m_bLoaded&&m_cTriggerFlag)
{
MdigHalt(MilDigitizer);
qDebug()<<"-->GrabThreadStop"<<m_bLoaded<<m_bStopWork;
MdigHookFunction(MilDigitizer, M_GRAB_FRAME_END+M_UNHOOK, ProcessingFunction, &UserHookData);
m_cTriggerFlag = false;
m_cChangeModeFlag2 = false;
ret = RETURN_OK;
}
#endif
return ret;
}
下面解析比较重要的几个点:
在操作过程中,请务必确保关闭相机的采图操作。否则的话, 你就无法继续执行其他功能.
MdigHalt(MilDigitizer);
然后必须关闭之前开启的回调模式。否则如果不这样做的话,在试图进行实时采图时也会自动触发该回调函数。请确保以下步骤已正确配置。
MdigHookFunction(MilDigitizer, M_GRAB_FRAME_END+M_UNHOOK, ProcessingFunction, &UserHookData);
关闭采图的相机等
关于如何关闭此Digitizer和其Display功能,请您在您的程序结束时调用析构函数来完成关闭操作。否则就会导致错误,并且错误信息来自MIL日志而非QT应用程序日志。具体实现代码如下:
MDeviceE2v::~MDeviceE2v()
{
qDebug()<<"~MDeviceE2v()";
#ifdef WIN32_E2v
m_fGrabCallbackEx =Q_NULLPTR;
m_fXmlChange =Q_NULLPTR;
m_fSettingChange =Q_NULLPTR;
m_fException =Q_NULLPTR;
SrcImageDataPtr = NULL;
PhysicsImagePtr = NULL;
MdigHalt(MilDigitizer);
if (M_NULL != MilImage)
{
MbufFree(MilImage);
}
MdigHalt(UserHookData.MilDigitizer);
MdispFree(MilDisplay);
m_pSerial->close();
#endif
}
打开采图模式
当你切换至回调模式时,该操作会立即开始执行.具体来说,这等同于开启实时采图功能.与之前所采用的实时采图方法相同,以下代码即可实现这一功能.如有进一步需求,请参考我的第二篇文章:
//开启采图过程
qint32 MDeviceE2v::AcquisitionStart()
{
qint32 ret = RETURN_FAIL;
int nGrabScaleSet = 4;//设置的采集比例
qDebug()<<"this"<<this;
#ifdef WIN32_E2v
qDebug()<<"MDeviceE2v::AcquisitionStart123 m_bLoaded and m_bStopWork"<<m_bLoaded<<m_bStopWork;
if(m_bLoaded)
{
if(m_bStopWork)
{
if (MilDigitizer)
{
UserHookData.MilImageDisp = MilImage;//这行代码一但注释掉就报错
UserHookData.MilDigitizer = MilDigitizer;
UserHookData.ProcessedImageCount = 0;
MgraFont(M_DEFAULT, M_FONT_DEFAULT_LARGE);
/*这部分会在图像中绘制一个不旋转或不填充颜色的矩形,或将其添加到图形列表中。
MgraText(M_DEFAULT, MilImage, (BufSizeX/8)*2, BufSizeY/2,
MIL_TEXT(" Welcome to MIL !!! "));
MgraRect(M_DEFAULT, MilImage, ((BufSizeX/8)*2)-60, (BufSizeY/2)-80,
((BufSizeX/8)*2)+370, (BufSizeY/2)+100);
MgraRect(M_DEFAULT, MilImage, ((BufSizeX/8)*2)-40, (BufSizeY/2)-60,
((BufSizeX/8)*2)+350, (BufSizeY/2)+80);
MgraRect(M_DEFAULT, MilImage, ((BufSizeX/8)*2)-20, (BufSizeY/2)-40,
((BufSizeX/8)*2)+330, (BufSizeY/2)+60);
*/
MbufInquire(MilImage, M_HOST_ADDRESS, &SrcImageDataPtr);//数据指针
MbufInquire(MilImage,M_PHYSICAL_ADDRESS,&PhysicsImagePtr);//数据物理地址指针,仅供主线意外的
MbufInquire(MilImage, M_SIZE_BYTE, &SrcImageDataSize);//数据大小
ret = RETURN_OK;
m_bStopWork =false;
}
}
}
#endif
return ret;
}
因为这种功能仅为了保持整体架构的完整性,在本设备上并未被应用;然而,在其他设备中通常会执行此操作。如需进一步了解,请参考关于大恒系列设备的操作指南。
触发操作
进入触发模式后, 自然进入触发流程, 本系统采用软按钮控制的方式, 通过点击TriggerSoftware按钮来控制相关功能, 具体操作流程将在下文详细说明如下:TriggerSoftwareExecute
qint32 MDeviceE2v::TriggerSoftwareExecute()
{
qint32 ret = RETURN_FAIL;
qDebug()<<"m_fGrabCallbackEx6"<<m_fGrabCallbackEx;
m_bStopWork = true;
if(MilDigitizer!=M_NULL)
{
MdigControl(MilDigitizer, M_GRAB_TRIGGER_SOFTWARE,1);
}
ret = RETURN_OK;
return ret;
}
执行按钮操作后立即开始的操作:
MdigControl(MilDigitizer, M_GRAB_TRIGGER_SOFTWARE,1); 这个代码段负责完成一次触发动作。触发完成后立即开始的操作即为此处的核心逻辑部分——启动相应的回调机制。经过一番准备工作完成后立即进入主流程并启动指定回调程序这一阶段通常被称为系统响应阶段在此阶段我们将打开ProcessingFunction这一关键组件请继续往下了解它的功能与作用。
执行回调函数
接下来,开始执行回调函数,完整的回调函数如下:
MDeviceE2v *globalPoint;
MIL_INT MFTYPE ProcessingFunction(MIL_INT HookType, MIL_ID HookId, void* HookDataPtr)
{
HookDataStruct *UserHookDataPtr = (HookDataStruct *)HookDataPtr;
MIL_ID ModifiedBufferId = 0;
//得到buffer 如果
MdigGetHookInfo(HookId, M_MODIFIED_BUFFER+M_BUFFER_ID, &ModifiedBufferId);
qDebug()<<"ModifiedBufferld HookType"<<ModifiedBufferId<<HookType;//先确定被修改的bufferID,找个HookType是一个字符串
//处理完的Buffer数据复制到现实buffer
MbufCopy(ModifiedBufferId,UserHookDataPtr->MilImageDisp);
UserHookDataPtr->ProcessedImageCount++;
globalPoint->triggerEvent(UserHookDataPtr);
return 0;
}
void MDeviceE2v::triggerEvent(HookDataStruct *UserHookDataPtr)
{
#ifdef WIN32_E2v
if(m_bStopWork)
{
qDebug()<<"triggerEvent m_bStopWork is true";
//return;
}
MIL_INT BufSizeX2 = 0;
MIL_INT BufSizeY2 = 0;
MIL_INT SrcImageDataSize2 = 0;
void *SrcImageDataPtr2 = NULL;
if(UserHookDataPtr->MilDigitizer&&UserHookDataPtr->MilImageDisp)
{
MdigInquire(UserHookDataPtr->MilDigitizer, M_SIZE_X, &BufSizeX2);
MdigInquire(UserHookDataPtr->MilDigitizer, M_SIZE_Y, &BufSizeY2);
MbufInquire(UserHookDataPtr->MilImageDisp, M_SIZE_BYTE, &SrcImageDataSize2);//数据大小
MbufInquire(UserHookDataPtr->MilImageDisp, M_HOST_ADDRESS, &SrcImageDataPtr2);//数据指针
qDebug()<<"triggerEvent"<<BufSizeX2<<BufSizeY2<<SrcImageDataSize2<<SrcImageDataPtr2;
qDebug()<<"m_fGrabCallbackEx3"<<m_fGrabCallbackEx;
if(m_fGrabCallbackEx)
{
MFrameInfo info;
info.nWidth = BufSizeX2;
info.nHeight = BufSizeY2;
info.nFramerLen = SrcImageDataSize2;
//info.cFormat = pFrameInfo.getImagePixelFormat();
uchar *pbit = (uchar*)SrcImageDataPtr2;//得到指向Buffer的地址
qDebug()<<"pBit"<<pbit;
qDebug()<<"m_pCallUser"<<m_pCallUser;
m_fGrabCallbackEx(m_pCallUser,pbit,&info);
}
else
{
qDebug()<<"m_fGrabCallbackEx is null";
}
}
else
{
qDebug()<<"UserHookDataPtrb is Null";
}
#endif
}
下面进行详细讲解:
当我们遇到这种情况时,在调用之前,并没有将这三个参数传递给MdigHookFunction;相反,在调用该函数时,并未提供任何额外的信息或数据;结果是这三个参数都被包含进去;这与我们之前所学的形参与实参的概念有所不同;如果你好奇具体的实现细节,则可以参考官方文档;其中建议的做法是直接输入该函数名即可;这样处理后会得到一个较为合理的响应结果;
HookDataStruct *UserHookDataPtr = (HookDataStruct *)HookDataPtr;
这个HookDataStruct的定义是这样的:
typedef struct
{
MIL_ID MilDigitizer;
MIL_ID MilImageDisp;
MIL_INT ProcessedImageCount;
} HookDataStruct; //自定义的要传递给回调函数的参数结构
因此,在输入时这个参数有三个可能的取值。这也是我们为何需要对其进行强制转换的原因。
- 随后的一条语句用于获取采集图像对应的bufferID值,并主要承担测试功能的角色,在当前应用场景中其作用并不显著程度不高因此建议删除
MdigGetHookInfo(HookId, M_MODIFIED_BUFFER+M_BUFFER_ID, &ModifiedBufferId);
- 这个关键语句构成了整个操作的核心环节。将该数据重新传入我们的插件中后发现存在一个问题:当前使用的回调函数属于全局函数无法直接调用我们插件类中的相关函数。关于这一知识点建议自行查阅相关资料获取更多信息。为了实现当前功能我采用了较为基础的方法即在外部创建一个全局变量并命名为
MDeviceE2v *globalPoint;通过该变量来间接指向需要触发的事件triggerEvent。在此前的方法中我们会在这个回调函数中传递一个指向该插件类的指针但因为ProcessFunction没有接受这种指针参数所以导致无法完成必要的功能调用。
globalPoint->triggerEvent(UserHookDataPtr);
接下来是这个triggerEvent这个函数执行的过程,它负责处理从缓冲区获取信息并将相关信息及其地址发送到顶层显示界面.在这里我们采用了 callback机制m_fGrabCallbackEx来进行数据传输.
总结
在处理图像采集的触发机制时
MIL开发实践(1)——环境配置准备
MIL开发实践(2)——MIL实时图像捕捉
MIL开发实践(4)——E2V相机参数配置
若有错误,欢迎指出,感谢~
