From a43e9fb10d48e6441b1b30260e3f1d8c0ca51faf Mon Sep 17 00:00:00 2001 From: xiewenji <527774126@qq.com> Date: Tue, 16 Jun 2026 16:57:42 +0800 Subject: [PATCH] =?UTF-8?q?add=20=E6=B7=BB=E5=8A=A0=E8=A7=A3=E7=A0=81?= =?UTF-8?q?=E5=AD=98=E5=9B=BE=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AlgorithmModule/include/Define_Base.h | 3 +- ConfigModule/src/ConfigManager.cpp | 33 ---- ExtractImageModule/include/JpgDecoder.h | 21 ++ ExtractImageModule/src/JpgDecoder.cpp | 246 ++++++++++++++++++++++++ example/test_example.cpp | 144 +++++++++++++- 5 files changed, 412 insertions(+), 35 deletions(-) create mode 100644 ExtractImageModule/include/JpgDecoder.h create mode 100644 ExtractImageModule/src/JpgDecoder.cpp diff --git a/AlgorithmModule/include/Define_Base.h b/AlgorithmModule/include/Define_Base.h index 4531050..0ae8e88 100644 --- a/AlgorithmModule/include/Define_Base.h +++ b/AlgorithmModule/include/Define_Base.h @@ -24,7 +24,8 @@ enum Camera_IDX static const std::string strCameraName[] = { "left", - "right"}; + "right" + }; struct Camera_Info { Camera_IDX camera_ID; diff --git a/ConfigModule/src/ConfigManager.cpp b/ConfigModule/src/ConfigManager.cpp index 4750763..f913b5d 100644 --- a/ConfigModule/src/ConfigManager.cpp +++ b/ConfigModule/src/ConfigManager.cpp @@ -20,39 +20,6 @@ ConfigManager::~ConfigManager() { } -std::vector QX_Result_Names = - { - "OK", - "aotudian", - "other", - "line", - "zangwu", - "edge", - "ymhs", - "dianzhuang", - "posun", - "xianwei", - "shuizi", - "danban", - "fuchen", - }; -std::vector QX_Result_Code = - { - "P0000", - "MA507", - "MA508", - "MA506", - "MA504", - "MA503", - "MA502", - "MA505", - "MA501", - "P0001", - "P0002", - "P0003", - "P0004", -}; - int ReadFlawCodeConfig(std::string json_path) { m_FlawCodeList.erase(m_FlawCodeList.begin(), m_FlawCodeList.end()); diff --git a/ExtractImageModule/include/JpgDecoder.h b/ExtractImageModule/include/JpgDecoder.h new file mode 100644 index 0000000..2ba960d --- /dev/null +++ b/ExtractImageModule/include/JpgDecoder.h @@ -0,0 +1,21 @@ +/* + * @Description: 独立 JPEG 解码模块 —— 从文件路径解码 JPEG 图片 + * 支持单文件中包含多张 JPEG(SOI/EOI 标记分隔),纵向拼接为一张灰度图 + */ +#ifndef JPG_DECODER_H_ +#define JPG_DECODER_H_ + +#include +#include + +/** + * @brief 解码 JPEG 图片文件 + * 自动处理单文件中包含多张 JPEG 的情况(通过 SOI/EOI 标记), + * 将所有图片纵向拼接为一张灰度图返回 + * + * @param filePath 待解码的图片文件路径 + * @return cv::Mat 解码后的灰度图(CV_8UC1),失败则返回空 Mat + */ +cv::Mat decodeJpgImage(const std::string &filePath); + +#endif // JPG_DECODER_H_ diff --git a/ExtractImageModule/src/JpgDecoder.cpp b/ExtractImageModule/src/JpgDecoder.cpp new file mode 100644 index 0000000..cfeb54c --- /dev/null +++ b/ExtractImageModule/src/JpgDecoder.cpp @@ -0,0 +1,246 @@ +/* + * @Description: 独立 JPEG 解码模块实现 + * 从文件路径读取含多张 JPEG 的原始文件,解码并纵向拼接为灰度图 + */ +#include "JpgDecoder.h" + +#include +#include + +#include +#include +#include +#include + +namespace { + +// ---- 内部数据结构 ---- + +struct JpgSegment +{ + size_t imgDataSize = 0; // JPEG 数据字节数 + unsigned char *ptr_start = nullptr; // 指向 SOI (0xFF 0xD8) + unsigned char *ptr_end = nullptr; // 指向 EOI 之后的位置 + int width = 0; + int height = 0; +}; + +// ---- 步骤 1:读取文件到内存 ---- + +int readFileToBuffer(const std::string &filePath, + std::vector &buffer) +{ + std::ifstream file(filePath, std::ios::binary | std::ios::ate); + if (!file.is_open()) + { + std::cerr << "[JpgDecoder] 无法打开文件: " << filePath << std::endl; + return -1; + } + + size_t fileSize = file.tellg(); + file.seekg(0, std::ios::beg); + + buffer.resize(fileSize); + file.read(reinterpret_cast(buffer.data()), fileSize); + file.close(); + + if (file.fail()) + { + std::cerr << "[JpgDecoder] 读取文件失败: " << filePath << std::endl; + return -2; + } + + return 0; +} + +// ---- 步骤 2:在内存中定位所有 JPEG 片段(SOI→EOI) ---- + +int findJpgSegments(unsigned char *buffer, size_t fileSize, + std::vector &segments) +{ + int count = 0; + int offset = 0; + const int MAX_IMAGES = 3; // 最多提取 3 张 + + while (count < MAX_IMAGES) + { + unsigned char *ptr = buffer + offset; + unsigned char *soi = nullptr; + unsigned char *eoi = nullptr; + + // 找 SOI (0xFF 0xD8) + for (; ptr < buffer + fileSize - 1; ++ptr) + { + if (*ptr == 0xFF && *(ptr + 1) == 0xD8) + { + soi = ptr; + break; + } + } + + if (soi == nullptr) + break; // 没有更多 JPEG + + // 找 EOI (0xFF 0xD9),注意可能遇到下一个 SOI + bool foundNextSoi = false; + for (ptr = soi + 2; ptr < buffer + fileSize - 1; ++ptr) + { + if (*ptr == 0xFF && *(ptr + 1) == 0xD9) + { + eoi = ptr + 2; // 包含 EOI 的两字节 + break; + } + if (*ptr == 0xFF && *(ptr + 1) == 0xD8) + { + // 还没找到 EOI 就又遇到 SOI,截断当前段 + eoi = ptr - 1; + foundNextSoi = true; + break; + } + } + + if (foundNextSoi) + { + offset = static_cast(eoi - buffer); + continue; + } + + if (eoi == nullptr) + break; // 没找到 EOI,文件可能损坏 + + JpgSegment seg; + seg.ptr_start = soi; + seg.ptr_end = eoi; + seg.imgDataSize = eoi - soi; + segments.push_back(seg); + + offset = static_cast(eoi - buffer); + count++; + } + + return 0; +} + +// ---- 步骤 3:turbojpeg 多线程解码,纵向拼接为一张灰度图 ---- + +int decodeJpgSegments(std::vector &segments, cv::Mat &outImg) +{ + if (segments.empty()) + return -1; + + int commonWidth = 0; + int totalHeight = 0; + + // 第一遍:读取所有 JPEG 头信息,验证宽度一致,累加高度 + for (auto &seg : segments) + { + tjhandle handle = tjInitDecompress(); + if (!handle) + { + std::cerr << "[JpgDecoder] tjInitDecompress 失败!" << std::endl; + return -2; + } + + int jpegSubsamp = 0, jpegColorspace = 0; + if (tjDecompressHeader3(handle, + seg.ptr_start, seg.imgDataSize, + &seg.width, &seg.height, + &jpegSubsamp, &jpegColorspace) != 0) + { + std::cerr << "[JpgDecoder] tjDecompressHeader3 错误: " + << tjGetErrorStr() << std::endl; + tjDestroy(handle); + return -3; + } + tjDestroy(handle); + + if (commonWidth == 0) + commonWidth = seg.width; + else if (commonWidth != seg.width) + { + std::cerr << "[JpgDecoder] 所有图片宽度必须一致!" << std::endl; + return -4; + } + + totalHeight += seg.height; + } + + // 分配输出大图(单通道灰度) + outImg = cv::Mat(totalHeight, commonWidth, CV_8UC1); + + // 多线程解码到不同纵向偏移位置 + auto worker = [&](JpgSegment &seg, int yOffset) + { + tjhandle handle = tjInitDecompress(); + if (!handle) + { + std::cerr << "[JpgDecoder] tjInitDecompress 失败!" << std::endl; + return; + } + + if (tjDecompress2(handle, + seg.ptr_start, seg.imgDataSize, + outImg.ptr(yOffset), seg.width, + outImg.step, seg.height, + TJPF_GRAY, TJFLAG_FASTDCT) != 0) + { + std::cerr << "[JpgDecoder] tjDecompress2 错误: " + << tjGetErrorStr() << std::endl; + } + + tjDestroy(handle); + }; + + std::vector threads; + threads.reserve(segments.size()); + + int yOffset = 0; + for (auto &seg : segments) + { + threads.emplace_back(worker, std::ref(seg), yOffset); + yOffset += seg.height; + } + + for (auto &t : threads) + { + if (t.joinable()) + t.join(); + } + + return 0; +} + +} // anonymous namespace + +// ============================================================ +// 公开接口 +// ============================================================ + +cv::Mat decodeJpgImage(const std::string &filePath) +{ + // 1. 读取文件到内存 + std::vector buffer; + if (readFileToBuffer(filePath, buffer) != 0) + { + return cv::Mat(); + } + + // 2. 定位所有 JPEG 片段 + std::vector segments; + if (findJpgSegments(buffer.data(), buffer.size(), segments) != 0 || + segments.empty()) + { + std::cerr << "[JpgDecoder] 未找到有效的 JPEG 数据: " << filePath << std::endl; + return cv::Mat(); + } + + // 3. 解码并拼接 + cv::Mat result; + if (decodeJpgSegments(segments, result) != 0 || result.empty()) + { + std::cerr << "[JpgDecoder] JPEG 解码失败: " << filePath << std::endl; + return cv::Mat(); + } + + return result; +} diff --git a/example/test_example.cpp b/example/test_example.cpp index 28dae2c..ab077a7 100644 --- a/example/test_example.cpp +++ b/example/test_example.cpp @@ -6,6 +6,12 @@ #include #include #include +#include "JpgDecoder.h" +#include +#include +#include + +namespace fs = std::filesystem; void handler(int sig) { printf("Get handler sig"); @@ -85,6 +91,7 @@ int main(int argc, char *argv[]) cout << "*** 5、./test_JBL_Check -falign filepath num : 批量测试定位算法,num 至多处理张数,结果:/home/aidlux/BOE/Align" << endl; cout << "*** 6、./test_JBL_Check -rjson filepath: 单张复测,filepath xx/xx/3A3K380001B1DK_20240408_152157_Main_0_2_L255" << endl; cout << "*** 6、./test_JBL_Check -rjsonall filepath: 一套图复测,filepath xx/xx/" << endl; + cout << "*** 7、./test_CellAOI -decode [dir]: 批量解码 .ytimage → .png 到 /home/aidlux/BOE/CELL_AOI/decode_img" << endl; cout << "******************************************************" << endl; return 0; @@ -96,12 +103,24 @@ int main(int argc, char *argv[]) printf("argv[%d]=%s\n", i, argv[i]); } + bool bDecodeMode = false; + std::string decodeInputDir; + if (argc > 1 && string(argv[1]) != "-h") { for (int i = 1; i < argc; i++) { - if (string(argv[i]) == "-rs") + if (string(argv[i]) == "-decode") + { + bDecodeMode = true; + if (i + 1 < argc && argv[i + 1][0] != '-') + { + decodeInputDir = string(argv[i + 1]); + i++; + } + } + else if (string(argv[i]) == "-rs") { test.runConfig.run_Type = Process_Run_SaveImg; } @@ -156,6 +175,129 @@ int main(int argc, char *argv[]) } } + // ====== 批量解码模式 (-decode) ====== + if (bDecodeMode) + { + std::string inputDir = decodeInputDir.empty() + ? std::string("../data/img/t1") + : decodeInputDir; + std::string outputDir = "/home/aidlux/BOE/CELL_AOI/decode_img"; + + printf("\n========== 批量解码模式 ==========\n"); + printf(" 输入目录: %s\n", inputDir.c_str()); + printf(" 输出目录: %s\n", outputDir.c_str()); + printf("===================================\n\n"); + + // 检查输入目录 + if (!fs::exists(inputDir) || !fs::is_directory(inputDir)) + { + printf("[decode] 输入目录不存在: %s\n", inputDir.c_str()); + return -1; + } + + // 创建输出目录 + _sysmkdirs(outputDir); + + // 只解码 .ytimage 原始多JPEG文件 + auto isYtimageFile = [](const std::string &ext) -> bool { + std::string lower = ext; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + return (lower == ".ytimage"); + }; + + // 扫描所有 .ytimage 文件 + std::vector imageFiles; + try + { + for (const auto &entry : fs::recursive_directory_iterator(inputDir)) + { + if (entry.is_regular_file() && isYtimageFile(entry.path().extension().string())) + imageFiles.push_back(entry.path()); + } + } + catch (const fs::filesystem_error &e) + { + printf("[decode] 扫描目录出错: %s\n", e.what()); + return -1; + } + + if (imageFiles.empty()) + { + printf("[decode] 未找到 .ytimage 文件: %s\n", inputDir.c_str()); + return 0; + } + + printf("[decode] 找到 %zu 个 .ytimage 文件,开始批量解码...\n", imageFiles.size()); + + // 逐个解码并保存 + int successCount = 0; + int failCount = 0; + struct timeval tvTotalStart, tvTotalEnd; + gettimeofday(&tvTotalStart, nullptr); + + for (size_t i = 0; i < imageFiles.size(); ++i) + { + const auto &imgPath = imageFiles[i]; + std::string inputPath = imgPath.string(); + + printf("[%zu/%zu] %s ... ", i + 1, imageFiles.size(), imgPath.filename().c_str()); + fflush(stdout); + + struct timeval tv1, tv2; + gettimeofday(&tv1, nullptr); + + // ★ 调用 JpgDecoder 独立模块解码 + cv::Mat decoded = decodeJpgImage(inputPath); + + gettimeofday(&tv2, nullptr); + long elapsed = (tv2.tv_sec - tv1.tv_sec) * 1000 + (tv2.tv_usec - tv1.tv_usec) / 1000; + + if (decoded.empty()) + { + printf("失败 (%ld ms)\n", elapsed); + failCount++; + continue; + } + + // 构造输出路径(保持相对目录结构) + fs::path relativePath = fs::relative(imgPath, inputDir); + fs::path outputPath = fs::path(outputDir) / relativePath; + outputPath.replace_extension(".png"); // 统一保存为 PNG + + // 确保输出子目录存在 + std::string outDir = outputPath.parent_path().string(); + _sysmkdirs(outDir); + + // 保存 + if (cv::imwrite(outputPath.string(), decoded)) + { + printf("OK → %s [%dx%d] (%ld ms)\n", + outputPath.filename().c_str(), decoded.cols, decoded.rows, elapsed); + successCount++; + } + else + { + printf("保存失败\n"); + failCount++; + } + } + + gettimeofday(&tvTotalEnd, nullptr); + long totalElapsed = (tvTotalEnd.tv_sec - tvTotalStart.tv_sec) * 1000 + + (tvTotalEnd.tv_usec - tvTotalStart.tv_usec) / 1000; + + printf("\n========================================\n"); + printf("[decode] 批量解码完成!\n"); + printf(" 输入目录: %s\n", inputDir.c_str()); + printf(" 输出目录: %s\n", outputDir.c_str()); + printf(" 成功: %d 张\n", successCount); + printf(" 失败: %d 张\n", failCount); + printf(" 总耗时: %ld ms\n", totalElapsed); + printf("========================================\n"); + + return (successCount > 0) ? 0 : -1; + } + test.runConfig.print("config"); // getchar(); signal(SIGINT, handler);