前言
接上面的功能完成之后,接下来就要完成触发采图了,这个触发采图耗费的时间比上面那个要长多了,主要原因还是在于对使用的函数理解不够深刻,后面听我细细道来。
首先,我们最主要使用的是这个回调函数,
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);
- 接下来,肯定是要对触发模式进行设置,要设置说你要的触发到底是软触发还是硬触发,是否马上开启触发等命令:
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);
这个语句被我注释掉了,并且也有进行了说明,我在查看文档的时候以为这个是可以读取相机参数的命令,但经过详细查看,发现是不行的,一旦执行这个语句,程序就会崩溃,关于读取相机参数的操作可以看一下我的第四篇文章会讲关于这部分的内容。因为我使用的接口是CL的,也就是CameraLink的,而这个接口只能被CoaXPress接口所使用,关于这个接口的定义是这样的:
CoaXPress (CXP) 是一项同轴电缆不对称高速串行通信标准,是专为机器视觉行业开发的一种数字接口规范。 对于需要高分辨率成像以及图像快速传输到主机的机器视觉应用,该标准的高速高带宽数据传输能力可谓理想的解决方案。
但我没用到就没办法了。所以,如果你的是CXP接口的话,可以尝试一下使用这个接口,就没必要按照我第四篇那么麻烦的方法去读取相应的参数以及采用串口的方式去设置参数了。
- 接下来的这个函数是这个:
MdigInquire(MilDigitizer, M_GRAB_CONTINUOUS_END_TRIGGER, &GrabContinuousEndTrigger);
这个函数是用来读取MIL这个采集卡的参数的,你要注意,它的返回值与我们平常用的不太一样,虽然它是使用MIL_INT进行声明的,但与Int差别不是一点点,它返回的东西很多都是对应的ComBox里面的值。所以,如果你要用到MIL的参数的话,你可以使用这个函数进行读取。关于这个函数的第二个参数,你还是需要去看上面第二点讲的那个地方的那个截图就是了,你找到里面你想要的参数打开。
关闭回调采图模式
先给出完整的关闭函数:
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的打开,你需要在你程序的析构函数,在你关闭你程序的时候执行的析构函数上面进行关闭,不然就一定会报错,不是QT报的错,而是MIL报的错。我关闭的语句是:
~MDeviceE2v
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
,下面进行详细讲解:
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
只传了这个函数的函数名而已,怎么会三个参数就都进去了,这跟我们之前学的形参和实参不太一样, 如果你想知道具体的过程,就去查询文档吧,把函数名输进去就可以了,反正,我这里就解释为:当你执行了这个MdigHookFunction
的时候,MIL会自动把三个参数给填进去这样理解就可以了。 - 接下来,对传进来的第三个参数进行强转:
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这个函数的执行过程,主要做的操作是把得到的这个buffer里面的信息和这个buffer的地址传给最上层的显示界面,我这里使用的是以一个回调函数
m_fGrabCallbackEx
的方式进行传递这些信息。
总结
关于触发采图的话,可能难点就是在之前我在挑选采图函数的时候,我选了MdigProcess
,这个可以采图,但它的触发信号是在每次有帧事件的时候就执行了,所以,不是我们想要的触发条件,所以,我们使用MdigHookFunction+MdigGrabContinuous
。
如果有需要可以去看看我的另外三篇文章。感谢您的观看~
若有错误,欢迎指出,感谢~