EASY EAI灵眸科技
直播中

fsdzdzy

10年用户 260经验值
擅长:嵌入式技术 控制/MCU opencv
私信 关注

【EASY EAI Nano开源套件试用体验】7AI人脸门禁打卡机综合例程

前面几篇文章,对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数据库操作等。在官方提供的例程的基础上,增加数据库内容显示界面,用来显示已录入的人脸情况,以及每个人的打卡记录信息,另外还可以通过界面的按钮来删除数据库,以重新录入数据。

0.png

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.png

1.1.2 界面间的模块交互

界面模块间的交互,应用了信号槽和事件机制,该种机制也是Qt为了去耦合而进行的针对性设计。而且Qt规定,非gui线程不能操作gui线程,因此从UIManager(非界面)到mainWidget(界面)的调用操作,也必须通过信号槽来完成。

2.png

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();
    /// 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);
}

应用调度器实质上是一个回调函数映射表,在创建基础业务功能块时,需要把业务功能块的回调函数注册到应用调度器的映射中

3.png

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实例化对象之间的桥梁。

4.png

1.2 归纳总结

官方的人脸门禁机项目,总结一些内容:

  • main.cpp中实例化一个AppScheduler类型的App,创建各个业务模块

  • 创建的业务模块包括:数据库管理、主线程、UI管理、语音播放管理、消息管理

    • 数据库管理,用于记录注册的人脸等数据
    • 主线程,用于开启摄像头和屏幕,并进行人脸检测和人脸识别
    • UI管理,管理4个图形界面
    • 语音播放管理,用于播放语音
    • 消息管理,用于模块间的消息通信
  • Qt 设计的4个U界面(主界面、待机界面、输入界面、通知界面)

    主界面占用整个屏幕,其它的3个界面占用主界面的上半部分或中间部分。

    • 主界面,主要的内容在界面的下半部分,包括欢迎按钮、时间日期的显示等,上半部分是空白,设置为透明显示,可显示摄像头的图像
    • 待机界面,实际是一个空白透明界面,占用屏幕的上半部分
    • 输入界面,用于数据密码数据,占用屏幕的上半部分
    • 通知界面,用于显示匹配成功的人脸信息,占用屏幕的中间部分

2 功能修改png

2.1 增加数据库显示界面

数据库中数据的显示,通过表格的形式展现,用到了Qt中的Table Widget组件,在Qt Creator中新建一个ui界面,添加该组件后,可再通过编写代码来实现将数据插入表格。

5.png

表格插入数据的实例程序:

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();
}

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;
}
​
//要执行的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;
}

3 演示

实验演示了人脸注册、人脸检测于识别、数据库的展示、数据库的删除、语音播报等,演示视频见文末。

为了测试多张人脸,使用的是电脑屏幕中的人物图像,因此需要将官方例程中人脸检测部分的活体检测功能去掉,这里就可以识别图片中的人脸了。

4 总结

本篇进行了AI人脸门禁打卡机综合例程的测试,在官方例程的基础上,增加了数据库的展示页面,可从待机界面切换到数据库展示界面,另外增加了数据库删除按钮,可清空数据库进行重新的人脸注册。

EASY EAI Nano AI人脸门禁打卡机

回帖(1)

华仔stm32

2022-10-16 18:36:48
案例非常好,感谢分享!
举报

更多回帖

发帖
×
20
完善资料,
赚取积分