# MER030_MultiCam_velocimeter **Repository Path**: jiaoboy96/mer030_-multi-cam_velocimeter ## Basic Information - **Project Name**: MER030_MultiCam_velocimeter - **Description**: 该项目是自己的第一个 MFC 应用程序项目,软件包括相机模块、测速仪模块两部分。MER030_MultiCam_velocimeter 软件主要实现对四路相机的数据的采集、显示和存储以及四路测速仪的数据显示和存储。后期对测速仪数据进行了曲线的绘制。 - **Primary Language**: C++ - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2020-09-16 - **Last Updated**: 2021-07-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 1 软件总体设计 1.1 整体架构设计 软件包括相机模块、测速仪模块两部分。根据现在所掌握的计算机技术,选择Visual Studio 2017作为应用程序开发工具。 MER030_MultiCam_velocimeter软件主要实现对四路相机的数据的采集显示以及数据的存储、四路测速仪的数据显示以及存储。 1.2 软件总体结构设计 MER030_MultiCam_velocimeter软件分为四路相机结构设计、四路测速仪结构设计两个部分。 (1)软件的主要功能如下: i. 相机的打开与关闭 ii. 相机的采集与关闭 iii. 设置相机的曝光于增益 iv. 相机的触发控制(触发模式、触发源等) v. 相机的存储设置(设置视频合成的播放帧率、开始录制与结束录制) vi. 同步器设定 vii. 测速仪的参数设定 viii. 测速仪的内触发模式的测距和测速以及外触发模式的测距测速 ix. 测速仪的数据保存 2 软件详细设计及编码实现 MER030_MultiCam_velocimeter软件有相机模块和测速仪模块两大部分。 2.1 相机模块的设计与实现 (1)相机的打开: 1、获取连接设备数量; 2、依次将所连接的设备打开,并设置相机的默认属性(采集模式、设备的宽高属性)以及为显示图像分配Buffer。 (2)相机的关闭: 1、for循环依次对每个相机进行关闭 2、for循环内检测相机是否正在录制,未停止录制先停止录制 3、如果未停止采集先停止采集 4、对连接相机注销回调,停止图像实时显示 5、释放为采集图像开辟的图像Buffer 6、将图像采集的标志位、相机句柄、设备打开标志位全部置为FALSE或者NULL 7、刷新界面 (3)相机开始采集 1、for循环依次为每个相机进行采集 2、为每个打开的设备注册回调函数 3、为每个设备发送采集命令 4、采集图像的标志位置为TRUE 5、刷新界面 (4)相机关闭采集 1、for循环依次为每个相机进行停止采集 2、对正在采集图像的设备发送停止采集命令 3、注销回调函数 4、采集图像的标志位置为FALSE 5、更新界面 (5)设置视频编码器 1、ICCompressorChoose函数实现视频编码器的选择,通过该函数将编码器数据保存到创建变量中,方便后期保存图像数据做准备。 (6)相机开始录制 1、为保存AVI视频文件准备资源 图像格式、保存路径以及文件名(时间命名) AVI文件、视频流、 通过选择的视频编码器创建压缩视频流的结构体 创建压缩视频流 2、创建成功后将保存视频的标志位置为TRUE 3、将线程标志位置为1,表示可以进入线程 4、分别为每个相机的存储创建相应的线程用来保存数据 5、刷新界面 (7)相机停止录制 1、将线程标志位和保存图像的标志位置为FALSE; 2、释放为保存图像准备的资源 3、退出 AVIFile 函数库 4、刷新界面 2.2测速仪模块设计与实现 (1) 测速仪的参数设定 向测速仪发送0x1B命令 设置MF并将其发送给测速仪 向测速仪发送读命令,并将读到的数据写到IDC_EDIT控件上 重新向测速仪发送0x1B命令 设置SA并将其发送给测速仪 向测速仪发送读命令,并将读到的数据写到IDC_EDIT控件上 (2) 测速仪的参数显示 向测速仪发送‘PA’命令 向测速仪发送读命令,并将读到的数据显示到IDC_EDIT控件上 (3) 测速仪的数据保存 创建文件对话框,选择文件路径 根据得到的路径名称创建TXT文件,将IDC_EDIT控件的数据写入文件 关闭文件 (4) 测速仪的测距 根据测速仪的线程标志位进入不同的逻辑部分(创建进程和结束进程) 向测速仪发送‘DT’命令,并且创建连续测距的线程 将线程标志位置为1,线程函数不断向测速仪发送读命令,并且将读到的数据显示在ListBox控件上。 向线程发送‘0x1B’命令,并且将线程标志位置为0 (5) 测速仪的测速 根据测速仪的线程标志位进入不同的逻辑部分(创建进程和结束进程) 向测速仪发送‘VT’命令,并且创建连续测速的线程 将线程标志位置为1,线程函数不断向测速仪发送读命令,并且将读到的数据显示在ListBox控件上。 向线程发送‘0x1B’命令,并且将线程标志位置为0 (6) 测速仪的外触发模式测距 根据测速仪的线程标志位进入不同的逻辑部分(创建进程和结束进程) 向测速仪发送‘DF’命令,并且创建外触发测距的线程 将线程标志位置为1,线程函数不断向测速仪发送读命令,并且将读到的数据显示在ListBox控件上。 外触发模式下,根据当前系统时间创建txt文件并且保存数据 向线程发送‘0x1B’命令,并且将线程标志位置为0 (7) 测速仪的外触发模式测速 根据测速仪的线程标志位进入不同的逻辑部分(创建进程和结束进程) 向测速仪发送‘DF’命令,并且创建外触发测速的线程 将线程标志位置为1,线程函数不断向测速仪发送读命令,并且将连续读到的50次数据保存在变量PA中。 根据PA中的数据计算出速度。并且将计算的结果显示在Listbox控件中 外触发模式下,根据当前系统时间创建txt文件并且保存数据 向线程发送‘0x1B’命令,并且将线程标志位置为0 3重要函数 3.1 相机和测速仪相关的库函数 GXGetLastError(); //获取错误信息长度,并申请内存空间 GXSetEnum(); //设置相机的属性 GXGetInt(); //获取相机整形相关属性 --- 图像的大小、宽度、高度等 GXGetEnum(); //获取相机枚举相关属性 --- 像素格式、像素位深等 GXGetEnumEntryNums(); // 获取功能的枚举数 GXGetEnumDescription(); // 获取功能的枚举描述 GXIsImplemented(); //判断相机是否支持某种属性 GXGetEnumEntryNums(); //获取功能的枚举数 GXGetFloatRange(); //获取相机属性的浮点数范围 -- 增益范 围、曝光范围 GXGetFloat(); //获取相机属性的当前值 --- 增益曝光等 GXInitLib(); //初始化设备库 GXOpenDevice(); //打开设备 GXCloseDevice(); //关闭设备 GXRegisterCaptureCallback(); //为指定设备注册相应的回调函数 GXUnregisterCaptureCallback(); //将指定的设备注销回调 GXSendCommand(); //向相机发送指定命令 --- 采集命令、停止采 集命令 通过以上函数实现对相机属性的更新,相机的属性通过宏定义的方式全部 封装在GxIAPI文件内,通过使用不同的函数,指定不同的宏来实现对相机 的设置。 4、软件用到的核心技术: 4.1 回调函数 回调函数就是一个通过函数指针(指向函数地址的指针)调用的函数。如果你把指针(函数的地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。 回调方法是任何一个被以该回调方法为其第一个参数的其它方法调用的方法。很多时候,回调是一个当某些事件发生时被调用的方法。 C++中对于回调函数的使用大致分为几个步骤: 1、定义回调函数的原型: eg: typedef void (*CallbackFun)(double height, void* contex); 2、定义回调函数: eg: void onHeight(double height, void* contex) { sprint("current height is %lf",height); } 3、定义注册回调函数: eg: void registHeightCallback(CallbackFun callback, void* contex) { double h=100; callback(h,nullptr); } 4、在主函数中注册回调函数。即调用registHeightCallback() 函数 注意:项目中对于相机的回调函数已经做了自定义的封装,需要的参数有设备句柄,父窗口指针,回调函数。函数的原型如下: //---------------------------------------------------------------------------------- /** \brief 注册采集回调函数 \attention 必须在发送开采命令之前注册采集回调函数 \param [in]hDevice 设备句柄 \param [in]pUserParam 用户私有数据 \param [in]callBackFun 用户注册的回调函数 \return GX_STATUS_SUCCESS 操作成功,没有发生错误 GX_STATUS_NOT_INIT_API 没有调用GXInitLib初始化库 GX_STATUS_INVALID_HANDLE 用户传入非法的句柄 GX_STATUS_INVALID_PARAMETER 用户传入指针为NULL GX_STATUS_INVALID_CALL 发送开采命令后,不能注册采集回调函数 其它错误情况请参见GX_STATUS_LIST */ //---------------------------------------------------------------------------------- GX_API GXRegisterCaptureCallback(GX_DEV_HANDLE hDevice, void *pUserParam, GXCaptureCallBack callBackFun); 4.2 多线程并发 MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。 工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。用户界面线程一般用于处理独立于其他线程之外的用户输入,响应用户及系统产生的事件和消息等。但对于Win32的API编程而言,这两种编程是没有区别的,他们都只需要线程的启动地址即可启动线程来执行任务。 对于该软件来说,对于图像和测速仪数据的保存有比较冗长的计算过程,因此采用的是工作者线程。 在MFC中,一般用全局函数AfxBeginThread()来创建并初始化一个线程的运行,该函数有两种重载形式,分别用于创建工作者线程和用户界面线程。这两种函数的重载和原型分别说明如下: (1)工作者线程 CWndThread *AfxBeginThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam, UINT nPriority=THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL); (2)IU线程(用户界面线程) CWndThread *AfxBeginThread(CRuntimeClass *pThreadClass, int nPriority=THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL); (3)软件中对于多线程的使用: 1、定义新线程的线程对象指针 2、开始保存图像数据,执行创建线程函数 --- AfxBeginThread(); 3、定义线程的入口函数,新线程执行图像数据的保存 4、停止保存图像数据时通过将定义的全局变量赋值为0正常结束进程。