|
|
|
@ -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:非复用相机,或复用失败(超时/源失败/无源图/尺寸不一致),调用方走自有定位
|
|
|
|
// 返回 false:非复用相机,或复用失败(超时/源失败/无源图/尺寸不一致),调用方走自有定位
|
|
|
|
bool ImgCheckAnalysisy::TryReuseAlignFromSource(std::string &strSrcCamera, std::string &strSrcChannel, int &nWaitMs)
|
|
|
|
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;
|
|
|
|
std::string strCamera;
|
|
|
|
GetProductCameraStr(strProduct, strCamera);
|
|
|
|
GetProductCameraStr(strProduct, strCamera);
|
|
|
|
|
|
|
|
|
|
|
|
// 相机名/通道名都要能按 dark->bright 替换,否则本相机不在复用规则内
|
|
|
|
// 是否复用由相机名决定(明/暗场是相机属性):相机名必须能按 dark->bright 替换
|
|
|
|
std::string strSrcCam = ReplaceDarkWithBright(strCamera);
|
|
|
|
std::string strSrcCam = ReplaceDarkWithBright(strCamera);
|
|
|
|
std::string strSrcChn = ReplaceDarkWithBright(m_strCurDetChannel);
|
|
|
|
if (strSrcCam.empty())
|
|
|
|
if (strSrcCam.empty() || strSrcChn.empty())
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 通道名可能不带 dark/bright(同一面的明/暗场相机共用一个通道名),此时沿用本相机通道名;
|
|
|
|
|
|
|
|
// 若带 dark 则同样替换,保证与源相机发布结果时的 key 一致
|
|
|
|
|
|
|
|
std::string strSrcChn = ReplaceDarkWithBright(m_strCurDetChannel);
|
|
|
|
|
|
|
|
if (strSrcChn.empty())
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
strSrcChn = m_strCurDetChannel;
|
|
|
|
|
|
|
|
}
|
|
|
|
strSrcCamera = strSrcCam;
|
|
|
|
strSrcCamera = strSrcCam;
|
|
|
|
strSrcChannel = strSrcChn;
|
|
|
|
strSrcChannel = strSrcChn;
|
|
|
|
|
|
|
|
|
|
|
|
@ -714,14 +720,13 @@ bool ImgCheckAnalysisy::TryReuseAlignFromSource(std::string &strSrcCamera, std::
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 复用明场定位结果:ROI/掩码/映射都用明场的
|
|
|
|
// 复用明场定位结果:ROI/掩码/映射都用明场的(标签检测由 CheckTagByBigMask 独立执行,不用明场的标签)
|
|
|
|
m_pEdge_Align_Result = pSrc;
|
|
|
|
m_pEdge_Align_Result = pSrc;
|
|
|
|
m_outer_rroi = pSrc->bigroi[0];
|
|
|
|
m_outer_rroi = pSrc->bigroi[0];
|
|
|
|
if (!pSrc->smallroi.empty())
|
|
|
|
if (!pSrc->smallroi.empty())
|
|
|
|
{
|
|
|
|
{
|
|
|
|
m_inner_rroi = pSrc->smallroi[0];
|
|
|
|
m_inner_rroi = pSrc->smallroi[0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m_tag_roiList = pSrc->tagroi; // 直接赋值:明场没有标签时清空(避免沿用上一张的标签)
|
|
|
|
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -797,7 +802,7 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
long time_edge_e;
|
|
|
|
long time_edge_e;
|
|
|
|
if (TryReuseAlignFromSource(strAlignSrcCamera, strAlignSrcChannel, nAlignReuseWaitMs))
|
|
|
|
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);
|
|
|
|
PublishAlignShare(true);
|
|
|
|
time_edge_e = CheckUtil::getcurTime();
|
|
|
|
time_edge_e = CheckUtil::getcurTime();
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "2、pre detect",
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "2、pre detect",
|
|
|
|
@ -810,6 +815,20 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
long handle_e = CheckUtil::getcurTime();
|
|
|
|
long handle_e = CheckUtil::getcurTime();
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "handle detect use time %ld", handle_e - handle_s);
|
|
|
|
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
|
|
|
|
else
|
|
|
|
{
|
|
|
|
{
|
|
|
|
@ -2688,6 +2707,140 @@ int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &img, const cv::Mat &b
|
|
|
|
return 0;
|
|
|
|
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)
|
|
|
|
// 匹配条件:1、凸起中心落在把手区域附近(区域宽/高各外扩 HANDLE_NEAR_EXPAND_RATIO)
|
|
|
|
// 2、凸起宽高与该区域外接矩形宽高的比值都在 [HANDLE_SIZE_MIN_RATIO, HANDLE_SIZE_MAX_RATIO](允许横竖互换)
|
|
|
|
// 2、凸起宽高与该区域外接矩形宽高的比值都在 [HANDLE_SIZE_MIN_RATIO, HANDLE_SIZE_MAX_RATIO](允许横竖互换)
|
|
|
|
|