/* * @Author: your name * @Date: 2022-04-20 15:50:00 * @LastEditTime: 2025-09-23 11:34:53 * @LastEditors: xiewenji 527774126@qq.com * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE * @FilePath: /ZCXD_MonitorPlatform/src/CoreLogicModule/src/CamDeal.cpp */ #include "ImgCheckAnalysisy.hpp" #include "CheckUtil.hpp" #include "Define.h" #include #include #include #include #include "AICommonDefine.h" #include #include // ===================== Pin 整体偏移估计参数 ===================== static const double PIN_SHIFT_MIN_SUPPORT_RATIO = 0.80; // 判定整体偏移所需的最小支持度(支持pin数 / 该排模板pin数) static const float PIN_SHIFT_BIN_SIZE_PX = 4.0f; // 位移投票的网格尺寸(px), 同时作为峰值邻域半径 static const float PIN_SHIFT_MAX_GAP_RATIO = 0.5f; // 整体位移量级上限 = 相邻pin最小间距 * 该比例 // ====================================================================================== // 计算点相对旋转矩形“长边轴线”的带符号法向距离(检测图坐标系): // 取矩形长边方向作为 Pin 排方向, 返回点沿短边(法线)方向相对矩形中心的投影; // 正负号天然把分布在矩形两侧的 Pin 分成两组(上下两排分别估计整体偏移)。 static float PinRowSideOfRect(const cv::RotatedRect &rr, const cv::Point2f &pt) { const float th = static_cast(rr.angle * CV_PI / 180.0); // OpenCV RotatedRect(图像坐标系, y 轴向下): 宽度方向 (cos, -sin), 高度方向 (sin, cos) const cv::Point2f widthDir(std::cos(th), -std::sin(th)); const cv::Point2f heightDir(std::sin(th), std::cos(th)); const cv::Point2f normalDir = (rr.size.width >= rr.size.height) ? heightDir : widthDir; return (pt - rr.center).dot(normalDir); } // 规范化 Pin 检测方案字符串: 去掉首尾空白并转小写(兼容配置里的大小写/空格差异) static std::string NormalizeDetMethodStr(const std::string &method) { size_t begin = method.find_first_not_of(" \t\r\n"); if (begin == std::string::npos) { return ""; } size_t end = method.find_last_not_of(" \t\r\n"); std::string ret = method.substr(begin, end - begin + 1); std::transform(ret.begin(), ret.end(), ret.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); return ret; } // 应用 2x3 仿射矩阵(CV_64F): 模板图坐标 -> 目标图坐标 static cv::Point2f ApplyAffine2D(const cv::Mat &M, const cv::Point2f &pt) { return cv::Point2f(static_cast(M.at(0, 0) * pt.x + M.at(0, 1) * pt.y + M.at(0, 2)), static_cast(M.at(1, 0) * pt.x + M.at(1, 1) * pt.y + M.at(1, 2))); } // 把 Pin 中心按"排"分组(最多两排), 用于内定位法线无法分排时的兜底: // 用 minAreaRect 求 Pin 群主方向, 沿短边(法线)投影, 在最大间隙处切分; // 间隙过小(单排)或切分后某组不足 2 个 Pin 时按一排处理。 static std::vector> SplitPinsIntoRows(const std::vector &pts) { std::vector> rows; const int n = static_cast(pts.size()); std::vector all(n); for (int i = 0; i < n; i++) { all[i] = i; } if (n < 4) { rows.push_back(all); return rows; } cv::RotatedRect rr = cv::minAreaRect(pts); const double th = rr.angle * CV_PI / 180.0; const cv::Point2f widthDir(static_cast(std::cos(th)), static_cast(-std::sin(th))); const cv::Point2f heightDir(static_cast(std::sin(th)), static_cast(std::cos(th))); cv::Point2f nor = (rr.size.width <= rr.size.height) ? widthDir : heightDir; // 统一法线朝向, 避免 ±180 度只是翻转上下排的先后顺序 if (nor.y < 0.0f || (nor.y == 0.0f && nor.x < 0.0f)) { nor = -nor; } std::vector proj(n); std::vector order(n); for (int i = 0; i < n; i++) { order[i] = i; proj[i] = pts[i].dot(nor); } std::sort(order.begin(), order.end(), [&](int a, int b) { return proj[a] < proj[b]; }); int splitK = -1; float bestGap = -1.0f; for (int k = 1; k <= n - 3; k++) { float gap = proj[order[k + 1]] - proj[order[k]]; if (gap > bestGap) { bestGap = gap; splitK = k; } } // 两排间距不明显(单排)或切分后某组不足 2 个 Pin -> 按一排处理 if (splitK < 1 || splitK > n - 3 || bestGap < 5.0f) { rows.push_back(all); return rows; } rows.push_back(std::vector(order.begin(), order.begin() + splitK + 1)); rows.push_back(std::vector(order.begin() + splitK + 1, order.end())); return rows; } // 分类映射配置文件(每行:label索引:缺陷名称;冒号后名称为该缺陷类型名称,行的顺序即缺陷类型索引) static const std::string CLS_LABEL_CONFIG_FILE = "/home/aidlux/BOE/UseModel_ZB/KK_cls.txt"; // 去掉字符串首尾的空白字符和引号 static std::string ClsTrimStr(const std::string &str) { size_t begin = str.find_first_not_of(" \t\r\n\""); if (begin == std::string::npos) { return ""; } size_t end = str.find_last_not_of(" \t\r\n\""); return str.substr(begin, end - begin + 1); } // 组装程序内默认缺陷名称列表(AI_CLass_QX_NAME_Names) static std::vector BuildDefaultAIQXNames() { std::vector vecNames; for (int i = 0; i < AI_CLass_QX_NAME_count; i++) { vecNames.push_back(AI_CLass_QX_NAME_Names[i]); } return vecNames; } // 分类映射配置:缺陷名称列表(索引=行序) + 分类label->缺陷名称 映射 struct ClsLabelConfig { std::vector vecNames; // 缺陷名称列表 std::map mapLabelName; // 分类 label -> 缺陷名称 }; // 读取分类映射配置(每行:label索引:缺陷名称;冒号后名称按行序构成缺陷名称列表;# 后内容忽略) static ClsLabelConfig ReadClsLabelConfig(const std::string &strFilePath) { ClsLabelConfig config; std::ifstream ifs(strFilePath.c_str()); if (!ifs.is_open()) { printf("Open cls label config failed, use default mapping: %s\n", strFilePath.c_str()); return config; } std::string strLine; while (std::getline(ifs, strLine)) { // # 后内容(含注释)忽略 size_t nPosComment = strLine.find('#'); if (nPosComment != std::string::npos) { strLine = strLine.substr(0, nPosComment); } size_t nPos = strLine.find(':'); if (nPos == std::string::npos) { // 无冒号:非标准行,跳过并提示(仅支持 label索引:缺陷名称 格式) std::string strTrim = ClsTrimStr(strLine); if (!strTrim.empty()) { printf("Cls label config skip invalid line: %s\n", strTrim.c_str()); } continue; } // label索引:缺陷名称(冒号后名称按行序构成缺陷名称列表) std::string strKey = ClsTrimStr(strLine.substr(0, nPos)); std::string strValue = ClsTrimStr(strLine.substr(nPos + 1)); if (!strKey.empty() && !strValue.empty()) { config.mapLabelName[atoi(strKey.c_str())] = strValue; config.vecNames.push_back(strValue); // 行序即缺陷类型索引 } } return config; } // 获取分类映射配置(只读一次) static const ClsLabelConfig &GetClsLabelConfig() { static const ClsLabelConfig config = ReadClsLabelConfig(CLS_LABEL_CONFIG_FILE); return config; } // 获取缺陷名称列表:优先取配置文件的名称列表,读不到用程序内默认列表 static const std::vector &GetAIQXNameList() { static const std::vector vecDefaultNames = BuildDefaultAIQXNames(); const ClsLabelConfig &config = GetClsLabelConfig(); if (!config.vecNames.empty()) { return config.vecNames; } return vecDefaultNames; } // 缺陷名称 -> AI_CLass 枚举(找不到返回 count 表示无效) static int ClsNameToAItype(const std::string &strName) { const std::vector &vecNames = GetAIQXNameList(); for (size_t i = 0; i < vecNames.size(); i++) { if (strName == vecNames[i]) { return (int)i; } } return AI_CLass_QX_NAME_count; } // 解析 label->缺陷名称 映射为 label->AI_CLass枚举(只读一次;无效名称仅提示一次) static std::map BuildLabelTypeMap() { const ClsLabelConfig &config = GetClsLabelConfig(); std::map mapLabelType; for (std::map::const_iterator it = config.mapLabelName.begin(); it != config.mapLabelName.end(); ++it) { int nType = ClsNameToAItype(it->second); if (nType < AI_CLass_QX_NAME_count) { mapLabelType[it->first] = nType; } else { printf("Cls label config unknown name: %s\n", it->second.c_str()); } } return mapLabelType; } // 获取分类 label 对应的 AI 缺陷类型:优先配置文件(只读一次),读不到用默认顺序 static int GetClsTypeByLabel(int nLabel) { static const std::map mapLabelType = BuildLabelTypeMap(); std::map::const_iterator it = mapLabelType.find(nLabel); if (it != mapLabelType.end()) { return it->second; } // 映射文件未读到、或该 label 未在文件中配置时,按程序内默认顺序兜底(避免文件缺失/不全导致异常) int cls_num = AI_CLass_QX_NAME_count; switch (nLabel) { case 0: cls_num = AI_CLass_QX_NAME_PIN_PoSun; break; case 1: cls_num = AI_CLass_QX_NAME_PIN_QueShi; break; case 2: cls_num = AI_CLass_QX_NAME_BTB_DuanLie; break; case 3: cls_num = AI_CLass_QX_NAME_LianXi; break; case 4: cls_num = AI_CLass_QX_NAME_QueXi; break; case 5: cls_num = AI_CLass_QX_NAME_YiWu; break; case 6: cls_num = AI_CLass_QX_NAME_C_BianXing; break; case 7: cls_num = AI_CLass_QX_NAME_Panel; break; default: break; } return cls_num; } // 用于排序轮廓的比较函数 static bool compareContourAreas(const vector &contour1, const vector &contour2) { double i = contourArea(contour1); double j = contourArea(contour2); return (i > j); } ImgCheckAnalysisy::ImgCheckAnalysisy() : m_pAI_Edge_Algin(m_pdetlog) , m_Pin_QX_Det(m_pdetlog) { m_nErrorCode = CHECK_OK; m_nThreadIdx = -1; m_bInitSucc = false; m_bExit = false; m_nRun_Status = CHECK_THREAD_STATUS_IDLE; m_fImgage_Scale_X = 0.03f; m_fImgage_Scale_Y = 0.03f; m_pBasicConfig = NULL; m_strCurDetChannel = ""; m_bupdateconfig = false; m_pbaseCheckFunction = &m_AnalysisyConfig.baseFunction; m_strLastDate = ""; m_strRootPath_KK_cls = "/home/aidlux/BOE/Cls/KK/"; creatsavedir(); m_pImageStorage = ImageStorage::getInstance(); m_nConfigIdx = -1; m_ImgBlobHFlagData = NULL; } ImgCheckAnalysisy::~ImgCheckAnalysisy() { ExitSystem(); if (m_ImgBlobHFlagData) { delete[] m_ImgBlobHFlagData; m_ImgBlobHFlagData = NULL; } } int ImgCheckAnalysisy::UpdateConfig(void *pconfig, int nConfigType) { int re = 0; switch (nConfigType) { case CHECK_CONFIG_Run: re = LoadRunConfig(pconfig); if (re == 0) { // printf("---> LoadRunConfig Succ\n"); } else { printf("---> LoadRunConfig Fail\n"); } break; case CHECK_CONFIG_Module: re = LoadCheckConfig(pconfig); if (re == 0) { // printf("---> LoadAnalysisConfig Succ\n"); } else { printf("---> LoadAnalysisConfig Fail\n"); } break; default: break; } return re; } int ImgCheckAnalysisy::RunStart(void *pconfig1) { // 1 、更新参数 并判断参数是否合法 int re = CHECK_OK; re = SetNewConfig(); if (CHECK_OK != re) { m_nErrorCode = re; return m_nErrorCode; } // printf("---> RunStart Start m_RunConfig.nThreadIdx %d \n", m_RunConfig.nThreadIdx); m_nThreadIdx = m_RunConfig.nThreadIdx; re = InitRun(m_RunConfig.nCpu_start_Idx); if (CHECK_OK != re) { m_nErrorCode = re; return m_nErrorCode; } runner = std::make_shared(); runner->Start(); m_nErrorCode = CHECK_OK; // printf("ImgCheckAnalysisy >>>> ImgCheckThread %d Start Succ \n", m_nThreadIdx); return m_nErrorCode; } int ImgCheckAnalysisy::SetDataRun_SharePtr(std::shared_ptr p) { // 设置正在检测 m_pImageAllResult = p; DetImgInfo_shareP = p->result->in_shareImage; m_CheckResult_shareP = p->result; m_pImageAllResult->setStep(ImageAllResult::DetStep_Deting); m_pdetlog = p->detlog; m_pDetResult = p->pDetResult; StartCheck(); m_nErrorCode = CHECK_OK; return m_nErrorCode; } int ImgCheckAnalysisy::GetCheckReuslt(std::shared_ptr &pResult) { m_CheckResult_shareP.reset(); DetImgInfo_shareP.reset(); SetIDLE(); // m_nErrorCode = CHECK_OK; // printf("4 DetImgInfo_shareP count %ld m_nCheckResultErrorCode %d \n", DetImgInfo_shareP.use_count(), m_nCheckResultErrorCode); return m_nCheckResultErrorCode; } int ImgCheckAnalysisy::CheckImg(std::shared_ptr p, std::shared_ptr &pResult) { return 0; } int ImgCheckAnalysisy::ReJsonResul(std::shared_ptr p, std::shared_ptr &pResult) { return 0; } int ImgCheckAnalysisy::InitRun(int nId) { int re = CHECK_OK; if (m_bInitSucc) { return CHECK_OK; } InitModel(); AI_Factory = AIFactory::GetInstance(); m_nRun_Status = CHECK_THREAD_STATUS_IDLE; re = StartThread(nId); if (CHECK_OK != re) { return re; } m_bInitSucc = true; return re; } int ImgCheckAnalysisy::GetStatus() { return m_nRun_Status; } std::string ImgCheckAnalysisy::GetVersion() { return std::string("BOE_1.3.1"); } std::string ImgCheckAnalysisy::GetErrorInfo() { std::string str = GetErrorCodeInfo(m_nErrorCode); printf("%s\n", str.c_str()); return str; } int ImgCheckAnalysisy::creatsavedir() { std::string curDate = CheckUtil::getCurrentDate(); if (curDate == m_strLastDate) { return 0; } m_strLastDate = curDate; m_strRootPath_KK_cls += m_strLastDate + "/"; for (int i = 0; i < AI_CLass_QX_NAME_count; i++) { CheckUtil::CreateDir(m_strRootPath_KK_cls + std::to_string(i) + "/"); } return 0; } void ImgCheckAnalysisy::SetTplInfo(const cv::RotatedRect &outerRect, const cv::RotatedRect &innerRect, const std::vector> &pinContours) { m_tplOuterRect = outerRect; m_tplInnerRect = innerRect; m_tplPinContours = pinContours; } int ImgCheckAnalysisy::LoadRunConfig(void *p) { if (p == NULL) { m_nErrorCode = CHECK_ERROR_Config_Null; return m_nErrorCode; } RunInfoST *pconfig = (RunInfoST *)p; m_RunConfig.copy(*pconfig); return CHECK_OK; } int ImgCheckAnalysisy::LoadCheckConfig(void *p) { if (p == NULL) { m_nErrorCode = CHECK_ERROR_Config_Null; return m_nErrorCode; } m_pConfig = (ConfigBase *)p; m_nConfigIdx = m_pConfig->GetConfigIdx(); m_nErrorCode = CHECK_OK; return m_nErrorCode; } // 开启检测 int ImgCheckAnalysisy::StartCheck() { m_nRun_Status = CHECK_THREAD_STATUS_READY; return 0; } int ImgCheckAnalysisy::SetIDLE() { // 更新参数 SetNewConfig(); m_nRun_Status = CHECK_THREAD_STATUS_IDLE; return 0; } int ImgCheckAnalysisy::StartThread(int nId) { // 开启检测线程 ptr_thread_Run = std::make_shared(std::bind(&ImgCheckAnalysisy::Run, this, nId)); if (!m_RunConfig.bRetest) { ptr_thread_AI = std::make_shared(std::bind(&ImgCheckAnalysisy::ThreadTask, this, nId + 1)); } return 0; } int ImgCheckAnalysisy::StopThread() { m_bExit = true; if (ptr_thread_Run != nullptr) { if (ptr_thread_Run->joinable()) { ptr_thread_Run->join(); } } if (ptr_thread_AI != nullptr) { if (ptr_thread_AI->joinable()) { ptr_thread_AI->join(); } } return 0; } int ImgCheckAnalysisy::ExitSystem() { StopThread(); return 0; } int ImgCheckAnalysisy::InitModel() { // 获取当前gpu号确定的 AI处理线程 m_OtherDet_Config.nDeviceId = m_RunConfig.nDeviceId; m_pAI_Edge_Algin.Init(&m_OtherDet_Config); m_pAI_Edge_Algin.InitModel_ALL(); return 0; } cv::Scalar ImgCheckAnalysisy::calc_blob_info_withstats(cv::Mat &img, const cv::Mat &mask, cv::Rect &stats, cv::Size k_size, int expand, double threshold) { // 解包 stats(x, y, w, h, area) int x = stats.x, y = stats.y, w = stats.width, h = stats.height; // 将图像转换为灰度图像 // 计算感兴趣区域 (ROI) cv::Rect roi(x - expand, y - expand, w + 2 * expand, h + 2 * expand); roi &= cv::Rect(0, 0, img.cols, img.rows); // 确保ROI在图像内 cv::Mat cimg = img(roi); // cv::cvtColor(cimg, cimg, cv::COLOR_BGR2GRAY); // return cv::Scalar(0, 0, 0); cv::Mat cmask = mask(roi); // getchar(); // 扩张掩膜 cv::Scalar mean_bk = cv::mean(cimg, ~cmask); double fbk = mean_bk[0]; cv::Scalar mean_det = cv::mean(cimg, cmask); double fdet = mean_det[0]; // 计算差异图像 cv::Mat diff = cv::abs(cimg - fbk); // cv::imwrite("cimg.png",cimg); // cv::imwrite("cmask.png",cmask); // printf("%f %f - %f \n",fbk,fdet,fbk-fdet); // getchar(); // diff = diff.mul(cmask > 0); cv::Mat masked_image; diff.copyTo(masked_image, cmask); // 计算能量 double energy = cv::sum(masked_image)[0]; // 计算 hj(差异图像大于0的像素均值) // double hj = std::abs(fbk - fdet); // double hj = CheckUtil::CalHj(cimg, cmask, mean_bk.val[0]); double hj = CheckUtil::CalHjWeighted(cimg, cmask, mean_bk.val[0], 2.0f); int worb = 0; if (fdet >= fbk) { worb = 1; } // cv::imwrite("cimg.png", cimg); // cv::imwrite("cmask.png", cmask); // cv::imwrite("diff.png", diff); // cv::imwrite("masked_image.png", masked_image); // printf("fbk %f fdet %f energy %f\n", fbk, fdet, energy); // getchar(); return cv::Scalar(worb, energy, hj); } // 计算仿射矩阵(不做图像 warp): // 基准点用当前基准矩形(内定位优先/外定位回退)的三个角点 -> 模板对应基准矩形的三个角点 // m_affine_cur2tpl: 当前图坐标 -> 模板图坐标 // m_affine_tpl2cur: 模板图坐标 -> 当前图坐标 int ImgCheckAnalysisy::CalAffineMats(const RotatedRect &tplRefRect, const RotatedRect &curRefRect) { m_affine_cur2tpl.release(); m_affine_tpl2cur.release(); m_bAffineValid = false; if (tplRefRect.size.width <= 0 || tplRefRect.size.height <= 0 || curRefRect.size.width <= 0 || curRefRect.size.height <= 0) { m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "CalAffineMats: invalid ref rect tpl[%f %f] cur[%f %f]", tplRefRect.size.width, tplRefRect.size.height, curRefRect.size.width, curRefRect.size.height); return 1; } vector cur_vertices = CheckUtil::sort_vertices(curRefRect); vector tpl_vertices = CheckUtil::sort_vertices(tplRefRect); cv::Point2f cur_pts[3] = { cur_vertices[0], // 左上 cur_vertices[1], // 右上 cur_vertices[2] // 左下 }; cv::Point2f tpl_pts[3] = { tpl_vertices[0], // 左上 tpl_vertices[1], // 右上 tpl_vertices[2] // 左下 }; m_affine_cur2tpl = cv::getAffineTransform(cur_pts, tpl_pts); m_affine_tpl2cur = cv::getAffineTransform(tpl_pts, cur_pts); m_bAffineValid = (!m_affine_cur2tpl.empty() && !m_affine_tpl2cur.empty()); if (!m_bAffineValid) { m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "CalAffineMats: getAffineTransform failed"); return 1; } // 仅用于日志: 分解当前->模板的旋转/缩放, 便于排查 const double *M = m_affine_cur2tpl.ptr(0); double fScaleX = std::sqrt(M[0] * M[0] + M[3] * M[3]); double fScaleY = std::sqrt(M[1] * M[1] + M[4] * M[4]); double fAngle = std::atan2(M[3], M[0]) * 180.0 / CV_PI; m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "CalAffineMats", "scaleX %.4f scaleY %.4f angle %.2f deg t(%.2f %.2f)", fScaleX, fScaleY, fAngle, M[2], M[5]); return 0; } // 把模板 Pin 轮廓反变换到当前检测图(detImg)坐标系: // m_tplPinContours 在 pinImg(模板外框裁剪图)坐标系 // -> 加模板外框原点得到模板图坐标 // -> m_affine_tpl2cur 得到当前图坐标 // -> 减 m_outer_roi 原点得到 detImg 坐标 int ImgCheckAnalysisy::TransformTplPinContoursToCur() { m_tplPinContoursCur.clear(); if (m_tplPinContours.empty()) { return 0; } if (!m_bAffineValid || m_affine_tpl2cur.empty()) { m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "TransformTplPinContoursToCur: affine invalid, template pin contours skipped"); return 1; } const cv::Rect tplBound = m_tplOuterRect.boundingRect(); const double *M = m_affine_tpl2cur.ptr(0); m_tplPinContoursCur.reserve(m_tplPinContours.size()); for (const auto &contour : m_tplPinContours) { std::vector mapped; mapped.reserve(contour.size()); for (const auto &pt : contour) { // pinImg 坐标 -> 模板图坐标 const double tplX = static_cast(pt.x) + tplBound.x; const double tplY = static_cast(pt.y) + tplBound.y; // 模板图坐标 -> 当前图坐标 const double curX = M[0] * tplX + M[1] * tplY + M[2]; const double curY = M[3] * tplX + M[4] * tplY + M[5]; // 当前图坐标 -> detImg 坐标 mapped.emplace_back(cvRound(curX - m_outer_roi.x), cvRound(curY - m_outer_roi.y)); } m_tplPinContoursCur.push_back(std::move(mapped)); } return 0; } int ImgCheckAnalysisy::CheckRun() { // printf(">>>%s ================start \n", m_pImageAllResult->strBaseInfo.c_str()); long t1, t2, t3, t4, t5, t6, t7; SetNewConfig(); t1 = CheckUtil::getcurTime(); CheckImgInit(); if (DetImgInfo_shareP->bsaveProcessImg) { m_pdetlog->bPrintStr = true; } // m_pdetlog->bPrintStr = true; m_fImgage_Scale_X = m_pBasicConfig->fImage_Scale_x; m_fImgage_Scale_Y = m_pBasicConfig->fImage_Scale_y; m_pdetlog->AddCheckstr(PrintLevel_0, "1、basic Info", "---------------------------1、basic Info---------------------------------"); m_pdetlog->AddCheckstr(PrintLevel_0, "Version", "%s", GetVersion().c_str()); m_pdetlog->AddCheckstr(PrintLevel_0, "Updateconfig", "%d", m_bupdateconfig); m_pdetlog->AddCheckstr(PrintLevel_0, "Start", "%s", m_pImageAllResult->strBaseInfo.c_str()); m_pdetlog->AddCheckstr(PrintLevel_0, "ImgageScale", "Scale_X = %f Scale_Y = %f", m_fImgage_Scale_X, m_fImgage_Scale_Y); m_bupdateconfig = false; // 返回结果状态初始化 m_CheckResult_shareP->checkStatus = 1; m_CheckResult_shareP->nresult = -1; m_CheckResult_shareP->basicResult.img_id = m_CheckResult_shareP->in_shareImage->img_id; m_CheckResult_shareP->basicResult.imgtype = m_CheckResult_shareP->in_shareImage->imgtype; m_CheckResult_shareP->basicResult.imgstr = m_CheckResult_shareP->in_shareImage->imgstr; m_CheckResult_shareP->basicResult.strChannel = m_CheckResult_shareP->in_shareImage->strChannel; m_strCurDetChannel = m_CheckResult_shareP->basicResult.strChannel; /*参数检查*/ int rec = ConfigCheck(DetImgInfo_shareP->img); if (rec != CHECK_OK) { m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "ConfigCheck is error type = %d", rec); m_nErrorCode = rec; m_nCheckResultErrorCode = m_nErrorCode; return m_nErrorCode; } long time_edge_s = CheckUtil::getcurTime(); /*AI 边缘定位(内外边缘)*/ // 模型定位内外边缘 int reedge = AI_Edge(m_CheckResult_shareP->in_shareImage->img, m_outer_rroi, m_inner_rroi); if (reedge != 0) { m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "AI_Edge is error type = %d", reedge); m_nErrorCode = reedge; m_nCheckResultErrorCode = m_nErrorCode; return m_nErrorCode; } long time_edge_e = CheckUtil::getcurTime(); m_pdetlog->AddCheckstr(PrintLevel_0, "2、pre detect", "-------------------------AI_Edge------------succ---time %ld----\n", time_edge_e - time_edge_s); // 计算内外边缘夹角 float aouter = CheckUtil::getLongAxisAngle(m_outer_rroi); float ainner = CheckUtil::getLongAxisAngle(m_inner_rroi); // 计算内边缘宽高与标准差值 float dinnerLen = std::abs(m_inner_rroi.size.width - m_pbaseCheckFunction->ALign_Config.len) * m_fImgage_Scale_X; float dinnerWidth = std::abs(m_inner_rroi.size.height - m_pbaseCheckFunction->ALign_Config.width) * m_fImgage_Scale_Y; bool bSizeerr = (dinnerLen > m_pbaseCheckFunction->ALign_Config.len_diff || dinnerWidth > m_pbaseCheckFunction->ALign_Config.width_diff); float diff_align = std::fabs(aouter - ainner); diff_align = std::fmod(diff_align, 180.0f); // 模 180 度 // 两条直线的夹角取锐角:min(diff, 180 - diff) if (diff_align > 90.0f) { diff_align = 180.0f - diff_align; } // 1. 使用 ALign_Config.angle_diff 卡控内外边缘夹角(未配置时默认 10 度) float angleTh = 10.0f; if (m_pbaseCheckFunction != NULL && m_pbaseCheckFunction->ALign_Config.angle_diff > 0.0f) { angleTh = m_pbaseCheckFunction->ALign_Config.angle_diff; } // 2. 使用 ALign_Config.x_offset / y_offset (mm) 卡控内外边缘中心偏移(mm 转像素) float offX_mm = 0.0f; float offY_mm = 0.0f; if (m_pbaseCheckFunction != NULL) { if (m_fImgage_Scale_X > 0.0f) { offX_mm = m_pbaseCheckFunction->ALign_Config.x_offset; } if (m_fImgage_Scale_Y > 0.0f) { offY_mm = m_pbaseCheckFunction->ALign_Config.y_offset; } } // 3. 内外边缘中心坐标偏移(像素) float dx_mm = std::fabs(m_outer_rroi.center.x - m_inner_rroi.center.x) * m_fImgage_Scale_X; float dy_mm = std::fabs(m_outer_rroi.center.y - m_inner_rroi.center.y) * m_fImgage_Scale_Y; bool bAngleErr = diff_align > angleTh; bool bOffsetXErr = (offX_mm > 0.0f) && (dx_mm > offX_mm); bool bOffsetYErr = (offY_mm > 0.0f) && (dy_mm > offY_mm); bool bOffsetErr = bOffsetXErr || bOffsetYErr; // 角度超差或中心偏移超差 -> 添加到缺陷列表 if (bAngleErr || bOffsetErr || bSizeerr) { QX_ERROR_INFO_ alignErr; alignErr.Idx = static_cast(m_pDetResult->pQx_ErrorList->size()); // roi 用整张检测图区域(detImg 坐标系); detImg 现在取当前外定位包围框, 尺寸与之一致 cv::Rect curBound = m_outer_rroi.boundingRect(); alignErr.roi = cv::Rect(0, 0, curBound.width, curBound.height); alignErr.area = alignErr.roi.width * alignErr.roi.height; alignErr.JudgArea = alignErr.area * m_fImgage_Scale_X * m_fImgage_Scale_Y; alignErr.flen = curBound.width * m_fImgage_Scale_X; alignErr.fbreadth = curBound.height * m_fImgage_Scale_Y; // 缺陷列表中原 other(其他)已移除;内外边缘对齐超差暂按 C型卡口异常 上报(待确认) alignErr.nconfig_qx_type = CONFIG_QX_NAME_HunLiao; alignErr.qx_name = CONFIG_QX_NAME_Names[CONFIG_QX_NAME_HunLiao]; alignErr.grayDis = diff_align; // 记录内外边缘夹角(度) alignErr.detRegionidxList.push_back(0); alignErr.detlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Align Check", "outer-inner angle diff %.2f deg", diff_align); m_pDetResult->pQx_ErrorList->push_back(alignErr); if (bAngleErr) { m_pdetlog->AddCheckstr(PrintLevel_0, "Align Check", "angle diff %.2f deg > %.2f", diff_align, angleTh); } if (bOffsetXErr) { m_pdetlog->AddCheckstr(PrintLevel_0, "Align Check", "center offset dx %.1f mm > %.1f mm", dx_mm, offX_mm); } if (bOffsetYErr) { m_pdetlog->AddCheckstr(PrintLevel_0, "Align Check", "center offset dy %.1f mm > %.1f mm", dy_mm, offY_mm); } if (bSizeerr) { m_pdetlog->AddCheckstr(PrintLevel_0, "Align Check", "inner size diff len %.1f mm, width %.1f mm > threshold", dinnerLen, dinnerWidth); } } /*投影对齐模板*/ // Pin 检测方案由参数 Det_Method 运行时选择(替代原 PIN_USE_DIRECT_MATCH 编译宏) if (IsDirectPinMethod()) { // 新方案(Det_Method == "line_fitting"): 不做仿射变换, 检测图直接按当前外定位结果裁剪; // Pin分析在 Pin_Qx_Det -> Pin_Direct_Check 中完成(只分析检测Pin, 不做模板对比) m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", "Align mode: Direct Pin Check (no affine)"); { cv::Rect curOuterBound = m_outer_rroi.boundingRect(); cv::Rect curImgBound(0, 0, m_CheckResult_shareP->in_shareImage->img.cols, m_CheckResult_shareP->in_shareImage->img.rows); curOuterBound &= curImgBound; if (curOuterBound.width <= 0 || curOuterBound.height <= 0) { cv::Rect rawBound = m_outer_rroi.boundingRect(); m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "Direct Pin Match: current outer roi invalid [%d %d %d %d]", rawBound.x, rawBound.y, rawBound.width, rawBound.height); m_nErrorCode = CHECK_ERROR_Config_cutRoi; m_nCheckResultErrorCode = m_nErrorCode; return m_nErrorCode; } m_outer_roi = curOuterBound; } } else if (IsPinMatrixMethod()) { // 方案三(Det_Method == "Pin_Matrix"): 投影矩阵在 Pin_Qx_Det 中由当前帧 Pin 拟合(上下两排分别计算); // 此处只需要用当前外定位包围框作为检测图裁剪区域(原图不 warp), 不依赖内/外定位仿射 m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", "Align mode: Pin Matrix (projection matrix fitted by pins, upper/lower rows separately, image NOT warped)"); { cv::Rect curOuterBound = m_outer_rroi.boundingRect(); cv::Rect curImgBound(0, 0, m_CheckResult_shareP->in_shareImage->img.cols, m_CheckResult_shareP->in_shareImage->img.rows); curOuterBound &= curImgBound; if (curOuterBound.width <= 0 || curOuterBound.height <= 0) { cv::Rect rawBound = m_outer_rroi.boundingRect(); m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "Pin Matrix: current outer roi invalid [%d %d %d %d]", rawBound.x, rawBound.y, rawBound.width, rawBound.height); m_nErrorCode = CHECK_ERROR_Config_cutRoi; m_nCheckResultErrorCode = m_nErrorCode; return m_nErrorCode; } m_outer_roi = curOuterBound; } } else { // 旧方案(Det_Method == "BTB_Marix" 或未配置): 不对原图做 warpAffine, 只计算矩阵, 并把模板 Pin 轮廓反变换到当前图坐标系。 bool bUseInnerAlign = (m_tplInnerRect.size.width > 0 && m_tplInnerRect.size.height > 0 && m_inner_rroi.size.width > 0 && m_inner_rroi.size.height > 0); if (bUseInnerAlign) { m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", "Affine align basis: Inner(inner_rroi -> tplInnerRect), template pins warp only, image NOT warped"); CalAffineMats(m_tplInnerRect, m_inner_rroi); } else { m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", "Affine align basis: Outer(outer_rroi -> tplOuterRect), template pins warp only, image NOT warped"); CalAffineMats(m_tplOuterRect, m_outer_rroi); } // 后续所有检测/裁剪都在当前图坐标系: 裁剪框取当前外定位的包围框(原图不 warp) { cv::Rect curOuterBound = m_outer_rroi.boundingRect(); cv::Rect curImgBound(0, 0, m_CheckResult_shareP->in_shareImage->img.cols, m_CheckResult_shareP->in_shareImage->img.rows); m_outer_roi = curOuterBound & curImgBound; if (m_outer_roi.width <= 0 || m_outer_roi.height <= 0) { m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "Affine mode: current outer roi invalid [%d %d %d %d]", curOuterBound.x, curOuterBound.y, curOuterBound.width, curOuterBound.height); m_nErrorCode = CHECK_ERROR_Config_cutRoi; m_nCheckResultErrorCode = m_nErrorCode; return m_nErrorCode; } } // 模板 Pin(pinImg 坐标) -> 模板图坐标 -> 当前图坐标 -> detImg 坐标 TransformTplPinContoursToCur(); m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "2.1、pre detect", "tpl pin contours -> current frame: %ld (affineValid %d)", static_cast(m_tplPinContoursCur.size()), static_cast(m_bAffineValid)); } m_Crop_Roi_paramImg = m_outer_roi; m_pImageAllResult->pDetResult->CutRoi = m_outer_roi; m_pImageAllResult->pDetResult->Param_CropRoi = m_Crop_Roi_paramImg; long time_calan_e = CheckUtil::getcurTime(); m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", "-------------------------CalAndAffine------------succ---time %ld----\n", time_calan_e - time_edge_s); /*生成 检测的图片(检测用图固定为单通道灰度;彩色原图按 BGR 顺序转灰度,与 imread(path,0) 的亮度一致)*/ cv::Mat image; image = m_CheckResult_shareP->in_shareImage->img; if (image.channels() == 3) { cv::cvtColor(image(m_outer_roi), m_pImageAllResult->detImg, cv::COLOR_BGR2GRAY); } else if (image.channels() == 4) { cv::cvtColor(image(m_outer_roi), m_pImageAllResult->detImg, cv::COLOR_BGRA2GRAY); } else { m_pImageAllResult->detImg = image(m_outer_roi).clone(); } /*多线程开启 AI 推理检测*/ long time_AI_s = CheckUtil::getcurTime(); m_AItask = std::make_shared(); m_AItask->taskname = Task_AI; m_task.sendTask(m_AItask); ImgPreDet(); /*更新检测区域*/ // Update_DetRoiList(); m_pdetlog->AddCheckstr(PrintLevel_0, "3、pre detect", "-------------------------pre Det--------------- \n"); /*实现PIN脚检测*/ { long t41 = CheckUtil::getcurTime(); int reedge1111 = Pin_Qx_Det(m_pImageAllResult->detImg); long t42 = CheckUtil::getcurTime(); m_pdetlog->AddCheckstr(PrintLevel_0, "4、detect", "-------------------------1、Pin_Qx_Det--------%ld-------\n", t42 - t41); } m_CheckResult_shareP->nresult = 0; /*把临时可以绘制的结果都绘制出来。*/ DrawResult_Step_1(); long time_AI_e; /*AI 推理生成*/ { long t211 = CheckUtil::getcurTime(); // 对AI的结果进行 处理,等待 AI 推理全部完成。 rec = AIMaskDet(); if (rec != CHECK_OK) { m_nErrorCode = rec; m_nCheckResultErrorCode = m_nErrorCode; return m_nErrorCode; } time_AI_e = CheckUtil::getcurTime(); m_pdetlog->AddCheckstr(PrintLevel_0, "4、detect", "-------------------------2、AI Detect--------%ld wait AI complate %ld-------\n", time_AI_e - time_AI_s, time_AI_e - t211); m_CheckResult_shareP->resultMaskImg = m_pImageAllResult->AIMaskImg; } { t4 = CheckUtil::getcurTime(); rec = GetCheckResultBLob(); if (rec != CHECK_OK) { m_nErrorCode = rec; m_nCheckResultErrorCode = m_nErrorCode; return m_nErrorCode; } t5 = CheckUtil::getcurTime(); m_pdetlog->AddCheckstr(PrintLevel_0, "5、BLob ", "------------------------ --------%ld -------\n", t5 - t4); } // m_pdetlog->printLog(m_strCurDetChannel); long te = CheckUtil::getcurTime(); m_pdetlog->bPrintStr = true; m_pdetlog->AddCheckstr(PrintLevel_1, "result", " ALL use Time %ld edge %ld AI %ld blob %ld", te - t1, time_edge_e - time_edge_s, time_AI_e - time_AI_s, t5 - t4); m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, "End", " Check Run"); return 0; } int ImgCheckAnalysisy::SetNewConfig() { if (m_nConfigIdx < 0) { return 1; /* code */ } if (m_pConfig->GetConfigUpdataStatus(ConfigType_Analysisy_Common_XL, m_nConfigIdx)) { m_bupdateconfig = true; // printf("************** ImgCheckAnalysisy::SetNewConfig m_nConfigIdx %d\n", m_nConfigIdx); m_old_productROI = cv::Rect(0, 0, 0, 0); m_pConfig->GetConfig(ConfigType_Analysisy_Common_XL, &m_AnalysisyConfig); if (m_AnalysisyConfig.commonCheckConfig.nodeConfigArr.size() > 0) { m_pCommonAnalysisyConfig = &m_AnalysisyConfig.commonCheckConfig.nodeConfigArr.at(0); m_pBasicConfig = &m_AnalysisyConfig.commonCheckConfig.baseConfig; m_pRegionAnalysisyParam = &m_pCommonAnalysisyConfig->regionConfigArr.at(0); UpdateImgageScale(); if (false) { printf("SetNewConfig m_nConfigIdx %d m_CheckConfig.strSkuName %s \n", m_nConfigIdx, m_AnalysisyConfig.strSkuName.c_str()); } } else { printf("m_AnalysisyConfig.commonCheckConfig.nodeConfigArr == 0 \n"); } } else { // printf("ConfigType_Analysisy_Common_XL no Update \n"); } m_nErrorCode = CHECK_OK; return CHECK_OK; } ChannelCheckFunction *ImgCheckAnalysisy::GetChannelFuntion(std::string strChannelName) { ChannelCheckFunction *p = NULL; for (int i = 0; i < m_AnalysisyConfig.checkFunction.channelFunctionArr.size(); i++) { if (CheckUtil::compareIgnoreCase(m_AnalysisyConfig.checkFunction.channelFunctionArr[i].strChannelName, strChannelName)) { p = &m_AnalysisyConfig.checkFunction.channelFunctionArr[i]; } } return p; } int ImgCheckAnalysisy::Run(int nId) { std::vector vi; vi.push_back(nId); auto nRet = set_cpu_id(vi); // printf("Check So %d bind cpu ret %d, %d\n", m_nThreadIdx, nRet, nId); while (!m_bExit) { // 数据准备完成,开启检测 if (m_nRun_Status == CHECK_THREAD_STATUS_READY) { m_nRun_Status = CHECK_THREAD_STATUS_BUSY; /* 检测 */ CheckRun(); m_nRun_Status = CHECK_THREAD_STATUS_COMPLETE; m_pImageAllResult->setStep(ImageAllResult::DetStep_Complet); m_nRun_Status = CHECK_THREAD_STATUS_IDLE; // printf("*--------%d\n", m_nRun_Status); } else { usleep(1000); } // printf("*-"); usleep(1000); } return 0; } int ImgCheckAnalysisy::set_cpu_id(const std::vector &cpu_set_vec) { // for cpu affinity int nRet = 0; #ifdef __linux cpu_set_t _cur_cpu_set; CPU_ZERO(&_cur_cpu_set); for (auto _id : cpu_set_vec) { CPU_SET(_id, &_cur_cpu_set); } if (0 > pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &_cur_cpu_set)) { perror("set cpu affinity failed: "); printf("Warning: set cpu affinity failed ... ...\n"); nRet = -1; } #endif //__linux return nRet; } int ImgCheckAnalysisy::CalBlob_Other() { m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "CalBlob_Other", " Start"); float fs_x = m_fImgage_Scale_X; float fs_y = m_fImgage_Scale_Y; float fs_resize_x = m_pImageAllResult->fscale_detToresult_x; float fs_resize_y = m_pImageAllResult->fscale_detToresult_y; // 遍历每个检测blob for (int i = 0; i < blobs.blobCount; i++) { // m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Blob", "%d/%d start", i, blobs.blobCount); cv::Rect roi; roi.x = blobs.blobTab[i].minx; roi.y = blobs.blobTab[i].miny; roi.width = blobs.blobTab[i].maxx - blobs.blobTab[i].minx + 1; roi.height = blobs.blobTab[i].maxy - blobs.blobTab[i].miny + 1; cv::Scalar result = calc_blob_info_withstats(m_pImageAllResult->detImg, m_pImageAllResult->AIMaskImg, roi); int QX_whiteBLACK = CONFIG_QX_BLACK; if (result[0] > 0) { QX_whiteBLACK = CONFIG_QX_WHITE; } double hj = result[2]; double energe = result[1]; blobs.blobTab[i].energy = energe; blobs.blobTab[i].grayDis = hj; float JudgArea = blobs.blobTab[i].area * fs_x * fs_y; blobs.blobTab[i].JudgArea = JudgArea; float flen = roi.width * fs_x; float fwid = roi.height * fs_x; if (roi.height * fs_y > flen) { flen = roi.height * fs_y; fwid = roi.width * fs_y; } blobs.blobTab[i].len = flen; blobs.blobTab[i].breadth = fwid; int nerrortype = 0; int checkFlage = 0; float fmaxScore = 0; int config_qx_type = 0; if (blobs.blobTab[i].ErrType == ERR_TYPE_2) { QX_whiteBLACK = CONFIG_QX_BLACK; // 强制为 黑色 } blobs.blobTab[i].whiteOrblack = QX_whiteBLACK; // 精确计算长度 vector re_len = Cal_QXLen(m_pImageAllResult->detImg(roi), config_qx_type, fs_x, fs_y); if (re_len.size() > 2) { if(re_len[0] >= 0){ flen = re_len[0]; blobs.blobTab[i].len = flen; } if(re_len[1] >= 0){ fwid = re_len[1]; blobs.blobTab[i].breadth = fwid; } } } return 0; } int ImgCheckAnalysisy::GetClassImg(const cv::Mat &img, cv::Mat &AIdetImg, cv::Rect qx_roi, int detwidth, int detheight) { cv::Rect cutroi; cv::Rect roi = qx_roi; bool bresize = false; { int pc_x = roi.x + roi.width * 0.5; int pc_y = roi.y + roi.height * 0.5; if (roi.width < detwidth && roi.height < detheight) { cutroi.width = detwidth; cutroi.x = pc_x - detwidth * 0.5; cutroi.height = detheight; cutroi.y = pc_y - detheight * 0.5; } else { // 宽 高 if (roi.width > roi.height) { cutroi.width = roi.width + 20; cutroi.x = roi.x - 10; float fsx = detheight * 1.0f / detwidth; cutroi.height = cutroi.width * fsx; cutroi.y = pc_y - cutroi.height * 0.5; } else { cutroi.height = roi.height + 20; cutroi.y = roi.y - 10; float fsy = detwidth * 1.0f / detheight; cutroi.width = cutroi.height * fsy; cutroi.x = pc_x - cutroi.width * 0.5; } bresize = true; } if (cutroi.x < 0) { cutroi.x = 0; } if (cutroi.y < 0) { cutroi.y = 0; } if (cutroi.x + cutroi.width >= img.cols) { cutroi.x = img.cols - cutroi.width; if (cutroi.x < 0) { cutroi.x = 0; if (cutroi.x + cutroi.width >= img.cols) { cutroi.width = img.cols; } } } if (cutroi.y + cutroi.height >= img.rows) { cutroi.y = img.rows - cutroi.height; if (cutroi.y < 0) { cutroi.y = 0; if (cutroi.y + cutroi.height >= img.rows) { cutroi.height = img.rows; } } } } if (!CheckUtil::RoiInImg(cutroi, img)) { return 1; } cv::Size sz; sz.width = detwidth; sz.height = detheight; if (cutroi.width != sz.width || cutroi.height != sz.height) { cv::resize(img(cutroi), AIdetImg, sz); } else { AIdetImg = img(cutroi).clone(); } if (1 != AIdetImg.channels()) { cv::cvtColor(AIdetImg, AIdetImg, cv::COLOR_BGR2GRAY); } return 0; } vector ImgCheckAnalysisy::Cal_QXLen(cv::Mat qx_maskImg, int qx_type, float fsc_x, float fsc_y) { vector resLen(3); float nlen = -1; cv::Mat detimg = qx_maskImg; resLen[0] = nlen; // 寻找轮廓 vector> contours; cv::findContours(detimg, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); // 找到最大面积的轮廓 double maxArea = -1; int maxAreaIdx = -1; for (size_t i = 0; i < contours.size(); ++i) { double area = contourArea(contours[i]); if (area > maxArea) { maxArea = area; maxAreaIdx = i; } } // 如果找到了最大面积的轮廓 if (maxAreaIdx >= 0) { // 使用minAreaRect找到最小外接矩形 cv::RotatedRect rect = cv::minAreaRect(contours[maxAreaIdx]); // // 绘制最小外接矩形 // Point2f vertices[4]; // rect.points(vertices); // for (int i = 0; i < 4; ++i) // { // line(detimg, vertices[i], vertices[(i + 1) % 4], Scalar(128, 255, 0), 2); // 绿色线 // } // 输出结果 // 获取最小外接矩形的尺寸 float width = rect.size.width; float height = rect.size.height; // std::cout << "1 Width: " << width << ", Height: " << height << std::endl; Point2f vertices[4]; rect.points(vertices); vector newcont; for (int i = 0; i < 4; ++i) { Point2f p; p.x = vertices[i].x * fsc_x; p.y = vertices[i].y * fsc_y; newcont.push_back(p); } vector> contours_New; contours_New.push_back(newcont); // Recreate the rotated rectangle with scaled vertices RotatedRect scaledRect = minAreaRect(contours_New[0]); // Calculate scaled width and height width = scaledRect.size.width; height = scaledRect.size.height; // std::cout << "2 width: " << width << ", height: " << height << std::endl; // float scale = 0.5; // 缩放比例 // rect.size.width *= fsc_x; // rect.size.height *= fsc_y; // std::cout << "fsc_x: " << fsc_x << ", fsc_y: " << fsc_y << std::endl; // std::cout << "Width: " << width << ", Height: " << height << std::endl; // 获取最小外接矩形的尺寸 // width = rect.size.width; // height = rect.size.height; if (width > height) { nlen = width; resLen[0] = width; resLen[1] = height; /* code */ } else { nlen = height; resLen[0] = height; resLen[1] = width; } } // getchar(); return resLen; } int ImgCheckAnalysisy::GetCheckResultBLob() { long t1, t2, t3, t4, t5, t6, t7; t1 = CheckUtil::getcurTime(); int re = GetALLBlob(); if (re != 0) { return re; } // 缺陷分类 多线程 实现。 long t11 = CheckUtil::getcurTime(); m_Classtask = std::make_shared(); m_Classtask->taskname = Task_Class; m_task.sendTask(m_Classtask); re = CalBlob_Other(); if (re != 0) { return re; } long t12 = CheckUtil::getcurTime(); // 等待分类 完成。 m_Classtask->waitComplate(); long t13 = CheckUtil::getcurTime(); // printf("=========== cls time %ld waite time %ld\n", t13 - t11, t13 - t12); BLobToDetResult(); return 0; } void worker(const unsigned char *img, int w, int startRow, int endRow, unsigned char *rowFlags) { for (int y = startRow; y < endRow; y++) { const unsigned char *p = img + (size_t)y * w; rowFlags[y] = 0; for (int x = 0; x < w; x++) { if (p[x] != 0) { rowFlags[y] = 1; break; } } } } int ImgCheckAnalysisy::GetALLBlob() { std::string strBaseLog = "Blob"; m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "image Blob Analysis Start"); if (m_pImageAllResult->AIMaskImg.empty()) { return 1; } unsigned char *pGrayErrordata = (unsigned char *)m_pImageAllResult->AIMaskImg.data; int width = m_pImageAllResult->AIMaskImg.cols; int height = m_pImageAllResult->AIMaskImg.rows; long t1 = CheckUtil::getcurTime(); memset(&blobs, 0x00, sizeof(ERROR_DOTS_BLOBS)); ERROR_DOTS_BLOBS blobs_v1; memset(&blobs_v1, 0x00, sizeof(ERROR_DOTS_BLOBS)); ERROR_DOTS_BLOBS blobs_big; memset(&blobs_big, 0x00, sizeof(ERROR_DOTS_BLOBS)); ERROR_DOTS_BLOBS blobs_big_2; memset(&blobs_big_2, 0x00, sizeof(ERROR_DOTS_BLOBS)); printf("=====>>>> GetALLBlob m_strCurDetChannel %s \n", m_strCurDetChannel.c_str()); if (m_strCurDetChannel == "CA") { printf("=====>>>>GetALLBlob USE CA %s \n", m_strCurDetChannel.c_str()); GetBlobs_ALL_New(&blobs_v1, pGrayErrordata, m_ImgBlobHFlagData, width, height, 15); m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "blob 1 num %d", blobs_v1.blobCount); if (blobs_v1.srcBlobCount >= _MAX_ERROR_DOT_BLOB) { GetBlobs_ALL_New(&blobs_big, pGrayErrordata, m_ImgBlobHFlagData, width, height, 200); m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "blob 2 num %d", blobs_big.blobCount); if (blobs_big.srcBlobCount >= _MAX_ERROR_DOT_BLOB) { m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "blob 3 num %d", blobs_big_2.blobCount); GetBlobs_ALL_New(&blobs_big_2, pGrayErrordata, m_ImgBlobHFlagData, width, height, 500); } } } else { GetBlobs_oneLabe(&blobs_v1, pGrayErrordata, m_ImgBlobHFlagData, width, height, 15); m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "blob 1 num %d", blobs_v1.blobCount); if (blobs_v1.srcBlobCount >= _MAX_ERROR_DOT_BLOB) { GetBlobs_oneLabe(&blobs_big, pGrayErrordata, m_ImgBlobHFlagData, width, height, 200); m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "blob 2 num %d", blobs_big.blobCount); if (blobs_big.srcBlobCount >= _MAX_ERROR_DOT_BLOB) { m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "blob 3 num %d", blobs_big_2.blobCount); GetBlobs_oneLabe(&blobs_big_2, pGrayErrordata, m_ImgBlobHFlagData, width, height, 500); } } } long t2 = CheckUtil::getcurTime(); PushBlob(&blobs, &blobs_big_2); PushBlob(&blobs, &blobs_big); PushBlob(&blobs, &blobs_v1); m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "blob ALL num %d", blobs.blobCount); // long t2 = CheckUtil::getcurTime(); // printf(" BLob time %ld \n", t2 - t1); if (false || DetImgInfo_shareP->bsaveProcessImg) { int font_face = cv::FONT_HERSHEY_SIMPLEX; double font_scale = 0.5; int thickness = 1; m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strBaseLog, "Save Tem Img"); cv::Mat tm = m_pImageAllResult->AIMaskImg.clone(); // 保持 mask 单通道,不再灰度转假彩 for (int i = 0; i < blobs.blobCount; i++) { cv::Rect roi; roi.x = blobs.blobTab[i].minx; roi.y = blobs.blobTab[i].miny; roi.width = blobs.blobTab[i].maxx - blobs.blobTab[i].minx + 1; roi.height = blobs.blobTab[i].maxy - blobs.blobTab[i].miny + 1; if (blobs.blobTab[i].ErrType == 0) { cv::rectangle(tm, roi, CheckUtil::GetDrawColor(tm, cv::Scalar(0, 0, 255))); } else { cv::rectangle(tm, roi, CheckUtil::GetDrawColor(tm, cv::Scalar(0, 255, 255))); } char buffer[128]; sprintf(buffer, "id:%d type:%d m %0.1f", i,blobs.blobTab[i].ErrType, blobs.blobTab[i].density); std::string text = buffer; cv::Point origin = cv::Point(roi.x, roi.y); cv::putText(tm, text, origin, font_face, font_scale, CheckUtil::GetDrawColor(tm, cv::Scalar(0, 255, 0)), thickness, 1, 0); // printf("type: %d %d %d %d %d %d %d\n", blobs.blobTab[i].ErrType, blobs.blobTab[i].area, blobs.blobTab[i].energy, roi.x, roi.y, roi.width, roi.height); } cv::imwrite(m_CheckResult_shareP->in_shareImage->strChannel + "_image_resize_blob.png", tm); } long te = CheckUtil::getcurTime(); m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "image Blob Analysis End use time %ld blob %ld ms", te - t1, t2 - t1); // getchar(); return 0; } int ImgCheckAnalysisy::AIMaskDet() { // 计算 mask 图片的一行 是否有 残点,用以加速 Blob的计算。 if (m_ImgBlobHFlagData) { delete[] m_ImgBlobHFlagData; m_ImgBlobHFlagData = NULL; } m_ImgBlobHFlagData = new unsigned char[m_pImageAllResult->detImg.rows]; memset(m_ImgBlobHFlagData, 0, sizeof(unsigned char) * m_pImageAllResult->detImg.rows); while (true) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::shared_ptr AImaskResult; { std::lock_guard lock(mtx_AIMaskImgBLobQueue); if (m_AIMaskImgBLobQueue.size() > 0) { AImaskResult = std::move(m_AIMaskImgBLobQueue.front()); m_AIMaskImgBLobQueue.pop(); // printf("size ============== %ld\n", m_AIMaskImgBLobQueue.size()); } } // 有AI mask 的结果 if (AImaskResult) { // static int ss = 0; cv::Mat &outimg = *(AImaskResult->output); // cv::imwrite(std::to_string(ss++) + ".png", outimg); unsigned char *pGrayErrordata = (unsigned char *)outimg.data; int width = outimg.cols; int height = outimg.rows; int start_Y = AImaskResult->roi.y; if (cv::countNonZero(outimg != 0) != 0) { // #pragma omp parallel for for (int y = 0; y < height; y++) { if (m_ImgBlobHFlagData[y + start_Y] == 0) { unsigned char *p = pGrayErrordata + y * width; for (int x = 0; x < width; x++) { if (p[x] != 0) { m_ImgBlobHFlagData[y + start_Y] = 1; break; } } } } } } else { /* code */ if (m_AItask->isComplate()) { break; } } } // printf("===========================================1 \n"); m_AItask->waitComplate(); int rec = m_AItask->nresult; if (rec != CHECK_OK) { return rec; } // printf("===========================================2 \n"); return 0; } int ImgCheckAnalysisy::Contours() { // std::string strBaseLog = "Contours"; // // m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "Contours Start"); // cv::Mat detimg; // { // // 膨胀 // cv::Mat se = getStructuringElement(0, Size(5, 5)); // 构造矩形结构元素 // cv::dilate(m_pdetlog->temImgList[TEM_IMG_IDX_AImask], detimg, se); // } // std::vector> contours; // std::vector hierarchy; // cv::findContours(detimg, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE); // 只找最外层轮廓 // // cv::cvtColor(m_pdetlog->temImgList[TEM_IMG_IDX_SrcCrop], m_pdetlog->temImgList[TEM_IMG_IDX_DrawSrc], cv::COLOR_GRAY2BGR); // for (int i = 0; i < contours.size(); ++i) // { // 绘制所有轮廓 // cv::drawContours(m_pdetlog->temImgList[TEM_IMG_IDX_DrawSrc], contours, i, cv::Scalar(255, 0, 255)); // thickness为-1时为填充整个轮廓 // } // float fx = m_pdetlog->temImgList[TEM_IMG_IDX_Result].cols * 1.0f / m_pdetlog->temImgList[TEM_IMG_IDX_SrcCrop].cols; // float fy = m_pdetlog->temImgList[TEM_IMG_IDX_Result].rows * 1.0f / m_pdetlog->temImgList[TEM_IMG_IDX_SrcCrop].rows; // for (int i = 0; i < contours.size(); ++i) // { // for (int j = 0; j < contours.at(i).size(); ++j) // { // contours.at(i).at(j).x *= fx; // contours.at(i).at(j).y *= fy; // } // } // // cv::cvtColor(m_pdetlog->temImgList[TEM_IMG_IDX_Result], m_pdetlog->temImgList[TEM_IMG_IDX_Result], cv::COLOR_GRAY2BGR); // for (int i = 0; i < contours.size(); ++i) // { // 绘制所有轮廓 // cv::drawContours(m_pdetlog->temImgList[TEM_IMG_IDX_Result], contours, i, cv::Scalar(255, 0, 255)); // thickness为-1时为填充整个轮廓 // } // cv::imwrite("TEM_IMG_IDX_Result.png", m_pdetlog->temImgList[TEM_IMG_IDX_Result]); // cv::imwrite("deeeee.png", m_pdetlog->temImgList[TEM_IMG_IDX_Drawmask]); // getchar(); // m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "Contours End"); return 0; } int ImgCheckAnalysisy::CheckImgInit() { // 1、初始化 m_pDetResult->pQx_ErrorList = std::make_shared>(); m_pdetlog->Init(); m_pdetlog->addLogLevel = DET_LOG_LEVEL_3; memset(&blobs, 0, sizeof(ERROR_DOTS_BLOBS)); m_nCheckResultErrorCode = 0; m_Draw_qxImageResult.erase(m_Draw_qxImageResult.begin(), m_Draw_qxImageResult.end()); m_DetRoiList.Init(); return 0; } int ImgCheckAnalysisy::ConfigCheck(cv::Mat img) { m_pdetlog->AddCheckstr(PrintLevel_0, "start", "Config Check"); int re = CHECK_OK; if (img.empty()) { m_nErrorCode = CHECK_ERROR_CheckImg_Empty; m_pdetlog->AddCheckstr(PrintLevel_1, "Error", "check Img empty"); return m_nErrorCode; } // if (m_pCommonAnalysisyConfig->regionConfigArr.size() <= 0) // { // m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Error", "regionConfig Num = 0"); // m_nErrorCode = CHECK_ERROR_Config_Value; // return m_nErrorCode; // } m_pdetlog->AddCheckstr(PrintLevel_0, "Succ", "Config Check Succ"); return re; } int ImgCheckAnalysisy::AI_Detect_Thread(const cv::Mat &img, cv::Mat &ResultImg) { std::string cur_time_s = CheckUtil::getCurTimeHMS(); // 依次使用多个检测模型,各模型输出 mask 取并集作为最终 outmask; // Defect_QueXi 模型是否参与合并由基础参数 Merge_QueXi 开关控制 std::vector> pAIDetList; pAIDetList.push_back(AI_Factory->Defect); pAIDetList.push_back(AI_Factory->Defect_QueXi); // 与 pAIDetList 一一对应的模型名称,用于区分残点由哪个模型检出 std::vector pAIDetNameList; pAIDetNameList.push_back(AI_DET_MODEL_NAME_Defect); pAIDetNameList.push_back(AI_DET_MODEL_NAME_QueXi); // Defect_QueXi 模型是否参与最终结果并集(开关关闭时: 模型照常推理, 但结果不合并到 ResultImg) bool bMerge_QueXi = (m_pbaseCheckFunction != NULL) && m_pbaseCheckFunction->Merge_QueXi.bOpen; std::string strBaseLog = "AI_Detect"; m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "AI_Detect Start"); long t1, t2, t3; t1 = CheckUtil::getcurTime(); if (img.empty()) { m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strBaseLog, " input img empty error"); m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "AI_Detect End"); return 1; } ResultImg = cv::Mat::zeros(img.size(), CV_8UC1); // 每个模型单独保存一份全图 mask,便于后续追溯残点由哪个模型检出 m_pImageAllResult->AI_Model_MaskList.clear(); std::vector modelMaskList(pAIDetList.size()); for (size_t m = 0; m < pAIDetList.size(); m++) { modelMaskList[m] = cv::Mat::zeros(img.size(), CV_8UC1); } // 整图检测模式下,所有模型的 ROI 都是整幅检测图 const cv::Rect fullRoi(0, 0, img.cols, img.rows); t2 = CheckUtil::getcurTime(); // 模型实际输入图(整图 resize 到模型输入大小)缓存一份,供结果记录使用 cv::Mat firstModelInput; // 依次用多个模型做检测:整图 resize 到模型输入大小 -> 推理 -> 输出 mask resize 回原图大小, // 所有模型输出的 mask 取并集作为最终 outmask for (size_t m = 0; m < pAIDetList.size(); m++) { std::shared_ptr pAIDet = pAIDetList[m]; std::string strModelName = (m < pAIDetNameList.size()) ? pAIDetNameList[m] : std::string(AI_DET_MODEL_NAME_UnKnown); // 当前模型的 mask 是否合并进最终结果: Defect 模型始终合并; Defect_QueXi 由 Merge_QueXi 开关控制 bool bMergeModel = (strModelName != AI_DET_MODEL_NAME_QueXi) || bMerge_QueXi; int deal_image_width = pAIDet->input_0.width; int deal_image_height = pAIDet->input_0.height; if (deal_image_width <= 0 || deal_image_height <= 0) { m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strBaseLog, " %s model input size error [%d %d]", strModelName.c_str(), deal_image_width, deal_image_height); m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "AI_Detect End"); return 2; } // 1、整图 resize 到模型输入大小 cv::Mat detInputImg; cv::resize(img, detInputImg, cv::Size(deal_image_width, deal_image_height)); // 模型输入要求单通道;原图为彩色时按 BGR 顺序转灰度(与 imread(path,0) 的亮度一致) if (detInputImg.channels() == 3) { cv::cvtColor(detInputImg, detInputImg, cv::COLOR_BGR2GRAY); } else if (detInputImg.channels() == 4) { cv::cvtColor(detInputImg, detInputImg, cv::COLOR_BGRA2GRAY); } if (m == 0) { firstModelInput = detInputImg; } m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strBaseLog, " %s input img [%d*%d] -> model [%d*%d]", strModelName.c_str(), img.cols, img.rows, deal_image_width, deal_image_height); // 临时存图:保存模型实际输入图 if (DetImgInfo_shareP->bsaveProcessImg) { cv::imwrite(DetImgInfo_shareP->strChannel + "_AI_Det_" + strModelName + "_in.jpg", detInputImg); } // 2、整图推理 cv::Mat modelMask; int re = pAIDet->AIDet(detInputImg, modelMask); if (re != 0) { m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strBaseLog, " %s AIDet error %d", strModelName.c_str(), re); m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "AI_Detect End"); return re; } // 3、输出 mask resize 回原图大小(最近邻,保证 mask 像素值不被插值改变) cv::Mat modelMaskFull; if (!modelMask.empty()) { cv::resize(modelMask, modelMaskFull, img.size(), 0, 0, cv::INTER_NEAREST); if (bMergeModel) { // 多个模型的 mask 取并集 cv::bitwise_or(ResultImg, modelMaskFull, ResultImg); // 当前模型自己的 mask 单独记录一份(只记录参与并集的模型, 保证残点来源追溯与结果一致) cv::bitwise_or(modelMaskList[m], modelMaskFull, modelMaskList[m]); } else { // Merge_QueXi 开关关闭: 不合并、不推加速队列、不记录残点来源 m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, " %s Merge_QueXi off, mask not merged (pixel num %d)", strModelName.c_str(), cv::countNonZero(modelMaskFull)); } } // AI 检测残点图存图:每个模型的结果都存,并增加一级 模型名称 目录, // 用以区分该残点是由哪个模型检出的 // 目录结构:/home/aidlux/BOE/ZB_SOCKET/AI_Detect/<模型名称>/<产品ID>/ if (m_pbaseCheckFunction->saveImg.bSaveAIDetImg && !modelMaskFull.empty()) { std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + DetImgInfo_shareP->strImgProductID + "/"; CheckUtil::CreateDir(strSaveDir); std::string strSavePath_out = strSaveDir + cur_time_s + "_" + m_strCurDetChannel + "_" + strModelName + "_out.png"; CheckUtil::SaveImgAsync(strSavePath_out, modelMaskFull); } // 4、mask 结果放入队列,用于 AIMaskDet 加速 blob 计算(整图 ROI) // 只推入参与并集的 mask, 保证行标志与最终结果 mask 一致 if (bMergeModel && !modelMaskFull.empty()) { std::shared_ptr maskTask = std::make_shared(); maskTask->id = static_cast(m); maskTask->roi = fullRoi; maskTask->output = std::make_shared(modelMaskFull); std::lock_guard lock(mtx_AIMaskImgBLobQueue); m_AIMaskImgBLobQueue.push(maskTask); } } t3 = CheckUtil::getcurTime(); float mean_AI = (t3 - t2) / (float)pAIDetList.size(); m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strBaseLog, " AI Run Time: sum %ld pre %ld Run %ld mean One Full Img %f", t3 - t1, t2 - t1, t3 - t2, mean_AI); // 保存各模型的 全图 mask,并输出各模型检出的残点像素数量,便于追溯 for (size_t m = 0; m < modelMaskList.size(); m++) { std::shared_ptr temModelMask = std::make_shared(); temModelMask->strModelName = (m < pAIDetNameList.size()) ? pAIDetNameList[m] : std::string(AI_DET_MODEL_NAME_UnKnown); temModelMask->ModelMask = modelMaskList[m]; m_pImageAllResult->AI_Model_MaskList.push_back(temModelMask); m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, " Det Model: %s mask pixel num %d", temModelMask->strModelName.c_str(), cv::countNonZero(temModelMask->ModelMask)); } // 整图检测模式下所有模型共用同一个 ROI(整幅检测图),多个模型的 mask 已取并集, // 按整图 ROI 记录一条 AI 检测结果,供 GetAIDetImg 按缺陷中心点取到 AI 输入图与并集 mask bool bFind = false; for (auto &item : m_pImageAllResult->AI_Qx_MaskList) { if (item->roi == fullRoi) { cv::bitwise_or(item->AI_mask, ResultImg, item->AI_mask); bFind = true; break; } } if (!bFind) { std::shared_ptr temAIresult = std::make_shared(); temAIresult->roi = fullRoi; temAIresult->AI_inImg = firstModelInput; temAIresult->AI_mask = ResultImg.clone(); m_pImageAllResult->AI_Qx_MaskList.push_back(temAIresult); } m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "AI_Detect End"); if (m_pbaseCheckFunction->saveImg.bSaveAIDetImg ) { std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + DetImgInfo_shareP->strImgProductID + "/"; CheckUtil::CreateDir(strSaveDir); std::string strSavePath_in = strSaveDir + cur_time_s + "_" + m_strCurDetChannel + "_in.png"; CheckUtil::SaveImgAsync(strSavePath_in, m_CheckResult_shareP->cutSrcimg); } // 临时存图 if (DetImgInfo_shareP->bsaveProcessImg) { cv::imwrite(DetImgInfo_shareP->strChannel + "_AI_mask.png", ResultImg); } // getchar(); return 0; } int ImgCheckAnalysisy::AI_QX_Class_Thread() { m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "QX_Class", " Start"); std::shared_ptr pAIDet; // printf("=====>>>>AI_QX_Class_Thread m_strCurDetChannel %s \n", m_strCurDetChannel.c_str()); { // printf("=====>>>>AI_QX_Class_Thread USE CA %s \n", m_strCurDetChannel.c_str()); pAIDet = AI_Factory->Class; } int deal_image_width = pAIDet->input_0.width; int deal_image_height = pAIDet->input_0.height; const int totalTasks = blobs.blobCount; int submitted = 0; int completed = 0; int detblobIdx = 0; int clsnum = 0; // std::string str_Root = m_strRootPath + pDetConfig->strProductID + "_" + pDetConfig->strchannel + "_"; long t2 = CheckUtil::getcurTime(); while (completed < totalTasks) { // std::this_thread::sleep_for(std::chrono::milliseconds(1)); if (submitted < totalTasks) { ERROR_DOTS_BLOB_DATA *pblob = &blobs.blobTab[submitted]; if (pblob->ErrType == ERR_TYPE_2) { pblob->AIclasstype = CONFIG_QX_NAME_YiWu; submitted++; completed++; continue; } else { // 如果任务还没提交完,且当前处理任务数 < 2,提交新任务 if (runner->GetProcessingCount() < 10) { clsnum++; cv::Rect roi; roi.x = pblob->minx; roi.y = pblob->miny; roi.width = pblob->maxx - pblob->minx + 1; roi.height = pblob->maxy - pblob->miny + 1; std::shared_ptr task = std::make_shared(); task->id = submitted; int re = GetClassImg(m_pImageAllResult->detImg, task->input, roi, deal_image_width, deal_image_height); if (re == 0) { task->bclass = true; task->engine = pAIDet; runner->SubmitTask(task); } else { completed++; } submitted++; } } } // 尝试取结果 std::shared_ptr result; if (runner->PopResult(result)) { int temClass = result->cls_label; // 分类 label 映射:优先读映射配置文件(KK_cls.txt),读不到用默认顺序 int cls_num = GetClsTypeByLabel(temClass); std::string strclassName = AI_CLass_QX_NAME_Names[cls_num]; m_pdetlog->AddCheckstr(PrintLevel_1, "AI Class num", "%d = %s %f\n", cls_num, strclassName.c_str(), result->cls_score); // 分类存图 开启。 if (m_pbaseCheckFunction->saveImg.bSaveClsImg) { std::string saveimgpaht = ""; { saveimgpaht = m_strRootPath_KK_cls + std::to_string(cls_num) + "/" + std::to_string(CheckUtil::getcurTime()) + "_" + std::to_string(result->cls_score) + ".png"; } { m_pImageStorage->addImage(saveimgpaht, result->input); } } int configqx = AIClassTypeToConfigType(cls_num); ERROR_DOTS_BLOB_DATA *pblob = &blobs.blobTab[result->id]; pblob->AIclasstype = configqx; completed++; } else { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } // printf("=====>>>>AI_QX_Class_Thread clsnum %d \n", clsnum); return 0; } int ImgCheckAnalysisy::ThreadTask(int nId) { while (!m_bExit) { std::this_thread::sleep_for(std::chrono::milliseconds(20)); // 等待是否有任务 std::shared_ptr task = m_task.GetTask(); // 把任务发送给对应的任务处理函数进行处理 switch (task->taskname) { case Task_AI: TaskFun_AIDet(task); break; case Task_Class: TaskFun_QxClass(task); break; default: break; } } return 0; } int ImgCheckAnalysisy::ResizeImg() { cv::Size sz; sz.width = RESIZE_IMAGE_WIDTH; float fw = RESIZE_IMAGE_WIDTH * 1.0f / m_pImageAllResult->detImg.cols; sz.height = int(m_pImageAllResult->detImg.rows * fw); // 结果图画布取自"原图裁剪区",保持原图通道(彩色原图 -> 真实彩色,不再灰度转假彩); // ReJson 复测模式下 in_shareImage->img 本身就是裁剪图,直接用检测图兜底 cv::Mat srcCanvas; const cv::Mat &srcImg = m_CheckResult_shareP->in_shareImage->img; if (m_CheckResult_shareP->in_shareImage->Det_Mode != DET_MODE_ReJson && !srcImg.empty() && CheckUtil::RoiInImg(m_outer_roi, srcImg)) { srcCanvas = srcImg(m_outer_roi); } else { srcCanvas = m_pImageAllResult->detImg; } cv::resize(srcCanvas, m_pImageAllResult->resultImg, sz); m_CheckResult_shareP->resultimg = m_pImageAllResult->resultImg; m_pdetlog->AddCheckstr(PrintLevel_1, "showImg size", " [w %d,h %d]-> show img [w %d,h %d] ", m_outer_roi.width, m_outer_roi.height, sz.width, sz.height); return 0; } void ImgCheckAnalysisy::TaskFun_AIDet(std::shared_ptr task) { task->SetStatus(TaskStep_run); long t1, t2; // AI 推理生成 { t1 = CheckUtil::getcurTime(); int rec = AI_Detect_Thread(m_pImageAllResult->detImg, m_pImageAllResult->AIMaskImg); t2 = CheckUtil::getcurTime(); task->nresult = rec; } task->SetStatus(TaskStep_compate); } void ImgCheckAnalysisy::TaskFun_QxClass(std::shared_ptr task) { task->SetStatus(TaskStep_run); long t1, t2; // AI 推理生成 { t1 = CheckUtil::getcurTime(); int rec = AI_QX_Class_Thread(); t2 = CheckUtil::getcurTime(); task->nresult = rec; } task->SetStatus(TaskStep_compate); } int ImgCheckAnalysisy::Update_DetRoiList() { float fx = m_pImageAllResult->fscale_detToresult_x; float fy = m_pImageAllResult->fscale_detToresult_y; int num = 0; cv::Rect offsetroi = m_Crop_Roi_paramImg; if (m_pEdge_Align_Result && m_pEdge_Align_Result->buseOfft) { for (const auto ®ion : m_pCommonAnalysisyConfig->regionConfigArr) { if (m_pEdge_Align_Result->H.empty()) { m_DetRoiList.Update_1(region.basicInfo.pointArry, offsetroi, m_pEdge_Align_Result->offt_x, m_pEdge_Align_Result->offt_y, fx, fy); } else { m_DetRoiList.Update_Marit(region.basicInfo.pointArry, offsetroi, m_pEdge_Align_Result->H, fx, fy); } // m_DetRoiList.Update_1(region.basicInfo.pointArry, offsetroi, m_pEdge_Align_Result->offt_x, m_pEdge_Align_Result->offt_y, fx, fy); } } else { for (const auto ®ion : m_pCommonAnalysisyConfig->regionConfigArr) { m_DetRoiList.Update(region.basicInfo.pointArry, m_Crop_Roi_paramImg, fx, fy); } } m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "info", "roiList_Src roi num %ld", m_DetRoiList.roiList_Src.size()); // m_DetRoiList.print("m_DetRoiList"); return 0; } int ImgCheckAnalysisy::DrawResult_Step_1() { // std::cout << m_pBasicConfig->bDrawPreRoi << std::endl; if (m_pBasicConfig->bDrawPreRoi) { int n = 0; // std::cout << m_DetRoiList.roiList_Show.size() << std::endl; for (const auto &polygon : m_DetRoiList.roiList_Show) { // n++; // if (n == 1) // { // continue; // } // std::cout << polygon << std::endl; // 绘制多边形的边界(不填充),线宽为1;颜色按画布通道自适应 cv::polylines(m_CheckResult_shareP->resultimg, polygon, true, CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, cv::Scalar(128, 128, 255)), 1); // true表示闭合多边形 } // cv::imwrite(DetImgInfo_shareP->strChannel + "roi_src.png", m_pdetlog->temImgList[TEM_IMG_IDX_DrawSrc]); // cv::imwrite(DetImgInfo_shareP->strChannel + "roi_ss.png", m_pdetlog->temImgList[TEM_IMG_IDX_Result]); // getchar(); } float fs_resize_x = m_pImageAllResult->resultImg.cols * 1.0f / m_pImageAllResult->detImg.cols; float fs_resize_y = m_pImageAllResult->resultImg.rows * 1.0f / m_pImageAllResult->detImg.rows; // 绘制模板 pin 轮廓(绿色)与当前 pin 轮廓(红色) // Pin 绘制内容按当前检测方案选择: 新方案绘制拟合直线/缺失标记, 旧方案绘制反变换后的模板Pin std::vector> directDrawPinContours; const std::vector> *pTplDrawPinContours = &m_tplPinContoursCur; if (IsDirectPinMethod()) { // 新方案: 不做模板对比; 将两排Pin拟合直线段与缺失标记组织成 2点"轮廓", 复用下方循环缩放绘制(绿色) for (const auto &seg : m_pinFitLines) { directDrawPinContours.push_back({cv::Point(cvRound(seg.first.x), cvRound(seg.first.y)), cv::Point(cvRound(seg.second.x), cvRound(seg.second.y))}); } for (const auto &mark : m_pinMissMarks) { // 缺失位置: 十字标记(两条短线) directDrawPinContours.push_back({cv::Point(cvRound(mark.x - 6.0f), cvRound(mark.y)), cv::Point(cvRound(mark.x + 6.0f), cvRound(mark.y))}); directDrawPinContours.push_back({cv::Point(cvRound(mark.x), cvRound(mark.y - 6.0f)), cv::Point(cvRound(mark.x), cvRound(mark.y + 6.0f))}); } pTplDrawPinContours = &directDrawPinContours; } for (const auto &contour : *pTplDrawPinContours) { std::vector draw_pts; draw_pts.reserve(contour.size()); for (const auto &pt : contour) { draw_pts.emplace_back(cvRound(pt.x * fs_resize_x), cvRound(pt.y * fs_resize_y)); } cv::polylines(m_CheckResult_shareP->resultimg, draw_pts, true, CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, cv::Scalar(255, 255, 0)), 1); } for (const auto &contour : m_curPinContours) { std::vector draw_pts; draw_pts.reserve(contour.size()); for (const auto &pt : contour) { draw_pts.emplace_back(cvRound(pt.x * fs_resize_x), cvRound(pt.y * fs_resize_y)); } cv::polylines(m_CheckResult_shareP->resultimg, draw_pts, true, CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, cv::Scalar(255, 0, 255)), 1); } // 绘制m_inner_rroi(蓝色) { cv::Point2f vertices[4]; m_inner_rroi.points(vertices); std::vector draw_pts; draw_pts.reserve(4); for (int i = 0; i < 4; i++) { draw_pts.emplace_back(cvRound((vertices[i].x - m_Crop_Roi_paramImg.x) * fs_resize_x), cvRound((vertices[i].y - m_Crop_Roi_paramImg.y) * fs_resize_y)); } cv::polylines(m_CheckResult_shareP->resultimg, draw_pts, true, CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, cv::Scalar(255, 0, 0)), 1); } // cv::imwrite("sss.png", m_CheckResult_shareP->resultimg); return 0; } int ImgCheckAnalysisy::AIClassTypeToConfigType(int nAIQXType) { // 无对应缺陷类型时返回 CONFIG_QX_NAME_count(缺陷列表已移除 other) int resultError_type = AI_CLass_QX_NAME_Panel; switch (nAIQXType) { case AI_CLass_QX_NAME_PIN_PoSun: resultError_type = CONFIG_QX_NAME_PIN_PoSun; break; case AI_CLass_QX_NAME_PIN_QueShi: resultError_type = CONFIG_QX_NAME_PIN_QueShi; break; case AI_CLass_QX_NAME_BTB_DuanLie: resultError_type = CONFIG_QX_NAME_BTB_DuanLie; break; case AI_CLass_QX_NAME_LianXi: resultError_type = CONFIG_QX_NAME_LianXi; break; case AI_CLass_QX_NAME_QueXi: resultError_type = CONFIG_QX_NAME_QueXi; break; case AI_CLass_QX_NAME_YiWu: resultError_type = CONFIG_QX_NAME_YiWu; break; case AI_CLass_QX_NAME_C_BianXing: resultError_type = CONFIG_QX_NAME_C_BianXing; break; case AI_CLass_QX_NAME_Panel: resultError_type = CONFIG_QX_NAME_Panel; break; default: break; } return resultError_type; } int ImgCheckAnalysisy::UpdateImgageScale() { if (m_pBasicConfig->fImage_Scale_x > 0 && m_pBasicConfig->fImage_Scale_x < 1 && m_pBasicConfig->fImage_Scale_y > 0 && m_pBasicConfig->fImage_Scale_y < 1) { m_fImgage_Scale_X = m_pBasicConfig->fImage_Scale_x; m_fImgage_Scale_Y = m_pBasicConfig->fImage_Scale_y; } return 0; } // 当前 Pin 检测方案是否为"直接分析"(新方案): 由参数 pinDet.Det_Method 在运行时决定 // "line_fitting" -> 新方案(不做模板对比); 其它/未配置 -> 模板对比方案 bool ImgCheckAnalysisy::IsDirectPinMethod() const { if (m_pbaseCheckFunction == NULL) { return false; } return NormalizeDetMethodStr(m_pbaseCheckFunction->pinDet.Det_Method) == "line_fitting"; } // 当前 Pin 检测方案是否为"Pin 投影矩阵"方案: pinDet.Det_Method == "Pin_Matrix"(忽略大小写/空白) // 该方案与 BTB_Marix 一样做模板 Pin 对比, 区别只在投影矩阵由 Pin 自身拟合(上下两排分别计算) bool ImgCheckAnalysisy::IsPinMatrixMethod() const { if (m_pbaseCheckFunction == NULL) { return false; } return NormalizeDetMethodStr(m_pbaseCheckFunction->pinDet.Det_Method) == "pin_matrix"; } // Pin_Matrix 方案: 过滤当前帧上明显的误检 blob(按模板 Pin 的包围框尺寸) // 以模板 Pin 外接矩形的中位宽/高为基准(按当前/模板外框面积比缩放), 当前 blob 的宽/高需落在 [0.4, 2.5] 倍范围内; // 模板 Pin 少于 3 个时不做过滤。 int ImgCheckAnalysisy::FilterCurPinBlobsByTplSize() { if (m_curPinContours.empty() || m_tplPinContours.size() < 3) { return 0; } std::vector ws; std::vector hs; ws.reserve(m_tplPinContours.size()); hs.reserve(m_tplPinContours.size()); for (size_t i = 0; i < m_tplPinContours.size(); i++) { cv::Rect r = cv::boundingRect(m_tplPinContours[i]); if (r.width > 0 && r.height > 0) { ws.push_back(r.width); hs.push_back(r.height); } } if (ws.size() < 3) { return 0; } std::sort(ws.begin(), ws.end()); std::sort(hs.begin(), hs.end()); float medW = static_cast(ws[ws.size() / 2]); float medH = static_cast(hs[hs.size() / 2]); // 模板/当前外框面积比 -> 尺寸基准缩放(同一相机一般约为 1) const double tplArea = static_cast(m_tplOuterRect.size.width) * m_tplOuterRect.size.height; const double curArea = static_cast(m_outer_rroi.size.width) * m_outer_rroi.size.height; if (tplArea > 0.0 && curArea > 0.0) { const float scaleRef = static_cast(std::sqrt(curArea / tplArea)); medW *= scaleRef; medH *= scaleRef; } const float kMinRatio = 0.4f; // 尺寸下限比例 const float kMaxRatio = 2.5f; // 尺寸上限比例 const size_t nBefore = m_curPinContours.size(); for (size_t i = m_curPinContours.size(); i-- > 0;) { cv::Rect r = cv::boundingRect(m_curPinContours[i]); const bool bSizeOk = (r.width > 0 && r.height > 0) && (r.width >= medW * kMinRatio) && (r.width <= medW * kMaxRatio) && (r.height >= medH * kMinRatio) && (r.height <= medH * kMaxRatio); if (!bSizeOk) { m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Matrix", "filter suspicious pin blob idx %d size[%d %d] (tpl median[%.1f %.1f])", static_cast(i), r.width, r.height, medW, medH); m_curPinContours.erase(m_curPinContours.begin() + static_cast(i)); } } if (m_curPinContours.size() != nBefore) { m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Matrix", "filter pin blobs: %ld -> %ld (tpl median size [%.1f %.1f])", static_cast(nBefore), static_cast(m_curPinContours.size()), medW, medH); } return 0; } // Det_Method == "Pin_Matrix": 与 BTB_Marix 一样做"模板 Pin vs 当前 Pin"的逐个比对(相交 -> 最近 -> dx/dy 超限), // 区别只在投影矩阵的来源: BTB_Marix 用内/外定位矩形求仿射, 本方案直接由 Pin 拟合: // 1) 内/外定位仿射只作为匹配初值(模板 Pin -> 当前检测图), 最终矩阵由 Pin 决定; // 2) 模板 Pin 按当前帧内定位矩形的长边法线分成上下两排, 每排独立估计一个相似变换(旋转+等比缩放+平移): // 一排 Pin 近似共线, 6 自由度全仿射在垂直排方向上病态, 因此用 4 自由度相似变换; // 3) 估计准则 = "模板 Pin 投影后与当前 Pin 重合的数量最多": 先固定初值的旋转/缩放做平移投票, // 再按最近邻一对一匹配 + RANSAC 拟合迭代求精, 取重合数最多的解; // 4) 某排可匹配 Pin 少于 2 个(无法拟合)时, 该排退回内/外定位仿射投影, 保证缺失 Pin 的 roi 仍能落位。 // 输出的 m_tplPinContoursCur 即按每排矩阵投影后的模板 Pin 轮廓(当前检测图坐标系), 供后续比对/绘制使用。 int ImgCheckAnalysisy::ComputePinProjectionMats() { m_tplPinContoursCur.clear(); if (m_tplPinContours.empty()) { m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Matrix", "no template pin contours, skip"); return 0; } // 0、过滤明显误检 blob(仅本方案; line_fitting / BTB_Marix 行为不变) FilterCurPinBlobsByTplSize(); const cv::Rect tplBound = m_tplOuterRect.boundingRect(); auto getCenter = [](const std::vector &contour) -> cv::Point2f { cv::Rect r = cv::boundingRect(contour); return cv::Point2f(r.x + r.width * 0.5f, r.y + r.height * 0.5f); }; const int nTpl = static_cast(m_tplPinContours.size()); const int nCur = static_cast(m_curPinContours.size()); // 1、匹配初值: 内定位优先/外定位回退(仅用于建立对应关系, 不参与最终判断) const bool bUseInnerAlign = (m_tplInnerRect.size.width > 0 && m_tplInnerRect.size.height > 0 && m_inner_rroi.size.width > 0 && m_inner_rroi.size.height > 0); if (bUseInnerAlign) { CalAffineMats(m_tplInnerRect, m_inner_rroi); } else { CalAffineMats(m_tplOuterRect, m_outer_rroi); } // T0: 模板图坐标 -> detImg 坐标 cv::Mat T0; if (m_bAffineValid && !m_affine_tpl2cur.empty()) { T0 = m_affine_tpl2cur.clone(); T0.at(0, 2) -= m_outer_roi.x; T0.at(1, 2) -= m_outer_roi.y; } else { // 防御: 定位矩形异常时退化为"模板外框中心 -> 当前外框中心"的平移 const cv::Rect curOuter = m_outer_rroi.boundingRect(); const double tx = (curOuter.x + curOuter.width * 0.5) - (tplBound.x + tplBound.width * 0.5) - m_outer_roi.x; const double ty = (curOuter.y + curOuter.height * 0.5) - (tplBound.y + tplBound.height * 0.5) - m_outer_roi.y; T0 = (cv::Mat_(2, 3) << 1.0, 0.0, tx, 0.0, 1.0, ty); m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Matrix", "affine init invalid, fallback to bbox translation t(%.1f %.1f) px", tx, ty); } // 2、模板 Pin 中心(模板图坐标) + 用初值投影到 detImg(仅用于分排) std::vector tplCenters(nTpl); std::vector tplCentersDet(nTpl); for (int i = 0; i < nTpl; i++) { cv::Point2f c = getCenter(m_tplPinContours[i]); tplCenters[i] = cv::Point2f(c.x + static_cast(tplBound.x), c.y + static_cast(tplBound.y)); tplCentersDet[i] = ApplyAffine2D(T0, tplCenters[i]); } std::vector curCenters(nCur); for (int j = 0; j < nCur; j++) { curCenters[j] = getCenter(m_curPinContours[j]); } // 3、模板 Pin 分两排: 当前帧内定位矩形的长边法线(与整体偏移估计一致); 内定位无效时按 Pin 自身几何兜底 std::vector> rows; { cv::RotatedRect innerRectDet = m_inner_rroi; innerRectDet.center.x -= static_cast(m_outer_roi.x); innerRectDet.center.y -= static_cast(m_outer_roi.y); if (innerRectDet.size.width > 0 && innerRectDet.size.height > 0) { std::vector> sideIdx(2); for (int i = 0; i < nTpl; i++) { sideIdx[(PinRowSideOfRect(innerRectDet, tplCentersDet[i]) < 0.0f) ? 0 : 1].push_back(i); } if (!sideIdx[0].empty() && !sideIdx[1].empty()) { rows.push_back(sideIdx[0]); rows.push_back(sideIdx[1]); } } if (rows.empty()) { rows = SplitPinsIntoRows(tplCentersDet); m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Matrix", "inner rect unavailable, split pins by pin geometry instead"); } } // 4、重合判据: pin_x_offset(mm) 换算为像素(pin_x_offset 未配置时用 pin_y_offset) float matchRadius = 0.0f; if (m_pbaseCheckFunction != NULL) { if (m_pbaseCheckFunction->pinDet.pin_x_offset > 0.0f && m_fImgage_Scale_X > 0.0f) { matchRadius = m_pbaseCheckFunction->pinDet.pin_x_offset / m_fImgage_Scale_X; } else if (m_pbaseCheckFunction->pinDet.pin_y_offset > 0.0f && m_fImgage_Scale_Y > 0.0f) { matchRadius = m_pbaseCheckFunction->pinDet.pin_y_offset / m_fImgage_Scale_Y; } } if (matchRadius <= 0.0f) { matchRadius = 20.0f; // 未配置阈值时的默认匹配半径 } if (matchRadius < 2.0f) { matchRadius = 2.0f; } // 5、逐排拟合: 每排放到对应的当前 Pin 组里做"平移投票(固定初值旋转/缩放) -> 最近邻一对一匹配 -> RANSAC 相似变换", // 取"模板 Pin 投影后与当前 Pin 重合数最多"的解; 两排的对应关系取总重合数最大的配对(兼容上下颠倒) struct PinRowFit { cv::Mat M; // 模板图坐标 -> detImg 坐标 int nMatch = 0; // 投影后与当前 Pin 重合的数量 float radius = 0.0f; bool bFromPins = false; }; // 当前帧 Pin 也按同一内定位法线分两排; 内定位无效时按 Pin 自身几何兜底 std::vector> curRows; { cv::RotatedRect innerRectDet = m_inner_rroi; innerRectDet.center.x -= static_cast(m_outer_roi.x); innerRectDet.center.y -= static_cast(m_outer_roi.y); if (innerRectDet.size.width > 0 && innerRectDet.size.height > 0) { std::vector> sideIdx(2); for (int j = 0; j < nCur; j++) { sideIdx[(PinRowSideOfRect(innerRectDet, curCenters[j]) < 0.0f) ? 0 : 1].push_back(j); } if (!sideIdx[0].empty() && !sideIdx[1].empty()) { curRows = sideIdx; } } if (curRows.empty() && nCur > 0) { curRows = SplitPinsIntoRows(curCenters); } } std::vector allCurIdx(nCur); for (int j = 0; j < nCur; j++) { allCurIdx[j] = j; } m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Matrix", "pin rows: tpl %d cur %d", static_cast(rows.size()), static_cast(curRows.size())); const double initAngle = std::atan2(T0.at(1, 0), T0.at(0, 0)); auto bReasonableMat = [&](const cv::Mat &M) -> bool { if (M.empty() || M.rows != 2 || M.cols != 3) { return false; } const double a = M.at(0, 0), b = M.at(0, 1); const double c = M.at(1, 0), d = M.at(1, 1); if (a * d - b * c <= 0.0) { return false; // 不允许镜像/退化 } const double scale = std::sqrt(a * a + c * c); if (scale < 0.5 || scale > 2.0) { return false; } double dAng = std::atan2(c, a) - initAngle; while (dAng > CV_PI) { dAng -= 2.0 * CV_PI; } while (dAng < -CV_PI) { dAng += 2.0 * CV_PI; } return std::fabs(dAng) <= 30.0 * CV_PI / 180.0; }; // 用模板 Pin 组 rowIdx 与当前 Pin 候选组 curIdxList 拟合一个相似变换(模板图坐标 -> detImg 坐标) auto fitOneRow = [&](const std::vector &rowIdx, const std::vector &curIdxList, PinRowFit &fit) { const int nRow = static_cast(rowIdx.size()); const int nCurSub = static_cast(curIdxList.size()); fit = PinRowFit(); fit.M = T0.clone(); fit.radius = matchRadius; // 匹配半径不能超过同排相邻 Pin 间距的一半, 否则会错配到相邻 Pin { float minGap = 0.0f; for (int a = 0; a < nRow; a++) { for (int b = a + 1; b < nRow; b++) { float d = static_cast(cv::norm(tplCenters[rowIdx[a]] - tplCenters[rowIdx[b]])); if (minGap <= 0.0f || d < minGap) { minGap = d; } } } if (minGap > 0.0f && fit.radius > minGap * 0.5f) { fit.radius = minGap * 0.5f; } } if (fit.radius < 1.0f) { fit.radius = 1.0f; } if (nRow < 2 || nCurSub < 2) { return; } // 5.1) 平移投票: 固定初值的旋转/缩放, 取"投影后重合数最多"的平移 const double l00 = T0.at(0, 0), l01 = T0.at(0, 1); const double l10 = T0.at(1, 0), l11 = T0.at(1, 1); const cv::Point2f t0(static_cast(T0.at(0, 2)), static_cast(T0.at(1, 2))); auto countOverlap = [&](const cv::Mat &M) -> int { // 粗略重合计数(允许多对一): 只看"投影点附近是否有当前 Pin", 用于平移投票 int cnt = 0; for (int a = 0; a < nRow; a++) { cv::Point2f p = ApplyAffine2D(M, tplCenters[rowIdx[a]]); for (int k = 0; k < nCurSub; k++) { if (cv::norm(curCenters[curIdxList[k]] - p) <= fit.radius) { cnt++; break; } } } return cnt; }; int bestCnt = -1; double bestTDist = 1e12; cv::Point2f bestT = t0; for (int a = 0; a < nRow; a++) { const cv::Point2f &tp = tplCenters[rowIdx[a]]; const double lx = l00 * tp.x + l01 * tp.y; const double ly = l10 * tp.x + l11 * tp.y; for (int k = 0; k < nCurSub; k++) { const cv::Point2f &curPt = curCenters[curIdxList[k]]; const cv::Point2f t(static_cast(curPt.x - lx), static_cast(curPt.y - ly)); cv::Mat M = T0.clone(); M.at(0, 2) = t.x; M.at(1, 2) = t.y; const int cnt = countOverlap(M); const double tDist = cv::norm(t - t0); // 重合数最多; 并列时取离初值平移更近的(避免跨排/跨列错位) if (cnt > bestCnt || (cnt == bestCnt && tDist < bestTDist)) { bestCnt = cnt; bestT = t; bestTDist = tDist; } } } cv::Mat MCur = T0.clone(); MCur.at(0, 2) = bestT.x; MCur.at(1, 2) = bestT.y; // 5.2) 最近邻一对一匹配 + RANSAC 相似变换迭代求精, 取重合数最多的解 auto matchPins = [&](const cv::Mat &M, std::vector> &matches) -> int { std::vector>> cands; cands.reserve(static_cast(nRow) * static_cast(nCurSub)); for (int a = 0; a < nRow; a++) { cv::Point2f p = ApplyAffine2D(M, tplCenters[rowIdx[a]]); for (int k = 0; k < nCurSub; k++) { const int j = curIdxList[k]; const double d = cv::norm(curCenters[j] - p); if (d <= fit.radius) { cands.push_back(std::make_pair(d, std::make_pair(a, j))); } } } std::sort(cands.begin(), cands.end()); std::vector usedT(nRow, 0); std::vector usedC(nCur, 0); matches.clear(); for (size_t k = 0; k < cands.size(); k++) { const int a = cands[k].second.first; const int j = cands[k].second.second; if (usedT[a] || usedC[j]) { continue; } usedT[a] = 1; usedC[j] = 1; matches.push_back(cands[k].second); } return static_cast(matches.size()); }; std::vector> matches; int bestOverlap = matchPins(MCur, matches); cv::Mat bestM = MCur.clone(); for (int iter = 0; iter < 5 && bestOverlap < nRow; iter++) { if (static_cast(matches.size()) < 2) { break; } std::vector srcPts; std::vector dstPts; srcPts.reserve(matches.size()); dstPts.reserve(matches.size()); for (size_t k = 0; k < matches.size(); k++) { srcPts.push_back(tplCenters[rowIdx[matches[k].first]]); dstPts.push_back(curCenters[matches[k].second]); } // 一排 Pin 近似共线, 用 4 自由度相似变换(旋转+等比缩放+平移)避免全仿射病态 cv::Mat MNew = cv::estimateAffinePartial2D(srcPts, dstPts, cv::noArray(), cv::RANSAC, 3.0, 2000, 0.995, 10); if (!bReasonableMat(MNew)) { break; } const int cnt = matchPins(MNew, matches); if (cnt > bestOverlap) { bestOverlap = cnt; bestM = MNew.clone(); } MCur = MNew; } if (bestOverlap >= 2 && bReasonableMat(bestM)) { fit.M = bestM; fit.nMatch = bestOverlap; fit.bFromPins = true; } }; std::vector rowFits(rows.size()); if (rows.size() == 2 && curRows.size() == 2) { // 两侧都成功分排: 每排分别与两个当前排各拟合一次, 再取总重合数更大的配对(兼容上下颠倒) PinRowFit fits[2][2]; for (int a = 0; a < 2; a++) { for (int b = 0; b < 2; b++) { fitOneRow(rows[a], curRows[b], fits[a][b]); } } const int scoreDirect = fits[0][0].nMatch + fits[1][1].nMatch; const int scoreSwap = fits[0][1].nMatch + fits[1][0].nMatch; if (scoreSwap > scoreDirect) { rowFits[0] = fits[0][1]; rowFits[1] = fits[1][0]; m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Matrix", "row pairing swapped (direct %d vs swap %d)", scoreDirect, scoreSwap); } else { rowFits[0] = fits[0][0]; rowFits[1] = fits[1][1]; } } else { // 无法两侧都分排: 每排与全部当前 Pin 匹配 for (size_t r = 0; r < rows.size(); r++) { fitOneRow(rows[r], allCurIdx, rowFits[r]); } } for (size_t r = 0; r < rowFits.size(); r++) { if (!rowFits[r].bFromPins) { // 该排可匹配 Pin 不足(无法拟合), 退回内/外定位仿射投影, 保证缺失 Pin 的 roi 仍能落位 rowFits[r].M = T0.clone(); rowFits[r].nMatch = 0; } } // 6、按每排矩阵把模板 Pin 轮廓投影到 detImg 坐标系 std::vector rowOfPin(nTpl, -1); for (size_t r = 0; r < rows.size(); r++) { for (size_t k = 0; k < rows[r].size(); k++) { rowOfPin[rows[r][k]] = static_cast(r); } } m_tplPinContoursCur.resize(nTpl); for (int i = 0; i < nTpl; i++) { const cv::Mat &M = (rowOfPin[i] >= 0) ? rowFits[rowOfPin[i]].M : T0; std::vector mapped; mapped.reserve(m_tplPinContours[i].size()); for (size_t k = 0; k < m_tplPinContours[i].size(); k++) { const cv::Point2f srcPt(static_cast(m_tplPinContours[i][k].x + tplBound.x), static_cast(m_tplPinContours[i][k].y + tplBound.y)); const cv::Point2f dstPt = ApplyAffine2D(M, srcPt); mapped.emplace_back(cvRound(dstPt.x), cvRound(dstPt.y)); } m_tplPinContoursCur[i] = std::move(mapped); } // 7、日志: 每排的矩阵(旋转/缩放/平移)与重合数 for (size_t r = 0; r < rows.size(); r++) { const cv::Mat &M = rowFits[r].M; const double scale = std::sqrt(M.at(0, 0) * M.at(0, 0) + M.at(1, 0) * M.at(1, 0)); const double angle = std::atan2(M.at(1, 0), M.at(0, 0)) * 180.0 / CV_PI; m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Matrix", "row %d pins %d matched %d/%d scale %.4f angle %.2f deg t(%.1f %.1f) px %s", static_cast(r), static_cast(rows[r].size()), rowFits[r].nMatch, static_cast(rows[r].size()), scale, angle, M.at(0, 2), M.at(1, 2), rowFits[r].bFromPins ? "" : "(fallback to align affine)"); } m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Matrix", "tpl %d cur %d rows %d match radius %.1f px (pin_x_offset %.2f mm scale_x %.4f)", nTpl, nCur, static_cast(rows.size()), rowFits.empty() ? matchRadius : rowFits[0].radius, m_pbaseCheckFunction != NULL ? m_pbaseCheckFunction->pinDet.pin_x_offset : 0.0f, m_fImgage_Scale_X); return 0; } int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img) { m_Edge_DetConfig.Init(); m_Edge_DetConfig.strChannel = m_strCurDetChannel; m_Edge_DetConfig.strProductID = DetImgInfo_shareP->strImgProductID; m_Edge_DetConfig.pBaseCheckFunction = m_pbaseCheckFunction; m_Edge_DetConfig.bSaveResultImg = false; if (DetImgInfo_shareP->bsaveProcessImg) { m_Edge_DetConfig.bSaveResultImg = true; } // m_pbaseCheckFunction->print("Pin_Qx_Det"); Mat pin_mask; int re = m_Pin_QX_Det.GetPinMask(img, &m_Edge_DetConfig, pin_mask); // 获取当前pinmask vector> pin_contours; vector hierarchy; cv::findContours(pin_mask, pin_contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE); //缩放比例 const float pin_scale_x = (pin_mask.cols > 0) ? static_cast(img.cols) / pin_mask.cols : 0.0f; const float pin_scale_y = (pin_mask.rows > 0) ? static_cast(img.rows) / pin_mask.rows : 0.0f; m_curPinContours.clear(); m_curPinContours.reserve(pin_contours.size()); for (size_t i = 0; i < pin_contours.size(); i++) { std::vector mapped; mapped.reserve(pin_contours[i].size()); for (const auto &pt : pin_contours[i]) { mapped.emplace_back(cvRound(pt.x * pin_scale_x), cvRound(pt.y * pin_scale_y)); } m_curPinContours.push_back(mapped); } if (IsDirectPinMethod()) { // 新方案: 不做模板对比, 只分析检测Pin(自动分两排 -> 逐排拟合直线判偏移 + 相邻间距判缺失) Pin_Direct_Check(); } else // 根据模板Pin和当前Pin轮廓进行对比,判断当前有无缺失和偏移 { if (IsPinMatrixMethod()) { // Det_Method == "Pin_Matrix": 投影矩阵由当前帧 Pin 拟合(上下两排分别计算), // 把模板 Pin 投影到当前检测图坐标系; 后续比对与 BTB_Marix 完全一致 ComputePinProjectionMats(); } // 计算轮廓中心点(外接矩形中心) auto getCenter = [](const std::vector &contour) -> cv::Point2f { cv::Rect r = cv::boundingRect(contour); return cv::Point2f(r.x + r.width * 0.5f, r.y + r.height * 0.5f); }; const int nTpl = static_cast(m_tplPinContoursCur.size()); const int nCur = static_cast(m_curPinContours.size()); if (nTpl > 0) { // 1、统计模板 pin 中心与外包矩形(已反变换到当前检测图坐标系) std::vector tplCenters(nTpl); std::vector tplRects(nTpl); for (int i = 0; i < nTpl; i++) { tplCenters[i] = getCenter(m_tplPinContoursCur[i]); tplRects[i] = cv::boundingRect(m_tplPinContoursCur[i]); } // 2、当前 pin 中心与外包矩形 std::vector curCenters(nCur); std::vector curRects(nCur); for (int i = 0; i < nCur; i++) { curCenters[i] = getCenter(m_curPinContours[i]); curRects[i] = cv::boundingRect(m_curPinContours[i]); } std::vector curUsed(nCur, false); // 整体偏移估计与消除(阈值见文件顶部 PIN_SHIFT_* 常量): // Pin_Matrix 方案的投影矩阵已由 Pin 自身拟合(上下两排分别计算), 不需要二次偏移矫正 if (!IsPinMatrixMethod()) { // inner 定位 ROI 转到 detImg 坐标系(detImg 由 m_outer_roi 裁剪得到) cv::RotatedRect innerRectDet = m_inner_rroi; innerRectDet.center.x -= static_cast(m_outer_roi.x); innerRectDet.center.y -= static_cast(m_outer_roi.y); // 1) 用 inner ROI 的长边法线方向区分两组 pin: 法向坐标 < 0 为一排, >= 0 为另一排 std::vector> shiftGroups; std::vector shiftGroupNames; if (m_inner_rroi.size.width > 0 && m_inner_rroi.size.height > 0) { std::vector> sideIdx(2); for (int i = 0; i < nTpl; i++) { sideIdx[(PinRowSideOfRect(innerRectDet, tplCenters[i]) < 0.0f) ? 0 : 1].push_back(i); } // 两排各自至少 2 个 pin 才能独立可靠估计; 否则回退为整体估计 if (sideIdx[0].size() >= 2 && sideIdx[1].size() >= 2) { shiftGroups.push_back(sideIdx[0]); shiftGroupNames.push_back("upper row"); shiftGroups.push_back(sideIdx[1]); shiftGroupNames.push_back("lower row"); } else { m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check", "pin row split by inner roi invalid: upper %d lower %d, estimate as a whole", static_cast(sideIdx[0].size()), static_cast(sideIdx[1].size())); } } else { m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check", "inner roi invalid, pin offset estimate as a whole"); } if (shiftGroups.empty()) { // inner ROI 无效或无法分排: 回退为整体估计 std::vector allIdx(nTpl); for (int i = 0; i < nTpl; i++) { allIdx[i] = i; } shiftGroups.push_back(allIdx); shiftGroupNames.push_back("all"); } // 2) 逐排(逐组)估计整体位移并消除 if (nCur > 0) { for (size_t g = 0; g < shiftGroups.size(); g++) { const std::vector &group = shiftGroups[g]; const int nGroup = static_cast(group.size()); if (nGroup <= 0) { continue; } // 组内相邻 pin 最小间距: 用于限制整体偏移的量级(整体偏移不应接近一个 pin 间距, 否则无法与"错配一列"区分) float minGap = 0.0f; for (int a = 0; a < nGroup; a++) { for (int b = a + 1; b < nGroup; b++) { float d = static_cast(cv::norm(tplCenters[group[a]] - tplCenters[group[b]])); if (minGap <= 0.0f || d < minGap) { minGap = d; } } } // 2.1) 组内每个模板 pin 取最近检测 pin 的位移作为候选位移(最近检测 pin 必定存在, nCur > 0) std::vector disp(nGroup); for (int a = 0; a < nGroup; a++) { int nearestIdx = 0; double bestD = 1e12; for (int j = 0; j < nCur; j++) { double d = cv::norm(curCenters[j] - tplCenters[group[a]]); if (d < bestD) { bestD = d; nearestIdx = j; } } disp[a] = curCenters[nearestIdx] - tplCenters[group[a]]; } // 2.2) 网格投票找主位移(峰值并列时优先取量级更小的) const float binSize = PIN_SHIFT_BIN_SIZE_PX; std::map, int> hist; int bestCount = 0; float bestX = 0.0f; float bestY = 0.0f; for (int a = 0; a < nGroup; a++) { std::pair key(static_cast(std::lround(disp[a].x / binSize)), static_cast(std::lround(disp[a].y / binSize))); int cnt = ++hist[key]; float cx = key.first * binSize; float cy = key.second * binSize; if (cnt > bestCount || (cnt == bestCount && cx * cx + cy * cy < bestX * bestX + bestY * bestY)) { bestCount = cnt; bestX = cx; bestY = cy; } } // 2.3) 峰值邻域(±1 档)内的候选位移取均值, 同时统计支持度 double sumX = 0.0; double sumY = 0.0; int sumN = 0; for (int a = 0; a < nGroup; a++) { if (std::fabs(disp[a].x - bestX) <= binSize && std::fabs(disp[a].y - bestY) <= binSize) { sumX += disp[a].x; sumY += disp[a].y; sumN++; } } cv::Point2f rowShift(0.0f, 0.0f); if (sumN > 0) { rowShift.x = static_cast(sumX / sumN); rowShift.y = static_cast(sumY / sumN); } else { rowShift.x = bestX; rowShift.y = bestY; } // 2.4) 支持度达标 且 位移量级小于 pin 间距的限定比例 -> 判定为该排整体偏移 const double supportRatio = static_cast(sumN) / nGroup; const float shiftLen = static_cast(cv::norm(rowShift)); const bool bRowShift = (supportRatio >= PIN_SHIFT_MIN_SUPPORT_RATIO) && (minGap <= 0.0f || shiftLen < minGap * PIN_SHIFT_MAX_GAP_RATIO); if (bRowShift) { // 该排模板 pin 轮廓整体平移, 使其与检测 pin 一致 const int shiftX = cvRound(rowShift.x); const int shiftY = cvRound(rowShift.y); for (int a = 0; a < nGroup; a++) { const int i = group[a]; for (auto &pt : m_tplPinContoursCur[i]) { pt.x += shiftX; pt.y += shiftY; } // 与前面的统计口径保持一致: 中心/外包框由平移后的轮廓重新计算 tplCenters[i] = getCenter(m_tplPinContoursCur[i]); tplRects[i] = cv::boundingRect(m_tplPinContoursCur[i]); } m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "global offset removed(%s): template pins shifted dx %.2f dy %.2f mm (%.1f %.1f px), support %d/%d", shiftGroupNames[g].c_str(), rowShift.x * m_fImgage_Scale_X, rowShift.y * m_fImgage_Scale_Y, rowShift.x, rowShift.y, sumN, nGroup); } else if (bestCount > 0) { m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check", "global offset not applied(%s): dx %.1f dy %.1f px, support %d/%d (ratio %.2f, pin gap %.1f px)", shiftGroupNames[g].c_str(), rowShift.x, rowShift.y, sumN, nGroup, supportRatio, minGap); } } } } // 3、偏移阈值直接使用基础参数 pin_x_offset / pin_y_offset (mm),与换算为 mm 的偏移量直接比较 float offsetThX = 0.0f; // mm float offsetThY = 0.0f; // mm if (m_pbaseCheckFunction != NULL) { offsetThX = m_pbaseCheckFunction->pinDet.pin_x_offset; offsetThY = m_pbaseCheckFunction->pinDet.pin_y_offset; } // 上报缺失/偏移缺陷(缺陷类型可按需调整) auto reportPinError = [&](int pinIdx, const cv::Point2f &tplCenter, const std::string &reason, int nconfig_qx_type) { QX_ERROR_INFO_ pinErr; pinErr.Idx = static_cast(m_pDetResult->pQx_ErrorList->size()); cv::Rect r = cv::boundingRect(m_tplPinContoursCur[pinIdx]); pinErr.roi = r; pinErr.area = static_cast(cv::contourArea(m_tplPinContoursCur[pinIdx])); pinErr.JudgArea = pinErr.area * m_fImgage_Scale_X * m_fImgage_Scale_Y; int longSide = (r.width > r.height) ? r.width : r.height; int shortSide = (r.width < r.height) ? r.width : r.height; pinErr.flen = longSide * m_fImgage_Scale_X; pinErr.fbreadth = shortSide * m_fImgage_Scale_Y; pinErr.nconfig_qx_type = nconfig_qx_type; pinErr.qx_name = CONFIG_QX_NAME_Names[nconfig_qx_type]; pinErr.detRegionidxList.push_back(0); pinErr.detlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check", "pin %d center(%d,%d) %s", pinIdx, static_cast(tplCenter.x), static_cast(tplCenter.y), reason.c_str()); m_pDetResult->pQx_ErrorList->push_back(pinErr); }; int nMiss = 0; int nOffset = 0; // 4、逐个模板 pin 找与其相交且未被占用的当前 pin for (int i = 0; i < nTpl; i++) { int bestIdx = -1; double bestDist = 1e12; for (int j = 0; j < nCur; j++) { if (curUsed[j]) { continue; } // 完全与模板没有相交 -> 不算候选 cv::Rect inter = tplRects[i] & curRects[j]; if (inter.width <= 0 || inter.height <= 0) { continue; } float dx = tplCenters[i].x - curCenters[j].x; float dy = tplCenters[i].y - curCenters[j].y; double d = std::sqrt(static_cast(dx) * dx + static_cast(dy) * dy); if (d < bestDist) { bestDist = d; bestIdx = j; } } // 没有相交的当前 pin -> 缺失 if (bestIdx < 0) { nMiss++; reportPinError(i, tplCenters[i], "miss", CONFIG_QX_NAME_PIN_QueShi); m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "pin %d MISS", i); continue; } curUsed[bestIdx] = true; // x 或 y 方向偏移超过配置阈值 -> 偏移;偏移量按成像精度换算为 mm (mm = px * 成像精度) float dx_mm = (tplCenters[i].x - curCenters[bestIdx].x) * m_fImgage_Scale_X; float dy_mm = (tplCenters[i].y - curCenters[bestIdx].y) * m_fImgage_Scale_Y; float absDx = std::fabs(dx_mm); float absDy = std::fabs(dy_mm); bool bOffset = (absDx > offsetThX || absDy > offsetThY); if (bOffset) { nOffset++; char buf[64]; snprintf(buf, sizeof(buf), "offset dx %.2f dy %.2f mm", absDx, absDy); reportPinError(i, tplCenters[i], buf, CONFIG_QX_NAME_PIN_PianYi); } // 每个pin脚都打印dx/dy(mm),方便调整 pin_x_offset / pin_y_offset 参数 m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "pin %d %s dx %.2f dy %.2f mm", i, bOffset ? "OFFSET" : "OK", absDx, absDy); } m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "tpl %d cur %d miss %d offset %d", nTpl, nCur, nMiss, nOffset); } } if (m_Edge_DetConfig.bSaveResultImg) { // 画布优先用原图裁剪区(保持原图通道),ReJson 或异常时退回检测图 Mat showimg; const cv::Mat &srcImg = m_CheckResult_shareP->in_shareImage->img; if (m_CheckResult_shareP->in_shareImage->Det_Mode != DET_MODE_ReJson && !srcImg.empty() && CheckUtil::RoiInImg(m_outer_roi, srcImg)) { showimg = srcImg(m_outer_roi).clone(); } else { showimg = img.clone(); } if (showimg.size() != img.size()) { showimg = img.clone(); } cv::drawContours(showimg, m_curPinContours, -1, CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 0, 255)), 1); if (IsDirectPinMethod()) { // 新方案: 绘制两排Pin的拟合直线(绿); 缺失位置以黄色十字标记 for (const auto &seg : m_pinFitLines) { cv::line(showimg, cv::Point(cvRound(seg.first.x), cvRound(seg.first.y)), cv::Point(cvRound(seg.second.x), cvRound(seg.second.y)), CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 0)), 1); } for (const auto &mark : m_pinMissMarks) { cv::drawMarker(showimg, cv::Point(cvRound(mark.x), cvRound(mark.y)), CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 255)), cv::MARKER_CROSS, 16, 1); } } else { cv::drawContours(showimg, m_tplPinContoursCur, -1, CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 0)), 1); } imwrite("pin_contours.png", showimg); } return 0; } #if 0 // ----- 旧配准匹配方案(Pin_Direct_Match)已停用, 仅保留代码参考; 如需启用需恢复 m_tplPinContoursCur 成员 ----- // 新方案(开关 PIN_USE_DIRECT_MATCH = 1): 不做仿射变换, 直接对模板Pin与检测Pin做配准后对比 // 输入: m_tplPinContours(模板裁剪坐标系) / m_curPinContours(当前检测图坐标系) // 输出: m_tplPinContoursCur(配准到当前检测图坐标系的模板Pin轮廓) + 缺失/偏移缺陷上报 // 思路: // 1) 平移投票: 统计所有 "检测pin中心 - 模板pin中心" 的位移, 取出现次数最多的位移作为全局平移, // 即让最多数量Pin对重合的平移(对缺失pin/误检pin鲁棒, 最大程度一一对应); // 2) 由近到远贪心做一对一匹配, 并用匹配对的位移中值迭代细化全局平移(抗异常); // 3) 配不上检测pin的模板pin -> 缺失; 配上但 x/y 偏移超阈值的 -> 偏移。 int ImgCheckAnalysisy::Pin_Direct_Match() { // 计算轮廓中心点(外接矩形中心) auto getCenter = [](const std::vector &contour) -> cv::Point2f { cv::Rect r = cv::boundingRect(contour); return cv::Point2f(r.x + r.width * 0.5f, r.y + r.height * 0.5f); }; const int nTpl = static_cast(m_tplPinContours.size()); const int nCur = static_cast(m_curPinContours.size()); m_tplPinContoursCur.clear(); if (nTpl <= 0) { return 0; } std::vector tplCenters(nTpl); for (int i = 0; i < nTpl; i++) { tplCenters[i] = getCenter(m_tplPinContours[i]); } std::vector curCenters(nCur); for (int i = 0; i < nCur; i++) { curCenters[i] = getCenter(m_curPinContours[i]); } // 匹配半径: 取模板pin相邻最小间距的一半, 避免相邻pin被错配; pin数不足时用默认值 float minGap = 0.0f; for (int i = 0; i < nTpl; i++) { for (int j = i + 1; j < nTpl; j++) { float d = static_cast(cv::norm(tplCenters[i] - tplCenters[j])); if (minGap <= 0.0f || d < minGap) { minGap = d; } } } float matchRadius = (minGap > 0.0f) ? (minGap * 0.5f) : 20.0f; if (matchRadius < 2.0f) { matchRadius = 2.0f; } // 1、平移投票: 统计出现次数最多的 "检测pin - 模板pin" 位移(4px 一档, 取峰值邻域均值细化) cv::Point2f T(0.0f, 0.0f); if (nCur > 0) { const float binSize = 4.0f; std::map, int> hist; int bestCount = 0; float bestX = 0.0f; float bestY = 0.0f; for (int i = 0; i < nTpl; i++) { for (int j = 0; j < nCur; j++) { float dx = curCenters[j].x - tplCenters[i].x; float dy = curCenters[j].y - tplCenters[i].y; std::pair key(static_cast(std::lround(dx / binSize)), static_cast(std::lround(dy / binSize))); int cnt = ++hist[key]; float cx = key.first * binSize; float cy = key.second * binSize; // 峰值并列时优先取位移更小的(两图裁剪都以产品为中心,真实位移通常较小) if (cnt > bestCount || (cnt == bestCount && cx * cx + cy * cy < bestX * bestX + bestY * bestY)) { bestCount = cnt; bestX = cx; bestY = cy; } } } // 用峰值邻域(±1档)的所有位移取均值, 得到更精确的全局平移 double sumX = 0.0; double sumY = 0.0; int sumN = 0; for (int i = 0; i < nTpl; i++) { for (int j = 0; j < nCur; j++) { float dx = curCenters[j].x - tplCenters[i].x; float dy = curCenters[j].y - tplCenters[i].y; if (std::fabs(dx - bestX) <= binSize && std::fabs(dy - bestY) <= binSize) { sumX += dx; sumY += dy; sumN++; } } } if (sumN > 0) { T.x = static_cast(sumX / sumN); T.y = static_cast(sumY / sumN); } else { T.x = bestX; T.y = bestY; } } // 2、由近到远贪心做一对一匹配, 并用匹配对的位移中值迭代细化全局平移 std::vector matchOfTpl(nTpl, -1); for (int iter = 0; iter < 3; iter++) { // 收集满足匹配半径的候选对, 按距离升序 std::vector>> cands; // (距离, (模板idx, 检测idx)) cands.reserve(static_cast(nTpl) * static_cast(nCur)); for (int i = 0; i < nTpl; i++) { cv::Point2f pred = tplCenters[i] + T; // 配准后模板pin在当前检测图中的预测位置 for (int j = 0; j < nCur; j++) { double d = cv::norm(curCenters[j] - pred); if (d <= matchRadius) { cands.push_back(std::make_pair(d, std::make_pair(i, j))); } } } std::sort(cands.begin(), cands.end()); std::vector tplUsed(nTpl, 0); std::vector curUsed(nCur, 0); matchOfTpl.assign(nTpl, -1); std::vector resDisX; std::vector resDisY; for (size_t k = 0; k < cands.size(); k++) { int i = cands[k].second.first; int j = cands[k].second.second; if (tplUsed[i] || curUsed[j]) { continue; } tplUsed[i] = 1; curUsed[j] = 1; matchOfTpl[i] = j; resDisX.push_back(curCenters[j].x - (tplCenters[i].x + T.x)); resDisY.push_back(curCenters[j].y - (tplCenters[i].y + T.y)); } if (resDisX.empty()) { break; } // 用位移中值(抗异常)更新全局平移, 使匹配对最大程度重合 std::sort(resDisX.begin(), resDisX.end()); std::sort(resDisY.begin(), resDisY.end()); float mx = resDisX[resDisX.size() / 2]; float my = resDisY[resDisY.size() / 2]; T.x += mx; T.y += my; if (std::fabs(mx) < 0.5f && std::fabs(my) < 0.5f) { break; } } // 3、把模板pin轮廓按最终平移映射到当前检测图坐标系, 供匹配/绘制/上报使用 m_tplPinContoursCur.reserve(nTpl); for (int i = 0; i < nTpl; i++) { std::vector mapped; mapped.reserve(m_tplPinContours[i].size()); for (const auto &pt : m_tplPinContours[i]) { mapped.emplace_back(cvRound(pt.x + T.x), cvRound(pt.y + T.y)); } m_tplPinContoursCur.push_back(mapped); } // 4、偏移阈值由基础参数 pin_x_offset / pin_y_offset (mm) 换算为像素 float offsetThX = 0.0f; float offsetThY = 0.0f; if (m_pbaseCheckFunction != NULL) { if (m_fImgage_Scale_X > 0.0f) { offsetThX = m_pbaseCheckFunction->pinDet.pin_x_offset / m_fImgage_Scale_X; } if (m_fImgage_Scale_Y > 0.0f) { offsetThY = m_pbaseCheckFunction->pinDet.pin_y_offset / m_fImgage_Scale_Y; } } // 上报缺失/偏移缺陷(pin 位置取配准后的模板pin) auto reportPinError = [&](int pinIdx, const cv::Point2f &tplCenter, const std::string &reason, int nconfig_qx_type) { QX_ERROR_INFO_ pinErr; pinErr.Idx = static_cast(m_pDetResult->pQx_ErrorList->size()); cv::Rect r = cv::boundingRect(m_tplPinContoursCur[pinIdx]); pinErr.roi = r; pinErr.area = static_cast(cv::contourArea(m_tplPinContoursCur[pinIdx])); pinErr.JudgArea = pinErr.area * m_fImgage_Scale_X * m_fImgage_Scale_Y; int longSide = (r.width > r.height) ? r.width : r.height; int shortSide = (r.width < r.height) ? r.width : r.height; pinErr.flen = longSide * m_fImgage_Scale_X; pinErr.fbreadth = shortSide * m_fImgage_Scale_Y; pinErr.nconfig_qx_type = nconfig_qx_type; pinErr.qx_name = CONFIG_QX_NAME_Names[nconfig_qx_type]; pinErr.detRegionidxList.push_back(0); pinErr.detlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check", "pin %d center(%d,%d) %s", pinIdx, static_cast(tplCenter.x), static_cast(tplCenter.y), reason.c_str()); m_pDetResult->pQx_ErrorList->push_back(pinErr); }; int nMiss = 0; int nOffset = 0; for (int i = 0; i < nTpl; i++) { // 配不上检测pin的模板pin -> 缺失 if (matchOfTpl[i] < 0) { nMiss++; reportPinError(i, tplCenters[i] + T, "miss", CONFIG_QX_NAME_PIN_QueShi); m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "pin %d MISS", i); continue; } // x 或 y 方向偏移超过配置阈值 -> 偏移 int j = matchOfTpl[i]; float dx = curCenters[j].x - (tplCenters[i].x + T.x); float dy = curCenters[j].y - (tplCenters[i].y + T.y); float absDx = std::fabs(dx); float absDy = std::fabs(dy); if (absDx > offsetThX || absDy > offsetThY) { nOffset++; char buf[64]; snprintf(buf, sizeof(buf), "offset dx %.1f dy %.1f", absDx, absDy); reportPinError(i, tplCenters[i] + T, buf, CONFIG_QX_NAME_PIN_PianYi); m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "pin %d OFFSET dx %.1f dy %.1f", i, absDx, absDy); } } m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "direct match: shift(%.1f, %.1f) tpl %d cur %d miss %d offset %d", T.x, T.y, nTpl, nCur, nMiss, nOffset); return 0; } #endif // 新方案(Det_Method == "line_fitting"): 不做模板对比, 只针对检测Pin分析 // 1) 方向扫描(0~180°, 步长1°): 取 "两排Pin沿法线投影组内方差之和" 最小的角度作为Pin排方向(对图像旋转不敏感), // 再沿该法线投影, 在最大间隙处把Pin分成两排(两排插口; Pin不足4个则按一排处理); // 2) 每排做最小二乘直线拟合(cv::fitLine): pin中心到直线(沿法线方向)距离按成像精度换算为 mm, 超过 pin_y_offset(mm) 判 偏移; // 3) 每排内沿排方向排序: 相邻两pin中心连线距离按成像精度换算为 mm, 超过 pin_x_offset(mm) 判 两pin中间缺失一个pin。 int ImgCheckAnalysisy::Pin_Direct_Check() { // 计算轮廓中心点(外接矩形中心) auto getCenter = [](const std::vector &contour) -> cv::Point2f { cv::Rect r = cv::boundingRect(contour); return cv::Point2f(r.x + r.width * 0.5f, r.y + r.height * 0.5f); }; const int nCur = static_cast(m_curPinContours.size()); m_pinFitLines.clear(); m_pinMissMarks.clear(); if (nCur <= 0) { return 0; } std::vector curCenters(nCur); std::vector curRects(nCur); for (int i = 0; i < nCur; i++) { curCenters[i] = getCenter(m_curPinContours[i]); curRects[i] = cv::boundingRect(m_curPinContours[i]); } if (nCur < 2) { m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "direct check: pin %d < 2, skip", nCur); return 0; } // 阈值直接使用 mm 参数(判据按 mm 比较): pin_x_offset -> 相邻pin间距(缺失)阈值; pin_y_offset -> 到拟合直线距离(偏移)阈值; <=0 时不判该项 float offsetThX = 0.0f; // mm float offsetThY = 0.0f; // mm if (m_pbaseCheckFunction != NULL) { offsetThX = m_pbaseCheckFunction->pinDet.pin_x_offset; offsetThY = m_pbaseCheckFunction->pinDet.pin_y_offset; } // 沿给定方向投影排序, 在最大间隙处切分成两排(order[0..splitK] 一排, order[splitK+1..] 另一排) // 两排各至少2个pin; pin不足4个时无法可靠分排, 返回 -1(按一排整体处理) auto splitByNormal = [&](const cv::Point2f &nor, std::vector &proj, std::vector &order) -> int { order.resize(nCur); proj.resize(nCur); for (int i = 0; i < nCur; i++) { order[i] = i; proj[i] = curCenters[i].dot(nor); } std::sort(order.begin(), order.end(), [&](int a, int b) { return proj[a] < proj[b]; }); int splitK = -1; if (nCur >= 4) { float bestGap = -1.0f; for (int k = 1; k <= nCur - 3; k++) { float gap = proj[order[k + 1]] - proj[order[k]]; if (gap > bestGap) { bestGap = gap; splitK = k; } } } return splitK; }; // 组内投影方差之和: 正确排方向上两排pin各自近似共线, 沿法线投影最集中, 该值最小 auto scoreOfRowDir = [&](const std::vector &proj, const std::vector &order, int splitK) -> double { double score = 0.0; const int nGroup = (splitK >= 0) ? 2 : 1; for (int g = 0; g < nGroup; g++) { int b = (g == 0) ? 0 : (splitK + 1); int e = (g == 0) ? (splitK + 1) : nCur; double m = 0.0; for (int i = b; i < e; i++) { m += proj[order[i]]; } m /= (e - b); for (int i = b; i < e; i++) { double d = proj[order[i]] - m; score += d * d; } } return score; }; // 1、方向扫描 0~180°(1°一档), 找到让"两排沿法线投影组内方差之和"最小的排方向(考虑图像旋转) float bestTheta = 0.0f; if (nCur >= 4) { double bestScore = -1.0; std::vector proj; std::vector order; for (int deg = 0; deg < 180; deg++) { float th = static_cast(deg * CV_PI / 180.0); cv::Point2f nor(-std::sin(th), std::cos(th)); int splitK = splitByNormal(nor, proj, order); double score = scoreOfRowDir(proj, order, splitK); if (bestScore < 0.0 || score < bestScore) { bestScore = score; bestTheta = th; } } } else { // pin 太少无法分排, 用PCA主方向兜底(按一排处理) cv::Point2f meanPt(0.0f, 0.0f); for (int i = 0; i < nCur; i++) { meanPt += curCenters[i]; } meanPt.x /= nCur; meanPt.y /= nCur; double sxx = 0.0, sxy = 0.0, syy = 0.0; for (int i = 0; i < nCur; i++) { double dx = curCenters[i].x - meanPt.x; double dy = curCenters[i].y - meanPt.y; sxx += dx * dx; sxy += dx * dy; syy += dy * dy; } bestTheta = static_cast(0.5 * std::atan2(2.0 * sxy, sxx - syy)); } // 用最优排方向重新投影, 把Pin分成两排 cv::Point2f dirRow(static_cast(std::cos(bestTheta)), static_cast(std::sin(bestTheta))); cv::Point2f dirNor(-dirRow.y, dirRow.x); std::vector proj; std::vector order; int splitK = splitByNormal(dirNor, proj, order); std::vector> rows; if (splitK >= 0) { rows.push_back(std::vector(order.begin(), order.begin() + splitK + 1)); rows.push_back(std::vector(order.begin() + splitK + 1, order.end())); } else { rows.push_back(order); } m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check", "row dir %.1f deg rows %d th[gap %.2f line %.2f] mm", bestTheta * 180.0f / static_cast(CV_PI), static_cast(rows.size()), offsetThX, offsetThY); // 上报缺陷(roi/area 由调用处给出; grayDis 记录对应度量: 到直线距离 / 相邻间距, 单位mm) auto reportPinError = [&](const cv::Rect &roi, int area, double grayDis, int nconfig_qx_type, const std::string &detail) { QX_ERROR_INFO_ pinErr; pinErr.Idx = static_cast(m_pDetResult->pQx_ErrorList->size()); pinErr.roi = roi; pinErr.area = area; pinErr.JudgArea = pinErr.area * m_fImgage_Scale_X * m_fImgage_Scale_Y; int longSide = (roi.width > roi.height) ? roi.width : roi.height; int shortSide = (roi.width < roi.height) ? roi.width : roi.height; pinErr.flen = longSide * m_fImgage_Scale_X; pinErr.fbreadth = shortSide * m_fImgage_Scale_Y; pinErr.nconfig_qx_type = nconfig_qx_type; pinErr.qx_name = CONFIG_QX_NAME_Names[nconfig_qx_type]; pinErr.grayDis = grayDis; pinErr.detRegionidxList.push_back(0); pinErr.detlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check", "%s", detail.c_str()); m_pDetResult->pQx_ErrorList->push_back(pinErr); }; int nOffset = 0; int nMiss = 0; // 2、3、逐排: 拟合直线 -> 判偏移; 沿排排序 -> 判相邻缺失 for (size_t r = 0; r < rows.size(); r++) { const std::vector &rowIdx = rows[r]; const int nRow = static_cast(rowIdx.size()); if (nRow < 2) { m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check", "row %d pin num %d < 2, skip", static_cast(r), nRow); continue; } std::vector rowPts(nRow); for (int i = 0; i < nRow; i++) { rowPts[i] = curCenters[rowIdx[i]]; } // 最小二乘直线拟合: lineDir 单位方向向量, lineP0 直线上一点, lineNor 单位法线 cv::Vec4f linePara; cv::fitLine(rowPts, linePara, cv::DIST_L2, 0, 0.01, 0.01); cv::Point2f lineDir(linePara[0], linePara[1]); cv::Point2f lineP0(linePara[2], linePara[3]); cv::Point2f lineNor(-lineDir.y, lineDir.x); // 记录拟合线段(沿排方向延伸到该排两端, 供绘制/回溯) float tMin = 0.0f; float tMax = 0.0f; for (int i = 0; i < nRow; i++) { float t = (rowPts[i] - lineP0).dot(lineDir); if (i == 0 || t < tMin) { tMin = t; } if (i == 0 || t > tMax) { tMax = t; } } m_pinFitLines.push_back(std::make_pair(lineP0 + lineDir * tMin, lineP0 + lineDir * tMax)); // 2、pin中心到直线(沿法线方向)距离超过 pin_y_offset -> 偏移;距离按成像精度换算为 mm (mm = px * 成像精度) if (offsetThY > 0.0f) { for (int i = 0; i < nRow; i++) { // 垂直偏移向量(px)的分量分别换算为 mm 后求模 float distPx = (rowPts[i] - lineP0).dot(lineNor); float dx_mm = lineNor.x * distPx * m_fImgage_Scale_X; float dy_mm = lineNor.y * distPx * m_fImgage_Scale_Y; float absDist = std::sqrt(dx_mm * dx_mm + dy_mm * dy_mm); int idx = rowIdx[i]; bool bOffset = (absDist > offsetThY); if (bOffset) { nOffset++; char buf[96]; snprintf(buf, sizeof(buf), "pin %d offset to row line %.2f mm > %.2f mm", idx, absDist, offsetThY); reportPinError(curRects[idx], static_cast(cv::contourArea(m_curPinContours[idx])), absDist, CONFIG_QX_NAME_PIN_PianYi, buf); } // 每个pin脚都打印到拟合直线的距离(mm),方便调整 pin_y_offset 参数 m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "row %d pin %d %s line %.2f mm", static_cast(r), idx, bOffset ? "OFFSET" : "OK", absDist); } } // 3、沿排方向排序, 相邻两pin中心连线距离超过 pin_x_offset -> 两pin中间缺失一个pin if (offsetThX > 0.0f) { std::vector> tOrder; tOrder.reserve(nRow); for (int i = 0; i < nRow; i++) { tOrder.push_back(std::make_pair((rowPts[i] - lineP0).dot(lineDir), i)); } std::sort(tOrder.begin(), tOrder.end()); for (int i = 0; i + 1 < nRow; i++) { int ia = tOrder[i].second; int ib = tOrder[i + 1].second; // 相邻pin中心连线距离(px)的分量分别换算为 mm 后求模 cv::Point2f dv = rowPts[ib] - rowPts[ia]; float dx_mm = dv.x * m_fImgage_Scale_X; float dy_mm = dv.y * m_fImgage_Scale_Y; float gap = std::sqrt(dx_mm * dx_mm + dy_mm * dy_mm); int idxa = rowIdx[ia]; int idxb = rowIdx[ib]; bool bMiss = (gap > offsetThX); if (bMiss) { cv::Point2f mid = (rowPts[ia] + rowPts[ib]) * 0.5f; m_pinMissMarks.push_back(mid); // 缺失位置按两个相邻pin的平均尺寸上报 cv::Rect roiAvg = curRects[idxa]; roiAvg.width = (curRects[idxa].width + curRects[idxb].width + 1) / 2; roiAvg.height = (curRects[idxa].height + curRects[idxb].height + 1) / 2; cv::Rect roiMiss(cvRound(mid.x - roiAvg.width * 0.5f), cvRound(mid.y - roiAvg.height * 0.5f), roiAvg.width, roiAvg.height); int areaAvg = (static_cast(cv::contourArea(m_curPinContours[idxa])) + static_cast(cv::contourArea(m_curPinContours[idxb]))) / 2; nMiss++; char buf[96]; snprintf(buf, sizeof(buf), "miss between pin %d-%d gap %.2f mm > %.2f mm", idxa, idxb, gap, offsetThX); reportPinError(roiMiss, areaAvg, gap, CONFIG_QX_NAME_PIN_QueShi, buf); } // 每对相邻pin都打印间距(mm),方便调整 pin_x_offset 参数 m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "row %d pin %d-%d %s gap %.2f mm", static_cast(r), idxa, idxb, bMiss ? "MISS" : "OK", gap); } } } m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "direct check: pin %d rows %d miss %d offset %d", nCur, static_cast(rows.size()), nMiss, nOffset); return 0; } // 获取指定 残点(blob) 是由哪个(哪些)检测模型检出的 // 做法:对各模型自己输出的全图 mask,在 blob 的 roi 内统计非零像素; // 非零则说明该模型在该残点区域有检出。多个模型都检出时用 "+" 连接。 std::string ImgCheckAnalysisy::GetBlobModelName(const cv::Rect &roi) { std::string strModelName = ""; if (!m_pImageAllResult) { return AI_DET_MODEL_NAME_UnKnown; } for (auto &pModelMask : m_pImageAllResult->AI_Model_MaskList) { if (!pModelMask || pModelMask->ModelMask.empty()) { continue; } cv::Rect imgRoi = cv::Rect(0, 0, pModelMask->ModelMask.cols, pModelMask->ModelMask.rows); cv::Rect cutRoi = roi & imgRoi; if (cutRoi.width <= 0 || cutRoi.height <= 0) { continue; } cv::Mat roiMask = pModelMask->ModelMask(cutRoi); if (cv::countNonZero(roiMask) > 0) { if (strModelName.empty()) { strModelName = pModelMask->strModelName; } else { strModelName += AI_DET_MODEL_NAME_JOIN + pModelMask->strModelName; } } } if (strModelName.empty()) { strModelName = AI_DET_MODEL_NAME_UnKnown; } return strModelName; } int ImgCheckAnalysisy::BLobToDetResult() { long t1 = CheckUtil::getcurTime(); m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "BLobToDetResult", " Start old qx num %ld", m_pDetResult->pQx_ErrorList->size()); // 遍历每个检测blob for (int i = 0; i < blobs.blobCount; i++) { cv::Rect roi; roi.x = blobs.blobTab[i].minx; roi.y = blobs.blobTab[i].miny; roi.width = blobs.blobTab[i].maxx - blobs.blobTab[i].minx + 1; roi.height = blobs.blobTab[i].maxy - blobs.blobTab[i].miny + 1; int config_qx_type = blobs.blobTab[i].AIclasstype; if (config_qx_type < 0 || config_qx_type >= CONFIG_QX_NAME_count) { // AI 分类无对应缺陷类型(无效类别)时,不参与缺陷结果分析 m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "BLobToDetResult", "blob idx %d invalid AIclasstype %d skip", i, config_qx_type); continue; } float JudgArea = blobs.blobTab[i].JudgArea; float fsecondArea = JudgArea; std::string qx_name = CONFIG_QX_NAME_Names[config_qx_type]; // 追溯该残点(blob) 是由哪个检测模型检出的 std::string strModelName = GetBlobModelName(roi); if (true) { QX_ERROR_INFO_ temerror; temerror.roi = roi; temerror.Idx = m_pDetResult->pQx_ErrorList->size(); temerror.area = blobs.blobTab[i].area; temerror.JudgArea = JudgArea; temerror.JudgArea_second = fsecondArea; temerror.energy = blobs.blobTab[i].energy; temerror.flen = blobs.blobTab[i].len; temerror.fbreadth = blobs.blobTab[i].breadth; temerror.nconfig_qx_type = config_qx_type; temerror.qx_name = qx_name; temerror.strModelName = strModelName; temerror.maxValue = blobs.blobTab[i].maxValue; temerror.grayDis = blobs.blobTab[i].grayDis; temerror.density = blobs.blobTab[i].density; temerror.fUpIou = 0; temerror.whiteOrBlack = blobs.blobTab[i].whiteOrblack; { cv::Point pCenter; pCenter.x = roi.x + roi.width * 0.5; pCenter.y = roi.y + roi.height * 0.5; int nmaxregionIdx = 0; for (int iregion = 0; iregion < m_DetRoiList.roiList_Src.size(); iregion++) { const std::vector &polygon = m_DetRoiList.roiList_Src[iregion]; double result = cv::pointPolygonTest(polygon, pCenter, false); if (result < 0) { continue; } nmaxregionIdx = iregion; } temerror.detRegionidxList.push_back(nmaxregionIdx); } // { // cv::Point pCenter; // pCenter.x = roi.x + roi.width * 0.5; // pCenter.y = roi.y + roi.height * 0.5; // for (int iregion = 0; iregion < m_DetRoiList.roiList_Src.size(); iregion++) // { // const std::vector &polygon = m_DetRoiList.roiList_Src[iregion]; // double result = cv::pointPolygonTest(polygon, pCenter, false); // if (result < 0) // { // continue; // } // temerror.detRegionidxList.push_back(iregion); // } // if (temerror.detRegionidxList.size() <= 0) // { // temerror.detRegionidxList.push_back(0); // } // } m_pDetResult->pQx_ErrorList->push_back(temerror); m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "BLobToDetResult", "blob idx %d Qx Idx %d qx %d %s detModel %s roi[%d %d %d %d]", i, temerror.Idx, config_qx_type, qx_name.c_str(), strModelName.c_str(), roi.x, roi.y, roi.width, roi.height); // printf("- %s idx %d a %f v %d h %f l %f\n", DetImgInfo_shareP->strChannel.c_str(), i, JudgArea, blobs.blobTab[i].maxValue, blobs.blobTab[i].grayDis, flen); } } long t2 = CheckUtil::getcurTime(); m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "BLobToDetResult", " end qx num %ld use time %ld", m_pDetResult->pQx_ErrorList->size(), t2 - t1); return 0; } int ImgCheckAnalysisy::AI_Edge(const cv::Mat &img, cv::RotatedRect &outerRoi, cv::RotatedRect &innerRoi) { m_pdetlog->AddCheckstr(PrintLevel_0, "AI_Edge", "-------------------start--------------"); AI_Edge_Algin::DetConfig config; if (DetImgInfo_shareP->bsaveProcessImg) { config.bSaveResultImg = true; } config.strChannel = DetImgInfo_shareP->strChannel; config.strProductID = DetImgInfo_shareP->strImgProductID; config.pBaseCheckFunction = m_pbaseCheckFunction; int re = m_pAI_Edge_Algin.Detect(img, &config, m_pEdge_Align_Result); if (re != 0) { printf("AI_Edge Is Error = %d \n", re); } outerRoi = m_pEdge_Align_Result->bigroi; innerRoi = m_pEdge_Align_Result->smallroi; // getchar(); return re; } int ImgCheckAnalysisy::CalProductSize() { float fw = m_outer_roi.width * m_fImgage_Scale_X; float fh = m_outer_roi.height * m_fImgage_Scale_Y; fw = std::ceil(fw * 10) / 10; fh = std::ceil(fh * 10) / 10; m_CheckResult_shareP->productWidht_mm = fw; m_CheckResult_shareP->productHeight_mm = fh; m_pdetlog->AddCheckstr(PrintLevel_1, "Det ROI", " [w %d,h %d] (piexl) -> [w %f h %f](mm),scale %f %f", m_outer_roi.width, m_outer_roi.height, fw, fh, m_fImgage_Scale_X, m_fImgage_Scale_Y); return 0; } int ImgCheckAnalysisy::ImgPreDet() { // 计算产品尺寸 CalProductSize(); if (m_pbaseCheckFunction->saveImg.bSaveClsImg) { creatsavedir(); } // _RE_CutImg 裁剪图保持传图原图通道(彩色原图 -> 彩色裁剪图),不再直接使用灰度检测图; // ReJson 复测模式的输入本身就是裁剪图,以及 ROI 越界等异常情况,退回检测图 { const cv::Mat &srcImg = m_CheckResult_shareP->in_shareImage->img; if (m_CheckResult_shareP->in_shareImage->Det_Mode != DET_MODE_ReJson && !srcImg.empty() && CheckUtil::RoiInImg(m_outer_roi, srcImg)) { m_CheckResult_shareP->cutSrcimg = srcImg(m_outer_roi).clone(); } else { m_CheckResult_shareP->cutSrcimg = m_pImageAllResult->detImg; } } ResizeImg(); m_pImageAllResult->fscale_detToresult_x = m_pImageAllResult->resultImg.cols * 1.0f / m_pImageAllResult->detImg.cols; m_pImageAllResult->fscale_detToresult_y = m_pImageAllResult->resultImg.rows * 1.0f / m_pImageAllResult->detImg.rows; return 0; }