前面讲到的都是离线的图像获取方法,实际中我们做机器视觉都是在线采集图像和处理,处理结果决定了计算机要给出的控制信号如电机运动等,这样就实现了实时视觉反馈运动。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. Zoj 3842 Beauty of Array

    Problem地址:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5520 根据题目的要求,需要算出所有连续子数组的the be ...

  2. Swift入门Hello World! Swift.

    苹果公司推出新的开发语言Swift,随着关于趋势,外观和OC什么是不一样的地方. 前提条件:已安装Xcode6-Beta(这个过程是不表) 1.打开Xcode6-Beta,第二选择Create a n ...

  3. 关于方法中的形参out

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  4. Python 执行字符串表达式函数(eval exec execfile)

    eval:计算字符串中的表达式 exec:执行字符串中的语句 execfile:用来执行一个文件 在python 2中exec是语句,在python3中exec变为函数,后面要跟括号.在python3 ...

  5. POJ 1159 回文LCS滚动数组优化

    详细解题报告可以看这个PPT 这题如果是直接开int 5000 * 5000  的空间肯定会MLE,优化方法是采用滚动数组. 原LCS转移方程 : dp[i][j] = dp[i - 1][j] + ...

  6. SSH整合,"sessionFactory " or "hibernateTemplate " is required异常

    首先遇到的问题就是HibernateDaoSupport引起的,程序中所有的DAO都继承自HibernateDaoSupport,而HibernateDaoSupport需要注入sessionfact ...

  7. oschina 开发工具

    开发工具 29反编译工具 26持续集成系统 19SQL注入工具 139Git开源工具 138Java开发工具 43.NET开发工具 85PHP开发工具 96C/C++开发工具 70Ruby/Rails ...

  8. Android的BUG(四) - Android app的卡死问题

    做android,免不了要去运行一些跑分程序,常用的跑分程序有quadrant(象限),nbench,安兔兔等.作为系统工程师,对这些跑分 程序都非常的不屑,这个只能是一个不客观的参考,但客户都喜欢拿 ...

  9. POJ 3422 Kaka&#39;s Matrix Travels (最小费用最大流)

    POJ 3422 Kaka's Matrix Travels 链接:http://poj.org/problem? id=3422 题意:有一个N*N的方格,每一个方格里面有一个数字.如今卡卡要从左上 ...

  10. 在C#中使用C++编写的类1

    转载地址:http://blog.csdn.net/starlee/article/details/2864588 现在在Windows下的应用程序开发,VS.Net占据了绝大多数的份额.因此很多以前 ...