/* * @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 "AICommonDefine.h" #include "AlignShare.h" #include #include // 把手判定参数: // 每个非空的"把手区域"附近都应该有一个把手:凸起宽高与该区域外接矩形宽高的比值落在下面范围内才算匹配 #define HANDLE_SIZE_MIN_RATIO 0.8f #define HANDLE_SIZE_MAX_RATIO 1.2f // "附近"的搜索范围:把手区域外接矩形在宽/高方向各外扩该比例 #define HANDLE_NEAR_EXPAND_RATIO 5 // 把手缺失检测开关见 Function_Support_Det::bDetHandle(CheckConfigDefine.h) // 用于排序轮廓的比较函数 static bool compareContourAreas(const vector &contour1, const vector &contour2) { double i = contourArea(contour1); double j = contourArea(contour2); return (i > j); } // 2x3 仿射矩阵小工具:变换点、矩形外接、组合矩阵(检测图摆正用) static cv::Point2f MatAffinePoint(const cv::Mat &mat32, const cv::Point2f &pt) { if (mat32.empty() || mat32.rows != 2 || mat32.cols != 3) { return pt; } const double *m = mat32.ptr(0); return cv::Point2f((float)(m[0] * pt.x + m[1] * pt.y + m[2]), (float)(m[3] * pt.x + m[4] * pt.y + m[5])); } // 矩形四角经仿射变换后取外接矩形 static cv::Rect MatAffineRect(const cv::Mat &mat32, const cv::Rect &roi) { std::vector pts(4); pts[0] = MatAffinePoint(mat32, cv::Point2f((float)roi.x, (float)roi.y)); pts[1] = MatAffinePoint(mat32, cv::Point2f((float)(roi.x + roi.width), (float)roi.y)); pts[2] = MatAffinePoint(mat32, cv::Point2f((float)(roi.x + roi.width), (float)(roi.y + roi.height))); pts[3] = MatAffinePoint(mat32, cv::Point2f((float)roi.x, (float)(roi.y + roi.height))); float fminX = pts[0].x, fmaxX = pts[0].x, fminY = pts[0].y, fmaxY = pts[0].y; for (int i = 1; i < 4; i++) { if (pts[i].x < fminX) fminX = pts[i].x; if (pts[i].x > fmaxX) fmaxX = pts[i].x; if (pts[i].y < fminY) fminY = pts[i].y; if (pts[i].y > fmaxY) fmaxY = pts[i].y; } int x0 = cvFloor(fminX); int y0 = cvFloor(fminY); int x1 = cvCeil(fmaxX); int y1 = cvCeil(fmaxY); return cv::Rect(x0, y0, x1 - x0, y1 - y0); } // 组合仿射:先应用 mB 再应用 mA,即 mA ∘ mB,结果仍为 2x3 static cv::Mat MatAffineCompose(const cv::Mat &mA, const cv::Mat &mB) { if (mA.empty() || mA.rows != 2 || mA.cols != 3) { return mB.clone(); } if (mB.empty() || mB.rows != 2 || mB.cols != 3) { return mA.clone(); } cv::Mat hA = cv::Mat::eye(3, 3, CV_64F); cv::Mat hB = cv::Mat::eye(3, 3, CV_64F); mA.copyTo(hA(cv::Rect(0, 0, 3, 2))); mB.copyTo(hB(cv::Rect(0, 0, 3, 2))); cv::Mat hC = hA * hB; return hC(cv::Rect(0, 0, 3, 2)).clone(); } ImgCheckAnalysisy::ImgCheckAnalysisy() : m_pAI_Edge_Algin(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_TA_cls = "/home/aidlux/BOE/Cls/TA/"; m_strRootPath_CA_cls = "/home/aidlux/BOE/Cls/CA/"; 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.7.91"); } 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_TA_cls += m_strLastDate + "/"; m_strRootPath_CA_cls += m_strLastDate + "/"; for (int i = 0; i < AI_CLass_QX_NAME_count; i++) { CheckUtil::CreateDir(m_strRootPath_TA_cls + std::to_string(i) + "/"); CheckUtil::CreateDir(m_strRootPath_CA_cls + std::to_string(i) + "/"); } return 0; } void ImgCheckAnalysisy::SetTplInfo(const cv::RotatedRect &outerRect) { m_tplOuterRect = outerRect; } 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); } int ImgCheckAnalysisy::Adapt_Config(RotatedRect tplOuterRect, RotatedRect curOuterRect, Mat& det_img){ vector cur_vertices = CheckUtil::sort_vertices(curOuterRect); vector tpl_vertices = CheckUtil::sort_vertices(tplOuterRect); cv::Point2f src_pts[3] = { cur_vertices[0], // 对应左上 cur_vertices[1], // 对应右上 cur_vertices[2] // 对应左下 }; cv::Point2f dst_pts[3] = { tpl_vertices[0], // 左上 tpl_vertices[1], // 右上 tpl_vertices[2] // 左下 }; cv::Mat affine_mat = cv::getAffineTransform(src_pts, dst_pts); // 仿射变换 cv::warpAffine(det_img, det_img, affine_mat, det_img.size()); // 将m_outer_rroi和m_inner_rroi变换到模板图像坐标系下 auto transformRotatedRect = [&affine_mat](cv::RotatedRect &rrect) { const double *M = affine_mat.ptr(0); cv::Point2f pts[4]; rrect.points(pts); std::vector transformed_pts(4); for (int i = 0; i < 4; i++) { float x = static_cast(M[0] * pts[i].x + M[1] * pts[i].y + M[2]); float y = static_cast(M[3] * pts[i].x + M[4] * pts[i].y + M[5]); transformed_pts[i] = cv::Point2f(x, y); } rrect = cv::minAreaRect(transformed_pts); }; transformRotatedRect(m_outer_rroi); transformRotatedRect(m_inner_rroi); return 0; } // 应用内 OpenCV 线程池随首个调用线程(通常为先绑核的检测线程)初始化成单线程,parallel_for 实际串行, // 全分辨率 warp 要 150~200ms;这里把输出按行切条、用独立线程并行 warp, // 结果与整图 warpAffine 逐像素一致(已离线验证 maxdiff=0),耗时降到约 1/3 static void WarpAffineBandParallel(const cv::Mat &src, cv::Mat &dst, const cv::Mat &affine, const cv::Size &size, int nThreads = 8, int interp = cv::INTER_LINEAR, int borderType = cv::BORDER_REPLICATE) { dst.create(size, src.type()); nThreads = std::max(1, std::min(nThreads, size.height)); int bandH = (size.height + nThreads - 1) / nThreads; int nBands = (size.height + bandH - 1) / bandH; auto warpBand = [&](int b) { // 新建线程默认继承调用线程的 CPU 亲和性(检测线程已绑单核),这里放开到全核,让条带真正并行 cpu_set_t cpus; CPU_ZERO(&cpus); long ncpu = sysconf(_SC_NPROCESSORS_ONLN); for (long i = 0; i < ncpu; i++) { CPU_SET(i, &cpus); } pthread_setaffinity_np(pthread_self(), sizeof(cpus), &cpus); int y0 = b * bandH; int h = std::min(bandH, size.height - y0); // warpAffine 默认接收正向矩阵(src->dst)并内部求逆;条带只把输出平移项减去 y0 cv::Mat matBand = affine.clone(); matBand.at(1, 2) -= y0; cv::warpAffine(src, dst(cv::Rect(0, y0, size.width, h)), matBand, cv::Size(size.width, h), interp, borderType); }; std::vector threads; threads.reserve(nBands); int b = 0; try { for (; b < nBands; b++) { threads.emplace_back(warpBand, b); } } catch (const std::system_error &) { // 线程资源不足:剩余条带留在本线程串行补完 } for (auto &t : threads) { t.join(); } for (; b < nBands; b++) { warpBand(b); } } // 暗场复用明场定位:把字符串里的 "dark"(大小写不敏感)替换为 "bright";未命中返回空串 static std::string ReplaceDarkWithBright(const std::string &strIn) { const std::string strKey = ALIGN_REUSE_FOLLOWER_KEY; // "dark" const std::string strSrc = ALIGN_REUSE_SOURCE_KEY; // "bright" if (strIn.size() < strKey.size()) { return ""; } for (size_t i = 0; i + strKey.size() <= strIn.size(); i++) { size_t k = 0; for (; k < strKey.size(); k++) { if (::tolower((unsigned char)strIn[i + k]) != (unsigned char)strKey[k]) { break; } } if (k == strKey.size()) { std::string strOut = strIn; strOut.replace(i, strKey.size(), strSrc); return strOut; } } return ""; } // ===== 暗场复用明场定位结果(ALIGN_REUSE_* 配置) ===== // 取当前图的产品ID/相机名:优先用 ImageAllResult 关联的基础结果,兕底用送图信息 void ImgCheckAnalysisy::GetProductCameraStr(std::string &strProduct, std::string &strCamera) { strProduct = ""; strCamera = ""; if (m_pImageAllResult) { if (m_pImageAllResult->productBaseResult) { strProduct = m_pImageAllResult->productBaseResult->strproductName; } if (m_pImageAllResult->cameraBaseResult) { strCamera = m_pImageAllResult->cameraBaseResult->strCameraName; } } if (m_CheckResult_shareP && m_CheckResult_shareP->in_shareImage) { if (strProduct.empty()) { strProduct = m_CheckResult_shareP->in_shareImage->strImgProductID; } if (strCamera.empty()) { strCamera = m_CheckResult_shareP->in_shareImage->strCameraName; } } } // 发布本图定位结果(成功)或失败标记(供同产品其它相机复用) void ImgCheckAnalysisy::PublishAlignShare(bool bSucc) { std::string strProduct; std::string strCamera; GetProductCameraStr(strProduct, strCamera); if (strProduct.empty() || strCamera.empty()) { return; } if (bSucc) { if (!m_pEdge_Align_Result) { return; } cv::Size sz(0, 0); if (m_CheckResult_shareP && m_CheckResult_shareP->in_shareImage) { sz = m_CheckResult_shareP->in_shareImage->img.size(); } AlignShare::GetInstance().PublishSuccess(strProduct, strCamera, m_strCurDetChannel, m_pEdge_Align_Result, sz); } else { AlignShare::GetInstance().PublishFailed(strProduct, strCamera, m_strCurDetChannel); } } // 暗场相机不做定位模型:等待并复用明场相机的定位结果 // 返回 true 时:m_pEdge_Align_Result / m_outer_rroi / m_inner_rroi 已用明场结果填充(标签检测独立执行) // 返回 false:非复用相机,或复用失败(超时/源失败/无源图/尺寸不一致),调用方走自有定位 bool ImgCheckAnalysisy::TryReuseAlignFromSource(std::string &strSrcCamera, std::string &strSrcChannel, int &nWaitMs) { nWaitMs = -1; #if !ALIGN_REUSE_ENABLE return false; #else std::string strProduct; std::string strCamera; GetProductCameraStr(strProduct, strCamera); // 是否复用由相机名决定(明/暗场是相机属性):相机名必须能按 dark->bright 替换 std::string strSrcCam = ReplaceDarkWithBright(strCamera); if (strSrcCam.empty()) { return false; } // 通道名可能不带 dark/bright(同一面的明/暗场相机共用一个通道名),此时沿用本相机通道名; // 若带 dark 则同样替换,保证与源相机发布结果时的 key 一致 std::string strSrcChn = ReplaceDarkWithBright(m_strCurDetChannel); if (strSrcChn.empty()) { strSrcChn = m_strCurDetChannel; } strSrcCamera = strSrcCam; strSrcChannel = strSrcChn; if (!m_pImageAllResult || !m_pImageAllResult->productBaseResult || !m_pImageAllResult->productBaseResult->alignPushState) { m_pdetlog->AddCheckstr(PrintLevel_0, "Align reuse", "reuse from %s|%s failed(no push state), do align by self", strSrcCam.c_str(), strSrcChn.c_str()); return false; } m_pdetlog->AddCheckstr(PrintLevel_0, "Align reuse", "wait %s|%s (timeout %d ms)", strSrcCam.c_str(), strSrcChn.c_str(), ALIGN_REUSE_WAIT_TIMEOUT_MS); AlignShare &alignShare = AlignShare::GetInstance(); std::shared_ptr pSrc; cv::Size srcSize; const long t1 = CheckUtil::getcurTime(); bool bGot = false; bool bSucc = false; std::string strFailReason = "timeout"; while (true) { if (alignShare.TryGet(strProduct, strSrcCam, strSrcChn, bSucc, pSrc, srcSize)) { bGot = true; if (!bSucc) { strFailReason = "source align failed"; } break; } // 源图不会再来了(送图已结束,且源相机没有图):立即回退,不干等超时 if (!m_pImageAllResult->productBaseResult->alignPushState->MayStillCome(strSrcCam)) { strFailReason = "no source image"; break; } if (CheckUtil::getcurTime() - t1 > ALIGN_REUSE_WAIT_TIMEOUT_MS) { break; } std::this_thread::sleep_for(std::chrono::milliseconds(ALIGN_REUSE_POLL_MS)); } nWaitMs = (int)(CheckUtil::getcurTime() - t1); if (!bGot || !bSucc) { m_pdetlog->AddCheckstr(PrintLevel_0, "Align reuse", "reuse from %s|%s failed(wait %d ms, %s), do align by self", strSrcCam.c_str(), strSrcChn.c_str(), nWaitMs, strFailReason.c_str()); return false; } if (!pSrc || pSrc->bigroi.empty()) { m_pdetlog->AddCheckstr(PrintLevel_0, "Align reuse", "reuse from %s|%s failed(wait %d ms, src roi empty), do align by self", strSrcCam.c_str(), strSrcChn.c_str(), nWaitMs); return false; } // 图像尺寸必须与源图一致,否则源坐标系不可用 if (srcSize.width > 0 && srcSize.height > 0 && m_CheckResult_shareP && m_CheckResult_shareP->in_shareImage) { const cv::Mat &img = m_CheckResult_shareP->in_shareImage->img; if (img.cols != srcSize.width || img.rows != srcSize.height) { m_pdetlog->AddCheckstr(PrintLevel_0, "Align reuse", "reuse from %s|%s failed, size %dx%d != cur %dx%d, do align by self", strSrcCam.c_str(), strSrcChn.c_str(), srcSize.width, srcSize.height, img.cols, img.rows); return false; } } // 复用明场定位结果:ROI/掩码/映射都用明场的(标签检测由 CheckTagByBigMask 独立执行,不用明场的标签) m_pEdge_Align_Result = pSrc; m_outer_rroi = pSrc->bigroi[0]; if (!pSrc->smallroi.empty()) { m_inner_rroi = pSrc->smallroi[0]; } return true; #endif } 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_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); PublishAlignShare(false); // 本图不会产出定位结果,通知可能的等待方(暗场复用) m_nErrorCode = rec; m_nCheckResultErrorCode = m_nErrorCode; return m_nErrorCode; } long time_edge_s = CheckUtil::getcurTime(); m_pFuntion = GetChannelFuntion(m_strCurDetChannel); int nfunction = 0; if (m_pFuntion != NULL) { m_pdetlog->AddCheckstr(PrintLevel_0, "Detect function", "%s", m_pFuntion->GetInfo("").c_str()); } else { nfunction = 1; m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "m_pFuntion is NULL"); PublishAlignShare(false); // 本图不会产出定位结果,通知可能的等待方(暗场复用) m_nErrorCode = 22; m_nCheckResultErrorCode = m_nErrorCode; return m_nErrorCode; } /*AI 边缘定位(内外边缘)*/ // 暗场相机不做定位模型,等待复用明场相机的定位结果(见 ALIGN_REUSE_* 配置); // 非复用相机、或复用失败(源超时/源失败/无源图/尺寸不一致)时,仍走原有定位流程 int reedge = 0; int nAlignReuseWaitMs = -1; std::string strAlignSrcCamera; std::string strAlignSrcChannel; long time_edge_e; if (TryReuseAlignFromSource(strAlignSrcCamera, strAlignSrcChannel, nAlignReuseWaitMs)) { // 复用成功:m_pEdge_Align_Result / m_outer_rroi / m_inner_rroi 已用明场结果填充(标签检测独立执行,不复用明场标签) PublishAlignShare(true); time_edge_e = CheckUtil::getcurTime(); m_pdetlog->AddCheckstr(PrintLevel_0, "2、pre detect", "-------------------AI_Edge reuse from %s|%s--wait %d ms----succ---time %ld----\n", strAlignSrcCamera.c_str(), strAlignSrcChannel.c_str(), nAlignReuseWaitMs, time_edge_e - time_edge_s); // 定位模型不跑,但把手检测仍要执行:暗场图像 + 明场 bigmask { long handle_s = CheckUtil::getcurTime(); CheckHandleByBigMask(m_pEdge_Align_Result->bigmask); long handle_e = CheckUtil::getcurTime(); m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "handle detect use time %ld", handle_e - handle_s); } // 标签检测(独立步骤):暗场图像 + 明场 bigmask(不再沿用明场检出的标签) { long tag_s = CheckUtil::getcurTime(); int reTag = CheckTagByBigMask(m_CheckResult_shareP->in_shareImage->img, m_pEdge_Align_Result->bigmask); long tag_e = CheckUtil::getcurTime(); m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Tag", "tag detect use time %ld", tag_e - tag_s); if (reTag != 0) { m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "CheckTagByBigMask is error type = %d", reTag); m_nErrorCode = reTag; m_nCheckResultErrorCode = m_nErrorCode; return m_nErrorCode; } } } else { reedge = AI_Edge(m_CheckResult_shareP->in_shareImage->img, m_outer_rroi, m_inner_rroi, m_tag_roiList); if (reedge != 0) { m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "AI_Edge is error type = %d", reedge); PublishAlignShare(false); // 定位失败:通知可能的等待方(暗场复用),让其回退自行定位 m_nErrorCode = reedge; m_nCheckResultErrorCode = m_nErrorCode; return m_nErrorCode; } PublishAlignShare(true); 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); } /*生成检测图:用 m_outer_rroi(旋转矩形)确定产品区域,并把该区域旋转摆正后输出; 原图坐标 -> 检测图坐标 的映射存入 m_matSrc2Det / m_matDet2Src,后续坐标换算都走这对矩阵*/ cv::Mat image = m_CheckResult_shareP->in_shareImage->img; cv::Rect imgRect(0, 0, image.cols, image.rows); // 1、旋转矩形四角 -> 轴对齐矩形四角 的仿射(把产品摆正;用顶点计算,避开 RotatedRect 角度约定的歧义) std::vector vtx = CheckUtil::sort_vertices(m_outer_rroi); // 近似 左上/右上/左下/右下 int detW = std::max(1, cvRound(cv::norm(vtx[1] - vtx[0]))); int detH = std::max(1, cvRound(cv::norm(vtx[2] - vtx[0]))); cv::Point2f srcTri[3] = {vtx[0], vtx[1], vtx[2]}; cv::Point2f dstTri[3] = {cv::Point2f(0, 0), cv::Point2f((float)detW, 0), cv::Point2f(0, (float)detH)}; m_matSrc2Det = cv::getAffineTransform(srcTri, dstTri); cv::invertAffineTransform(m_matSrc2Det, m_matDet2Src); // 2、只对旋转矩形附近区域做 warp,提高效率;先裁到该区域,warp 矩阵同步做平移补偿 cv::Rect padRoi = m_outer_rroi.boundingRect() & imgRect; if (padRoi.width <= 0 || padRoi.height <= 0) { m_pdetlog->AddCheckstr(PrintLevel_1, "Error", "deskew roi error [%d %d %d %d]", padRoi.x, padRoi.y, padRoi.width, padRoi.height); m_nErrorCode = CHECK_ERROR_CheckImg_Empty; m_nCheckResultErrorCode = m_nErrorCode; return m_nErrorCode; } cv::Mat matPad = m_matSrc2Det.clone(); matPad.at(0, 2) += matPad.at(0, 0) * padRoi.x + matPad.at(0, 1) * padRoi.y; matPad.at(1, 2) += matPad.at(1, 0) * padRoi.x + matPad.at(1, 1) * padRoi.y; // 3、检测图:单通道灰度(彩色原图按 BGR 顺序转灰度,与 imread(path,0) 的亮度一致),按摆正矩阵重采样; // 彩色结果图在 ResizeImg 里按显示尺寸直接 warp(比全分辨率彩色 warp 省很多) m_deskewPadRoi = padRoi; cv::Mat colorPatch = image(padRoi); cv::Mat grayPatch; if (colorPatch.channels() == 1) { grayPatch = colorPatch; } else if (colorPatch.channels() == 3) { cv::cvtColor(colorPatch, grayPatch, cv::COLOR_BGR2GRAY); } else { cv::cvtColor(colorPatch, grayPatch, cv::COLOR_BGRA2GRAY); } WarpAffineBandParallel(grayPatch, m_pImageAllResult->detImg, matPad, cv::Size(detW, detH)); // 彩色原图时再输出一张同样摆正的彩色检测图:供裁剪图(_RE_CutImg)保存时保留原图通道 // (检测算法仍只用灰度 detImg) if (colorPatch.channels() == 3 || colorPatch.channels() == 4) { WarpAffineBandParallel(colorPatch, m_pImageAllResult->detColorImg, matPad, cv::Size(detW, detH)); } else { m_pImageAllResult->detColorImg.release(); } // 4、检测图坐标系下 m_outer_roi 即整张检测图;原图坐标系下 CutRoi 仍取旋转矩形外接区域(兼容旧语义/JSON) m_outer_roi = cv::Rect(0, 0, detW, detH); m_Crop_Roi_paramImg = m_outer_rroi.boundingRect() & imgRect; m_pImageAllResult->pDetResult->CutRoi = m_Crop_Roi_paramImg; m_pImageAllResult->pDetResult->Param_CropRoi = m_Crop_Roi_paramImg; // 检测图坐标 -> 原图坐标 的映射,供结果解析时取回缺陷小图/换算坐标 m_pImageAllResult->pDetResult->detToSrcMat = m_matDet2Src.clone(); // 5、参数(模板)坐标 -> 检测图坐标 的映射: // 有对齐时先把参数点映射到原图再摆正;无对齐时参数按产品坐标系处理(与摆正后的检测图同向), // 直接按旋转矩形中心平移(θ=0 时与旧的裁剪平移完全一致,旋转/平移变化时区域跟随产品) { cv::RotatedRect &rroi = m_outer_rroi; if (m_pEdge_Align_Result && m_pEdge_Align_Result->buseOfft) { if (!m_pEdge_Align_Result->H.empty()) { m_matParam2Det = MatAffineCompose(m_matSrc2Det, m_pEdge_Align_Result->H); } else { cv::Mat matOfft = (cv::Mat_(2, 3) << 1, 0, (double)m_pEdge_Align_Result->offt_x, 0, 1, (double)m_pEdge_Align_Result->offt_y); m_matParam2Det = MatAffineCompose(m_matSrc2Det, matOfft); } } else { m_matParam2Det = (cv::Mat_(2, 3) << 1, 0, -rroi.center.x + detW * 0.5, 0, 1, -rroi.center.y + detH * 0.5); } } long time_calan_e = CheckUtil::getcurTime(); m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", "-------------------------deskew--------------succ--- det [%d %d] src bbox [%d %d %d %d] time %ld----\n", detW, detH, m_Crop_Roi_paramImg.x, m_Crop_Roi_paramImg.y, m_Crop_Roi_paramImg.width, m_Crop_Roi_paramImg.height, time_calan_e - time_edge_s); /*多线程开启 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"); /* 检测支架偏移 */ { Function_Support_Det &supportDet = m_pFuntion->function.f_supportDet; if (supportDet.bOpen && supportDet.supportRect.size.width > 0 && supportDet.supportRect.size.height > 0) { cv::RotatedRect &inner = m_inner_rroi; cv::RotatedRect &support = supportDet.supportRect; // x、y 中心偏移 float dx = fabs(inner.center.x - support.center.x); float dy = fabs(inner.center.y - support.center.y); // 旋转偏移(矩形旋转 90 度等价,角度取最小差) float dangle = fabs(inner.angle - support.angle); if (dangle > 45.0f) { dangle = fabs(dangle - 90.0f); } bool bOffset = (dx > supportDet.x_offset) || (dy > supportDet.y_offset) || (dangle > supportDet.r_offset); // 缺失判定:内边缘面积小于支架模板面积的 75% float innerArea = inner.size.width * inner.size.height; float supportArea = support.size.width * support.size.height; bool bLoss = (supportArea > 0.0f) && (innerArea < supportArea * 0.75f); if (bOffset || bLoss) { QX_ERROR_INFO_ temerror; temerror.Idx = m_pDetResult->pQx_ErrorList->size(); // roi 在原图坐标系,按摆正仿射转换到检测图(detImg)坐标系 cv::Rect roi = MatAffineRect(m_matSrc2Det, inner.boundingRect()); roi &= cv::Rect(0, 0, m_pImageAllResult->detImg.cols, m_pImageAllResult->detImg.rows); temerror.roi = roi; temerror.area = roi.width * roi.height; temerror.JudgArea = roi.width * m_fImgage_Scale_X * roi.height * m_fImgage_Scale_Y; temerror.JudgArea_second = temerror.JudgArea; float w = inner.size.width; float h = inner.size.height; temerror.flen = (w > h ? w : h) * m_fImgage_Scale_X; temerror.fbreadth = (w > h ? h : w) * m_fImgage_Scale_Y; if (bLoss) { temerror.nconfig_qx_type = CONFIG_QX_NAME_support_loss; } else { temerror.nconfig_qx_type = CONFIG_QX_NAME_support_offset; } temerror.qx_name = CONFIG_QX_NAME_Names[temerror.nconfig_qx_type]; temerror.result = QX_RESULT_TYPE_NG; temerror.result_name = QX_RESULT_TYPE_Names[QX_RESULT_TYPE_NG]; // 计算支架中心所在的检测区域 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); m_pDetResult->pQx_ErrorList->push_back(temerror); m_pdetlog->AddCheckstr(PrintLevel_0, "支架检测", "%s %d roi [%d %d %d %d] dx %f dy %f dangle %f", temerror.qx_name.c_str(), temerror.Idx, roi.x, roi.y, roi.width, roi.height, dx, dy, dangle); } } } /* 把手缺失检测:bigmask 的凸起本身不算 NG,只有"参数里的某个把手区域附近找不到把手"才算 NG(每个缺失区域报一条) */ { if (m_bHandleDetSucc && m_bHandleExpect && !m_bHandleFound) { for (size_t iRegion = 0; iRegion < m_HandleRegionResultList.size(); iRegion++) { Handle_Region_Result_ ®ionResult = m_HandleRegionResultList[iRegion]; if (regionResult.bFound) { continue; } // 上报该把手区域的位置:参数(模板)坐标 -> 检测图(detImg)坐标 cv::Rect roi = MatAffineRect(m_matParam2Det, regionResult.boxRegion); roi &= cv::Rect(0, 0, m_pImageAllResult->detImg.cols, m_pImageAllResult->detImg.rows); if (roi.width <= 0 || roi.height <= 0) { continue; } QX_ERROR_INFO_ temerror; temerror.Idx = m_pDetResult->pQx_ErrorList->size(); temerror.roi = roi; temerror.area = roi.width * roi.height; temerror.JudgArea = roi.width * m_fImgage_Scale_X * roi.height * m_fImgage_Scale_Y; temerror.JudgArea_second = temerror.JudgArea; float w = roi.width; float h = roi.height; temerror.flen = (w > h ? w : h) * m_fImgage_Scale_X; temerror.fbreadth = (w > h ? h : w) * m_fImgage_Scale_Y; temerror.nconfig_qx_type = CONFIG_QX_NAME_handle_loss; temerror.qx_name = CONFIG_QX_NAME_Names[CONFIG_QX_NAME_handle_loss]; temerror.result = QX_RESULT_TYPE_NG; temerror.result_name = QX_RESULT_TYPE_Names[QX_RESULT_TYPE_NG]; // 计算把手区域中心所在的检测区域 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); m_pDetResult->pQx_ErrorList->push_back(temerror); m_pdetlog->AddCheckstr(PrintLevel_0, "把手检测", "%s %d region %d roi [%d %d %d %d]", temerror.qx_name.c_str(), temerror.Idx, regionResult.nIdx, roi.x, roi.y, roi.width, roi.height); } } } /* Tag检测 */ { for (const auto &tagRoi : m_tag_roiList) { // tag roi 在原图坐标系,按摆正仿射转换到检测图(detImg)坐标系 cv::Rect roi = MatAffineRect(m_matSrc2Det, tagRoi.boundingRect()); roi &= cv::Rect(0, 0, m_pImageAllResult->detImg.cols, m_pImageAllResult->detImg.rows); if (roi.width <= 0 || roi.height <= 0) { continue; } QX_ERROR_INFO_ temerror; temerror.Idx = m_pDetResult->pQx_ErrorList->size(); temerror.roi = roi; temerror.area = roi.width * roi.height; temerror.JudgArea = roi.width * m_fImgage_Scale_X * roi.height * m_fImgage_Scale_Y; temerror.JudgArea_second = temerror.JudgArea; float tw = tagRoi.size.width; float th = tagRoi.size.height; temerror.flen = (tw > th ? tw : th) * m_fImgage_Scale_X; temerror.fbreadth = (tw > th ? th : tw) * m_fImgage_Scale_Y; temerror.nconfig_qx_type = CONFIG_QX_NAME_cell_tag; temerror.qx_name = CONFIG_QX_NAME_Names[CONFIG_QX_NAME_cell_tag]; temerror.result = QX_RESULT_TYPE_NG; temerror.result_name = QX_RESULT_TYPE_Names[QX_RESULT_TYPE_NG]; // 计算标签中心所在的检测区域 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); m_pDetResult->pQx_ErrorList->push_back(temerror); m_pdetlog->AddCheckstr(PrintLevel_0, "Tag检测", "tag %d roi [%d %d %d %d] -> NG", temerror.Idx, roi.x, roi.y, roi.width, roi.height); } } 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::shared_ptr pAIDet; // printf("=====>>>>AI_Detect_Thread m_strCurDetChannel %s \n", m_strCurDetChannel.c_str()); { // printf("=====>>>>AI_Detect_Thread USE CA %s \n", m_strCurDetChannel.c_str()); pAIDet = AI_Factory->Defect; } 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(); SmallRoiList.clear(); SmallRoiList.erase(SmallRoiList.begin(), SmallRoiList.end()); cv::Rect cutRoi; cutRoi.x = 0; cutRoi.y = 0; cutRoi.width = img.cols - 0; cutRoi.height = img.rows - 0; int deal_image_width = pAIDet->input_0.width; int deal_image_height = pAIDet->input_0.height; int re = CheckUtil::cutSmallImg(img, SmallRoiList, cutRoi, deal_image_width, deal_image_height, 0, 0); m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strBaseLog, " cutSmallImg Num %d", SmallRoiList.size()); if (re != 0 || SmallRoiList.size() <= 0) { m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strBaseLog, " cutSmallImg error %d", re); m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "AI_Detect End"); return re; /* code */ } // 临时存图 if (DetImgInfo_shareP->bsaveProcessImg) { cv::Mat imshow = img.clone(); // 保持输入通道,不再灰度转假彩 for (int i = 0; i < SmallRoiList.size(); i++) { cv::rectangle(imshow, SmallRoiList.at(i), CheckUtil::GetDrawColor(imshow, cv::Scalar(0, 0, 255)), 3); } cv::imwrite(DetImgInfo_shareP->strChannel + "_AI_Det_ROI.jpg", imshow); } ResultImg = cv::Mat::zeros(img.size(), CV_8UC1); const int totalTasks = SmallRoiList.size(); int submitted = 0; int completed = 0; // std::string str_Root = m_strRootPath + pDetConfig->strProductID + "_" + pDetConfig->strchannel + "_"; t2 = CheckUtil::getcurTime(); while (completed < totalTasks) { // 如果任务还没提交完,且当前处理任务数 < 2,提交新任务 if (submitted < totalTasks && runner->GetProcessingCount() < 10) { std::shared_ptr task = std::make_shared(); task->id = completed; task->roi = SmallRoiList.at(submitted); task->input = img(SmallRoiList.at(submitted)).clone(); task->output = std::make_shared(); task->engine = pAIDet; runner->SubmitTask(task); submitted++; } // 尝试取结果 std::shared_ptr result; if (runner->PopResult(result)) { { std::lock_guard lock(mtx_AIMaskImgBLobQueue); m_AIMaskImgBLobQueue.push(result); } cv::Mat &outimg = *(result->output); if (!outimg.empty()) { outimg.copyTo(ResultImg(result->roi), outimg); } { std::shared_ptr temAIresult = std::make_shared(); temAIresult->roi = result->roi; temAIresult->AI_inImg = result->input; temAIresult->AI_mask = outimg; m_pImageAllResult->AI_Qx_MaskList.push_back(temAIresult); } completed++; } else { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } t3 = CheckUtil::getcurTime(); float mean_AI = (t3 - t2) / SmallRoiList.size(); m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strBaseLog, " AI Run Time: sum %ld pre %ld Run %ld mean One Small Img %f", t3 - t1, t2 - t1, t3 - t2, mean_AI); m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "AI_Detect End"); // 临时存图 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_cell_ymhs; 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; int cls_num = 0; switch (temClass) { case 0: cls_num = AI_CLass_QX_NAME_aotudian; break; case 1: cls_num = AI_CLass_QX_NAME_other; break; case 2: cls_num = AI_CLass_QX_NAME_line; break; case 3: cls_num = AI_CLass_QX_NAME_zangwu; break; case 4: cls_num = AI_CLass_QX_NAME_dianzhuang; break; case 5: cls_num = AI_CLass_QX_NAME_posun; break; case 6: cls_num = AI_CLass_QX_NAME_xianwei; break; case 7: cls_num = AI_CLass_QX_NAME_shuizi; break; case 8: cls_num = AI_CLass_QX_NAME_danban; break; case 9: cls_num = AI_CLass_QX_NAME_fuchen; break; default: cls_num = AI_CLass_QX_NAME_zangwu; break; } // std::string strclassName = AI_CLass_QX_NAME_Names[cls_num]; // printf("AI Class num %d = %s %f\n", cls_num, strclassName.c_str(), result->cls_score); // 分类存图 开启。 if (m_pbaseCheckFunction->saveImg.bSaveAlginImg) { std::string saveimgpaht = ""; if (m_strCurDetChannel == "TA") { saveimgpaht = m_strRootPath_TA_cls + std::to_string(cls_num) + "/" + std::to_string(CheckUtil::getcurTime()) + "_" + std::to_string(result->cls_score) + ".png"; } else { saveimgpaht = m_strRootPath_CA_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); // 结果图画布在显示尺寸上按摆正矩阵直接从原图重采样(保持原图通道 -> 真实彩色,不再灰度转假彩); // 异常情况(如未走摆正流程)直接用检测图兜底 const cv::Mat &srcImg = m_CheckResult_shareP->in_shareImage->img; if (m_CheckResult_shareP->in_shareImage->Det_Mode != DET_MODE_ReJson && !srcImg.empty() && !m_matSrc2Det.empty() && m_deskewPadRoi.width > 0 && m_deskewPadRoi.height > 0 && CheckUtil::RoiInImg(m_deskewPadRoi, srcImg)) { // M_disp = 显示缩放 ∘ M_src2det,并把输入平移到原图裁剪区(padRoi)的局部坐标 cv::Mat matDisp = m_matSrc2Det.clone(); double fsx = sz.width * 1.0 / m_pImageAllResult->detImg.cols; double fsy = sz.height * 1.0 / m_pImageAllResult->detImg.rows; matDisp.at(0, 0) *= fsx; matDisp.at(0, 1) *= fsx; matDisp.at(0, 2) *= fsx; matDisp.at(1, 0) *= fsy; matDisp.at(1, 1) *= fsy; matDisp.at(1, 2) *= fsy; matDisp.at(0, 2) += matDisp.at(0, 0) * m_deskewPadRoi.x + matDisp.at(0, 1) * m_deskewPadRoi.y; matDisp.at(1, 2) += matDisp.at(1, 0) * m_deskewPadRoi.x + matDisp.at(1, 1) * m_deskewPadRoi.y; cv::warpAffine(srcImg(m_deskewPadRoi), m_pImageAllResult->resultImg, matDisp, sz, cv::INTER_LINEAR, cv::BORDER_REPLICATE); } else { cv::resize(m_pImageAllResult->detImg, 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; // 参数多边形 -> 检测图(摆正坐标系) 的映射在摆正时已算好(m_matParam2Det) for (const auto ®ion : m_pCommonAnalysisyConfig->regionConfigArr) { m_DetRoiList.Update_Mat(region.basicInfo.pointArry, m_matParam2Det, 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; // 绘制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++) { cv::Point2f detPt = MatAffinePoint(m_matSrc2Det, vertices[i]); draw_pts.emplace_back(cvRound(detPt.x * fs_resize_x), cvRound(detPt.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); } if (m_pbaseCheckFunction && m_pbaseCheckFunction->markLine.region.size() > 1) { std::vector product_roi_show; for (const auto &pt : m_pbaseCheckFunction->markLine.region) { // 参数(模板)坐标 -> 检测图(摆正) -> 结果图 cv::Point2f detPt = MatAffinePoint(m_matParam2Det, cv::Point2f((float)pt.x, (float)pt.y)); cv::Point draw_pt; draw_pt.x = cvRound(detPt.x * fs_resize_x); draw_pt.y = cvRound(detPt.y * fs_resize_y); product_roi_show.push_back(draw_pt); } cv::polylines( m_CheckResult_shareP->resultimg, product_roi_show, true, CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, cv::Scalar(0, 255, 0)), 1); } if (m_pEdge_Align_Result.get() != NULL) { if (m_pbaseCheckFunction->markLine.bDraw) { for (size_t i = 0; i < m_pEdge_Align_Result->markresulList.size(); i++) { // if (m_pEdge_Align_Result->markresulList[i].status == Mark_Result_Status_OK) { const cv::Point &markPt = m_pEdge_Align_Result->markresulList[i].det_Local_DetImg; cv::Point2f detPt = MatAffinePoint(m_matSrc2Det, cv::Point2f((float)markPt.x, (float)markPt.y)); cv::Point p; p.x = cvRound(detPt.x * fs_resize_x); p.y = cvRound(detPt.y * fs_resize_y); cv::circle(m_CheckResult_shareP->resultimg, p, 5, CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, cv::Scalar(0, 255, 0))); } } } } // cv::imwrite("sss.png", m_CheckResult_shareP->resultimg); return 0; } int ImgCheckAnalysisy::AIClassTypeToConfigType(int nAIQXType) { int resultError_type = CONFIG_QX_NAME_cell_aotudian; switch (nAIQXType) { case AI_CLass_QX_NAME_aotudian: resultError_type = CONFIG_QX_NAME_cell_aotudian; break; case AI_CLass_QX_NAME_other: resultError_type = CONFIG_QX_NAME_cell_other; break; case AI_CLass_QX_NAME_line: resultError_type = CONFIG_QX_NAME_cell_line; break; case AI_CLass_QX_NAME_zangwu: resultError_type = CONFIG_QX_NAME_cell_zangwu; break; case AI_CLass_QX_NAME_dianzhuang: resultError_type = CONFIG_QX_NAME_cell_dianzhuang; break; case AI_CLass_QX_NAME_posun: resultError_type = CONFIG_QX_NAME_cell_posun; break; case AI_CLass_QX_NAME_xianwei: resultError_type = CONFIG_QX_NAME_cell_xianwei; break; case AI_CLass_QX_NAME_shuizi: resultError_type = CONFIG_QX_NAME_cell_shuizi; break; case AI_CLass_QX_NAME_danban: resultError_type = CONFIG_QX_NAME_cell_danban; break; case AI_CLass_QX_NAME_fuchen: resultError_type = CONFIG_QX_NAME_cell_fuchen; 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; } 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; float JudgArea = blobs.blobTab[i].JudgArea; float fsecondArea = JudgArea; std::string qx_name = CONFIG_QX_NAME_Names[config_qx_type]; 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.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); // 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, vector &tagroiList) { 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.pBaseCheckFunction = m_pbaseCheckFunction; /* 定位获取三个roi */ int re = m_pAI_Edge_Algin.Detect(img, &config, m_pEdge_Align_Result); if (re != 0) { printf("AI_Edge Is Error = %d \n", re); } if (!m_pEdge_Align_Result->bigroi.empty()) { outerRoi = m_pEdge_Align_Result->bigroi[0]; } if (!m_pEdge_Align_Result->smallroi.empty()) { innerRoi = m_pEdge_Align_Result->smallroi[0]; } if (!m_pEdge_Align_Result->tagroi.empty()) { tagroiList = m_pEdge_Align_Result->tagroi; } /*使用m_pEdge_Align_Result->bigmask检测把手是否缺失*/ { long handle_s = CheckUtil::getcurTime(); CheckHandleByBigMask(m_pEdge_Align_Result->bigmask); long handle_e = CheckUtil::getcurTime(); m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "handle detect use time %ld", handle_e - handle_s); } return re; } // 用 bigmask 检测把手: // 1、bigmask 主体近似矩形,把手是主体四周凸起的"类矩形块" // 2、把主体四周的凸起抠出来(凸起在该边的边界值上表现为一次突变进入 + 一次反向突变离开) // 3、参数里每个非空的"把手区域"附近都应该有一个把手:在区域附近找凸起, // 凸起宽高与区域外接矩形宽高相近(HANDLE_SIZE_MIN_RATIO ~ HANDLE_SIZE_MAX_RATIO)才算匹配 // 4、凸起本身不算 NG,只有"某个把手区域附近找不到匹配的凸起"才算把手缺失(NG 由 CheckRun 上报) int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &bigmask) { m_bHandleDetSucc = false; m_bHandleFound = false; m_bHandleExpect = false; m_HandleCandList.clear(); m_HandleRegionResultList.clear(); if (bigmask.empty()) { m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "bigmask is empty, skip handle detect"); return 1; } // 1、抠出 bigmask 四周凸起的矩形块 std::vector protrusionList; int re = CheckUtil::GetMaskProtrusions(bigmask, protrusionList); if (re != 0) { m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "GetMaskProtrusions error %d", re); return re; } m_bHandleDetSucc = true; // 2、把手检测开关 + 把手区域(最多4个,非空区域表示该处应该有一个把手) bool bDetHandle = false; std::vector regionBoxList; if (m_pFuntion != NULL) { const Function_Support_Det &supportDet = m_pFuntion->function.f_supportDet; bDetHandle = supportDet.bDetHandle; for (size_t i = 0; i < supportDet.handleBoxes.size() && i < HANDLE_REGION_MAX_NUM; i++) { regionBoxList.push_back(supportDet.handleBoxes[i]); } } m_bHandleExpect = (bDetHandle && !regionBoxList.empty()); m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "protrusion num %ld handle region num %ld bDetHandle %s expect %s", protrusionList.size(), regionBoxList.size(), BOOL_TO_STR(bDetHandle), BOOL_TO_STR(m_bHandleExpect)); // 3、凸起候选实测特征(面积/填充率,仅用于日志排查) for (size_t i = 0; i < protrusionList.size(); i++) { Handle_Candidate_ cand; cand.protr = protrusionList[i]; // 左/右凸起:沿边为 y 方向,深度为 x 方向;上/下凸起相反 bool bHorz = (cand.protr.nSide == 0 || cand.protr.nSide == 1); cand.nLen = bHorz ? cand.protr.roi.height : cand.protr.roi.width; cand.nDepth = bHorz ? cand.protr.roi.width : cand.protr.roi.height; m_HandleCandList.push_back(cand); m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "protrusion %ld side %d roi [%d %d %d %d] len %d depth %d area %0.0f fill %0.2f", i, cand.protr.nSide, cand.protr.roi.x, cand.protr.roi.y, cand.protr.roi.width, cand.protr.roi.height, cand.nLen, cand.nDepth, cand.protr.fArea, cand.protr.fFillRatio); } // 4、逐个把手区域:在区域附近找宽高匹配的凸起(同一个凸起只匹配给一个区域) std::vector usedList(m_HandleCandList.size(), false); int nFound = 0; for (size_t ir = 0; ir < regionBoxList.size(); ir++) { Handle_Region_Result_ result; result.nIdx = (int)ir + 1; result.boxRegion = regionBoxList[ir]; int nMatchIdx = MatchHandleRegion(m_HandleCandList, usedList, result.boxRegion, result.roiHandle); result.bFound = (nMatchIdx >= 0); if (result.bFound) { usedList[nMatchIdx] = true; nFound++; } m_HandleRegionResultList.push_back(result); std::string strResult = "handle missing !!"; if (result.bFound) { strResult = "handle roi [" + std::to_string(result.roiHandle.x) + " " + std::to_string(result.roiHandle.y) + " " + std::to_string(result.roiHandle.width) + " " + std::to_string(result.roiHandle.height) + "]"; } m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "handle region %d box [%d %d %d %d] -> %s", result.nIdx, result.boxRegion.x, result.boxRegion.y, result.boxRegion.width, result.boxRegion.height, strResult.c_str()); } m_bHandleFound = (!regionBoxList.empty() && nFound == (int)regionBoxList.size()); m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, "Handle", "handle region num %ld found num %d -> %s", regionBoxList.size(), nFound, m_bHandleFound ? "handle ok" : "handle missing !!"); // 5、存过程图:凸起红框、把手区域蓝框、匹配上的把手绿框(仅调试时) if (DetImgInfo_shareP->bsaveProcessImg) { cv::Mat show; if (bigmask.channels() == 1) { cv::cvtColor(bigmask, show, cv::COLOR_GRAY2BGR); } else { show = bigmask.clone(); } for (size_t i = 0; i < m_HandleCandList.size(); i++) { cv::rectangle(show, m_HandleCandList[i].protr.roi, cv::Scalar(0, 0, 255), 8); } for (size_t i = 0; i < m_HandleRegionResultList.size(); i++) { cv::rectangle(show, m_HandleRegionResultList[i].boxRegion, cv::Scalar(255, 0, 0), 8); if (m_HandleRegionResultList[i].bFound) { cv::rectangle(show, m_HandleRegionResultList[i].roiHandle, cv::Scalar(0, 255, 0), 12); } } cv::Mat showSmall; cv::resize(show, showSmall, cv::Size(show.cols / 4, show.rows / 4)); std::vector paramJpg = {cv::IMWRITE_JPEG_QUALITY, 90}; cv::imwrite(DetImgInfo_shareP->strChannel + "_handle_det.jpg", showSmall, paramJpg); } return 0; } // 标签检测(独立步骤):用 (图像 + 定位结果) 跑 Tag_Loc 模型 // 裁剪区域与定位流程里 Tag_Loc 的口径完全一致(定位得到的产品外接旋转矩形): // 明场相机用自己的定位结果;暗场相机没做定位模型,用复用明场的结果 —— 保证暗/明场对同一摆位切出同样区域。 // 注意:标签模型对裁剪尺度很敏感(裁剪差 3px 就会让检出个数变化),不能另用 bigmask 重推裁剪框。 // bigmask 仅在定位外接矩形异常时作兜底(最大轮廓外接框)。 // 检出的标签 roi 存 m_tag_roiList(裁剪图坐标系,与定位流程 tagroi 的约定一致),由 CheckRun 统一上报 NG。 int ImgCheckAnalysisy::CheckTagByBigMask(const cv::Mat &img, const cv::Mat &bigmask) { m_tag_roiList.clear(); if (!AI_Factory || !AI_Factory->Tag_Loc) { m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "Tag_Loc model is null"); return 1; } if (img.empty()) { m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "tag check img is empty"); return 2; } // 1、裁剪区域:与定位流程 Tag_Loc 的裁剪口径一致(产品外接旋转矩形,暗场为复用明场的值) cv::Rect roiCrop = m_outer_rroi.boundingRect() & cv::Rect(0, 0, img.cols, img.rows); if (roiCrop.width <= 0 || roiCrop.height <= 0) { // 异常兜底:定位外接矩形无效时,用 bigmask 最大轮廓外接框 if (bigmask.empty()) { m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "tag check roi invalid and bigmask is empty"); return 3; } cv::Mat binMask; { cv::Mat grayMask; if (bigmask.channels() != 1) { cv::cvtColor(bigmask, grayMask, cv::COLOR_BGR2GRAY); } else { grayMask = bigmask; } double dMin = 0; double dMax = 0; cv::minMaxLoc(grayMask, &dMin, &dMax); // 兼容 0/1 与 0/255 掩膜:取半强度作阈值(重采样后的边缘取到真实边界) cv::threshold(grayMask, binMask, dMax > 1 ? dMax * 0.5 : 0, 255, cv::THRESH_BINARY); } bool bFound = false; std::vector contour = CheckUtil::getLargestContourROI(binMask, bFound); if (!bFound || contour.size() < 3) { m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "tag check bigmask contour too few"); return 4; } roiCrop = cv::minAreaRect(contour).boundingRect() & cv::Rect(0, 0, img.cols, img.rows); if (roiCrop.width <= 0 || roiCrop.height <= 0) { m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "tag check crop roi error [%d %d %d %d]", roiCrop.x, roiCrop.y, roiCrop.width, roiCrop.height); return 5; } m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Tag", "tag check use bigmask roi [%d %d %d %d]", roiCrop.x, roiCrop.y, roiCrop.width, roiCrop.height); } // 2、裁剪 -> resize 到 Tag_Loc 模型输入尺寸 -> 灰度 std::shared_ptr pTag = AI_Factory->Tag_Loc; cv::Size sz(pTag->input_0.width, pTag->input_0.height); if (sz.width <= 0 || sz.height <= 0) { m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "tag check invalid model input size"); return 6; } cv::Mat detImg; cv::resize(img(roiCrop), detImg, sz); if (detImg.channels() == 3) { cv::cvtColor(detImg, detImg, cv::COLOR_BGR2GRAY); } else if (detImg.channels() == 4) { cv::cvtColor(detImg, detImg, cv::COLOR_BGRA2GRAY); } // 3、推理 cv::Mat mask; int re = pTag->AIDet(detImg, mask); if (re != 0) { m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "Tag_Loc AIDet error %d", re); return 100 + re; } if (mask.empty()) { m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Tag", "tag mask empty, no tag"); return 0; } // 调试存图(仅 -s 时保存) if (DetImgInfo_shareP && DetImgInfo_shareP->bsaveProcessImg) { cv::imwrite(DetImgInfo_shareP->strChannel + "_tag_in.png", detImg); cv::imwrite(DetImgInfo_shareP->strChannel + "_tag_out_mask.png", mask); } // 4、mask 轮廓 -> 还原到裁剪图坐标 -> RotatedRect(与定位流程 tagroi 的坐标系一致) std::vector> contours; std::vector hierarchy; cv::findContours(mask.clone(), contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); const float scale_x = (float)roiCrop.width / (float)sz.width; const float scale_y = (float)roiCrop.height / (float)sz.height; for (size_t i = 0; i < contours.size(); i++) { if (contours[i].size() < 3 || cv::contourArea(contours[i]) <= 0) { continue; } std::vector srcContour; srcContour.reserve(contours[i].size()); for (const auto &pt : contours[i]) { srcContour.emplace_back(pt.x * scale_x, pt.y * scale_y); } m_tag_roiList.emplace_back(cv::minAreaRect(srcContour)); } // 面积从大到小排序(与定位流程 tagroi 的顺序一致) std::sort(m_tag_roiList.begin(), m_tag_roiList.end(), [](const cv::RotatedRect &a, const cv::RotatedRect &b) { return a.size.width * a.size.height > b.size.width * b.size.height; }); m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Tag", "tag region num %ld crop [%d %d %d %d] model [%d %d]", m_tag_roiList.size(), roiCrop.x, roiCrop.y, roiCrop.width, roiCrop.height, sz.width, sz.height); return 0; } // 在某个把手区域附近找宽高匹配的凸起 // 匹配条件:1、凸起中心落在把手区域附近(区域宽/高各外扩 HANDLE_NEAR_EXPAND_RATIO) // 2、凸起宽高与该区域外接矩形宽高的比值都在 [HANDLE_SIZE_MIN_RATIO, HANDLE_SIZE_MAX_RATIO](允许横竖互换) // 返回凸起在 candList 中的下标,-1 表示没找到 int ImgCheckAnalysisy::MatchHandleRegion(const std::vector &candList, const std::vector &usedList, const cv::Rect &boxRegion, cv::Rect &roiHandle) { if (boxRegion.width <= 0 || boxRegion.height <= 0) { return -1; } // 搜索范围:把手区域外扩(宽/高各外扩区域尺寸的一半),容忍产品摆放偏差 int nx = std::max(10, (int)(boxRegion.width * HANDLE_NEAR_EXPAND_RATIO)); int ny = std::max(10, (int)(boxRegion.height * HANDLE_NEAR_EXPAND_RATIO)); cv::Rect searchBox(boxRegion.x - nx, boxRegion.y - ny, boxRegion.width + 2 * nx, boxRegion.height + 2 * ny); cv::Point pRegionCenter(boxRegion.x + boxRegion.width / 2, boxRegion.y + boxRegion.height / 2); int nBestIdx = -1; double fBestDis = 1e18; int nNearIdx = -1; // 区域内离区域中心最近的凸起(仅用于日志排查) double fNearDis = 1e18; for (size_t i = 0; i < candList.size(); i++) { if (i < usedList.size() && usedList[i]) { continue; } const Handle_Candidate_ &cand = candList[i]; const cv::Rect &roi = cand.protr.roi; if (roi.width <= 0 || roi.height <= 0) { continue; } // 位置:凸起中心要落在把手区域附近 cv::Point pCenter(roi.x + roi.width / 2, roi.y + roi.height / 2); if (!searchBox.contains(pCenter)) { continue; } double fDis = CheckUtil::calDis(pCenter, pRegionCenter); if (fDis < fNearDis) { fNearDis = fDis; nNearIdx = (int)i; } // 宽高:与把手区域外接矩形的宽高相近(允许横竖互换) float fw = (float)roi.width; float fh = (float)roi.height; bool bSizeOK = (fw >= boxRegion.width * HANDLE_SIZE_MIN_RATIO && fw <= boxRegion.width * HANDLE_SIZE_MAX_RATIO && fh >= boxRegion.height * HANDLE_SIZE_MIN_RATIO && fh <= boxRegion.height * HANDLE_SIZE_MAX_RATIO); if (!bSizeOK) { bSizeOK = (fw >= boxRegion.height * HANDLE_SIZE_MIN_RATIO && fw <= boxRegion.height * HANDLE_SIZE_MAX_RATIO && fh >= boxRegion.width * HANDLE_SIZE_MIN_RATIO && fh <= boxRegion.width * HANDLE_SIZE_MAX_RATIO); } if (!bSizeOK) { continue; } if (fDis < fBestDis) { fBestDis = fDis; nBestIdx = (int)i; } } if (nBestIdx < 0 && nNearIdx >= 0) { // 没匹配上时打印区域内最近的凸起,便于排查阈值 const cv::Rect &roi = candList[nNearIdx].protr.roi; m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "region box [%d %d %d %d] no match, nearest protrusion roi [%d %d %d %d] w/h ratio %0.2f %0.2f", boxRegion.x, boxRegion.y, boxRegion.width, boxRegion.height, roi.x, roi.y, roi.width, roi.height, (float)roi.width / (float)boxRegion.width, (float)roi.height / (float)boxRegion.height); } if (nBestIdx >= 0) { roiHandle = candList[nBestIdx].protr.roi; } return nBestIdx; } 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)保留原图通道:源图为彩色时用彩色摆正图,否则回退灰度检测图 m_CheckResult_shareP->cutSrcimg = m_pImageAllResult->detColorImg.empty() ? m_pImageAllResult->detImg : m_pImageAllResult->detColorImg; 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; }