前面讲到的都是离线的图像获取方法,实际中我们做机器视觉都是在线采集图像和处理,处理结果决定了计算机要给出的控制信号如电机运动等,这样就实现了实时视觉反馈运动。MIL中的采集需要Matrox采集板卡的支持,本文中以实验室的Matrox Helios板卡为例讲解MIL的采集。

1.采集系统构成

谈到采集,首先必须理解一套完整的采集系统从硬件到软件的构成,下面采集系统示意图采用Matrox板卡、MIL软件,图中各种CPU、MCU、GPU交互通信的详细过程并没有表示出来,只是为了说明大概流程,实际过程中完整采集系统差别不大。(以后有时间我会考虑单独出一个机器视觉硬件系列博文,后话啦)

对照上图,简要说明一下采图过程:光源照射下,物体反射光经过相机镜头在相机CCD(或CMOS)芯片上,这个过程成称为Capture,相机的时序控制器控制间隔一定的时间将CCD中的数据传输到相机的缓存Buffer中,这个过程称为Acquisition,注意如果这个Buffer的数据不及时取出来的话下次acquisition会覆盖以前的数据,相机连接到插入PCI-E接口上的Matrox板卡上,在板卡上的时序控制单元(Time control unit)控制从相机中Buffer中拿数据,这一过程称为Grab,从相机buffer中拿的还是模拟信号,在板卡中会通过A/D单元做一个A/D转换,将拿到的数据转成相应量化的数值存到相应的MIL buffer中,这一过程称为Digtize。在这里Capture和Acquisition在相机(Camera)中完成输出的是模拟信号,这个相机是模拟相机,Grab和Digtize在相机采集板卡(Frame Grabber)中完成,一般这样的相机和板卡之间用的是Camera Link接口,也有用1394接口的,适用于高速采集的情况;也有相机将Capture、Acquisition、Grab、Digtize做在一起的,实际上这也是大多数普通工业相机(13fps-30fps)的做法,他们输出的是数字信号,称为数字相机,一般采用GigE 、1394或USB接口。注意这里我用红字标识的四个英文单词Capture、Acquisition、Grab、Digtize,他们都可以翻译为采集,英文有些单词意义近似但是有微妙的不同,用中文是没有办法明确的区分它们的意思,事实上,我们通常所说的采集是站在PC获取物体图像的角度来说的,是这四个过程的总称。

当我们在上位机(PC)中操作整个采集过程,MIL提供给我们用于采集的是分配的Digtizer对象,对应Mdig开头函数。在分配Digitizer对象时要同时指明一个DCF(Device Configure File)文件,这个文件定义了Grab时的频率和分辨率等等,是非常重要的,简单来说就是相机时序控制器往相机Buffer存入数据的频率和板卡时序控制单元从相机Buffer中获取数据的频率必须有一个匹配关系。默认MIL安装时会让用户设置一个默认的DCF文件,分配Digtizer时默认使用这个文件,MIL提供了一系列对应相机的DCF文件,如果没有还可以在MIL Intellicam中自定义DCF文件,同一个相机,可以在定义Digtizer时候采用不同的DCF文件改变采集的频率和采集的图像大小,如图中的Digtizer1和Digtizer2,如果想在定义了Digtizer以后实时调整DCF中对应的采集参数可以用MdigControl开头函数。

下面对照上图说明几个概念,对大家看手册有帮助:

Acquisition path:从Capture历经Acquisition、Grab到Digtize一条完整的过程,如图对于彩色相机有6条Acquisition path(RGB每一个算一个通道,即你可以把彩色相机当做单色相机来使),对于单色相机分别各有2条Acquisition path,每一条Acquisition path都必须包含一个Time control unit。每条Acquisition path上可连接若干Digtizer,如图中Digtizer1和Digitizer2,但是同一Acquisition path上一次只能有一个Digtizer工作,你可以预分配多个Digtizer,在需要的时候做切换工作即可。

Independent acquisition path:一个Time control unit一次只能控制一条Acquisition path采集,如果想实现两个相机同时采集就必须用两个Time control unit,两条拥有不同的Time control unit的Acquisition path称为Independent acquisition path。如图彩色相机和单色相机的Acquisition path之间就是Independent acquisition path,可以同时采集。连接到两个Independent acquisition path上的Digtizer可以同时工作采图。

Data input channel (channel):经常我们会听到双通道采集,就是同一个Time control unit的Capture源分为多个,每一个称为一个通道,在采集的时候可以切换采集,但是不能同时采集。

Device Number:MIL中分配Digtizer时要求指明每个Channel的第一条Acquisition path的device number,MIL会根据相应的DCF文件自动计算总的Acquisition path数目,如指明channel 0的彩色相机的R Acquisition path为M_DEV0,那么G Acquisition path和B Acquisition path相应就为M_DEV1和M_DEV2。这时候Channel 0的单色相机就只能分配从M_DEV3开始的Device Number了。如果板卡只有一个Time control unit,那么必须指明为M_DEV0或M_DEFAULT。

2.MIL采集和实时显示

MIL中采集有关函数都是以Mdig开头,开启采集操作的函数为MdigGrab,MdigGrabContinuous,MdigProcess,这三个函数的功能从字面上就很容易理解,分别对应单帧采集,连续采集和采集的同时处理。
在具体将这三个函数前,还要说明一点的就是要清楚
三个Buffer之间的转移和
大小及类型对应关系。这三个Buffer按数据传递顺序为相机Buffer、PC的Buffer(内存)、显存Buffer,示意图如下:


当我们采集的时候使用Mdig函数将数据从相机buffer传到PC的内存buffer(这里采用MIL定义的Buffer)中,当我们显示的时候使用Mdisp函数将数据从内存buffer从传递到显存Buffer(这里指代显示的窗口)中。
这里注意示意图上的每一个小方格代表一个像素点数据,可以看到不是每个Buffer一开始就是大小不一定是一样的:如果相机Buffer大于PC Buffer采集的时候会
自动将多的像素点截掉;如果相机Buffer小于PC Buffer采集的时候会
自动从Buffer的初始点(默认为左上角)开始填充,PC Buffer中剩下未填充的会保持初始化状态(所以一般分配完Buffer后立即初始化);PC Buffer和显存Buffer的转移关系类似。如果相邻Buffer之间大小不一样,我们可以使用MdigControl控制采集时的比例(例如采集比例设为1/2,那么采集时从相机Buffer间隔取值,相当于相机Buffer为原来的1/2),使用MdispZoom控制显示的比例(例如显示比例设为2,那么显示时从PC Buffer线性插补为原来2倍)。另外,如果想控制默认对齐的初始点,可采用MdigControl和MdispPan调整期对应参数,一般不改变,具体参看MIL手册。为了获得采集的最佳效果,我们
不应当对相机Buffer做任何假设,应该采用MdigInquire函数来查询相机Buffer的大小和类型(例如8+M_UNSIGNED),分配对应的PC Buffer,同样
不应当对显存Buffer做任何假设,应当查询显示Buffer大小,显示时对PC Buffer做相应放大缩小操作。

MdigGrab

示例代码如下
	//分配默认的应用、系统
MappAllocDefault(M_SETUP, &MilApplication, &MilSystem, M_NULL, M_NULL, M_NULL); //分配采集器
MdigAlloc(MilSystem, M_DEFAULT, "M_DEFAULT", M_DEFAULT, &MilDigitizer); int nGrabScaleSet = 4;//设置的采集比例 //分配buffer
if (MsysInquire(MilSystem, M_SYSTEM_TYPE, M_NULL) == M_SYSTEM_HELIOS_TYPE)
{
BufferLocation = M_ON_BOARD;
}
MbufAlloc2d(MilSystem,
long(MdigInquire(MilDigitizer, M_SIZE_X, M_NULL) / nGrabScaleSet),
long(MdigInquire(MilDigitizer, M_SIZE_Y, M_NULL) / nGrabScaleSet),
MdigInquire(MilDigitizer, M_TYPE, M_NULL),
M_DISP + M_IMAGE + M_GRAB + BufferLocation,
&MilBufferImage);
MbufClear(MilBufferImage, 0xFF); //分配显示
MdispAlloc(MilSystem, M_DEFAULT, "M_DEFAULT", M_WINDOWED, &MilDisplay); //Buffer和Display绑定
MdispSelectWindow(MilDisplay, MilBufferImage, GetDlgItem(IDS_DISPLAY)->GetSafeHwnd()); //单帧采集两帧
MdigControl(MilDigitizer, M_GRAB_SCALE_X, 1.0/nGrabScaleSet);
MdigControl(MilDigitizer, M_GRAB_SCALE_Y, 1.0/nGrabScaleSet);
MdigControl(MilDigitizer, M_GRAB_MODE, M_ASYNCHRONOUS );
MdigGrab(MilDigitizer, MilBufferImage);
Sleep(1000);//停顿一秒
MdispZoom(MilDisplay, 2, 2);
MdigGrab(MilDigitizer, MilBufferImage);
Sleep(1000);//停顿一秒 //释放资源
if (M_NULL != MilBufferImage)
{
MbufFree(MilBufferImage);
}
if (M_NULL != MilDisplay)
{
MdispFree(MilDisplay);
}
if (M_NULL != MilDigitizer)
{
MdigFree(MilDigitizer);
}
if (M_NULL != MilApplication)
{
MappFreeDefault(MilApplication, MilSystem, M_NULL, M_NULL, M_NULL);
}

MdigGrab很简单,就是每次从相机Buffer中抓取一帧到PC内存Buffer中,注意我们这里采用MdigInquire函数查询相机Buffer大小和分配,MdigControl控制采集比例,这里分配的PC Buffer和相机Buffer大小和类型是匹配的,但是这里的PC Buffer和显存Buffer(窗口大小)是不一样的,读者自己处理吧(GetWindowRect和MdispZoom)。
还需要注意的是采集的时候有两种基本模式,异步和同步,一般采集异步(M_ASYNCHRONOUS),具体含义请查看MIL手册。

MdigGrabContinuous

开始连续采集
	//分配默认的应用、系统
MappAllocDefault(M_SETUP, &MilApplication, &MilSystem, M_NULL, M_NULL, M_NULL); //分配采集器
MdigAlloc(MilSystem, M_DEFAULT, "M_DEFAULT", M_DEFAULT, &MilDigitizer); int nGrabScaleSet = 4;//设置的采集比例 //分配buffer
if (MsysInquire(MilSystem, M_SYSTEM_TYPE, M_NULL) == M_SYSTEM_HELIOS_TYPE)
{
BufferLocation = M_ON_BOARD;
}
MbufAlloc2d(MilSystem,
long(MdigInquire(MilDigitizer, M_SIZE_X, M_NULL) / nGrabScaleSet),
long(MdigInquire(MilDigitizer, M_SIZE_Y, M_NULL) / nGrabScaleSet),
MdigInquire(MilDigitizer, M_TYPE, M_NULL),
M_DISP + M_IMAGE + M_GRAB + BufferLocation,
&MilBufferImage);
MbufClear(MilBufferImage, 0xFF); //分配显示
MdispAlloc(MilSystem, M_DEFAULT, "M_DEFAULT", M_WINDOWED, &MilDisplay); //Buffer和Display绑定
MdispSelectWindow(MilDisplay, MilBufferImage, GetDlgItem(IDS_DISPLAY)->GetSafeHwnd()); //开始连续采集
MdigGrabContinuous(MilDigitizer, MilBufferImage);
关闭连续采集
	MdigHalt(MilDigitizer);

	//释放资源
if (M_NULL != MilBufferImage)
{
MbufFree(MilBufferImage);
}
if (M_NULL != MilDisplay)
{
MdispFree(MilDisplay);
}
if (M_NULL != MilDigitizer)
{
MdigFree(MilDigitizer);
}
if (M_NULL != MilApplication)
{
MappFreeDefault(MilApplication, MilSystem, M_NULL, M_NULL, M_NULL);
}

注意MdigGrabContinuous为异步采集模式,在采集过程中是直接送到显存中的,不保存在MilBufferImage中,只有停止采集后的最后一帧保存在MilBufferImage中,一般用于实现在线观测功能,要想实现实时抓取图像和处理只能采用MdigGrab和MdigProcess函数。

MdigProcess

开始采集
	// TODO: Add your control notification handler code here
//分配默认的应用、系统
MappAllocDefault(M_SETUP, &MilApplication, &MilSystem, M_NULL, M_NULL, M_NULL); //分配采集器
MdigAlloc(MilSystem, M_DEFAULT, "M_DEFAULT", M_DEFAULT, &MilDigitizer); //分配显示buffer
MbufAlloc2d(MilSystem,
long(MdigInquire(MilDigitizer, M_SIZE_X, M_NULL)),
long(MdigInquire(MilDigitizer, M_SIZE_Y, M_NULL)),
MdigInquire(MilDigitizer, M_TYPE, M_NULL),
M_DISP + M_IMAGE,
&MilBufferImage);
MbufClear(MilBufferImage, 0xFF); //分配显示
MdispAlloc(MilSystem, M_DEFAULT, "M_DEFAULT", M_WINDOWED, &MilDisplay); //Buffer和Display绑定
MdispSelectWindow(MilDisplay, MilBufferImage, GetDlgItem(IDS_DISPLAY)->GetSafeHwnd()); /************************************************************************/
/* 分配Buffer List */
/************************************************************************/
MappControl(M_ERROR, M_PRINT_DISABLE); //初始化buffer list
for(m = 0; m < BUFFERING_SIZE_MAX; m++)
{
MilGrabBufferList[m] = M_NULL;
} //分配尽可能多的buffer list
if (MsysInquire(MilSystem, M_SYSTEM_TYPE, M_NULL) == M_SYSTEM_HELIOS_TYPE)
{
BufferLocation = M_ON_BOARD;
}
MilGrabBufferListSize=0;
for(m = 0; m < BUFFERING_SIZE_MAX; m++)
{
//分配一个Buffer
MbufAlloc2d(MilSystem,
MdigInquire(MilDigitizer, M_SIZE_X, M_NULL),
MdigInquire(MilDigitizer, M_SIZE_Y, M_NULL),
MdigInquire(MilDigitizer, M_TYPE, M_NULL),
M_IMAGE+M_GRAB+M_PROC+BufferLocation,
&MilGrabBufferList[m]); if (MilGrabBufferList[m])//分配成功则初始化
{
MbufClear(MilGrabBufferList[m], 0xFF); LastAllocatedM = m;
MilGrabBufferListSize++;
}
else//分配失败则停止分配
{
break;
}
} MappControl(M_ERROR, M_PRINT_ENABLE); //防止占完内存空间,释放最后一个buffer
MbufFree(MilGrabBufferList[LastAllocatedM]);
MilGrabBufferList[LastAllocatedM] = M_NULL;
MilGrabBufferListSize--;//注意这里释放后一定要将相应的size-1,否则调用MdigProcess检测
//到实际可用buffer size和传入的size参数不符,会报错 /************************************************************************/
/*采集和处理*/
/************************************************************************/
//设置待传递的数据
UserHookData.MilImageDisp = MilBufferImage;
UserHookData.ProcessedImageCount = 0; MdigProcess(MilDigitizer, MilGrabBufferList, MilGrabBufferListSize,
M_START, M_DEFAULT, ProcessingFunction, &UserHookData);
停止采集
	/************************************************************************/
/*停止采集和处理*/
/************************************************************************/
MdigProcess(MilDigitizer, MilGrabBufferList, MilGrabBufferListSize,
M_STOP, M_DEFAULT, ProcessingFunction, &UserHookData); //释放资源
MappControl(M_ERROR, M_PRINT_DISABLE);
for (m = 0; m < BUFFERING_SIZE_MAX; m++)
{
if(M_NULL != MilGrabBufferList[m])
{
MbufFree(MilGrabBufferList[m]);
MilGrabBufferList[m] = M_NULL;
}
}
if (M_NULL != MilBufferImage)
{
MbufFree(MilBufferImage);
}
if (M_NULL != MilDisplay)
{
MdispFree(MilDisplay);
}
if (M_NULL != MilDigitizer)
{
MdigFree(MilDigitizer);
}
MappControl(M_ERROR, M_PRINT_ENABLE); if (M_NULL != MilApplication)
{
MappFreeDefault(MilApplication, MilSystem, M_NULL, M_NULL, M_NULL);
}
采集回调函数
long MFTYPE ProcessingFunction(long HookType, MIL_ID HookId, void MPTYPE *HookDataPtr)
{
HookDataStruct *UserHookDataPtr = (HookDataStruct *)HookDataPtr;
MIL_ID ModifiedBufferId;
char Text[10]= {'\0'}; //得到buffer list获得采集数据的buffer号
MdigGetHookInfo(HookId, M_MODIFIED_BUFFER+M_BUFFER_ID, &ModifiedBufferId); UserHookDataPtr->ProcessedImageCount++; //当前图片上写入采集编号
MOs_ltoa(UserHookDataPtr->ProcessedImageCount, Text, 10);
MgraText(M_DEFAULT, ModifiedBufferId, 10, 10, Text); //处理完的Buffer数据复制到显示buffer
MbufCopy(ModifiedBufferId, UserHookDataPtr->MilImageDisp); return 0;
}

原则上我们推荐使用MdigProcess函数,首先可以使用多Buffer优化程序性能,其次给了我们对采集过程的强大控制能力,我们可以在采集回调函数中实时处理采集到的图像或保存采集的每一帧(这也是MIL录像功能实现的原理)。在示例中,我演示的是使用回调函数对采集到的每一帧添加一个序号。

程序注释我已经写的很清楚了,结合MIL手册相信写出完整的采集程序不是什么大问题了。

博客中完整代码文件
下载链接

原创,转载请注明来自
http://blog.csdn.net/wenzhou1219

6.MIL采集和实时显示的更多相关文章

  1. python websocket网页实时显示远程服务器日志信息

    功能:用websocket技术,在运维工具的浏览器上实时显示远程服务器上的日志信息 一般我们在运维工具部署环境的时候,需要实时展现部署过程中的信息,或者在浏览器中实时显示程序日志给开发人员看.你还在用 ...

  2. HSmartWindowControl 之 摄像头实时显示( 使用 WPF )

    1.添加Halcon控件,创建WPF项目 在VS2013中创建一个WPF工程,然后添加halcon的控件和工具包,参见: HSmartWindowControl之安装篇 (Visual Studio ...

  3. 运维开发:python websocket网页实时显示远程服务器日志信息

    功能:用websocket技术,在运维工具的浏览器上实时显示远程服务器上的日志信息 一般我们在运维工具部署环境的时候,需要实时展现部署过程中的信息,或者在浏览器中实时显示程序日志给开发人员看.你还在用 ...

  4. 基于STM32的脉搏心率检测仪(OLED可以实时显示脉冲波形)

    —设计完整,功能可全部实现,有完整报告文档说明.程序以及pcb文件— 可作为:课程设计,STM32实践学习,电子制作等 设计所实现的功能: 利用STM32的AD采集功能实时采集心率传感器信号输出引脚输 ...

  5. 实时显示内容(Thread+Handler)

    class LocThread extends Thread{ @Override public void run() { while (true){ try { Thread.sleep(99); ...

  6. 基于MATLAB的GUI(Graphical User Interface)音频实时显示设计

    摘要:本文章的设计主要讲基于matlab的gui音频实时显示设计,此次设计的gui相当于一个简洁的音乐播放器,界面只有”录音“和”播放“两个控件,哈哈,够简洁吧.通过”录音“按钮可以实现声音从电脑的声 ...

  7. 使用Uploadify实现上传图片生成缩略图例子,实时显示进度条

    不了解Uploadify的,先看看前一篇详细说明 http://www.cnblogs.com/XuebinDing/archive/2012/04/26/2470995.html Uploadify ...

  8. jsp实时显示后台批处理进度 - out分块,简单的长连接方式

    这两天在实现一个批处理操作,但是想让前台实时显示后台批处理进度,本想着用复杂一些的框架可以实现异步信息调用 但是鉴于是内部管理系统,且只有一两个人用到这个功能,所以做了一个简单的长连接方式的实时响应 ...

  9. python实现websocket服务器,可以在web实时显示远程服务器日志

    一.开始的话 使用python简单的实现websocket服务器,可以在浏览器上实时显示远程服务器的日志信息. 之前做了一个web版的发布系统,但没实现在线看日志,每次发布版本后,都需要登录到服务器上 ...

随机推荐

  1. window下如何搭建linux环境

    1.使用虚拟机 使用VMware虚拟机,下载linux内核系统,加载运行. 2.cygwin 安装cygwin,设置环境变量. 第二种方法还是比较简便的.优先考虑.

  2. ThinkPHP - 关联模型 - 一对多

    使用之前,先引入文件夹,否则相应的功能不能实现. 如果对thinkPHP不精通,使用或开发的时候,最好直接使用完成版本的ThinkPHP. 关系模型定义: <?php /** * 继承自 Rel ...

  3. git阶段学习总结

    学习git大约有两个星期了,脑子里总算有点干货了,可以拿出来总结一下: git,用于版本控制的,刚开始觉得它是linux下默认的命令,其实也是个工具需要apt-get install git 安装一下 ...

  4. 17.1.1.4 Obtaining the Replication Master Binary Log Coordinates 获取复制Master Binary Log的坐标:

    17.1.1.4 Obtaining the Replication Master Binary Log Coordinates 获取复制Master Binary Log的坐标: 你需要master ...

  5. 17.1.1 How to Set Up Replication

    17.1.1 How to Set Up Replication 17.1.1.1 Setting the Replication Master Configuration 17.1.1.2 Sett ...

  6. 设计模式(十二)职责链模式(Chain of Responsibility)(对象行为型)

     设计模式(十二)职责链模式(Chain of Responsibility)(对象行为型) 1.概述 你去政府部门求人办事过吗?有时候你会遇到过官员踢球推责,你的问题在我这里能解决就解决,不能解决就 ...

  7. socket通信技术介绍

    [-] 网络中进程之间怎样通信 什么是Socket socket一词的起源 socket的基本操作 socket函数 bind函数 网络字节序与主机字节序 listenconnect函数 accept ...

  8. Ext.net中常用的三种交互方式

    http://www.ext.net.cn/forum.php?mod=viewthread&tid=10433

  9. 一天一个类,一点也不累之TreeSet

    一天一个类,一点也不累. 现在要说的是---TreeSet public class TreeSet<E> extends AbstractSet<E> implements ...

  10. java--类继承和实现的接口中含有相同的方法

    首先,说一下,当某一个类实现了两个接口的时候,两个接口中存在两个相同的方法,在实现的类中只需实现一个方法的方法体. 当一个类继承一个类,并且实现一个或者多个接口的时候,其中,父类和父接口中存在相同的方 ...