#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