From 22dd824f944488710473d34a627c06e9fe73100f Mon Sep 17 00:00:00 2001 From: liusiyang Date: Mon, 13 Jul 2026 10:04:35 +0800 Subject: [PATCH] =?UTF-8?q?feat=20=E6=B7=BB=E5=8A=A0=E4=BC=A0=E7=BB=9F?= =?UTF-8?q?=E6=A3=80=E6=B5=8B=E7=9B=B8=E5=85=B3=E5=8F=82=E6=95=B0=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AlgorithmModule/src/ImgCheckAnalysisy.cpp | 43 +++++++----- ConfigModule/include/CheckConfigDefine.h | 65 ++++++++++++++++-- ConfigModule/src/JsonConfig.cpp | 57 +++++++++++++++- TcsCheckModule/include/TcsCheck.h | 9 +-- TcsCheckModule/src/TcsCheck.cpp | 42 +++++++----- example/deal.cpp | 80 +++++++++++++++++++++-- example/deal.h | 2 + 7 files changed, 247 insertions(+), 51 deletions(-) diff --git a/AlgorithmModule/src/ImgCheckAnalysisy.cpp b/AlgorithmModule/src/ImgCheckAnalysisy.cpp index 14df0b1..faa6221 100644 --- a/AlgorithmModule/src/ImgCheckAnalysisy.cpp +++ b/AlgorithmModule/src/ImgCheckAnalysisy.cpp @@ -513,7 +513,8 @@ int ImgCheckAnalysisy::Adapt_Config(Mat img, Rect cur_roi, bool b_update){ if(!b_update){ return 1; } - if(cur_roi.width <= 0 || cur_roi.height <= 0){ + int get_edge_roi = GetEdgeRoi(img, cur_roi, 20, 20); + if(get_edge_roi != 0){ return 2; } m_pdetlog->AddCheckstr(PrintLevel_0, "Adapt_Config", "-------------------start--------------"); @@ -639,6 +640,9 @@ int ImgCheckAnalysisy::CheckRun() m_CheckResult_shareP->basicResult.strChannel = m_CheckResult_shareP->in_shareImage->strChannel; m_strCurDetChannel = m_CheckResult_shareP->basicResult.strChannel; + /*自适应更新参数*/ + Adapt_Config(m_CheckResult_shareP->in_shareImage->img, m_CutRoi, m_pbaseCheckFunction->markLine.badapt_region); + // 2、参数检查 int rec = ConfigCheck(DetImgInfo_shareP->img); if (rec != CHECK_OK) @@ -665,9 +669,6 @@ int ImgCheckAnalysisy::CheckRun() m_pImageAllResult->pDetResult->CutRoi = m_CutRoi; m_pImageAllResult->pDetResult->Param_CropRoi = m_Crop_Roi_paramImg; - /*自适应更新参数 — 基于AI边缘定位的精准ROI*/ - Adapt_Config(m_CheckResult_shareP->in_shareImage->img, m_CutRoi, m_pbaseCheckFunction->markLine.badapt_region); - // 生成 检测的图片 cv::Mat image; image = m_CheckResult_shareP->in_shareImage->img; @@ -1308,7 +1309,7 @@ int ImgCheckAnalysisy::AIMaskDet() memset(m_ImgBlobHFlagData, 0, sizeof(unsigned char) * m_pImageAllResult->detImg.rows); // 传统检测路径:等待异步任务完成后,计算HFlag - if(m_pBasicConfig->bTraditionalDetect) + if(m_pbaseCheckFunction->traditionDet.bOpen) { m_AItask->waitComplate(); int rec = m_AItask->nresult; @@ -1630,28 +1631,34 @@ int ImgCheckAnalysisy::Traditional_Detect_Thread(const cv::Mat &img, cv::Mat &Re std::string strBaseLog = "Traditional_Detect"; m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "Traditional_Detect Start"); + Base_Function_TraditionDet traditionParam = m_pbaseCheckFunction->traditionDet; // 首次调用时初始化传统检测参数(从 m_AnalysisyConfig 映射) static bool bTcsInited = false; if (!bTcsInited) { CHECK_PARAM cp; cp.nAreaLowFilter = 80; - cp.nBlockSize = 100; - cp.nDiscardTop = 170; - cp.nDiscardBottom = 170; - cp.nDiscardLeft = 180; - cp.nDiscardRight = 460; - cp.fZoomRatio = 0.25f; + cp.nBlockSize = traditionParam.nBlockSize; + cp.nDiscardTop = 0; + cp.nDiscardBottom = 0; + cp.nDiscardLeft = 0; + cp.nDiscardRight = 0; + cp.fZoomRatio = traditionParam.fZoomRatio; cp.nFilterLow = 15; cp.nFilterHigh = 15; - cp.nAreaFilter = 10; - cp.nCountFilter = 50; + cp.nAreaFilter = traditionParam.nAreaFilter; + cp.nCountFilter = traditionParam.nCountFilter; m_tcsCheck.SetChecConfig(&cp); bTcsInited = true; } - // 调用传统检测:输出残点二值图(已内部完成 crop+resize 反向映射,与 img 同尺寸) - int ret = m_tcsCheck.TraditionalDetect(img, ResultImg); + cv::Rect detroi = traditionParam.detArea_ROI; + if(!traditionParam.bdetArea) + { + detroi = cv::Rect(0, 0, img.cols, img.rows); + } + // 调用传统检测:输出残点二值图 + int ret = m_tcsCheck.TraditionalDetect(img, detroi, ResultImg); if (ret != 0) { m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "Traditional_Detect FAILED (no product)"); @@ -1832,7 +1839,7 @@ int ImgCheckAnalysisy::Traditional_QX_Class_Thread() { m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Traditional_Class", " Start"); - // 1. 调用传统分类:基于上次 TraditionalDetect 缓存的模糊图 + 当前 mask + // 1. 调用传统分类 if (m_pImageAllResult == nullptr || m_pImageAllResult->AIMaskImg.empty()) { m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Traditional_Class", " No mask image"); @@ -1960,7 +1967,7 @@ void ImgCheckAnalysisy::TaskFun_AIDet(std::shared_ptr task) t1 = CheckUtil::getcurTime(); int rec; - if(m_pBasicConfig->bTraditionalDetect) + if(m_pbaseCheckFunction->traditionDet.bOpen) { rec = Traditional_Detect_Thread(m_pImageAllResult->detImg, m_pImageAllResult->AIMaskImg); } @@ -1984,7 +1991,7 @@ void ImgCheckAnalysisy::TaskFun_QxClass(std::shared_ptr task) t1 = CheckUtil::getcurTime(); int rec; - if(m_pBasicConfig->bTraditionalDetect) + if(m_pbaseCheckFunction->traditionDet.bOpen) { rec = Traditional_QX_Class_Thread(); } diff --git a/ConfigModule/include/CheckConfigDefine.h b/ConfigModule/include/CheckConfigDefine.h index f14b459..204adcd 100644 --- a/ConfigModule/include/CheckConfigDefine.h +++ b/ConfigModule/include/CheckConfigDefine.h @@ -407,7 +407,6 @@ struct BasicConfig float Product_Size_Height_mm; // 产品尺寸 高度 mm float fImage_Scale_x; // 成像精度 float fImage_Scale_y; // 成像精度 - bool bTraditionalDetect; // 使用传统算法检测 std::string strCamName; // float density_R_mm; // 密度计算半径 像素 @@ -428,7 +427,6 @@ struct BasicConfig Product_Size_Height_mm = 1000; fImage_Scale_x = 0.03; fImage_Scale_y = 0.03; - bTraditionalDetect = false; density_R_mm = 5; strCamName = ""; strCamearName = EMPTY_CONFIG_NAME; @@ -452,7 +450,6 @@ struct BasicConfig this->Product_Size_Height_mm = tem.Product_Size_Height_mm; this->fImage_Scale_x = tem.fImage_Scale_x; this->fImage_Scale_y = tem.fImage_Scale_y; - this->bTraditionalDetect = tem.bTraditionalDetect; this->density_R_mm = tem.density_R_mm; this->strCamearName = tem.strCamearName; } @@ -461,7 +458,7 @@ struct BasicConfig printf("============================↓↓↓↓↓↓%s↓↓ %s ↓↓↓↓↓=========================\n", str.c_str(), strCamearName.c_str()); printf("bCal_ImageScale %d Product_Size_Width =%f Product_Size_Height =%f \n", bCal_ImageScale, Product_Size_Width_mm, Product_Size_Height_mm); printf("fImage_Scale_x =%f fImage_Scale_y=%f \n", fImage_Scale_x, fImage_Scale_y); - printf("bTraditionalDetect %d density_R_mm=%f \n", bTraditionalDetect, density_R_mm); + printf("density_R_mm=%f \n", density_R_mm); // printf("height_min =%d height_max=%d \n", height_min, height_max); printf("bDrawShieldRoi %d bShield_ZF %d DrawPreRoi %d fUP_IOU %f density_R_mm %f\n", bDrawShieldRoi, bShield_ZF, bDrawPreRoi, fUP_IOU, density_R_mm); printf("============================↑↑↑↑↑↑%s↑↑↑↑↑↑=========================\n", str.c_str()); @@ -1782,6 +1779,61 @@ struct Base_Function_SaveImg } }; +//传统检测 +struct Base_Function_TraditionDet +{ + bool bOpen; // 是否开启 + float nAreaLowFilter; // 定位阈值 + int nBlockSize; // 分块大小 + float fZoomRatio; // 缩放比例 + float nAreaFilter; // 面积过滤 + int nCountFilter; // 数量过滤 + cv::Rect detArea_ROI; + std::vector detArea; + bool bdetArea; // 是否使用区域 + + Base_Function_TraditionDet() + { + Init(); + } + void Init() + { + bOpen = false; + nAreaLowFilter = 0; + nBlockSize = 0; + fZoomRatio = 0; + nAreaFilter = 0; + nCountFilter = 0; + bdetArea = false; + detArea_ROI = cv::Rect(0, 0, 0, 0); + detArea.clear(); + } + void copy(Base_Function_TraditionDet tem) + { + this->bOpen = tem.bOpen; + this->nAreaLowFilter = tem.nAreaLowFilter; + this->nBlockSize = tem.nBlockSize; + this->fZoomRatio = tem.fZoomRatio; + this->nAreaFilter = tem.nAreaFilter; + this->nCountFilter = tem.nCountFilter; + this->bdetArea = tem.bdetArea; + this->detArea_ROI = tem.detArea_ROI; + this->detArea.assign(tem.detArea.begin(), tem.detArea.end()); + } + void print(std::string str) + { + printf("%s>>bOpen %d nAreaLowFilter %f nBlockSize %d fZoomRatio %f nAreaFilter %f nCountFilter %d \n", str.c_str(), + bOpen, nAreaLowFilter, nBlockSize, fZoomRatio, nAreaFilter, nCountFilter); + } + std::string GetInfo(std::string str) + { + char buffer[256]; + sprintf(buffer, "%s>>bOpen %d nAreaLowFilter %f nBlockSize %d fZoomRatio %f nAreaFilter %f nCountFilter %d \n", str.c_str(), + bOpen, nAreaLowFilter, nBlockSize, fZoomRatio, nAreaFilter, nCountFilter); + std::string str123 = buffer; + return str123; + } +}; // 大缺陷 NG struct Big_NG { @@ -1970,6 +2022,7 @@ struct BaseCheckFunction Base_Function_MarkLine markLine; Base_Function_Edge_Det edgeDet; Base_Function_SaveImg saveImg; + Base_Function_TraditionDet traditionDet; Base_Function_BigNG bigNG; BaseCheckFunction() { @@ -1980,6 +2033,7 @@ struct BaseCheckFunction markLine.Init(); edgeDet.Init(); saveImg.Init(); + traditionDet.Init(); bigNG.Init(); } void copy(BaseCheckFunction tem) @@ -1987,6 +2041,7 @@ struct BaseCheckFunction this->markLine.copy(tem.markLine); this->edgeDet.copy(tem.edgeDet); this->saveImg.copy(tem.saveImg); + this->traditionDet.copy(tem.traditionDet); this->bigNG.copy(tem.bigNG); } void print(std::string str) @@ -1995,6 +2050,7 @@ struct BaseCheckFunction markLine.print("markLine"); edgeDet.print("edgeDet"); saveImg.print("saveImg"); + traditionDet.print("traditionDet"); bigNG.print("bigNG"); } std::string GetInfo(std::string str) @@ -2003,6 +2059,7 @@ struct BaseCheckFunction str123 += markLine.GetInfo("markLine"); str123 += edgeDet.GetInfo("edgeDet"); str123 += saveImg.GetInfo("saveImg"); + str123 += traditionDet.GetInfo("traditionDet"); str123 += bigNG.GetInfo("bigNG"); // str123 += "\n"; return str123; diff --git a/ConfigModule/src/JsonConfig.cpp b/ConfigModule/src/JsonConfig.cpp index 8c7ccd3..171cda6 100644 --- a/ConfigModule/src/JsonConfig.cpp +++ b/ConfigModule/src/JsonConfig.cpp @@ -57,7 +57,6 @@ void CommonParamToCheckConfigJson::toObjectFromValue(Json::Value root) _config.baseConfig.Product_Size_Height_mm = value["Product_Size_H"].asFloat(); _config.baseConfig.fImage_Scale_x = value["Image_Scale_X"].asFloat(); _config.baseConfig.fImage_Scale_y = value["Image_Scale_Y"].asFloat(); - _config.baseConfig.bTraditionalDetect = value["bTraditionalDetect"].asFloat(); if (value["Density_R"]) { _config.baseConfig.density_R_mm = value["Density_R"].asFloat(); @@ -1309,6 +1308,62 @@ int BaseFuntonConfigJson::GetFunction(Json::Value value) // _config.edgeDet.print("edgeDet"); // getchar(); } + if ("Tradition_Detect" == strCode) + { + auto value_f = value; + // std::cout << value_f << std::endl; + // getchar(); + _config.traditionDet.bOpen = value_f["isOpen"].asBool(); + if (_config.traditionDet.bOpen) + { + + if (value_f["form"]["Tradition_Param"]["nBlockSize"]) + { + _config.traditionDet.nBlockSize = value_f["form"]["Tradition_Param"]["nBlockSize"].asInt(); + } + if (value_f["form"]["Tradition_Param"]["fZoomRatio"]) + { + _config.traditionDet.fZoomRatio = value_f["form"]["Tradition_Param"]["fZoomRatio"].asFloat(); + } + if (value_f["form"]["Tradition_Param"]["nAreaFilter"]) + { + _config.traditionDet.nAreaFilter = value_f["form"]["Tradition_Param"]["nAreaFilter"].asFloat(); + } + if (value_f["form"]["Tradition_Param"]["nCountFilter"]) + { + _config.traditionDet.nCountFilter = value_f["form"]["Tradition_Param"]["nCountFilter"].asInt(); + } + // 2、读取区域点 + { + auto value_region = value_f["form"]["Tradition_Param"]["detArea"]; + if (value_region.isArray()) + { + for (int idx = 0; idx < value_region.size(); idx++) + { + cv::Point p; + p.x = value_region[idx][0].asInt(); + p.y = value_region[idx][1].asInt(); + + _config.traditionDet.detArea.emplace_back(p); + } + if (_config.traditionDet.detArea.size() > 0) + { + _config.traditionDet.detArea_ROI = boundingRect(_config.traditionDet.detArea); + } + } + } + if (value_f["form"]["Tradition_Param"]["bdetArea"]) + { + _config.traditionDet.bdetArea = value_f["form"]["Tradition_Param"]["bdetArea"].asFloat(); + } + } + else + { + _config.traditionDet.Init(); + } + // _config.traditionDet.print("traditionDet"); + // getchar(); + } if ("Big_NG" == strCode) { auto value_f = value; diff --git a/TcsCheckModule/include/TcsCheck.h b/TcsCheckModule/include/TcsCheck.h index df33bb4..93fd1af 100644 --- a/TcsCheckModule/include/TcsCheck.h +++ b/TcsCheckModule/include/TcsCheck.h @@ -96,12 +96,9 @@ public: void ProcessImages(bool bDrawResult); // ========== 独立检测/分类接口 ========== - // 传统检测:对单张图做完整预处理+自适应二值化,输出残点二值图 - // 返回: 0=成功, -1=无产品/输入为空 - int TraditionalDetect(const cv::Mat& img, cv::Mat& blobImg); - // 传统分类:对残点二值图做连通域分析+缺陷分类,结果写入 m_vecDefectInfo - // 前提: 已调用 TraditionalDetect(内部 m_matBlur 已就绪) - // 返回: 分类到的缺陷数量,<0 表示异常 + // 传统检测,输出残点二值图 0=成功, -1=无产品/输入为空 + int TraditionalDetect(const cv::Mat& img, cv::Rect detRoi, cv::Mat& blobImg); + // 传统分类,结果写入 m_vecDefectInfo int TraditionalClassify(const cv::Mat& blobImg); private: diff --git a/TcsCheckModule/src/TcsCheck.cpp b/TcsCheckModule/src/TcsCheck.cpp index 434d447..0947c6c 100644 --- a/TcsCheckModule/src/TcsCheck.cpp +++ b/TcsCheckModule/src/TcsCheck.cpp @@ -15,9 +15,10 @@ protected: }; CTcsCheck::CTcsCheck() + : m_nInitStart(0) + , m_bSystemExit(0) + , m_nInitEnd(0) { - memset(&m_nInitStart, 0, offsetof(CTcsCheck, m_nInitEnd) - offsetof(CTcsCheck, m_nInitStart) + sizeof(m_nInitEnd)); - m_cpCfg.nAreaLowFilter = 80; m_cpCfg.nBlockSize = 100; m_cpCfg.nDiscardTop = 170; @@ -310,32 +311,37 @@ cv::Mat CTcsCheck::DrawBlobInfoImage(const cv::Mat& imgCrop, const cv::Mat& imgB return cropColor; } // ============================================================ -// TraditionalDetect — 独立检测接口 +// 独立检测接口 // 对单张图做:阈值定位→裁剪→缩放→高斯模糊→自适应二值化 // 输出残点二值图 blobImg,内部缓存 m_matBlur 供后续分类 // 返回: 0=成功, -1=无产品或输入异常 // ============================================================ -int CTcsCheck::TraditionalDetect(const cv::Mat& img, cv::Mat& blobImg) +int CTcsCheck::TraditionalDetect(const cv::Mat& img, cv::Rect detRoi, cv::Mat& blobImg) { - if (img.empty()) return -1; + // if (img.empty()) return -1; m_matLoad = img; - m_sizeImage = img.size(); + cv::Rect rtCrop = detRoi; + if(detRoi.size() == img.size()) + { + m_sizeImage = img.size(); - // 1. 全局阈值 → 产品区域定位 - cv::Mat matBinary; - cv::threshold(m_matLoad, matBinary, m_cpCfg.nAreaLowFilter, 255, cv::THRESH_BINARY); + // 1. 全局阈值 → 产品区域定位 + cv::Mat matBinary; + cv::threshold(m_matLoad, matBinary, m_cpCfg.nAreaLowFilter, 255, cv::THRESH_BINARY); - // 2. 获取最大连通域外接矩形 - cv::Rect rtValid = GetBoundingRect(matBinary); - if (rtValid == cv::Rect(0, 0, 0, 0)) - { - blobImg = cv::Mat(); - return -1; - } + // 2. 获取最大连通域外接矩形 + cv::Rect rtValid = GetBoundingRect(matBinary); + if (rtValid == cv::Rect(0, 0, 0, 0)) + { + blobImg = cv::Mat(); + return -1; + } - // 3. 裁剪边缘 - cv::Rect rtCrop = GetCropArea(rtValid); + // 3. 裁剪边缘 + rtCrop = GetCropArea(rtValid); + } + cv::Mat matCrop = m_matLoad(rtCrop).clone(); // 4. 缩放 diff --git a/example/deal.cpp b/example/deal.cpp index b8f467f..7a0e407 100644 --- a/example/deal.cpp +++ b/example/deal.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "CheckUtil.hpp" std::string ExtractFileNameWithoutExtension(const std::string &strImgPath) { @@ -1083,10 +1084,6 @@ bool deal::ReadSystemConfig(const std::string &strPath) } m_system_param.Use_CPU_StartIdx = root["Use_CPU_StartIdx"].asInt(); // path - m_system_param.Analysis_Config_path = root["Analysis_Config_path"].asString(); - m_system_param.Analysis_Config_path_Cam2 = root["Analysis_Config_path_Cam2"].asString(); - m_system_param.Analysis_Config_path_Cam3 = root["Analysis_Config_path_Cam3"].asString(); - m_system_param.Analysis_Config_path_Cam4 = root["Analysis_Config_path_Cam4"].asString(); m_system_param.config_Root_Path = root["Config_Root_Path"].asString(); m_system_param.Check_Config_path = root["Check_Config_path"].asString(); @@ -1095,8 +1092,83 @@ bool deal::ReadSystemConfig(const std::string &strPath) m_system_param.preCHeck_defect = root["preCHeck_defect"].asInt(); m_nCurUseCPUIDX = m_system_param.Use_CPU_StartIdx; + // 从 Config_Root_Path 目录下自动扫描匹配 param_*.json 文件,映射到各相机 + ScanConfigPaths(); + return m_system_param.valid(); } +int deal::ScanConfigPaths() +{ + std::string configRoot = m_system_param.config_Root_Path; + if (configRoot.empty()) + { + printf("ScanConfigPaths: Config_Root_Path is empty\n"); + return -1; + } + + // 确保路径以 / 结尾 + if (configRoot.back() != '/') + { + configRoot += '/'; + } + + // 相机名称与对应存储指针的映射: Cam1=BCA, Cam2=BTA, Cam3=DCA, Cam4=DTA + struct CamPathMapping + { + std::string camName; + std::string *targetPath; + }; + CamPathMapping mappings[] = { + {"BCA", &m_system_param.Analysis_Config_path}, + {"BTA", &m_system_param.Analysis_Config_path_Cam2}, + {"DCA", &m_system_param.Analysis_Config_path_Cam3}, + {"DTA", &m_system_param.Analysis_Config_path_Cam4}, + }; + + // 先清空所有路径 + for (auto &m : mappings) + { + *m.targetPath = ""; + } + + if (!std::filesystem::exists(configRoot)) + { + printf("ScanConfigPaths: directory does not exist: %s\n", configRoot.c_str()); + return -1; + } + + int foundCount = 0; + for (const auto &entry : std::filesystem::directory_iterator(configRoot)) + { + if (!entry.is_regular_file()) + continue; + + std::string filename = entry.path().filename().string(); + // 匹配 param_XXX.json 格式 + if (filename.size() < 11) // "param_X.json" 最少 11 字符 + continue; + if (filename.substr(0, 6) != "param_" || filename.substr(filename.size() - 5) != ".json") + continue; + + // 提取相机名称: param_BCA.json → BCA + std::string camName = filename.substr(6, filename.size() - 11); + + for (auto &m : mappings) + { + if (camName == m.camName) + { + *m.targetPath = entry.path().string(); + printf("ScanConfigPaths: auto-discovered %s config → %s\n", + m.camName.c_str(), m.targetPath->c_str()); + foundCount++; + break; + } + } + } + + printf("ScanConfigPaths: found %d param config file(s) in %s\n", foundCount, configRoot.c_str()); + return foundCount > 0 ? 0 : -1; +} int deal::GetJcImageInfo(std::string strpath, std::vector &jcImageInfoList) { LoadOfflineCheckImg(strpath); diff --git a/example/deal.h b/example/deal.h index b15e8a8..8b473e6 100644 --- a/example/deal.h +++ b/example/deal.h @@ -578,6 +578,8 @@ private: void GetDealResultToQueu(); // 加载系统配置文件 bool ReadSystemConfig(const std::string &strPath); + // 从 Config_Root_Path 自动扫描匹配 param_*.json 文件 + int ScanConfigPaths(); int GetJcImageInfo(std::string strpath, std::vector &jcImageInfoList); int ReadTestImgaData();