From 12404cc400e3fb35f840f9dfee08149c12787255 Mon Sep 17 00:00:00 2001 From: liusiyang Date: Mon, 21 Sep 2026 14:00:25 +0800 Subject: [PATCH] =?UTF-8?q?feat=20=E5=88=9D=E6=AD=A5=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=9A=97=E5=9C=BA=E5=A4=8D=E7=94=A8=E6=98=8E=E5=9C=BA=E5=AE=9A?= =?UTF-8?q?=E4=BD=8D=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AlgorithmModule/include/BrightGeoShare.hpp | 141 +++++++++++ AlgorithmModule/include/ImgCheckAnalysisy.hpp | 12 +- AlgorithmModule/include/ImgMmPatch.hpp | 35 ++- AlgorithmModule/src/ImgCheckAnalysisy.cpp | 219 ++++++++++++++++-- 4 files changed, 376 insertions(+), 31 deletions(-) create mode 100644 AlgorithmModule/include/BrightGeoShare.hpp diff --git a/AlgorithmModule/include/BrightGeoShare.hpp b/AlgorithmModule/include/BrightGeoShare.hpp new file mode 100644 index 0000000..8b807a6 --- /dev/null +++ b/AlgorithmModule/include/BrightGeoShare.hpp @@ -0,0 +1,141 @@ +#ifndef BRIGHT_GEOSHARE_HPP_ +#define BRIGHT_GEOSHARE_HPP_ + +// ============================================================================ +// 明场(B) -> 暗场(D) 几何结果共享 +// +// 背景:本机 4 台相机 BCA/DCA/BTA/DTA 中,B 与 D 是同一工位的两个光照通道 +// (配对:BCA<->DCA, BTA<->DTA),产品/物料在图像中的位置一致,仅明暗场不同。 +// 暗场图对比度低,定位(GetEdgeRoi/AI_Edge)与参数自适应(Adapt)不稳定, +// 因此 D 相机直接复用 B 相机算出的: +// 1) 定位结果 m_CutRoi(检测裁切框) +// 2) Adapt 目标:产品旋转外接矩形(rot_cur_roi) +// 3) 产品真实宽高(mm) +// +// 用法: +// Bright 侧(BCA/BTA)在几何计算完成后 Publish 快照; +// Dark 侧(DCA/DTA)在需要时 Take 快照(带超时,拿不到则回退自身逻辑)。 +// +// 匹配键 = 工位对("CA"/"TA"),不按产品名匹配:实际流程中四个相机可能以各自独立 +// 的“产品”送检(如 ET 套图流程按 BCA→BTA→DCA→DTA 顺序各送一次),B 与 D 的产品名 +// 并不相同。取用即消费(Take 成功后删除),保证一份明场结果只服务一次暗场, +// 不会把上一块板的坐标复用到下一块板;键集合只有 CA/TA 两个,永不增长。 +// +// 线程安全:内部 mutex + condition_variable,可在多相机线程间并发使用。 +// 关闭开关:环境变量 AOI_DISABLE_BRIGHT_DARK_SHARE=1(用于 A/B 对比旧行为)。 +// ============================================================================ + +#include +#include +#include +#include +#include +#include +#include + +// 等待明场结果的超时(ms):超时后暗场回退到自身定位/自适应逻辑。 +// 明场与暗场同时开始检查,该值只需覆盖明场“拼接 + 粗定位 + Adapt + AI_Edge”的耗时。 +static const long kBrightGeoWaitTimeoutMs = 60000; + +// 明暗场共享开关(默认开启) +inline bool BrightGeoShareEnabled() +{ + static const bool bEnabled = []() -> bool { + const char *pEnv = std::getenv("AOI_DISABLE_BRIGHT_DARK_SHARE"); + if (pEnv == nullptr) + { + return true; + } + std::string s(pEnv); + return !(s == "1" || s == "true" || s == "TRUE" || s == "on" || s == "ON"); + }(); + return bEnabled; +} + +// 相机代码(BCA/BTA/DCA/DTA) -> 工位对(CA/TA);非预期输入返回空串 +inline std::string BrightGeoPairOfCamera(const std::string &strCameraCode) +{ + if (strCameraCode.find("CA") != std::string::npos) + { + return "CA"; + } + if (strCameraCode.find("TA") != std::string::npos) + { + return "TA"; + } + return ""; +} + +// 是否明场相机(代码以 B 开头,如 BCA/BTA) +inline bool BrightGeoCameraIsBright(const std::string &strCameraCode) +{ + return !strCameraCode.empty() && (strCameraCode[0] == 'B' || strCameraCode[0] == 'b'); +} + +// 是否暗场相机(代码以 D 开头,如 DCA/DTA) +inline bool BrightGeoCameraIsDark(const std::string &strCameraCode) +{ + return !strCameraCode.empty() && (strCameraCode[0] == 'D' || strCameraCode[0] == 'd'); +} + +// 共享快照的键:工位对("CA"/"TA") +// (注:不按产品名匹配,原因见文件头说明) + +// 明场发布给暗场的几何快照 +struct BrightGeoSnapshot +{ + std::string strProduct; // 发布该快照的产品名(仅用于日志追踪) + bool bValid = false; // 快照是否可用于暗场(明场定位流程是否正常完成) + bool bRoiValid = false; // 产品旋转外接矩形是否有效(明场 Adapt 是否成功) + cv::RotatedRect rotRoi; // 产品旋转外接矩形(原图坐标) + cv::Rect cutRoi; // 定位裁切框(原图坐标) + cv::Size imgSize; // 明场拼接图尺寸(暗场做一致性校验) + float productWidth_mm = 0.f; // 产品真实宽度(mm) + float productHeight_mm = 0.f; // 产品真实高度(mm) +}; + +class BrightGeoShare +{ +public: + static BrightGeoShare &Instance() + { + static BrightGeoShare s_ins; + return s_ins; + } + + // 明场发布(同一工位对覆盖旧值),唤醒等待中的暗场线程 + void Publish(const std::string &strPair, const BrightGeoSnapshot &snap) + { + { + std::lock_guard lk(m_mtx); + m_map[strPair] = snap; + } + m_cv.notify_all(); + } + + // 暗场等待并取走明场结果(超时返回 false;成功则取走并删除,一份结果只服务一次) + bool Take(const std::string &strPair, BrightGeoSnapshot &snap, long nTimeoutMs) + { + std::unique_lock lk(m_mtx); + bool bOk = m_cv.wait_for(lk, std::chrono::milliseconds(nTimeoutMs), + [&]() { return m_map.find(strPair) != m_map.end(); }); + if (!bOk) + { + return false; + } + auto it = m_map.find(strPair); + snap = it->second; + m_map.erase(it); + return true; + } + +private: + BrightGeoShare() {} + ~BrightGeoShare() {} + + std::mutex m_mtx; + std::condition_variable m_cv; + std::map m_map; +}; + +#endif diff --git a/AlgorithmModule/include/ImgCheckAnalysisy.hpp b/AlgorithmModule/include/ImgCheckAnalysisy.hpp index 24ca1e0..123fc12 100644 --- a/AlgorithmModule/include/ImgCheckAnalysisy.hpp +++ b/AlgorithmModule/include/ImgCheckAnalysisy.hpp @@ -37,6 +37,7 @@ #include "ImageAllResult.h" #include "Task.h" #include "TcsCheck.h" +#include "BrightGeoShare.hpp" using namespace std; using namespace cv; @@ -118,6 +119,10 @@ private: int AI_Edge(const cv::Mat &img, cv::Rect &cutRoi); // 计算产品尺寸 int CalProductSize(); + // 计算产品物理宽高(mm)(未取整前的原始换算 + 现有格式取整),供 CalProductSize 与明场发布共用 + void ComputeProductSizeValues(float &fw, float &fh, float &fhPixel); + // 解析当前检测图的相机代码(BCA/BTA/DCA/DTA),无法解析返回空串 + std::string GetCurCameraCode(); // 图片预处理 int ImgPreDet(); @@ -203,7 +208,8 @@ private: int BLobToDetResult(); //自适应参数区域更新 - int Adapt_Config(Mat img, Rect cur_roi, cv::RotatedRect& rot_cur_roi, bool b_update); + // pTargetRoi != nullptr 时使用外部(明场相机)提供的产品旋转矩形作为仿射目标,跳过自身边缘定位 + int Adapt_Config(Mat img, Rect cur_roi, cv::RotatedRect& rot_cur_roi, bool b_update, const cv::RotatedRect *pTargetRoi = nullptr); std::shared_ptr AI_Factory; @@ -247,6 +253,10 @@ private: cv::RotatedRect m_rot_cur_roi; // 产品旋转外接矩形(原图坐标) bool m_bRotCurRoiValid = false; // m_rot_cur_roi 是否有效 + // 暗场(DCA/DTA)复用明场(BCA/BTA)几何结果(定位/Adapt目标/产品宽高),详见 BrightGeoShare.hpp + bool m_bDarkUseBrightGeo = false; // 本次检测暗场是否成功拿到明场结果 + BrightGeoSnapshot m_brightGeoSnap; // 暗场复用的明场结果快照 + std::string m_strCurDetChannel; // 当前处理的图片通道 // // 检测控制参数 diff --git a/AlgorithmModule/include/ImgMmPatch.hpp b/AlgorithmModule/include/ImgMmPatch.hpp index cdb8dca..f63bc59 100644 --- a/AlgorithmModule/include/ImgMmPatch.hpp +++ b/AlgorithmModule/include/ImgMmPatch.hpp @@ -5,11 +5,14 @@ // 【临时补丁】产品长度方向(y) 像素 -> 物理毫米 的分段换算 // // 背景:产品沿长度方向运动/扫描时速率不恒定(尾端有拉伸),像素↔mm 不是单一 -// 比例,实测标定出 5 段,每段斜率不同。 +// 比例,实测标定出 6 段,每段斜率不同。 // // 分段表(分段点 = 像素,**以产品顶边为 0 点**;斜率为 mm/px)见函数内 -// yPx1..yPx5 / pmm1..pmm5,改标定只需改那一段(此处不重复写数值,避免两处不同步)。 +// yPx0..yPx5 / pmm0..pmm5(共 6 段),改标定只需改那一段 +// (此处不重复写数值,避免两处不同步)。 // 分段点处前后两段取值相同,函数连续,不会在分段点跳变。 +// 5000px 是最前端新加的分段点:把原来的 0~10928 拆成 0~5000 与 5000~10928, +// 当 pmm0 == pmm1 == 0.016869 时与拆分前逐位等价。 // // 共用方(必须共用同一张表,否则"产品高度"和"缺陷坐标"会各用一把尺子): // 1) 缺陷 y 物理坐标:ImageResultJudge::BuildDefectImage() @@ -20,33 +23,43 @@ inline float PixelYToMm(float fYPixel) { // ================= 标定参数:改标定只需改这一段 ================= - // 各段斜率(mm/px) - const float pmm1 = 0.016869; - const float pmm2 = 0.016463; - const float pmm3 = 0.016220; - const float pmm4 = 0.015669; - const float pmm5 = 0.014337; + // 各段斜率(mm/px),下标 0..5 依次对应 6 个分段 + // 注:pmm0 / pmm1 是新增分段点 5000 拆出来的两段,标定值待填; + // 两者都取原 0~10928 段的 0.016869 时,与拆分前结果逐位相同。 + const float pmm0 = 0.016869; // 0 ~ 5000 + const float pmm1 = 0.016869; // 5000 ~ 10928 + const float pmm2 = 0.016463; // 10928 ~ 17002 + const float pmm3 = 0.016220; // 17002 ~ 23167 + const float pmm4 = 0.015669; // 23167 ~ 29549 + const float pmm5 = 0.014337; // >= 29549 // 各段起始像素(以产品顶边为 0 点) - const float yPx1 = 0.0f; + const float yPx0 = 0.0f; + const float yPx1 = 5000.0f; const float yPx2 = 10928.0f; const float yPx3 = 17002.0f; const float yPx4 = 23167.0f; const float yPx5 = 29549.0f; // 各段像素长度(px) + const float dPx0 = yPx1 - yPx0; const float dPx1 = yPx2 - yPx1; const float dPx2 = yPx3 - yPx2; const float dPx3 = yPx4 - yPx3; const float dPx4 = yPx5 - yPx4; // 各段末端的累计物理长度(mm) - const float d1 = dPx1 * pmm1; + const float d0 = dPx0 * pmm0; + const float d1 = d0 + dPx1 * pmm1; const float d2 = d1 + dPx2 * pmm2; const float d3 = d2 + dPx3 * pmm3; const float d4 = d3 + dPx4 * pmm4; + if (fYPixel < yPx1) + { + return fYPixel * pmm0; + } if (fYPixel < yPx2) { - return fYPixel * pmm1; + return (fYPixel - yPx1) * pmm1 + d0; } if (fYPixel < yPx3) { diff --git a/AlgorithmModule/src/ImgCheckAnalysisy.cpp b/AlgorithmModule/src/ImgCheckAnalysisy.cpp index 865270a..065db7e 100644 --- a/AlgorithmModule/src/ImgCheckAnalysisy.cpp +++ b/AlgorithmModule/src/ImgCheckAnalysisy.cpp @@ -610,7 +610,7 @@ std::vector sort_vertices(cv::RotatedRect rrect) { return vertices; } -int ImgCheckAnalysisy::Adapt_Config(Mat img, Rect cur_roi, cv::RotatedRect& rot_cur_roi, bool b_update){ +int ImgCheckAnalysisy::Adapt_Config(Mat img, Rect cur_roi, cv::RotatedRect& rot_cur_roi, bool b_update, const cv::RotatedRect *pTargetRoi){ if (img.empty()) { std::cerr << "Error: Input image 'img' is empty!" << std::endl; return -1; @@ -626,9 +626,19 @@ int ImgCheckAnalysisy::Adapt_Config(Mat img, Rect cur_roi, cv::RotatedRect& rot_ { strCameraName = DetImgInfo_shareP->strCameraName; } - int get_edge_roi = GetEdgeRoi(img, cur_roi, rot_cur_roi, 20, 20, bSaveProcessImg, strCameraName); - if(get_edge_roi != 0){ - return 2; + if (pTargetRoi != nullptr) + { + // 暗场复用明场:以明场检测到的产品旋转矩形作为仿射目标,跳过暗场自身的边缘定位(GetEdgeRoi) + rot_cur_roi = *pTargetRoi; + m_pdetlog->AddCheckstr(PrintLevel_0, "Adapt_Config", "use external target (Bright rot_roi) center(%.1f, %.1f) size(%.1f, %.1f) angle %.2f", + rot_cur_roi.center.x, rot_cur_roi.center.y, rot_cur_roi.size.width, rot_cur_roi.size.height, rot_cur_roi.angle); + } + else + { + int get_edge_roi = GetEdgeRoi(img, cur_roi, rot_cur_roi, 20, 20, bSaveProcessImg, strCameraName); + if(get_edge_roi != 0){ + return 2; + } } m_pdetlog->AddCheckstr(PrintLevel_0, "Adapt_Config", "-------------------start--------------"); @@ -830,18 +840,78 @@ int ImgCheckAnalysisy::CheckRun() m_CheckResult_shareP->basicResult.strChannel = m_CheckResult_shareP->in_shareImage->strChannel; m_strCurDetChannel = m_CheckResult_shareP->basicResult.strChannel; + // 相机角色:BCA<->DCA、BTA<->DTA 为同一工位的明/暗场通道,产品位置一致。 + // 暗场图对比度低,定位与参数自适应不稳定,故 DCA/DTA 复用 BCA/BTA 的定位/Adapt/产品宽高 + // 结果(经 BrightGeoShare 传递;拿不到时回退原逻辑)。 + const std::string strCamCode = GetCurCameraCode(); + const bool bDarkCam = BrightGeoCameraIsDark(strCamCode) && !BrightGeoPairOfCamera(strCamCode).empty(); + const bool bBrightCam = BrightGeoCameraIsBright(strCamCode) && !BrightGeoPairOfCamera(strCamCode).empty(); + + m_bDarkUseBrightGeo = false; + m_brightGeoSnap = BrightGeoSnapshot(); + /*自适应更新参数*/ long time_adapt_s = CheckUtil::getcurTime(); cv::RotatedRect rot_cur_roi; - int nAdaptRe = Adapt_Config(m_CheckResult_shareP->in_shareImage->img, m_CutRoi, rot_cur_roi, m_pbaseCheckFunction->markLine.badapt_region); - if (nAdaptRe == 0) + int nAdaptRe = 1; + + // 暗场:等待并复用明场的几何结果(带超时;超时/不可用时回退自身逻辑) + bool bUseBrightGeo = false; + if (bDarkCam && BrightGeoShareEnabled()) { - m_rot_cur_roi = rot_cur_roi; - m_bRotCurRoiValid = true; + BrightGeoSnapshot snap; + const std::string strPair = BrightGeoPairOfCamera(strCamCode); + long tw_s = CheckUtil::getcurTime(); + bool bGot = BrightGeoShare::Instance().Take(strPair, snap, kBrightGeoWaitTimeoutMs); + long tw_e = CheckUtil::getcurTime(); + if (bGot && snap.bValid && snap.imgSize == m_CheckResult_shareP->in_shareImage->img.size()) + { + bUseBrightGeo = true; + m_brightGeoSnap = snap; + m_pdetlog->AddCheckstr(PrintLevel_0, "BrightDark", "Dark[%s] use Bright[%s] pair[%s] wait %ld ms", strCamCode.c_str(), snap.strProduct.c_str(), strPair.c_str(), tw_e - tw_s); + } + else if (bGot) + { + m_pdetlog->AddCheckstr(PrintLevel_0, "BrightDark", "Dark[%s] Bright[%s] result unusable(valid %d, imgSize %dx%d vs %dx%d), fallback", + strCamCode.c_str(), snap.strProduct.c_str(), snap.bValid, snap.imgSize.width, snap.imgSize.height, + m_CheckResult_shareP->in_shareImage->img.cols, m_CheckResult_shareP->in_shareImage->img.rows); + } + else + { + m_pdetlog->AddCheckstr(PrintLevel_0, "BrightDark", "Dark[%s] wait Bright timeout(%ld ms), fallback", strCamCode.c_str(), kBrightGeoWaitTimeoutMs); + } + } + + if (bUseBrightGeo) + { + // 1) 参数自适应:以明场产品旋转矩形为仿射目标(跳过暗场自身定位) + m_bDarkUseBrightGeo = true; + rot_cur_roi = m_brightGeoSnap.rotRoi; + m_bRotCurRoiValid = m_brightGeoSnap.bRoiValid; + if (m_brightGeoSnap.bRoiValid) + { + m_rot_cur_roi = m_brightGeoSnap.rotRoi; + nAdaptRe = Adapt_Config(m_CheckResult_shareP->in_shareImage->img, m_CutRoi, rot_cur_roi, + m_pbaseCheckFunction->markLine.badapt_region, &m_brightGeoSnap.rotRoi); + m_pdetlog->AddCheckstr(PrintLevel_0, "BrightDark", "Dark[%s] Adapt with Bright target ret = %d", strCamCode.c_str(), nAdaptRe); + } + else + { + m_pdetlog->AddCheckstr(PrintLevel_0, "BrightDark", "Dark[%s] Bright rot_roi invalid, skip Adapt (regions keep current)", strCamCode.c_str()); + } } else { - m_bRotCurRoiValid = false; + nAdaptRe = Adapt_Config(m_CheckResult_shareP->in_shareImage->img, m_CutRoi, rot_cur_roi, m_pbaseCheckFunction->markLine.badapt_region); + if (nAdaptRe == 0) + { + m_rot_cur_roi = rot_cur_roi; + m_bRotCurRoiValid = true; + } + else + { + m_bRotCurRoiValid = false; + } } long time_adapt_e = CheckUtil::getcurTime(); m_pdetlog->AddCheckstr(PrintLevel_0, "Adapt_Config", "time = %ld", time_adapt_e - time_adapt_s); @@ -857,20 +927,48 @@ int ImgCheckAnalysisy::CheckRun() } long time_edge_s = CheckUtil::getcurTime(); // 3、AI 边缘定位 - if(m_CheckResult_shareP->basicResult.strChannel == "DCA" || m_CheckResult_shareP->basicResult.strChannel == "DTA") + if(bDarkCam) { - m_pdetlog->AddCheckstr(PrintLevel_0, "Edge", "Not Use AI_Edge %s", m_CheckResult_shareP->basicResult.strChannel.c_str()); - m_CutRoi = cv::Rect(rot_cur_roi.boundingRect().x - 50 , rot_cur_roi.boundingRect().y - 50, rot_cur_roi.boundingRect().width + 100, rot_cur_roi.boundingRect().height + 100) - & cv::Rect(0, 0, m_CheckResult_shareP->in_shareImage->img.cols, m_CheckResult_shareP->in_shareImage->img.rows); - m_pdetlog->AddCheckstr(PrintLevel_0, "Edge", "rot_cur_roi = [%d, %d, %d, %d]", rot_cur_roi.boundingRect().x, rot_cur_roi.boundingRect().y, rot_cur_roi.boundingRect().width, rot_cur_roi.boundingRect().height); - m_pdetlog->AddCheckstr(PrintLevel_0, "Edge", "CutRoi = [%d, %d, %d, %d]", m_CutRoi.x, m_CutRoi.y, m_CutRoi.width, m_CutRoi.height); + const cv::Mat &imgCur = m_CheckResult_shareP->in_shareImage->img; + const cv::Rect rectImg(0, 0, imgCur.cols, imgCur.rows); + if (m_bDarkUseBrightGeo) + { + // 定位:直接采用明场相机的定位裁切框(B/D 同一工位,图像坐标一致;尺寸已在取快照时校验) + m_pdetlog->AddCheckstr(PrintLevel_0, "Edge", "Not Use AI_Edge %s (use Bright cutRoi)", strCamCode.c_str()); + m_CutRoi = m_brightGeoSnap.cutRoi & rectImg; + if (m_CutRoi.width <= 0 || m_CutRoi.height <= 0) + { + m_pdetlog->AddCheckstr(PrintLevel_0, "Edge", "Error Bright cutRoi invalid after clamp, fallback to local rot_cur_roi"); + const cv::Rect rt = rot_cur_roi.boundingRect(); + m_CutRoi = cv::Rect(rt.x - 50, rt.y - 50, rt.width + 100, rt.height + 100) & rectImg; + } + m_pdetlog->AddCheckstr(PrintLevel_0, "Edge", "CutRoi = [%d, %d, %d, %d]", m_CutRoi.x, m_CutRoi.y, m_CutRoi.width, m_CutRoi.height); + } + else + { + m_pdetlog->AddCheckstr(PrintLevel_0, "Edge", "Not Use AI_Edge %s", strCamCode.c_str()); + m_CutRoi = cv::Rect(rot_cur_roi.boundingRect().x - 50 , rot_cur_roi.boundingRect().y - 50, rot_cur_roi.boundingRect().width + 100, rot_cur_roi.boundingRect().height + 100) + & rectImg; + m_pdetlog->AddCheckstr(PrintLevel_0, "Edge", "rot_cur_roi = [%d, %d, %d, %d]", rot_cur_roi.boundingRect().x, rot_cur_roi.boundingRect().y, rot_cur_roi.boundingRect().width, rot_cur_roi.boundingRect().height); + m_pdetlog->AddCheckstr(PrintLevel_0, "Edge", "CutRoi = [%d, %d, %d, %d]", m_CutRoi.x, m_CutRoi.y, m_CutRoi.width, m_CutRoi.height); + } } else { - m_pdetlog->AddCheckstr(PrintLevel_0, "Edge", "Use AI_Edge %s", m_CheckResult_shareP->basicResult.strChannel.c_str()); + m_pdetlog->AddCheckstr(PrintLevel_0, "Edge", "Use AI_Edge %s", strCamCode.c_str()); int reedge = AI_Edge(m_CheckResult_shareP->in_shareImage->img, m_CutRoi); if (reedge != 0) { + // 明场失败时也要尽快通知暗场,避免暗场一直等到超时 + if (bBrightCam && BrightGeoShareEnabled()) + { + BrightGeoSnapshot snapFail; + snapFail.strProduct = m_CheckResult_shareP->in_shareImage->strImgProductID; + snapFail.bValid = false; + const std::string strPair = BrightGeoPairOfCamera(strCamCode); + BrightGeoShare::Instance().Publish(strPair, snapFail); + m_pdetlog->AddCheckstr(PrintLevel_0, "BrightDark", "Bright[%s] publish failure(pair[%s])", strCamCode.c_str(), strPair.c_str()); + } m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "AI_Edge is error type = %d", reedge); m_nErrorCode = reedge; m_nCheckResultErrorCode = m_nErrorCode; @@ -878,6 +976,27 @@ int ImgCheckAnalysisy::CheckRun() } } + // 明场(BCA/BTA):发布定位/Adapt目标/产品宽高,供同工位暗场(DCA/DTA)复用 + if (bBrightCam && BrightGeoShareEnabled()) + { + BrightGeoSnapshot snap; + snap.strProduct = m_CheckResult_shareP->in_shareImage->strImgProductID; + snap.bValid = true; + snap.bRoiValid = m_bRotCurRoiValid; + snap.rotRoi = m_rot_cur_roi; // bRoiValid=false 时此值无效,暗场不会使用 + snap.cutRoi = m_CutRoi; + snap.imgSize = m_CheckResult_shareP->in_shareImage->img.size(); + float fw = 0.f, fh = 0.f, fhPixel = 0.f; + ComputeProductSizeValues(fw, fh, fhPixel); + snap.productWidth_mm = fw; + snap.productHeight_mm = fh; + const std::string strPair = BrightGeoPairOfCamera(strCamCode); + BrightGeoShare::Instance().Publish(strPair, snap); + m_pdetlog->AddCheckstr(PrintLevel_0, "BrightDark", "Bright[%s] publish pair[%s] product[%s] rotRoiValid %d cutRoi[%d,%d,%d,%d] product %f x %f mm (hPixel %f)", + strCamCode.c_str(), strPair.c_str(), snap.strProduct.c_str(), snap.bRoiValid, + snap.cutRoi.x, snap.cutRoi.y, snap.cutRoi.width, snap.cutRoi.height, fw, fh, fhPixel); + } + 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); @@ -2823,10 +2942,12 @@ int ImgCheckAnalysisy::AI_Edge(const cv::Mat &img, cv::Rect &cutRoi) return re; } -int ImgCheckAnalysisy::CalProductSize() +// 计算产品物理宽高(mm):旋转矩形有效时用旋转矩形边长,退化时用裁切框。 +// 宽度用成像精度换算、高度走长度方向分段像素->mm 补丁(ImgMmPatch.hpp), +// 结果按现有格式取整到 0.1mm。供 CalProductSize 与明场快照发布共用,保证两处数值一致。 +void ImgCheckAnalysisy::ComputeProductSizeValues(float &fw, float &fh, float &fhPixel) { - float fw, fh; - float fhPixel = 0.f; // 产品沿长度方向(y)的像素高度,供长度方向分段补丁换算用 + fhPixel = 0.f; if (m_bRotCurRoiValid) { // 产品存在旋转角:用旋转矩形四个顶点的实际边长换算物理尺寸 @@ -2857,6 +2978,66 @@ int ImgCheckAnalysisy::CalProductSize() fh = PixelYToMm(fhPixel); fw = std::ceil(fw * 10) / 10; fh = std::ceil(fh * 10) / 10; +} + +// 解析当前检测图的相机代码(BCA/BTA/DCA/DTA);无法解析返回空串。 +// 依次尝试:配置相机名 -> shareImage 相机名 -> shareImage 通道名 -> 结果通道名(兼容不同命名来源)。 +std::string ImgCheckAnalysisy::GetCurCameraCode() +{ + std::vector candidates; + candidates.push_back(m_AnalysisyConfig.commonCheckConfig.baseConfig.strCamearName); + if (DetImgInfo_shareP != nullptr) + { + candidates.push_back(DetImgInfo_shareP->strCameraName); + candidates.push_back(DetImgInfo_shareP->strChannel); + } + if (m_CheckResult_shareP != nullptr) + { + candidates.push_back(m_CheckResult_shareP->basicResult.strChannel); + } + + static const char *kCameraCodes[] = {"BCA", "BTA", "DCA", "DTA"}; + for (const std::string &cand : candidates) + { + if (cand.empty()) + { + continue; + } + // 统一转大写,避免大小写差异 + std::string strUpper = cand; + for (size_t ci = 0; ci < strUpper.size(); ci++) + { + if (strUpper[ci] >= 'a' && strUpper[ci] <= 'z') + { + strUpper[ci] = (char)(strUpper[ci] - 'a' + 'A'); + } + } + for (size_t ci = 0; ci < sizeof(kCameraCodes) / sizeof(kCameraCodes[0]); ci++) + { + if (strUpper.find(kCameraCodes[ci]) != std::string::npos) + { + return std::string(kCameraCodes[ci]); + } + } + } + return std::string(); +} + +int ImgCheckAnalysisy::CalProductSize() +{ + float fw, fh; + float fhPixel = 0.f; // 产品沿长度方向(y)的像素高度,供长度方向分段补丁换算用 + if (m_bDarkUseBrightGeo) + { + // 暗场:产品真实宽高直接采用明场相机的计算结果(B/D 同一工位,物理尺寸一致) + fw = m_brightGeoSnap.productWidth_mm; + fh = m_brightGeoSnap.productHeight_mm; + m_pdetlog->AddCheckstr(PrintLevel_0, "BrightDark", "Dark product size from Bright: w %f h %f (mm)", fw, fh); + } + else + { + ComputeProductSizeValues(fw, fh, fhPixel); + } m_CheckResult_shareP->productWidht_mm = fw; m_CheckResult_shareP->productHeight_mm = fh;