354 lines
13 KiB
C++
354 lines
13 KiB
C++
#include "Application.h"
|
|
#include "AsyncEvent.h"
|
|
#include "BoostLog.h"
|
|
#include "CategoryLogSinkBackend.h"
|
|
#include "Configuration.h"
|
|
#include "Database.h"
|
|
#include "DeviceDiscovery.h"
|
|
#include "VideoFrameProvider.h"
|
|
#include "VideoPlayer.h"
|
|
#include <QApplication>
|
|
#include <QFont>
|
|
#include <QImage>
|
|
#include <QQmlApplicationEngine>
|
|
#include <QSerialPortInfo>
|
|
#include <QTimer>
|
|
#include <fstream>
|
|
#include <mbedtls/md5.h>
|
|
|
|
constexpr uint32_t ImageSliceSize = 2048;
|
|
|
|
Application::Application(int &argc, char **argv) : m_app(std::make_shared<QApplication>(argc, argv)) {
|
|
m_app->setApplicationVersion(QString("v%1_%2 build: %3 %4").arg(APP_VERSION, GIT_COMMIT_ID, __DATE__, __TIME__));
|
|
QFont font;
|
|
font.setPointSize(16);
|
|
m_app->setFont(font);
|
|
|
|
m_verifyTimer = new QTimer(this);
|
|
m_verifyTimer->setSingleShot(true);
|
|
connect(m_verifyTimer, &QTimer::timeout, this, &Application::onVerifyTimeout);
|
|
|
|
m_database = std::make_shared<Database>();
|
|
QTimer::singleShot(0, this, [this]() {
|
|
if (!m_database->open("database.db")) {
|
|
LOG(error) << "open database failed.";
|
|
}
|
|
});
|
|
|
|
m_videoFrameProvider = new VideoFrameProvider();
|
|
|
|
qmlRegisterSingletonInstance("Analyser", 1, 0, "App", this);
|
|
}
|
|
|
|
void Application::onNewVerifyResult(uint16_t userid, const QString &username) {
|
|
QTimer::singleShot(0, this, [this, userid, username]() {
|
|
emit newStatusTip(Info, QString("%1,识别耗时: %2ms")
|
|
.arg(userid == ModuleCommunication::InvalidUserId ? "未录入用户" : username)
|
|
.arg(m_verifyElapsed.count()));
|
|
});
|
|
}
|
|
|
|
void Application::initializeLogger() {
|
|
auto backend = boost::make_shared<CategoryLogSinkBackend>("GUI", [this](const std::string &log) {
|
|
Amass::executeAtObjectThread(this, [this, log]() { emit newLog(QString::fromStdString(log)); });
|
|
});
|
|
using SynchronousCategorySink = boost::log::sinks::synchronous_sink<CategoryLogSinkBackend>;
|
|
auto sink = boost::make_shared<SynchronousCategorySink>(backend);
|
|
boost::log::core::get()->add_sink(sink);
|
|
}
|
|
|
|
int Application::exec() {
|
|
QQmlApplicationEngine engine;
|
|
engine.addImageProvider("videoframe", m_videoFrameProvider);
|
|
const QUrl url(QStringLiteral("qrc:/Analyser/qml/main.qml"));
|
|
QObject::connect(
|
|
&engine, &QQmlApplicationEngine::objectCreationFailed, m_app.get(), []() { QCoreApplication::exit(-1); },
|
|
Qt::QueuedConnection);
|
|
engine.load(url);
|
|
|
|
return m_app->exec();
|
|
}
|
|
|
|
QStringList Application::availableSerialPorts() const {
|
|
QStringList ret;
|
|
auto ports = QSerialPortInfo::availablePorts();
|
|
for (auto &port : ports) {
|
|
if (port.description() == "蓝牙链接上的标准串行") continue;
|
|
ret << port.portName();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
QStringList Application::availableUsbVideoCameras() const {
|
|
QStringList ret;
|
|
DeviceDiscovery d;
|
|
auto devices = d.devices();
|
|
for (auto &device : devices) {
|
|
ret << QString::fromStdString(device);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool Application::open(const QString &portName, int baudRate) {
|
|
m_communication = std::make_shared<ModuleCommunication>();
|
|
connect(m_communication.get(), &ModuleCommunication::commandStarted, this, &Application::onCommandStarted);
|
|
connect(m_communication.get(), &ModuleCommunication::commandFinished, this, &Application::onCommandFinished);
|
|
connect(m_communication.get(), &ModuleCommunication::newVerifyResult, this, &Application::onNewVerifyResult);
|
|
connect(m_communication.get(), &ModuleCommunication::newPalmFeature, this, &Application::onNewPalmFeature);
|
|
connect(m_communication.get(), &ModuleCommunication::newEnrolledImageInfo, this,
|
|
&Application::onNewEnrolledImageInfo);
|
|
connect(m_communication.get(), &ModuleCommunication::newImageSliceData, this, &Application::onNewImageSliceData);
|
|
connect(m_communication.get(), &ModuleCommunication::errorOccurred, this, &Application::onErrorOccurred);
|
|
emit connectedChanged();
|
|
|
|
auto status = m_communication->open(portName, baudRate);
|
|
emit newStatusTip(status ? Tip : Error, status ? "串口打开成功" : "串口打开失败");
|
|
return status;
|
|
}
|
|
|
|
bool Application::openUVC(const QString &deviceName) {
|
|
m_videoPlayer = std::make_shared<VideoPlayer>();
|
|
m_videoPlayer->setFrameCallback([this](const QImage &image) {
|
|
Amass::executeAtObjectThread(this, [this, image]() {
|
|
m_videoFrameProvider->setImage(image);
|
|
emit newVideoFrame();
|
|
});
|
|
});
|
|
m_videoPlayer->setErrorCallback([this](int error, const std::string &message) {
|
|
LOG_CAT(info, GUI) << "UVC错误: " << message;
|
|
Amass::executeAtObjectThread(this, [this, error]() {
|
|
if (error == -EIO) {
|
|
closeUVC();
|
|
}
|
|
});
|
|
});
|
|
|
|
bool status = m_videoPlayer->open(deviceName.toStdString());
|
|
if (!status) {
|
|
m_videoPlayer.reset();
|
|
}
|
|
emit uvcOpenedChanged();
|
|
emit newStatusTip(status ? Tip : Error, status ? "UVC打开成功" : "UVC打开失败");
|
|
return status;
|
|
}
|
|
|
|
void Application::close() {
|
|
m_communication.reset();
|
|
emit connectedChanged();
|
|
|
|
m_persistenceModeStarted = false;
|
|
m_verifyTimer->stop();
|
|
emit isVerifyingChanged();
|
|
}
|
|
|
|
void Application::closeUVC() {
|
|
m_videoFrameProvider->reset();
|
|
emit newVideoFrame();
|
|
|
|
m_videoPlayer.reset();
|
|
emit uvcOpenedChanged();
|
|
}
|
|
|
|
void Application::verify(uint8_t timeout) {
|
|
if (m_communication->currentMessageId() != ModuleCommunication::Idle) {
|
|
m_communication->reset();
|
|
}
|
|
m_communication->verify(timeout);
|
|
}
|
|
|
|
void Application::enroll(const QString &username, uint8_t timeout) {
|
|
if (m_communication->currentMessageId() != ModuleCommunication::Idle) {
|
|
m_communication->reset();
|
|
}
|
|
m_communication->enroll(username.toStdString(), timeout);
|
|
}
|
|
|
|
void Application::deleteUser(uint16_t userid) {
|
|
if (m_communication->currentMessageId() != ModuleCommunication::Idle) {
|
|
m_communication->reset();
|
|
}
|
|
m_communication->deleteUser(userid);
|
|
}
|
|
|
|
void Application::deleteAll() {
|
|
if (m_communication->currentMessageId() != ModuleCommunication::Idle) {
|
|
m_communication->reset();
|
|
}
|
|
m_communication->deleteAll();
|
|
}
|
|
|
|
void Application::getEnrolledImage(const QString &username, uint8_t timeout) {
|
|
if (m_communication->currentMessageId() != ModuleCommunication::Idle) {
|
|
m_communication->reset();
|
|
}
|
|
m_communication->enrollEx(username.toStdString(), timeout);
|
|
}
|
|
|
|
void Application::uploadImage() {
|
|
m_uploadImageSendedSize = 0;
|
|
std::ifstream ifs("palm.yuv", std::ofstream::binary);
|
|
m_uploadBuffer = std::vector<char>((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
|
|
|
|
ModuleCommunication::UploadImageInformation request;
|
|
mbedtls_md5_context context;
|
|
mbedtls_md5_init(&context);
|
|
mbedtls_md5_starts(&context);
|
|
mbedtls_md5_update(&context, reinterpret_cast<const uint8_t *>(m_uploadBuffer.data()), m_uploadBuffer.size());
|
|
mbedtls_md5_finish(&context, request.md5);
|
|
mbedtls_md5_free(&context);
|
|
|
|
request.operation = 0;
|
|
request.width = 600;
|
|
request.height = 800;
|
|
request.size = m_uploadBuffer.size();
|
|
strncpy(request.username, "下发测试", sizeof(request.username));
|
|
m_communication->uploadImageInfo(request);
|
|
LOG(info) << "upload image, md5: "
|
|
<< ModuleCommunication::protocolDataFormatString(request.md5, sizeof(request.md5));
|
|
|
|
m_startUploadTime = std::chrono::system_clock::now();
|
|
}
|
|
|
|
ModuleCommunication *Application::module() const {
|
|
return m_communication.get();
|
|
}
|
|
|
|
bool Application::connected() const {
|
|
return static_cast<bool>(m_communication);
|
|
}
|
|
|
|
bool Application::uvcOpened() const {
|
|
return static_cast<bool>(m_videoPlayer);
|
|
}
|
|
|
|
bool Application::persistenceMode() const {
|
|
return m_persistenceMode;
|
|
}
|
|
|
|
void Application::setPersistenceMode(bool enabled) {
|
|
if (m_persistenceMode != enabled) {
|
|
m_persistenceMode = enabled;
|
|
emit persistenceModeChanged();
|
|
}
|
|
}
|
|
|
|
int Application::persistenceVerifyInterval() const {
|
|
return m_persistenceVerifyInterval;
|
|
}
|
|
|
|
void Application::setPersistenceVerifyInterval(int interval) {
|
|
if (m_persistenceVerifyInterval != interval) {
|
|
m_persistenceVerifyInterval = interval;
|
|
emit persistenceVerifyIntervalChanged();
|
|
}
|
|
}
|
|
|
|
bool Application::isVerifying() const {
|
|
if (!m_communication) {
|
|
return false;
|
|
}
|
|
return (m_persistenceMode && m_persistenceModeStarted) ||
|
|
(m_communication->currentMessageId() == ModuleCommunication::Verify);
|
|
}
|
|
|
|
void Application::onNewPalmFeature(const PalmFeature &feature) {
|
|
auto palms = m_database->palmFeatures();
|
|
if (std::find(palms.cbegin(), palms.cend(), feature) != palms.cend()) {
|
|
LOG(warning) << "本地数据库已有相同特征数据。";
|
|
return;
|
|
}
|
|
if (!m_database->addPalmFeature(feature)) {
|
|
LOG(error) << "add palm feature failed.";
|
|
}
|
|
}
|
|
|
|
void Application::onErrorOccurred(const QString &error) {
|
|
QTimer::singleShot(0, this, [this]() { close(); });
|
|
}
|
|
|
|
void Application::onNewEnrolledImageInfo(uint32_t size, const uint8_t *md5) {
|
|
using namespace std::chrono;
|
|
m_enrolledImageSize = size;
|
|
m_communication->requestEnrolledImage(0, ImageSliceSize);
|
|
m_enrollYImageBuffer.clear();
|
|
m_startUploadTime = system_clock::now();
|
|
}
|
|
|
|
void Application::onNewImageSliceData(const std::vector<uint8_t> &data) {
|
|
using namespace std::chrono;
|
|
// LOG(info) << "onNewImageSliceData:" << data.size() << ", already received: " << m_enrollYImageBuffer.size()
|
|
// << ", total size: " << m_enrolledImageSize;
|
|
m_enrollYImageBuffer.append(reinterpret_cast<const char *>(data.data()), data.size());
|
|
if (m_enrollYImageBuffer.size() < m_enrolledImageSize) {
|
|
m_communication->requestEnrolledImage(m_enrollYImageBuffer.size(), ImageSliceSize);
|
|
} else {
|
|
LOG(info) << "request finished, elapsed: "
|
|
<< duration_cast<milliseconds>(system_clock::now() - m_startUploadTime);
|
|
QImage image(reinterpret_cast<const uint8_t *>(m_enrollYImageBuffer.data()), 600, 800,
|
|
QImage::Format_Grayscale8);
|
|
image.save("test.jpg");
|
|
|
|
std::ofstream ofs("palm.yuv", std::ofstream::binary);
|
|
ofs.write(m_enrollYImageBuffer.data(), m_enrollYImageBuffer.size());
|
|
}
|
|
}
|
|
|
|
void Application::onCommandStarted(ModuleCommunication::MessageId messageId) {
|
|
using namespace std::chrono;
|
|
if (messageId == ModuleCommunication::Verify) {
|
|
m_verifyStartTime = system_clock::now();
|
|
}
|
|
emit isVerifyingChanged();
|
|
}
|
|
|
|
void Application::onCommandFinished(ModuleCommunication::MessageId messageId,
|
|
ModuleCommunication::MessageStatus status) {
|
|
// LOG(info) << m_persistenceMode << " " << m_persistenceModeStarted << " " << m_persistenceVerifyInterval;
|
|
using namespace std::chrono;
|
|
if (messageId == ModuleCommunication::Verify) {
|
|
m_verifyElapsed = duration_cast<milliseconds>(system_clock::now() - m_verifyStartTime);
|
|
LOG(info) << "verify elapsed " << m_verifyElapsed;
|
|
if (m_verifyElapsed > hours(10 * 1000)) {
|
|
m_verifyElapsed = milliseconds(100);
|
|
}
|
|
}
|
|
|
|
if (messageId == ModuleCommunication::UploadImageInfo) {
|
|
m_communication->uploadImageData(
|
|
m_uploadImageSendedSize, (const uint8_t *)m_uploadBuffer.data() + m_uploadImageSendedSize, ImageSliceSize);
|
|
} else if (messageId == ModuleCommunication::UploadImageData) {
|
|
m_uploadImageSendedSize += ImageSliceSize;
|
|
if (m_uploadImageSendedSize < m_uploadBuffer.size()) {
|
|
auto remainSize = m_uploadBuffer.size() - m_uploadImageSendedSize;
|
|
m_communication->uploadImageData(m_uploadImageSendedSize,
|
|
(const uint8_t *)m_uploadBuffer.data() + m_uploadImageSendedSize,
|
|
remainSize < ImageSliceSize ? remainSize : ImageSliceSize);
|
|
} else {
|
|
LOG(info) << "upload finished, elapsed: "
|
|
<< duration_cast<milliseconds>(system_clock::now() - m_startUploadTime);
|
|
}
|
|
}
|
|
|
|
if (messageId == ModuleCommunication::Verify && m_persistenceMode) { // 持续识别逻辑
|
|
m_persistenceModeStarted = true;
|
|
} else if (messageId == ModuleCommunication::Reset) {
|
|
m_persistenceModeStarted = false;
|
|
m_verifyTimer->stop();
|
|
}
|
|
|
|
if (m_persistenceMode && m_persistenceModeStarted && messageId == ModuleCommunication::Verify &&
|
|
((status == ModuleCommunication::Success) || (status == ModuleCommunication::Failed4UnknownUser) ||
|
|
(status == ModuleCommunication::Failed4Timeout) || (status == ModuleCommunication::Failed4UnknownReason))) {
|
|
m_verifyTimer->start(m_persistenceVerifyInterval * 1000);
|
|
}
|
|
|
|
if ((messageId == ModuleCommunication::EnrollSingle) && (status == ModuleCommunication::Success)) {
|
|
emit newStatusTip(Info, "录入成功。");
|
|
}
|
|
emit isVerifyingChanged();
|
|
}
|
|
|
|
void Application::onVerifyTimeout() {
|
|
m_communication->verify(120);
|
|
}
|