本方案为类人脸门禁机的产品级解决方案,已为用户构建一个带调度框架的UI应用工程;准备好我司的easyeai-api链接调用;准备好UI的开发环境。具备低模块耦合度的特点。其目的在于方便用户快速拓展自定义的业务功能模块,以及快速更换UI皮肤。
如果您初次阅读此文档,请阅读《入门指南/开发环境准备/Easy-Eai编译环境准备与更新》,并按照其相关的操作,进行编译环境的部署。
在PC端Ubuntu系统中执行run脚本,进入EASY-EAI编译环境,具体如下所示。
cd ~/develop_environment ./run.sh
在EASY-EAI编译环境下创建存放源码仓库的管理目录:
cd /opt mkdir EASY-EAI-Toolkit cd EASY-EAI-Toolkit
通过git工具,在管理目录内克隆远程仓库
git clone https://github.com/EASY-EAI/EASY-EAI-Toolkit-C-UiSolution.git
注:
* 此处可能会因网络原因造成卡顿,请耐心等待。
* 如果实在要在gitHub网页上下载,也要把整个仓库下载下来,不能单独下载本实例对应的目录。
进入到对应的例程目录执行编译操作,具体命令如下所示:
cd EASY-EAI-Toolkit-C-UiSolution/qSolu-facialGate/ ./build.sh
注:
* 由于依赖库部署在板卡上,因此交叉编译过程中必须保持adb连接。
【百度网盘】
链接:https://pan.baidu.com/s/1mrhVHxHWJ8cY9Fl9k5KtYg
提取码:0k7j
本方案用到两个模型:face_detect.model和face_recognition.model
直接把模型下载到本地Windows主机,复制
进入PC端Ubuntu创建存放model目录:
cd /opt mkdir model
然后把模型从本地Windows主机粘贴到PC端Ubuntu中:
使用下方命令再次回到开发实例目录
cd /opt/EASY-EAI-Toolkit-C-UiSolution/qSolu-facialGate/
然后,通过执行以下命令,将编译结果手动部署到板卡中。
cp Release/qSolu-* /mnt/userdata/apps/facialGate cp QResource/audio -r /mnt/userdata/apps/facialGate
最后,将准备好的模型部署到板卡中(注意:模型要放到编译结果的同一目录中),执行命令如下所示。
cp /opt/model/face_detect.model /mnt/userdata/apps/facialGate cp /opt/model/face_recognition.model /mnt/userdata/apps/facialGate
通过按键Ctrl Shift T创建一个新窗口,执行adb shell命令,进入板卡运行环境。
adb shell
进入板卡后,定位到例程部署的位置:
cd /userdata/apps/facialGate
运行例程命令如下所示:
./qSolu-facialGate
运行打印:
液晶显示屏上会显示如下画面:
点击“欢迎”按钮,可以呼出或者关闭键盘。
在对准摄像头时,点击注册,即可完成人脸录入,录入后回到待机页面。
当用户再次对准摄像头,即可识别相关的用户信息
在EASY-EAI编译环境中的任意位置,通过下方命令,后台打开qtcreator:
qtcreator &
注:若虚拟机配置较低,打开qtcreator可能要等待10几秒。
注:进行远程调试前,首先要用build.sh脚本把相关的资源拷贝到【开发板】相对应的目录上,否则会因缺少文件导致运行异常。
注:任何修改*.pro或者*.pri的操作,都要clean掉Makefile后,再重新编译。
本章节侧重于讲解代码组织架构,以便于对代码的辅助阅读和理解,因此不涉及具体的操作指导和详尽的代码内容解析。若需要进行上手操作的调试和开发,可直接参考“开发指南”章节。
本方案为产品级别的解决方案,需要调度种类繁多的各种资源,而且为了应对不同用户各自丰富多样的需求,则需要对功能模块进行解耦。故而针对该种情况设计了一套模块调度框架。
框架概览 & 源码目录分布
源码目录说明:
组件子目录 | 描述 |
QSrcCode/business/ | 应用框架的核心代码,用于实现、创建、各个业务功能模块,以及协调各模块间的相互调度。 |
QSrcCode/ui/ | 构建界面相关的代码,用于描述页面的布局与显示。 |
QSrcCode/common/ | 用户的自定义代码,也可以是存放和管理第三方代码。 |
QSrcCode/apiWrapper/ | 若easyeai-api的代码不能完全满足产品要求,用户可以在此目录对easyeai-api进行抽象再封装。 |
模板间的交互分为两种,一种是非界面模块间的交互,另一种是界面模块间的交互。
非界面模块间的交互,其特点是通过“应用调度器”进行相互调度。
界面模块间的交互,应用了信号槽和事件机制,该种机制也是Qt为了去耦合而进行的针对性设计。而且Qt规定,非gui线程不能操作gui线程,因此从UIManager(非界面)到mainWidget(界面)的调用操作,也必须通过信号槽来完成。
EASY-EAI-Toolkit-C-UiSolution/qSolu-facialGate/QSrcCode/ui/main.cpp是程序入口,应用调度器在此处被创建,是本程序首个被创建的对象,具体代码如下所示:
AppScheduler App;
应用调度器类的构成与功能:
class AppScheduler { public: AppScheduler(); ~AppScheduler(); int PosDataTo(Modeler mod, void *pData); private: int InitBusinessModel(); int Register(Modeler mod, BusinessCB pCBFunc); int UnRegister(Modeler mod); std::map m_BusinessModel; };
应用调度器实质上是一个回调函数映射表,在创建基础业务功能块时,需要把业务功能块的回调函数注册到应用调度器的映射中(具体的注册代码,会在“开发指南”章节中的“添加一个新的业务功能模块”涉及)。
所有的业务功能块,都要继承于基础业务功能块(BaseModel)。应用调度器是各个BaseModel实例化对象之间的桥梁。
BaseModel源码位于:
EASY-EAI-Toolkit-C-UiSolution/qSolu-facialGate/QSrcCode/business/basemodel.cpp
EASY-EAI-Toolkit-C-UiSolution/qSolu-facialGate/QSrcCode/business/basemodel.h
class BaseModel { public: explicit BaseModel(); ~BaseModel(); int SetScheduler(AppScheduler *pScheduler); /// 输出>>>>: 向其他模块输出数据 virtual int SendDataToDataAnnouncement(int cmdType, int dataLen, void *data); virtual int SendDataToDataBase(int cmdType, int dataLen, void *data); virtual int SendDataToMainThread(int cmdType, int dataLen, void *data); virtual int SendDataToMsgAdapter(int cmdType, int dataLen, void *data); virtual int SendDataToUI(int tagPage, int cmdType, int dataLen, void *data); private: int SendDataTo(Modeler mod, void *pData); AppScheduler *m_pScheduler; };
BaseModel在初始化时通过SetScheduler方法向子模块绑定应用调度器对象的指针。在程序交互的过程中通过调用SendDataTo方法向其他子模块回调发送数据。
UiSolution git仓库仅会放置两个解决方案。
一是最简洁的UI调用方案,用户可以基于此方案,快速进行需要带界面交互的产品开发。
二是带调度框架的UI应用方案,该方案为类人脸门禁机的产品级解决方案,其特点是模块之间的耦合度低,用户可以快速拓展自定义的业务功能模块,以及快速更换UI皮肤。
UiSolution工程构成如下所示,由功能组件easyeai-api和各个解决方案构成。
功能组件的描述如下所示,easyeai-api是经过高度封装的易用性组件接口,便于用户直接调用板卡资源。
功能 | 组件目录 | 组件子目录 | 描述 |
功能组件 | easyeai-api | algorithm_api | 算法组件 |
common_api | 通用组件 | ||
media_api | 多媒体组件 | ||
netProtocol_api | 网络协议组件 | ||
peripheral_api | 外设硬件组件 |
解决方案的描述如下所示,单个“qSolu-”开头的目录即为一个解决方案案例,代码内调用“EASY EAI-API”来满足某一实际应用场景的需求。
功能 | 工程目录 | 描述 |
解决方案 | qSolu-QDemo | 最简单的UI交互方案 |
qSolu-facialGate | 类人脸识别门禁机解决方案 |
每个解决方案就是一个独立的项目,facialGate项目内包含部分如下所示,项目使用qmake构建自动编译部署。
具体介绍如下所示。
组成部分 | 描述 |
build.sh | 编译脚本,用于管理生成可执行文件后的部署准备工作,用户可自定义shell命令。 |
qSolu-facialGate.pro | 工程管理文件,用于组织整个工程结构,指导qmake生成Makefile。 |
resource.qrc | 工程管理文件,用于组织管理贴图资源,样式表资源等。 |
api.pri | 工程管理文件,用于组织管理“对easyeai-api拓展封装”子模块相关源码。 |
business.pri | 工程管理文件,用于组织管理“业务功能”子模块相关源码。 |
common.pri | 工程管理文件,用于组织管理“第三方”子模块相关源码。 |
ui.pri | 工程管理文件,用于组织管理“UI界面效果”相关源码。 |
QResource | 用于存放贴图资源,样式表资源等。 |
QSrcCode | 用于存放工程源代码。 |
第一部分为输出配置,如下所示:
配置信息如下所示。
配置项 | 描述 |
TARGET | 输出文件名称 |
TEMPLATE | 输出文件类型,app为可执行文件,lib为库文件 |
第二部分为全局编译选项配置,如下所示:
配置信息如下所示。
配置项 | 描述 |
LIBS | 全局链接库,通常是本Ubuntu系统提供的库 |
QMAKE_CXXFLAGS | 全局C 编译参数,可传入一些宏或者C 编译配置 |
第三部分为加载自定义子模块,如下所示:
第四部分为加载资源管理,如下所示:
第五部分为指定文件输出目录,如下所示:
本工程文件是对我司的功能组件库的管理,若用户有“对我司的功能组件库进行拓展封装”的需求,则可通过本文件来管理。(针对当前方案进行:配置EASY EAI API头文件目录、库文件目录以及配置库链接参数):
配置信息如下所示。
配置项 | 描述 |
INCLUDEPATH | 向工程指定头文件的查找路径 |
LIBS | 指定对应的easyeai-api库文件以及其依赖的编译参数 |
SOURCES | 向工程添加需要编译的源文件 |
HEADERS | 向工程添加需要编译的头文件 |
本工程文件是具体的应用业务功能管理,用户封装的业务功能模块可放置此处进行管理:
配置信息如下所示。
配置项 | 描述 |
INCLUDEPATH | 向工程指定头文件的查找路径 |
SOURCES | 向工程添加需要编译的源文件 |
HEADERS | 向工程添加需要编译的头文件 |
本工程文件是第三方的库的配置(针对当前方案进行:配置第三方头文件目录、库文件目录、配置第三方库链接参数以及配置源码目录):
配置信息如下所示。
配置项 | 描述 |
INCLUDEPATH | 向工程指定头文件的查找路径 |
SOURCES | 向工程添加需要编译的源文件 |
HEADERS | 向工程添加需要编译的头文件 |
本工程文件是交互界面相关的源码文件配置,内容如下所示:
配置项如下所示。
配置项 | 描述 |
SOURCES | 向工程添加需要编译的源文件 |
HEADERS | 向工程添加需要编译的头文件 |
FORMS | 向工程添加Qt设计师产生的界面文件 |
第一部分用于提取目录用于编译操作,内容如下所示:(进入build.sh脚本所在目录,并且提取当前目录绝对路径,提取当前目录名称)
第二部分清除操作,清除目录为Release,内容如下所示:(执行build.sh脚本时,带入了参数“clear”,则清空编译输出;带入了参数“all”,则重新编译)
第三部分,编译直接调用qmake,内容如下所示:(重新编译,并生成部署目录)
以名字为“myModel”为例:
从工程目录EASY-EAI-Toolkit-C-UiSolution/qSolu-facialGate切换到business目录:
cd QSrcCode/business/
在business目录下执行:
mkdir myModel touch myModel/myModel.cpp touch myModel/myModel.h
在myModel.h中填入源代码,具体操作为:
输入命令:
vim myModel/myModel.h
在vim环境下,按“i”后,开始输入代码:
#ifndef __MYMODEL_H__ #define __MYMODEL_H__ #include "business/basemodel.h" /* *说明:由于每个模块都是一个单例对象,因此: * 1,只能通过调用MyModel::createMyModel()去创建。 * 2,除了示例展示的2处new以外,其它地方不能使用new MyModel 去创建,否则会引发程序崩溃 */ class MyModel : public BaseModel { public: explicit MyModel(); ~MyModel(); static MyModel *instance() { if(m_pSelf == NULL){ once_flag oc; call_once(oc, [&] { m_pSelf = new MyModel; }); } return m_pSelf; } static void createMyModel(); private: static MyModel *m_pSelf; }; #endif // __MYMODEL_H__
输入代码完毕后,依次按下“Esc键”、“:键”、“w键”、“q键”、“回车键”保存代码。(注意:w、q是小写)
同myModel.h的操作方法,对myModel.cpp填入代码:
#include "system.h" #include "myModel.h" MyModel *MyModel::m_pSelf = NULL; /* *说明:本回调为“阻塞回调”。因此有以下注意事项: * 1,返回值能够直接在调用模块中获取。 * 2,通过修改输入参数的指针指向的数据,也能进行数据取回。 * 3,若在此回调进行阻塞操作,调用模块也会被阻塞。 */ int MyModelCallback(void *data) { /*此回调处理其他模块送入的消息*/ return 0; } MyModel::MyModel() { /*特别注意:不建议在构造函数内部使用跨模块调度(m_pScheduler->PosDataTo(mod,data)),目标业务模块有可能未被注册*/ } MyModel::~MyModel() { if(m_pSelf){ delete m_pSelf; m_pSelf = NULL; } } void MyModel::createMyModel() { if(m_pSelf == NULL){ once_flag oc; call_once(oc, [&] { m_pSelf = new MyModel; }); } }
在business目录下执行命令:
vim business.pri
同myModel.h的操作方法,添加下图所示两行代码:
保存退出:
:wq
在business目录下执行命令:
vim bsprotocol.h
在模块映射枚举中,加入“MYMODEL”索引。
保存退出:
:wq
在business目录下执行命令:
vim appscheduler.app
在appscheduler.cpp文件原有结构基础上,添加以下代码:
新增模块的头文件:
#include "myModel/myModel.h"
新增模块的回调函数声明:
extern int MyModelCallback(void *data);
在AppScheduler中创建并注册新增模块:
MyModel::createMyModel(); MyModel::instance()->SetScheduler(this); Register(MYMODEL, MyModelCallback);
保存退出:
:wq
回到工程目录EASY-EAI-Toolkit-C-UiSolution/qSolu-facialGate中,执行命令:
./build.sh all
结果为:编译成功,且在Release目录中生成有myModel.o文件。则说明“myModel”业务功能被成功添加
编译成功截图:
myModel.o截图:
此操作为本应用调度框架的核心设计,若项目不需要某个功能。仅需在appscheduler.cpp的构造函数中注释掉其创建与注册即可。除此以外无须改动任何代码!因此本调度框架具备极强的灵活性以及低耦合度。
示例:
↓↓↓↓↓
免责声明:本文为转载,非本网原创内容,不代表本网观点。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
如有疑问请发送邮件至:bangqikeconnect@gmail.com