update 整合通道,相机区分明暗场

dev_lsy
liusiyang 4 weeks ago
parent 0f54b521f6
commit d8e953e160

@ -153,11 +153,15 @@ private:
// 取当前图的产品ID/相机名(优先 productBaseResult/cameraBaseResult,兕底送图信息)
void GetProductCameraStr(std::string &strProduct, std::string &strCamera);
// 判断并在需要时等待"源相机(明场)"的定位结果;成功返回 true(结果已装入成员变量)
// 注意:标签检测不在此复用,由 CheckTagByBigMask 独立执行
bool TryReuseAlignFromSource(std::string &strSrcCamera, std::string &strSrcChannel, int &nWaitMs);
// 发布本图定位结果/失败标记(供同产品其它相机复用)
void PublishAlignShare(bool bSucc);
// 用 bigmask 检测把手(bigmask 主体近似矩形,把手是四周凸起的矩形块)
int CheckHandleByBigMask(const cv::Mat &img, const cv::Mat &bigmask);
// 标签检测(独立步骤):用 (图像 + bigmask) 跑 Tag_Loc 模型,检出结果存 m_tag_roiList
// 明场传自身 bigmask;暗场不做定位模型,传复用明场的 bigmask
int CheckTagByBigMask(const cv::Mat &img, const cv::Mat &bigmask);
// 在某个把手区域附近找宽高匹配的凸起:返回凸起在 candList 中的下标(-1=没找到)
int MatchHandleRegion(const std::vector<Handle_Candidate_> &candList, const std::vector<bool> &usedList,
const cv::Rect &boxRegion, const Handle_Check_Param &param, cv::Rect &roiHandle);

@ -628,7 +628,7 @@ void ImgCheckAnalysisy::PublishAlignShare(bool bSucc)
}
// 暗场相机不做定位模型:等待并复用明场相机的定位结果
// 返回 true 时:m_pEdge_Align_Result / m_outer_rroi / m_inner_rroi / m_tag_roiList 已用明场结果填充
// 返回 true 时:m_pEdge_Align_Result / m_outer_rroi / m_inner_rroi 已用明场结果填充(标签检测独立执行)
// 返回 false:非复用相机,或复用失败(超时/源失败/无源图/尺寸不一致),调用方走自有定位
bool ImgCheckAnalysisy::TryReuseAlignFromSource(std::string &strSrcCamera, std::string &strSrcChannel, int &nWaitMs)
{
@ -640,13 +640,19 @@ bool ImgCheckAnalysisy::TryReuseAlignFromSource(std::string &strSrcCamera, std::
std::string strCamera;
GetProductCameraStr(strProduct, strCamera);
// 相机名/通道名都要能按 dark->bright 替换,否则本相机不在复用规则内
// 是否复用由相机名决定(明/暗场是相机属性):相机名必须能按 dark->bright 替换
std::string strSrcCam = ReplaceDarkWithBright(strCamera);
std::string strSrcChn = ReplaceDarkWithBright(m_strCurDetChannel);
if (strSrcCam.empty() || strSrcChn.empty())
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;
@ -714,14 +720,13 @@ bool ImgCheckAnalysisy::TryReuseAlignFromSource(std::string &strSrcCamera, std::
}
}
// 复用明场定位结果:ROI/掩码/映射都用明场的
// 复用明场定位结果:ROI/掩码/映射都用明场的(标签检测由 CheckTagByBigMask 独立执行,不用明场的标签)
m_pEdge_Align_Result = pSrc;
m_outer_rroi = pSrc->bigroi[0];
if (!pSrc->smallroi.empty())
{
m_inner_rroi = pSrc->smallroi[0];
}
m_tag_roiList = pSrc->tagroi; // 直接赋值:明场没有标签时清空(避免沿用上一张的标签)
return true;
#endif
}
@ -797,7 +802,7 @@ int ImgCheckAnalysisy::CheckRun()
long time_edge_e;
if (TryReuseAlignFromSource(strAlignSrcCamera, strAlignSrcChannel, nAlignReuseWaitMs))
{
// 复用成功:m_pEdge_Align_Result / m_outer_rroi / m_inner_rroi / m_tag_roiList 已用明场结果填充
// 复用成功: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",
@ -810,6 +815,20 @@ int ImgCheckAnalysisy::CheckRun()
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
{
@ -2688,6 +2707,140 @@ int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &img, const cv::Mat &b
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<cv::Point> 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<AIModel_Base> 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<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> 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<cv::Point2f> 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](允许横竖互换)

@ -1039,7 +1039,8 @@ int deal::SendImgToCheck(std::shared_ptr<JC_IMAGE_INFO_> pDetImageInfo, IN_IMG_S
tem->strImgProductID = pDetImageInfo->strProductID;
tem->imgstr = m_strCurDate;
tem->strCameraName = pDetImageInfo->strCamID;
tem->camera_ID = 0;
// 按相机区分相机ID(明场=0/暗场=1):两个相机通道名相同时,避免结果文件同名互相覆盖
tem->camera_ID = (pDetImageInfo->strCamID.find("dark") != std::string::npos) ? 1 : 0;
if (m_nSaveDetprocessImg == 1)
{
tem->otherValue = 9;

Loading…
Cancel
Save