diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/casic/face/CasicFaceInterface.h b/casic/face/CasicFaceInterface.h new file mode 100644 index 0000000..d6d4494 --- /dev/null +++ b/casic/face/CasicFaceInterface.h @@ -0,0 +1,91 @@ +#ifndef CASICFACEINTERFACE_H +#define CASICFACEINTERFACE_H + +#include "opencv2/opencv.hpp" +#include "seeta/FaceDetector.h" +#include "seeta/FaceLandmarker.h" +#include "seeta/QualityAssessor.h" +#include "seeta/QualityOfBrightness.h" +#include "seeta/QualityOfClarity.h" +#include "seeta/QualityOfIntegrity.h" +#include "seeta/QualityOfResolution.h" +#include "seeta/QualityOfPoseEx.h" +#include "seeta/FaceAntiSpoofing.h" +#include "seeta/FaceRecognizer.h" + +#include "CasicFaceInfo.h" + +static auto red = CV_RGB(255, 0, 0); +static auto green = CV_RGB(0, 255, 0); +static auto blue = CV_RGB(0, 0, 255); + +namespace casic { + namespace face { + class CasicFaceInterface + { + public: + ~CasicFaceInterface(); + CasicFaceInterface(const CasicFaceInterface&)=delete; + CasicFaceInterface& operator=(const CasicFaceInterface&)=delete; + + static CasicFaceInterface& getInstance() { + static CasicFaceInterface instance; + return instance; + } + + void setDetectorModelPath(std::string detectorModelPath); + void setMarkPts5ModelPath(std::string markPts5ModelPath); + void setPoseModelPath(std::string poseModelPath); + void setFas1stModelPath(std::string fas1stModelPath); + void setFas2ndModelPath(std::string fas2ndModelPath); + void setRecognizerModelPath(std::string recognizerModelPath); + + void setAntiThreshold(float clarity, float reality); + + CasicFaceInfo faceDetect(cv::Mat frame); + CasicFaceInfo faceQuality(CasicFaceInfo faceInfo); + CasicFaceInfo faceAntiSpoofing(CasicFaceInfo faceInfo); + CasicFaceInfo faceFeatureExtract(CasicFaceInfo faceInfo); + float faceSimCalculate(float* feature, float* otherFeature); + + cv::Rect faceDetectByCVCascade(cv::Mat frame); + cv::Rect eyeDetectByCVCascade(cv::Mat frame); + void setMinFaceSize(int minFaceSize); + void setMinEyeSize(int minEyeSize); + private: + CasicFaceInterface(); + + int deviceId = 0; + seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO; + + float clarity = 0.3f; + float reality = 0.3f; + + std::string cvFaceCascadeName = "./model/haarcascade_frontalface_default.xml"; + std::string cvEyeCascadeName = "./model/haarcascade_eye.xml"; + int minFaceSize = 320; + int maxFaceSize = 720; + int minEyeSize = 100; + int maxEyeSize = 600; + + std::string detectorModelPath = "./model/face_detector.csta"; + std::string markPts5ModelPath = "./model/face_landmarker_pts5.csta"; + std::string poseModelPath = "./model/pose_estimation.csta"; + std::string fas1stModelPath = "./model/fas_first.csta"; + std::string fas2ndModelPath = "./model/fas_second.csta"; + std::string recognizerModelPath = "./model/face_recognizer.csta"; + + seeta::FaceDetector * detector = nullptr; + seeta::FaceLandmarker * marker = nullptr; + seeta::QualityOfPoseEx * poseEx = nullptr; + seeta::FaceAntiSpoofing * processor = nullptr; + seeta::FaceRecognizer * recognizer = nullptr; + + cv::CascadeClassifier * cascade; + cv::CascadeClassifier * eyeCascade; + }; + } +} + + +#endif // CASICFACEINTERFACE_H diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/casic/face/CasicFaceInterface.h b/casic/face/CasicFaceInterface.h new file mode 100644 index 0000000..d6d4494 --- /dev/null +++ b/casic/face/CasicFaceInterface.h @@ -0,0 +1,91 @@ +#ifndef CASICFACEINTERFACE_H +#define CASICFACEINTERFACE_H + +#include "opencv2/opencv.hpp" +#include "seeta/FaceDetector.h" +#include "seeta/FaceLandmarker.h" +#include "seeta/QualityAssessor.h" +#include "seeta/QualityOfBrightness.h" +#include "seeta/QualityOfClarity.h" +#include "seeta/QualityOfIntegrity.h" +#include "seeta/QualityOfResolution.h" +#include "seeta/QualityOfPoseEx.h" +#include "seeta/FaceAntiSpoofing.h" +#include "seeta/FaceRecognizer.h" + +#include "CasicFaceInfo.h" + +static auto red = CV_RGB(255, 0, 0); +static auto green = CV_RGB(0, 255, 0); +static auto blue = CV_RGB(0, 0, 255); + +namespace casic { + namespace face { + class CasicFaceInterface + { + public: + ~CasicFaceInterface(); + CasicFaceInterface(const CasicFaceInterface&)=delete; + CasicFaceInterface& operator=(const CasicFaceInterface&)=delete; + + static CasicFaceInterface& getInstance() { + static CasicFaceInterface instance; + return instance; + } + + void setDetectorModelPath(std::string detectorModelPath); + void setMarkPts5ModelPath(std::string markPts5ModelPath); + void setPoseModelPath(std::string poseModelPath); + void setFas1stModelPath(std::string fas1stModelPath); + void setFas2ndModelPath(std::string fas2ndModelPath); + void setRecognizerModelPath(std::string recognizerModelPath); + + void setAntiThreshold(float clarity, float reality); + + CasicFaceInfo faceDetect(cv::Mat frame); + CasicFaceInfo faceQuality(CasicFaceInfo faceInfo); + CasicFaceInfo faceAntiSpoofing(CasicFaceInfo faceInfo); + CasicFaceInfo faceFeatureExtract(CasicFaceInfo faceInfo); + float faceSimCalculate(float* feature, float* otherFeature); + + cv::Rect faceDetectByCVCascade(cv::Mat frame); + cv::Rect eyeDetectByCVCascade(cv::Mat frame); + void setMinFaceSize(int minFaceSize); + void setMinEyeSize(int minEyeSize); + private: + CasicFaceInterface(); + + int deviceId = 0; + seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO; + + float clarity = 0.3f; + float reality = 0.3f; + + std::string cvFaceCascadeName = "./model/haarcascade_frontalface_default.xml"; + std::string cvEyeCascadeName = "./model/haarcascade_eye.xml"; + int minFaceSize = 320; + int maxFaceSize = 720; + int minEyeSize = 100; + int maxEyeSize = 600; + + std::string detectorModelPath = "./model/face_detector.csta"; + std::string markPts5ModelPath = "./model/face_landmarker_pts5.csta"; + std::string poseModelPath = "./model/pose_estimation.csta"; + std::string fas1stModelPath = "./model/fas_first.csta"; + std::string fas2ndModelPath = "./model/fas_second.csta"; + std::string recognizerModelPath = "./model/face_recognizer.csta"; + + seeta::FaceDetector * detector = nullptr; + seeta::FaceLandmarker * marker = nullptr; + seeta::QualityOfPoseEx * poseEx = nullptr; + seeta::FaceAntiSpoofing * processor = nullptr; + seeta::FaceRecognizer * recognizer = nullptr; + + cv::CascadeClassifier * cascade; + cv::CascadeClassifier * eyeCascade; + }; + } +} + + +#endif // CASICFACEINTERFACE_H diff --git a/casic/face/casicFace.pri b/casic/face/casicFace.pri new file mode 100644 index 0000000..da337d9 --- /dev/null +++ b/casic/face/casicFace.pri @@ -0,0 +1,9 @@ +HEADERS += $$PWD/CasicFaceInfo.h +HEADERS += $$PWD/CasicFaceInterface.h + +SOURCES += $$PWD/CasicFaceInterface.cpp + +INCLUDEPATH += seeta/ + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600 -lSeetaFaceLandmarker600 -lSeetaFaceAntiSpoofingX600 -lSeetaFaceRecognizer610 -lSeetaQualityAssessor300 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600d -lSeetaFaceLandmarker600d -lSeetaFaceAntiSpoofingX600d -lSeetaFaceRecognizer610d -lSeetaQualityAssessor300d diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/casic/face/CasicFaceInterface.h b/casic/face/CasicFaceInterface.h new file mode 100644 index 0000000..d6d4494 --- /dev/null +++ b/casic/face/CasicFaceInterface.h @@ -0,0 +1,91 @@ +#ifndef CASICFACEINTERFACE_H +#define CASICFACEINTERFACE_H + +#include "opencv2/opencv.hpp" +#include "seeta/FaceDetector.h" +#include "seeta/FaceLandmarker.h" +#include "seeta/QualityAssessor.h" +#include "seeta/QualityOfBrightness.h" +#include "seeta/QualityOfClarity.h" +#include "seeta/QualityOfIntegrity.h" +#include "seeta/QualityOfResolution.h" +#include "seeta/QualityOfPoseEx.h" +#include "seeta/FaceAntiSpoofing.h" +#include "seeta/FaceRecognizer.h" + +#include "CasicFaceInfo.h" + +static auto red = CV_RGB(255, 0, 0); +static auto green = CV_RGB(0, 255, 0); +static auto blue = CV_RGB(0, 0, 255); + +namespace casic { + namespace face { + class CasicFaceInterface + { + public: + ~CasicFaceInterface(); + CasicFaceInterface(const CasicFaceInterface&)=delete; + CasicFaceInterface& operator=(const CasicFaceInterface&)=delete; + + static CasicFaceInterface& getInstance() { + static CasicFaceInterface instance; + return instance; + } + + void setDetectorModelPath(std::string detectorModelPath); + void setMarkPts5ModelPath(std::string markPts5ModelPath); + void setPoseModelPath(std::string poseModelPath); + void setFas1stModelPath(std::string fas1stModelPath); + void setFas2ndModelPath(std::string fas2ndModelPath); + void setRecognizerModelPath(std::string recognizerModelPath); + + void setAntiThreshold(float clarity, float reality); + + CasicFaceInfo faceDetect(cv::Mat frame); + CasicFaceInfo faceQuality(CasicFaceInfo faceInfo); + CasicFaceInfo faceAntiSpoofing(CasicFaceInfo faceInfo); + CasicFaceInfo faceFeatureExtract(CasicFaceInfo faceInfo); + float faceSimCalculate(float* feature, float* otherFeature); + + cv::Rect faceDetectByCVCascade(cv::Mat frame); + cv::Rect eyeDetectByCVCascade(cv::Mat frame); + void setMinFaceSize(int minFaceSize); + void setMinEyeSize(int minEyeSize); + private: + CasicFaceInterface(); + + int deviceId = 0; + seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO; + + float clarity = 0.3f; + float reality = 0.3f; + + std::string cvFaceCascadeName = "./model/haarcascade_frontalface_default.xml"; + std::string cvEyeCascadeName = "./model/haarcascade_eye.xml"; + int minFaceSize = 320; + int maxFaceSize = 720; + int minEyeSize = 100; + int maxEyeSize = 600; + + std::string detectorModelPath = "./model/face_detector.csta"; + std::string markPts5ModelPath = "./model/face_landmarker_pts5.csta"; + std::string poseModelPath = "./model/pose_estimation.csta"; + std::string fas1stModelPath = "./model/fas_first.csta"; + std::string fas2ndModelPath = "./model/fas_second.csta"; + std::string recognizerModelPath = "./model/face_recognizer.csta"; + + seeta::FaceDetector * detector = nullptr; + seeta::FaceLandmarker * marker = nullptr; + seeta::QualityOfPoseEx * poseEx = nullptr; + seeta::FaceAntiSpoofing * processor = nullptr; + seeta::FaceRecognizer * recognizer = nullptr; + + cv::CascadeClassifier * cascade; + cv::CascadeClassifier * eyeCascade; + }; + } +} + + +#endif // CASICFACEINTERFACE_H diff --git a/casic/face/casicFace.pri b/casic/face/casicFace.pri new file mode 100644 index 0000000..da337d9 --- /dev/null +++ b/casic/face/casicFace.pri @@ -0,0 +1,9 @@ +HEADERS += $$PWD/CasicFaceInfo.h +HEADERS += $$PWD/CasicFaceInterface.h + +SOURCES += $$PWD/CasicFaceInterface.cpp + +INCLUDEPATH += seeta/ + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600 -lSeetaFaceLandmarker600 -lSeetaFaceAntiSpoofingX600 -lSeetaFaceRecognizer610 -lSeetaQualityAssessor300 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600d -lSeetaFaceLandmarker600d -lSeetaFaceAntiSpoofingX600d -lSeetaFaceRecognizer610d -lSeetaQualityAssessor300d diff --git a/dao/FaceDataImgDao.cpp b/dao/FaceDataImgDao.cpp index 83f22a0..d863752 100644 --- a/dao/FaceDataImgDao.cpp +++ b/dao/FaceDataImgDao.cpp @@ -78,20 +78,14 @@ // 返回结果 QVariantMap result; - // 获取结果集的大小 - query.last(); - int count = query.at() + 1; - - if (count >=1) + if (query.next()) { - query.first(); - result.insert("id", query.value("id").toString()); result.insert("person_id", query.value("person_id").toString()); result.insert("face_image", query.value("face_image").toString()); } - LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[%1][id=%2][%3]").arg(count).arg(query.value("id").toString()).arg(sql).toStdString(); + LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/casic/face/CasicFaceInterface.h b/casic/face/CasicFaceInterface.h new file mode 100644 index 0000000..d6d4494 --- /dev/null +++ b/casic/face/CasicFaceInterface.h @@ -0,0 +1,91 @@ +#ifndef CASICFACEINTERFACE_H +#define CASICFACEINTERFACE_H + +#include "opencv2/opencv.hpp" +#include "seeta/FaceDetector.h" +#include "seeta/FaceLandmarker.h" +#include "seeta/QualityAssessor.h" +#include "seeta/QualityOfBrightness.h" +#include "seeta/QualityOfClarity.h" +#include "seeta/QualityOfIntegrity.h" +#include "seeta/QualityOfResolution.h" +#include "seeta/QualityOfPoseEx.h" +#include "seeta/FaceAntiSpoofing.h" +#include "seeta/FaceRecognizer.h" + +#include "CasicFaceInfo.h" + +static auto red = CV_RGB(255, 0, 0); +static auto green = CV_RGB(0, 255, 0); +static auto blue = CV_RGB(0, 0, 255); + +namespace casic { + namespace face { + class CasicFaceInterface + { + public: + ~CasicFaceInterface(); + CasicFaceInterface(const CasicFaceInterface&)=delete; + CasicFaceInterface& operator=(const CasicFaceInterface&)=delete; + + static CasicFaceInterface& getInstance() { + static CasicFaceInterface instance; + return instance; + } + + void setDetectorModelPath(std::string detectorModelPath); + void setMarkPts5ModelPath(std::string markPts5ModelPath); + void setPoseModelPath(std::string poseModelPath); + void setFas1stModelPath(std::string fas1stModelPath); + void setFas2ndModelPath(std::string fas2ndModelPath); + void setRecognizerModelPath(std::string recognizerModelPath); + + void setAntiThreshold(float clarity, float reality); + + CasicFaceInfo faceDetect(cv::Mat frame); + CasicFaceInfo faceQuality(CasicFaceInfo faceInfo); + CasicFaceInfo faceAntiSpoofing(CasicFaceInfo faceInfo); + CasicFaceInfo faceFeatureExtract(CasicFaceInfo faceInfo); + float faceSimCalculate(float* feature, float* otherFeature); + + cv::Rect faceDetectByCVCascade(cv::Mat frame); + cv::Rect eyeDetectByCVCascade(cv::Mat frame); + void setMinFaceSize(int minFaceSize); + void setMinEyeSize(int minEyeSize); + private: + CasicFaceInterface(); + + int deviceId = 0; + seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO; + + float clarity = 0.3f; + float reality = 0.3f; + + std::string cvFaceCascadeName = "./model/haarcascade_frontalface_default.xml"; + std::string cvEyeCascadeName = "./model/haarcascade_eye.xml"; + int minFaceSize = 320; + int maxFaceSize = 720; + int minEyeSize = 100; + int maxEyeSize = 600; + + std::string detectorModelPath = "./model/face_detector.csta"; + std::string markPts5ModelPath = "./model/face_landmarker_pts5.csta"; + std::string poseModelPath = "./model/pose_estimation.csta"; + std::string fas1stModelPath = "./model/fas_first.csta"; + std::string fas2ndModelPath = "./model/fas_second.csta"; + std::string recognizerModelPath = "./model/face_recognizer.csta"; + + seeta::FaceDetector * detector = nullptr; + seeta::FaceLandmarker * marker = nullptr; + seeta::QualityOfPoseEx * poseEx = nullptr; + seeta::FaceAntiSpoofing * processor = nullptr; + seeta::FaceRecognizer * recognizer = nullptr; + + cv::CascadeClassifier * cascade; + cv::CascadeClassifier * eyeCascade; + }; + } +} + + +#endif // CASICFACEINTERFACE_H diff --git a/casic/face/casicFace.pri b/casic/face/casicFace.pri new file mode 100644 index 0000000..da337d9 --- /dev/null +++ b/casic/face/casicFace.pri @@ -0,0 +1,9 @@ +HEADERS += $$PWD/CasicFaceInfo.h +HEADERS += $$PWD/CasicFaceInterface.h + +SOURCES += $$PWD/CasicFaceInterface.cpp + +INCLUDEPATH += seeta/ + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600 -lSeetaFaceLandmarker600 -lSeetaFaceAntiSpoofingX600 -lSeetaFaceRecognizer610 -lSeetaQualityAssessor300 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600d -lSeetaFaceLandmarker600d -lSeetaFaceAntiSpoofingX600d -lSeetaFaceRecognizer610d -lSeetaQualityAssessor300d diff --git a/dao/FaceDataImgDao.cpp b/dao/FaceDataImgDao.cpp index 83f22a0..d863752 100644 --- a/dao/FaceDataImgDao.cpp +++ b/dao/FaceDataImgDao.cpp @@ -78,20 +78,14 @@ // 返回结果 QVariantMap result; - // 获取结果集的大小 - query.last(); - int count = query.at() + 1; - - if (count >=1) + if (query.next()) { - query.first(); - result.insert("id", query.value("id").toString()); result.insert("person_id", query.value("person_id").toString()); result.insert("face_image", query.value("face_image").toString()); } - LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[%1][id=%2][%3]").arg(count).arg(query.value("id").toString()).arg(sql).toStdString(); + LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/dao/IrisDataImgDao.cpp b/dao/IrisDataImgDao.cpp index af0d45b..8a0c9af 100644 --- a/dao/IrisDataImgDao.cpp +++ b/dao/IrisDataImgDao.cpp @@ -97,7 +97,7 @@ result.insert("right_image1", query.value("right_image1").toString()); } - LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId:%1][%2]").arg(personId).arg(sql).toStdString(); + LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/casic/face/CasicFaceInterface.h b/casic/face/CasicFaceInterface.h new file mode 100644 index 0000000..d6d4494 --- /dev/null +++ b/casic/face/CasicFaceInterface.h @@ -0,0 +1,91 @@ +#ifndef CASICFACEINTERFACE_H +#define CASICFACEINTERFACE_H + +#include "opencv2/opencv.hpp" +#include "seeta/FaceDetector.h" +#include "seeta/FaceLandmarker.h" +#include "seeta/QualityAssessor.h" +#include "seeta/QualityOfBrightness.h" +#include "seeta/QualityOfClarity.h" +#include "seeta/QualityOfIntegrity.h" +#include "seeta/QualityOfResolution.h" +#include "seeta/QualityOfPoseEx.h" +#include "seeta/FaceAntiSpoofing.h" +#include "seeta/FaceRecognizer.h" + +#include "CasicFaceInfo.h" + +static auto red = CV_RGB(255, 0, 0); +static auto green = CV_RGB(0, 255, 0); +static auto blue = CV_RGB(0, 0, 255); + +namespace casic { + namespace face { + class CasicFaceInterface + { + public: + ~CasicFaceInterface(); + CasicFaceInterface(const CasicFaceInterface&)=delete; + CasicFaceInterface& operator=(const CasicFaceInterface&)=delete; + + static CasicFaceInterface& getInstance() { + static CasicFaceInterface instance; + return instance; + } + + void setDetectorModelPath(std::string detectorModelPath); + void setMarkPts5ModelPath(std::string markPts5ModelPath); + void setPoseModelPath(std::string poseModelPath); + void setFas1stModelPath(std::string fas1stModelPath); + void setFas2ndModelPath(std::string fas2ndModelPath); + void setRecognizerModelPath(std::string recognizerModelPath); + + void setAntiThreshold(float clarity, float reality); + + CasicFaceInfo faceDetect(cv::Mat frame); + CasicFaceInfo faceQuality(CasicFaceInfo faceInfo); + CasicFaceInfo faceAntiSpoofing(CasicFaceInfo faceInfo); + CasicFaceInfo faceFeatureExtract(CasicFaceInfo faceInfo); + float faceSimCalculate(float* feature, float* otherFeature); + + cv::Rect faceDetectByCVCascade(cv::Mat frame); + cv::Rect eyeDetectByCVCascade(cv::Mat frame); + void setMinFaceSize(int minFaceSize); + void setMinEyeSize(int minEyeSize); + private: + CasicFaceInterface(); + + int deviceId = 0; + seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO; + + float clarity = 0.3f; + float reality = 0.3f; + + std::string cvFaceCascadeName = "./model/haarcascade_frontalface_default.xml"; + std::string cvEyeCascadeName = "./model/haarcascade_eye.xml"; + int minFaceSize = 320; + int maxFaceSize = 720; + int minEyeSize = 100; + int maxEyeSize = 600; + + std::string detectorModelPath = "./model/face_detector.csta"; + std::string markPts5ModelPath = "./model/face_landmarker_pts5.csta"; + std::string poseModelPath = "./model/pose_estimation.csta"; + std::string fas1stModelPath = "./model/fas_first.csta"; + std::string fas2ndModelPath = "./model/fas_second.csta"; + std::string recognizerModelPath = "./model/face_recognizer.csta"; + + seeta::FaceDetector * detector = nullptr; + seeta::FaceLandmarker * marker = nullptr; + seeta::QualityOfPoseEx * poseEx = nullptr; + seeta::FaceAntiSpoofing * processor = nullptr; + seeta::FaceRecognizer * recognizer = nullptr; + + cv::CascadeClassifier * cascade; + cv::CascadeClassifier * eyeCascade; + }; + } +} + + +#endif // CASICFACEINTERFACE_H diff --git a/casic/face/casicFace.pri b/casic/face/casicFace.pri new file mode 100644 index 0000000..da337d9 --- /dev/null +++ b/casic/face/casicFace.pri @@ -0,0 +1,9 @@ +HEADERS += $$PWD/CasicFaceInfo.h +HEADERS += $$PWD/CasicFaceInterface.h + +SOURCES += $$PWD/CasicFaceInterface.cpp + +INCLUDEPATH += seeta/ + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600 -lSeetaFaceLandmarker600 -lSeetaFaceAntiSpoofingX600 -lSeetaFaceRecognizer610 -lSeetaQualityAssessor300 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600d -lSeetaFaceLandmarker600d -lSeetaFaceAntiSpoofingX600d -lSeetaFaceRecognizer610d -lSeetaQualityAssessor300d diff --git a/dao/FaceDataImgDao.cpp b/dao/FaceDataImgDao.cpp index 83f22a0..d863752 100644 --- a/dao/FaceDataImgDao.cpp +++ b/dao/FaceDataImgDao.cpp @@ -78,20 +78,14 @@ // 返回结果 QVariantMap result; - // 获取结果集的大小 - query.last(); - int count = query.at() + 1; - - if (count >=1) + if (query.next()) { - query.first(); - result.insert("id", query.value("id").toString()); result.insert("person_id", query.value("person_id").toString()); result.insert("face_image", query.value("face_image").toString()); } - LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[%1][id=%2][%3]").arg(count).arg(query.value("id").toString()).arg(sql).toStdString(); + LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/dao/IrisDataImgDao.cpp b/dao/IrisDataImgDao.cpp index af0d45b..8a0c9af 100644 --- a/dao/IrisDataImgDao.cpp +++ b/dao/IrisDataImgDao.cpp @@ -97,7 +97,7 @@ result.insert("right_image1", query.value("right_image1").toString()); } - LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId:%1][%2]").arg(personId).arg(sql).toStdString(); + LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/device/FaceCameraController.cpp b/device/FaceCameraController.cpp new file mode 100644 index 0000000..bb325a4 --- /dev/null +++ b/device/FaceCameraController.cpp @@ -0,0 +1,60 @@ +#include "FaceCameraController.h" +#include +#include +#include + +FaceCameraController::FaceCameraController(QObject *parent) : QObject(parent) +{ + // 获取定时器, 绑定定时函数 + connect(TimeCounterUtil::getInstance().faceCapCounter, &QTimer::timeout, + this, &FaceCameraController::getOneFaceFrm); +} + +FaceCameraController::~FaceCameraController() +{ + this->closeFaceCamera(); +} + + +void FaceCameraController::openFaceCamera() +{ + this->faceCap = new cv::VideoCapture(SettingConfig::getInstance().FACE_CAMERA_INDEX, cv::CAP_DSHOW); + faceCap->set(cv::CAP_PROP_FRAME_WIDTH, SettingConfig::getInstance().FACE_FRAME_WIDTH); + faceCap->set(cv::CAP_PROP_FRAME_HEIGHT, SettingConfig::getInstance().FACE_FRAME_HEIGHT); + + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]打开相机[%1][%2 * %3]") + .arg(SettingConfig::getInstance().FACE_CAMERA_INDEX) + .arg(SettingConfig::getInstance().FACE_FRAME_WIDTH) + .arg(SettingConfig::getInstance().FACE_FRAME_HEIGHT).toStdString(); + + // 启动定时器 + TimeCounterUtil::getInstance().faceCapCounter->setInterval(SettingConfig::getInstance().FACE_FRAME_INTERVAL); + TimeCounterUtil::getInstance().faceCapCounter->start(); + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]相机开始拍图[%1ms]") + .arg(SettingConfig::getInstance().FACE_FRAME_INTERVAL).toStdString(); +} + +void FaceCameraController::closeFaceCamera() +{ + faceCap->release(); + + delete faceCap; +} + + +void FaceCameraController::getOneFaceFrm() +{ + faceCap->read(faceMat); + + // clone一个mat, 用于界面显示 + cv::Mat faceMatDisp = faceMat.clone(); + QImage imgDisplay = ImageUtil::MatImageToQImage(faceMatDisp); + + // 发送信号用于界面显示 + emit sendImageToDraw(); + + LOG(DEBUG) << " TAKE ONE FACE FRAME " << faceMat.cols << " * " << faceMat.rows; + + // 发送信号用于人脸检测和生成特征值 +// emit sendImageToDetect(faceMat); +} diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/casic/face/CasicFaceInterface.h b/casic/face/CasicFaceInterface.h new file mode 100644 index 0000000..d6d4494 --- /dev/null +++ b/casic/face/CasicFaceInterface.h @@ -0,0 +1,91 @@ +#ifndef CASICFACEINTERFACE_H +#define CASICFACEINTERFACE_H + +#include "opencv2/opencv.hpp" +#include "seeta/FaceDetector.h" +#include "seeta/FaceLandmarker.h" +#include "seeta/QualityAssessor.h" +#include "seeta/QualityOfBrightness.h" +#include "seeta/QualityOfClarity.h" +#include "seeta/QualityOfIntegrity.h" +#include "seeta/QualityOfResolution.h" +#include "seeta/QualityOfPoseEx.h" +#include "seeta/FaceAntiSpoofing.h" +#include "seeta/FaceRecognizer.h" + +#include "CasicFaceInfo.h" + +static auto red = CV_RGB(255, 0, 0); +static auto green = CV_RGB(0, 255, 0); +static auto blue = CV_RGB(0, 0, 255); + +namespace casic { + namespace face { + class CasicFaceInterface + { + public: + ~CasicFaceInterface(); + CasicFaceInterface(const CasicFaceInterface&)=delete; + CasicFaceInterface& operator=(const CasicFaceInterface&)=delete; + + static CasicFaceInterface& getInstance() { + static CasicFaceInterface instance; + return instance; + } + + void setDetectorModelPath(std::string detectorModelPath); + void setMarkPts5ModelPath(std::string markPts5ModelPath); + void setPoseModelPath(std::string poseModelPath); + void setFas1stModelPath(std::string fas1stModelPath); + void setFas2ndModelPath(std::string fas2ndModelPath); + void setRecognizerModelPath(std::string recognizerModelPath); + + void setAntiThreshold(float clarity, float reality); + + CasicFaceInfo faceDetect(cv::Mat frame); + CasicFaceInfo faceQuality(CasicFaceInfo faceInfo); + CasicFaceInfo faceAntiSpoofing(CasicFaceInfo faceInfo); + CasicFaceInfo faceFeatureExtract(CasicFaceInfo faceInfo); + float faceSimCalculate(float* feature, float* otherFeature); + + cv::Rect faceDetectByCVCascade(cv::Mat frame); + cv::Rect eyeDetectByCVCascade(cv::Mat frame); + void setMinFaceSize(int minFaceSize); + void setMinEyeSize(int minEyeSize); + private: + CasicFaceInterface(); + + int deviceId = 0; + seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO; + + float clarity = 0.3f; + float reality = 0.3f; + + std::string cvFaceCascadeName = "./model/haarcascade_frontalface_default.xml"; + std::string cvEyeCascadeName = "./model/haarcascade_eye.xml"; + int minFaceSize = 320; + int maxFaceSize = 720; + int minEyeSize = 100; + int maxEyeSize = 600; + + std::string detectorModelPath = "./model/face_detector.csta"; + std::string markPts5ModelPath = "./model/face_landmarker_pts5.csta"; + std::string poseModelPath = "./model/pose_estimation.csta"; + std::string fas1stModelPath = "./model/fas_first.csta"; + std::string fas2ndModelPath = "./model/fas_second.csta"; + std::string recognizerModelPath = "./model/face_recognizer.csta"; + + seeta::FaceDetector * detector = nullptr; + seeta::FaceLandmarker * marker = nullptr; + seeta::QualityOfPoseEx * poseEx = nullptr; + seeta::FaceAntiSpoofing * processor = nullptr; + seeta::FaceRecognizer * recognizer = nullptr; + + cv::CascadeClassifier * cascade; + cv::CascadeClassifier * eyeCascade; + }; + } +} + + +#endif // CASICFACEINTERFACE_H diff --git a/casic/face/casicFace.pri b/casic/face/casicFace.pri new file mode 100644 index 0000000..da337d9 --- /dev/null +++ b/casic/face/casicFace.pri @@ -0,0 +1,9 @@ +HEADERS += $$PWD/CasicFaceInfo.h +HEADERS += $$PWD/CasicFaceInterface.h + +SOURCES += $$PWD/CasicFaceInterface.cpp + +INCLUDEPATH += seeta/ + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600 -lSeetaFaceLandmarker600 -lSeetaFaceAntiSpoofingX600 -lSeetaFaceRecognizer610 -lSeetaQualityAssessor300 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600d -lSeetaFaceLandmarker600d -lSeetaFaceAntiSpoofingX600d -lSeetaFaceRecognizer610d -lSeetaQualityAssessor300d diff --git a/dao/FaceDataImgDao.cpp b/dao/FaceDataImgDao.cpp index 83f22a0..d863752 100644 --- a/dao/FaceDataImgDao.cpp +++ b/dao/FaceDataImgDao.cpp @@ -78,20 +78,14 @@ // 返回结果 QVariantMap result; - // 获取结果集的大小 - query.last(); - int count = query.at() + 1; - - if (count >=1) + if (query.next()) { - query.first(); - result.insert("id", query.value("id").toString()); result.insert("person_id", query.value("person_id").toString()); result.insert("face_image", query.value("face_image").toString()); } - LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[%1][id=%2][%3]").arg(count).arg(query.value("id").toString()).arg(sql).toStdString(); + LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/dao/IrisDataImgDao.cpp b/dao/IrisDataImgDao.cpp index af0d45b..8a0c9af 100644 --- a/dao/IrisDataImgDao.cpp +++ b/dao/IrisDataImgDao.cpp @@ -97,7 +97,7 @@ result.insert("right_image1", query.value("right_image1").toString()); } - LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId:%1][%2]").arg(personId).arg(sql).toStdString(); + LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/device/FaceCameraController.cpp b/device/FaceCameraController.cpp new file mode 100644 index 0000000..bb325a4 --- /dev/null +++ b/device/FaceCameraController.cpp @@ -0,0 +1,60 @@ +#include "FaceCameraController.h" +#include +#include +#include + +FaceCameraController::FaceCameraController(QObject *parent) : QObject(parent) +{ + // 获取定时器, 绑定定时函数 + connect(TimeCounterUtil::getInstance().faceCapCounter, &QTimer::timeout, + this, &FaceCameraController::getOneFaceFrm); +} + +FaceCameraController::~FaceCameraController() +{ + this->closeFaceCamera(); +} + + +void FaceCameraController::openFaceCamera() +{ + this->faceCap = new cv::VideoCapture(SettingConfig::getInstance().FACE_CAMERA_INDEX, cv::CAP_DSHOW); + faceCap->set(cv::CAP_PROP_FRAME_WIDTH, SettingConfig::getInstance().FACE_FRAME_WIDTH); + faceCap->set(cv::CAP_PROP_FRAME_HEIGHT, SettingConfig::getInstance().FACE_FRAME_HEIGHT); + + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]打开相机[%1][%2 * %3]") + .arg(SettingConfig::getInstance().FACE_CAMERA_INDEX) + .arg(SettingConfig::getInstance().FACE_FRAME_WIDTH) + .arg(SettingConfig::getInstance().FACE_FRAME_HEIGHT).toStdString(); + + // 启动定时器 + TimeCounterUtil::getInstance().faceCapCounter->setInterval(SettingConfig::getInstance().FACE_FRAME_INTERVAL); + TimeCounterUtil::getInstance().faceCapCounter->start(); + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]相机开始拍图[%1ms]") + .arg(SettingConfig::getInstance().FACE_FRAME_INTERVAL).toStdString(); +} + +void FaceCameraController::closeFaceCamera() +{ + faceCap->release(); + + delete faceCap; +} + + +void FaceCameraController::getOneFaceFrm() +{ + faceCap->read(faceMat); + + // clone一个mat, 用于界面显示 + cv::Mat faceMatDisp = faceMat.clone(); + QImage imgDisplay = ImageUtil::MatImageToQImage(faceMatDisp); + + // 发送信号用于界面显示 + emit sendImageToDraw(); + + LOG(DEBUG) << " TAKE ONE FACE FRAME " << faceMat.cols << " * " << faceMat.rows; + + // 发送信号用于人脸检测和生成特征值 +// emit sendImageToDetect(faceMat); +} diff --git a/device/FaceCameraController.h b/device/FaceCameraController.h new file mode 100644 index 0000000..c75fcda --- /dev/null +++ b/device/FaceCameraController.h @@ -0,0 +1,40 @@ +#ifndef CAMERACONTROLLER_H +#define CAMERACONTROLLER_H + +#include + +#include "opencv2/opencv.hpp" + +//#include "casic/face/CasicFaceInterface.h" +//#include "process/memory/ProMemory.h" +//#include "process/face/CasicFaceRecState.h" +#include "utils/ImageUtil.h" +#include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" +#include "utils/easyloggingpp/easylogging++.h" + +class FaceCameraController : public QObject +{ + Q_OBJECT +public: + explicit FaceCameraController(QObject *parent = nullptr); + ~FaceCameraController(); + + // 初始化并打开人脸相机 + void openFaceCamera(); + void closeFaceCamera(); + +private: + cv::VideoCapture * faceCap; + + cv::Mat faceMat; + +public slots: + void getOneFaceFrm(); + +signals: + void sendImageToDraw(); + void sendImageToDetect(cv::Mat imgMat); +}; + +#endif // CAMERACONTROLLER_H diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/casic/face/CasicFaceInterface.h b/casic/face/CasicFaceInterface.h new file mode 100644 index 0000000..d6d4494 --- /dev/null +++ b/casic/face/CasicFaceInterface.h @@ -0,0 +1,91 @@ +#ifndef CASICFACEINTERFACE_H +#define CASICFACEINTERFACE_H + +#include "opencv2/opencv.hpp" +#include "seeta/FaceDetector.h" +#include "seeta/FaceLandmarker.h" +#include "seeta/QualityAssessor.h" +#include "seeta/QualityOfBrightness.h" +#include "seeta/QualityOfClarity.h" +#include "seeta/QualityOfIntegrity.h" +#include "seeta/QualityOfResolution.h" +#include "seeta/QualityOfPoseEx.h" +#include "seeta/FaceAntiSpoofing.h" +#include "seeta/FaceRecognizer.h" + +#include "CasicFaceInfo.h" + +static auto red = CV_RGB(255, 0, 0); +static auto green = CV_RGB(0, 255, 0); +static auto blue = CV_RGB(0, 0, 255); + +namespace casic { + namespace face { + class CasicFaceInterface + { + public: + ~CasicFaceInterface(); + CasicFaceInterface(const CasicFaceInterface&)=delete; + CasicFaceInterface& operator=(const CasicFaceInterface&)=delete; + + static CasicFaceInterface& getInstance() { + static CasicFaceInterface instance; + return instance; + } + + void setDetectorModelPath(std::string detectorModelPath); + void setMarkPts5ModelPath(std::string markPts5ModelPath); + void setPoseModelPath(std::string poseModelPath); + void setFas1stModelPath(std::string fas1stModelPath); + void setFas2ndModelPath(std::string fas2ndModelPath); + void setRecognizerModelPath(std::string recognizerModelPath); + + void setAntiThreshold(float clarity, float reality); + + CasicFaceInfo faceDetect(cv::Mat frame); + CasicFaceInfo faceQuality(CasicFaceInfo faceInfo); + CasicFaceInfo faceAntiSpoofing(CasicFaceInfo faceInfo); + CasicFaceInfo faceFeatureExtract(CasicFaceInfo faceInfo); + float faceSimCalculate(float* feature, float* otherFeature); + + cv::Rect faceDetectByCVCascade(cv::Mat frame); + cv::Rect eyeDetectByCVCascade(cv::Mat frame); + void setMinFaceSize(int minFaceSize); + void setMinEyeSize(int minEyeSize); + private: + CasicFaceInterface(); + + int deviceId = 0; + seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO; + + float clarity = 0.3f; + float reality = 0.3f; + + std::string cvFaceCascadeName = "./model/haarcascade_frontalface_default.xml"; + std::string cvEyeCascadeName = "./model/haarcascade_eye.xml"; + int minFaceSize = 320; + int maxFaceSize = 720; + int minEyeSize = 100; + int maxEyeSize = 600; + + std::string detectorModelPath = "./model/face_detector.csta"; + std::string markPts5ModelPath = "./model/face_landmarker_pts5.csta"; + std::string poseModelPath = "./model/pose_estimation.csta"; + std::string fas1stModelPath = "./model/fas_first.csta"; + std::string fas2ndModelPath = "./model/fas_second.csta"; + std::string recognizerModelPath = "./model/face_recognizer.csta"; + + seeta::FaceDetector * detector = nullptr; + seeta::FaceLandmarker * marker = nullptr; + seeta::QualityOfPoseEx * poseEx = nullptr; + seeta::FaceAntiSpoofing * processor = nullptr; + seeta::FaceRecognizer * recognizer = nullptr; + + cv::CascadeClassifier * cascade; + cv::CascadeClassifier * eyeCascade; + }; + } +} + + +#endif // CASICFACEINTERFACE_H diff --git a/casic/face/casicFace.pri b/casic/face/casicFace.pri new file mode 100644 index 0000000..da337d9 --- /dev/null +++ b/casic/face/casicFace.pri @@ -0,0 +1,9 @@ +HEADERS += $$PWD/CasicFaceInfo.h +HEADERS += $$PWD/CasicFaceInterface.h + +SOURCES += $$PWD/CasicFaceInterface.cpp + +INCLUDEPATH += seeta/ + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600 -lSeetaFaceLandmarker600 -lSeetaFaceAntiSpoofingX600 -lSeetaFaceRecognizer610 -lSeetaQualityAssessor300 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600d -lSeetaFaceLandmarker600d -lSeetaFaceAntiSpoofingX600d -lSeetaFaceRecognizer610d -lSeetaQualityAssessor300d diff --git a/dao/FaceDataImgDao.cpp b/dao/FaceDataImgDao.cpp index 83f22a0..d863752 100644 --- a/dao/FaceDataImgDao.cpp +++ b/dao/FaceDataImgDao.cpp @@ -78,20 +78,14 @@ // 返回结果 QVariantMap result; - // 获取结果集的大小 - query.last(); - int count = query.at() + 1; - - if (count >=1) + if (query.next()) { - query.first(); - result.insert("id", query.value("id").toString()); result.insert("person_id", query.value("person_id").toString()); result.insert("face_image", query.value("face_image").toString()); } - LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[%1][id=%2][%3]").arg(count).arg(query.value("id").toString()).arg(sql).toStdString(); + LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/dao/IrisDataImgDao.cpp b/dao/IrisDataImgDao.cpp index af0d45b..8a0c9af 100644 --- a/dao/IrisDataImgDao.cpp +++ b/dao/IrisDataImgDao.cpp @@ -97,7 +97,7 @@ result.insert("right_image1", query.value("right_image1").toString()); } - LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId:%1][%2]").arg(personId).arg(sql).toStdString(); + LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/device/FaceCameraController.cpp b/device/FaceCameraController.cpp new file mode 100644 index 0000000..bb325a4 --- /dev/null +++ b/device/FaceCameraController.cpp @@ -0,0 +1,60 @@ +#include "FaceCameraController.h" +#include +#include +#include + +FaceCameraController::FaceCameraController(QObject *parent) : QObject(parent) +{ + // 获取定时器, 绑定定时函数 + connect(TimeCounterUtil::getInstance().faceCapCounter, &QTimer::timeout, + this, &FaceCameraController::getOneFaceFrm); +} + +FaceCameraController::~FaceCameraController() +{ + this->closeFaceCamera(); +} + + +void FaceCameraController::openFaceCamera() +{ + this->faceCap = new cv::VideoCapture(SettingConfig::getInstance().FACE_CAMERA_INDEX, cv::CAP_DSHOW); + faceCap->set(cv::CAP_PROP_FRAME_WIDTH, SettingConfig::getInstance().FACE_FRAME_WIDTH); + faceCap->set(cv::CAP_PROP_FRAME_HEIGHT, SettingConfig::getInstance().FACE_FRAME_HEIGHT); + + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]打开相机[%1][%2 * %3]") + .arg(SettingConfig::getInstance().FACE_CAMERA_INDEX) + .arg(SettingConfig::getInstance().FACE_FRAME_WIDTH) + .arg(SettingConfig::getInstance().FACE_FRAME_HEIGHT).toStdString(); + + // 启动定时器 + TimeCounterUtil::getInstance().faceCapCounter->setInterval(SettingConfig::getInstance().FACE_FRAME_INTERVAL); + TimeCounterUtil::getInstance().faceCapCounter->start(); + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]相机开始拍图[%1ms]") + .arg(SettingConfig::getInstance().FACE_FRAME_INTERVAL).toStdString(); +} + +void FaceCameraController::closeFaceCamera() +{ + faceCap->release(); + + delete faceCap; +} + + +void FaceCameraController::getOneFaceFrm() +{ + faceCap->read(faceMat); + + // clone一个mat, 用于界面显示 + cv::Mat faceMatDisp = faceMat.clone(); + QImage imgDisplay = ImageUtil::MatImageToQImage(faceMatDisp); + + // 发送信号用于界面显示 + emit sendImageToDraw(); + + LOG(DEBUG) << " TAKE ONE FACE FRAME " << faceMat.cols << " * " << faceMat.rows; + + // 发送信号用于人脸检测和生成特征值 +// emit sendImageToDetect(faceMat); +} diff --git a/device/FaceCameraController.h b/device/FaceCameraController.h new file mode 100644 index 0000000..c75fcda --- /dev/null +++ b/device/FaceCameraController.h @@ -0,0 +1,40 @@ +#ifndef CAMERACONTROLLER_H +#define CAMERACONTROLLER_H + +#include + +#include "opencv2/opencv.hpp" + +//#include "casic/face/CasicFaceInterface.h" +//#include "process/memory/ProMemory.h" +//#include "process/face/CasicFaceRecState.h" +#include "utils/ImageUtil.h" +#include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" +#include "utils/easyloggingpp/easylogging++.h" + +class FaceCameraController : public QObject +{ + Q_OBJECT +public: + explicit FaceCameraController(QObject *parent = nullptr); + ~FaceCameraController(); + + // 初始化并打开人脸相机 + void openFaceCamera(); + void closeFaceCamera(); + +private: + cv::VideoCapture * faceCap; + + cv::Mat faceMat; + +public slots: + void getOneFaceFrm(); + +signals: + void sendImageToDraw(); + void sendImageToDetect(cv::Mat imgMat); +}; + +#endif // CAMERACONTROLLER_H diff --git a/device/device.pri b/device/device.pri new file mode 100644 index 0000000..99f9438 --- /dev/null +++ b/device/device.pri @@ -0,0 +1,19 @@ + +HEADERS += $$PWD/FaceCameraController.h +HEADERS += $$PWD/face/FaceDetectRegistProcess.h +HEADERS += $$PWD/face/CasicFaceRecState.h + +SOURCES += $$PWD/FaceCameraController.cpp +SOURCES += $$PWD/face/FaceDetectRegistProcess.cpp +SOURCES += $$PWD/face/CasicFaceRecState.cpp + + +#HEADERS += $$PWD/IrisCameraController.h +#HEADERS += $$PWD/IrisCameraCapEventHandler.h +#HEADERS += $$PWD/MotoController.h +#HEADERS += $$PWD/DeviceEnumerator.h + +#SOURCES += $$PWD/IrisCameraController.cpp +#SOURCES += $$PWD/IrisCameraCapEventHandler.cpp +#SOURCES += $$PWD/MotoController.cpp +#SOURCES += $$PWD/DeviceEnumerator.cpp diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/casic/face/CasicFaceInterface.h b/casic/face/CasicFaceInterface.h new file mode 100644 index 0000000..d6d4494 --- /dev/null +++ b/casic/face/CasicFaceInterface.h @@ -0,0 +1,91 @@ +#ifndef CASICFACEINTERFACE_H +#define CASICFACEINTERFACE_H + +#include "opencv2/opencv.hpp" +#include "seeta/FaceDetector.h" +#include "seeta/FaceLandmarker.h" +#include "seeta/QualityAssessor.h" +#include "seeta/QualityOfBrightness.h" +#include "seeta/QualityOfClarity.h" +#include "seeta/QualityOfIntegrity.h" +#include "seeta/QualityOfResolution.h" +#include "seeta/QualityOfPoseEx.h" +#include "seeta/FaceAntiSpoofing.h" +#include "seeta/FaceRecognizer.h" + +#include "CasicFaceInfo.h" + +static auto red = CV_RGB(255, 0, 0); +static auto green = CV_RGB(0, 255, 0); +static auto blue = CV_RGB(0, 0, 255); + +namespace casic { + namespace face { + class CasicFaceInterface + { + public: + ~CasicFaceInterface(); + CasicFaceInterface(const CasicFaceInterface&)=delete; + CasicFaceInterface& operator=(const CasicFaceInterface&)=delete; + + static CasicFaceInterface& getInstance() { + static CasicFaceInterface instance; + return instance; + } + + void setDetectorModelPath(std::string detectorModelPath); + void setMarkPts5ModelPath(std::string markPts5ModelPath); + void setPoseModelPath(std::string poseModelPath); + void setFas1stModelPath(std::string fas1stModelPath); + void setFas2ndModelPath(std::string fas2ndModelPath); + void setRecognizerModelPath(std::string recognizerModelPath); + + void setAntiThreshold(float clarity, float reality); + + CasicFaceInfo faceDetect(cv::Mat frame); + CasicFaceInfo faceQuality(CasicFaceInfo faceInfo); + CasicFaceInfo faceAntiSpoofing(CasicFaceInfo faceInfo); + CasicFaceInfo faceFeatureExtract(CasicFaceInfo faceInfo); + float faceSimCalculate(float* feature, float* otherFeature); + + cv::Rect faceDetectByCVCascade(cv::Mat frame); + cv::Rect eyeDetectByCVCascade(cv::Mat frame); + void setMinFaceSize(int minFaceSize); + void setMinEyeSize(int minEyeSize); + private: + CasicFaceInterface(); + + int deviceId = 0; + seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO; + + float clarity = 0.3f; + float reality = 0.3f; + + std::string cvFaceCascadeName = "./model/haarcascade_frontalface_default.xml"; + std::string cvEyeCascadeName = "./model/haarcascade_eye.xml"; + int minFaceSize = 320; + int maxFaceSize = 720; + int minEyeSize = 100; + int maxEyeSize = 600; + + std::string detectorModelPath = "./model/face_detector.csta"; + std::string markPts5ModelPath = "./model/face_landmarker_pts5.csta"; + std::string poseModelPath = "./model/pose_estimation.csta"; + std::string fas1stModelPath = "./model/fas_first.csta"; + std::string fas2ndModelPath = "./model/fas_second.csta"; + std::string recognizerModelPath = "./model/face_recognizer.csta"; + + seeta::FaceDetector * detector = nullptr; + seeta::FaceLandmarker * marker = nullptr; + seeta::QualityOfPoseEx * poseEx = nullptr; + seeta::FaceAntiSpoofing * processor = nullptr; + seeta::FaceRecognizer * recognizer = nullptr; + + cv::CascadeClassifier * cascade; + cv::CascadeClassifier * eyeCascade; + }; + } +} + + +#endif // CASICFACEINTERFACE_H diff --git a/casic/face/casicFace.pri b/casic/face/casicFace.pri new file mode 100644 index 0000000..da337d9 --- /dev/null +++ b/casic/face/casicFace.pri @@ -0,0 +1,9 @@ +HEADERS += $$PWD/CasicFaceInfo.h +HEADERS += $$PWD/CasicFaceInterface.h + +SOURCES += $$PWD/CasicFaceInterface.cpp + +INCLUDEPATH += seeta/ + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600 -lSeetaFaceLandmarker600 -lSeetaFaceAntiSpoofingX600 -lSeetaFaceRecognizer610 -lSeetaQualityAssessor300 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600d -lSeetaFaceLandmarker600d -lSeetaFaceAntiSpoofingX600d -lSeetaFaceRecognizer610d -lSeetaQualityAssessor300d diff --git a/dao/FaceDataImgDao.cpp b/dao/FaceDataImgDao.cpp index 83f22a0..d863752 100644 --- a/dao/FaceDataImgDao.cpp +++ b/dao/FaceDataImgDao.cpp @@ -78,20 +78,14 @@ // 返回结果 QVariantMap result; - // 获取结果集的大小 - query.last(); - int count = query.at() + 1; - - if (count >=1) + if (query.next()) { - query.first(); - result.insert("id", query.value("id").toString()); result.insert("person_id", query.value("person_id").toString()); result.insert("face_image", query.value("face_image").toString()); } - LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[%1][id=%2][%3]").arg(count).arg(query.value("id").toString()).arg(sql).toStdString(); + LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/dao/IrisDataImgDao.cpp b/dao/IrisDataImgDao.cpp index af0d45b..8a0c9af 100644 --- a/dao/IrisDataImgDao.cpp +++ b/dao/IrisDataImgDao.cpp @@ -97,7 +97,7 @@ result.insert("right_image1", query.value("right_image1").toString()); } - LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId:%1][%2]").arg(personId).arg(sql).toStdString(); + LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/device/FaceCameraController.cpp b/device/FaceCameraController.cpp new file mode 100644 index 0000000..bb325a4 --- /dev/null +++ b/device/FaceCameraController.cpp @@ -0,0 +1,60 @@ +#include "FaceCameraController.h" +#include +#include +#include + +FaceCameraController::FaceCameraController(QObject *parent) : QObject(parent) +{ + // 获取定时器, 绑定定时函数 + connect(TimeCounterUtil::getInstance().faceCapCounter, &QTimer::timeout, + this, &FaceCameraController::getOneFaceFrm); +} + +FaceCameraController::~FaceCameraController() +{ + this->closeFaceCamera(); +} + + +void FaceCameraController::openFaceCamera() +{ + this->faceCap = new cv::VideoCapture(SettingConfig::getInstance().FACE_CAMERA_INDEX, cv::CAP_DSHOW); + faceCap->set(cv::CAP_PROP_FRAME_WIDTH, SettingConfig::getInstance().FACE_FRAME_WIDTH); + faceCap->set(cv::CAP_PROP_FRAME_HEIGHT, SettingConfig::getInstance().FACE_FRAME_HEIGHT); + + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]打开相机[%1][%2 * %3]") + .arg(SettingConfig::getInstance().FACE_CAMERA_INDEX) + .arg(SettingConfig::getInstance().FACE_FRAME_WIDTH) + .arg(SettingConfig::getInstance().FACE_FRAME_HEIGHT).toStdString(); + + // 启动定时器 + TimeCounterUtil::getInstance().faceCapCounter->setInterval(SettingConfig::getInstance().FACE_FRAME_INTERVAL); + TimeCounterUtil::getInstance().faceCapCounter->start(); + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]相机开始拍图[%1ms]") + .arg(SettingConfig::getInstance().FACE_FRAME_INTERVAL).toStdString(); +} + +void FaceCameraController::closeFaceCamera() +{ + faceCap->release(); + + delete faceCap; +} + + +void FaceCameraController::getOneFaceFrm() +{ + faceCap->read(faceMat); + + // clone一个mat, 用于界面显示 + cv::Mat faceMatDisp = faceMat.clone(); + QImage imgDisplay = ImageUtil::MatImageToQImage(faceMatDisp); + + // 发送信号用于界面显示 + emit sendImageToDraw(); + + LOG(DEBUG) << " TAKE ONE FACE FRAME " << faceMat.cols << " * " << faceMat.rows; + + // 发送信号用于人脸检测和生成特征值 +// emit sendImageToDetect(faceMat); +} diff --git a/device/FaceCameraController.h b/device/FaceCameraController.h new file mode 100644 index 0000000..c75fcda --- /dev/null +++ b/device/FaceCameraController.h @@ -0,0 +1,40 @@ +#ifndef CAMERACONTROLLER_H +#define CAMERACONTROLLER_H + +#include + +#include "opencv2/opencv.hpp" + +//#include "casic/face/CasicFaceInterface.h" +//#include "process/memory/ProMemory.h" +//#include "process/face/CasicFaceRecState.h" +#include "utils/ImageUtil.h" +#include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" +#include "utils/easyloggingpp/easylogging++.h" + +class FaceCameraController : public QObject +{ + Q_OBJECT +public: + explicit FaceCameraController(QObject *parent = nullptr); + ~FaceCameraController(); + + // 初始化并打开人脸相机 + void openFaceCamera(); + void closeFaceCamera(); + +private: + cv::VideoCapture * faceCap; + + cv::Mat faceMat; + +public slots: + void getOneFaceFrm(); + +signals: + void sendImageToDraw(); + void sendImageToDetect(cv::Mat imgMat); +}; + +#endif // CAMERACONTROLLER_H diff --git a/device/device.pri b/device/device.pri new file mode 100644 index 0000000..99f9438 --- /dev/null +++ b/device/device.pri @@ -0,0 +1,19 @@ + +HEADERS += $$PWD/FaceCameraController.h +HEADERS += $$PWD/face/FaceDetectRegistProcess.h +HEADERS += $$PWD/face/CasicFaceRecState.h + +SOURCES += $$PWD/FaceCameraController.cpp +SOURCES += $$PWD/face/FaceDetectRegistProcess.cpp +SOURCES += $$PWD/face/CasicFaceRecState.cpp + + +#HEADERS += $$PWD/IrisCameraController.h +#HEADERS += $$PWD/IrisCameraCapEventHandler.h +#HEADERS += $$PWD/MotoController.h +#HEADERS += $$PWD/DeviceEnumerator.h + +#SOURCES += $$PWD/IrisCameraController.cpp +#SOURCES += $$PWD/IrisCameraCapEventHandler.cpp +#SOURCES += $$PWD/MotoController.cpp +#SOURCES += $$PWD/DeviceEnumerator.cpp diff --git a/device/face/CasicFaceRecState.cpp b/device/face/CasicFaceRecState.cpp new file mode 100644 index 0000000..3ba8470 --- /dev/null +++ b/device/face/CasicFaceRecState.cpp @@ -0,0 +1,6 @@ +#include "CasicFaceRecState.h" + +CasicFaceRecState::CasicFaceRecState() +{ + +} diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/casic/face/CasicFaceInterface.h b/casic/face/CasicFaceInterface.h new file mode 100644 index 0000000..d6d4494 --- /dev/null +++ b/casic/face/CasicFaceInterface.h @@ -0,0 +1,91 @@ +#ifndef CASICFACEINTERFACE_H +#define CASICFACEINTERFACE_H + +#include "opencv2/opencv.hpp" +#include "seeta/FaceDetector.h" +#include "seeta/FaceLandmarker.h" +#include "seeta/QualityAssessor.h" +#include "seeta/QualityOfBrightness.h" +#include "seeta/QualityOfClarity.h" +#include "seeta/QualityOfIntegrity.h" +#include "seeta/QualityOfResolution.h" +#include "seeta/QualityOfPoseEx.h" +#include "seeta/FaceAntiSpoofing.h" +#include "seeta/FaceRecognizer.h" + +#include "CasicFaceInfo.h" + +static auto red = CV_RGB(255, 0, 0); +static auto green = CV_RGB(0, 255, 0); +static auto blue = CV_RGB(0, 0, 255); + +namespace casic { + namespace face { + class CasicFaceInterface + { + public: + ~CasicFaceInterface(); + CasicFaceInterface(const CasicFaceInterface&)=delete; + CasicFaceInterface& operator=(const CasicFaceInterface&)=delete; + + static CasicFaceInterface& getInstance() { + static CasicFaceInterface instance; + return instance; + } + + void setDetectorModelPath(std::string detectorModelPath); + void setMarkPts5ModelPath(std::string markPts5ModelPath); + void setPoseModelPath(std::string poseModelPath); + void setFas1stModelPath(std::string fas1stModelPath); + void setFas2ndModelPath(std::string fas2ndModelPath); + void setRecognizerModelPath(std::string recognizerModelPath); + + void setAntiThreshold(float clarity, float reality); + + CasicFaceInfo faceDetect(cv::Mat frame); + CasicFaceInfo faceQuality(CasicFaceInfo faceInfo); + CasicFaceInfo faceAntiSpoofing(CasicFaceInfo faceInfo); + CasicFaceInfo faceFeatureExtract(CasicFaceInfo faceInfo); + float faceSimCalculate(float* feature, float* otherFeature); + + cv::Rect faceDetectByCVCascade(cv::Mat frame); + cv::Rect eyeDetectByCVCascade(cv::Mat frame); + void setMinFaceSize(int minFaceSize); + void setMinEyeSize(int minEyeSize); + private: + CasicFaceInterface(); + + int deviceId = 0; + seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO; + + float clarity = 0.3f; + float reality = 0.3f; + + std::string cvFaceCascadeName = "./model/haarcascade_frontalface_default.xml"; + std::string cvEyeCascadeName = "./model/haarcascade_eye.xml"; + int minFaceSize = 320; + int maxFaceSize = 720; + int minEyeSize = 100; + int maxEyeSize = 600; + + std::string detectorModelPath = "./model/face_detector.csta"; + std::string markPts5ModelPath = "./model/face_landmarker_pts5.csta"; + std::string poseModelPath = "./model/pose_estimation.csta"; + std::string fas1stModelPath = "./model/fas_first.csta"; + std::string fas2ndModelPath = "./model/fas_second.csta"; + std::string recognizerModelPath = "./model/face_recognizer.csta"; + + seeta::FaceDetector * detector = nullptr; + seeta::FaceLandmarker * marker = nullptr; + seeta::QualityOfPoseEx * poseEx = nullptr; + seeta::FaceAntiSpoofing * processor = nullptr; + seeta::FaceRecognizer * recognizer = nullptr; + + cv::CascadeClassifier * cascade; + cv::CascadeClassifier * eyeCascade; + }; + } +} + + +#endif // CASICFACEINTERFACE_H diff --git a/casic/face/casicFace.pri b/casic/face/casicFace.pri new file mode 100644 index 0000000..da337d9 --- /dev/null +++ b/casic/face/casicFace.pri @@ -0,0 +1,9 @@ +HEADERS += $$PWD/CasicFaceInfo.h +HEADERS += $$PWD/CasicFaceInterface.h + +SOURCES += $$PWD/CasicFaceInterface.cpp + +INCLUDEPATH += seeta/ + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600 -lSeetaFaceLandmarker600 -lSeetaFaceAntiSpoofingX600 -lSeetaFaceRecognizer610 -lSeetaQualityAssessor300 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600d -lSeetaFaceLandmarker600d -lSeetaFaceAntiSpoofingX600d -lSeetaFaceRecognizer610d -lSeetaQualityAssessor300d diff --git a/dao/FaceDataImgDao.cpp b/dao/FaceDataImgDao.cpp index 83f22a0..d863752 100644 --- a/dao/FaceDataImgDao.cpp +++ b/dao/FaceDataImgDao.cpp @@ -78,20 +78,14 @@ // 返回结果 QVariantMap result; - // 获取结果集的大小 - query.last(); - int count = query.at() + 1; - - if (count >=1) + if (query.next()) { - query.first(); - result.insert("id", query.value("id").toString()); result.insert("person_id", query.value("person_id").toString()); result.insert("face_image", query.value("face_image").toString()); } - LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[%1][id=%2][%3]").arg(count).arg(query.value("id").toString()).arg(sql).toStdString(); + LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/dao/IrisDataImgDao.cpp b/dao/IrisDataImgDao.cpp index af0d45b..8a0c9af 100644 --- a/dao/IrisDataImgDao.cpp +++ b/dao/IrisDataImgDao.cpp @@ -97,7 +97,7 @@ result.insert("right_image1", query.value("right_image1").toString()); } - LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId:%1][%2]").arg(personId).arg(sql).toStdString(); + LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/device/FaceCameraController.cpp b/device/FaceCameraController.cpp new file mode 100644 index 0000000..bb325a4 --- /dev/null +++ b/device/FaceCameraController.cpp @@ -0,0 +1,60 @@ +#include "FaceCameraController.h" +#include +#include +#include + +FaceCameraController::FaceCameraController(QObject *parent) : QObject(parent) +{ + // 获取定时器, 绑定定时函数 + connect(TimeCounterUtil::getInstance().faceCapCounter, &QTimer::timeout, + this, &FaceCameraController::getOneFaceFrm); +} + +FaceCameraController::~FaceCameraController() +{ + this->closeFaceCamera(); +} + + +void FaceCameraController::openFaceCamera() +{ + this->faceCap = new cv::VideoCapture(SettingConfig::getInstance().FACE_CAMERA_INDEX, cv::CAP_DSHOW); + faceCap->set(cv::CAP_PROP_FRAME_WIDTH, SettingConfig::getInstance().FACE_FRAME_WIDTH); + faceCap->set(cv::CAP_PROP_FRAME_HEIGHT, SettingConfig::getInstance().FACE_FRAME_HEIGHT); + + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]打开相机[%1][%2 * %3]") + .arg(SettingConfig::getInstance().FACE_CAMERA_INDEX) + .arg(SettingConfig::getInstance().FACE_FRAME_WIDTH) + .arg(SettingConfig::getInstance().FACE_FRAME_HEIGHT).toStdString(); + + // 启动定时器 + TimeCounterUtil::getInstance().faceCapCounter->setInterval(SettingConfig::getInstance().FACE_FRAME_INTERVAL); + TimeCounterUtil::getInstance().faceCapCounter->start(); + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]相机开始拍图[%1ms]") + .arg(SettingConfig::getInstance().FACE_FRAME_INTERVAL).toStdString(); +} + +void FaceCameraController::closeFaceCamera() +{ + faceCap->release(); + + delete faceCap; +} + + +void FaceCameraController::getOneFaceFrm() +{ + faceCap->read(faceMat); + + // clone一个mat, 用于界面显示 + cv::Mat faceMatDisp = faceMat.clone(); + QImage imgDisplay = ImageUtil::MatImageToQImage(faceMatDisp); + + // 发送信号用于界面显示 + emit sendImageToDraw(); + + LOG(DEBUG) << " TAKE ONE FACE FRAME " << faceMat.cols << " * " << faceMat.rows; + + // 发送信号用于人脸检测和生成特征值 +// emit sendImageToDetect(faceMat); +} diff --git a/device/FaceCameraController.h b/device/FaceCameraController.h new file mode 100644 index 0000000..c75fcda --- /dev/null +++ b/device/FaceCameraController.h @@ -0,0 +1,40 @@ +#ifndef CAMERACONTROLLER_H +#define CAMERACONTROLLER_H + +#include + +#include "opencv2/opencv.hpp" + +//#include "casic/face/CasicFaceInterface.h" +//#include "process/memory/ProMemory.h" +//#include "process/face/CasicFaceRecState.h" +#include "utils/ImageUtil.h" +#include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" +#include "utils/easyloggingpp/easylogging++.h" + +class FaceCameraController : public QObject +{ + Q_OBJECT +public: + explicit FaceCameraController(QObject *parent = nullptr); + ~FaceCameraController(); + + // 初始化并打开人脸相机 + void openFaceCamera(); + void closeFaceCamera(); + +private: + cv::VideoCapture * faceCap; + + cv::Mat faceMat; + +public slots: + void getOneFaceFrm(); + +signals: + void sendImageToDraw(); + void sendImageToDetect(cv::Mat imgMat); +}; + +#endif // CAMERACONTROLLER_H diff --git a/device/device.pri b/device/device.pri new file mode 100644 index 0000000..99f9438 --- /dev/null +++ b/device/device.pri @@ -0,0 +1,19 @@ + +HEADERS += $$PWD/FaceCameraController.h +HEADERS += $$PWD/face/FaceDetectRegistProcess.h +HEADERS += $$PWD/face/CasicFaceRecState.h + +SOURCES += $$PWD/FaceCameraController.cpp +SOURCES += $$PWD/face/FaceDetectRegistProcess.cpp +SOURCES += $$PWD/face/CasicFaceRecState.cpp + + +#HEADERS += $$PWD/IrisCameraController.h +#HEADERS += $$PWD/IrisCameraCapEventHandler.h +#HEADERS += $$PWD/MotoController.h +#HEADERS += $$PWD/DeviceEnumerator.h + +#SOURCES += $$PWD/IrisCameraController.cpp +#SOURCES += $$PWD/IrisCameraCapEventHandler.cpp +#SOURCES += $$PWD/MotoController.cpp +#SOURCES += $$PWD/DeviceEnumerator.cpp diff --git a/device/face/CasicFaceRecState.cpp b/device/face/CasicFaceRecState.cpp new file mode 100644 index 0000000..3ba8470 --- /dev/null +++ b/device/face/CasicFaceRecState.cpp @@ -0,0 +1,6 @@ +#include "CasicFaceRecState.h" + +CasicFaceRecState::CasicFaceRecState() +{ + +} diff --git a/device/face/CasicFaceRecState.h b/device/face/CasicFaceRecState.h new file mode 100644 index 0000000..999486e --- /dev/null +++ b/device/face/CasicFaceRecState.h @@ -0,0 +1,41 @@ +#ifndef CASICFACERECSTATE_H +#define CASICFACERECSTATE_H + +#include +#include "casic/face/CasicFaceInfo.h" + +class CasicFaceRecState : public QObject +{ +public: + ~CasicFaceRecState() {}; + CasicFaceRecState(const CasicFaceRecState&)=delete; + CasicFaceRecState& operator=(const CasicFaceRecState&)=delete; + + static CasicFaceRecState& getInstance() { + static CasicFaceRecState instance; + return instance; + } + + void initRecognize(); + std::string toString(); + QJsonObject toJSON(); + + std::string recoginzeId; // 识别过程id + qint64 timeStamp = 0; // 识别开始时间戳 + qint64 timeStampSucc = 0; // 识别成功时的时间戳 + + CasicFaceInfo * faceInfo; // 人脸信息 + QString imgBase64; // 人脸的base64码数据, 用于存库 + + qint8 tryCount = 0; // 识别尝试次数 + qint8 noFaceCount = 0; // 连续没有找到人脸次数 + float recogTimeLast = 0.0; // 识别成功耗时 + +private: + CasicFaceRecState(); + +signals: + +}; + +#endif // CASICFACERECSTATE_H diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/casic/face/CasicFaceInterface.h b/casic/face/CasicFaceInterface.h new file mode 100644 index 0000000..d6d4494 --- /dev/null +++ b/casic/face/CasicFaceInterface.h @@ -0,0 +1,91 @@ +#ifndef CASICFACEINTERFACE_H +#define CASICFACEINTERFACE_H + +#include "opencv2/opencv.hpp" +#include "seeta/FaceDetector.h" +#include "seeta/FaceLandmarker.h" +#include "seeta/QualityAssessor.h" +#include "seeta/QualityOfBrightness.h" +#include "seeta/QualityOfClarity.h" +#include "seeta/QualityOfIntegrity.h" +#include "seeta/QualityOfResolution.h" +#include "seeta/QualityOfPoseEx.h" +#include "seeta/FaceAntiSpoofing.h" +#include "seeta/FaceRecognizer.h" + +#include "CasicFaceInfo.h" + +static auto red = CV_RGB(255, 0, 0); +static auto green = CV_RGB(0, 255, 0); +static auto blue = CV_RGB(0, 0, 255); + +namespace casic { + namespace face { + class CasicFaceInterface + { + public: + ~CasicFaceInterface(); + CasicFaceInterface(const CasicFaceInterface&)=delete; + CasicFaceInterface& operator=(const CasicFaceInterface&)=delete; + + static CasicFaceInterface& getInstance() { + static CasicFaceInterface instance; + return instance; + } + + void setDetectorModelPath(std::string detectorModelPath); + void setMarkPts5ModelPath(std::string markPts5ModelPath); + void setPoseModelPath(std::string poseModelPath); + void setFas1stModelPath(std::string fas1stModelPath); + void setFas2ndModelPath(std::string fas2ndModelPath); + void setRecognizerModelPath(std::string recognizerModelPath); + + void setAntiThreshold(float clarity, float reality); + + CasicFaceInfo faceDetect(cv::Mat frame); + CasicFaceInfo faceQuality(CasicFaceInfo faceInfo); + CasicFaceInfo faceAntiSpoofing(CasicFaceInfo faceInfo); + CasicFaceInfo faceFeatureExtract(CasicFaceInfo faceInfo); + float faceSimCalculate(float* feature, float* otherFeature); + + cv::Rect faceDetectByCVCascade(cv::Mat frame); + cv::Rect eyeDetectByCVCascade(cv::Mat frame); + void setMinFaceSize(int minFaceSize); + void setMinEyeSize(int minEyeSize); + private: + CasicFaceInterface(); + + int deviceId = 0; + seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO; + + float clarity = 0.3f; + float reality = 0.3f; + + std::string cvFaceCascadeName = "./model/haarcascade_frontalface_default.xml"; + std::string cvEyeCascadeName = "./model/haarcascade_eye.xml"; + int minFaceSize = 320; + int maxFaceSize = 720; + int minEyeSize = 100; + int maxEyeSize = 600; + + std::string detectorModelPath = "./model/face_detector.csta"; + std::string markPts5ModelPath = "./model/face_landmarker_pts5.csta"; + std::string poseModelPath = "./model/pose_estimation.csta"; + std::string fas1stModelPath = "./model/fas_first.csta"; + std::string fas2ndModelPath = "./model/fas_second.csta"; + std::string recognizerModelPath = "./model/face_recognizer.csta"; + + seeta::FaceDetector * detector = nullptr; + seeta::FaceLandmarker * marker = nullptr; + seeta::QualityOfPoseEx * poseEx = nullptr; + seeta::FaceAntiSpoofing * processor = nullptr; + seeta::FaceRecognizer * recognizer = nullptr; + + cv::CascadeClassifier * cascade; + cv::CascadeClassifier * eyeCascade; + }; + } +} + + +#endif // CASICFACEINTERFACE_H diff --git a/casic/face/casicFace.pri b/casic/face/casicFace.pri new file mode 100644 index 0000000..da337d9 --- /dev/null +++ b/casic/face/casicFace.pri @@ -0,0 +1,9 @@ +HEADERS += $$PWD/CasicFaceInfo.h +HEADERS += $$PWD/CasicFaceInterface.h + +SOURCES += $$PWD/CasicFaceInterface.cpp + +INCLUDEPATH += seeta/ + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600 -lSeetaFaceLandmarker600 -lSeetaFaceAntiSpoofingX600 -lSeetaFaceRecognizer610 -lSeetaQualityAssessor300 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600d -lSeetaFaceLandmarker600d -lSeetaFaceAntiSpoofingX600d -lSeetaFaceRecognizer610d -lSeetaQualityAssessor300d diff --git a/dao/FaceDataImgDao.cpp b/dao/FaceDataImgDao.cpp index 83f22a0..d863752 100644 --- a/dao/FaceDataImgDao.cpp +++ b/dao/FaceDataImgDao.cpp @@ -78,20 +78,14 @@ // 返回结果 QVariantMap result; - // 获取结果集的大小 - query.last(); - int count = query.at() + 1; - - if (count >=1) + if (query.next()) { - query.first(); - result.insert("id", query.value("id").toString()); result.insert("person_id", query.value("person_id").toString()); result.insert("face_image", query.value("face_image").toString()); } - LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[%1][id=%2][%3]").arg(count).arg(query.value("id").toString()).arg(sql).toStdString(); + LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/dao/IrisDataImgDao.cpp b/dao/IrisDataImgDao.cpp index af0d45b..8a0c9af 100644 --- a/dao/IrisDataImgDao.cpp +++ b/dao/IrisDataImgDao.cpp @@ -97,7 +97,7 @@ result.insert("right_image1", query.value("right_image1").toString()); } - LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId:%1][%2]").arg(personId).arg(sql).toStdString(); + LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/device/FaceCameraController.cpp b/device/FaceCameraController.cpp new file mode 100644 index 0000000..bb325a4 --- /dev/null +++ b/device/FaceCameraController.cpp @@ -0,0 +1,60 @@ +#include "FaceCameraController.h" +#include +#include +#include + +FaceCameraController::FaceCameraController(QObject *parent) : QObject(parent) +{ + // 获取定时器, 绑定定时函数 + connect(TimeCounterUtil::getInstance().faceCapCounter, &QTimer::timeout, + this, &FaceCameraController::getOneFaceFrm); +} + +FaceCameraController::~FaceCameraController() +{ + this->closeFaceCamera(); +} + + +void FaceCameraController::openFaceCamera() +{ + this->faceCap = new cv::VideoCapture(SettingConfig::getInstance().FACE_CAMERA_INDEX, cv::CAP_DSHOW); + faceCap->set(cv::CAP_PROP_FRAME_WIDTH, SettingConfig::getInstance().FACE_FRAME_WIDTH); + faceCap->set(cv::CAP_PROP_FRAME_HEIGHT, SettingConfig::getInstance().FACE_FRAME_HEIGHT); + + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]打开相机[%1][%2 * %3]") + .arg(SettingConfig::getInstance().FACE_CAMERA_INDEX) + .arg(SettingConfig::getInstance().FACE_FRAME_WIDTH) + .arg(SettingConfig::getInstance().FACE_FRAME_HEIGHT).toStdString(); + + // 启动定时器 + TimeCounterUtil::getInstance().faceCapCounter->setInterval(SettingConfig::getInstance().FACE_FRAME_INTERVAL); + TimeCounterUtil::getInstance().faceCapCounter->start(); + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]相机开始拍图[%1ms]") + .arg(SettingConfig::getInstance().FACE_FRAME_INTERVAL).toStdString(); +} + +void FaceCameraController::closeFaceCamera() +{ + faceCap->release(); + + delete faceCap; +} + + +void FaceCameraController::getOneFaceFrm() +{ + faceCap->read(faceMat); + + // clone一个mat, 用于界面显示 + cv::Mat faceMatDisp = faceMat.clone(); + QImage imgDisplay = ImageUtil::MatImageToQImage(faceMatDisp); + + // 发送信号用于界面显示 + emit sendImageToDraw(); + + LOG(DEBUG) << " TAKE ONE FACE FRAME " << faceMat.cols << " * " << faceMat.rows; + + // 发送信号用于人脸检测和生成特征值 +// emit sendImageToDetect(faceMat); +} diff --git a/device/FaceCameraController.h b/device/FaceCameraController.h new file mode 100644 index 0000000..c75fcda --- /dev/null +++ b/device/FaceCameraController.h @@ -0,0 +1,40 @@ +#ifndef CAMERACONTROLLER_H +#define CAMERACONTROLLER_H + +#include + +#include "opencv2/opencv.hpp" + +//#include "casic/face/CasicFaceInterface.h" +//#include "process/memory/ProMemory.h" +//#include "process/face/CasicFaceRecState.h" +#include "utils/ImageUtil.h" +#include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" +#include "utils/easyloggingpp/easylogging++.h" + +class FaceCameraController : public QObject +{ + Q_OBJECT +public: + explicit FaceCameraController(QObject *parent = nullptr); + ~FaceCameraController(); + + // 初始化并打开人脸相机 + void openFaceCamera(); + void closeFaceCamera(); + +private: + cv::VideoCapture * faceCap; + + cv::Mat faceMat; + +public slots: + void getOneFaceFrm(); + +signals: + void sendImageToDraw(); + void sendImageToDetect(cv::Mat imgMat); +}; + +#endif // CAMERACONTROLLER_H diff --git a/device/device.pri b/device/device.pri new file mode 100644 index 0000000..99f9438 --- /dev/null +++ b/device/device.pri @@ -0,0 +1,19 @@ + +HEADERS += $$PWD/FaceCameraController.h +HEADERS += $$PWD/face/FaceDetectRegistProcess.h +HEADERS += $$PWD/face/CasicFaceRecState.h + +SOURCES += $$PWD/FaceCameraController.cpp +SOURCES += $$PWD/face/FaceDetectRegistProcess.cpp +SOURCES += $$PWD/face/CasicFaceRecState.cpp + + +#HEADERS += $$PWD/IrisCameraController.h +#HEADERS += $$PWD/IrisCameraCapEventHandler.h +#HEADERS += $$PWD/MotoController.h +#HEADERS += $$PWD/DeviceEnumerator.h + +#SOURCES += $$PWD/IrisCameraController.cpp +#SOURCES += $$PWD/IrisCameraCapEventHandler.cpp +#SOURCES += $$PWD/MotoController.cpp +#SOURCES += $$PWD/DeviceEnumerator.cpp diff --git a/device/face/CasicFaceRecState.cpp b/device/face/CasicFaceRecState.cpp new file mode 100644 index 0000000..3ba8470 --- /dev/null +++ b/device/face/CasicFaceRecState.cpp @@ -0,0 +1,6 @@ +#include "CasicFaceRecState.h" + +CasicFaceRecState::CasicFaceRecState() +{ + +} diff --git a/device/face/CasicFaceRecState.h b/device/face/CasicFaceRecState.h new file mode 100644 index 0000000..999486e --- /dev/null +++ b/device/face/CasicFaceRecState.h @@ -0,0 +1,41 @@ +#ifndef CASICFACERECSTATE_H +#define CASICFACERECSTATE_H + +#include +#include "casic/face/CasicFaceInfo.h" + +class CasicFaceRecState : public QObject +{ +public: + ~CasicFaceRecState() {}; + CasicFaceRecState(const CasicFaceRecState&)=delete; + CasicFaceRecState& operator=(const CasicFaceRecState&)=delete; + + static CasicFaceRecState& getInstance() { + static CasicFaceRecState instance; + return instance; + } + + void initRecognize(); + std::string toString(); + QJsonObject toJSON(); + + std::string recoginzeId; // 识别过程id + qint64 timeStamp = 0; // 识别开始时间戳 + qint64 timeStampSucc = 0; // 识别成功时的时间戳 + + CasicFaceInfo * faceInfo; // 人脸信息 + QString imgBase64; // 人脸的base64码数据, 用于存库 + + qint8 tryCount = 0; // 识别尝试次数 + qint8 noFaceCount = 0; // 连续没有找到人脸次数 + float recogTimeLast = 0.0; // 识别成功耗时 + +private: + CasicFaceRecState(); + +signals: + +}; + +#endif // CASICFACERECSTATE_H diff --git a/device/face/FaceDetectRegistProcess.cpp b/device/face/FaceDetectRegistProcess.cpp new file mode 100644 index 0000000..3a6748d --- /dev/null +++ b/device/face/FaceDetectRegistProcess.cpp @@ -0,0 +1,70 @@ +#include "FaceDetectRegistProcess.h" + +FaceDetectRegistProcess::FaceDetectRegistProcess(QObject *parent) : QObject(parent) +{ + +} + +void FaceDetectRegistProcess::faceDetect(cv::Mat faceMat) +{ + // 如果已经在人脸检测工作中则退出 + if (FACE_DETECT_FLAG == true) + { + LOG(DEBUG) << "ALREADY IN FACE DETECT PROCESS"; + return; + } + + // 开始人脸检测 + FACE_DETECT_FLAG = true; + LOG(DEBUG) << "START FACE DETECT"; + + // 调用人脸检测算法 + CasicFaceInfo faceInfo = casic::face::CasicFaceInterface::getInstance().faceDetect(faceMat); + if (faceInfo.hasFace == false) + { + // 没有找到人脸进行如下处理 + + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; + return; + } + + // 继续进行后面的步骤 + // 开始人脸活体检测, 提高活体检测的阈值到0.3/0.6 + casic::face::CasicFaceInterface::getInstance().setAntiThreshold(0.3, 0.6); + faceInfo = casic::face::CasicFaceInterface::getInstance().faceAntiSpoofing(faceInfo); + // 活体检测判断为假脸 + if (faceInfo.antiStatus != seeta::FaceAntiSpoofing::Status::REAL) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸活体检测未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 判定为真实人脸, 开始质量评估 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceQuality(faceInfo); + + // 质量评估不为HIGH则返回 + if (faceInfo.quality.level != seeta::QualityLevel::HIGH) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸质量评估未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 调用算法提取特征值, 1024个字节的float数组 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceFeatureExtract(faceInfo); + + LOG(DEBUG) << QString("[faceDetect]特征提取成功").toStdString(); + + // 发送信号去进行比对, 执行后续业务逻辑 + emit this->extractFeatureSuccess(CasicFaceRecState::getInstance()); + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; +} diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/casic/face/CasicFaceInterface.h b/casic/face/CasicFaceInterface.h new file mode 100644 index 0000000..d6d4494 --- /dev/null +++ b/casic/face/CasicFaceInterface.h @@ -0,0 +1,91 @@ +#ifndef CASICFACEINTERFACE_H +#define CASICFACEINTERFACE_H + +#include "opencv2/opencv.hpp" +#include "seeta/FaceDetector.h" +#include "seeta/FaceLandmarker.h" +#include "seeta/QualityAssessor.h" +#include "seeta/QualityOfBrightness.h" +#include "seeta/QualityOfClarity.h" +#include "seeta/QualityOfIntegrity.h" +#include "seeta/QualityOfResolution.h" +#include "seeta/QualityOfPoseEx.h" +#include "seeta/FaceAntiSpoofing.h" +#include "seeta/FaceRecognizer.h" + +#include "CasicFaceInfo.h" + +static auto red = CV_RGB(255, 0, 0); +static auto green = CV_RGB(0, 255, 0); +static auto blue = CV_RGB(0, 0, 255); + +namespace casic { + namespace face { + class CasicFaceInterface + { + public: + ~CasicFaceInterface(); + CasicFaceInterface(const CasicFaceInterface&)=delete; + CasicFaceInterface& operator=(const CasicFaceInterface&)=delete; + + static CasicFaceInterface& getInstance() { + static CasicFaceInterface instance; + return instance; + } + + void setDetectorModelPath(std::string detectorModelPath); + void setMarkPts5ModelPath(std::string markPts5ModelPath); + void setPoseModelPath(std::string poseModelPath); + void setFas1stModelPath(std::string fas1stModelPath); + void setFas2ndModelPath(std::string fas2ndModelPath); + void setRecognizerModelPath(std::string recognizerModelPath); + + void setAntiThreshold(float clarity, float reality); + + CasicFaceInfo faceDetect(cv::Mat frame); + CasicFaceInfo faceQuality(CasicFaceInfo faceInfo); + CasicFaceInfo faceAntiSpoofing(CasicFaceInfo faceInfo); + CasicFaceInfo faceFeatureExtract(CasicFaceInfo faceInfo); + float faceSimCalculate(float* feature, float* otherFeature); + + cv::Rect faceDetectByCVCascade(cv::Mat frame); + cv::Rect eyeDetectByCVCascade(cv::Mat frame); + void setMinFaceSize(int minFaceSize); + void setMinEyeSize(int minEyeSize); + private: + CasicFaceInterface(); + + int deviceId = 0; + seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO; + + float clarity = 0.3f; + float reality = 0.3f; + + std::string cvFaceCascadeName = "./model/haarcascade_frontalface_default.xml"; + std::string cvEyeCascadeName = "./model/haarcascade_eye.xml"; + int minFaceSize = 320; + int maxFaceSize = 720; + int minEyeSize = 100; + int maxEyeSize = 600; + + std::string detectorModelPath = "./model/face_detector.csta"; + std::string markPts5ModelPath = "./model/face_landmarker_pts5.csta"; + std::string poseModelPath = "./model/pose_estimation.csta"; + std::string fas1stModelPath = "./model/fas_first.csta"; + std::string fas2ndModelPath = "./model/fas_second.csta"; + std::string recognizerModelPath = "./model/face_recognizer.csta"; + + seeta::FaceDetector * detector = nullptr; + seeta::FaceLandmarker * marker = nullptr; + seeta::QualityOfPoseEx * poseEx = nullptr; + seeta::FaceAntiSpoofing * processor = nullptr; + seeta::FaceRecognizer * recognizer = nullptr; + + cv::CascadeClassifier * cascade; + cv::CascadeClassifier * eyeCascade; + }; + } +} + + +#endif // CASICFACEINTERFACE_H diff --git a/casic/face/casicFace.pri b/casic/face/casicFace.pri new file mode 100644 index 0000000..da337d9 --- /dev/null +++ b/casic/face/casicFace.pri @@ -0,0 +1,9 @@ +HEADERS += $$PWD/CasicFaceInfo.h +HEADERS += $$PWD/CasicFaceInterface.h + +SOURCES += $$PWD/CasicFaceInterface.cpp + +INCLUDEPATH += seeta/ + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600 -lSeetaFaceLandmarker600 -lSeetaFaceAntiSpoofingX600 -lSeetaFaceRecognizer610 -lSeetaQualityAssessor300 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600d -lSeetaFaceLandmarker600d -lSeetaFaceAntiSpoofingX600d -lSeetaFaceRecognizer610d -lSeetaQualityAssessor300d diff --git a/dao/FaceDataImgDao.cpp b/dao/FaceDataImgDao.cpp index 83f22a0..d863752 100644 --- a/dao/FaceDataImgDao.cpp +++ b/dao/FaceDataImgDao.cpp @@ -78,20 +78,14 @@ // 返回结果 QVariantMap result; - // 获取结果集的大小 - query.last(); - int count = query.at() + 1; - - if (count >=1) + if (query.next()) { - query.first(); - result.insert("id", query.value("id").toString()); result.insert("person_id", query.value("person_id").toString()); result.insert("face_image", query.value("face_image").toString()); } - LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[%1][id=%2][%3]").arg(count).arg(query.value("id").toString()).arg(sql).toStdString(); + LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/dao/IrisDataImgDao.cpp b/dao/IrisDataImgDao.cpp index af0d45b..8a0c9af 100644 --- a/dao/IrisDataImgDao.cpp +++ b/dao/IrisDataImgDao.cpp @@ -97,7 +97,7 @@ result.insert("right_image1", query.value("right_image1").toString()); } - LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId:%1][%2]").arg(personId).arg(sql).toStdString(); + LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/device/FaceCameraController.cpp b/device/FaceCameraController.cpp new file mode 100644 index 0000000..bb325a4 --- /dev/null +++ b/device/FaceCameraController.cpp @@ -0,0 +1,60 @@ +#include "FaceCameraController.h" +#include +#include +#include + +FaceCameraController::FaceCameraController(QObject *parent) : QObject(parent) +{ + // 获取定时器, 绑定定时函数 + connect(TimeCounterUtil::getInstance().faceCapCounter, &QTimer::timeout, + this, &FaceCameraController::getOneFaceFrm); +} + +FaceCameraController::~FaceCameraController() +{ + this->closeFaceCamera(); +} + + +void FaceCameraController::openFaceCamera() +{ + this->faceCap = new cv::VideoCapture(SettingConfig::getInstance().FACE_CAMERA_INDEX, cv::CAP_DSHOW); + faceCap->set(cv::CAP_PROP_FRAME_WIDTH, SettingConfig::getInstance().FACE_FRAME_WIDTH); + faceCap->set(cv::CAP_PROP_FRAME_HEIGHT, SettingConfig::getInstance().FACE_FRAME_HEIGHT); + + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]打开相机[%1][%2 * %3]") + .arg(SettingConfig::getInstance().FACE_CAMERA_INDEX) + .arg(SettingConfig::getInstance().FACE_FRAME_WIDTH) + .arg(SettingConfig::getInstance().FACE_FRAME_HEIGHT).toStdString(); + + // 启动定时器 + TimeCounterUtil::getInstance().faceCapCounter->setInterval(SettingConfig::getInstance().FACE_FRAME_INTERVAL); + TimeCounterUtil::getInstance().faceCapCounter->start(); + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]相机开始拍图[%1ms]") + .arg(SettingConfig::getInstance().FACE_FRAME_INTERVAL).toStdString(); +} + +void FaceCameraController::closeFaceCamera() +{ + faceCap->release(); + + delete faceCap; +} + + +void FaceCameraController::getOneFaceFrm() +{ + faceCap->read(faceMat); + + // clone一个mat, 用于界面显示 + cv::Mat faceMatDisp = faceMat.clone(); + QImage imgDisplay = ImageUtil::MatImageToQImage(faceMatDisp); + + // 发送信号用于界面显示 + emit sendImageToDraw(); + + LOG(DEBUG) << " TAKE ONE FACE FRAME " << faceMat.cols << " * " << faceMat.rows; + + // 发送信号用于人脸检测和生成特征值 +// emit sendImageToDetect(faceMat); +} diff --git a/device/FaceCameraController.h b/device/FaceCameraController.h new file mode 100644 index 0000000..c75fcda --- /dev/null +++ b/device/FaceCameraController.h @@ -0,0 +1,40 @@ +#ifndef CAMERACONTROLLER_H +#define CAMERACONTROLLER_H + +#include + +#include "opencv2/opencv.hpp" + +//#include "casic/face/CasicFaceInterface.h" +//#include "process/memory/ProMemory.h" +//#include "process/face/CasicFaceRecState.h" +#include "utils/ImageUtil.h" +#include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" +#include "utils/easyloggingpp/easylogging++.h" + +class FaceCameraController : public QObject +{ + Q_OBJECT +public: + explicit FaceCameraController(QObject *parent = nullptr); + ~FaceCameraController(); + + // 初始化并打开人脸相机 + void openFaceCamera(); + void closeFaceCamera(); + +private: + cv::VideoCapture * faceCap; + + cv::Mat faceMat; + +public slots: + void getOneFaceFrm(); + +signals: + void sendImageToDraw(); + void sendImageToDetect(cv::Mat imgMat); +}; + +#endif // CAMERACONTROLLER_H diff --git a/device/device.pri b/device/device.pri new file mode 100644 index 0000000..99f9438 --- /dev/null +++ b/device/device.pri @@ -0,0 +1,19 @@ + +HEADERS += $$PWD/FaceCameraController.h +HEADERS += $$PWD/face/FaceDetectRegistProcess.h +HEADERS += $$PWD/face/CasicFaceRecState.h + +SOURCES += $$PWD/FaceCameraController.cpp +SOURCES += $$PWD/face/FaceDetectRegistProcess.cpp +SOURCES += $$PWD/face/CasicFaceRecState.cpp + + +#HEADERS += $$PWD/IrisCameraController.h +#HEADERS += $$PWD/IrisCameraCapEventHandler.h +#HEADERS += $$PWD/MotoController.h +#HEADERS += $$PWD/DeviceEnumerator.h + +#SOURCES += $$PWD/IrisCameraController.cpp +#SOURCES += $$PWD/IrisCameraCapEventHandler.cpp +#SOURCES += $$PWD/MotoController.cpp +#SOURCES += $$PWD/DeviceEnumerator.cpp diff --git a/device/face/CasicFaceRecState.cpp b/device/face/CasicFaceRecState.cpp new file mode 100644 index 0000000..3ba8470 --- /dev/null +++ b/device/face/CasicFaceRecState.cpp @@ -0,0 +1,6 @@ +#include "CasicFaceRecState.h" + +CasicFaceRecState::CasicFaceRecState() +{ + +} diff --git a/device/face/CasicFaceRecState.h b/device/face/CasicFaceRecState.h new file mode 100644 index 0000000..999486e --- /dev/null +++ b/device/face/CasicFaceRecState.h @@ -0,0 +1,41 @@ +#ifndef CASICFACERECSTATE_H +#define CASICFACERECSTATE_H + +#include +#include "casic/face/CasicFaceInfo.h" + +class CasicFaceRecState : public QObject +{ +public: + ~CasicFaceRecState() {}; + CasicFaceRecState(const CasicFaceRecState&)=delete; + CasicFaceRecState& operator=(const CasicFaceRecState&)=delete; + + static CasicFaceRecState& getInstance() { + static CasicFaceRecState instance; + return instance; + } + + void initRecognize(); + std::string toString(); + QJsonObject toJSON(); + + std::string recoginzeId; // 识别过程id + qint64 timeStamp = 0; // 识别开始时间戳 + qint64 timeStampSucc = 0; // 识别成功时的时间戳 + + CasicFaceInfo * faceInfo; // 人脸信息 + QString imgBase64; // 人脸的base64码数据, 用于存库 + + qint8 tryCount = 0; // 识别尝试次数 + qint8 noFaceCount = 0; // 连续没有找到人脸次数 + float recogTimeLast = 0.0; // 识别成功耗时 + +private: + CasicFaceRecState(); + +signals: + +}; + +#endif // CASICFACERECSTATE_H diff --git a/device/face/FaceDetectRegistProcess.cpp b/device/face/FaceDetectRegistProcess.cpp new file mode 100644 index 0000000..3a6748d --- /dev/null +++ b/device/face/FaceDetectRegistProcess.cpp @@ -0,0 +1,70 @@ +#include "FaceDetectRegistProcess.h" + +FaceDetectRegistProcess::FaceDetectRegistProcess(QObject *parent) : QObject(parent) +{ + +} + +void FaceDetectRegistProcess::faceDetect(cv::Mat faceMat) +{ + // 如果已经在人脸检测工作中则退出 + if (FACE_DETECT_FLAG == true) + { + LOG(DEBUG) << "ALREADY IN FACE DETECT PROCESS"; + return; + } + + // 开始人脸检测 + FACE_DETECT_FLAG = true; + LOG(DEBUG) << "START FACE DETECT"; + + // 调用人脸检测算法 + CasicFaceInfo faceInfo = casic::face::CasicFaceInterface::getInstance().faceDetect(faceMat); + if (faceInfo.hasFace == false) + { + // 没有找到人脸进行如下处理 + + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; + return; + } + + // 继续进行后面的步骤 + // 开始人脸活体检测, 提高活体检测的阈值到0.3/0.6 + casic::face::CasicFaceInterface::getInstance().setAntiThreshold(0.3, 0.6); + faceInfo = casic::face::CasicFaceInterface::getInstance().faceAntiSpoofing(faceInfo); + // 活体检测判断为假脸 + if (faceInfo.antiStatus != seeta::FaceAntiSpoofing::Status::REAL) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸活体检测未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 判定为真实人脸, 开始质量评估 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceQuality(faceInfo); + + // 质量评估不为HIGH则返回 + if (faceInfo.quality.level != seeta::QualityLevel::HIGH) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸质量评估未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 调用算法提取特征值, 1024个字节的float数组 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceFeatureExtract(faceInfo); + + LOG(DEBUG) << QString("[faceDetect]特征提取成功").toStdString(); + + // 发送信号去进行比对, 执行后续业务逻辑 + emit this->extractFeatureSuccess(CasicFaceRecState::getInstance()); + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; +} diff --git a/device/face/FaceDetectRegistProcess.h b/device/face/FaceDetectRegistProcess.h new file mode 100644 index 0000000..5d42bbe --- /dev/null +++ b/device/face/FaceDetectRegistProcess.h @@ -0,0 +1,28 @@ +#ifndef FACEDETECTREGISTPROCESS_H +#define FACEDETECTREGISTPROCESS_H + +#include +#include "opencv2/opencv.hpp" + +#include "utils/easyloggingpp/easylogging++.h" + +#include "CasicFaceRecState.h" +#include "casic/face/CasicFaceInterface.h" + +static bool FACE_DETECT_FLAG = false; + +class FaceDetectRegistProcess : public QObject +{ + Q_OBJECT +public: + explicit FaceDetectRegistProcess(QObject *parent = nullptr); + +public slots: + void faceDetect(cv::Mat faceMat); + +signals: + void extractFeatureSuccess(CasicFaceRecState& faceRecState); + +}; + +#endif // FACEDETECTREGISTPROCESS_H diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/casic/face/CasicFaceInterface.h b/casic/face/CasicFaceInterface.h new file mode 100644 index 0000000..d6d4494 --- /dev/null +++ b/casic/face/CasicFaceInterface.h @@ -0,0 +1,91 @@ +#ifndef CASICFACEINTERFACE_H +#define CASICFACEINTERFACE_H + +#include "opencv2/opencv.hpp" +#include "seeta/FaceDetector.h" +#include "seeta/FaceLandmarker.h" +#include "seeta/QualityAssessor.h" +#include "seeta/QualityOfBrightness.h" +#include "seeta/QualityOfClarity.h" +#include "seeta/QualityOfIntegrity.h" +#include "seeta/QualityOfResolution.h" +#include "seeta/QualityOfPoseEx.h" +#include "seeta/FaceAntiSpoofing.h" +#include "seeta/FaceRecognizer.h" + +#include "CasicFaceInfo.h" + +static auto red = CV_RGB(255, 0, 0); +static auto green = CV_RGB(0, 255, 0); +static auto blue = CV_RGB(0, 0, 255); + +namespace casic { + namespace face { + class CasicFaceInterface + { + public: + ~CasicFaceInterface(); + CasicFaceInterface(const CasicFaceInterface&)=delete; + CasicFaceInterface& operator=(const CasicFaceInterface&)=delete; + + static CasicFaceInterface& getInstance() { + static CasicFaceInterface instance; + return instance; + } + + void setDetectorModelPath(std::string detectorModelPath); + void setMarkPts5ModelPath(std::string markPts5ModelPath); + void setPoseModelPath(std::string poseModelPath); + void setFas1stModelPath(std::string fas1stModelPath); + void setFas2ndModelPath(std::string fas2ndModelPath); + void setRecognizerModelPath(std::string recognizerModelPath); + + void setAntiThreshold(float clarity, float reality); + + CasicFaceInfo faceDetect(cv::Mat frame); + CasicFaceInfo faceQuality(CasicFaceInfo faceInfo); + CasicFaceInfo faceAntiSpoofing(CasicFaceInfo faceInfo); + CasicFaceInfo faceFeatureExtract(CasicFaceInfo faceInfo); + float faceSimCalculate(float* feature, float* otherFeature); + + cv::Rect faceDetectByCVCascade(cv::Mat frame); + cv::Rect eyeDetectByCVCascade(cv::Mat frame); + void setMinFaceSize(int minFaceSize); + void setMinEyeSize(int minEyeSize); + private: + CasicFaceInterface(); + + int deviceId = 0; + seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO; + + float clarity = 0.3f; + float reality = 0.3f; + + std::string cvFaceCascadeName = "./model/haarcascade_frontalface_default.xml"; + std::string cvEyeCascadeName = "./model/haarcascade_eye.xml"; + int minFaceSize = 320; + int maxFaceSize = 720; + int minEyeSize = 100; + int maxEyeSize = 600; + + std::string detectorModelPath = "./model/face_detector.csta"; + std::string markPts5ModelPath = "./model/face_landmarker_pts5.csta"; + std::string poseModelPath = "./model/pose_estimation.csta"; + std::string fas1stModelPath = "./model/fas_first.csta"; + std::string fas2ndModelPath = "./model/fas_second.csta"; + std::string recognizerModelPath = "./model/face_recognizer.csta"; + + seeta::FaceDetector * detector = nullptr; + seeta::FaceLandmarker * marker = nullptr; + seeta::QualityOfPoseEx * poseEx = nullptr; + seeta::FaceAntiSpoofing * processor = nullptr; + seeta::FaceRecognizer * recognizer = nullptr; + + cv::CascadeClassifier * cascade; + cv::CascadeClassifier * eyeCascade; + }; + } +} + + +#endif // CASICFACEINTERFACE_H diff --git a/casic/face/casicFace.pri b/casic/face/casicFace.pri new file mode 100644 index 0000000..da337d9 --- /dev/null +++ b/casic/face/casicFace.pri @@ -0,0 +1,9 @@ +HEADERS += $$PWD/CasicFaceInfo.h +HEADERS += $$PWD/CasicFaceInterface.h + +SOURCES += $$PWD/CasicFaceInterface.cpp + +INCLUDEPATH += seeta/ + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600 -lSeetaFaceLandmarker600 -lSeetaFaceAntiSpoofingX600 -lSeetaFaceRecognizer610 -lSeetaQualityAssessor300 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600d -lSeetaFaceLandmarker600d -lSeetaFaceAntiSpoofingX600d -lSeetaFaceRecognizer610d -lSeetaQualityAssessor300d diff --git a/dao/FaceDataImgDao.cpp b/dao/FaceDataImgDao.cpp index 83f22a0..d863752 100644 --- a/dao/FaceDataImgDao.cpp +++ b/dao/FaceDataImgDao.cpp @@ -78,20 +78,14 @@ // 返回结果 QVariantMap result; - // 获取结果集的大小 - query.last(); - int count = query.at() + 1; - - if (count >=1) + if (query.next()) { - query.first(); - result.insert("id", query.value("id").toString()); result.insert("person_id", query.value("person_id").toString()); result.insert("face_image", query.value("face_image").toString()); } - LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[%1][id=%2][%3]").arg(count).arg(query.value("id").toString()).arg(sql).toStdString(); + LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/dao/IrisDataImgDao.cpp b/dao/IrisDataImgDao.cpp index af0d45b..8a0c9af 100644 --- a/dao/IrisDataImgDao.cpp +++ b/dao/IrisDataImgDao.cpp @@ -97,7 +97,7 @@ result.insert("right_image1", query.value("right_image1").toString()); } - LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId:%1][%2]").arg(personId).arg(sql).toStdString(); + LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/device/FaceCameraController.cpp b/device/FaceCameraController.cpp new file mode 100644 index 0000000..bb325a4 --- /dev/null +++ b/device/FaceCameraController.cpp @@ -0,0 +1,60 @@ +#include "FaceCameraController.h" +#include +#include +#include + +FaceCameraController::FaceCameraController(QObject *parent) : QObject(parent) +{ + // 获取定时器, 绑定定时函数 + connect(TimeCounterUtil::getInstance().faceCapCounter, &QTimer::timeout, + this, &FaceCameraController::getOneFaceFrm); +} + +FaceCameraController::~FaceCameraController() +{ + this->closeFaceCamera(); +} + + +void FaceCameraController::openFaceCamera() +{ + this->faceCap = new cv::VideoCapture(SettingConfig::getInstance().FACE_CAMERA_INDEX, cv::CAP_DSHOW); + faceCap->set(cv::CAP_PROP_FRAME_WIDTH, SettingConfig::getInstance().FACE_FRAME_WIDTH); + faceCap->set(cv::CAP_PROP_FRAME_HEIGHT, SettingConfig::getInstance().FACE_FRAME_HEIGHT); + + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]打开相机[%1][%2 * %3]") + .arg(SettingConfig::getInstance().FACE_CAMERA_INDEX) + .arg(SettingConfig::getInstance().FACE_FRAME_WIDTH) + .arg(SettingConfig::getInstance().FACE_FRAME_HEIGHT).toStdString(); + + // 启动定时器 + TimeCounterUtil::getInstance().faceCapCounter->setInterval(SettingConfig::getInstance().FACE_FRAME_INTERVAL); + TimeCounterUtil::getInstance().faceCapCounter->start(); + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]相机开始拍图[%1ms]") + .arg(SettingConfig::getInstance().FACE_FRAME_INTERVAL).toStdString(); +} + +void FaceCameraController::closeFaceCamera() +{ + faceCap->release(); + + delete faceCap; +} + + +void FaceCameraController::getOneFaceFrm() +{ + faceCap->read(faceMat); + + // clone一个mat, 用于界面显示 + cv::Mat faceMatDisp = faceMat.clone(); + QImage imgDisplay = ImageUtil::MatImageToQImage(faceMatDisp); + + // 发送信号用于界面显示 + emit sendImageToDraw(); + + LOG(DEBUG) << " TAKE ONE FACE FRAME " << faceMat.cols << " * " << faceMat.rows; + + // 发送信号用于人脸检测和生成特征值 +// emit sendImageToDetect(faceMat); +} diff --git a/device/FaceCameraController.h b/device/FaceCameraController.h new file mode 100644 index 0000000..c75fcda --- /dev/null +++ b/device/FaceCameraController.h @@ -0,0 +1,40 @@ +#ifndef CAMERACONTROLLER_H +#define CAMERACONTROLLER_H + +#include + +#include "opencv2/opencv.hpp" + +//#include "casic/face/CasicFaceInterface.h" +//#include "process/memory/ProMemory.h" +//#include "process/face/CasicFaceRecState.h" +#include "utils/ImageUtil.h" +#include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" +#include "utils/easyloggingpp/easylogging++.h" + +class FaceCameraController : public QObject +{ + Q_OBJECT +public: + explicit FaceCameraController(QObject *parent = nullptr); + ~FaceCameraController(); + + // 初始化并打开人脸相机 + void openFaceCamera(); + void closeFaceCamera(); + +private: + cv::VideoCapture * faceCap; + + cv::Mat faceMat; + +public slots: + void getOneFaceFrm(); + +signals: + void sendImageToDraw(); + void sendImageToDetect(cv::Mat imgMat); +}; + +#endif // CAMERACONTROLLER_H diff --git a/device/device.pri b/device/device.pri new file mode 100644 index 0000000..99f9438 --- /dev/null +++ b/device/device.pri @@ -0,0 +1,19 @@ + +HEADERS += $$PWD/FaceCameraController.h +HEADERS += $$PWD/face/FaceDetectRegistProcess.h +HEADERS += $$PWD/face/CasicFaceRecState.h + +SOURCES += $$PWD/FaceCameraController.cpp +SOURCES += $$PWD/face/FaceDetectRegistProcess.cpp +SOURCES += $$PWD/face/CasicFaceRecState.cpp + + +#HEADERS += $$PWD/IrisCameraController.h +#HEADERS += $$PWD/IrisCameraCapEventHandler.h +#HEADERS += $$PWD/MotoController.h +#HEADERS += $$PWD/DeviceEnumerator.h + +#SOURCES += $$PWD/IrisCameraController.cpp +#SOURCES += $$PWD/IrisCameraCapEventHandler.cpp +#SOURCES += $$PWD/MotoController.cpp +#SOURCES += $$PWD/DeviceEnumerator.cpp diff --git a/device/face/CasicFaceRecState.cpp b/device/face/CasicFaceRecState.cpp new file mode 100644 index 0000000..3ba8470 --- /dev/null +++ b/device/face/CasicFaceRecState.cpp @@ -0,0 +1,6 @@ +#include "CasicFaceRecState.h" + +CasicFaceRecState::CasicFaceRecState() +{ + +} diff --git a/device/face/CasicFaceRecState.h b/device/face/CasicFaceRecState.h new file mode 100644 index 0000000..999486e --- /dev/null +++ b/device/face/CasicFaceRecState.h @@ -0,0 +1,41 @@ +#ifndef CASICFACERECSTATE_H +#define CASICFACERECSTATE_H + +#include +#include "casic/face/CasicFaceInfo.h" + +class CasicFaceRecState : public QObject +{ +public: + ~CasicFaceRecState() {}; + CasicFaceRecState(const CasicFaceRecState&)=delete; + CasicFaceRecState& operator=(const CasicFaceRecState&)=delete; + + static CasicFaceRecState& getInstance() { + static CasicFaceRecState instance; + return instance; + } + + void initRecognize(); + std::string toString(); + QJsonObject toJSON(); + + std::string recoginzeId; // 识别过程id + qint64 timeStamp = 0; // 识别开始时间戳 + qint64 timeStampSucc = 0; // 识别成功时的时间戳 + + CasicFaceInfo * faceInfo; // 人脸信息 + QString imgBase64; // 人脸的base64码数据, 用于存库 + + qint8 tryCount = 0; // 识别尝试次数 + qint8 noFaceCount = 0; // 连续没有找到人脸次数 + float recogTimeLast = 0.0; // 识别成功耗时 + +private: + CasicFaceRecState(); + +signals: + +}; + +#endif // CASICFACERECSTATE_H diff --git a/device/face/FaceDetectRegistProcess.cpp b/device/face/FaceDetectRegistProcess.cpp new file mode 100644 index 0000000..3a6748d --- /dev/null +++ b/device/face/FaceDetectRegistProcess.cpp @@ -0,0 +1,70 @@ +#include "FaceDetectRegistProcess.h" + +FaceDetectRegistProcess::FaceDetectRegistProcess(QObject *parent) : QObject(parent) +{ + +} + +void FaceDetectRegistProcess::faceDetect(cv::Mat faceMat) +{ + // 如果已经在人脸检测工作中则退出 + if (FACE_DETECT_FLAG == true) + { + LOG(DEBUG) << "ALREADY IN FACE DETECT PROCESS"; + return; + } + + // 开始人脸检测 + FACE_DETECT_FLAG = true; + LOG(DEBUG) << "START FACE DETECT"; + + // 调用人脸检测算法 + CasicFaceInfo faceInfo = casic::face::CasicFaceInterface::getInstance().faceDetect(faceMat); + if (faceInfo.hasFace == false) + { + // 没有找到人脸进行如下处理 + + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; + return; + } + + // 继续进行后面的步骤 + // 开始人脸活体检测, 提高活体检测的阈值到0.3/0.6 + casic::face::CasicFaceInterface::getInstance().setAntiThreshold(0.3, 0.6); + faceInfo = casic::face::CasicFaceInterface::getInstance().faceAntiSpoofing(faceInfo); + // 活体检测判断为假脸 + if (faceInfo.antiStatus != seeta::FaceAntiSpoofing::Status::REAL) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸活体检测未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 判定为真实人脸, 开始质量评估 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceQuality(faceInfo); + + // 质量评估不为HIGH则返回 + if (faceInfo.quality.level != seeta::QualityLevel::HIGH) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸质量评估未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 调用算法提取特征值, 1024个字节的float数组 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceFeatureExtract(faceInfo); + + LOG(DEBUG) << QString("[faceDetect]特征提取成功").toStdString(); + + // 发送信号去进行比对, 执行后续业务逻辑 + emit this->extractFeatureSuccess(CasicFaceRecState::getInstance()); + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; +} diff --git a/device/face/FaceDetectRegistProcess.h b/device/face/FaceDetectRegistProcess.h new file mode 100644 index 0000000..5d42bbe --- /dev/null +++ b/device/face/FaceDetectRegistProcess.h @@ -0,0 +1,28 @@ +#ifndef FACEDETECTREGISTPROCESS_H +#define FACEDETECTREGISTPROCESS_H + +#include +#include "opencv2/opencv.hpp" + +#include "utils/easyloggingpp/easylogging++.h" + +#include "CasicFaceRecState.h" +#include "casic/face/CasicFaceInterface.h" + +static bool FACE_DETECT_FLAG = false; + +class FaceDetectRegistProcess : public QObject +{ + Q_OBJECT +public: + explicit FaceDetectRegistProcess(QObject *parent = nullptr); + +public slots: + void faceDetect(cv::Mat faceMat); + +signals: + void extractFeatureSuccess(CasicFaceRecState& faceRecState); + +}; + +#endif // FACEDETECTREGISTPROCESS_H diff --git a/qss/dialogTips.css b/qss/dialogTips.css index 9c9b4d6..a80bfe1 100644 --- a/qss/dialogTips.css +++ b/qss/dialogTips.css @@ -1,20 +1,36 @@ +QDialog { + border: 1px solid #5F1BC6; +} + QLabel { color: #6868A6; font-family: "Microsoft YaHei"; } - -QLabel#labDate { - font-size: 36px; +QLabel#labTitle { + font-size: 20px; + line-height: 50px; +} +QLabel#labTipsContent { + font-size: 32px; } -QLabel#labTime { - font-size: 100px; -} - -QToolButton { - color: #6868A6; +QDialogButtonBox#btnBoxOk QPushButton { + color: #FFFFFF; font-family: "Microsoft YaHei"; - font-size: 48px; - background: transparent; - border-style: none; + font-size: 24px; + background: #6868A6; + border-radius: 12px; + min-width: 200px; + min-height: 50px; +} + +QDialogButtonBox#btnBoxConfirm QPushButton { + color: #FFFFFF; + font-family: "Microsoft YaHei"; + font-size: 24px; + background: #6868A6; + border-radius: 12px; + min-width: 120px; + min-height: 50px; + margin-right: 30px; } diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/casic/face/CasicFaceInterface.h b/casic/face/CasicFaceInterface.h new file mode 100644 index 0000000..d6d4494 --- /dev/null +++ b/casic/face/CasicFaceInterface.h @@ -0,0 +1,91 @@ +#ifndef CASICFACEINTERFACE_H +#define CASICFACEINTERFACE_H + +#include "opencv2/opencv.hpp" +#include "seeta/FaceDetector.h" +#include "seeta/FaceLandmarker.h" +#include "seeta/QualityAssessor.h" +#include "seeta/QualityOfBrightness.h" +#include "seeta/QualityOfClarity.h" +#include "seeta/QualityOfIntegrity.h" +#include "seeta/QualityOfResolution.h" +#include "seeta/QualityOfPoseEx.h" +#include "seeta/FaceAntiSpoofing.h" +#include "seeta/FaceRecognizer.h" + +#include "CasicFaceInfo.h" + +static auto red = CV_RGB(255, 0, 0); +static auto green = CV_RGB(0, 255, 0); +static auto blue = CV_RGB(0, 0, 255); + +namespace casic { + namespace face { + class CasicFaceInterface + { + public: + ~CasicFaceInterface(); + CasicFaceInterface(const CasicFaceInterface&)=delete; + CasicFaceInterface& operator=(const CasicFaceInterface&)=delete; + + static CasicFaceInterface& getInstance() { + static CasicFaceInterface instance; + return instance; + } + + void setDetectorModelPath(std::string detectorModelPath); + void setMarkPts5ModelPath(std::string markPts5ModelPath); + void setPoseModelPath(std::string poseModelPath); + void setFas1stModelPath(std::string fas1stModelPath); + void setFas2ndModelPath(std::string fas2ndModelPath); + void setRecognizerModelPath(std::string recognizerModelPath); + + void setAntiThreshold(float clarity, float reality); + + CasicFaceInfo faceDetect(cv::Mat frame); + CasicFaceInfo faceQuality(CasicFaceInfo faceInfo); + CasicFaceInfo faceAntiSpoofing(CasicFaceInfo faceInfo); + CasicFaceInfo faceFeatureExtract(CasicFaceInfo faceInfo); + float faceSimCalculate(float* feature, float* otherFeature); + + cv::Rect faceDetectByCVCascade(cv::Mat frame); + cv::Rect eyeDetectByCVCascade(cv::Mat frame); + void setMinFaceSize(int minFaceSize); + void setMinEyeSize(int minEyeSize); + private: + CasicFaceInterface(); + + int deviceId = 0; + seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO; + + float clarity = 0.3f; + float reality = 0.3f; + + std::string cvFaceCascadeName = "./model/haarcascade_frontalface_default.xml"; + std::string cvEyeCascadeName = "./model/haarcascade_eye.xml"; + int minFaceSize = 320; + int maxFaceSize = 720; + int minEyeSize = 100; + int maxEyeSize = 600; + + std::string detectorModelPath = "./model/face_detector.csta"; + std::string markPts5ModelPath = "./model/face_landmarker_pts5.csta"; + std::string poseModelPath = "./model/pose_estimation.csta"; + std::string fas1stModelPath = "./model/fas_first.csta"; + std::string fas2ndModelPath = "./model/fas_second.csta"; + std::string recognizerModelPath = "./model/face_recognizer.csta"; + + seeta::FaceDetector * detector = nullptr; + seeta::FaceLandmarker * marker = nullptr; + seeta::QualityOfPoseEx * poseEx = nullptr; + seeta::FaceAntiSpoofing * processor = nullptr; + seeta::FaceRecognizer * recognizer = nullptr; + + cv::CascadeClassifier * cascade; + cv::CascadeClassifier * eyeCascade; + }; + } +} + + +#endif // CASICFACEINTERFACE_H diff --git a/casic/face/casicFace.pri b/casic/face/casicFace.pri new file mode 100644 index 0000000..da337d9 --- /dev/null +++ b/casic/face/casicFace.pri @@ -0,0 +1,9 @@ +HEADERS += $$PWD/CasicFaceInfo.h +HEADERS += $$PWD/CasicFaceInterface.h + +SOURCES += $$PWD/CasicFaceInterface.cpp + +INCLUDEPATH += seeta/ + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600 -lSeetaFaceLandmarker600 -lSeetaFaceAntiSpoofingX600 -lSeetaFaceRecognizer610 -lSeetaQualityAssessor300 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600d -lSeetaFaceLandmarker600d -lSeetaFaceAntiSpoofingX600d -lSeetaFaceRecognizer610d -lSeetaQualityAssessor300d diff --git a/dao/FaceDataImgDao.cpp b/dao/FaceDataImgDao.cpp index 83f22a0..d863752 100644 --- a/dao/FaceDataImgDao.cpp +++ b/dao/FaceDataImgDao.cpp @@ -78,20 +78,14 @@ // 返回结果 QVariantMap result; - // 获取结果集的大小 - query.last(); - int count = query.at() + 1; - - if (count >=1) + if (query.next()) { - query.first(); - result.insert("id", query.value("id").toString()); result.insert("person_id", query.value("person_id").toString()); result.insert("face_image", query.value("face_image").toString()); } - LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[%1][id=%2][%3]").arg(count).arg(query.value("id").toString()).arg(sql).toStdString(); + LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/dao/IrisDataImgDao.cpp b/dao/IrisDataImgDao.cpp index af0d45b..8a0c9af 100644 --- a/dao/IrisDataImgDao.cpp +++ b/dao/IrisDataImgDao.cpp @@ -97,7 +97,7 @@ result.insert("right_image1", query.value("right_image1").toString()); } - LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId:%1][%2]").arg(personId).arg(sql).toStdString(); + LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/device/FaceCameraController.cpp b/device/FaceCameraController.cpp new file mode 100644 index 0000000..bb325a4 --- /dev/null +++ b/device/FaceCameraController.cpp @@ -0,0 +1,60 @@ +#include "FaceCameraController.h" +#include +#include +#include + +FaceCameraController::FaceCameraController(QObject *parent) : QObject(parent) +{ + // 获取定时器, 绑定定时函数 + connect(TimeCounterUtil::getInstance().faceCapCounter, &QTimer::timeout, + this, &FaceCameraController::getOneFaceFrm); +} + +FaceCameraController::~FaceCameraController() +{ + this->closeFaceCamera(); +} + + +void FaceCameraController::openFaceCamera() +{ + this->faceCap = new cv::VideoCapture(SettingConfig::getInstance().FACE_CAMERA_INDEX, cv::CAP_DSHOW); + faceCap->set(cv::CAP_PROP_FRAME_WIDTH, SettingConfig::getInstance().FACE_FRAME_WIDTH); + faceCap->set(cv::CAP_PROP_FRAME_HEIGHT, SettingConfig::getInstance().FACE_FRAME_HEIGHT); + + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]打开相机[%1][%2 * %3]") + .arg(SettingConfig::getInstance().FACE_CAMERA_INDEX) + .arg(SettingConfig::getInstance().FACE_FRAME_WIDTH) + .arg(SettingConfig::getInstance().FACE_FRAME_HEIGHT).toStdString(); + + // 启动定时器 + TimeCounterUtil::getInstance().faceCapCounter->setInterval(SettingConfig::getInstance().FACE_FRAME_INTERVAL); + TimeCounterUtil::getInstance().faceCapCounter->start(); + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]相机开始拍图[%1ms]") + .arg(SettingConfig::getInstance().FACE_FRAME_INTERVAL).toStdString(); +} + +void FaceCameraController::closeFaceCamera() +{ + faceCap->release(); + + delete faceCap; +} + + +void FaceCameraController::getOneFaceFrm() +{ + faceCap->read(faceMat); + + // clone一个mat, 用于界面显示 + cv::Mat faceMatDisp = faceMat.clone(); + QImage imgDisplay = ImageUtil::MatImageToQImage(faceMatDisp); + + // 发送信号用于界面显示 + emit sendImageToDraw(); + + LOG(DEBUG) << " TAKE ONE FACE FRAME " << faceMat.cols << " * " << faceMat.rows; + + // 发送信号用于人脸检测和生成特征值 +// emit sendImageToDetect(faceMat); +} diff --git a/device/FaceCameraController.h b/device/FaceCameraController.h new file mode 100644 index 0000000..c75fcda --- /dev/null +++ b/device/FaceCameraController.h @@ -0,0 +1,40 @@ +#ifndef CAMERACONTROLLER_H +#define CAMERACONTROLLER_H + +#include + +#include "opencv2/opencv.hpp" + +//#include "casic/face/CasicFaceInterface.h" +//#include "process/memory/ProMemory.h" +//#include "process/face/CasicFaceRecState.h" +#include "utils/ImageUtil.h" +#include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" +#include "utils/easyloggingpp/easylogging++.h" + +class FaceCameraController : public QObject +{ + Q_OBJECT +public: + explicit FaceCameraController(QObject *parent = nullptr); + ~FaceCameraController(); + + // 初始化并打开人脸相机 + void openFaceCamera(); + void closeFaceCamera(); + +private: + cv::VideoCapture * faceCap; + + cv::Mat faceMat; + +public slots: + void getOneFaceFrm(); + +signals: + void sendImageToDraw(); + void sendImageToDetect(cv::Mat imgMat); +}; + +#endif // CAMERACONTROLLER_H diff --git a/device/device.pri b/device/device.pri new file mode 100644 index 0000000..99f9438 --- /dev/null +++ b/device/device.pri @@ -0,0 +1,19 @@ + +HEADERS += $$PWD/FaceCameraController.h +HEADERS += $$PWD/face/FaceDetectRegistProcess.h +HEADERS += $$PWD/face/CasicFaceRecState.h + +SOURCES += $$PWD/FaceCameraController.cpp +SOURCES += $$PWD/face/FaceDetectRegistProcess.cpp +SOURCES += $$PWD/face/CasicFaceRecState.cpp + + +#HEADERS += $$PWD/IrisCameraController.h +#HEADERS += $$PWD/IrisCameraCapEventHandler.h +#HEADERS += $$PWD/MotoController.h +#HEADERS += $$PWD/DeviceEnumerator.h + +#SOURCES += $$PWD/IrisCameraController.cpp +#SOURCES += $$PWD/IrisCameraCapEventHandler.cpp +#SOURCES += $$PWD/MotoController.cpp +#SOURCES += $$PWD/DeviceEnumerator.cpp diff --git a/device/face/CasicFaceRecState.cpp b/device/face/CasicFaceRecState.cpp new file mode 100644 index 0000000..3ba8470 --- /dev/null +++ b/device/face/CasicFaceRecState.cpp @@ -0,0 +1,6 @@ +#include "CasicFaceRecState.h" + +CasicFaceRecState::CasicFaceRecState() +{ + +} diff --git a/device/face/CasicFaceRecState.h b/device/face/CasicFaceRecState.h new file mode 100644 index 0000000..999486e --- /dev/null +++ b/device/face/CasicFaceRecState.h @@ -0,0 +1,41 @@ +#ifndef CASICFACERECSTATE_H +#define CASICFACERECSTATE_H + +#include +#include "casic/face/CasicFaceInfo.h" + +class CasicFaceRecState : public QObject +{ +public: + ~CasicFaceRecState() {}; + CasicFaceRecState(const CasicFaceRecState&)=delete; + CasicFaceRecState& operator=(const CasicFaceRecState&)=delete; + + static CasicFaceRecState& getInstance() { + static CasicFaceRecState instance; + return instance; + } + + void initRecognize(); + std::string toString(); + QJsonObject toJSON(); + + std::string recoginzeId; // 识别过程id + qint64 timeStamp = 0; // 识别开始时间戳 + qint64 timeStampSucc = 0; // 识别成功时的时间戳 + + CasicFaceInfo * faceInfo; // 人脸信息 + QString imgBase64; // 人脸的base64码数据, 用于存库 + + qint8 tryCount = 0; // 识别尝试次数 + qint8 noFaceCount = 0; // 连续没有找到人脸次数 + float recogTimeLast = 0.0; // 识别成功耗时 + +private: + CasicFaceRecState(); + +signals: + +}; + +#endif // CASICFACERECSTATE_H diff --git a/device/face/FaceDetectRegistProcess.cpp b/device/face/FaceDetectRegistProcess.cpp new file mode 100644 index 0000000..3a6748d --- /dev/null +++ b/device/face/FaceDetectRegistProcess.cpp @@ -0,0 +1,70 @@ +#include "FaceDetectRegistProcess.h" + +FaceDetectRegistProcess::FaceDetectRegistProcess(QObject *parent) : QObject(parent) +{ + +} + +void FaceDetectRegistProcess::faceDetect(cv::Mat faceMat) +{ + // 如果已经在人脸检测工作中则退出 + if (FACE_DETECT_FLAG == true) + { + LOG(DEBUG) << "ALREADY IN FACE DETECT PROCESS"; + return; + } + + // 开始人脸检测 + FACE_DETECT_FLAG = true; + LOG(DEBUG) << "START FACE DETECT"; + + // 调用人脸检测算法 + CasicFaceInfo faceInfo = casic::face::CasicFaceInterface::getInstance().faceDetect(faceMat); + if (faceInfo.hasFace == false) + { + // 没有找到人脸进行如下处理 + + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; + return; + } + + // 继续进行后面的步骤 + // 开始人脸活体检测, 提高活体检测的阈值到0.3/0.6 + casic::face::CasicFaceInterface::getInstance().setAntiThreshold(0.3, 0.6); + faceInfo = casic::face::CasicFaceInterface::getInstance().faceAntiSpoofing(faceInfo); + // 活体检测判断为假脸 + if (faceInfo.antiStatus != seeta::FaceAntiSpoofing::Status::REAL) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸活体检测未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 判定为真实人脸, 开始质量评估 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceQuality(faceInfo); + + // 质量评估不为HIGH则返回 + if (faceInfo.quality.level != seeta::QualityLevel::HIGH) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸质量评估未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 调用算法提取特征值, 1024个字节的float数组 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceFeatureExtract(faceInfo); + + LOG(DEBUG) << QString("[faceDetect]特征提取成功").toStdString(); + + // 发送信号去进行比对, 执行后续业务逻辑 + emit this->extractFeatureSuccess(CasicFaceRecState::getInstance()); + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; +} diff --git a/device/face/FaceDetectRegistProcess.h b/device/face/FaceDetectRegistProcess.h new file mode 100644 index 0000000..5d42bbe --- /dev/null +++ b/device/face/FaceDetectRegistProcess.h @@ -0,0 +1,28 @@ +#ifndef FACEDETECTREGISTPROCESS_H +#define FACEDETECTREGISTPROCESS_H + +#include +#include "opencv2/opencv.hpp" + +#include "utils/easyloggingpp/easylogging++.h" + +#include "CasicFaceRecState.h" +#include "casic/face/CasicFaceInterface.h" + +static bool FACE_DETECT_FLAG = false; + +class FaceDetectRegistProcess : public QObject +{ + Q_OBJECT +public: + explicit FaceDetectRegistProcess(QObject *parent = nullptr); + +public slots: + void faceDetect(cv::Mat faceMat); + +signals: + void extractFeatureSuccess(CasicFaceRecState& faceRecState); + +}; + +#endif // FACEDETECTREGISTPROCESS_H diff --git a/qss/dialogTips.css b/qss/dialogTips.css index 9c9b4d6..a80bfe1 100644 --- a/qss/dialogTips.css +++ b/qss/dialogTips.css @@ -1,20 +1,36 @@ +QDialog { + border: 1px solid #5F1BC6; +} + QLabel { color: #6868A6; font-family: "Microsoft YaHei"; } - -QLabel#labDate { - font-size: 36px; +QLabel#labTitle { + font-size: 20px; + line-height: 50px; +} +QLabel#labTipsContent { + font-size: 32px; } -QLabel#labTime { - font-size: 100px; -} - -QToolButton { - color: #6868A6; +QDialogButtonBox#btnBoxOk QPushButton { + color: #FFFFFF; font-family: "Microsoft YaHei"; - font-size: 48px; - background: transparent; - border-style: none; + font-size: 24px; + background: #6868A6; + border-radius: 12px; + min-width: 200px; + min-height: 50px; +} + +QDialogButtonBox#btnBoxConfirm QPushButton { + color: #FFFFFF; + font-family: "Microsoft YaHei"; + font-size: 24px; + background: #6868A6; + border-radius: 12px; + min-width: 120px; + min-height: 50px; + margin-right: 30px; } diff --git a/utils/ImageUtil.cpp b/utils/ImageUtil.cpp new file mode 100644 index 0000000..7604572 --- /dev/null +++ b/utils/ImageUtil.cpp @@ -0,0 +1,97 @@ +#include "ImageUtil.h" + +ImageUtil::ImageUtil() +{ + +} + +QImage ImageUtil::MatImageToQImage(const cv::Mat &src) +{ + //CV_8UC1 8位无符号的单通道---灰度图片 + if(src.type() == CV_8UC1) + { + QImage qImage((const unsigned char *)(src.data), src.cols, src.rows, src.cols, QImage::Format_Grayscale8); + return qImage; + } + //为3通道的彩色图片 + else if(src.type() == CV_8UC3) + { + //得到图像的的首地址 + const uchar *pSrc = (const uchar*)src.data; + //以src构造图片 + QImage qImage(pSrc, src.cols, src.rows, src.step, QImage::Format_RGB888); + //在不改变实际图像数据的条件下, 交换红蓝通道 + return qImage.rgbSwapped(); + } + //四通道图片, 带Alpha通道的RGB彩色图像 + else if(src.type() == CV_8UC4) + { + const uchar *pSrc = (const uchar*)src.data; + QImage qImage(pSrc, src.cols, src.rows, src.step, QImage::Format_ARGB32); + //返回图像的子区域作为一个新图像 + return qImage.copy(); + } + else + { + return QImage(); + } +} +/* +QImage ImageUtil::UcharToQImage(unsigned char* data, int width, int height, QImage::Format format) +{ + QImage qImage(data, width, height, format); + //在不改变实际图像数据的条件下, 交换红蓝通道 + return qImage.rgbSwapped(); +} + +cv::Mat ImageUtil::QImageToMat(QImage image) +{ + cv::Mat mat; + switch(image.format()) + { + case QImage::Format_ARGB32: + case QImage::Format_RGB32: + case QImage::Format_ARGB32_Premultiplied: + mat = cv::Mat(image.height(), image.width(), CV_8UC4, (void*)image.constBits(), image.bytesPerLine()); + break; + case QImage::Format_RGB888: + mat = cv::Mat(image.height(), image.width(), CV_8UC3, (void*)image.constBits(), image.bytesPerLine()); + cv::cvtColor(mat, mat, cv::COLOR_BGR2RGB); + break; + case QImage::Format_Indexed8: + mat = cv::Mat(image.height(), image.width(), CV_8UC1, (void*)image.constBits(), image.bytesPerLine()); + break; + case QImage::Format_Grayscale8: + mat = cv::Mat(image.height(), image.width(), CV_8UC1, (void *)image.bits(), image.bytesPerLine()); + break; + } + + mat = mat.clone(); + + return mat; +} + +QString ImageUtil::QImageToBase64(QImage image) +{ + QByteArray ba; + QBuffer buf(&ba); + image.save(&buf, "bmp"); + QString base64String = ba.toBase64(); + return base64String; +} + +cv::Mat ImageUtil::MatImageRect(const cv::Mat &src, cv::Rect rect, int delta) +{ + cv::Mat matClone = src.clone(); + cv::Rect bigRect; + + bigRect.x = rect.x - delta < 0 ? 0 : rect.x - delta; + bigRect.y = rect.y - delta < 0 ? 0 : rect.y - delta; + bigRect.width = rect.x + rect.width + 2 * delta > src.cols ? src.cols - rect.x : rect.width + 2 * delta; + bigRect.height = rect.y + rect.height + 2 * delta > src.rows ? src.rows - rect.y : rect.height + 2 * delta; + + cv::Mat roi = matClone(bigRect); + + return roi; +} +*/ diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/casic/face/CasicFaceInterface.h b/casic/face/CasicFaceInterface.h new file mode 100644 index 0000000..d6d4494 --- /dev/null +++ b/casic/face/CasicFaceInterface.h @@ -0,0 +1,91 @@ +#ifndef CASICFACEINTERFACE_H +#define CASICFACEINTERFACE_H + +#include "opencv2/opencv.hpp" +#include "seeta/FaceDetector.h" +#include "seeta/FaceLandmarker.h" +#include "seeta/QualityAssessor.h" +#include "seeta/QualityOfBrightness.h" +#include "seeta/QualityOfClarity.h" +#include "seeta/QualityOfIntegrity.h" +#include "seeta/QualityOfResolution.h" +#include "seeta/QualityOfPoseEx.h" +#include "seeta/FaceAntiSpoofing.h" +#include "seeta/FaceRecognizer.h" + +#include "CasicFaceInfo.h" + +static auto red = CV_RGB(255, 0, 0); +static auto green = CV_RGB(0, 255, 0); +static auto blue = CV_RGB(0, 0, 255); + +namespace casic { + namespace face { + class CasicFaceInterface + { + public: + ~CasicFaceInterface(); + CasicFaceInterface(const CasicFaceInterface&)=delete; + CasicFaceInterface& operator=(const CasicFaceInterface&)=delete; + + static CasicFaceInterface& getInstance() { + static CasicFaceInterface instance; + return instance; + } + + void setDetectorModelPath(std::string detectorModelPath); + void setMarkPts5ModelPath(std::string markPts5ModelPath); + void setPoseModelPath(std::string poseModelPath); + void setFas1stModelPath(std::string fas1stModelPath); + void setFas2ndModelPath(std::string fas2ndModelPath); + void setRecognizerModelPath(std::string recognizerModelPath); + + void setAntiThreshold(float clarity, float reality); + + CasicFaceInfo faceDetect(cv::Mat frame); + CasicFaceInfo faceQuality(CasicFaceInfo faceInfo); + CasicFaceInfo faceAntiSpoofing(CasicFaceInfo faceInfo); + CasicFaceInfo faceFeatureExtract(CasicFaceInfo faceInfo); + float faceSimCalculate(float* feature, float* otherFeature); + + cv::Rect faceDetectByCVCascade(cv::Mat frame); + cv::Rect eyeDetectByCVCascade(cv::Mat frame); + void setMinFaceSize(int minFaceSize); + void setMinEyeSize(int minEyeSize); + private: + CasicFaceInterface(); + + int deviceId = 0; + seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO; + + float clarity = 0.3f; + float reality = 0.3f; + + std::string cvFaceCascadeName = "./model/haarcascade_frontalface_default.xml"; + std::string cvEyeCascadeName = "./model/haarcascade_eye.xml"; + int minFaceSize = 320; + int maxFaceSize = 720; + int minEyeSize = 100; + int maxEyeSize = 600; + + std::string detectorModelPath = "./model/face_detector.csta"; + std::string markPts5ModelPath = "./model/face_landmarker_pts5.csta"; + std::string poseModelPath = "./model/pose_estimation.csta"; + std::string fas1stModelPath = "./model/fas_first.csta"; + std::string fas2ndModelPath = "./model/fas_second.csta"; + std::string recognizerModelPath = "./model/face_recognizer.csta"; + + seeta::FaceDetector * detector = nullptr; + seeta::FaceLandmarker * marker = nullptr; + seeta::QualityOfPoseEx * poseEx = nullptr; + seeta::FaceAntiSpoofing * processor = nullptr; + seeta::FaceRecognizer * recognizer = nullptr; + + cv::CascadeClassifier * cascade; + cv::CascadeClassifier * eyeCascade; + }; + } +} + + +#endif // CASICFACEINTERFACE_H diff --git a/casic/face/casicFace.pri b/casic/face/casicFace.pri new file mode 100644 index 0000000..da337d9 --- /dev/null +++ b/casic/face/casicFace.pri @@ -0,0 +1,9 @@ +HEADERS += $$PWD/CasicFaceInfo.h +HEADERS += $$PWD/CasicFaceInterface.h + +SOURCES += $$PWD/CasicFaceInterface.cpp + +INCLUDEPATH += seeta/ + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600 -lSeetaFaceLandmarker600 -lSeetaFaceAntiSpoofingX600 -lSeetaFaceRecognizer610 -lSeetaQualityAssessor300 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600d -lSeetaFaceLandmarker600d -lSeetaFaceAntiSpoofingX600d -lSeetaFaceRecognizer610d -lSeetaQualityAssessor300d diff --git a/dao/FaceDataImgDao.cpp b/dao/FaceDataImgDao.cpp index 83f22a0..d863752 100644 --- a/dao/FaceDataImgDao.cpp +++ b/dao/FaceDataImgDao.cpp @@ -78,20 +78,14 @@ // 返回结果 QVariantMap result; - // 获取结果集的大小 - query.last(); - int count = query.at() + 1; - - if (count >=1) + if (query.next()) { - query.first(); - result.insert("id", query.value("id").toString()); result.insert("person_id", query.value("person_id").toString()); result.insert("face_image", query.value("face_image").toString()); } - LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[%1][id=%2][%3]").arg(count).arg(query.value("id").toString()).arg(sql).toStdString(); + LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/dao/IrisDataImgDao.cpp b/dao/IrisDataImgDao.cpp index af0d45b..8a0c9af 100644 --- a/dao/IrisDataImgDao.cpp +++ b/dao/IrisDataImgDao.cpp @@ -97,7 +97,7 @@ result.insert("right_image1", query.value("right_image1").toString()); } - LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId:%1][%2]").arg(personId).arg(sql).toStdString(); + LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/device/FaceCameraController.cpp b/device/FaceCameraController.cpp new file mode 100644 index 0000000..bb325a4 --- /dev/null +++ b/device/FaceCameraController.cpp @@ -0,0 +1,60 @@ +#include "FaceCameraController.h" +#include +#include +#include + +FaceCameraController::FaceCameraController(QObject *parent) : QObject(parent) +{ + // 获取定时器, 绑定定时函数 + connect(TimeCounterUtil::getInstance().faceCapCounter, &QTimer::timeout, + this, &FaceCameraController::getOneFaceFrm); +} + +FaceCameraController::~FaceCameraController() +{ + this->closeFaceCamera(); +} + + +void FaceCameraController::openFaceCamera() +{ + this->faceCap = new cv::VideoCapture(SettingConfig::getInstance().FACE_CAMERA_INDEX, cv::CAP_DSHOW); + faceCap->set(cv::CAP_PROP_FRAME_WIDTH, SettingConfig::getInstance().FACE_FRAME_WIDTH); + faceCap->set(cv::CAP_PROP_FRAME_HEIGHT, SettingConfig::getInstance().FACE_FRAME_HEIGHT); + + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]打开相机[%1][%2 * %3]") + .arg(SettingConfig::getInstance().FACE_CAMERA_INDEX) + .arg(SettingConfig::getInstance().FACE_FRAME_WIDTH) + .arg(SettingConfig::getInstance().FACE_FRAME_HEIGHT).toStdString(); + + // 启动定时器 + TimeCounterUtil::getInstance().faceCapCounter->setInterval(SettingConfig::getInstance().FACE_FRAME_INTERVAL); + TimeCounterUtil::getInstance().faceCapCounter->start(); + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]相机开始拍图[%1ms]") + .arg(SettingConfig::getInstance().FACE_FRAME_INTERVAL).toStdString(); +} + +void FaceCameraController::closeFaceCamera() +{ + faceCap->release(); + + delete faceCap; +} + + +void FaceCameraController::getOneFaceFrm() +{ + faceCap->read(faceMat); + + // clone一个mat, 用于界面显示 + cv::Mat faceMatDisp = faceMat.clone(); + QImage imgDisplay = ImageUtil::MatImageToQImage(faceMatDisp); + + // 发送信号用于界面显示 + emit sendImageToDraw(); + + LOG(DEBUG) << " TAKE ONE FACE FRAME " << faceMat.cols << " * " << faceMat.rows; + + // 发送信号用于人脸检测和生成特征值 +// emit sendImageToDetect(faceMat); +} diff --git a/device/FaceCameraController.h b/device/FaceCameraController.h new file mode 100644 index 0000000..c75fcda --- /dev/null +++ b/device/FaceCameraController.h @@ -0,0 +1,40 @@ +#ifndef CAMERACONTROLLER_H +#define CAMERACONTROLLER_H + +#include + +#include "opencv2/opencv.hpp" + +//#include "casic/face/CasicFaceInterface.h" +//#include "process/memory/ProMemory.h" +//#include "process/face/CasicFaceRecState.h" +#include "utils/ImageUtil.h" +#include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" +#include "utils/easyloggingpp/easylogging++.h" + +class FaceCameraController : public QObject +{ + Q_OBJECT +public: + explicit FaceCameraController(QObject *parent = nullptr); + ~FaceCameraController(); + + // 初始化并打开人脸相机 + void openFaceCamera(); + void closeFaceCamera(); + +private: + cv::VideoCapture * faceCap; + + cv::Mat faceMat; + +public slots: + void getOneFaceFrm(); + +signals: + void sendImageToDraw(); + void sendImageToDetect(cv::Mat imgMat); +}; + +#endif // CAMERACONTROLLER_H diff --git a/device/device.pri b/device/device.pri new file mode 100644 index 0000000..99f9438 --- /dev/null +++ b/device/device.pri @@ -0,0 +1,19 @@ + +HEADERS += $$PWD/FaceCameraController.h +HEADERS += $$PWD/face/FaceDetectRegistProcess.h +HEADERS += $$PWD/face/CasicFaceRecState.h + +SOURCES += $$PWD/FaceCameraController.cpp +SOURCES += $$PWD/face/FaceDetectRegistProcess.cpp +SOURCES += $$PWD/face/CasicFaceRecState.cpp + + +#HEADERS += $$PWD/IrisCameraController.h +#HEADERS += $$PWD/IrisCameraCapEventHandler.h +#HEADERS += $$PWD/MotoController.h +#HEADERS += $$PWD/DeviceEnumerator.h + +#SOURCES += $$PWD/IrisCameraController.cpp +#SOURCES += $$PWD/IrisCameraCapEventHandler.cpp +#SOURCES += $$PWD/MotoController.cpp +#SOURCES += $$PWD/DeviceEnumerator.cpp diff --git a/device/face/CasicFaceRecState.cpp b/device/face/CasicFaceRecState.cpp new file mode 100644 index 0000000..3ba8470 --- /dev/null +++ b/device/face/CasicFaceRecState.cpp @@ -0,0 +1,6 @@ +#include "CasicFaceRecState.h" + +CasicFaceRecState::CasicFaceRecState() +{ + +} diff --git a/device/face/CasicFaceRecState.h b/device/face/CasicFaceRecState.h new file mode 100644 index 0000000..999486e --- /dev/null +++ b/device/face/CasicFaceRecState.h @@ -0,0 +1,41 @@ +#ifndef CASICFACERECSTATE_H +#define CASICFACERECSTATE_H + +#include +#include "casic/face/CasicFaceInfo.h" + +class CasicFaceRecState : public QObject +{ +public: + ~CasicFaceRecState() {}; + CasicFaceRecState(const CasicFaceRecState&)=delete; + CasicFaceRecState& operator=(const CasicFaceRecState&)=delete; + + static CasicFaceRecState& getInstance() { + static CasicFaceRecState instance; + return instance; + } + + void initRecognize(); + std::string toString(); + QJsonObject toJSON(); + + std::string recoginzeId; // 识别过程id + qint64 timeStamp = 0; // 识别开始时间戳 + qint64 timeStampSucc = 0; // 识别成功时的时间戳 + + CasicFaceInfo * faceInfo; // 人脸信息 + QString imgBase64; // 人脸的base64码数据, 用于存库 + + qint8 tryCount = 0; // 识别尝试次数 + qint8 noFaceCount = 0; // 连续没有找到人脸次数 + float recogTimeLast = 0.0; // 识别成功耗时 + +private: + CasicFaceRecState(); + +signals: + +}; + +#endif // CASICFACERECSTATE_H diff --git a/device/face/FaceDetectRegistProcess.cpp b/device/face/FaceDetectRegistProcess.cpp new file mode 100644 index 0000000..3a6748d --- /dev/null +++ b/device/face/FaceDetectRegistProcess.cpp @@ -0,0 +1,70 @@ +#include "FaceDetectRegistProcess.h" + +FaceDetectRegistProcess::FaceDetectRegistProcess(QObject *parent) : QObject(parent) +{ + +} + +void FaceDetectRegistProcess::faceDetect(cv::Mat faceMat) +{ + // 如果已经在人脸检测工作中则退出 + if (FACE_DETECT_FLAG == true) + { + LOG(DEBUG) << "ALREADY IN FACE DETECT PROCESS"; + return; + } + + // 开始人脸检测 + FACE_DETECT_FLAG = true; + LOG(DEBUG) << "START FACE DETECT"; + + // 调用人脸检测算法 + CasicFaceInfo faceInfo = casic::face::CasicFaceInterface::getInstance().faceDetect(faceMat); + if (faceInfo.hasFace == false) + { + // 没有找到人脸进行如下处理 + + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; + return; + } + + // 继续进行后面的步骤 + // 开始人脸活体检测, 提高活体检测的阈值到0.3/0.6 + casic::face::CasicFaceInterface::getInstance().setAntiThreshold(0.3, 0.6); + faceInfo = casic::face::CasicFaceInterface::getInstance().faceAntiSpoofing(faceInfo); + // 活体检测判断为假脸 + if (faceInfo.antiStatus != seeta::FaceAntiSpoofing::Status::REAL) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸活体检测未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 判定为真实人脸, 开始质量评估 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceQuality(faceInfo); + + // 质量评估不为HIGH则返回 + if (faceInfo.quality.level != seeta::QualityLevel::HIGH) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸质量评估未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 调用算法提取特征值, 1024个字节的float数组 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceFeatureExtract(faceInfo); + + LOG(DEBUG) << QString("[faceDetect]特征提取成功").toStdString(); + + // 发送信号去进行比对, 执行后续业务逻辑 + emit this->extractFeatureSuccess(CasicFaceRecState::getInstance()); + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; +} diff --git a/device/face/FaceDetectRegistProcess.h b/device/face/FaceDetectRegistProcess.h new file mode 100644 index 0000000..5d42bbe --- /dev/null +++ b/device/face/FaceDetectRegistProcess.h @@ -0,0 +1,28 @@ +#ifndef FACEDETECTREGISTPROCESS_H +#define FACEDETECTREGISTPROCESS_H + +#include +#include "opencv2/opencv.hpp" + +#include "utils/easyloggingpp/easylogging++.h" + +#include "CasicFaceRecState.h" +#include "casic/face/CasicFaceInterface.h" + +static bool FACE_DETECT_FLAG = false; + +class FaceDetectRegistProcess : public QObject +{ + Q_OBJECT +public: + explicit FaceDetectRegistProcess(QObject *parent = nullptr); + +public slots: + void faceDetect(cv::Mat faceMat); + +signals: + void extractFeatureSuccess(CasicFaceRecState& faceRecState); + +}; + +#endif // FACEDETECTREGISTPROCESS_H diff --git a/qss/dialogTips.css b/qss/dialogTips.css index 9c9b4d6..a80bfe1 100644 --- a/qss/dialogTips.css +++ b/qss/dialogTips.css @@ -1,20 +1,36 @@ +QDialog { + border: 1px solid #5F1BC6; +} + QLabel { color: #6868A6; font-family: "Microsoft YaHei"; } - -QLabel#labDate { - font-size: 36px; +QLabel#labTitle { + font-size: 20px; + line-height: 50px; +} +QLabel#labTipsContent { + font-size: 32px; } -QLabel#labTime { - font-size: 100px; -} - -QToolButton { - color: #6868A6; +QDialogButtonBox#btnBoxOk QPushButton { + color: #FFFFFF; font-family: "Microsoft YaHei"; - font-size: 48px; - background: transparent; - border-style: none; + font-size: 24px; + background: #6868A6; + border-radius: 12px; + min-width: 200px; + min-height: 50px; +} + +QDialogButtonBox#btnBoxConfirm QPushButton { + color: #FFFFFF; + font-family: "Microsoft YaHei"; + font-size: 24px; + background: #6868A6; + border-radius: 12px; + min-width: 120px; + min-height: 50px; + margin-right: 30px; } diff --git a/utils/ImageUtil.cpp b/utils/ImageUtil.cpp new file mode 100644 index 0000000..7604572 --- /dev/null +++ b/utils/ImageUtil.cpp @@ -0,0 +1,97 @@ +#include "ImageUtil.h" + +ImageUtil::ImageUtil() +{ + +} + +QImage ImageUtil::MatImageToQImage(const cv::Mat &src) +{ + //CV_8UC1 8位无符号的单通道---灰度图片 + if(src.type() == CV_8UC1) + { + QImage qImage((const unsigned char *)(src.data), src.cols, src.rows, src.cols, QImage::Format_Grayscale8); + return qImage; + } + //为3通道的彩色图片 + else if(src.type() == CV_8UC3) + { + //得到图像的的首地址 + const uchar *pSrc = (const uchar*)src.data; + //以src构造图片 + QImage qImage(pSrc, src.cols, src.rows, src.step, QImage::Format_RGB888); + //在不改变实际图像数据的条件下, 交换红蓝通道 + return qImage.rgbSwapped(); + } + //四通道图片, 带Alpha通道的RGB彩色图像 + else if(src.type() == CV_8UC4) + { + const uchar *pSrc = (const uchar*)src.data; + QImage qImage(pSrc, src.cols, src.rows, src.step, QImage::Format_ARGB32); + //返回图像的子区域作为一个新图像 + return qImage.copy(); + } + else + { + return QImage(); + } +} +/* +QImage ImageUtil::UcharToQImage(unsigned char* data, int width, int height, QImage::Format format) +{ + QImage qImage(data, width, height, format); + //在不改变实际图像数据的条件下, 交换红蓝通道 + return qImage.rgbSwapped(); +} + +cv::Mat ImageUtil::QImageToMat(QImage image) +{ + cv::Mat mat; + switch(image.format()) + { + case QImage::Format_ARGB32: + case QImage::Format_RGB32: + case QImage::Format_ARGB32_Premultiplied: + mat = cv::Mat(image.height(), image.width(), CV_8UC4, (void*)image.constBits(), image.bytesPerLine()); + break; + case QImage::Format_RGB888: + mat = cv::Mat(image.height(), image.width(), CV_8UC3, (void*)image.constBits(), image.bytesPerLine()); + cv::cvtColor(mat, mat, cv::COLOR_BGR2RGB); + break; + case QImage::Format_Indexed8: + mat = cv::Mat(image.height(), image.width(), CV_8UC1, (void*)image.constBits(), image.bytesPerLine()); + break; + case QImage::Format_Grayscale8: + mat = cv::Mat(image.height(), image.width(), CV_8UC1, (void *)image.bits(), image.bytesPerLine()); + break; + } + + mat = mat.clone(); + + return mat; +} + +QString ImageUtil::QImageToBase64(QImage image) +{ + QByteArray ba; + QBuffer buf(&ba); + image.save(&buf, "bmp"); + QString base64String = ba.toBase64(); + return base64String; +} + +cv::Mat ImageUtil::MatImageRect(const cv::Mat &src, cv::Rect rect, int delta) +{ + cv::Mat matClone = src.clone(); + cv::Rect bigRect; + + bigRect.x = rect.x - delta < 0 ? 0 : rect.x - delta; + bigRect.y = rect.y - delta < 0 ? 0 : rect.y - delta; + bigRect.width = rect.x + rect.width + 2 * delta > src.cols ? src.cols - rect.x : rect.width + 2 * delta; + bigRect.height = rect.y + rect.height + 2 * delta > src.rows ? src.rows - rect.y : rect.height + 2 * delta; + + cv::Mat roi = matClone(bigRect); + + return roi; +} +*/ diff --git a/utils/ImageUtil.h b/utils/ImageUtil.h new file mode 100644 index 0000000..dbfdd48 --- /dev/null +++ b/utils/ImageUtil.h @@ -0,0 +1,22 @@ +#ifndef IMAGEUTIL_H +#define IMAGEUTIL_H + +#include +#include +#include "opencv2/opencv.hpp" + +class ImageUtil +{ +public: + ImageUtil(); + + static QImage MatImageToQImage(const cv::Mat &src); +// static QImage UcharToQImage(unsigned char* data, int width, int height, QImage::Format format); + +// static cv::Mat QImageToMat(QImage image); +// static QString QImageToBase64(QImage image); + +// static cv::Mat MatImageRect(const cv::Mat &src, cv::Rect rect, int delta); +}; + +#endif // IMAGEUTIL_H diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/casic/face/CasicFaceInterface.h b/casic/face/CasicFaceInterface.h new file mode 100644 index 0000000..d6d4494 --- /dev/null +++ b/casic/face/CasicFaceInterface.h @@ -0,0 +1,91 @@ +#ifndef CASICFACEINTERFACE_H +#define CASICFACEINTERFACE_H + +#include "opencv2/opencv.hpp" +#include "seeta/FaceDetector.h" +#include "seeta/FaceLandmarker.h" +#include "seeta/QualityAssessor.h" +#include "seeta/QualityOfBrightness.h" +#include "seeta/QualityOfClarity.h" +#include "seeta/QualityOfIntegrity.h" +#include "seeta/QualityOfResolution.h" +#include "seeta/QualityOfPoseEx.h" +#include "seeta/FaceAntiSpoofing.h" +#include "seeta/FaceRecognizer.h" + +#include "CasicFaceInfo.h" + +static auto red = CV_RGB(255, 0, 0); +static auto green = CV_RGB(0, 255, 0); +static auto blue = CV_RGB(0, 0, 255); + +namespace casic { + namespace face { + class CasicFaceInterface + { + public: + ~CasicFaceInterface(); + CasicFaceInterface(const CasicFaceInterface&)=delete; + CasicFaceInterface& operator=(const CasicFaceInterface&)=delete; + + static CasicFaceInterface& getInstance() { + static CasicFaceInterface instance; + return instance; + } + + void setDetectorModelPath(std::string detectorModelPath); + void setMarkPts5ModelPath(std::string markPts5ModelPath); + void setPoseModelPath(std::string poseModelPath); + void setFas1stModelPath(std::string fas1stModelPath); + void setFas2ndModelPath(std::string fas2ndModelPath); + void setRecognizerModelPath(std::string recognizerModelPath); + + void setAntiThreshold(float clarity, float reality); + + CasicFaceInfo faceDetect(cv::Mat frame); + CasicFaceInfo faceQuality(CasicFaceInfo faceInfo); + CasicFaceInfo faceAntiSpoofing(CasicFaceInfo faceInfo); + CasicFaceInfo faceFeatureExtract(CasicFaceInfo faceInfo); + float faceSimCalculate(float* feature, float* otherFeature); + + cv::Rect faceDetectByCVCascade(cv::Mat frame); + cv::Rect eyeDetectByCVCascade(cv::Mat frame); + void setMinFaceSize(int minFaceSize); + void setMinEyeSize(int minEyeSize); + private: + CasicFaceInterface(); + + int deviceId = 0; + seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO; + + float clarity = 0.3f; + float reality = 0.3f; + + std::string cvFaceCascadeName = "./model/haarcascade_frontalface_default.xml"; + std::string cvEyeCascadeName = "./model/haarcascade_eye.xml"; + int minFaceSize = 320; + int maxFaceSize = 720; + int minEyeSize = 100; + int maxEyeSize = 600; + + std::string detectorModelPath = "./model/face_detector.csta"; + std::string markPts5ModelPath = "./model/face_landmarker_pts5.csta"; + std::string poseModelPath = "./model/pose_estimation.csta"; + std::string fas1stModelPath = "./model/fas_first.csta"; + std::string fas2ndModelPath = "./model/fas_second.csta"; + std::string recognizerModelPath = "./model/face_recognizer.csta"; + + seeta::FaceDetector * detector = nullptr; + seeta::FaceLandmarker * marker = nullptr; + seeta::QualityOfPoseEx * poseEx = nullptr; + seeta::FaceAntiSpoofing * processor = nullptr; + seeta::FaceRecognizer * recognizer = nullptr; + + cv::CascadeClassifier * cascade; + cv::CascadeClassifier * eyeCascade; + }; + } +} + + +#endif // CASICFACEINTERFACE_H diff --git a/casic/face/casicFace.pri b/casic/face/casicFace.pri new file mode 100644 index 0000000..da337d9 --- /dev/null +++ b/casic/face/casicFace.pri @@ -0,0 +1,9 @@ +HEADERS += $$PWD/CasicFaceInfo.h +HEADERS += $$PWD/CasicFaceInterface.h + +SOURCES += $$PWD/CasicFaceInterface.cpp + +INCLUDEPATH += seeta/ + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600 -lSeetaFaceLandmarker600 -lSeetaFaceAntiSpoofingX600 -lSeetaFaceRecognizer610 -lSeetaQualityAssessor300 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600d -lSeetaFaceLandmarker600d -lSeetaFaceAntiSpoofingX600d -lSeetaFaceRecognizer610d -lSeetaQualityAssessor300d diff --git a/dao/FaceDataImgDao.cpp b/dao/FaceDataImgDao.cpp index 83f22a0..d863752 100644 --- a/dao/FaceDataImgDao.cpp +++ b/dao/FaceDataImgDao.cpp @@ -78,20 +78,14 @@ // 返回结果 QVariantMap result; - // 获取结果集的大小 - query.last(); - int count = query.at() + 1; - - if (count >=1) + if (query.next()) { - query.first(); - result.insert("id", query.value("id").toString()); result.insert("person_id", query.value("person_id").toString()); result.insert("face_image", query.value("face_image").toString()); } - LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[%1][id=%2][%3]").arg(count).arg(query.value("id").toString()).arg(sql).toStdString(); + LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/dao/IrisDataImgDao.cpp b/dao/IrisDataImgDao.cpp index af0d45b..8a0c9af 100644 --- a/dao/IrisDataImgDao.cpp +++ b/dao/IrisDataImgDao.cpp @@ -97,7 +97,7 @@ result.insert("right_image1", query.value("right_image1").toString()); } - LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId:%1][%2]").arg(personId).arg(sql).toStdString(); + LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/device/FaceCameraController.cpp b/device/FaceCameraController.cpp new file mode 100644 index 0000000..bb325a4 --- /dev/null +++ b/device/FaceCameraController.cpp @@ -0,0 +1,60 @@ +#include "FaceCameraController.h" +#include +#include +#include + +FaceCameraController::FaceCameraController(QObject *parent) : QObject(parent) +{ + // 获取定时器, 绑定定时函数 + connect(TimeCounterUtil::getInstance().faceCapCounter, &QTimer::timeout, + this, &FaceCameraController::getOneFaceFrm); +} + +FaceCameraController::~FaceCameraController() +{ + this->closeFaceCamera(); +} + + +void FaceCameraController::openFaceCamera() +{ + this->faceCap = new cv::VideoCapture(SettingConfig::getInstance().FACE_CAMERA_INDEX, cv::CAP_DSHOW); + faceCap->set(cv::CAP_PROP_FRAME_WIDTH, SettingConfig::getInstance().FACE_FRAME_WIDTH); + faceCap->set(cv::CAP_PROP_FRAME_HEIGHT, SettingConfig::getInstance().FACE_FRAME_HEIGHT); + + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]打开相机[%1][%2 * %3]") + .arg(SettingConfig::getInstance().FACE_CAMERA_INDEX) + .arg(SettingConfig::getInstance().FACE_FRAME_WIDTH) + .arg(SettingConfig::getInstance().FACE_FRAME_HEIGHT).toStdString(); + + // 启动定时器 + TimeCounterUtil::getInstance().faceCapCounter->setInterval(SettingConfig::getInstance().FACE_FRAME_INTERVAL); + TimeCounterUtil::getInstance().faceCapCounter->start(); + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]相机开始拍图[%1ms]") + .arg(SettingConfig::getInstance().FACE_FRAME_INTERVAL).toStdString(); +} + +void FaceCameraController::closeFaceCamera() +{ + faceCap->release(); + + delete faceCap; +} + + +void FaceCameraController::getOneFaceFrm() +{ + faceCap->read(faceMat); + + // clone一个mat, 用于界面显示 + cv::Mat faceMatDisp = faceMat.clone(); + QImage imgDisplay = ImageUtil::MatImageToQImage(faceMatDisp); + + // 发送信号用于界面显示 + emit sendImageToDraw(); + + LOG(DEBUG) << " TAKE ONE FACE FRAME " << faceMat.cols << " * " << faceMat.rows; + + // 发送信号用于人脸检测和生成特征值 +// emit sendImageToDetect(faceMat); +} diff --git a/device/FaceCameraController.h b/device/FaceCameraController.h new file mode 100644 index 0000000..c75fcda --- /dev/null +++ b/device/FaceCameraController.h @@ -0,0 +1,40 @@ +#ifndef CAMERACONTROLLER_H +#define CAMERACONTROLLER_H + +#include + +#include "opencv2/opencv.hpp" + +//#include "casic/face/CasicFaceInterface.h" +//#include "process/memory/ProMemory.h" +//#include "process/face/CasicFaceRecState.h" +#include "utils/ImageUtil.h" +#include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" +#include "utils/easyloggingpp/easylogging++.h" + +class FaceCameraController : public QObject +{ + Q_OBJECT +public: + explicit FaceCameraController(QObject *parent = nullptr); + ~FaceCameraController(); + + // 初始化并打开人脸相机 + void openFaceCamera(); + void closeFaceCamera(); + +private: + cv::VideoCapture * faceCap; + + cv::Mat faceMat; + +public slots: + void getOneFaceFrm(); + +signals: + void sendImageToDraw(); + void sendImageToDetect(cv::Mat imgMat); +}; + +#endif // CAMERACONTROLLER_H diff --git a/device/device.pri b/device/device.pri new file mode 100644 index 0000000..99f9438 --- /dev/null +++ b/device/device.pri @@ -0,0 +1,19 @@ + +HEADERS += $$PWD/FaceCameraController.h +HEADERS += $$PWD/face/FaceDetectRegistProcess.h +HEADERS += $$PWD/face/CasicFaceRecState.h + +SOURCES += $$PWD/FaceCameraController.cpp +SOURCES += $$PWD/face/FaceDetectRegistProcess.cpp +SOURCES += $$PWD/face/CasicFaceRecState.cpp + + +#HEADERS += $$PWD/IrisCameraController.h +#HEADERS += $$PWD/IrisCameraCapEventHandler.h +#HEADERS += $$PWD/MotoController.h +#HEADERS += $$PWD/DeviceEnumerator.h + +#SOURCES += $$PWD/IrisCameraController.cpp +#SOURCES += $$PWD/IrisCameraCapEventHandler.cpp +#SOURCES += $$PWD/MotoController.cpp +#SOURCES += $$PWD/DeviceEnumerator.cpp diff --git a/device/face/CasicFaceRecState.cpp b/device/face/CasicFaceRecState.cpp new file mode 100644 index 0000000..3ba8470 --- /dev/null +++ b/device/face/CasicFaceRecState.cpp @@ -0,0 +1,6 @@ +#include "CasicFaceRecState.h" + +CasicFaceRecState::CasicFaceRecState() +{ + +} diff --git a/device/face/CasicFaceRecState.h b/device/face/CasicFaceRecState.h new file mode 100644 index 0000000..999486e --- /dev/null +++ b/device/face/CasicFaceRecState.h @@ -0,0 +1,41 @@ +#ifndef CASICFACERECSTATE_H +#define CASICFACERECSTATE_H + +#include +#include "casic/face/CasicFaceInfo.h" + +class CasicFaceRecState : public QObject +{ +public: + ~CasicFaceRecState() {}; + CasicFaceRecState(const CasicFaceRecState&)=delete; + CasicFaceRecState& operator=(const CasicFaceRecState&)=delete; + + static CasicFaceRecState& getInstance() { + static CasicFaceRecState instance; + return instance; + } + + void initRecognize(); + std::string toString(); + QJsonObject toJSON(); + + std::string recoginzeId; // 识别过程id + qint64 timeStamp = 0; // 识别开始时间戳 + qint64 timeStampSucc = 0; // 识别成功时的时间戳 + + CasicFaceInfo * faceInfo; // 人脸信息 + QString imgBase64; // 人脸的base64码数据, 用于存库 + + qint8 tryCount = 0; // 识别尝试次数 + qint8 noFaceCount = 0; // 连续没有找到人脸次数 + float recogTimeLast = 0.0; // 识别成功耗时 + +private: + CasicFaceRecState(); + +signals: + +}; + +#endif // CASICFACERECSTATE_H diff --git a/device/face/FaceDetectRegistProcess.cpp b/device/face/FaceDetectRegistProcess.cpp new file mode 100644 index 0000000..3a6748d --- /dev/null +++ b/device/face/FaceDetectRegistProcess.cpp @@ -0,0 +1,70 @@ +#include "FaceDetectRegistProcess.h" + +FaceDetectRegistProcess::FaceDetectRegistProcess(QObject *parent) : QObject(parent) +{ + +} + +void FaceDetectRegistProcess::faceDetect(cv::Mat faceMat) +{ + // 如果已经在人脸检测工作中则退出 + if (FACE_DETECT_FLAG == true) + { + LOG(DEBUG) << "ALREADY IN FACE DETECT PROCESS"; + return; + } + + // 开始人脸检测 + FACE_DETECT_FLAG = true; + LOG(DEBUG) << "START FACE DETECT"; + + // 调用人脸检测算法 + CasicFaceInfo faceInfo = casic::face::CasicFaceInterface::getInstance().faceDetect(faceMat); + if (faceInfo.hasFace == false) + { + // 没有找到人脸进行如下处理 + + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; + return; + } + + // 继续进行后面的步骤 + // 开始人脸活体检测, 提高活体检测的阈值到0.3/0.6 + casic::face::CasicFaceInterface::getInstance().setAntiThreshold(0.3, 0.6); + faceInfo = casic::face::CasicFaceInterface::getInstance().faceAntiSpoofing(faceInfo); + // 活体检测判断为假脸 + if (faceInfo.antiStatus != seeta::FaceAntiSpoofing::Status::REAL) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸活体检测未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 判定为真实人脸, 开始质量评估 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceQuality(faceInfo); + + // 质量评估不为HIGH则返回 + if (faceInfo.quality.level != seeta::QualityLevel::HIGH) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸质量评估未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 调用算法提取特征值, 1024个字节的float数组 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceFeatureExtract(faceInfo); + + LOG(DEBUG) << QString("[faceDetect]特征提取成功").toStdString(); + + // 发送信号去进行比对, 执行后续业务逻辑 + emit this->extractFeatureSuccess(CasicFaceRecState::getInstance()); + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; +} diff --git a/device/face/FaceDetectRegistProcess.h b/device/face/FaceDetectRegistProcess.h new file mode 100644 index 0000000..5d42bbe --- /dev/null +++ b/device/face/FaceDetectRegistProcess.h @@ -0,0 +1,28 @@ +#ifndef FACEDETECTREGISTPROCESS_H +#define FACEDETECTREGISTPROCESS_H + +#include +#include "opencv2/opencv.hpp" + +#include "utils/easyloggingpp/easylogging++.h" + +#include "CasicFaceRecState.h" +#include "casic/face/CasicFaceInterface.h" + +static bool FACE_DETECT_FLAG = false; + +class FaceDetectRegistProcess : public QObject +{ + Q_OBJECT +public: + explicit FaceDetectRegistProcess(QObject *parent = nullptr); + +public slots: + void faceDetect(cv::Mat faceMat); + +signals: + void extractFeatureSuccess(CasicFaceRecState& faceRecState); + +}; + +#endif // FACEDETECTREGISTPROCESS_H diff --git a/qss/dialogTips.css b/qss/dialogTips.css index 9c9b4d6..a80bfe1 100644 --- a/qss/dialogTips.css +++ b/qss/dialogTips.css @@ -1,20 +1,36 @@ +QDialog { + border: 1px solid #5F1BC6; +} + QLabel { color: #6868A6; font-family: "Microsoft YaHei"; } - -QLabel#labDate { - font-size: 36px; +QLabel#labTitle { + font-size: 20px; + line-height: 50px; +} +QLabel#labTipsContent { + font-size: 32px; } -QLabel#labTime { - font-size: 100px; -} - -QToolButton { - color: #6868A6; +QDialogButtonBox#btnBoxOk QPushButton { + color: #FFFFFF; font-family: "Microsoft YaHei"; - font-size: 48px; - background: transparent; - border-style: none; + font-size: 24px; + background: #6868A6; + border-radius: 12px; + min-width: 200px; + min-height: 50px; +} + +QDialogButtonBox#btnBoxConfirm QPushButton { + color: #FFFFFF; + font-family: "Microsoft YaHei"; + font-size: 24px; + background: #6868A6; + border-radius: 12px; + min-width: 120px; + min-height: 50px; + margin-right: 30px; } diff --git a/utils/ImageUtil.cpp b/utils/ImageUtil.cpp new file mode 100644 index 0000000..7604572 --- /dev/null +++ b/utils/ImageUtil.cpp @@ -0,0 +1,97 @@ +#include "ImageUtil.h" + +ImageUtil::ImageUtil() +{ + +} + +QImage ImageUtil::MatImageToQImage(const cv::Mat &src) +{ + //CV_8UC1 8位无符号的单通道---灰度图片 + if(src.type() == CV_8UC1) + { + QImage qImage((const unsigned char *)(src.data), src.cols, src.rows, src.cols, QImage::Format_Grayscale8); + return qImage; + } + //为3通道的彩色图片 + else if(src.type() == CV_8UC3) + { + //得到图像的的首地址 + const uchar *pSrc = (const uchar*)src.data; + //以src构造图片 + QImage qImage(pSrc, src.cols, src.rows, src.step, QImage::Format_RGB888); + //在不改变实际图像数据的条件下, 交换红蓝通道 + return qImage.rgbSwapped(); + } + //四通道图片, 带Alpha通道的RGB彩色图像 + else if(src.type() == CV_8UC4) + { + const uchar *pSrc = (const uchar*)src.data; + QImage qImage(pSrc, src.cols, src.rows, src.step, QImage::Format_ARGB32); + //返回图像的子区域作为一个新图像 + return qImage.copy(); + } + else + { + return QImage(); + } +} +/* +QImage ImageUtil::UcharToQImage(unsigned char* data, int width, int height, QImage::Format format) +{ + QImage qImage(data, width, height, format); + //在不改变实际图像数据的条件下, 交换红蓝通道 + return qImage.rgbSwapped(); +} + +cv::Mat ImageUtil::QImageToMat(QImage image) +{ + cv::Mat mat; + switch(image.format()) + { + case QImage::Format_ARGB32: + case QImage::Format_RGB32: + case QImage::Format_ARGB32_Premultiplied: + mat = cv::Mat(image.height(), image.width(), CV_8UC4, (void*)image.constBits(), image.bytesPerLine()); + break; + case QImage::Format_RGB888: + mat = cv::Mat(image.height(), image.width(), CV_8UC3, (void*)image.constBits(), image.bytesPerLine()); + cv::cvtColor(mat, mat, cv::COLOR_BGR2RGB); + break; + case QImage::Format_Indexed8: + mat = cv::Mat(image.height(), image.width(), CV_8UC1, (void*)image.constBits(), image.bytesPerLine()); + break; + case QImage::Format_Grayscale8: + mat = cv::Mat(image.height(), image.width(), CV_8UC1, (void *)image.bits(), image.bytesPerLine()); + break; + } + + mat = mat.clone(); + + return mat; +} + +QString ImageUtil::QImageToBase64(QImage image) +{ + QByteArray ba; + QBuffer buf(&ba); + image.save(&buf, "bmp"); + QString base64String = ba.toBase64(); + return base64String; +} + +cv::Mat ImageUtil::MatImageRect(const cv::Mat &src, cv::Rect rect, int delta) +{ + cv::Mat matClone = src.clone(); + cv::Rect bigRect; + + bigRect.x = rect.x - delta < 0 ? 0 : rect.x - delta; + bigRect.y = rect.y - delta < 0 ? 0 : rect.y - delta; + bigRect.width = rect.x + rect.width + 2 * delta > src.cols ? src.cols - rect.x : rect.width + 2 * delta; + bigRect.height = rect.y + rect.height + 2 * delta > src.rows ? src.rows - rect.y : rect.height + 2 * delta; + + cv::Mat roi = matClone(bigRect); + + return roi; +} +*/ diff --git a/utils/ImageUtil.h b/utils/ImageUtil.h new file mode 100644 index 0000000..dbfdd48 --- /dev/null +++ b/utils/ImageUtil.h @@ -0,0 +1,22 @@ +#ifndef IMAGEUTIL_H +#define IMAGEUTIL_H + +#include +#include +#include "opencv2/opencv.hpp" + +class ImageUtil +{ +public: + ImageUtil(); + + static QImage MatImageToQImage(const cv::Mat &src); +// static QImage UcharToQImage(unsigned char* data, int width, int height, QImage::Format format); + +// static cv::Mat QImageToMat(QImage image); +// static QString QImageToBase64(QImage image); + +// static cv::Mat MatImageRect(const cv::Mat &src, cv::Rect rect, int delta); +}; + +#endif // IMAGEUTIL_H diff --git a/utils/QDblClickLabel.cpp b/utils/QDblClickLabel.cpp deleted file mode 100644 index d68899c..0000000 --- a/utils/QDblClickLabel.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "QDblClickLabel.h" - -QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) -{ - -} - -QDblClickLabel::~QDblClickLabel() -{ - -} - -void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) -{ - emit doubleClicked(); -} diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/casic/face/CasicFaceInterface.h b/casic/face/CasicFaceInterface.h new file mode 100644 index 0000000..d6d4494 --- /dev/null +++ b/casic/face/CasicFaceInterface.h @@ -0,0 +1,91 @@ +#ifndef CASICFACEINTERFACE_H +#define CASICFACEINTERFACE_H + +#include "opencv2/opencv.hpp" +#include "seeta/FaceDetector.h" +#include "seeta/FaceLandmarker.h" +#include "seeta/QualityAssessor.h" +#include "seeta/QualityOfBrightness.h" +#include "seeta/QualityOfClarity.h" +#include "seeta/QualityOfIntegrity.h" +#include "seeta/QualityOfResolution.h" +#include "seeta/QualityOfPoseEx.h" +#include "seeta/FaceAntiSpoofing.h" +#include "seeta/FaceRecognizer.h" + +#include "CasicFaceInfo.h" + +static auto red = CV_RGB(255, 0, 0); +static auto green = CV_RGB(0, 255, 0); +static auto blue = CV_RGB(0, 0, 255); + +namespace casic { + namespace face { + class CasicFaceInterface + { + public: + ~CasicFaceInterface(); + CasicFaceInterface(const CasicFaceInterface&)=delete; + CasicFaceInterface& operator=(const CasicFaceInterface&)=delete; + + static CasicFaceInterface& getInstance() { + static CasicFaceInterface instance; + return instance; + } + + void setDetectorModelPath(std::string detectorModelPath); + void setMarkPts5ModelPath(std::string markPts5ModelPath); + void setPoseModelPath(std::string poseModelPath); + void setFas1stModelPath(std::string fas1stModelPath); + void setFas2ndModelPath(std::string fas2ndModelPath); + void setRecognizerModelPath(std::string recognizerModelPath); + + void setAntiThreshold(float clarity, float reality); + + CasicFaceInfo faceDetect(cv::Mat frame); + CasicFaceInfo faceQuality(CasicFaceInfo faceInfo); + CasicFaceInfo faceAntiSpoofing(CasicFaceInfo faceInfo); + CasicFaceInfo faceFeatureExtract(CasicFaceInfo faceInfo); + float faceSimCalculate(float* feature, float* otherFeature); + + cv::Rect faceDetectByCVCascade(cv::Mat frame); + cv::Rect eyeDetectByCVCascade(cv::Mat frame); + void setMinFaceSize(int minFaceSize); + void setMinEyeSize(int minEyeSize); + private: + CasicFaceInterface(); + + int deviceId = 0; + seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO; + + float clarity = 0.3f; + float reality = 0.3f; + + std::string cvFaceCascadeName = "./model/haarcascade_frontalface_default.xml"; + std::string cvEyeCascadeName = "./model/haarcascade_eye.xml"; + int minFaceSize = 320; + int maxFaceSize = 720; + int minEyeSize = 100; + int maxEyeSize = 600; + + std::string detectorModelPath = "./model/face_detector.csta"; + std::string markPts5ModelPath = "./model/face_landmarker_pts5.csta"; + std::string poseModelPath = "./model/pose_estimation.csta"; + std::string fas1stModelPath = "./model/fas_first.csta"; + std::string fas2ndModelPath = "./model/fas_second.csta"; + std::string recognizerModelPath = "./model/face_recognizer.csta"; + + seeta::FaceDetector * detector = nullptr; + seeta::FaceLandmarker * marker = nullptr; + seeta::QualityOfPoseEx * poseEx = nullptr; + seeta::FaceAntiSpoofing * processor = nullptr; + seeta::FaceRecognizer * recognizer = nullptr; + + cv::CascadeClassifier * cascade; + cv::CascadeClassifier * eyeCascade; + }; + } +} + + +#endif // CASICFACEINTERFACE_H diff --git a/casic/face/casicFace.pri b/casic/face/casicFace.pri new file mode 100644 index 0000000..da337d9 --- /dev/null +++ b/casic/face/casicFace.pri @@ -0,0 +1,9 @@ +HEADERS += $$PWD/CasicFaceInfo.h +HEADERS += $$PWD/CasicFaceInterface.h + +SOURCES += $$PWD/CasicFaceInterface.cpp + +INCLUDEPATH += seeta/ + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600 -lSeetaFaceLandmarker600 -lSeetaFaceAntiSpoofingX600 -lSeetaFaceRecognizer610 -lSeetaQualityAssessor300 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600d -lSeetaFaceLandmarker600d -lSeetaFaceAntiSpoofingX600d -lSeetaFaceRecognizer610d -lSeetaQualityAssessor300d diff --git a/dao/FaceDataImgDao.cpp b/dao/FaceDataImgDao.cpp index 83f22a0..d863752 100644 --- a/dao/FaceDataImgDao.cpp +++ b/dao/FaceDataImgDao.cpp @@ -78,20 +78,14 @@ // 返回结果 QVariantMap result; - // 获取结果集的大小 - query.last(); - int count = query.at() + 1; - - if (count >=1) + if (query.next()) { - query.first(); - result.insert("id", query.value("id").toString()); result.insert("person_id", query.value("person_id").toString()); result.insert("face_image", query.value("face_image").toString()); } - LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[%1][id=%2][%3]").arg(count).arg(query.value("id").toString()).arg(sql).toStdString(); + LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/dao/IrisDataImgDao.cpp b/dao/IrisDataImgDao.cpp index af0d45b..8a0c9af 100644 --- a/dao/IrisDataImgDao.cpp +++ b/dao/IrisDataImgDao.cpp @@ -97,7 +97,7 @@ result.insert("right_image1", query.value("right_image1").toString()); } - LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId:%1][%2]").arg(personId).arg(sql).toStdString(); + LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/device/FaceCameraController.cpp b/device/FaceCameraController.cpp new file mode 100644 index 0000000..bb325a4 --- /dev/null +++ b/device/FaceCameraController.cpp @@ -0,0 +1,60 @@ +#include "FaceCameraController.h" +#include +#include +#include + +FaceCameraController::FaceCameraController(QObject *parent) : QObject(parent) +{ + // 获取定时器, 绑定定时函数 + connect(TimeCounterUtil::getInstance().faceCapCounter, &QTimer::timeout, + this, &FaceCameraController::getOneFaceFrm); +} + +FaceCameraController::~FaceCameraController() +{ + this->closeFaceCamera(); +} + + +void FaceCameraController::openFaceCamera() +{ + this->faceCap = new cv::VideoCapture(SettingConfig::getInstance().FACE_CAMERA_INDEX, cv::CAP_DSHOW); + faceCap->set(cv::CAP_PROP_FRAME_WIDTH, SettingConfig::getInstance().FACE_FRAME_WIDTH); + faceCap->set(cv::CAP_PROP_FRAME_HEIGHT, SettingConfig::getInstance().FACE_FRAME_HEIGHT); + + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]打开相机[%1][%2 * %3]") + .arg(SettingConfig::getInstance().FACE_CAMERA_INDEX) + .arg(SettingConfig::getInstance().FACE_FRAME_WIDTH) + .arg(SettingConfig::getInstance().FACE_FRAME_HEIGHT).toStdString(); + + // 启动定时器 + TimeCounterUtil::getInstance().faceCapCounter->setInterval(SettingConfig::getInstance().FACE_FRAME_INTERVAL); + TimeCounterUtil::getInstance().faceCapCounter->start(); + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]相机开始拍图[%1ms]") + .arg(SettingConfig::getInstance().FACE_FRAME_INTERVAL).toStdString(); +} + +void FaceCameraController::closeFaceCamera() +{ + faceCap->release(); + + delete faceCap; +} + + +void FaceCameraController::getOneFaceFrm() +{ + faceCap->read(faceMat); + + // clone一个mat, 用于界面显示 + cv::Mat faceMatDisp = faceMat.clone(); + QImage imgDisplay = ImageUtil::MatImageToQImage(faceMatDisp); + + // 发送信号用于界面显示 + emit sendImageToDraw(); + + LOG(DEBUG) << " TAKE ONE FACE FRAME " << faceMat.cols << " * " << faceMat.rows; + + // 发送信号用于人脸检测和生成特征值 +// emit sendImageToDetect(faceMat); +} diff --git a/device/FaceCameraController.h b/device/FaceCameraController.h new file mode 100644 index 0000000..c75fcda --- /dev/null +++ b/device/FaceCameraController.h @@ -0,0 +1,40 @@ +#ifndef CAMERACONTROLLER_H +#define CAMERACONTROLLER_H + +#include + +#include "opencv2/opencv.hpp" + +//#include "casic/face/CasicFaceInterface.h" +//#include "process/memory/ProMemory.h" +//#include "process/face/CasicFaceRecState.h" +#include "utils/ImageUtil.h" +#include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" +#include "utils/easyloggingpp/easylogging++.h" + +class FaceCameraController : public QObject +{ + Q_OBJECT +public: + explicit FaceCameraController(QObject *parent = nullptr); + ~FaceCameraController(); + + // 初始化并打开人脸相机 + void openFaceCamera(); + void closeFaceCamera(); + +private: + cv::VideoCapture * faceCap; + + cv::Mat faceMat; + +public slots: + void getOneFaceFrm(); + +signals: + void sendImageToDraw(); + void sendImageToDetect(cv::Mat imgMat); +}; + +#endif // CAMERACONTROLLER_H diff --git a/device/device.pri b/device/device.pri new file mode 100644 index 0000000..99f9438 --- /dev/null +++ b/device/device.pri @@ -0,0 +1,19 @@ + +HEADERS += $$PWD/FaceCameraController.h +HEADERS += $$PWD/face/FaceDetectRegistProcess.h +HEADERS += $$PWD/face/CasicFaceRecState.h + +SOURCES += $$PWD/FaceCameraController.cpp +SOURCES += $$PWD/face/FaceDetectRegistProcess.cpp +SOURCES += $$PWD/face/CasicFaceRecState.cpp + + +#HEADERS += $$PWD/IrisCameraController.h +#HEADERS += $$PWD/IrisCameraCapEventHandler.h +#HEADERS += $$PWD/MotoController.h +#HEADERS += $$PWD/DeviceEnumerator.h + +#SOURCES += $$PWD/IrisCameraController.cpp +#SOURCES += $$PWD/IrisCameraCapEventHandler.cpp +#SOURCES += $$PWD/MotoController.cpp +#SOURCES += $$PWD/DeviceEnumerator.cpp diff --git a/device/face/CasicFaceRecState.cpp b/device/face/CasicFaceRecState.cpp new file mode 100644 index 0000000..3ba8470 --- /dev/null +++ b/device/face/CasicFaceRecState.cpp @@ -0,0 +1,6 @@ +#include "CasicFaceRecState.h" + +CasicFaceRecState::CasicFaceRecState() +{ + +} diff --git a/device/face/CasicFaceRecState.h b/device/face/CasicFaceRecState.h new file mode 100644 index 0000000..999486e --- /dev/null +++ b/device/face/CasicFaceRecState.h @@ -0,0 +1,41 @@ +#ifndef CASICFACERECSTATE_H +#define CASICFACERECSTATE_H + +#include +#include "casic/face/CasicFaceInfo.h" + +class CasicFaceRecState : public QObject +{ +public: + ~CasicFaceRecState() {}; + CasicFaceRecState(const CasicFaceRecState&)=delete; + CasicFaceRecState& operator=(const CasicFaceRecState&)=delete; + + static CasicFaceRecState& getInstance() { + static CasicFaceRecState instance; + return instance; + } + + void initRecognize(); + std::string toString(); + QJsonObject toJSON(); + + std::string recoginzeId; // 识别过程id + qint64 timeStamp = 0; // 识别开始时间戳 + qint64 timeStampSucc = 0; // 识别成功时的时间戳 + + CasicFaceInfo * faceInfo; // 人脸信息 + QString imgBase64; // 人脸的base64码数据, 用于存库 + + qint8 tryCount = 0; // 识别尝试次数 + qint8 noFaceCount = 0; // 连续没有找到人脸次数 + float recogTimeLast = 0.0; // 识别成功耗时 + +private: + CasicFaceRecState(); + +signals: + +}; + +#endif // CASICFACERECSTATE_H diff --git a/device/face/FaceDetectRegistProcess.cpp b/device/face/FaceDetectRegistProcess.cpp new file mode 100644 index 0000000..3a6748d --- /dev/null +++ b/device/face/FaceDetectRegistProcess.cpp @@ -0,0 +1,70 @@ +#include "FaceDetectRegistProcess.h" + +FaceDetectRegistProcess::FaceDetectRegistProcess(QObject *parent) : QObject(parent) +{ + +} + +void FaceDetectRegistProcess::faceDetect(cv::Mat faceMat) +{ + // 如果已经在人脸检测工作中则退出 + if (FACE_DETECT_FLAG == true) + { + LOG(DEBUG) << "ALREADY IN FACE DETECT PROCESS"; + return; + } + + // 开始人脸检测 + FACE_DETECT_FLAG = true; + LOG(DEBUG) << "START FACE DETECT"; + + // 调用人脸检测算法 + CasicFaceInfo faceInfo = casic::face::CasicFaceInterface::getInstance().faceDetect(faceMat); + if (faceInfo.hasFace == false) + { + // 没有找到人脸进行如下处理 + + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; + return; + } + + // 继续进行后面的步骤 + // 开始人脸活体检测, 提高活体检测的阈值到0.3/0.6 + casic::face::CasicFaceInterface::getInstance().setAntiThreshold(0.3, 0.6); + faceInfo = casic::face::CasicFaceInterface::getInstance().faceAntiSpoofing(faceInfo); + // 活体检测判断为假脸 + if (faceInfo.antiStatus != seeta::FaceAntiSpoofing::Status::REAL) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸活体检测未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 判定为真实人脸, 开始质量评估 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceQuality(faceInfo); + + // 质量评估不为HIGH则返回 + if (faceInfo.quality.level != seeta::QualityLevel::HIGH) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸质量评估未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 调用算法提取特征值, 1024个字节的float数组 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceFeatureExtract(faceInfo); + + LOG(DEBUG) << QString("[faceDetect]特征提取成功").toStdString(); + + // 发送信号去进行比对, 执行后续业务逻辑 + emit this->extractFeatureSuccess(CasicFaceRecState::getInstance()); + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; +} diff --git a/device/face/FaceDetectRegistProcess.h b/device/face/FaceDetectRegistProcess.h new file mode 100644 index 0000000..5d42bbe --- /dev/null +++ b/device/face/FaceDetectRegistProcess.h @@ -0,0 +1,28 @@ +#ifndef FACEDETECTREGISTPROCESS_H +#define FACEDETECTREGISTPROCESS_H + +#include +#include "opencv2/opencv.hpp" + +#include "utils/easyloggingpp/easylogging++.h" + +#include "CasicFaceRecState.h" +#include "casic/face/CasicFaceInterface.h" + +static bool FACE_DETECT_FLAG = false; + +class FaceDetectRegistProcess : public QObject +{ + Q_OBJECT +public: + explicit FaceDetectRegistProcess(QObject *parent = nullptr); + +public slots: + void faceDetect(cv::Mat faceMat); + +signals: + void extractFeatureSuccess(CasicFaceRecState& faceRecState); + +}; + +#endif // FACEDETECTREGISTPROCESS_H diff --git a/qss/dialogTips.css b/qss/dialogTips.css index 9c9b4d6..a80bfe1 100644 --- a/qss/dialogTips.css +++ b/qss/dialogTips.css @@ -1,20 +1,36 @@ +QDialog { + border: 1px solid #5F1BC6; +} + QLabel { color: #6868A6; font-family: "Microsoft YaHei"; } - -QLabel#labDate { - font-size: 36px; +QLabel#labTitle { + font-size: 20px; + line-height: 50px; +} +QLabel#labTipsContent { + font-size: 32px; } -QLabel#labTime { - font-size: 100px; -} - -QToolButton { - color: #6868A6; +QDialogButtonBox#btnBoxOk QPushButton { + color: #FFFFFF; font-family: "Microsoft YaHei"; - font-size: 48px; - background: transparent; - border-style: none; + font-size: 24px; + background: #6868A6; + border-radius: 12px; + min-width: 200px; + min-height: 50px; +} + +QDialogButtonBox#btnBoxConfirm QPushButton { + color: #FFFFFF; + font-family: "Microsoft YaHei"; + font-size: 24px; + background: #6868A6; + border-radius: 12px; + min-width: 120px; + min-height: 50px; + margin-right: 30px; } diff --git a/utils/ImageUtil.cpp b/utils/ImageUtil.cpp new file mode 100644 index 0000000..7604572 --- /dev/null +++ b/utils/ImageUtil.cpp @@ -0,0 +1,97 @@ +#include "ImageUtil.h" + +ImageUtil::ImageUtil() +{ + +} + +QImage ImageUtil::MatImageToQImage(const cv::Mat &src) +{ + //CV_8UC1 8位无符号的单通道---灰度图片 + if(src.type() == CV_8UC1) + { + QImage qImage((const unsigned char *)(src.data), src.cols, src.rows, src.cols, QImage::Format_Grayscale8); + return qImage; + } + //为3通道的彩色图片 + else if(src.type() == CV_8UC3) + { + //得到图像的的首地址 + const uchar *pSrc = (const uchar*)src.data; + //以src构造图片 + QImage qImage(pSrc, src.cols, src.rows, src.step, QImage::Format_RGB888); + //在不改变实际图像数据的条件下, 交换红蓝通道 + return qImage.rgbSwapped(); + } + //四通道图片, 带Alpha通道的RGB彩色图像 + else if(src.type() == CV_8UC4) + { + const uchar *pSrc = (const uchar*)src.data; + QImage qImage(pSrc, src.cols, src.rows, src.step, QImage::Format_ARGB32); + //返回图像的子区域作为一个新图像 + return qImage.copy(); + } + else + { + return QImage(); + } +} +/* +QImage ImageUtil::UcharToQImage(unsigned char* data, int width, int height, QImage::Format format) +{ + QImage qImage(data, width, height, format); + //在不改变实际图像数据的条件下, 交换红蓝通道 + return qImage.rgbSwapped(); +} + +cv::Mat ImageUtil::QImageToMat(QImage image) +{ + cv::Mat mat; + switch(image.format()) + { + case QImage::Format_ARGB32: + case QImage::Format_RGB32: + case QImage::Format_ARGB32_Premultiplied: + mat = cv::Mat(image.height(), image.width(), CV_8UC4, (void*)image.constBits(), image.bytesPerLine()); + break; + case QImage::Format_RGB888: + mat = cv::Mat(image.height(), image.width(), CV_8UC3, (void*)image.constBits(), image.bytesPerLine()); + cv::cvtColor(mat, mat, cv::COLOR_BGR2RGB); + break; + case QImage::Format_Indexed8: + mat = cv::Mat(image.height(), image.width(), CV_8UC1, (void*)image.constBits(), image.bytesPerLine()); + break; + case QImage::Format_Grayscale8: + mat = cv::Mat(image.height(), image.width(), CV_8UC1, (void *)image.bits(), image.bytesPerLine()); + break; + } + + mat = mat.clone(); + + return mat; +} + +QString ImageUtil::QImageToBase64(QImage image) +{ + QByteArray ba; + QBuffer buf(&ba); + image.save(&buf, "bmp"); + QString base64String = ba.toBase64(); + return base64String; +} + +cv::Mat ImageUtil::MatImageRect(const cv::Mat &src, cv::Rect rect, int delta) +{ + cv::Mat matClone = src.clone(); + cv::Rect bigRect; + + bigRect.x = rect.x - delta < 0 ? 0 : rect.x - delta; + bigRect.y = rect.y - delta < 0 ? 0 : rect.y - delta; + bigRect.width = rect.x + rect.width + 2 * delta > src.cols ? src.cols - rect.x : rect.width + 2 * delta; + bigRect.height = rect.y + rect.height + 2 * delta > src.rows ? src.rows - rect.y : rect.height + 2 * delta; + + cv::Mat roi = matClone(bigRect); + + return roi; +} +*/ diff --git a/utils/ImageUtil.h b/utils/ImageUtil.h new file mode 100644 index 0000000..dbfdd48 --- /dev/null +++ b/utils/ImageUtil.h @@ -0,0 +1,22 @@ +#ifndef IMAGEUTIL_H +#define IMAGEUTIL_H + +#include +#include +#include "opencv2/opencv.hpp" + +class ImageUtil +{ +public: + ImageUtil(); + + static QImage MatImageToQImage(const cv::Mat &src); +// static QImage UcharToQImage(unsigned char* data, int width, int height, QImage::Format format); + +// static cv::Mat QImageToMat(QImage image); +// static QString QImageToBase64(QImage image); + +// static cv::Mat MatImageRect(const cv::Mat &src, cv::Rect rect, int delta); +}; + +#endif // IMAGEUTIL_H diff --git a/utils/QDblClickLabel.cpp b/utils/QDblClickLabel.cpp deleted file mode 100644 index d68899c..0000000 --- a/utils/QDblClickLabel.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "QDblClickLabel.h" - -QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) -{ - -} - -QDblClickLabel::~QDblClickLabel() -{ - -} - -void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) -{ - emit doubleClicked(); -} diff --git a/utils/QDblClickLabel.h b/utils/QDblClickLabel.h deleted file mode 100644 index bd7c532..0000000 --- a/utils/QDblClickLabel.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef QDBLCLICKLABEL_H -#define QDBLCLICKLABEL_H - -#include -#include - -class QDblClickLabel : public QLabel -{ - Q_OBJECT -public: - QDblClickLabel(QWidget * parent = 0); - ~QDblClickLabel(); - void mouseDoubleClickEvent(QMouseEvent * event); - -signals: - void doubleClicked(); -}; - -#endif // QDBLCLICKLABEL_H diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/casic/face/CasicFaceInterface.h b/casic/face/CasicFaceInterface.h new file mode 100644 index 0000000..d6d4494 --- /dev/null +++ b/casic/face/CasicFaceInterface.h @@ -0,0 +1,91 @@ +#ifndef CASICFACEINTERFACE_H +#define CASICFACEINTERFACE_H + +#include "opencv2/opencv.hpp" +#include "seeta/FaceDetector.h" +#include "seeta/FaceLandmarker.h" +#include "seeta/QualityAssessor.h" +#include "seeta/QualityOfBrightness.h" +#include "seeta/QualityOfClarity.h" +#include "seeta/QualityOfIntegrity.h" +#include "seeta/QualityOfResolution.h" +#include "seeta/QualityOfPoseEx.h" +#include "seeta/FaceAntiSpoofing.h" +#include "seeta/FaceRecognizer.h" + +#include "CasicFaceInfo.h" + +static auto red = CV_RGB(255, 0, 0); +static auto green = CV_RGB(0, 255, 0); +static auto blue = CV_RGB(0, 0, 255); + +namespace casic { + namespace face { + class CasicFaceInterface + { + public: + ~CasicFaceInterface(); + CasicFaceInterface(const CasicFaceInterface&)=delete; + CasicFaceInterface& operator=(const CasicFaceInterface&)=delete; + + static CasicFaceInterface& getInstance() { + static CasicFaceInterface instance; + return instance; + } + + void setDetectorModelPath(std::string detectorModelPath); + void setMarkPts5ModelPath(std::string markPts5ModelPath); + void setPoseModelPath(std::string poseModelPath); + void setFas1stModelPath(std::string fas1stModelPath); + void setFas2ndModelPath(std::string fas2ndModelPath); + void setRecognizerModelPath(std::string recognizerModelPath); + + void setAntiThreshold(float clarity, float reality); + + CasicFaceInfo faceDetect(cv::Mat frame); + CasicFaceInfo faceQuality(CasicFaceInfo faceInfo); + CasicFaceInfo faceAntiSpoofing(CasicFaceInfo faceInfo); + CasicFaceInfo faceFeatureExtract(CasicFaceInfo faceInfo); + float faceSimCalculate(float* feature, float* otherFeature); + + cv::Rect faceDetectByCVCascade(cv::Mat frame); + cv::Rect eyeDetectByCVCascade(cv::Mat frame); + void setMinFaceSize(int minFaceSize); + void setMinEyeSize(int minEyeSize); + private: + CasicFaceInterface(); + + int deviceId = 0; + seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO; + + float clarity = 0.3f; + float reality = 0.3f; + + std::string cvFaceCascadeName = "./model/haarcascade_frontalface_default.xml"; + std::string cvEyeCascadeName = "./model/haarcascade_eye.xml"; + int minFaceSize = 320; + int maxFaceSize = 720; + int minEyeSize = 100; + int maxEyeSize = 600; + + std::string detectorModelPath = "./model/face_detector.csta"; + std::string markPts5ModelPath = "./model/face_landmarker_pts5.csta"; + std::string poseModelPath = "./model/pose_estimation.csta"; + std::string fas1stModelPath = "./model/fas_first.csta"; + std::string fas2ndModelPath = "./model/fas_second.csta"; + std::string recognizerModelPath = "./model/face_recognizer.csta"; + + seeta::FaceDetector * detector = nullptr; + seeta::FaceLandmarker * marker = nullptr; + seeta::QualityOfPoseEx * poseEx = nullptr; + seeta::FaceAntiSpoofing * processor = nullptr; + seeta::FaceRecognizer * recognizer = nullptr; + + cv::CascadeClassifier * cascade; + cv::CascadeClassifier * eyeCascade; + }; + } +} + + +#endif // CASICFACEINTERFACE_H diff --git a/casic/face/casicFace.pri b/casic/face/casicFace.pri new file mode 100644 index 0000000..da337d9 --- /dev/null +++ b/casic/face/casicFace.pri @@ -0,0 +1,9 @@ +HEADERS += $$PWD/CasicFaceInfo.h +HEADERS += $$PWD/CasicFaceInterface.h + +SOURCES += $$PWD/CasicFaceInterface.cpp + +INCLUDEPATH += seeta/ + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600 -lSeetaFaceLandmarker600 -lSeetaFaceAntiSpoofingX600 -lSeetaFaceRecognizer610 -lSeetaQualityAssessor300 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600d -lSeetaFaceLandmarker600d -lSeetaFaceAntiSpoofingX600d -lSeetaFaceRecognizer610d -lSeetaQualityAssessor300d diff --git a/dao/FaceDataImgDao.cpp b/dao/FaceDataImgDao.cpp index 83f22a0..d863752 100644 --- a/dao/FaceDataImgDao.cpp +++ b/dao/FaceDataImgDao.cpp @@ -78,20 +78,14 @@ // 返回结果 QVariantMap result; - // 获取结果集的大小 - query.last(); - int count = query.at() + 1; - - if (count >=1) + if (query.next()) { - query.first(); - result.insert("id", query.value("id").toString()); result.insert("person_id", query.value("person_id").toString()); result.insert("face_image", query.value("face_image").toString()); } - LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[%1][id=%2][%3]").arg(count).arg(query.value("id").toString()).arg(sql).toStdString(); + LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/dao/IrisDataImgDao.cpp b/dao/IrisDataImgDao.cpp index af0d45b..8a0c9af 100644 --- a/dao/IrisDataImgDao.cpp +++ b/dao/IrisDataImgDao.cpp @@ -97,7 +97,7 @@ result.insert("right_image1", query.value("right_image1").toString()); } - LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId:%1][%2]").arg(personId).arg(sql).toStdString(); + LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/device/FaceCameraController.cpp b/device/FaceCameraController.cpp new file mode 100644 index 0000000..bb325a4 --- /dev/null +++ b/device/FaceCameraController.cpp @@ -0,0 +1,60 @@ +#include "FaceCameraController.h" +#include +#include +#include + +FaceCameraController::FaceCameraController(QObject *parent) : QObject(parent) +{ + // 获取定时器, 绑定定时函数 + connect(TimeCounterUtil::getInstance().faceCapCounter, &QTimer::timeout, + this, &FaceCameraController::getOneFaceFrm); +} + +FaceCameraController::~FaceCameraController() +{ + this->closeFaceCamera(); +} + + +void FaceCameraController::openFaceCamera() +{ + this->faceCap = new cv::VideoCapture(SettingConfig::getInstance().FACE_CAMERA_INDEX, cv::CAP_DSHOW); + faceCap->set(cv::CAP_PROP_FRAME_WIDTH, SettingConfig::getInstance().FACE_FRAME_WIDTH); + faceCap->set(cv::CAP_PROP_FRAME_HEIGHT, SettingConfig::getInstance().FACE_FRAME_HEIGHT); + + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]打开相机[%1][%2 * %3]") + .arg(SettingConfig::getInstance().FACE_CAMERA_INDEX) + .arg(SettingConfig::getInstance().FACE_FRAME_WIDTH) + .arg(SettingConfig::getInstance().FACE_FRAME_HEIGHT).toStdString(); + + // 启动定时器 + TimeCounterUtil::getInstance().faceCapCounter->setInterval(SettingConfig::getInstance().FACE_FRAME_INTERVAL); + TimeCounterUtil::getInstance().faceCapCounter->start(); + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]相机开始拍图[%1ms]") + .arg(SettingConfig::getInstance().FACE_FRAME_INTERVAL).toStdString(); +} + +void FaceCameraController::closeFaceCamera() +{ + faceCap->release(); + + delete faceCap; +} + + +void FaceCameraController::getOneFaceFrm() +{ + faceCap->read(faceMat); + + // clone一个mat, 用于界面显示 + cv::Mat faceMatDisp = faceMat.clone(); + QImage imgDisplay = ImageUtil::MatImageToQImage(faceMatDisp); + + // 发送信号用于界面显示 + emit sendImageToDraw(); + + LOG(DEBUG) << " TAKE ONE FACE FRAME " << faceMat.cols << " * " << faceMat.rows; + + // 发送信号用于人脸检测和生成特征值 +// emit sendImageToDetect(faceMat); +} diff --git a/device/FaceCameraController.h b/device/FaceCameraController.h new file mode 100644 index 0000000..c75fcda --- /dev/null +++ b/device/FaceCameraController.h @@ -0,0 +1,40 @@ +#ifndef CAMERACONTROLLER_H +#define CAMERACONTROLLER_H + +#include + +#include "opencv2/opencv.hpp" + +//#include "casic/face/CasicFaceInterface.h" +//#include "process/memory/ProMemory.h" +//#include "process/face/CasicFaceRecState.h" +#include "utils/ImageUtil.h" +#include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" +#include "utils/easyloggingpp/easylogging++.h" + +class FaceCameraController : public QObject +{ + Q_OBJECT +public: + explicit FaceCameraController(QObject *parent = nullptr); + ~FaceCameraController(); + + // 初始化并打开人脸相机 + void openFaceCamera(); + void closeFaceCamera(); + +private: + cv::VideoCapture * faceCap; + + cv::Mat faceMat; + +public slots: + void getOneFaceFrm(); + +signals: + void sendImageToDraw(); + void sendImageToDetect(cv::Mat imgMat); +}; + +#endif // CAMERACONTROLLER_H diff --git a/device/device.pri b/device/device.pri new file mode 100644 index 0000000..99f9438 --- /dev/null +++ b/device/device.pri @@ -0,0 +1,19 @@ + +HEADERS += $$PWD/FaceCameraController.h +HEADERS += $$PWD/face/FaceDetectRegistProcess.h +HEADERS += $$PWD/face/CasicFaceRecState.h + +SOURCES += $$PWD/FaceCameraController.cpp +SOURCES += $$PWD/face/FaceDetectRegistProcess.cpp +SOURCES += $$PWD/face/CasicFaceRecState.cpp + + +#HEADERS += $$PWD/IrisCameraController.h +#HEADERS += $$PWD/IrisCameraCapEventHandler.h +#HEADERS += $$PWD/MotoController.h +#HEADERS += $$PWD/DeviceEnumerator.h + +#SOURCES += $$PWD/IrisCameraController.cpp +#SOURCES += $$PWD/IrisCameraCapEventHandler.cpp +#SOURCES += $$PWD/MotoController.cpp +#SOURCES += $$PWD/DeviceEnumerator.cpp diff --git a/device/face/CasicFaceRecState.cpp b/device/face/CasicFaceRecState.cpp new file mode 100644 index 0000000..3ba8470 --- /dev/null +++ b/device/face/CasicFaceRecState.cpp @@ -0,0 +1,6 @@ +#include "CasicFaceRecState.h" + +CasicFaceRecState::CasicFaceRecState() +{ + +} diff --git a/device/face/CasicFaceRecState.h b/device/face/CasicFaceRecState.h new file mode 100644 index 0000000..999486e --- /dev/null +++ b/device/face/CasicFaceRecState.h @@ -0,0 +1,41 @@ +#ifndef CASICFACERECSTATE_H +#define CASICFACERECSTATE_H + +#include +#include "casic/face/CasicFaceInfo.h" + +class CasicFaceRecState : public QObject +{ +public: + ~CasicFaceRecState() {}; + CasicFaceRecState(const CasicFaceRecState&)=delete; + CasicFaceRecState& operator=(const CasicFaceRecState&)=delete; + + static CasicFaceRecState& getInstance() { + static CasicFaceRecState instance; + return instance; + } + + void initRecognize(); + std::string toString(); + QJsonObject toJSON(); + + std::string recoginzeId; // 识别过程id + qint64 timeStamp = 0; // 识别开始时间戳 + qint64 timeStampSucc = 0; // 识别成功时的时间戳 + + CasicFaceInfo * faceInfo; // 人脸信息 + QString imgBase64; // 人脸的base64码数据, 用于存库 + + qint8 tryCount = 0; // 识别尝试次数 + qint8 noFaceCount = 0; // 连续没有找到人脸次数 + float recogTimeLast = 0.0; // 识别成功耗时 + +private: + CasicFaceRecState(); + +signals: + +}; + +#endif // CASICFACERECSTATE_H diff --git a/device/face/FaceDetectRegistProcess.cpp b/device/face/FaceDetectRegistProcess.cpp new file mode 100644 index 0000000..3a6748d --- /dev/null +++ b/device/face/FaceDetectRegistProcess.cpp @@ -0,0 +1,70 @@ +#include "FaceDetectRegistProcess.h" + +FaceDetectRegistProcess::FaceDetectRegistProcess(QObject *parent) : QObject(parent) +{ + +} + +void FaceDetectRegistProcess::faceDetect(cv::Mat faceMat) +{ + // 如果已经在人脸检测工作中则退出 + if (FACE_DETECT_FLAG == true) + { + LOG(DEBUG) << "ALREADY IN FACE DETECT PROCESS"; + return; + } + + // 开始人脸检测 + FACE_DETECT_FLAG = true; + LOG(DEBUG) << "START FACE DETECT"; + + // 调用人脸检测算法 + CasicFaceInfo faceInfo = casic::face::CasicFaceInterface::getInstance().faceDetect(faceMat); + if (faceInfo.hasFace == false) + { + // 没有找到人脸进行如下处理 + + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; + return; + } + + // 继续进行后面的步骤 + // 开始人脸活体检测, 提高活体检测的阈值到0.3/0.6 + casic::face::CasicFaceInterface::getInstance().setAntiThreshold(0.3, 0.6); + faceInfo = casic::face::CasicFaceInterface::getInstance().faceAntiSpoofing(faceInfo); + // 活体检测判断为假脸 + if (faceInfo.antiStatus != seeta::FaceAntiSpoofing::Status::REAL) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸活体检测未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 判定为真实人脸, 开始质量评估 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceQuality(faceInfo); + + // 质量评估不为HIGH则返回 + if (faceInfo.quality.level != seeta::QualityLevel::HIGH) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸质量评估未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 调用算法提取特征值, 1024个字节的float数组 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceFeatureExtract(faceInfo); + + LOG(DEBUG) << QString("[faceDetect]特征提取成功").toStdString(); + + // 发送信号去进行比对, 执行后续业务逻辑 + emit this->extractFeatureSuccess(CasicFaceRecState::getInstance()); + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; +} diff --git a/device/face/FaceDetectRegistProcess.h b/device/face/FaceDetectRegistProcess.h new file mode 100644 index 0000000..5d42bbe --- /dev/null +++ b/device/face/FaceDetectRegistProcess.h @@ -0,0 +1,28 @@ +#ifndef FACEDETECTREGISTPROCESS_H +#define FACEDETECTREGISTPROCESS_H + +#include +#include "opencv2/opencv.hpp" + +#include "utils/easyloggingpp/easylogging++.h" + +#include "CasicFaceRecState.h" +#include "casic/face/CasicFaceInterface.h" + +static bool FACE_DETECT_FLAG = false; + +class FaceDetectRegistProcess : public QObject +{ + Q_OBJECT +public: + explicit FaceDetectRegistProcess(QObject *parent = nullptr); + +public slots: + void faceDetect(cv::Mat faceMat); + +signals: + void extractFeatureSuccess(CasicFaceRecState& faceRecState); + +}; + +#endif // FACEDETECTREGISTPROCESS_H diff --git a/qss/dialogTips.css b/qss/dialogTips.css index 9c9b4d6..a80bfe1 100644 --- a/qss/dialogTips.css +++ b/qss/dialogTips.css @@ -1,20 +1,36 @@ +QDialog { + border: 1px solid #5F1BC6; +} + QLabel { color: #6868A6; font-family: "Microsoft YaHei"; } - -QLabel#labDate { - font-size: 36px; +QLabel#labTitle { + font-size: 20px; + line-height: 50px; +} +QLabel#labTipsContent { + font-size: 32px; } -QLabel#labTime { - font-size: 100px; -} - -QToolButton { - color: #6868A6; +QDialogButtonBox#btnBoxOk QPushButton { + color: #FFFFFF; font-family: "Microsoft YaHei"; - font-size: 48px; - background: transparent; - border-style: none; + font-size: 24px; + background: #6868A6; + border-radius: 12px; + min-width: 200px; + min-height: 50px; +} + +QDialogButtonBox#btnBoxConfirm QPushButton { + color: #FFFFFF; + font-family: "Microsoft YaHei"; + font-size: 24px; + background: #6868A6; + border-radius: 12px; + min-width: 120px; + min-height: 50px; + margin-right: 30px; } diff --git a/utils/ImageUtil.cpp b/utils/ImageUtil.cpp new file mode 100644 index 0000000..7604572 --- /dev/null +++ b/utils/ImageUtil.cpp @@ -0,0 +1,97 @@ +#include "ImageUtil.h" + +ImageUtil::ImageUtil() +{ + +} + +QImage ImageUtil::MatImageToQImage(const cv::Mat &src) +{ + //CV_8UC1 8位无符号的单通道---灰度图片 + if(src.type() == CV_8UC1) + { + QImage qImage((const unsigned char *)(src.data), src.cols, src.rows, src.cols, QImage::Format_Grayscale8); + return qImage; + } + //为3通道的彩色图片 + else if(src.type() == CV_8UC3) + { + //得到图像的的首地址 + const uchar *pSrc = (const uchar*)src.data; + //以src构造图片 + QImage qImage(pSrc, src.cols, src.rows, src.step, QImage::Format_RGB888); + //在不改变实际图像数据的条件下, 交换红蓝通道 + return qImage.rgbSwapped(); + } + //四通道图片, 带Alpha通道的RGB彩色图像 + else if(src.type() == CV_8UC4) + { + const uchar *pSrc = (const uchar*)src.data; + QImage qImage(pSrc, src.cols, src.rows, src.step, QImage::Format_ARGB32); + //返回图像的子区域作为一个新图像 + return qImage.copy(); + } + else + { + return QImage(); + } +} +/* +QImage ImageUtil::UcharToQImage(unsigned char* data, int width, int height, QImage::Format format) +{ + QImage qImage(data, width, height, format); + //在不改变实际图像数据的条件下, 交换红蓝通道 + return qImage.rgbSwapped(); +} + +cv::Mat ImageUtil::QImageToMat(QImage image) +{ + cv::Mat mat; + switch(image.format()) + { + case QImage::Format_ARGB32: + case QImage::Format_RGB32: + case QImage::Format_ARGB32_Premultiplied: + mat = cv::Mat(image.height(), image.width(), CV_8UC4, (void*)image.constBits(), image.bytesPerLine()); + break; + case QImage::Format_RGB888: + mat = cv::Mat(image.height(), image.width(), CV_8UC3, (void*)image.constBits(), image.bytesPerLine()); + cv::cvtColor(mat, mat, cv::COLOR_BGR2RGB); + break; + case QImage::Format_Indexed8: + mat = cv::Mat(image.height(), image.width(), CV_8UC1, (void*)image.constBits(), image.bytesPerLine()); + break; + case QImage::Format_Grayscale8: + mat = cv::Mat(image.height(), image.width(), CV_8UC1, (void *)image.bits(), image.bytesPerLine()); + break; + } + + mat = mat.clone(); + + return mat; +} + +QString ImageUtil::QImageToBase64(QImage image) +{ + QByteArray ba; + QBuffer buf(&ba); + image.save(&buf, "bmp"); + QString base64String = ba.toBase64(); + return base64String; +} + +cv::Mat ImageUtil::MatImageRect(const cv::Mat &src, cv::Rect rect, int delta) +{ + cv::Mat matClone = src.clone(); + cv::Rect bigRect; + + bigRect.x = rect.x - delta < 0 ? 0 : rect.x - delta; + bigRect.y = rect.y - delta < 0 ? 0 : rect.y - delta; + bigRect.width = rect.x + rect.width + 2 * delta > src.cols ? src.cols - rect.x : rect.width + 2 * delta; + bigRect.height = rect.y + rect.height + 2 * delta > src.rows ? src.rows - rect.y : rect.height + 2 * delta; + + cv::Mat roi = matClone(bigRect); + + return roi; +} +*/ diff --git a/utils/ImageUtil.h b/utils/ImageUtil.h new file mode 100644 index 0000000..dbfdd48 --- /dev/null +++ b/utils/ImageUtil.h @@ -0,0 +1,22 @@ +#ifndef IMAGEUTIL_H +#define IMAGEUTIL_H + +#include +#include +#include "opencv2/opencv.hpp" + +class ImageUtil +{ +public: + ImageUtil(); + + static QImage MatImageToQImage(const cv::Mat &src); +// static QImage UcharToQImage(unsigned char* data, int width, int height, QImage::Format format); + +// static cv::Mat QImageToMat(QImage image); +// static QString QImageToBase64(QImage image); + +// static cv::Mat MatImageRect(const cv::Mat &src, cv::Rect rect, int delta); +}; + +#endif // IMAGEUTIL_H diff --git a/utils/QDblClickLabel.cpp b/utils/QDblClickLabel.cpp deleted file mode 100644 index d68899c..0000000 --- a/utils/QDblClickLabel.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "QDblClickLabel.h" - -QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) -{ - -} - -QDblClickLabel::~QDblClickLabel() -{ - -} - -void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) -{ - emit doubleClicked(); -} diff --git a/utils/QDblClickLabel.h b/utils/QDblClickLabel.h deleted file mode 100644 index bd7c532..0000000 --- a/utils/QDblClickLabel.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef QDBLCLICKLABEL_H -#define QDBLCLICKLABEL_H - -#include -#include - -class QDblClickLabel : public QLabel -{ - Q_OBJECT -public: - QDblClickLabel(QWidget * parent = 0); - ~QDblClickLabel(); - void mouseDoubleClickEvent(QMouseEvent * event); - -signals: - void doubleClicked(); -}; - -#endif // QDBLCLICKLABEL_H diff --git a/utils/TimeCounterUtil.cpp b/utils/TimeCounterUtil.cpp new file mode 100644 index 0000000..275945f --- /dev/null +++ b/utils/TimeCounterUtil.cpp @@ -0,0 +1,8 @@ +#include "TimeCounterUtil.h" + +TimeCounterUtil::TimeCounterUtil() +{ + faceCapCounter = new QTimer(); + irisCapCounter = new QTimer(); + clockCounter = new QTimer(); +} diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/casic/face/CasicFaceInterface.h b/casic/face/CasicFaceInterface.h new file mode 100644 index 0000000..d6d4494 --- /dev/null +++ b/casic/face/CasicFaceInterface.h @@ -0,0 +1,91 @@ +#ifndef CASICFACEINTERFACE_H +#define CASICFACEINTERFACE_H + +#include "opencv2/opencv.hpp" +#include "seeta/FaceDetector.h" +#include "seeta/FaceLandmarker.h" +#include "seeta/QualityAssessor.h" +#include "seeta/QualityOfBrightness.h" +#include "seeta/QualityOfClarity.h" +#include "seeta/QualityOfIntegrity.h" +#include "seeta/QualityOfResolution.h" +#include "seeta/QualityOfPoseEx.h" +#include "seeta/FaceAntiSpoofing.h" +#include "seeta/FaceRecognizer.h" + +#include "CasicFaceInfo.h" + +static auto red = CV_RGB(255, 0, 0); +static auto green = CV_RGB(0, 255, 0); +static auto blue = CV_RGB(0, 0, 255); + +namespace casic { + namespace face { + class CasicFaceInterface + { + public: + ~CasicFaceInterface(); + CasicFaceInterface(const CasicFaceInterface&)=delete; + CasicFaceInterface& operator=(const CasicFaceInterface&)=delete; + + static CasicFaceInterface& getInstance() { + static CasicFaceInterface instance; + return instance; + } + + void setDetectorModelPath(std::string detectorModelPath); + void setMarkPts5ModelPath(std::string markPts5ModelPath); + void setPoseModelPath(std::string poseModelPath); + void setFas1stModelPath(std::string fas1stModelPath); + void setFas2ndModelPath(std::string fas2ndModelPath); + void setRecognizerModelPath(std::string recognizerModelPath); + + void setAntiThreshold(float clarity, float reality); + + CasicFaceInfo faceDetect(cv::Mat frame); + CasicFaceInfo faceQuality(CasicFaceInfo faceInfo); + CasicFaceInfo faceAntiSpoofing(CasicFaceInfo faceInfo); + CasicFaceInfo faceFeatureExtract(CasicFaceInfo faceInfo); + float faceSimCalculate(float* feature, float* otherFeature); + + cv::Rect faceDetectByCVCascade(cv::Mat frame); + cv::Rect eyeDetectByCVCascade(cv::Mat frame); + void setMinFaceSize(int minFaceSize); + void setMinEyeSize(int minEyeSize); + private: + CasicFaceInterface(); + + int deviceId = 0; + seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO; + + float clarity = 0.3f; + float reality = 0.3f; + + std::string cvFaceCascadeName = "./model/haarcascade_frontalface_default.xml"; + std::string cvEyeCascadeName = "./model/haarcascade_eye.xml"; + int minFaceSize = 320; + int maxFaceSize = 720; + int minEyeSize = 100; + int maxEyeSize = 600; + + std::string detectorModelPath = "./model/face_detector.csta"; + std::string markPts5ModelPath = "./model/face_landmarker_pts5.csta"; + std::string poseModelPath = "./model/pose_estimation.csta"; + std::string fas1stModelPath = "./model/fas_first.csta"; + std::string fas2ndModelPath = "./model/fas_second.csta"; + std::string recognizerModelPath = "./model/face_recognizer.csta"; + + seeta::FaceDetector * detector = nullptr; + seeta::FaceLandmarker * marker = nullptr; + seeta::QualityOfPoseEx * poseEx = nullptr; + seeta::FaceAntiSpoofing * processor = nullptr; + seeta::FaceRecognizer * recognizer = nullptr; + + cv::CascadeClassifier * cascade; + cv::CascadeClassifier * eyeCascade; + }; + } +} + + +#endif // CASICFACEINTERFACE_H diff --git a/casic/face/casicFace.pri b/casic/face/casicFace.pri new file mode 100644 index 0000000..da337d9 --- /dev/null +++ b/casic/face/casicFace.pri @@ -0,0 +1,9 @@ +HEADERS += $$PWD/CasicFaceInfo.h +HEADERS += $$PWD/CasicFaceInterface.h + +SOURCES += $$PWD/CasicFaceInterface.cpp + +INCLUDEPATH += seeta/ + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600 -lSeetaFaceLandmarker600 -lSeetaFaceAntiSpoofingX600 -lSeetaFaceRecognizer610 -lSeetaQualityAssessor300 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600d -lSeetaFaceLandmarker600d -lSeetaFaceAntiSpoofingX600d -lSeetaFaceRecognizer610d -lSeetaQualityAssessor300d diff --git a/dao/FaceDataImgDao.cpp b/dao/FaceDataImgDao.cpp index 83f22a0..d863752 100644 --- a/dao/FaceDataImgDao.cpp +++ b/dao/FaceDataImgDao.cpp @@ -78,20 +78,14 @@ // 返回结果 QVariantMap result; - // 获取结果集的大小 - query.last(); - int count = query.at() + 1; - - if (count >=1) + if (query.next()) { - query.first(); - result.insert("id", query.value("id").toString()); result.insert("person_id", query.value("person_id").toString()); result.insert("face_image", query.value("face_image").toString()); } - LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[%1][id=%2][%3]").arg(count).arg(query.value("id").toString()).arg(sql).toStdString(); + LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/dao/IrisDataImgDao.cpp b/dao/IrisDataImgDao.cpp index af0d45b..8a0c9af 100644 --- a/dao/IrisDataImgDao.cpp +++ b/dao/IrisDataImgDao.cpp @@ -97,7 +97,7 @@ result.insert("right_image1", query.value("right_image1").toString()); } - LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId:%1][%2]").arg(personId).arg(sql).toStdString(); + LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/device/FaceCameraController.cpp b/device/FaceCameraController.cpp new file mode 100644 index 0000000..bb325a4 --- /dev/null +++ b/device/FaceCameraController.cpp @@ -0,0 +1,60 @@ +#include "FaceCameraController.h" +#include +#include +#include + +FaceCameraController::FaceCameraController(QObject *parent) : QObject(parent) +{ + // 获取定时器, 绑定定时函数 + connect(TimeCounterUtil::getInstance().faceCapCounter, &QTimer::timeout, + this, &FaceCameraController::getOneFaceFrm); +} + +FaceCameraController::~FaceCameraController() +{ + this->closeFaceCamera(); +} + + +void FaceCameraController::openFaceCamera() +{ + this->faceCap = new cv::VideoCapture(SettingConfig::getInstance().FACE_CAMERA_INDEX, cv::CAP_DSHOW); + faceCap->set(cv::CAP_PROP_FRAME_WIDTH, SettingConfig::getInstance().FACE_FRAME_WIDTH); + faceCap->set(cv::CAP_PROP_FRAME_HEIGHT, SettingConfig::getInstance().FACE_FRAME_HEIGHT); + + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]打开相机[%1][%2 * %3]") + .arg(SettingConfig::getInstance().FACE_CAMERA_INDEX) + .arg(SettingConfig::getInstance().FACE_FRAME_WIDTH) + .arg(SettingConfig::getInstance().FACE_FRAME_HEIGHT).toStdString(); + + // 启动定时器 + TimeCounterUtil::getInstance().faceCapCounter->setInterval(SettingConfig::getInstance().FACE_FRAME_INTERVAL); + TimeCounterUtil::getInstance().faceCapCounter->start(); + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]相机开始拍图[%1ms]") + .arg(SettingConfig::getInstance().FACE_FRAME_INTERVAL).toStdString(); +} + +void FaceCameraController::closeFaceCamera() +{ + faceCap->release(); + + delete faceCap; +} + + +void FaceCameraController::getOneFaceFrm() +{ + faceCap->read(faceMat); + + // clone一个mat, 用于界面显示 + cv::Mat faceMatDisp = faceMat.clone(); + QImage imgDisplay = ImageUtil::MatImageToQImage(faceMatDisp); + + // 发送信号用于界面显示 + emit sendImageToDraw(); + + LOG(DEBUG) << " TAKE ONE FACE FRAME " << faceMat.cols << " * " << faceMat.rows; + + // 发送信号用于人脸检测和生成特征值 +// emit sendImageToDetect(faceMat); +} diff --git a/device/FaceCameraController.h b/device/FaceCameraController.h new file mode 100644 index 0000000..c75fcda --- /dev/null +++ b/device/FaceCameraController.h @@ -0,0 +1,40 @@ +#ifndef CAMERACONTROLLER_H +#define CAMERACONTROLLER_H + +#include + +#include "opencv2/opencv.hpp" + +//#include "casic/face/CasicFaceInterface.h" +//#include "process/memory/ProMemory.h" +//#include "process/face/CasicFaceRecState.h" +#include "utils/ImageUtil.h" +#include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" +#include "utils/easyloggingpp/easylogging++.h" + +class FaceCameraController : public QObject +{ + Q_OBJECT +public: + explicit FaceCameraController(QObject *parent = nullptr); + ~FaceCameraController(); + + // 初始化并打开人脸相机 + void openFaceCamera(); + void closeFaceCamera(); + +private: + cv::VideoCapture * faceCap; + + cv::Mat faceMat; + +public slots: + void getOneFaceFrm(); + +signals: + void sendImageToDraw(); + void sendImageToDetect(cv::Mat imgMat); +}; + +#endif // CAMERACONTROLLER_H diff --git a/device/device.pri b/device/device.pri new file mode 100644 index 0000000..99f9438 --- /dev/null +++ b/device/device.pri @@ -0,0 +1,19 @@ + +HEADERS += $$PWD/FaceCameraController.h +HEADERS += $$PWD/face/FaceDetectRegistProcess.h +HEADERS += $$PWD/face/CasicFaceRecState.h + +SOURCES += $$PWD/FaceCameraController.cpp +SOURCES += $$PWD/face/FaceDetectRegistProcess.cpp +SOURCES += $$PWD/face/CasicFaceRecState.cpp + + +#HEADERS += $$PWD/IrisCameraController.h +#HEADERS += $$PWD/IrisCameraCapEventHandler.h +#HEADERS += $$PWD/MotoController.h +#HEADERS += $$PWD/DeviceEnumerator.h + +#SOURCES += $$PWD/IrisCameraController.cpp +#SOURCES += $$PWD/IrisCameraCapEventHandler.cpp +#SOURCES += $$PWD/MotoController.cpp +#SOURCES += $$PWD/DeviceEnumerator.cpp diff --git a/device/face/CasicFaceRecState.cpp b/device/face/CasicFaceRecState.cpp new file mode 100644 index 0000000..3ba8470 --- /dev/null +++ b/device/face/CasicFaceRecState.cpp @@ -0,0 +1,6 @@ +#include "CasicFaceRecState.h" + +CasicFaceRecState::CasicFaceRecState() +{ + +} diff --git a/device/face/CasicFaceRecState.h b/device/face/CasicFaceRecState.h new file mode 100644 index 0000000..999486e --- /dev/null +++ b/device/face/CasicFaceRecState.h @@ -0,0 +1,41 @@ +#ifndef CASICFACERECSTATE_H +#define CASICFACERECSTATE_H + +#include +#include "casic/face/CasicFaceInfo.h" + +class CasicFaceRecState : public QObject +{ +public: + ~CasicFaceRecState() {}; + CasicFaceRecState(const CasicFaceRecState&)=delete; + CasicFaceRecState& operator=(const CasicFaceRecState&)=delete; + + static CasicFaceRecState& getInstance() { + static CasicFaceRecState instance; + return instance; + } + + void initRecognize(); + std::string toString(); + QJsonObject toJSON(); + + std::string recoginzeId; // 识别过程id + qint64 timeStamp = 0; // 识别开始时间戳 + qint64 timeStampSucc = 0; // 识别成功时的时间戳 + + CasicFaceInfo * faceInfo; // 人脸信息 + QString imgBase64; // 人脸的base64码数据, 用于存库 + + qint8 tryCount = 0; // 识别尝试次数 + qint8 noFaceCount = 0; // 连续没有找到人脸次数 + float recogTimeLast = 0.0; // 识别成功耗时 + +private: + CasicFaceRecState(); + +signals: + +}; + +#endif // CASICFACERECSTATE_H diff --git a/device/face/FaceDetectRegistProcess.cpp b/device/face/FaceDetectRegistProcess.cpp new file mode 100644 index 0000000..3a6748d --- /dev/null +++ b/device/face/FaceDetectRegistProcess.cpp @@ -0,0 +1,70 @@ +#include "FaceDetectRegistProcess.h" + +FaceDetectRegistProcess::FaceDetectRegistProcess(QObject *parent) : QObject(parent) +{ + +} + +void FaceDetectRegistProcess::faceDetect(cv::Mat faceMat) +{ + // 如果已经在人脸检测工作中则退出 + if (FACE_DETECT_FLAG == true) + { + LOG(DEBUG) << "ALREADY IN FACE DETECT PROCESS"; + return; + } + + // 开始人脸检测 + FACE_DETECT_FLAG = true; + LOG(DEBUG) << "START FACE DETECT"; + + // 调用人脸检测算法 + CasicFaceInfo faceInfo = casic::face::CasicFaceInterface::getInstance().faceDetect(faceMat); + if (faceInfo.hasFace == false) + { + // 没有找到人脸进行如下处理 + + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; + return; + } + + // 继续进行后面的步骤 + // 开始人脸活体检测, 提高活体检测的阈值到0.3/0.6 + casic::face::CasicFaceInterface::getInstance().setAntiThreshold(0.3, 0.6); + faceInfo = casic::face::CasicFaceInterface::getInstance().faceAntiSpoofing(faceInfo); + // 活体检测判断为假脸 + if (faceInfo.antiStatus != seeta::FaceAntiSpoofing::Status::REAL) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸活体检测未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 判定为真实人脸, 开始质量评估 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceQuality(faceInfo); + + // 质量评估不为HIGH则返回 + if (faceInfo.quality.level != seeta::QualityLevel::HIGH) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸质量评估未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 调用算法提取特征值, 1024个字节的float数组 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceFeatureExtract(faceInfo); + + LOG(DEBUG) << QString("[faceDetect]特征提取成功").toStdString(); + + // 发送信号去进行比对, 执行后续业务逻辑 + emit this->extractFeatureSuccess(CasicFaceRecState::getInstance()); + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; +} diff --git a/device/face/FaceDetectRegistProcess.h b/device/face/FaceDetectRegistProcess.h new file mode 100644 index 0000000..5d42bbe --- /dev/null +++ b/device/face/FaceDetectRegistProcess.h @@ -0,0 +1,28 @@ +#ifndef FACEDETECTREGISTPROCESS_H +#define FACEDETECTREGISTPROCESS_H + +#include +#include "opencv2/opencv.hpp" + +#include "utils/easyloggingpp/easylogging++.h" + +#include "CasicFaceRecState.h" +#include "casic/face/CasicFaceInterface.h" + +static bool FACE_DETECT_FLAG = false; + +class FaceDetectRegistProcess : public QObject +{ + Q_OBJECT +public: + explicit FaceDetectRegistProcess(QObject *parent = nullptr); + +public slots: + void faceDetect(cv::Mat faceMat); + +signals: + void extractFeatureSuccess(CasicFaceRecState& faceRecState); + +}; + +#endif // FACEDETECTREGISTPROCESS_H diff --git a/qss/dialogTips.css b/qss/dialogTips.css index 9c9b4d6..a80bfe1 100644 --- a/qss/dialogTips.css +++ b/qss/dialogTips.css @@ -1,20 +1,36 @@ +QDialog { + border: 1px solid #5F1BC6; +} + QLabel { color: #6868A6; font-family: "Microsoft YaHei"; } - -QLabel#labDate { - font-size: 36px; +QLabel#labTitle { + font-size: 20px; + line-height: 50px; +} +QLabel#labTipsContent { + font-size: 32px; } -QLabel#labTime { - font-size: 100px; -} - -QToolButton { - color: #6868A6; +QDialogButtonBox#btnBoxOk QPushButton { + color: #FFFFFF; font-family: "Microsoft YaHei"; - font-size: 48px; - background: transparent; - border-style: none; + font-size: 24px; + background: #6868A6; + border-radius: 12px; + min-width: 200px; + min-height: 50px; +} + +QDialogButtonBox#btnBoxConfirm QPushButton { + color: #FFFFFF; + font-family: "Microsoft YaHei"; + font-size: 24px; + background: #6868A6; + border-radius: 12px; + min-width: 120px; + min-height: 50px; + margin-right: 30px; } diff --git a/utils/ImageUtil.cpp b/utils/ImageUtil.cpp new file mode 100644 index 0000000..7604572 --- /dev/null +++ b/utils/ImageUtil.cpp @@ -0,0 +1,97 @@ +#include "ImageUtil.h" + +ImageUtil::ImageUtil() +{ + +} + +QImage ImageUtil::MatImageToQImage(const cv::Mat &src) +{ + //CV_8UC1 8位无符号的单通道---灰度图片 + if(src.type() == CV_8UC1) + { + QImage qImage((const unsigned char *)(src.data), src.cols, src.rows, src.cols, QImage::Format_Grayscale8); + return qImage; + } + //为3通道的彩色图片 + else if(src.type() == CV_8UC3) + { + //得到图像的的首地址 + const uchar *pSrc = (const uchar*)src.data; + //以src构造图片 + QImage qImage(pSrc, src.cols, src.rows, src.step, QImage::Format_RGB888); + //在不改变实际图像数据的条件下, 交换红蓝通道 + return qImage.rgbSwapped(); + } + //四通道图片, 带Alpha通道的RGB彩色图像 + else if(src.type() == CV_8UC4) + { + const uchar *pSrc = (const uchar*)src.data; + QImage qImage(pSrc, src.cols, src.rows, src.step, QImage::Format_ARGB32); + //返回图像的子区域作为一个新图像 + return qImage.copy(); + } + else + { + return QImage(); + } +} +/* +QImage ImageUtil::UcharToQImage(unsigned char* data, int width, int height, QImage::Format format) +{ + QImage qImage(data, width, height, format); + //在不改变实际图像数据的条件下, 交换红蓝通道 + return qImage.rgbSwapped(); +} + +cv::Mat ImageUtil::QImageToMat(QImage image) +{ + cv::Mat mat; + switch(image.format()) + { + case QImage::Format_ARGB32: + case QImage::Format_RGB32: + case QImage::Format_ARGB32_Premultiplied: + mat = cv::Mat(image.height(), image.width(), CV_8UC4, (void*)image.constBits(), image.bytesPerLine()); + break; + case QImage::Format_RGB888: + mat = cv::Mat(image.height(), image.width(), CV_8UC3, (void*)image.constBits(), image.bytesPerLine()); + cv::cvtColor(mat, mat, cv::COLOR_BGR2RGB); + break; + case QImage::Format_Indexed8: + mat = cv::Mat(image.height(), image.width(), CV_8UC1, (void*)image.constBits(), image.bytesPerLine()); + break; + case QImage::Format_Grayscale8: + mat = cv::Mat(image.height(), image.width(), CV_8UC1, (void *)image.bits(), image.bytesPerLine()); + break; + } + + mat = mat.clone(); + + return mat; +} + +QString ImageUtil::QImageToBase64(QImage image) +{ + QByteArray ba; + QBuffer buf(&ba); + image.save(&buf, "bmp"); + QString base64String = ba.toBase64(); + return base64String; +} + +cv::Mat ImageUtil::MatImageRect(const cv::Mat &src, cv::Rect rect, int delta) +{ + cv::Mat matClone = src.clone(); + cv::Rect bigRect; + + bigRect.x = rect.x - delta < 0 ? 0 : rect.x - delta; + bigRect.y = rect.y - delta < 0 ? 0 : rect.y - delta; + bigRect.width = rect.x + rect.width + 2 * delta > src.cols ? src.cols - rect.x : rect.width + 2 * delta; + bigRect.height = rect.y + rect.height + 2 * delta > src.rows ? src.rows - rect.y : rect.height + 2 * delta; + + cv::Mat roi = matClone(bigRect); + + return roi; +} +*/ diff --git a/utils/ImageUtil.h b/utils/ImageUtil.h new file mode 100644 index 0000000..dbfdd48 --- /dev/null +++ b/utils/ImageUtil.h @@ -0,0 +1,22 @@ +#ifndef IMAGEUTIL_H +#define IMAGEUTIL_H + +#include +#include +#include "opencv2/opencv.hpp" + +class ImageUtil +{ +public: + ImageUtil(); + + static QImage MatImageToQImage(const cv::Mat &src); +// static QImage UcharToQImage(unsigned char* data, int width, int height, QImage::Format format); + +// static cv::Mat QImageToMat(QImage image); +// static QString QImageToBase64(QImage image); + +// static cv::Mat MatImageRect(const cv::Mat &src, cv::Rect rect, int delta); +}; + +#endif // IMAGEUTIL_H diff --git a/utils/QDblClickLabel.cpp b/utils/QDblClickLabel.cpp deleted file mode 100644 index d68899c..0000000 --- a/utils/QDblClickLabel.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "QDblClickLabel.h" - -QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) -{ - -} - -QDblClickLabel::~QDblClickLabel() -{ - -} - -void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) -{ - emit doubleClicked(); -} diff --git a/utils/QDblClickLabel.h b/utils/QDblClickLabel.h deleted file mode 100644 index bd7c532..0000000 --- a/utils/QDblClickLabel.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef QDBLCLICKLABEL_H -#define QDBLCLICKLABEL_H - -#include -#include - -class QDblClickLabel : public QLabel -{ - Q_OBJECT -public: - QDblClickLabel(QWidget * parent = 0); - ~QDblClickLabel(); - void mouseDoubleClickEvent(QMouseEvent * event); - -signals: - void doubleClicked(); -}; - -#endif // QDBLCLICKLABEL_H diff --git a/utils/TimeCounterUtil.cpp b/utils/TimeCounterUtil.cpp new file mode 100644 index 0000000..275945f --- /dev/null +++ b/utils/TimeCounterUtil.cpp @@ -0,0 +1,8 @@ +#include "TimeCounterUtil.h" + +TimeCounterUtil::TimeCounterUtil() +{ + faceCapCounter = new QTimer(); + irisCapCounter = new QTimer(); + clockCounter = new QTimer(); +} diff --git a/utils/TimeCounterUtil.h b/utils/TimeCounterUtil.h new file mode 100644 index 0000000..28b29f1 --- /dev/null +++ b/utils/TimeCounterUtil.h @@ -0,0 +1,29 @@ +#ifndef TIMECOUNTERUTIL_H +#define TIMECOUNTERUTIL_H + +#include +#include + +class TimeCounterUtil : public QObject +{ +public: + + ~TimeCounterUtil() {}; + TimeCounterUtil(const TimeCounterUtil&)=delete; + TimeCounterUtil& operator=(const TimeCounterUtil&)=delete; + + static TimeCounterUtil& getInstance() { + static TimeCounterUtil instance; + return instance; + } + + QTimer * faceCapCounter; + QTimer * irisCapCounter; + QTimer * clockCounter; + +private: + TimeCounterUtil(); + +}; + +#endif // TIMECOUNTERUTIL_H diff --git a/AddPersonForm.cpp b/AddPersonForm.cpp index 7d9172b..94437d8 100644 --- a/AddPersonForm.cpp +++ b/AddPersonForm.cpp @@ -1,5 +1,6 @@ #include "AddPersonForm.h" #include "ui_AddPersonForm.h" +#include "CasicBioRecWin.h" AddPersonForm::AddPersonForm(QWidget *parent) : QWidget(parent), @@ -18,8 +19,13 @@ SelectDeptUtil::getInstance().initSelectDept(ui->selectDept, CacheManager::getInstance().getDeptCachePtr()); + faceLabel = new QLabel(this); + faceLabel->hide(); + // 绑定图像双击槽函数 - connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); + connect(ui->labPhotoFace, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoFaceDoubleClicked); + connect(ui->labPhotoEyeLeft, &QDblClickLabel::doubleClicked, this, &AddPersonForm::onPhotoIrisDoubleClicked); + connect(ui->labPhotoEyeRight, &QDblClickLabel::doubleClicked, this, &AddPersonForm::on_btnHome_clicked); } AddPersonForm::~AddPersonForm() @@ -146,6 +152,95 @@ ui->labPhotoEyeRight->setPixmap(QPixmap(":/images/photoEyeRight.png")); } +void AddPersonForm::drawImageOnForm() +{ +// if (this->faceLabel->isVisible() == true) +// { + LOG(DEBUG) << "DRAW IMAGE ON FORM ";// << imgDisplay.width() << "*" << imgDisplay.height(); +// imgDisplay = imgDisplay.mirrored(true, false); +// faceLabel->setPixmap(QPixmap::fromImage(imgDisplay)); +// } +} + +bool AddPersonForm::validateForm() +{ + + return true; +} + +void AddPersonForm::registPerson() +{ + SysPersonDao personDao; + + QVariantMap perToRegist; + + // 添加姓名 员工编号 所在部门 性别等信息 + perToRegist.insert("name", ui->inputName->text()); + perToRegist.insert("person_code", ui->inputCardNo->text()); + perToRegist.insert("deptid", ui->selectDept->currentData().toString()); + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToRegist.insert("gender", gender); + + // 数据库操作 + QString perIdReg = personDao.save(perToRegist); + + // 弹出提示框 + bool succ = perIdReg == "-1" ? false : true; + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + int ret = tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); + + // 注册人脸信息 + + // 注册虹膜信息 + + if (ret == 1) + { + emit switchToUserListForm(); + } +} + +void AddPersonForm::editPersonInfo() +{ + SysPersonDao personDao; + + // 只能重新选择所在部门 + QVariantMap perToEdit; + perToEdit.insert("deptid", ui->selectDept->currentData().toString()); + + // 如果性别为空则可以重新选择 + QString gender = ""; + if (ui->radioMale->isChecked()) + { + gender = "1"; + } else if (ui->radioFemale->isChecked()) + { + gender = "2"; + } + perToEdit.insert("gender", gender); + + // 数据库操作 + bool succ = personDao.edit(perToEdit, personId); + + // 弹出提示框 + OperationTipsDialog tipsDlg; + tipsDlg.setTipsDialogType(succ); + tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); + tipsDlg.exec(); + + LOG(INFO) << QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败").toStdString(); +} + void AddPersonForm::on_btnBack_clicked() { emit switchToUserListForm(); @@ -158,61 +253,33 @@ void AddPersonForm::on_btnSave_clicked() { - SysPersonDao personDao; - if (personId.isEmpty() == false) + if (validateForm() == false) { - // 人员信息编辑 - QVariantMap perToEdit; - perToEdit.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToEdit.insert("gender", gender); - bool succ = personDao.edit(perToEdit, personId); - - // 弹出提示框 - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员信息编辑%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - tipsDlg.exec(); - } else { - // 人员注册 - QVariantMap perToRegist; - - perToRegist.insert("name", ui->inputName->text()); - perToRegist.insert("person_code", ui->inputCardNo->text()); - perToRegist.insert("deptid", ui->selectDept->currentData().toString()); - QString gender = ""; - if (ui->radioMale->isChecked()) - { - gender = "1"; - } else if (ui->radioFemale->isChecked()) - { - gender = "2"; - } - perToRegist.insert("gender", gender); - QString perIdReg = personDao.save(perToRegist); - - // 注册人脸信息 - - // 注册虹膜信息 - - // 弹出提示框 - bool succ = perIdReg == "-1" ? false : true; - OperationTipsDialog tipsDlg; - tipsDlg.setTipsDialogType(succ); - tipsDlg.setTipsText(QString("%1 人员注册%2").arg(ui->inputName->text()).arg(succ == true ? "成功" : "失败")); - int ret = tipsDlg.exec(); - - if (ret == 1) - { - emit switchToUserListForm(); - } + return ; } + // 表单验证通过后执行后续保存的操作 + if (personId.isEmpty() == false) + { + editPersonInfo(); + } else { + registPerson(); + } +} + +void AddPersonForm::onPhotoFaceDoubleClicked() +{ + // 1人脸图像显示的容器 + faceLabel->resize(1280, 720); + faceLabel->move(0, 80); + faceLabel->setStyleSheet("background: rgba(0, 255, 0, 0.5)"); + faceLabel->raise(); + faceLabel->show(); + + LOG(DEBUG) << "FACE IMAGE DOUBLE CLICKED"; +} + +void AddPersonForm::onPhotoIrisDoubleClicked() +{ + LOG(DEBUG) << "DOUBLE CLICKED IRIS"; } diff --git a/AddPersonForm.h b/AddPersonForm.h index 1e26253..b9cc49f 100644 --- a/AddPersonForm.h +++ b/AddPersonForm.h @@ -3,10 +3,12 @@ #include #include +#include "QDblClickLabel.h" #include "OperationTipsDialog.h" #include "dao/util/CacheManager.h" #include "utils/SelectDeptUtil.h" -#include "utils/QDblClickLabel.h" +#include "utils/SettingConfig.h" +#include "utils/easyloggingpp/easylogging++.h" namespace Ui { class AddPersonForm; @@ -26,6 +28,9 @@ void loadPersonInfo(QString personId); void clearPersonInfo(); +public slots: + void drawImageOnForm(); + private slots: void on_btnBack_clicked(); @@ -33,16 +38,24 @@ void on_btnSave_clicked(); + void onPhotoFaceDoubleClicked(); + void onPhotoIrisDoubleClicked(); + private: Ui::AddPersonForm *ui; QString personId; - void initSelectDetp(); + QLabel * faceLabel; // 采集人脸时显示的画面 + + bool validateForm(); + void registPerson(); + void editPersonInfo(); signals: void switchToUserListForm(); void backToHomePage(); + void startCaptureFace(); }; #endif // ADDPERSONFORM_H diff --git a/CasicBioRecNew.pro b/CasicBioRecNew.pro index 426ca49..41f36a4 100644 --- a/CasicBioRecNew.pro +++ b/CasicBioRecNew.pro @@ -17,6 +17,8 @@ include(utils/utils.pri) include(dao/dao.pri) +include(device/device.pri) +include(casic/face/casicFace.pri) SOURCES += main.cpp SOURCES += CasicBioRecWin.cpp @@ -35,6 +37,9 @@ HEADERS += OperationTipsDialog.h HEADERS += ConfirmTipsDialog.h +HEADERS += $$PWD/QDblClickLabel.h +SOURCES += $$PWD/QDblClickLabel.cpp + FORMS += CasicBioRecWin.ui FORMS += StartupForm.ui FORMS += PersonListForm.ui @@ -56,3 +61,10 @@ qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../opencv420/build/x64/vc15/lib/ -lopencv_world420d + +INCLUDEPATH += $$PWD/../../opencv420/build/include +DEPENDPATH += $$PWD/../../opencv420/build/include diff --git a/CasicBioRecWin.cpp b/CasicBioRecWin.cpp index 0a5ccd2..a5dd210 100644 --- a/CasicBioRecWin.cpp +++ b/CasicBioRecWin.cpp @@ -24,6 +24,16 @@ // 初始化各个form界面并绑定切换响应函数 this->initFormsPtr(); + // 初始化人脸相机控制 + this->faceCam = new FaceCameraController(this); + bool ok = connect(faceCam, &FaceCameraController::sendImageToDraw, addPersonForm, &AddPersonForm::drawImageOnForm); + faceCam->openFaceCamera(); + LOG(DEBUG) << "CONNECT OK: " << ok; +// this->faceRegistPro = new FaceDetectRegistProcess(this); +// connect(faceCam, &FaceCameraController::sendImageToDetect, +// faceRegistPro, &FaceDetectRegistProcess::faceDetect); + + // 打印日志 LOG(INFO) << QString("应用程序启动成功[Application Startup Success]").toStdString(); } @@ -90,7 +100,7 @@ ui->stacked->addWidget(settingForm); ui->stacked->addWidget(addPersonForm); - ui->stacked->setCurrentWidget(personListForm); +// ui->stacked->setCurrentWidget(personListForm); // 绑定按钮函数 connect(startForm, &StartupForm::switchToUserListForm, diff --git a/CasicBioRecWin.h b/CasicBioRecWin.h index c2e1c58..e4ba24d 100644 --- a/CasicBioRecWin.h +++ b/CasicBioRecWin.h @@ -12,6 +12,9 @@ #include "SettingForm.h" #include "AddPersonForm.h" +#include "device/FaceCameraController.h" +#include "device/face/FaceDetectRegistProcess.h" + QT_BEGIN_NAMESPACE namespace Ui { class CasicBioRecWin; } QT_END_NAMESPACE @@ -24,6 +27,9 @@ CasicBioRecWin(QWidget *parent = nullptr); ~CasicBioRecWin(); + FaceCameraController * faceCam; + FaceDetectRegistProcess * faceRegistPro; + public slots: void backToStandByForm(); void switchToUserListForm(); diff --git a/PersonListForm.cpp b/PersonListForm.cpp index 83f8a48..d314f13 100644 --- a/PersonListForm.cpp +++ b/PersonListForm.cpp @@ -196,11 +196,13 @@ void PersonListForm::on_btnHome_clicked() { + this->currentPage = 0; emit backToHomePage(); } void PersonListForm::on_btnBack_clicked() { + this->currentPage = 0; emit backToHomePage(); } diff --git a/QDblClickLabel.cpp b/QDblClickLabel.cpp new file mode 100644 index 0000000..d68899c --- /dev/null +++ b/QDblClickLabel.cpp @@ -0,0 +1,16 @@ +#include "QDblClickLabel.h" + +QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) +{ + +} + +QDblClickLabel::~QDblClickLabel() +{ + +} + +void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) +{ + emit doubleClicked(); +} diff --git a/QDblClickLabel.h b/QDblClickLabel.h new file mode 100644 index 0000000..bd7c532 --- /dev/null +++ b/QDblClickLabel.h @@ -0,0 +1,19 @@ +#ifndef QDBLCLICKLABEL_H +#define QDBLCLICKLABEL_H + +#include +#include + +class QDblClickLabel : public QLabel +{ + Q_OBJECT +public: + QDblClickLabel(QWidget * parent = 0); + ~QDblClickLabel(); + void mouseDoubleClickEvent(QMouseEvent * event); + +signals: + void doubleClicked(); +}; + +#endif // QDBLCLICKLABEL_H diff --git a/StartupForm.cpp b/StartupForm.cpp index 50c68c4..af51bcc 100644 --- a/StartupForm.cpp +++ b/StartupForm.cpp @@ -27,9 +27,11 @@ // 初始化更新界面的定时器 // 每分钟执行一次 - clockTimer = new QTimer(this); - connect(clockTimer, &QTimer::timeout, this, &StartupForm::updateDateAndTime); - clockTimer->start(1000); + connect(TimeCounterUtil::getInstance().clockCounter, &QTimer::timeout, + this, &StartupForm::updateDateAndTime); + + TimeCounterUtil::getInstance().clockCounter->setInterval(1000); + TimeCounterUtil::getInstance().clockCounter->start(); } StartupForm::~StartupForm() diff --git a/StartupForm.h b/StartupForm.h index 5d07579..6aed14d 100644 --- a/StartupForm.h +++ b/StartupForm.h @@ -3,9 +3,10 @@ #include #include -#include #include + #include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" namespace Ui { class StartupForm; @@ -22,8 +23,6 @@ private: Ui::StartupForm *ui; - QTimer * clockTimer; - private slots: void updateDateAndTime(); void on_btnUser_clicked(); diff --git a/casic/face/CasicFaceInfo.h b/casic/face/CasicFaceInfo.h new file mode 100644 index 0000000..134c904 --- /dev/null +++ b/casic/face/CasicFaceInfo.h @@ -0,0 +1,46 @@ +#ifndef CASICFACEINFO_H +#define CASICFACEINFO_H + +#include +#include "opencv2/opencv.hpp" +#include "seeta/CFaceInfo.h" +#include "seeta/QualityStructure.h" +#include "seeta/FaceAntiSpoofing.h" + +struct CasicFaceInfo +{ + // 是否有人脸, 默认为false + bool hasFace = false; + + // 包含人脸的数据 + // 后续计算需要使用 + SeetaImageData data; + cv::Mat matData; + + // seeta的人脸信息结构{ pos, score } + // 后续计算需要使用 + SeetaFaceInfo face; // 第一个人脸 + int * faceRecTL; // 人脸区域的左上角坐标 + int * faceRecRB; // 人脸区域的右下角坐标 + + // seeta的人脸5点关键点结果 + // 后续计算需要使用 + std::vector points; + + // seeta的人脸质量检测结果 + seeta::QualityResult quality; + + + // seeta的人脸活体检测结果 + seeta::FaceAntiSpoofing::Status antiStatus; + float antiClarity = 0.0; + float antiReality = 0.0; + + // seeta的人脸特征值 + float * feature; + + // seeta的识别成功特征值相似度值 + float sim = 0.0; +}; + +#endif // CASICFACEINFO_H diff --git a/casic/face/CasicFaceInterface.cpp b/casic/face/CasicFaceInterface.cpp new file mode 100644 index 0000000..6afa140 --- /dev/null +++ b/casic/face/CasicFaceInterface.cpp @@ -0,0 +1,412 @@ +#include +#include +#include "CasicFaceInterface.h" +#include "utils/easyloggingpp/easylogging++.h" + +casic::face::CasicFaceInterface::CasicFaceInterface() +{ + // 构建OpenCV自带的眼睛分类器 +// if (this->cascade == nullptr) { +// this->cascade = new cv::CascadeClassifier(); +// this->cascade->load(cvFaceCascadeName); + +// LOG(DEBUG) << "构建OpenCV自带的人脸分类器"; +// } +} + + +casic::face::CasicFaceInterface::~CasicFaceInterface() +{ + if (this->detector != nullptr) { + delete this->detector; + delete this->marker; + + this->detector = nullptr; + this->marker = nullptr; + } + + if (this->poseEx != nullptr) { + delete this->poseEx; + this->poseEx = nullptr; + } + + if (this->processor != nullptr) { + delete this->processor; + this->processor = nullptr; + } + + if (this->recognizer != nullptr) { + delete this->recognizer; + this->recognizer = nullptr; + } + + if (this->cascade != nullptr) + { + delete this->cascade; + this->cascade = nullptr; + } + + LOG(DEBUG) << "delete models in destructor"; +} + +void casic::face::CasicFaceInterface::setDetectorModelPath(std::string detectorModelPath) +{ + this->detectorModelPath = detectorModelPath; +} + +void casic::face::CasicFaceInterface::setMarkPts5ModelPath(std::string markPts5ModelPath) +{ + this->markPts5ModelPath = markPts5ModelPath; +} + +void casic::face::CasicFaceInterface::setPoseModelPath(std::string poseModelPath) +{ + this->poseModelPath = poseModelPath; +} + +void casic::face::CasicFaceInterface::setFas1stModelPath(std::string fas1stModelPath) +{ + this->fas1stModelPath = fas1stModelPath; +} + +void casic::face::CasicFaceInterface::setFas2ndModelPath(std::string fas2ndModelPath) +{ + this->fas2ndModelPath = fas2ndModelPath; +} + +void casic::face::CasicFaceInterface::setRecognizerModelPath(std::string recognizerModelPath) +{ + this->recognizerModelPath = recognizerModelPath; +} + +void casic::face::CasicFaceInterface::setAntiThreshold(float clarity, float reality) +{ + this->clarity = clarity; + this->reality = reality; + if (this->processor != nullptr) + { + this->processor->SetThreshold(clarity, reality); + } +} + + +CasicFaceInfo casic::face::CasicFaceInterface::faceDetect(cv::Mat frame) +{ + SeetaImageData image; + image.height = frame.rows; + image.width = frame.cols; + image.channels = frame.channels(); + image.data = frame.data; + + // 构建人脸检测和标注模型 + if (this->detector == nullptr) { + seeta::ModelSetting msd; // 人脸检测模型属性 + msd.set_device(this->device); + msd.set_id(this->deviceId); + msd.append(this->detectorModelPath); + + this->detector = new seeta::FaceDetector(msd); + + seeta::ModelSetting msm; // 人脸标注模型属性 + msm.set_device(this->device); + msm.set_id(this->deviceId); + msm.append(this->markPts5ModelPath); + + this->marker = new seeta::FaceLandmarker(msm); + } + + QElapsedTimer timer; + timer.start(); + + // ★调用seeta的detect算法检测人脸模型 + SeetaFaceInfoArray faces = this->detector->detect(image); + + if (faces.size != 0) + { + LOG(DEBUG) << QString("人脸检测算法[tm: %1 ms][count: %2][rect: (%3,%4), (%5,%6)][size: (%7,%8)]") + .arg(timer.elapsed()).arg(faces.size) + .arg(faces.data[0].pos.x).arg(faces.data[0].pos.y).arg(faces.data[0].pos.x + faces.data[0].pos.width).arg(faces.data[0].pos.y + faces.data[0].pos.height) + .arg(faces.data[0].pos.width).arg(faces.data[0].pos.height).toLocal8Bit().data(); + } + + CasicFaceInfo faceInfo; + if (faces.size == 0) // 没找到人脸, 直接返回 + { + faceInfo.hasFace = false; + faceInfo.data = image; + faceInfo.matData = frame; + return faceInfo; + } + + // 找到人脸 + faceInfo.hasFace = true; + faceInfo.data = image; + faceInfo.matData = frame; + faceInfo.face = faces.data[0]; // 默认使用第一个人脸, 算法返回的人脸是按照置信度排序的 + faceInfo.points = std::vector(this->marker->number()); + faceInfo.faceRecTL = new int[2] {(int) faces.data[0].pos.x, (int) faces.data[0].pos.y}; + faceInfo.faceRecRB = new int[2] {(int) faces.data[0].pos.x + faces.data[0].pos.width, (int) faces.data[0].pos.y + faces.data[0].pos.height}; + + // ★调用seeta的mark算法, 标记人脸的五个关键点 + this->marker->mark(image, faceInfo.face.pos, faceInfo.points.data()); + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceQuality(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // 亮度评估 + seeta::QualityOfBrightness qBright; + seeta::QualityResult brightResult = qBright.check(image, face, points, 5); + + LOG(DEBUG) << QString("亮度评估[tm: %1 ms][bright: %2][score: %3]").arg(timer.elapsed()).arg(brightResult.level).arg(brightResult.score).toLocal8Bit().data(); + + if (brightResult.level != seeta::QualityLevel::HIGH) + { + // 亮度评估不满足要求, 直接返回 + faceInfo.quality = brightResult; + return faceInfo; + } + + timer.restart(); + + // 清晰度评估 + seeta::QualityOfClarity qClarity; + seeta::QualityResult clarityResult = qClarity.check(image, face, points, 5); + + LOG(DEBUG) << QString("清晰度评估[tm: %1 ms][clarity: %2]").arg(timer.elapsed()).arg(clarityResult.level).toLocal8Bit().data(); + + if (clarityResult.level != seeta::QualityLevel::HIGH) + { + // 清晰度不够, 直接返回 + faceInfo.quality = clarityResult; + return faceInfo; + } + +/* + timer.restart(); + + // 完整度评估 + seeta::QualityOfIntegrity qIntegrity; + seeta::QualityResult integrityResult = qIntegrity.check(image, face, points, 5); + LOG(DEBUG) << "完整度评估" + << QString("[tm: %1 ms][integrity: %2]").arg(timer.elapsed()).arg(integrityResult.level).toStdString(); + + if (integrityResult.level != seeta::QualityLevel::HIGH) + { + // 完整度不够, 直接返回 + faceInfo.quality = integrityResult; + return faceInfo; + } +*/ + timer.restart(); + + // 分辨率评估 + seeta::QualityOfResolution qReso; + seeta::QualityResult resoResult = qReso.check(image, face, points, 5); + LOG(DEBUG) << QString("分辨率评估[tm: %1 ms][reso: %2]").arg(timer.elapsed()).arg(resoResult.level).toLocal8Bit().data(); + if (resoResult.level != seeta::QualityLevel::HIGH) + { + // 分辨率不够, 直接返回 + faceInfo.quality = resoResult; + return faceInfo; + } + + timer.restart(); + + // 姿势评估(深度学习方法) + if (this->poseEx == nullptr) { + seeta::ModelSetting msp; // 人脸姿势检测模型属性 + msp.set_device(this->device); + msp.set_id(this->deviceId); + msp.append(this->poseModelPath); + + this->poseEx = new seeta::QualityOfPoseEx(msp); + + // 设置三个方向的默认阈值 + poseEx->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25); + poseEx->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20); + poseEx->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10); + + poseEx->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f); + poseEx->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f); + } + + seeta::QualityResult poseResult = poseEx->check(image, face, points, 5); + + LOG(DEBUG) << QString("姿势评估[tm: %1ms][pose: %2][score: %3]").arg(timer.elapsed()).arg(poseResult.score).arg(poseResult.level).toLocal8Bit().data(); + + if (poseResult.level != seeta::QualityLevel::HIGH) + { + // 姿势评估不满足, 直接返回 + faceInfo.quality = poseResult; + return faceInfo; + } else + { + // 五个维度的质量评估结果都是HIGH, 返回合格 + faceInfo.quality.level = seeta::QualityLevel::HIGH; + } + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceAntiSpoofing(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + if (this->processor == nullptr) + { + seeta::ModelSetting msa; // 人脸活体检测模型属性 + msa.set_device(this->device); + msa.set_id(this->deviceId); + msa.append(this->fas1stModelPath); +// msa.append(this->fas2ndModelPath); // 加快速度, 只用局部活体检测算法 + + this->processor = new seeta::FaceAntiSpoofing(msa); + this->processor->SetThreshold(this->clarity, this->reality); + } + + SeetaImageData image = faceInfo.data; + auto &face = faceInfo.face.pos; + auto points = faceInfo.points.data(); + + QElapsedTimer timer; + timer.start(); + + // ★调用人脸活体检测算法 + auto status = this->processor->Predict(image, face, points); + faceInfo.antiStatus = status; + + processor->GetPreFrameScore(&faceInfo.antiClarity, &faceInfo.antiReality); + + LOG(DEBUG) << QString("活体检测[tm: %1 ms][anti: %2][clarity: %3, reality: %4]").arg(timer.elapsed()).arg(status).arg(faceInfo.antiClarity).arg(faceInfo.antiReality).toLocal8Bit().data(); + } + + return faceInfo; +} + +CasicFaceInfo casic::face::CasicFaceInterface::faceFeatureExtract(CasicFaceInfo faceInfo) +{ + if (faceInfo.hasFace == true) + { + float * featureTemp; + + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + featureTemp = new (float[this->recognizer->GetExtractFeatureSize()]); + + SeetaImageData image = faceInfo.data; + auto points = faceInfo.points.data(); + + this->recognizer->Extract(image, points, featureTemp); + + faceInfo.feature = featureTemp; + } + + return faceInfo; +} + +float casic::face::CasicFaceInterface::faceSimCalculate(float* feature, float* otherFeature) +{ + if (this->recognizer == nullptr) + { + seeta::ModelSetting msr; // 人脸识别模型属性 + msr.set_device(this->device); + msr.set_id(this->deviceId); + msr.append(this->recognizerModelPath); + + this->recognizer = new seeta::FaceRecognizer(msr); + } + + float sim = this->recognizer->CalculateSimilarity(feature, otherFeature); + return sim; +} + + +cv::Rect casic::face::CasicFaceInterface::faceDetectByCVCascade(cv::Mat frame) +{ + // 构建OpenCV自带的人脸分类器 + if (this->cascade == nullptr) { + this->cascade = new cv::CascadeClassifier(); + this->cascade->load(cvFaceCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minFaceSize, minFaceSize); + cv::Size maxRectSize(maxFaceSize, maxFaceSize); + + // ★分类器对象调用 + cascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("人脸分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +cv::Rect casic::face::CasicFaceInterface::eyeDetectByCVCascade(cv::Mat frame) +{ + // 构建openCV自带的眼睛分类器 + if (this->eyeCascade == nullptr) + { + this->eyeCascade = new cv::CascadeClassifier(); + this->eyeCascade->load(cvEyeCascadeName); + } + + QElapsedTimer timer; + timer.start(); + + std::vector rect; + cv::Size minRectSize(minEyeSize, minEyeSize); + cv::Size maxRectSize(maxEyeSize, maxEyeSize); + + // ★分类器对象调用 + eyeCascade->detectMultiScale(frame, rect, 1.1, 3, cv::CASCADE_FIND_BIGGEST_OBJECT, minRectSize, maxRectSize); + + if (rect.size() == 0) + { + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][0]").arg(timer.elapsed()).toLocal8Bit().data(); + return cv::Rect(0, 0, 0, 0); + } + + LOG(DEBUG) << QString("眼睛分类检测算法[tm: %1 ms][%2, %3]").arg(timer.elapsed()).arg(rect.at(0).width).arg(rect.at(0).height).toLocal8Bit().data(); + + return rect.at(0); +} + +void casic::face::CasicFaceInterface::setMinFaceSize(int minFaceSize) +{ + this->minFaceSize = minFaceSize; +} +void casic::face::CasicFaceInterface::setMinEyeSize(int minEyeSize) +{ + this->minEyeSize = minEyeSize; +} diff --git a/casic/face/CasicFaceInterface.h b/casic/face/CasicFaceInterface.h new file mode 100644 index 0000000..d6d4494 --- /dev/null +++ b/casic/face/CasicFaceInterface.h @@ -0,0 +1,91 @@ +#ifndef CASICFACEINTERFACE_H +#define CASICFACEINTERFACE_H + +#include "opencv2/opencv.hpp" +#include "seeta/FaceDetector.h" +#include "seeta/FaceLandmarker.h" +#include "seeta/QualityAssessor.h" +#include "seeta/QualityOfBrightness.h" +#include "seeta/QualityOfClarity.h" +#include "seeta/QualityOfIntegrity.h" +#include "seeta/QualityOfResolution.h" +#include "seeta/QualityOfPoseEx.h" +#include "seeta/FaceAntiSpoofing.h" +#include "seeta/FaceRecognizer.h" + +#include "CasicFaceInfo.h" + +static auto red = CV_RGB(255, 0, 0); +static auto green = CV_RGB(0, 255, 0); +static auto blue = CV_RGB(0, 0, 255); + +namespace casic { + namespace face { + class CasicFaceInterface + { + public: + ~CasicFaceInterface(); + CasicFaceInterface(const CasicFaceInterface&)=delete; + CasicFaceInterface& operator=(const CasicFaceInterface&)=delete; + + static CasicFaceInterface& getInstance() { + static CasicFaceInterface instance; + return instance; + } + + void setDetectorModelPath(std::string detectorModelPath); + void setMarkPts5ModelPath(std::string markPts5ModelPath); + void setPoseModelPath(std::string poseModelPath); + void setFas1stModelPath(std::string fas1stModelPath); + void setFas2ndModelPath(std::string fas2ndModelPath); + void setRecognizerModelPath(std::string recognizerModelPath); + + void setAntiThreshold(float clarity, float reality); + + CasicFaceInfo faceDetect(cv::Mat frame); + CasicFaceInfo faceQuality(CasicFaceInfo faceInfo); + CasicFaceInfo faceAntiSpoofing(CasicFaceInfo faceInfo); + CasicFaceInfo faceFeatureExtract(CasicFaceInfo faceInfo); + float faceSimCalculate(float* feature, float* otherFeature); + + cv::Rect faceDetectByCVCascade(cv::Mat frame); + cv::Rect eyeDetectByCVCascade(cv::Mat frame); + void setMinFaceSize(int minFaceSize); + void setMinEyeSize(int minEyeSize); + private: + CasicFaceInterface(); + + int deviceId = 0; + seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO; + + float clarity = 0.3f; + float reality = 0.3f; + + std::string cvFaceCascadeName = "./model/haarcascade_frontalface_default.xml"; + std::string cvEyeCascadeName = "./model/haarcascade_eye.xml"; + int minFaceSize = 320; + int maxFaceSize = 720; + int minEyeSize = 100; + int maxEyeSize = 600; + + std::string detectorModelPath = "./model/face_detector.csta"; + std::string markPts5ModelPath = "./model/face_landmarker_pts5.csta"; + std::string poseModelPath = "./model/pose_estimation.csta"; + std::string fas1stModelPath = "./model/fas_first.csta"; + std::string fas2ndModelPath = "./model/fas_second.csta"; + std::string recognizerModelPath = "./model/face_recognizer.csta"; + + seeta::FaceDetector * detector = nullptr; + seeta::FaceLandmarker * marker = nullptr; + seeta::QualityOfPoseEx * poseEx = nullptr; + seeta::FaceAntiSpoofing * processor = nullptr; + seeta::FaceRecognizer * recognizer = nullptr; + + cv::CascadeClassifier * cascade; + cv::CascadeClassifier * eyeCascade; + }; + } +} + + +#endif // CASICFACEINTERFACE_H diff --git a/casic/face/casicFace.pri b/casic/face/casicFace.pri new file mode 100644 index 0000000..da337d9 --- /dev/null +++ b/casic/face/casicFace.pri @@ -0,0 +1,9 @@ +HEADERS += $$PWD/CasicFaceInfo.h +HEADERS += $$PWD/CasicFaceInterface.h + +SOURCES += $$PWD/CasicFaceInterface.cpp + +INCLUDEPATH += seeta/ + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600 -lSeetaFaceLandmarker600 -lSeetaFaceAntiSpoofingX600 -lSeetaFaceRecognizer610 -lSeetaQualityAssessor300 +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/x64/ -lSeetaFaceDetector600d -lSeetaFaceLandmarker600d -lSeetaFaceAntiSpoofingX600d -lSeetaFaceRecognizer610d -lSeetaQualityAssessor300d diff --git a/dao/FaceDataImgDao.cpp b/dao/FaceDataImgDao.cpp index 83f22a0..d863752 100644 --- a/dao/FaceDataImgDao.cpp +++ b/dao/FaceDataImgDao.cpp @@ -78,20 +78,14 @@ // 返回结果 QVariantMap result; - // 获取结果集的大小 - query.last(); - int count = query.at() + 1; - - if (count >=1) + if (query.next()) { - query.first(); - result.insert("id", query.value("id").toString()); result.insert("person_id", query.value("person_id").toString()); result.insert("face_image", query.value("face_image").toString()); } - LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[%1][id=%2][%3]").arg(count).arg(query.value("id").toString()).arg(sql).toStdString(); + LOG(TRACE) << QString("根据id查询FACE_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/dao/IrisDataImgDao.cpp b/dao/IrisDataImgDao.cpp index af0d45b..8a0c9af 100644 --- a/dao/IrisDataImgDao.cpp +++ b/dao/IrisDataImgDao.cpp @@ -97,7 +97,7 @@ result.insert("right_image1", query.value("right_image1").toString()); } - LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId:%1][%2]").arg(personId).arg(sql).toStdString(); + LOG(TRACE) << QString("根据personId查询IRIS_DATA_IMAGE表的记录[personId=%1][%2]").arg(personId).arg(sql).toStdString(); return result; } diff --git a/device/FaceCameraController.cpp b/device/FaceCameraController.cpp new file mode 100644 index 0000000..bb325a4 --- /dev/null +++ b/device/FaceCameraController.cpp @@ -0,0 +1,60 @@ +#include "FaceCameraController.h" +#include +#include +#include + +FaceCameraController::FaceCameraController(QObject *parent) : QObject(parent) +{ + // 获取定时器, 绑定定时函数 + connect(TimeCounterUtil::getInstance().faceCapCounter, &QTimer::timeout, + this, &FaceCameraController::getOneFaceFrm); +} + +FaceCameraController::~FaceCameraController() +{ + this->closeFaceCamera(); +} + + +void FaceCameraController::openFaceCamera() +{ + this->faceCap = new cv::VideoCapture(SettingConfig::getInstance().FACE_CAMERA_INDEX, cv::CAP_DSHOW); + faceCap->set(cv::CAP_PROP_FRAME_WIDTH, SettingConfig::getInstance().FACE_FRAME_WIDTH); + faceCap->set(cv::CAP_PROP_FRAME_HEIGHT, SettingConfig::getInstance().FACE_FRAME_HEIGHT); + + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]打开相机[%1][%2 * %3]") + .arg(SettingConfig::getInstance().FACE_CAMERA_INDEX) + .arg(SettingConfig::getInstance().FACE_FRAME_WIDTH) + .arg(SettingConfig::getInstance().FACE_FRAME_HEIGHT).toStdString(); + + // 启动定时器 + TimeCounterUtil::getInstance().faceCapCounter->setInterval(SettingConfig::getInstance().FACE_FRAME_INTERVAL); + TimeCounterUtil::getInstance().faceCapCounter->start(); + LOG(DEBUG) << QString("[FaceCameraController][openFaceCamera]相机开始拍图[%1ms]") + .arg(SettingConfig::getInstance().FACE_FRAME_INTERVAL).toStdString(); +} + +void FaceCameraController::closeFaceCamera() +{ + faceCap->release(); + + delete faceCap; +} + + +void FaceCameraController::getOneFaceFrm() +{ + faceCap->read(faceMat); + + // clone一个mat, 用于界面显示 + cv::Mat faceMatDisp = faceMat.clone(); + QImage imgDisplay = ImageUtil::MatImageToQImage(faceMatDisp); + + // 发送信号用于界面显示 + emit sendImageToDraw(); + + LOG(DEBUG) << " TAKE ONE FACE FRAME " << faceMat.cols << " * " << faceMat.rows; + + // 发送信号用于人脸检测和生成特征值 +// emit sendImageToDetect(faceMat); +} diff --git a/device/FaceCameraController.h b/device/FaceCameraController.h new file mode 100644 index 0000000..c75fcda --- /dev/null +++ b/device/FaceCameraController.h @@ -0,0 +1,40 @@ +#ifndef CAMERACONTROLLER_H +#define CAMERACONTROLLER_H + +#include + +#include "opencv2/opencv.hpp" + +//#include "casic/face/CasicFaceInterface.h" +//#include "process/memory/ProMemory.h" +//#include "process/face/CasicFaceRecState.h" +#include "utils/ImageUtil.h" +#include "utils/SettingConfig.h" +#include "utils/TimeCounterUtil.h" +#include "utils/easyloggingpp/easylogging++.h" + +class FaceCameraController : public QObject +{ + Q_OBJECT +public: + explicit FaceCameraController(QObject *parent = nullptr); + ~FaceCameraController(); + + // 初始化并打开人脸相机 + void openFaceCamera(); + void closeFaceCamera(); + +private: + cv::VideoCapture * faceCap; + + cv::Mat faceMat; + +public slots: + void getOneFaceFrm(); + +signals: + void sendImageToDraw(); + void sendImageToDetect(cv::Mat imgMat); +}; + +#endif // CAMERACONTROLLER_H diff --git a/device/device.pri b/device/device.pri new file mode 100644 index 0000000..99f9438 --- /dev/null +++ b/device/device.pri @@ -0,0 +1,19 @@ + +HEADERS += $$PWD/FaceCameraController.h +HEADERS += $$PWD/face/FaceDetectRegistProcess.h +HEADERS += $$PWD/face/CasicFaceRecState.h + +SOURCES += $$PWD/FaceCameraController.cpp +SOURCES += $$PWD/face/FaceDetectRegistProcess.cpp +SOURCES += $$PWD/face/CasicFaceRecState.cpp + + +#HEADERS += $$PWD/IrisCameraController.h +#HEADERS += $$PWD/IrisCameraCapEventHandler.h +#HEADERS += $$PWD/MotoController.h +#HEADERS += $$PWD/DeviceEnumerator.h + +#SOURCES += $$PWD/IrisCameraController.cpp +#SOURCES += $$PWD/IrisCameraCapEventHandler.cpp +#SOURCES += $$PWD/MotoController.cpp +#SOURCES += $$PWD/DeviceEnumerator.cpp diff --git a/device/face/CasicFaceRecState.cpp b/device/face/CasicFaceRecState.cpp new file mode 100644 index 0000000..3ba8470 --- /dev/null +++ b/device/face/CasicFaceRecState.cpp @@ -0,0 +1,6 @@ +#include "CasicFaceRecState.h" + +CasicFaceRecState::CasicFaceRecState() +{ + +} diff --git a/device/face/CasicFaceRecState.h b/device/face/CasicFaceRecState.h new file mode 100644 index 0000000..999486e --- /dev/null +++ b/device/face/CasicFaceRecState.h @@ -0,0 +1,41 @@ +#ifndef CASICFACERECSTATE_H +#define CASICFACERECSTATE_H + +#include +#include "casic/face/CasicFaceInfo.h" + +class CasicFaceRecState : public QObject +{ +public: + ~CasicFaceRecState() {}; + CasicFaceRecState(const CasicFaceRecState&)=delete; + CasicFaceRecState& operator=(const CasicFaceRecState&)=delete; + + static CasicFaceRecState& getInstance() { + static CasicFaceRecState instance; + return instance; + } + + void initRecognize(); + std::string toString(); + QJsonObject toJSON(); + + std::string recoginzeId; // 识别过程id + qint64 timeStamp = 0; // 识别开始时间戳 + qint64 timeStampSucc = 0; // 识别成功时的时间戳 + + CasicFaceInfo * faceInfo; // 人脸信息 + QString imgBase64; // 人脸的base64码数据, 用于存库 + + qint8 tryCount = 0; // 识别尝试次数 + qint8 noFaceCount = 0; // 连续没有找到人脸次数 + float recogTimeLast = 0.0; // 识别成功耗时 + +private: + CasicFaceRecState(); + +signals: + +}; + +#endif // CASICFACERECSTATE_H diff --git a/device/face/FaceDetectRegistProcess.cpp b/device/face/FaceDetectRegistProcess.cpp new file mode 100644 index 0000000..3a6748d --- /dev/null +++ b/device/face/FaceDetectRegistProcess.cpp @@ -0,0 +1,70 @@ +#include "FaceDetectRegistProcess.h" + +FaceDetectRegistProcess::FaceDetectRegistProcess(QObject *parent) : QObject(parent) +{ + +} + +void FaceDetectRegistProcess::faceDetect(cv::Mat faceMat) +{ + // 如果已经在人脸检测工作中则退出 + if (FACE_DETECT_FLAG == true) + { + LOG(DEBUG) << "ALREADY IN FACE DETECT PROCESS"; + return; + } + + // 开始人脸检测 + FACE_DETECT_FLAG = true; + LOG(DEBUG) << "START FACE DETECT"; + + // 调用人脸检测算法 + CasicFaceInfo faceInfo = casic::face::CasicFaceInterface::getInstance().faceDetect(faceMat); + if (faceInfo.hasFace == false) + { + // 没有找到人脸进行如下处理 + + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; + return; + } + + // 继续进行后面的步骤 + // 开始人脸活体检测, 提高活体检测的阈值到0.3/0.6 + casic::face::CasicFaceInterface::getInstance().setAntiThreshold(0.3, 0.6); + faceInfo = casic::face::CasicFaceInterface::getInstance().faceAntiSpoofing(faceInfo); + // 活体检测判断为假脸 + if (faceInfo.antiStatus != seeta::FaceAntiSpoofing::Status::REAL) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸活体检测未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 判定为真实人脸, 开始质量评估 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceQuality(faceInfo); + + // 质量评估不为HIGH则返回 + if (faceInfo.quality.level != seeta::QualityLevel::HIGH) + { + // 表示本次识别结束, 可以开始下一次识别过程 + LOG(DEBUG) << QString("[faceDetect]人脸质量评估未通过").toStdString(); + + FACE_DETECT_FLAG = false; + return; + } + + // 调用算法提取特征值, 1024个字节的float数组 + faceInfo = casic::face::CasicFaceInterface::getInstance().faceFeatureExtract(faceInfo); + + LOG(DEBUG) << QString("[faceDetect]特征提取成功").toStdString(); + + // 发送信号去进行比对, 执行后续业务逻辑 + emit this->extractFeatureSuccess(CasicFaceRecState::getInstance()); + + // 结束检测 重置工作标志位 + FACE_DETECT_FLAG = false; +} diff --git a/device/face/FaceDetectRegistProcess.h b/device/face/FaceDetectRegistProcess.h new file mode 100644 index 0000000..5d42bbe --- /dev/null +++ b/device/face/FaceDetectRegistProcess.h @@ -0,0 +1,28 @@ +#ifndef FACEDETECTREGISTPROCESS_H +#define FACEDETECTREGISTPROCESS_H + +#include +#include "opencv2/opencv.hpp" + +#include "utils/easyloggingpp/easylogging++.h" + +#include "CasicFaceRecState.h" +#include "casic/face/CasicFaceInterface.h" + +static bool FACE_DETECT_FLAG = false; + +class FaceDetectRegistProcess : public QObject +{ + Q_OBJECT +public: + explicit FaceDetectRegistProcess(QObject *parent = nullptr); + +public slots: + void faceDetect(cv::Mat faceMat); + +signals: + void extractFeatureSuccess(CasicFaceRecState& faceRecState); + +}; + +#endif // FACEDETECTREGISTPROCESS_H diff --git a/qss/dialogTips.css b/qss/dialogTips.css index 9c9b4d6..a80bfe1 100644 --- a/qss/dialogTips.css +++ b/qss/dialogTips.css @@ -1,20 +1,36 @@ +QDialog { + border: 1px solid #5F1BC6; +} + QLabel { color: #6868A6; font-family: "Microsoft YaHei"; } - -QLabel#labDate { - font-size: 36px; +QLabel#labTitle { + font-size: 20px; + line-height: 50px; +} +QLabel#labTipsContent { + font-size: 32px; } -QLabel#labTime { - font-size: 100px; -} - -QToolButton { - color: #6868A6; +QDialogButtonBox#btnBoxOk QPushButton { + color: #FFFFFF; font-family: "Microsoft YaHei"; - font-size: 48px; - background: transparent; - border-style: none; + font-size: 24px; + background: #6868A6; + border-radius: 12px; + min-width: 200px; + min-height: 50px; +} + +QDialogButtonBox#btnBoxConfirm QPushButton { + color: #FFFFFF; + font-family: "Microsoft YaHei"; + font-size: 24px; + background: #6868A6; + border-radius: 12px; + min-width: 120px; + min-height: 50px; + margin-right: 30px; } diff --git a/utils/ImageUtil.cpp b/utils/ImageUtil.cpp new file mode 100644 index 0000000..7604572 --- /dev/null +++ b/utils/ImageUtil.cpp @@ -0,0 +1,97 @@ +#include "ImageUtil.h" + +ImageUtil::ImageUtil() +{ + +} + +QImage ImageUtil::MatImageToQImage(const cv::Mat &src) +{ + //CV_8UC1 8位无符号的单通道---灰度图片 + if(src.type() == CV_8UC1) + { + QImage qImage((const unsigned char *)(src.data), src.cols, src.rows, src.cols, QImage::Format_Grayscale8); + return qImage; + } + //为3通道的彩色图片 + else if(src.type() == CV_8UC3) + { + //得到图像的的首地址 + const uchar *pSrc = (const uchar*)src.data; + //以src构造图片 + QImage qImage(pSrc, src.cols, src.rows, src.step, QImage::Format_RGB888); + //在不改变实际图像数据的条件下, 交换红蓝通道 + return qImage.rgbSwapped(); + } + //四通道图片, 带Alpha通道的RGB彩色图像 + else if(src.type() == CV_8UC4) + { + const uchar *pSrc = (const uchar*)src.data; + QImage qImage(pSrc, src.cols, src.rows, src.step, QImage::Format_ARGB32); + //返回图像的子区域作为一个新图像 + return qImage.copy(); + } + else + { + return QImage(); + } +} +/* +QImage ImageUtil::UcharToQImage(unsigned char* data, int width, int height, QImage::Format format) +{ + QImage qImage(data, width, height, format); + //在不改变实际图像数据的条件下, 交换红蓝通道 + return qImage.rgbSwapped(); +} + +cv::Mat ImageUtil::QImageToMat(QImage image) +{ + cv::Mat mat; + switch(image.format()) + { + case QImage::Format_ARGB32: + case QImage::Format_RGB32: + case QImage::Format_ARGB32_Premultiplied: + mat = cv::Mat(image.height(), image.width(), CV_8UC4, (void*)image.constBits(), image.bytesPerLine()); + break; + case QImage::Format_RGB888: + mat = cv::Mat(image.height(), image.width(), CV_8UC3, (void*)image.constBits(), image.bytesPerLine()); + cv::cvtColor(mat, mat, cv::COLOR_BGR2RGB); + break; + case QImage::Format_Indexed8: + mat = cv::Mat(image.height(), image.width(), CV_8UC1, (void*)image.constBits(), image.bytesPerLine()); + break; + case QImage::Format_Grayscale8: + mat = cv::Mat(image.height(), image.width(), CV_8UC1, (void *)image.bits(), image.bytesPerLine()); + break; + } + + mat = mat.clone(); + + return mat; +} + +QString ImageUtil::QImageToBase64(QImage image) +{ + QByteArray ba; + QBuffer buf(&ba); + image.save(&buf, "bmp"); + QString base64String = ba.toBase64(); + return base64String; +} + +cv::Mat ImageUtil::MatImageRect(const cv::Mat &src, cv::Rect rect, int delta) +{ + cv::Mat matClone = src.clone(); + cv::Rect bigRect; + + bigRect.x = rect.x - delta < 0 ? 0 : rect.x - delta; + bigRect.y = rect.y - delta < 0 ? 0 : rect.y - delta; + bigRect.width = rect.x + rect.width + 2 * delta > src.cols ? src.cols - rect.x : rect.width + 2 * delta; + bigRect.height = rect.y + rect.height + 2 * delta > src.rows ? src.rows - rect.y : rect.height + 2 * delta; + + cv::Mat roi = matClone(bigRect); + + return roi; +} +*/ diff --git a/utils/ImageUtil.h b/utils/ImageUtil.h new file mode 100644 index 0000000..dbfdd48 --- /dev/null +++ b/utils/ImageUtil.h @@ -0,0 +1,22 @@ +#ifndef IMAGEUTIL_H +#define IMAGEUTIL_H + +#include +#include +#include "opencv2/opencv.hpp" + +class ImageUtil +{ +public: + ImageUtil(); + + static QImage MatImageToQImage(const cv::Mat &src); +// static QImage UcharToQImage(unsigned char* data, int width, int height, QImage::Format format); + +// static cv::Mat QImageToMat(QImage image); +// static QString QImageToBase64(QImage image); + +// static cv::Mat MatImageRect(const cv::Mat &src, cv::Rect rect, int delta); +}; + +#endif // IMAGEUTIL_H diff --git a/utils/QDblClickLabel.cpp b/utils/QDblClickLabel.cpp deleted file mode 100644 index d68899c..0000000 --- a/utils/QDblClickLabel.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "QDblClickLabel.h" - -QDblClickLabel::QDblClickLabel(QWidget *parent) : QLabel(parent) -{ - -} - -QDblClickLabel::~QDblClickLabel() -{ - -} - -void QDblClickLabel::mouseDoubleClickEvent(QMouseEvent *event) -{ - emit doubleClicked(); -} diff --git a/utils/QDblClickLabel.h b/utils/QDblClickLabel.h deleted file mode 100644 index bd7c532..0000000 --- a/utils/QDblClickLabel.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef QDBLCLICKLABEL_H -#define QDBLCLICKLABEL_H - -#include -#include - -class QDblClickLabel : public QLabel -{ - Q_OBJECT -public: - QDblClickLabel(QWidget * parent = 0); - ~QDblClickLabel(); - void mouseDoubleClickEvent(QMouseEvent * event); - -signals: - void doubleClicked(); -}; - -#endif // QDBLCLICKLABEL_H diff --git a/utils/TimeCounterUtil.cpp b/utils/TimeCounterUtil.cpp new file mode 100644 index 0000000..275945f --- /dev/null +++ b/utils/TimeCounterUtil.cpp @@ -0,0 +1,8 @@ +#include "TimeCounterUtil.h" + +TimeCounterUtil::TimeCounterUtil() +{ + faceCapCounter = new QTimer(); + irisCapCounter = new QTimer(); + clockCounter = new QTimer(); +} diff --git a/utils/TimeCounterUtil.h b/utils/TimeCounterUtil.h new file mode 100644 index 0000000..28b29f1 --- /dev/null +++ b/utils/TimeCounterUtil.h @@ -0,0 +1,29 @@ +#ifndef TIMECOUNTERUTIL_H +#define TIMECOUNTERUTIL_H + +#include +#include + +class TimeCounterUtil : public QObject +{ +public: + + ~TimeCounterUtil() {}; + TimeCounterUtil(const TimeCounterUtil&)=delete; + TimeCounterUtil& operator=(const TimeCounterUtil&)=delete; + + static TimeCounterUtil& getInstance() { + static TimeCounterUtil instance; + return instance; + } + + QTimer * faceCapCounter; + QTimer * irisCapCounter; + QTimer * clockCounter; + +private: + TimeCounterUtil(); + +}; + +#endif // TIMECOUNTERUTIL_H diff --git a/utils/utils.pri b/utils/utils.pri index 2f81cd9..384cda1 100644 --- a/utils/utils.pri +++ b/utils/utils.pri @@ -10,24 +10,23 @@ HEADERS += $$PWD/SettingConfig.h SOURCES += $$PWD/SettingConfig.cpp +HEADERS += $$PWD/TimeCounterUtil.h +SOURCES += $$PWD/TimeCounterUtil.cpp + HEADERS += $$PWD/SelectDeptUtil.h SOURCES += $$PWD/SelectDeptUtil.cpp -HEADERS += $$PWD/QDblClickLabel.h -SOURCES += $$PWD/QDblClickLabel.cpp +HEADERS += $$PWD/ImageUtil.h +SOURCES += $$PWD/ImageUtil.cpp #HEADERS += $$PWD/QByteUtil.h -#HEADERS += $$PWD/QImageUtil.h #HEADERS += $$PWD/QSocketClientUtil.h - #HEADERS += $$PWD/TimerCounter.h #HEADERS += $$PWD/SpeakerUtil.h #SOURCES += $$PWD/QByteUtil.cpp -#SOURCES += $$PWD/QImageUtil.cpp #SOURCES += $$PWD/QSocketClientUtil.cpp - #SOURCES += $$PWD/TimerCounter.cpp #SOURCES += $$PWD/SpeakerUtil.cpp