diff --git a/AlgorithmModule/include/ImageDetConfig.h b/AlgorithmModule/include/ImageDetConfig.h index 24df4f1..1f9a5df 100644 --- a/AlgorithmModule/include/ImageDetConfig.h +++ b/AlgorithmModule/include/ImageDetConfig.h @@ -98,6 +98,18 @@ struct One_Image_CheckResult_ cv::Rect CutRoi; // 当前检测图片 对应的裁切区域 cv::Rect Param_CropRoi; // 参数模版 对应的裁切区域 std::shared_ptr> pQx_ErrorList; // 缺陷错误信息 + + bool bBlurNG; // 图像模糊判定: true = 检测图像模糊(判 NG) + float fBlurScore; // 图像清晰度(拉普拉斯方差), 越大越清晰 + + One_Image_CheckResult_() + { + CutRoi = cv::Rect(0, 0, 0, 0); + Param_CropRoi = cv::Rect(0, 0, 0, 0); + pQx_ErrorList = std::make_shared>(); + bBlurNG = false; + fBlurScore = 0.0f; + } void print(std::string str = "") { std::cout << str << ": " diff --git a/AlgorithmModule/include/ImgCheckAnalysisy.hpp b/AlgorithmModule/include/ImgCheckAnalysisy.hpp index f6e6e0d..b42e8f7 100644 --- a/AlgorithmModule/include/ImgCheckAnalysisy.hpp +++ b/AlgorithmModule/include/ImgCheckAnalysisy.hpp @@ -125,6 +125,9 @@ private: // 图片预处理 int ImgPreDet(); + // 图像模糊度(清晰度)检测: 在 AI 检测之前执行, 返回 1 表示图像模糊(判 NG), 0 正常 + int BlurCheck(const cv::Mat &img); + // 设置新的检测参数 int SetNewConfig(); diff --git a/AlgorithmModule/src/ALLImgCheckAnalysisy.cpp b/AlgorithmModule/src/ALLImgCheckAnalysisy.cpp index c91c9d6..8b0dfe4 100644 --- a/AlgorithmModule/src/ALLImgCheckAnalysisy.cpp +++ b/AlgorithmModule/src/ALLImgCheckAnalysisy.cpp @@ -623,7 +623,8 @@ int ALLImgCheckAnalysisy::SetProductResult(std::shared_ptr product) std::shared_ptr pCheckResult = pcam->ImageALLDetResultList.at(i)->result; - if (pCheckResult->qxImageResult.size() > 0) + // 图像 NG(包括图像模糊在 AI 检测之前直接判定的 NG) 也要计入产品级结果 + if (pCheckResult->qxImageResult.size() > 0 || pCheckResult->nresult > ERROR_TYPE_OK) { bNG = true; } diff --git a/AlgorithmModule/src/CheckResultJson.cpp b/AlgorithmModule/src/CheckResultJson.cpp index fd34292..55f029f 100644 --- a/AlgorithmModule/src/CheckResultJson.cpp +++ b/AlgorithmModule/src/CheckResultJson.cpp @@ -18,6 +18,11 @@ Json::Value CheckResultJson::toJsonValue() root["Param_CropRoi"]["width"] = m_pOneImgDetResult->Param_CropRoi.width; root["Param_CropRoi"]["height"] = m_pOneImgDetResult->Param_CropRoi.height; } + { + // 图像模糊度(清晰度)检测结果: 在 AI 检测之前计算 + root["blurScore"] = m_pOneImgDetResult->fBlurScore; + root["isBlur"] = m_pOneImgDetResult->bBlurNG; + } for (int i = 0; i < m_pOneImgDetResult->pQx_ErrorList->size(); i++) { QX_ERROR_INFO_ *p = &m_pOneImgDetResult->pQx_ErrorList->at(i); @@ -86,6 +91,17 @@ void CheckResultJson::toObjectFromValue(Json::Value root) m_pOneImgDetResult->Param_CropRoi.height = root["Param_CropRoi"]["height"].asInt(); } } + { + // 图像模糊度(清晰度)检测结果; 旧版本 json 可能没有该字段 + if (root.isMember("blurScore")) + { + m_pOneImgDetResult->fBlurScore = root["blurScore"].asFloat(); + } + if (root.isMember("isBlur")) + { + m_pOneImgDetResult->bBlurNG = root["isBlur"].asBool(); + } + } { if (root.isMember("Qx")) { diff --git a/AlgorithmModule/src/ImageResultJudge.cpp b/AlgorithmModule/src/ImageResultJudge.cpp index 5820116..30e3371 100644 --- a/AlgorithmModule/src/ImageResultJudge.cpp +++ b/AlgorithmModule/src/ImageResultJudge.cpp @@ -147,6 +147,14 @@ int ImageResultJudge::ResultJudge(std::shared_ptr pImageResult) std::shared_ptr m_CheckResult_shareP = pImageResult->result; // 返回结果 int qxNum = pDetResult->pQx_ErrorList->size(); + // 图像模糊: 检测阶段已在 AI 检测之前判定, 这里保证结果保持 NG(复测 ReJson 读取 json 后同样生效) + if (pDetResult->bBlurNG) + { + m_CheckResult_shareP->nresult = 1; + pImageLog->AddCheckstr(PrintLevel_0, "Blur Check", + "image blurred -> NG (variance %.2f)", pDetResult->fBlurScore); + } + // map> allQxZoning; map> allQxZoning; diff --git a/AlgorithmModule/src/ImgCheckAnalysisy.cpp b/AlgorithmModule/src/ImgCheckAnalysisy.cpp index 80414d1..d7f5d58 100644 --- a/AlgorithmModule/src/ImgCheckAnalysisy.cpp +++ b/AlgorithmModule/src/ImgCheckAnalysisy.cpp @@ -23,6 +23,11 @@ static const float PIN_SHIFT_BIN_SIZE_PX = 4.0f; // 位移投票的网格 static const float PIN_SHIFT_MAX_GAP_RATIO = 0.8f; // 整体位移量级上限 = 相邻pin最小间距 * 该比例 // ====================================================================================== +// ===================== 图像模糊度(清晰度)检测参数 ===================== +// 计算清晰度前把检测图最长边缩放到该尺寸(只缩小不放大), 使阈值与图像分辨率无关 +static const int BLUR_CHECK_MAX_SIZE = 1000; +// ====================================================================== + // 计算点相对旋转矩形“长边轴线”的带符号法向距离(检测图坐标系): // 取矩形长边方向作为 Pin 排方向, 返回点沿短边(法线)方向相对矩形中心的投影; // 正负号天然把分布在矩形两侧的 Pin 分成两组(上下两排分别估计整体偏移)。 @@ -804,6 +809,98 @@ int ImgCheckAnalysisy::TransformTplPinContoursToCur() return 0; } +// 图像模糊度(清晰度)检测: 必须在 AI 检测之前执行。 +// 1) 检测图 detImg 转灰度(本身就是单通道, 这里做兼容); +// 2) 最长边缩放到 BLUR_CHECK_MAX_SIZE(只缩小不放大), 保证阈值与图像分辨率无关; +// 3) 用拉普拉斯算子(CV_64F)响应的方差作为清晰度指标: 方差越小越模糊; +// 4) 方差 < 阈值(基础参数 blurDet.fThreshold, 对应界面参数 blur_threshold) -> 判为模糊图片, +// 参数 blurDet.bNG(界面 bNG)打开时直接判 NG。 +// 返回: 1 图像模糊且需要判 NG; 0 正常 / 功能未开启 / 图像无效 +int ImgCheckAnalysisy::BlurCheck(const cv::Mat &img) +{ + if (m_pbaseCheckFunction == NULL || !m_pbaseCheckFunction->blurDet.bOpen || + m_pbaseCheckFunction->blurDet.fThreshold <= 0.0f) + { + return 0; + } + + // 每帧重置模糊判定结果 + if (m_pDetResult) + { + m_pDetResult->bBlurNG = false; + m_pDetResult->fBlurScore = 0.0f; + } + + if (img.empty() || img.cols < 5 || img.rows < 5) + { + m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Blur Check", "input img invalid, skip"); + return 0; + } + + // 1、转灰度(detImg 为单通道, 其他通道情况做兼容) + cv::Mat grayImg; + if (1 == img.channels()) + { + grayImg = img; + } + else if (3 == img.channels()) + { + cv::cvtColor(img, grayImg, cv::COLOR_BGR2GRAY); + } + else if (4 == img.channels()) + { + cv::cvtColor(img, grayImg, cv::COLOR_BGRA2GRAY); + } + else + { + m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Blur Check", "unsupported channels %d, skip", img.channels()); + return 0; + } + + // 2、按最长边缩放(只缩小不放大) + cv::Mat checkImg = grayImg; + const int nMaxSide = std::max(grayImg.cols, grayImg.rows); + if (nMaxSide > BLUR_CHECK_MAX_SIZE) + { + const double fScale = static_cast(BLUR_CHECK_MAX_SIZE) / static_cast(nMaxSide); + cv::resize(grayImg, checkImg, cv::Size(), fScale, fScale, cv::INTER_AREA); + } + + // 3、拉普拉斯方差(越大越清晰) + cv::Mat lapImg; + cv::Laplacian(checkImg, lapImg, CV_64F, 3); + cv::Scalar meanVal; + cv::Scalar stdVal; + cv::meanStdDev(lapImg, meanVal, stdVal); + const float fScore = static_cast(stdVal[0] * stdVal[0]); + const float fThreshold = m_pbaseCheckFunction->blurDet.fThreshold; + const bool bNGOpen = m_pbaseCheckFunction->blurDet.bNG; + const bool bBlur = (fScore < fThreshold); + + if (m_pDetResult) + { + m_pDetResult->fBlurScore = fScore; + m_pDetResult->bBlurNG = (bBlur && bNGOpen); + } + + m_pdetlog->AddCheckstr(PrintLevel_0, "Blur Check", + "variance %.2f %s threshold %.2f -> %s (bNG %d, img [%d %d] -> check [%d %d])", + fScore, bBlur ? "<" : ">=", fThreshold, bBlur ? "BLUR" : "OK", + static_cast(bNGOpen), + grayImg.cols, grayImg.rows, checkImg.cols, checkImg.rows); + + if (bBlur && bNGOpen) + { + m_pdetlog->AddCheckstr(PrintLevel_0, "Blur Check", "image blurred -> judge NG"); + return 1; + } + if (bBlur) + { + m_pdetlog->AddCheckstr(PrintLevel_0, "Blur Check", "image blurred, but bNG is off -> not NG"); + } + return 0; +} + int ImgCheckAnalysisy::CheckRun() { @@ -1064,6 +1161,15 @@ int ImgCheckAnalysisy::CheckRun() { m_pImageAllResult->detImg = image(m_outer_roi).clone(); } + + /*图像模糊度(清晰度)检测: 必须在 AI 检测之前完成, 模糊图片直接判 NG*/ + long time_blur_s = CheckUtil::getcurTime(); + const bool bBlurNG = (BlurCheck(m_pImageAllResult->detImg) == 1); + long time_blur_e = CheckUtil::getcurTime(); + m_pdetlog->AddCheckstr(PrintLevel_0, "2.2、pre detect", + "-------------------------Blur Check------------%s---time %ld----\n", + bBlurNG ? "NG" : "OK", time_blur_e - time_blur_s); + /*多线程开启 AI 推理检测*/ long time_AI_s = CheckUtil::getcurTime(); m_AItask = std::make_shared(); @@ -1086,6 +1192,12 @@ int ImgCheckAnalysisy::CheckRun() m_CheckResult_shareP->nresult = 0; + // 图像模糊: 在 AI 检测之前已判定, 直接判 NG(nresult=1 与区域参数判断出的 NG 语义一致) + if (bBlurNG) + { + m_CheckResult_shareP->nresult = 1; + } + /*把临时可以绘制的结果都绘制出来。*/ DrawResult_Step_1(); long time_AI_e; diff --git a/ConfigModule/include/CheckConfigDefine.h b/ConfigModule/include/CheckConfigDefine.h index 94dbacf..d5f6f2c 100644 --- a/ConfigModule/include/CheckConfigDefine.h +++ b/ConfigModule/include/CheckConfigDefine.h @@ -1187,6 +1187,46 @@ struct Base_Function_Merge_QueXi }; +// 基础检测功能 图像模糊度(清晰度)检测 +// 执行时机: AI 残点检测之前, 对送检图(检测图 detImg)计算模糊度, 模糊图片可直接判 NG +// 界面参数: itemCode = "Blur_Detect", form.Blur_Config = { bNG, blur_threshold } +struct Base_Function_Blur +{ + + bool bOpen; // 是否开启 + bool bNG; // 模糊图片是否直接判 NG + float fThreshold; // 模糊度阈值(拉普拉斯方差下限): 方差低于该值判为模糊 + + Base_Function_Blur() + { + Init(); + } + void Init() + { + bOpen = true; // 默认开启(参数里没有该配置项时也做模糊判定) + bNG = true; // 默认模糊即 NG + fThreshold = 800.0f; // 默认阈值(实测: 清晰图 ~8100, sigma=3 模糊 ~880, sigma=5 模糊 ~250) + } + + void copy(Base_Function_Blur tem) + { + this->bOpen = tem.bOpen; + this->bNG = tem.bNG; + this->fThreshold = tem.fThreshold; + } + void print(std::string str) + { + printf("%s>>bOpen %d bNG %d fThreshold %f \n", str.c_str(), bOpen, bNG, fThreshold); + } + std::string GetInfo(std::string str) + { + char buffer[256]; + sprintf(buffer, "%s>>bOpen %d bNG %d fThreshold %f \n", str.c_str(), bOpen, bNG, fThreshold); + std::string str123 = buffer; + return str123; + } +}; + // 基础检测功能 大缺陷 NG struct Base_Function_BigNG { @@ -1295,6 +1335,7 @@ struct BaseCheckFunction Base_Function_Pin_Det pinDet; Base_Function_SaveImg saveImg; Base_Function_Merge_QueXi Merge_QueXi; + Base_Function_Blur blurDet; Base_Function_BigNG bigNG; BaseCheckFunction() { @@ -1306,6 +1347,7 @@ struct BaseCheckFunction pinDet.Init(); saveImg.Init(); Merge_QueXi.Init(); + blurDet.Init(); bigNG.Init(); } void copy(BaseCheckFunction tem) @@ -1314,6 +1356,7 @@ struct BaseCheckFunction this->pinDet.copy(tem.pinDet); this->saveImg.copy(tem.saveImg); this->Merge_QueXi.copy(tem.Merge_QueXi); + this->blurDet.copy(tem.blurDet); this->bigNG.copy(tem.bigNG); } void print(std::string str) @@ -1323,6 +1366,7 @@ struct BaseCheckFunction pinDet.print("pinDet"); saveImg.print("saveImg"); Merge_QueXi.print("Merge_QueXi"); + blurDet.print("blurDet"); bigNG.print("bigNG"); } std::string GetInfo(std::string str) @@ -1332,6 +1376,7 @@ struct BaseCheckFunction str123 += pinDet.GetInfo("pinDet"); str123 += saveImg.GetInfo("saveImg"); str123 += Merge_QueXi.GetInfo("Merge_QueXi"); + str123 += blurDet.GetInfo("blurDet"); str123 += bigNG.GetInfo("bigNG"); // str123 += "\n"; return str123; diff --git a/ConfigModule/src/JsonConfig.cpp b/ConfigModule/src/JsonConfig.cpp index fcd9905..91f3e7f 100644 --- a/ConfigModule/src/JsonConfig.cpp +++ b/ConfigModule/src/JsonConfig.cpp @@ -887,6 +887,43 @@ int BaseFuntonConfigJson::GetFunction(Json::Value value) _config.Merge_QueXi.bOpen = value_f["isOpen"].asBool(); } + // 图像模糊度(清晰度)检测: AI 检测之前执行, 模糊图片可判 NG + if ("Blur_Detect" == strCode) + { + auto value_f = value; + // std::cout << value_f << std::endl; + // getchar(); + _config.blurDet.bOpen = value_f["isOpen"].asBool(); + if (_config.blurDet.bOpen) + { + if (value_f["form"]["Blur_Config"]["bNG"]) + { + _config.blurDet.bNG = value_f["form"]["Blur_Config"]["bNG"].asBool(); + } + // 模糊度阈值: blur_threshold(界面) / threshold 兼容; 未配置或 <= 0 时保留默认阈值 + float fThreshold = 0.0f; + if (value_f["form"]["Blur_Config"]["blur_threshold"]) + { + fThreshold = value_f["form"]["Blur_Config"]["blur_threshold"].asFloat(); + } + else if (value_f["form"]["Blur_Config"]["threshold"]) + { + fThreshold = value_f["form"]["Blur_Config"]["threshold"].asFloat(); + } + if (fThreshold > 0.0f) + { + _config.blurDet.fThreshold = fThreshold; + } + } + else + { + // 关闭: 阈值置 0, 不做模糊判定 + _config.blurDet.bOpen = false; + _config.blurDet.fThreshold = 0.0f; + } + // _config.blurDet.print("blurDet"); + } + if ("Big_NG" == strCode) { auto value_f = value;