前面几篇文章,对EASY EAI Nano的各部分模块功能进行了测试:
【EASY EAI Nano开源套件试用体验】1开箱测评与开机运行测试
【EASY EAI Nano开源套件试用体验】2软件开发环境搭建
【EASY EAI Nano开源套件试用体验】3摄像头与屏幕的使用
【EASY EAI Nano开源套件试用体验】4AI功能测试之人脸检测
【EASY EAI Nano开源套件试用体验】5AI功能测试之多人脸识别
【EASY EAI Nano开源套件试用体验】6Qt测试之秒表
本篇进行人脸门禁打卡机这个综合例程的测试,用到了Qt开发,AI人脸检测与识别,SQLite数据库操作等。在官方提供的例程的基础上,增加数据库内容显示界面,用来显示已录入的人脸情况,以及每个人的打卡记录信息,另外还可以通过界面的按钮来删除数据库,以重新录入数据。
人脸门禁例程参考官方文档: https://www.easy-eai.com/document_details/3/177
该综合例程新用到的模块有:
组件子目录 | 描述 |
---|---|
QSrcCode/business/ | 应用框架的核心代码,用于实现、创建、各个业务功能模块,以及协调各模块间的相互调度。 |
QSrcCode/ui/ | 构建界面相关的代码,用于描述页面的布局与显示。 |
QSrcCode/common/ | 用户的自定义代码,也可以是存放和管理第三方代码。 |
QSrcCode/apiWrapper/ | 若easyeai-api的代码不能完全满足产品要求,用户可以在此目录对easyeai-api进行抽象再封装。 |
本项目用到的Qt界面,模块间的交互可分为两种,一种是非界面模块间的交互,另一种是界面模块间的交互,这两种交互方式是不一样的,来看下官方教程中对这两种交互方式的介绍。
下图中,上半部分的4个界面(主界面、待机界面、输入界面、通知界面)属于界面程序,下半部分的UI管理器、应用调度器、主线程、数据库管理器、公共播放器、消息适配器等属于非界面程序。
非界面模块间的交互,其特点是通过“应用调度器”进行相互调度。
界面模块间的交互,应用了信号槽和事件机制,该种机制也是Qt为了去耦合而进行的针对性设计。而且Qt规定,非gui线程不能操作gui线程,因此从UIManager(非界面)到mainWidget(界面)的调用操作,也必须通过信号槽来完成。
再来看一下应用调度器,主要功能是管理各个子模块
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<int, BusinessCB> m_BusinessModel;
};
AppScheduler::AppScheduler()
{
printf("----------------------start app-----------------------\n");
InitBusinessModel();
/// 1 - 创建数据库
DataBaseMgr::createDataBaseMgr();
//1.1 - 注册数据库
DataBaseMgr::instance()->SetScheduler(this);
Register(DATABASE_MANAGER, DataBaseMgrCallback);
/// 2 - 创建主逻辑业务块 - 用于执行周期性逻辑代码
MainThread::createMainThread();
//2.1 - 注册主逻辑业务块
MainThread::instance()->SetScheduler(this);
Register(MAIN_THREAD, MainThreadCallback);
/// 3 - 创建UI
UIManager::createUIManager();
//3.1 - 注册UI
UIManager::instance()->SetScheduler(this);
Register(UI_MANAGER, UIManagerCallback);
/// 4 - 创建语音播报器
Announcement::createAnnouncement();
//4.1 - 注册语音播报器
Announcement::instance()->SetScheduler(this);
Register(ANNOUNCEMENT, AnnouncementCallback);
/// 5 - 创建消息适配器 - 用于分发外部设备的协议处理
MsgAdapter::createMsgAdapter();
//5.1 - 注册消息适配器
MsgAdapter::instance()->init();
MsgAdapter::instance()->SetScheduler(this);
Register(MSG_ADAPTER, MsgAdapterCallback);
}
应用调度器实质上是一个回调函数映射表,在创建基础业务功能块时,需要把业务功能块的回调函数注册到应用调度器的映射中
基础业务模块是一个基类:
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)。应用调度器是各个BaseModel实例化对象之间的桥梁。
官方的人脸门禁机项目,总结一些内容:
main.cpp中实例化一个AppScheduler类型的App,创建各个业务模块
创建的业务模块包括:数据库管理、主线程、UI管理、语音播放管理、消息管理
Qt 设计的4个U界面(主界面、待机界面、输入界面、通知界面)
主界面占用整个屏幕,其它的3个界面占用主界面的上半部分或中间部分。
数据库中数据的显示,通过表格的形式展现,用到了Qt中的Table Widget组件,在Qt Creator中新建一个ui界面,添加该组件后,可再通过编写代码来实现将数据插入表格。
表格插入数据的实例程序:
ui->tableWidget->setColumnCount(4);
ui->tableWidget->setFont(QFont("宋体", 7));
ui->tableWidget->setHorizontalHeaderLabels(QStringList() << "id" << "姓名" << "打卡时间1" << "打卡时间2");
ui->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
vector<CLOCKIN_DATA_T> data;
if (DataBaseMgr::instance()->getAllClockInData(data))
{
for (auto item : data)
{
printf("%s %s\n", item.idStr.c_str(), item.nameStr.c_str());
int curRow = ui->tableWidget->rowCount();
ui->tableWidget->insertRow(curRow);
ui->tableWidget->setItem(curRow, 0, new QTableWidgetItem(item.idStr.c_str()));
ui->tableWidget->setItem(curRow, 1, new QTableWidgetItem(item.nameStr.c_str()));
//ui->tableWidget->setItem(curRow, 2, new QTableWidgetItem(item.startTimeStr.c_str()));
//ui->tableWidget->setItem(curRow, 3, new QTableWidgetItem(item.endTimeStr.c_str()));
}
}
由于打卡时间的数据库功能还在调试中,暂不显示打卡时间。
另外,增加两个按钮,来实现数据库页面的退出和数据库的删除,其槽函数如下:
//返回到待机页面
void DataBasePage::on_mpReturnBtn_clicked()
{
if(UIManager::instance()->MainWidget_instance())
{
QEvent *ceEvent = new MainWidgetEvent(MainWidgetEvent::MainWidgetEvent_SwitchToStandBy);
QApplication::postEvent(UIManager::instance()->MainWidget_instance(), ceEvent ); // Qt will delete it when done
}
}
//删除数据库所有数据
void DataBasePage::on_mpDeleteDBBtn_clicked()
{
DataBaseMgr::instance()->clearDateBase();
ui->tableWidget->clearContents();
}
这里预留了打卡时间的数据,在获取数据库数据时,将对应的打卡时间也获取出来:
//打卡数据结构体
struct CLOCKIN_DATA_T
{
std::string idStr;
std::string nameStr;
std::string startTimeStr = "2022-08-08 09:00";
std::string endTimeStr = "2022-08-08 18:00";
};
//获取数据库中的数据
bool database_get_all_clockin_time(vector<CLOCKIN_DATA_T> &data)
{
if (g_db == nullptr)
{
return false;
}
//要执行的sql语句
sqlite3_stmt *stmt = nullptr;
std::string sql="select id, name from face_table";
int ret = sqlite3_prepare_v2(g_db, sql.c_str(), sql.length(), &stmt, NULL);
if(ret != SQLITE_OK)
{
printf("prepare_v2 err: %s\n", sqlite3_errmsg(g_db));
goto err;
}
data.clear();
while(sqlite3_step(stmt) == SQLITE_ROW)
{
CLOCKIN_DATA_T clockin;
clockin.idStr = (char *) sqlite3_column_text(stmt, 0);
clockin.nameStr = (char *) sqlite3_column_text(stmt, 1);
printf("DB get idStr:%d, nameStr:%d\n", clockin.idStr.c_str(), clockin.nameStr.c_str());
data.push_back(clockin);
}
if(stmt) sqlite3_finalize(stmt);
return true;
err:
if(stmt) sqlite3_finalize(stmt);
return false;
}
实验演示了人脸注册、人脸检测于识别、数据库的展示、数据库的删除、语音播报等,演示视频见文末。
为了测试多张人脸,使用的是电脑屏幕中的人物图像,因此需要将官方例程中人脸检测部分的活体检测功能去掉,这里就可以识别图片中的人脸了。
本篇进行了AI人脸门禁打卡机综合例程的测试,在官方例程的基础上,增加了数据库的展示页面,可从待机界面切换到数据库展示界面,另外增加了数据库删除按钮,可清空数据库进行重新的人脸注册。
更多回帖