diff --git a/Analyser/Application.cpp b/Analyser/Application.cpp index a19c284..cbc1127 100644 --- a/Analyser/Application.cpp +++ b/Analyser/Application.cpp @@ -4,7 +4,10 @@ #include "CategoryLogSinkBackend.h" #include "Configuration.h" #include "Database.h" +#include "DateTime.h" #include "DeviceDiscovery.h" +#include "ImageDecoder.h" +#include "StringUtility.h" #include "VideoFrameProvider.h" #include "VideoPlayer.h" #include @@ -13,6 +16,7 @@ #include #include #include +#include #include #include @@ -39,6 +43,12 @@ Application::Application(int &argc, char **argv) : m_app(std::make_sharedverify(timeout); } -void Application::enroll(const QString &username, uint8_t timeout) { +void Application::enroll(const QString &username, bool persistence, uint8_t timeout) { if (m_communication->currentMessageId() != ModuleCommunication::Idle) { m_communication->reset(); } - m_communication->enroll(username.toStdString(), timeout); + m_communication->enroll(username.toStdString(), persistence, timeout); + m_enrollUsername = username; } void Application::deleteUser(uint16_t userid) { @@ -178,23 +189,32 @@ void Application::deleteAll() { m_communication->deleteAll(); } -void Application::getEnrolledImage(const QString &username, uint8_t timeout) { +void Application::enrollExtended(const QString &username, bool persistence, uint8_t timeout) { if (m_communication->currentMessageId() != ModuleCommunication::Idle) { m_communication->reset(); } - m_communication->enrollEx(username.toStdString(), timeout); + m_communication->enrollExtended(username.toStdString(), persistence, timeout); + m_enrollUsername = username; } void Application::uploadImage() { m_uploadImageSendedSize = 0; - std::ifstream ifs("palm.yuv", std::ofstream::binary); - m_uploadBuffer = std::vector((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); + + // std::ifstream ifs("palm.yuv", std::ofstream::binary); + // m_uploadBuffer = std::vector((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); + auto image = ImageDecoder::extractJpegYComponent("E:/Downloads/7207002051849490432.jpg"); + if (!image) { + LOG(error) << "decode failed."; + return; + } + m_uploadBuffer = image->data; + LOG(info) << "width: " << image->width << ", height: " << image->height; ModuleCommunication::UploadImageInformation request; mbedtls_md5_context context; mbedtls_md5_init(&context); mbedtls_md5_starts(&context); - mbedtls_md5_update(&context, reinterpret_cast(m_uploadBuffer.data()), m_uploadBuffer.size()); + mbedtls_md5_update(&context, m_uploadBuffer.data(), m_uploadBuffer.size()); mbedtls_md5_finish(&context, request.md5); mbedtls_md5_free(&context); @@ -283,14 +303,21 @@ void Application::onNewImageSliceData(const std::vector &data) { if (m_enrollYImageBuffer.size() < m_enrolledImageSize) { m_communication->requestEnrolledImage(m_enrollYImageBuffer.size(), ImageSliceSize); } else { - LOG(info) << "request finished, elapsed: " - << duration_cast(system_clock::now() - m_startUploadTime); + auto username = m_enrollUsername.toStdString(); + LOG(info) << "request finished, username: " << username + << ", elapsed: " << duration_cast(system_clock::now() - m_startUploadTime); + std::ostringstream oss; + oss << YuvPath << "/" << username << "_" << DateTime::toString(std::chrono::system_clock::now(), "%Y%m%d%H%M%S") + << ".yuv"; + std::ofstream ofs(Amass::StringUtility::UTF8ToGBK(oss.str()), std::ofstream::binary); + ofs.write(m_enrollYImageBuffer.data(), m_enrollYImageBuffer.size()); + QImage image(reinterpret_cast(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()); + oss.str(""); + oss << JpgPath << "/" << username << "_" << DateTime::toString(std::chrono::system_clock::now(), "%Y%m%d%H%M%S") + << ".jpg"; + image.save(QString::fromStdString(oss.str()), "jpg", 100); } } @@ -304,8 +331,8 @@ void Application::onCommandFinished(ModuleCommunication::MessageId messageId, // LOG(info) << m_persistenceMode << " " << m_persistenceModeStarted << " " << m_persistenceVerifyInterval; using namespace std::chrono; if (messageId == ModuleCommunication::UploadImageInfo) { - m_communication->uploadImageData( - m_uploadImageSendedSize, (const uint8_t *)m_uploadBuffer.data() + m_uploadImageSendedSize, ImageSliceSize); + m_communication->uploadImageData(m_uploadImageSendedSize, m_uploadBuffer.data() + m_uploadImageSendedSize, + ImageSliceSize); } else if (messageId == ModuleCommunication::UploadImageData) { m_uploadImageSendedSize += ImageSliceSize; if (m_uploadImageSendedSize < m_uploadBuffer.size()) { @@ -332,7 +359,8 @@ void Application::onCommandFinished(ModuleCommunication::MessageId messageId, m_verifyTimer->start(m_persistenceVerifyInterval * 1000); } - if ((messageId == ModuleCommunication::EnrollSingle) && (status == ModuleCommunication::Success)) { + if (((messageId == ModuleCommunication::EnrollSingle) || (messageId == ModuleCommunication::EnrollExtended)) && + (status == ModuleCommunication::Success)) { emit newStatusTip(Info, "录入成功。"); } emit isVerifyingChanged(); diff --git a/Analyser/Application.h b/Analyser/Application.h index a237e66..156e737 100644 --- a/Analyser/Application.h +++ b/Analyser/Application.h @@ -25,6 +25,8 @@ class Application : public QObject { persistenceVerifyIntervalChanged) Q_PROPERTY(bool isVerifying READ isVerifying NOTIFY isVerifyingChanged) friend class Amass::Singleton; + static constexpr auto JpgPath = "jpg"; + static constexpr auto YuvPath = "yuv"; public: enum TipType { @@ -43,10 +45,10 @@ public: Q_INVOKABLE void close(); Q_INVOKABLE void closeUVC(); Q_INVOKABLE void verify(uint8_t timeout); - Q_INVOKABLE void enroll(const QString &username, uint8_t timeout); + Q_INVOKABLE void enroll(const QString &username, bool persistence, uint8_t timeout); + Q_INVOKABLE void enrollExtended(const QString &username, bool persistence, uint8_t timeout); Q_INVOKABLE void deleteUser(uint16_t userid); Q_INVOKABLE void deleteAll(); - Q_INVOKABLE void getEnrolledImage(const QString &username, uint8_t timeout); Q_INVOKABLE void uploadImage(); ModuleCommunication *module() const; bool connected() const; @@ -90,11 +92,12 @@ private: QTimer *m_verifyTimer = nullptr; uint32_t m_enrolledImageSize = 0; + QString m_enrollUsername; QByteArray m_enrollYImageBuffer; std::chrono::system_clock::time_point m_startUploadTime; uint32_t m_uploadImageSendedSize; - std::vector m_uploadBuffer; + std::vector m_uploadBuffer; std::shared_ptr m_videoPlayer; VideoFrameProvider *m_videoFrameProvider; diff --git a/Analyser/CMakeLists.txt b/Analyser/CMakeLists.txt index 6bc9541..2ca7922 100644 --- a/Analyser/CMakeLists.txt +++ b/Analyser/CMakeLists.txt @@ -4,8 +4,8 @@ set(APPLICATION_NAME "掌静脉测试工具") set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Quick QuickTemplates2 Widgets SerialPort) -find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Quick QuickTemplates2 Widgets SerialPort) +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Quick QuickTemplates2 Widgets SerialPort JpegPrivate BundledLibjpeg) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Quick QuickTemplates2 Widgets SerialPort JpegPrivate BundledLibjpeg) configure_file(Configuration.h.in Configuration.h) @@ -13,6 +13,7 @@ add_executable(Analyser Analyser.rc main.cpp Application.h Application.cpp CategoryLogSinkBackend.h CategoryLogSinkBackend.cpp + ImageDecoder.h ImageDecoder.cpp Widget.h Widget.cpp ModuleCommunication.h ModuleCommunication.cpp PalmFeatureTableModel.h PalmFeatureTableModel.cpp @@ -67,4 +68,6 @@ target_link_libraries(Analyser PRIVATE Qt${QT_VERSION_MAJOR}::QuickTemplates2 PRIVATE Qt${QT_VERSION_MAJOR}::Widgets PRIVATE Qt${QT_VERSION_MAJOR}::SerialPort + PRIVATE Qt${QT_VERSION_MAJOR}::JpegPrivate + PRIVATE Qt${QT_VERSION_MAJOR}::BundledLibjpeg ) diff --git a/Analyser/ImageDecoder.cpp b/Analyser/ImageDecoder.cpp new file mode 100644 index 0000000..ef4e269 --- /dev/null +++ b/Analyser/ImageDecoder.cpp @@ -0,0 +1,41 @@ +#include "ImageDecoder.h" +#include "BoostLog.h" +#include +#include + +std::optional ImageDecoder::extractJpegYComponent(const std::string &filename) { + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_decompress(&cinfo); + + FILE *infile = fopen(filename.c_str(), "rb"); + if (infile == NULL) { + LOG(error) << "cannot open " << filename; + return std::nullopt; + } + ImageDecoder::Image ret; + jpeg_stdio_src(&cinfo, infile); + jpeg_read_header(&cinfo, TRUE); + cinfo.out_color_space = JCS_YCbCr; // We want YCbCr color space + jpeg_start_decompress(&cinfo); + + int row_stride = cinfo.output_width * cinfo.output_components; + JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1); + + ret.width = cinfo.output_width; + ret.height = cinfo.output_height; + ret.data.resize(ret.width * ret.height); + while (cinfo.output_scanline < cinfo.output_height) { + jpeg_read_scanlines(&cinfo, buffer, 1); + for (unsigned int i = 0; i < cinfo.output_width; ++i) { + ret.data[(cinfo.output_scanline - 1) * cinfo.output_width + i] = + buffer[0][i * 3]; // Y component is the first byte in YCbCr + } + } + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + fclose(infile); + return std::make_optional(ret); +} diff --git a/Analyser/ImageDecoder.h b/Analyser/ImageDecoder.h new file mode 100644 index 0000000..b35ad51 --- /dev/null +++ b/Analyser/ImageDecoder.h @@ -0,0 +1,18 @@ +#ifndef __IMAGEDECODER_H__ +#define __IMAGEDECODER_H__ + +#include +#include +#include + +class ImageDecoder { +public: + struct Image { + uint32_t width; + uint32_t height; + std::vector data; + }; + static std::optional extractJpegYComponent(const std::string &filename); +}; + +#endif // __IMAGEDECODER_H__ diff --git a/Analyser/ModuleCommunication.cpp b/Analyser/ModuleCommunication.cpp index 4e29de1..16af56c 100644 --- a/Analyser/ModuleCommunication.cpp +++ b/Analyser/ModuleCommunication.cpp @@ -53,9 +53,10 @@ void ModuleCommunication::reset() { LOG_CAT(info, GUI) << Separator; } -void ModuleCommunication::enroll(const std::string &username, uint8_t timeout) { +void ModuleCommunication::enroll(const std::string &username, bool persistence, uint8_t timeout) { EnrollData data = {0}; data.timeout = timeout; + data.skipSave = persistence ? 0 : 1; strncpy(reinterpret_cast(data.username), username.c_str(), sizeof(data.username)); auto [frameData, frameSize] = generateFrame(EnrollSingle, reinterpret_cast(&data), sizeof(data)); m_serialPort->write(reinterpret_cast(frameData), frameSize); @@ -66,11 +67,12 @@ void ModuleCommunication::enroll(const std::string &username, uint8_t timeout) { LOG_CAT(info, GUI) << Separator; } -void ModuleCommunication::enrollEx(const std::string &username, uint8_t timeout) { +void ModuleCommunication::enrollExtended(const std::string &username, bool persistence, uint8_t timeout) { EnrollData data = {0}; data.timeout = timeout; + data.skipSave = persistence ? 0 : 1; strncpy(reinterpret_cast(data.username), username.c_str(), sizeof(data.username)); - auto [frameData, frameSize] = generateFrame(EnrollGetImage, reinterpret_cast(&data), sizeof(data)); + auto [frameData, frameSize] = generateFrame(EnrollExtended, reinterpret_cast(&data), sizeof(data)); m_serialPort->write(reinterpret_cast(frameData), frameSize); LOG_CAT(info, GUI) << "发送获取注册照片指令: " << protocolDataFormatString(frameData, frameSize); @@ -176,6 +178,13 @@ void ModuleCommunication::requestCurrentStatus() { LOG_CAT(info, GUI) << Separator; } +void ModuleCommunication::requestUniqueId() { + auto [frameData, frameSize] = generateFrame(GetUniqueID); + m_serialPort->write(reinterpret_cast(frameData), frameSize); + LOG_CAT(info, GUI) << "发送获取ID指令: " << protocolDataFormatString(frameData, frameSize); + LOG_CAT(info, GUI) << Separator; +} + ModuleCommunication::MessageId ModuleCommunication::currentMessageId() const { return m_currentMessageId; } @@ -236,13 +245,14 @@ void ModuleCommunication::processPackage(const uint8_t *data, uint16_t size) { LOG_CAT(info, GUI) << Separator; break; } - case EnrollGetImage: { + case EnrollExtended: { LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size); if (result == Success) { - auto info = reinterpret_cast(data + 7); - uint16_t width = ntohs(info->width); - uint16_t height = ntohs(info->height); - LOG_CAT(info, GUI) << "图片大小: " << width << "x" << height; + auto info = reinterpret_cast(data + 7); + uint16_t width = ntohs(info->image_width); + uint16_t height = ntohs(info->image_height); + LOG_CAT(info, GUI) << "注册成功,用户ID: " << ntohs(info->userid) << ", 图片大小: " << width << "x" + << height; emit newEnrolledImageInfo(width * height, info->md5); } LOG_CAT(info, GUI) << Separator; @@ -309,6 +319,9 @@ void ModuleCommunication::processPackage(const uint8_t *data, uint16_t size) { auto info = reinterpret_cast(data + 7); LOG_CAT(info, GUI) << "图片下发注册成功,用户ID: " << ntohs(info->userid); LOG_CAT(info, GUI) << Separator; + } else if (result == Needmore) { + } else { + LOG(info) << "UploadImageData status: " << static_cast(result); } break; } @@ -318,6 +331,13 @@ void ModuleCommunication::processPackage(const uint8_t *data, uint16_t size) { LOG_CAT(info, GUI) << Separator; break; } + case GetUniqueID: { + LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size); + auto id = reinterpret_cast(data + 7); + LOG_CAT(info, GUI) << "模组ID: " << std::string_view(id->id, sizeof(id->id)); + LOG_CAT(info, GUI) << Separator; + break; + } default: LOG(warning) << "unknown reply command: 0x" << (static_cast(replyId) & 0xff) << ", data: " << protocolDataFormatString(data, size); diff --git a/Analyser/ModuleCommunication.h b/Analyser/ModuleCommunication.h index d1f4dec..b750105 100644 --- a/Analyser/ModuleCommunication.h +++ b/Analyser/ModuleCommunication.h @@ -23,10 +23,11 @@ public: GetCurrentStatus = 0x11, Verify = 0x12, EnrollSingle = 0x1D, - EnrollGetImage = 0x1E, + EnrollExtended = 0x1E, GetEnrolledImage = 0x1F, DeleteUser = 0x20, DeleteAll = 0x21, + GetUniqueID = 0xAC, UploadImageInfo = 0xF6, UploadImageData = 0xF7, RegisterPalmFeature = 0xF9, @@ -57,6 +58,7 @@ public: Failed4ReadFile = 19, Failed4WriteFile = 20, Failed4NoEncrypt = 21, + Needmore = 25, }; #pragma pack(1) @@ -80,7 +82,7 @@ public: struct EnrollData { uint8_t admin = 0; uint8_t username[32]; - uint8_t palmDirection; + uint8_t skipSave = 0; uint8_t timeout; }; @@ -89,10 +91,11 @@ public: uint8_t face_direction; // depleted, user ignore this field }; - struct EnrolledImageReply { + struct EnrollExtendedReply { + uint16_t userid; + uint16_t image_width; + uint16_t image_height; uint8_t image_format; // 0: 只有Y分量,灰度图 - uint16_t width; - uint16_t height; uint8_t md5[16]; }; @@ -139,16 +142,21 @@ public: uint8_t data[0]; }; + struct ModuleId { + char id[32]; + }; + #pragma pack() explicit ModuleCommunication(QObject *parent = nullptr); bool open(const QString &portName, int baudRate); Q_INVOKABLE void verify(uint8_t timeout); Q_INVOKABLE void reset(); - void enroll(const std::string &username, uint8_t timeout); - void enrollEx(const std::string &username, uint8_t timeout); + void enroll(const std::string &username, bool persistence, uint8_t timeout); + void enrollExtended(const std::string &username, bool persistence, uint8_t timeout); Q_INVOKABLE void deleteUser(uint16_t userid); Q_INVOKABLE void deleteAll(); + Q_INVOKABLE void requestUniqueId(); void requestEnrolledImage(uint32_t offset, uint32_t size); void requestPalmFeature(uint16_t userid); diff --git a/Analyser/Widget.cpp b/Analyser/Widget.cpp index 04fa671..66a8c72 100644 --- a/Analyser/Widget.cpp +++ b/Analyser/Widget.cpp @@ -229,7 +229,7 @@ void Widget::onEnrollButtonClicked() { if (!module) return; auto name = m_enrollNameEdit->text(); auto timeout = m_enrollTimeoutEdit->text().toInt(); - module->enroll(name.toStdString(), timeout); + module->enroll(name.toStdString(), true, timeout); } void Widget::onEnrollExButtonClicked() { @@ -237,7 +237,7 @@ void Widget::onEnrollExButtonClicked() { if (!module) return; auto name = m_enrollNameEdit->text(); auto timeout = m_enrollTimeoutEdit->text().toInt(); - module->enrollEx(name.toStdString(), timeout); + module->enrollExtended(name.toStdString(), true, timeout); } void Widget::onVerifyButtonClicked() { diff --git a/Analyser/qml/OperationItem.qml b/Analyser/qml/OperationItem.qml index a504af5..8fed3dd 100644 --- a/Analyser/qml/OperationItem.qml +++ b/Analyser/qml/OperationItem.qml @@ -31,13 +31,40 @@ ColumnLayout { implicitWidth: 100 text: "10" } + + Text { + text: qsTr("持久化") + } + Switch { + id: persistence + checked: true + } + + Text { + text: qsTr("保存图片") + } + Switch { + id: extendedMode + } + Button { - text: !App.module ? "注册" : App.module.currentMessageId - === 0x1d ? "取消" : "注册" - onClicked: App.module.currentMessageId - === 0x1d ? App.module.reset() : App.enroll( - enrollName.text, - parseInt(enrollTimeout.text)) + property bool enrolling: App.module ? (App.module.currentMessageId === 0x1d) + || (App.module.currentMessageId + === 0x1e) : false + text: enrolling ? "取消" : "注册" + onClicked: { + if (enrolling) { + App.module.reset() + } else if (extendedMode.checked) { + App.enrollExtended(enrollName.text, + persistence.checked, + parseInt(enrollTimeout.text)) + } else { + App.enroll(enrollName.text, + persistence.checked, + parseInt(enrollTimeout.text)) + } + } } } } @@ -108,10 +135,6 @@ ColumnLayout { title: "图片注册" GridLayout { columns: 1 - Button { - text: "录入图片上报" - onClicked: App.getEnrolledImage("", 60) - } Button { text: "图片下发注册" onClicked: App.uploadImage() @@ -126,6 +149,10 @@ ColumnLayout { text: "状态查询" onClicked: App.module.requestCurrentStatus() } + Button { + text: "ID查询" + onClicked: App.module.requestUniqueId() + } } } } diff --git a/Readme.md b/Readme.md index 7f7d95b..c51d806 100644 --- a/Readme.md +++ b/Readme.md @@ -57,7 +57,7 @@ HOST_TOOLS := /opt/Xuantie-900-gcc-elf-newlib-x86_64-V2.6.1/bin ```shell ./rebuild-app.sh y L015 V200 R002 # 编译烧录固件 -./rebuild-app-ota.sh y L015 V200 R002 12 # 编译OTA固件,11为OTA版本号 +./rebuild-app-ota.sh y L015 V200 R002 13 # 编译OTA固件,11为OTA版本号 600X800 ```