diff --git a/example/deal.cpp b/example/deal.cpp index 925f60a..3bdb8b9 100644 --- a/example/deal.cpp +++ b/example/deal.cpp @@ -1327,8 +1327,9 @@ int deal::DetImg() return 0; } -/// @brief 监控模式:每 5 分钟执行一次模型推理,并把当前设备资源消耗(CPU 内存 / GPU 显存) -/// 写入 monitor.log;当某次推理输出 out 图至少一个像素不为全黑时,保存该 out 图并退出监控。 +/// @brief 监控模式:每 10 秒执行一次模型推理,并把当前设备资源消耗(CPU 内存 / GPU 显存) +/// 写入 monitor.log;out 图重复覆盖保存到输入图同名 out 文件(*_in.png -> *_out.png), +/// 当某次推理输出 out 图至少一个像素不为全黑时退出监控。 /// @return 0 表示成功,非 0 表示初始化失败 int deal::MonitorRun() { @@ -1397,9 +1398,25 @@ int deal::MonitorRun() return 1; } - // 预处理成模型输入(只读一次,每轮复用) + // 输出目录:输入目录去掉末尾 '/' 后追加 "_TEST/"(与 TestAIDet 保持一致) + std::string strDstRoot = strSrcRoot; + while (strDstRoot.size() > 1 && strDstRoot.back() == '/') + { + strDstRoot.pop_back(); + } + strDstRoot += "_TEST/"; + { + std::error_code ec; + fs::create_directories(strDstRoot, ec); + } + + // 预处理成模型输入(只读一次,每轮复用),并计算对应输出路径(*_in.png -> *_out.png) + std::vector okInList; std::vector inImgs; + std::vector outList; + okInList.reserve(inList.size()); inImgs.reserve(inList.size()); + outList.reserve(inList.size()); for (size_t i = 0; i < inList.size(); i++) { cv::Mat inImg = cv::imread(inList[i], cv::IMREAD_UNCHANGED); @@ -1429,7 +1446,18 @@ int deal::MonitorRun() { cv::resize(inImg, inImg, cv::Size(nModelInW, nModelInH)); } + okInList.push_back(inList[i]); inImgs.push_back(inImg); + + std::string rel = inList[i].substr(strSrcRoot.size()); + const std::string inSuffix = "_in.png"; + const std::string outSuffix = "_out.png"; + size_t pos = rel.rfind(inSuffix); + if (pos != std::string::npos) + { + rel.replace(pos, inSuffix.size(), outSuffix); + } + outList.push_back(strDstRoot + rel); } if (inImgs.empty()) { @@ -1437,28 +1465,8 @@ int deal::MonitorRun() return 1; } - // 输出目录:输入目录去掉末尾 '/' 后追加 "_TEST/"(与 TestAIDet 保持一致) - std::string strDstRoot = strSrcRoot; - while (strDstRoot.size() > 1 && strDstRoot.back() == '/') - { - strDstRoot.pop_back(); - } - strDstRoot += "_TEST/"; - - // 本次监控的时间戳输出目录(存图和 log 都放这里) - time_t startT = time(NULL); - struct tm stmv; - localtime_r(&startT, &stmv); - char dirTs[64] = {0}; - strftime(dirTs, sizeof(dirTs), "monitor_%Y%m%d_%H%M%S", &stmv); - std::string strOutDir = strDstRoot + dirTs + "/"; - { - std::error_code ec; - fs::create_directories(strOutDir, ec); - } - // 打开监控日志(追加写) - std::string strLogPath = strOutDir + "monitor.log"; + std::string strLogPath = strDstRoot + "monitor.log"; std::ofstream logFile(strLogPath, std::ios::app); if (!logFile) { @@ -1474,11 +1482,9 @@ int deal::MonitorRun() logFile.flush(); }; - const int nIntervalSec = 5 * 60; // 5 分钟 - const int nMaxRunSec = 30 * 60; // 30 分钟 + const int nIntervalSec = 1; // 1 秒 long nRound = 0; - auto monitorStart = std::chrono::steady_clock::now(); - logLine("===== monitor start (run max 30 min) ====="); + logLine("===== monitor start (every 10s, exit on first non-black out) ====="); // 初始化 CPU 采样基准(用于计算 CPU 使用率) struct rusage usagePrev; @@ -1489,16 +1495,6 @@ int deal::MonitorRun() while (true) { - // 0. 运行 30 分钟后停止 - double elapsedSec = std::chrono::duration( - std::chrono::steady_clock::now() - monitorStart) - .count(); - if (elapsedSec >= nMaxRunSec) - { - logLine("===== monitor exit (timeout 30 min) ====="); - break; - } - nRound++; // 1. 采集设备资源消耗 @@ -1557,8 +1553,8 @@ int deal::MonitorRun() gpuUsedMB, gpuTotalMB); logLine(buf); - // 2. 逐图推理,遇到 out 非全黑则保存对应的输入图和输出图 - int nHit = 0; + // 2. 逐图推理,out 图重复覆盖保存;遇到非全黑则退出 + bool bHit = false; for (size_t i = 0; i < inImgs.size(); i++) { cv::Mat outImg; @@ -1567,49 +1563,215 @@ int deal::MonitorRun() { snprintf(buf, sizeof(buf), "[round %ld] infer fail(%d) on image %zu: %s", - nRound, re, i, inList[i].c_str()); + nRound, re, i, okInList[i].c_str()); logLine(buf); continue; } + // 输出图重复覆盖保存到 *_out.png + bool bSaved = false; + { + fs::path outParent = fs::path(outList[i]).parent_path(); + if (!outParent.empty()) + { + std::error_code ec; + fs::create_directories(outParent, ec); + } + bSaved = cv::imwrite(outList[i], outImg); + } + int nNonZero = cv::countNonZero(outImg); if (nNonZero > 0) { - // 输出图不全黑 -> 保存对应的输入图和输出图 - std::string saveInPath = strOutDir + "monitor_hit_round" + - std::to_string(nRound) + "_img" + - std::to_string(i) + "_in.png"; - std::string saveOutPath = strOutDir + "monitor_hit_round" + - std::to_string(nRound) + "_img" + - std::to_string(i) + "_out.png"; - cv::imwrite(saveInPath, inImgs[i]); - cv::imwrite(saveOutPath, outImg); - snprintf(buf, sizeof(buf), - "[round %ld] hit! out image %zu non-zero pixels: %d -> save %s / %s", - nRound, i, nNonZero, saveInPath.c_str(), saveOutPath.c_str()); + "[round %ld] hit! out image %zu non-zero pixels: %d -> %s (saved=%d), exit monitor", + nRound, i, nNonZero, outList[i].c_str(), bSaved ? 1 : 0); logLine(buf); - nHit++; + bHit = true; + break; } } - snprintf(buf, sizeof(buf), "[round %ld] %d non-black out saved, sleep %d s ...", - nRound, nHit, nIntervalSec); - logLine(buf); - - // 3. 休眠(不超过剩余运行时间) - double remainSec = nMaxRunSec - elapsedSec; - int sleepSec = (int)(remainSec < nIntervalSec ? remainSec : nIntervalSec); - if (sleepSec > 0) + if (bHit) { - std::this_thread::sleep_for(std::chrono::seconds(sleepSec)); + break; } + + snprintf(buf, sizeof(buf), "[round %ld] all out black, sleep %d s ...", nRound, nIntervalSec); + logLine(buf); + + std::this_thread::sleep_for(std::chrono::seconds(nIntervalSec)); } logFile.close(); return 0; } +/// @brief AI 检测独立测试:遍历 /home/aidlux/BOE/FOG/AI_Detect/ 下所有 *_in.png, +/// 送入 AI_defect_NF 模型推理,将输出 *_out.png 保存到 /home/aidlux/BOE/FOG/AI_Detect_TEST/ +/// @return 0 表示成功 +/*int deal::TestAIDet() +{ + // 输入目录:优先使用命令行指定路径,否则使用默认路径 + std::string strSrcRoot = runConfig.filePath; + if (strSrcRoot.empty()) + { + strSrcRoot = "/home/aidlux/BOE/FOG/AI_Detect/"; + } + else if (strSrcRoot.back() != '/') + { + strSrcRoot += "/"; + } + + // 输出目录:输入目录去掉末尾 '/' 后追加 "_TEST/" + // 默认输入 /home/aidlux/BOE/FOG/AI_Detect/ -> 输出 /home/aidlux/BOE/FOG/AI_Detect_TEST/ + std::string strDstRoot = strSrcRoot; + while (strDstRoot.size() > 1 && strDstRoot.back() == '/') + { + strDstRoot.pop_back(); + } + strDstRoot += "_TEST/"; + + if(runConfig.bmonitor) + { + // 监控模式:每 5 分钟执行一次模型推理,记录设备资源消耗到 log; + // 当某次推理输出 out 图至少一个像素不为全黑时退出监控。 + return MonitorRun(); + } + + std::shared_ptr pAIFactory = AIFactory::GetInstance(); + std::shared_ptr pAI_Model = pAIFactory->AI_defect_NF; + if (!pAI_Model || !pAI_Model->m_bInitSuccess) + { + printf("TestAIDet: AI model (AI_defect_NF) not ready!\n"); + return 1; + } + + printf("TestAIDet: model input [c=%d w=%d h=%d] output [c=%d w=%d h=%d]\n", + pAI_Model->input_0.channel, pAI_Model->input_0.width, pAI_Model->input_0.height, + pAI_Model->output_0.channel, pAI_Model->output_0.width, pAI_Model->output_0.height); + + // 收集所有 *_in.png 文件 + std::vector inList; + try + { + if (!fs::exists(strSrcRoot)) + { + printf("TestAIDet: src dir not exist: %s\n", strSrcRoot.c_str()); + return 1; + } + for (const auto &entry : fs::recursive_directory_iterator(strSrcRoot)) + { + if (!entry.is_regular_file()) + { + continue; + } + std::string path = entry.path().string(); + std::string name = entry.path().filename().string(); + const std::string suffix = "_in.png"; + if (name.size() >= suffix.size() && + name.compare(name.size() - suffix.size(), suffix.size(), suffix) == 0) + { + inList.push_back(path); + } + } + } + catch (const std::exception &e) + { + printf("TestAIDet: traverse error %s\n", e.what()); + return 1; + } + + std::sort(inList.begin(), inList.end()); + printf("TestAIDet: total %zu in images\n", inList.size()); + + const int nModelInCh = pAI_Model->input_0.channel; + const int nModelInW = pAI_Model->input_0.width; + const int nModelInH = pAI_Model->input_0.height; + + int nOK = 0; + for (size_t i = 0; i < inList.size(); i++) + { + const std::string &inPath = inList[i]; + cv::Mat inImg = cv::imread(inPath, cv::IMREAD_UNCHANGED); + if (inImg.empty()) + { + printf("[%zu/%zu] read fail: %s\n", i + 1, inList.size(), inPath.c_str()); + continue; + } + + // 统一成模型要求的输入:单通道 8U、模型输入尺寸 + if (inImg.channels() != nModelInCh) + { + cv::Mat tmp; + if (inImg.channels() >= 3) + { + cv::cvtColor(inImg, tmp, cv::COLOR_BGR2GRAY); + } + else + { + tmp = inImg.clone(); + } + inImg = tmp; + } + if (inImg.type() != CV_8U) + { + inImg.convertTo(inImg, CV_8U); + } + if (inImg.cols != nModelInW || inImg.rows != nModelInH) + { + cv::resize(inImg, inImg, cv::Size(nModelInW, nModelInH)); + } + + cv::Mat outImg; + int re = pAI_Model->AIDet(inImg, outImg); + if (re != 0 || outImg.empty()) + { + printf("[%zu/%zu] infer fail(%d): %s\n", i + 1, inList.size(), re, inPath.c_str()); + continue; + } + + // 输出 resize 回模型输入尺寸(与 AI_Detect_QX 保存的 out 图尺寸保持一致) + if (outImg.cols != nModelInW || outImg.rows != nModelInH) + { + cv::resize(outImg, outImg, cv::Size(nModelInW, nModelInH), 0, 0, 0); + } + + // 相对路径,并保持子目录结构 + std::string rel = inPath.substr(strSrcRoot.size()); + std::string outRel = rel; + const std::string inSuffix = "_in.png"; + const std::string outSuffix = "_out.png"; + size_t pos = outRel.rfind(inSuffix); + if (pos != std::string::npos) + { + outRel.replace(pos, inSuffix.size(), outSuffix); + } + std::string outPath = strDstRoot + outRel; + + // 创建输出父目录 + fs::path outParent = fs::path(outPath).parent_path(); + if (!outParent.empty()) + { + std::error_code ec; + fs::create_directories(outParent, ec); + } + + if (cv::imwrite(outPath, outImg)) + { + nOK++; + printf("[%zu/%zu] ok: %s -> %s\n", i + 1, inList.size(), rel.c_str(), outRel.c_str()); + } + else + { + printf("[%zu/%zu] save fail: %s\n", i + 1, inList.size(), outPath.c_str()); + } + } + + printf("TestAIDet: complete, success %d / %zu\n", nOK, inList.size()); + return 0; +}*/ + /// @brief AI 检测独立测试:遍历 /home/aidlux/BOE/FOG/AI_Detect/ 下所有 *_in.png, /// 送入 AI_defect_NF 模型推理,将输出 *_out.png 保存到 /home/aidlux/BOE/FOG/AI_Detect_TEST/ /// @return 0 表示成功