前面几篇文章,对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数据库操作等。在官方提供的例程的基础上,增加数据库内容显示界面,用来显示已录入的人脸情况,以及每个人的打卡记录信息,另外还可以通过界面的按钮来删除数据库,以重新录入数据。
1 官方例程分析
人脸门禁例程参考官方文档: https://www.easy-eai.com/document_details/3/177
该综合例程新用到的模块有:
组件子目录 |
描述 |
QSrcCode/business/ |
应用框架的核心代码,用于实现、创建、各个业务功能模块,以及协调各模块间的相互调度。 |
QSrcCode/ui/ |
构建界面相关的代码,用于描述页面的布局与显示。 |
QSrcCode/common/ |
用户的自定义代码,也可以是存放和管理第三方代码。 |
QSrcCode/apiWrapper/ |
若easyeai-api的代码不能完全满足产品要求,用户可以在此目录对easyeai-api进行抽象再封装。 |
1.1 项目框架
本项目用到的Qt界面,模块间的交互可分为两种,一种是非界面模块间的交互,另一种是界面模块间的交互,这两种交互方式是不一样的,来看下官方教程中对这两种交互方式的介绍。
1.1.1 非界面模块之间的交互
下图中,上半部分的4个界面(主界面、待机界面、输入界面、通知界面)属于界面程序,下半部分的UI管理器、应用调度器、主线程、数据库管理器、公共播放器、消息适配器等属于非界面程序。
非界面模块间的交互,其特点是通过“应用调度器”进行相互调度。
1.1.2 界面间的模块交互
界面模块间的交互,应用了信号槽和事件机制,该种机制也是Qt为了去耦合而进行的针对性设计。而且Qt规定,非gui线程不能操作gui线程,因此从UIManager(非界面)到mainWidget(界面)的调用操作,也必须通过信号槽来完成。
1.1.3 应用调度器
再来看一下应用调度器,主要功能是管理各个子模块
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();
DataBaseMgr::createDataBaseMgr();
DataBaseMgr::instance()->SetScheduler(this);
Register(DATABASE_MANAGER, DataBaseMgrCallback);
MainThread::createMainThread();
MainThread::instance()->SetScheduler(this);
Register(MAIN_THREAD, MainThreadCallback);
UIManager::createUIManager();
UIManager::instance()->SetScheduler(this);
Register(UI_MANAGER, UIManagerCallback);
Announcement::createAnnouncement();
Announcement::instance()->SetScheduler(this);
Register(ANNOUNCEMENT, AnnouncementCallback);
MsgAdapter::createMsgAdapter();
MsgAdapter::instance()->init();
MsgAdapter::instance()->SetScheduler(this);
Register(MSG_ADAPTER, MsgAdapterCallback);
}
应用调度器实质上是一个回调函数映射表,在创建基础业务功能块时,需要把业务功能块的回调函数注册到应用调度器的映射中
1.1.4 基础业务功能块
基础业务模块是一个基类:
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实例化对象之间的桥梁。
1.2 归纳总结
官方的人脸门禁机项目,总结一些内容:
-
main.cpp中实例化一个AppScheduler类型的App,创建各个业务模块
-
创建的业务模块包括:数据库管理、主线程、UI管理、语音播放管理、消息管理
- 数据库管理,用于记录注册的人脸等数据
- 主线程,用于开启摄像头和屏幕,并进行人脸检测和人脸识别
- UI管理,管理4个图形界面
- 语音播放管理,用于播放语音
- 消息管理,用于模块间的消息通信
-
Qt 设计的4个U界面(主界面、待机界面、输入界面、通知界面)
主界面占用整个屏幕,其它的3个界面占用主界面的上半部分或中间部分。
- 主界面,主要的内容在界面的下半部分,包括欢迎按钮、时间日期的显示等,上半部分是空白,设置为透明显示,可显示摄像头的图像
- 待机界面,实际是一个空白透明界面,占用屏幕的上半部分
- 输入界面,用于数据密码数据,占用屏幕的上半部分
- 通知界面,用于显示匹配成功的人脸信息,占用屏幕的中间部分
2 功能修改png
2.1 增加数据库显示界面
数据库中数据的显示,通过表格的形式展现,用到了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 );
}
}
void DataBasePage::on_mpDeleteDBBtn_clicked()
{
DataBaseMgr::instance()->clearDateBase();
ui->tableWidget->clearContents();
}
2.2 获取数据库数据
这里预留了打卡时间的数据,在获取数据库数据时,将对应的打卡时间也获取出来:
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;
}
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;
}
3 演示
实验演示了人脸注册、人脸检测于识别、数据库的展示、数据库的删除、语音播报等,演示视频见文末。
为了测试多张人脸,使用的是电脑屏幕中的人物图像,因此需要将官方例程中人脸检测部分的活体检测功能去掉,这里就可以识别图片中的人脸了。
4 总结
本篇进行了AI人脸门禁打卡机综合例程的测试,在官方例程的基础上,增加了数据库的展示页面,可从待机界面切换到数据库展示界面,另外增加了数据库删除按钮,可清空数据库进行重新的人脸注册。