You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

142 lines
4.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#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 <string>
#include <map>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <cstdlib>
#include <opencv2/opencv.hpp>
// 等待明场结果的超时(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<std::mutex> 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<std::mutex> 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<std::string, BrightGeoSnapshot> m_map;
};
#endif