Compare commits

...

10 Commits

@ -26,18 +26,22 @@
#include <opencv2/opencv.hpp> #include <opencv2/opencv.hpp>
using namespace std; using namespace std;
// 掩膜主体(近似矩形)四周凸起(如把手)的描述 // 掩膜主体(圆角矩形)外的凸起区域(如把手)的描述
struct Mask_Protrusion_ struct Mask_Protrusion_
{ {
int nSide; // 所在边:0 左 1 右 2 上 3 下 int nCorner; // 就近角(产品主轴坐标系):0 左上 1 右上 2 左下 3 右下
cv::Rect roi; // 凸起外接矩形(输入图坐标系,已裁剪到图像范围内) cv::Rect roi; // 凸起外接矩形(输入图坐标系,已裁剪到图像范围内)
float fLen; // 沿该边的长度(像素) cv::Point2f ptCenter; // 凸起质心(输入图坐标系)
float fDepth; // 伸出主体的深度(像素) float fAngle; // 边缘倾角(度,日志排查用)
float fArea; // 凸起面积(roi 内掩膜像素数) float fLen; // 凸起外接矩形长边(像素)
float fFillRatio; // roi 内掩膜像素占比(用于判定是否"类似矩形块") float fDepth; // 凸起相对主体直边的最大伸出深度(像素)
float fArea; // 凸起面积(掩膜像素数)
float fFillRatio; // 凸起外接矩形内掩膜像素占比
Mask_Protrusion_() Mask_Protrusion_()
{ {
nSide = 0; nCorner = 0;
ptCenter = cv::Point2f(0.0f, 0.0f);
fAngle = 0.0f;
fLen = 0.0f; fLen = 0.0f;
fDepth = 0.0f; fDepth = 0.0f;
fArea = 0.0f; fArea = 0.0f;
@ -78,28 +82,36 @@ public:
// 找图片的最大外轮廓 // 找图片的最大外轮廓
static std::vector<cv::Point> getLargestContourROI(const cv::Mat &binaryImg, bool &found); static std::vector<cv::Point> getLargestContourROI(const cv::Mat &binaryImg, bool &found);
// 抠出掩膜四周的凸起:主体近似矩形,凸起在该边表现为边界值的一次突变(进入)与一次反向突变(离开) // 从外轮廓掩膜(outer定位mask)中分离凸起区域(如把手):
// binMask:掩膜(非零为前景,兼容 0/1 与 0/255);protrusions:输出的凸起列表 // 主体是圆角矩形、把手基本出现在四角附近:逐行/逐列扫描掩膜边界,为每条边缘搜索带内点数最多的直线;
// 参数调整影响(nJumpThresh/nMinRun/nMinDepth 为输入图像素,内部按降采样比例 nScale(1/2/4) 缩小后使用;nMaxRun 不换算,直接用工作图像素): // 掩膜中超出四条边缘(超过容差带)的像素按连通域切分成凸起区域(含就近角/质心/面积/深度/填充率)。
// nJumpThresh:边界突变阈值,相邻行/列边界值一次变化超过它才算"进入/离开"台阶; // binMask:掩膜(非零为前景,兼容 0/1 与 0/255);protrusions:输出的凸起列表(按面积从大到小)
// 调大:只认陡峭台阶,圆角/噪声/锯齿被过滤,过大时边缘圆滑的真把手会漏检; // 可调参数(应对实际情况):
// 调小:敏感,噪声/斜边易触发假配对(可能形成跨度很大的假凸起)。 // nMinDepth:凸起最小深度(输入图像素;<=0 时按掩膜短边/150 自动取值,下限 6)
// nMinRun:凸起最小长度("离开"突变搜索从 s+nMinRun 开始;profile 比它短的边直接跳过); // 调大:只报明显外凸的把手,浅把手会漏检,可压住边缘毛刺/噪声;
// 调大:短凸起被排除,若大于真把手长度,真"离开"突变被跳过、会配到更远突变 → 出更大的框; // 调小:浅凸起与噪声都易报出(误报变多)。mask 由模型输出放大而来时直边带量化台阶
// 调小:小抖动/碎框变多。 // (台阶高≈量化步长),建议取 2 个量化步长左右(调用方见 CheckHandleByBigMask)。
// nMinDepth:凸起最小深度,区间内 ≥80% 采样点相对首尾连线基线外偏 ≥ 它(且最大外偏 ≥ 它)才保留(滤斜边/圆角); // nMinArea:凸起最小面积(输入图像素²;<=0 时按 nMinDepth² 自动取值)
// 调大:只保留明显凸出的,浅凸起漏检,可挡"浅而长"的假配对; // 调大:小碎块被过滤,把手很小或被遮挡一部分时可能漏检;
// 调小:浅波纹/边缘抖动也过检,假凸起变多。 // 调小:碎块残留为候选的几率变大(仍会被 nMinDepth 再过滤一遍)。
// nMaxRun:凸起最大长度(框沿边长度的硬上限);<=0 时取 max(100, 掩膜短边/3)(工作图像素); // nEdgeMargin:边缘外扩量(输入图像素;<=0 时自动取“最小深度的一半”)
// 调大:允许远距离突变配对 → 会出"覆盖整边"的大框;调小:框被截短,小于真把手长度时真把手漏检。 // 把四条边缘线整体向外推该距离后再判凸起,等效于把凸起区域相应减小:
// 调大:边缘顺边毛刺/线条偏差被吞掉,凸起区域更小更干净;过大时浅把手漏检;
// 调小:更灵敏,但边缘毛刺容易连进凸起区域。
// 边缘拟合的细节参数(带宽容差/斜率上限)见 FitEdgeLine 注释。
// pBodyQuad:可选输出,拟合出的主体四边形(左上/右上/右下/左下,输入图坐标),仅 -s 存图观察用
// 返回:0 成功、1 掩膜为空、2 无有效前景 // 返回:0 成功、1 掩膜为空、2 无有效前景
static int GetMaskProtrusions(const cv::Mat &binMask, std::vector<Mask_Protrusion_> &protrusions, static int GetMaskProtrusions(const cv::Mat &binMask, std::vector<Mask_Protrusion_> &protrusions,
int nJumpThresh = 40, int nMinRun = 40, int nMinDepth = 40, int nMaxRun = 0); int nMinDepth = 0, int nMinArea = 0, std::vector<cv::Point2f> *pBodyQuad = NULL,
int nEdgeMargin = 0);
static std::string GetRectString(cv::Rect rect); static std::string GetRectString(cv::Rect rect);
//创建目录 //创建目录
static int CreateDir(const std::string &dir); static int CreateDir(const std::string &dir);
// 异步存图
static int SaveImgAsync(const std::string &path, const cv::Mat &img);
// 点的距离是否过小 // 点的距离是否过小
static bool bcalDis(cv::Point p1, cv::Point p2, int disT); static bool bcalDis(cv::Point p1, cv::Point p2, int disT);
static double calDis(cv::Point p1, cv::Point p2); static double calDis(cv::Point p1, cv::Point p2);

@ -50,19 +50,6 @@ enum AT_THRESHOLD_TYPE_
// 全局静态变量, 记录图像灰度值异常累计数量 // 全局静态变量, 记录图像灰度值异常累计数量
static int g_nImgBrightnessErrorCount = 0; static int g_nImgBrightnessErrorCount = 0;
// 把手候选:bigmask 凸起 + 实测特征(面积/填充率,仅供日志排查)
struct Handle_Candidate_
{
Mask_Protrusion_ protr; // 凸起几何信息(roi / 沿边长度 / 深度 / 面积 / 填充率)
int nLen; // 沿边长度(像素)
int nDepth; // 垂直深度(像素)
Handle_Candidate_()
{
nLen = 0;
nDepth = 0;
}
};
// 单个把手区域的判定结果 // 单个把手区域的判定结果
struct Handle_Region_Result_ struct Handle_Region_Result_
{ {
@ -90,11 +77,17 @@ struct Support_Draw_Info_
float fDist; // 实测距离(原图像素) float fDist; // 实测距离(原图像素)
float fStandard; // 标准距离(原图像素) float fStandard; // 标准距离(原图像素)
bool bExceed; // |fDist-fStandard| 超限差(x_offset) -> 红色 bool bExceed; // |fDist-fStandard| 超限差(x_offset) -> 红色
cv::Point2f ptEdge; // 连线端点:所计算边缘上、支架中心行/列处的点(原图坐标)
cv::Point2f ptEdgeA; // 所计算边缘线段端点A(原图坐标)
cv::Point2f ptEdgeB; // 所计算边缘线段端点B(原图坐标)
Edge_Draw_() Edge_Draw_()
{ {
fDist = 0.0f; fDist = 0.0f;
fStandard = 0.0f; fStandard = 0.0f;
bExceed = false; bExceed = false;
ptEdge = cv::Point2f(0.0f, 0.0f);
ptEdgeA = cv::Point2f(0.0f, 0.0f);
ptEdgeB = cv::Point2f(0.0f, 0.0f);
} }
}; };
bool bValid; // 本次是否有效计算出支架偏移几何 bool bValid; // 本次是否有效计算出支架偏移几何
@ -194,13 +187,19 @@ private:
bool TryReuseAlignFromSource(std::string &strSrcCamera, std::string &strSrcChannel, int &nWaitMs); bool TryReuseAlignFromSource(std::string &strSrcCamera, std::string &strSrcChannel, int &nWaitMs);
// 发布本图定位结果/失败标记(供同产品其它相机复用) // 发布本图定位结果/失败标记(供同产品其它相机复用)
void PublishAlignShare(bool bSucc); void PublishAlignShare(bool bSucc);
// 用 bigmask 检测把手(bigmask 主体近似矩形,把手是四周凸起的矩形块) // 用 outer 定位 mask(bigmask) 检测把手:拟合主体上/下/左/右四条边缘,
// 把边缘外的凸起按连通域分离为把手候选(见 CheckUtil::GetMaskProtrusions),
// 再与参数的把手区域匹配;某个区域附近找不到匹配凸起 → 该区域把手缺失
// -s(存过程图)时输出 {通道}_handle_det.jpg:绿色四边形=拟合主体、红框=凸起(带编号/角/深度)、
// 蓝框=参数把手区域、绿框=匹配到的把手,便于从图像排查
int CheckHandleByBigMask(const cv::Mat &bigmask); int CheckHandleByBigMask(const cv::Mat &bigmask);
// 标签检测(独立步骤):用 (图像 + bigmask) 跑 Tag_Loc 模型,检出结果存 m_tag_roiList // 标签检测(独立步骤):用 (图像 + bigmask) 跑 Tag_Loc 模型,检出结果存 m_tag_roiList
// 明场传自身 bigmask;暗场不做定位模型,传复用明场的 bigmask // 明场传自身 bigmask;暗场不做定位模型,传复用明场的 bigmask
int CheckTagByBigMask(const cv::Mat &img, const cv::Mat &bigmask); int CheckTagByBigMask(const cv::Mat &img, const cv::Mat &bigmask);
// 在某个把手区域附近找宽高匹配的凸起:返回凸起在 candList 中的下标(-1=没找到) // 在某个把手区域附近找宽高匹配的凸起:返回凸起在 candList 中的下标(-1=没找到)
int MatchHandleRegion(const std::vector<Handle_Candidate_> &candList, const std::vector<bool> &usedList, // 匹配参数(搜索范围 handle_search_ratio、大小比区间 handle_size_min/handle_size_max)
// 可 JSON(Handle_Detect.form.handle_param)配置,未配置时用 cpp 顶部宏默认值,见 ImgCheckAnalysisy.cpp 参数注释
int MatchHandleRegion(const std::vector<Mask_Protrusion_> &candList, const std::vector<bool> &usedList,
const cv::Rect &boxRegion, cv::Rect &roiHandle); const cv::Rect &boxRegion, cv::Rect &roiHandle);
// 计算产品尺寸 // 计算产品尺寸
int CalProductSize(); int CalProductSize();
@ -381,9 +380,10 @@ private:
cv::RotatedRect m_inner_rroi; cv::RotatedRect m_inner_rroi;
std::vector<cv::RotatedRect> m_tag_roiList; std::vector<cv::RotatedRect> m_tag_roiList;
// 把手检测结果(bigmask 四周凸起 + 各把手区域判定结果) // 把手检测结果(从 outer 定位 mask 分离的凸起区域 + 各把手区域判定结果)
std::vector<Handle_Candidate_> m_HandleCandList; // 从 bigmask 四周抠出的凸起候选 + 实测特征 std::vector<Mask_Protrusion_> m_HandleProtrusionList; // 分离出的凸起区域(把手候选:roi/质心/深度/就近角等)
std::vector<Handle_Region_Result_> m_HandleRegionResultList; // 各把手区域(最多4个)的判定结果 std::vector<Handle_Region_Result_> m_HandleRegionResultList; // 各把手区域(最多4个)的判定结果
std::vector<cv::Point2f> m_HandleBodyQuad; // 拟合出的主体四边形(TL/TR/BR/BL,原图坐标;支架偏移检测复用其四边抗凸起)
bool m_bHandleDetSucc; // 本次是否完成把手检测 bool m_bHandleDetSucc; // 本次是否完成把手检测
bool m_bHandleFound; // 所有非空把手区域是否都找到了把手 bool m_bHandleFound; // 所有非空把手区域是否都找到了把手
bool m_bHandleExpect; // 是否开启了把手缺失检测(且有非空把手区域) bool m_bHandleExpect; // 是否开启了把手缺失检测(且有非空把手区域)

@ -19,6 +19,8 @@
#include <stdlib.h> #include <stdlib.h>
#include <fstream> #include <fstream>
#include <thread> #include <thread>
#include <algorithm>
#include <cmath>
#include "snowflake.hpp" #include "snowflake.hpp"
int _sysmkdir_2(const std::string &dir) int _sysmkdir_2(const std::string &dir)
@ -89,9 +91,9 @@ std::string CheckUtil::getCurTimeHMS()
int millisec = tv.tv_usec / 1000; // 微秒转毫秒 int millisec = tv.tv_usec / 1000; // 微秒转毫秒
std::ostringstream oss; std::ostringstream oss;
oss << std::setfill('0') << std::setw(2) << time_info->tm_hour << ":" oss << std::setfill('0') << std::setw(2) << time_info->tm_hour << "-"
<< std::setfill('0') << std::setw(2) << time_info->tm_min << ":" << std::setfill('0') << std::setw(2) << time_info->tm_min << "-"
<< std::setfill('0') << std::setw(2) << time_info->tm_sec << "." << std::setfill('0') << std::setw(2) << time_info->tm_sec << "-"
<< std::setfill('0') << std::setw(3) << millisec; << std::setfill('0') << std::setw(3) << millisec;
return oss.str(); // 返回格式 "HH:MM:SS.mmm" return oss.str(); // 返回格式 "HH:MM:SS.mmm"
@ -501,6 +503,32 @@ int CheckUtil::CreateDir(const std::string &dir)
return ret; return ret;
} }
int CheckUtil::SaveImgAsync(const std::string &path, const cv::Mat &img)
{
if (path.empty() || img.empty())
{
return -1;
}
// 拷贝图像,避免异步线程执行时原图像被释放或修改
cv::Mat imgCopy = img.clone();
// 创建保存路径的父目录
std::string::size_type pos = path.find_last_of('/');
if (pos != std::string::npos)
{
std::string parentDir = path.substr(0, pos);
CheckUtil::CreateDir(parentDir);
}
// 启动后台线程异步存图
std::thread([path, imgCopy]() {
cv::imwrite(path, imgCopy);
}).detach();
return 0;
}
bool CheckUtil::bcalDis(cv::Point p1, cv::Point p2, int disT) bool CheckUtil::bcalDis(cv::Point p1, cv::Point p2, int disT)
{ {
double dis = std::sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)); double dis = std::sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
@ -694,8 +722,144 @@ cv::Point2f CheckUtil::transformPoint(const cv::Point2f &point, const cv::Mat &t
return cv::Point2f(result_mat.at<double>(0), result_mat.at<double>(1)); return cv::Point2f(result_mat.at<double>(0), result_mat.at<double>(1));
} }
// 拟合边缘直线:val ≈ fK * idx + fB(点集为 (扫描序号, 边界值))
// 直边检测:在 |k| <= fMaxSlope 的斜率范围内搜索“带宽 ±fTol 内点数最多”的直线:
// 主体直边段的点最多且共线 → 被选中;把手段(点数更少)、角部过渡/另一侧边缘
// (斜率超出限制)等都不会获胜。随后在带内点集上做最小二乘精修(两轮收紧容差)。
// 可调参数(应对实际情况):
// fTol:带宽容差(工作图像素)。调大→主体边缘带变宽,浅把手更容易被并入主体(漏检)、
// 但更能压住量化台阶毛刺;调小→台阶噪声可能分裂支撑或拟合到非主体结构。
// 默认由调用方按“量化台阶+2px”传入(见 GetMaskProtrusions 第 5 步)。
// fMaxSlope:直线搜索斜率上限。调大→允许产品倾斜更大;过大时角部斜过渡段可能
// 被当成直边;调小→产品倾斜稍大时拟合失败(退回常量线)。
// 点集为空返回 false;点太少/无有效直线时退回常量线(值取中位数)
static bool FitEdgeLine(const std::vector<cv::Point2f> &pts, double fTol, double fMaxSlope, double &fK, double &fB)
{
if (pts.empty())
{
return false;
}
// 常量线兜底:值取中位数
fK = 0.0;
{
std::vector<float> valList;
valList.reserve(pts.size());
for (size_t i = 0; i < pts.size(); i++)
{
valList.push_back(pts[i].y);
}
std::nth_element(valList.begin(), valList.begin() + valList.size() / 2, valList.end());
fB = (double)valList[valList.size() / 2];
}
if (pts.size() < 16)
{
return true; // 点太少:用常量线兜底
}
// 1、粗搜索:斜率网格 × 抽样锚点,取带内点数最多的直线
int nBestCnt = -1;
double fBestK = 0.0;
double fBestB = fB;
const size_t nStride = std::max<size_t>(1, pts.size() / 64);
auto fnCount = [&](double fk, double fb) -> int {
int nCnt = 0;
for (size_t i = 0; i < pts.size(); i++)
{
if (std::fabs((double)pts[i].y - (fk * pts[i].x + fb)) <= fTol)
{
nCnt++;
}
}
return nCnt;
};
const int nCoarseN = (int)(fMaxSlope / 0.03 + 0.5); // 斜率粗网格:±nCoarseN×0.03
for (int nk = -nCoarseN; nk <= nCoarseN; nk++)
{
double fk = nk * 0.03;
for (size_t i = 0; i < pts.size(); i += nStride)
{
double fb = (double)pts[i].y - fk * (double)pts[i].x;
int nCnt = fnCount(fk, fb);
if (nCnt > nBestCnt)
{
nBestCnt = nCnt;
fBestK = fk;
fBestB = fb;
}
}
}
// 2、细搜索:在粗搜索最优斜率附近收紧网格
for (int nk = -6; nk <= 6; nk++)
{
double fk = fBestK + nk * 0.005;
for (size_t i = 0; i < pts.size(); i += nStride)
{
double fb = (double)pts[i].y - fk * (double)pts[i].x;
int nCnt = fnCount(fk, fb);
if (nCnt > nBestCnt)
{
nBestCnt = nCnt;
fBestK = fk;
fBestB = fb;
}
}
}
// 3、带内点最小二乘精修(两轮收紧容差)
fK = fBestK;
fB = fBestB;
double fUseTol = fTol;
for (int nIter = 0; nIter < 2; nIter++)
{
std::vector<cv::Point2f> inl;
inl.reserve(pts.size());
for (size_t i = 0; i < pts.size(); i++)
{
if (std::fabs((double)pts[i].y - (fK * pts[i].x + fB)) <= fUseTol)
{
inl.push_back(pts[i]);
}
}
if (inl.size() < 8)
{
break;
}
cv::Vec4f line;
cv::fitLine(inl, line, cv::DIST_L2, 0, 1e-3, 1e-3);
if (std::fabs((double)line[0]) > 1e-6)
{
fK = (double)line[1] / (double)line[0];
fB = (double)line[3] - fK * (double)line[2];
}
fUseTol = std::max(3.0, fUseTol * 0.5);
}
return true;
}
// 从外轮廓掩膜(outer定位mask,如bigmask)中分离凸起区域(如把手):
// 产品主体是圆角矩形、把手基本出现在四角附近,把手在掩膜上表现为超出主体四条边缘的外凸块:
// 1、取最大连通域为主体;
// 2、逐行/逐列扫描主体边界,得到 左/右(x=f(y))、上/下(y=f(x)) 四条边缘的边界点集;
// 3、对每条边缘做直线拟合(见 FitEdgeLine:取带内点数最多的直线,允许产品小角度倾斜);
// 4、掩膜中超出四条边缘(超过容差带)的像素按连通域切分成凸起区域,
// 并按质心所在象限给出就近角(0 左上 1 右上 2 左下 3 右下);深度/面积不足的量化毛刺被过滤。
// 可调参数(应对实际情况):
// nMinDepth:凸起最小深度(输入图像素;<=0 时按掩膜短边/150 自动取值,下限 6)
// 调大:只报明显外凸的把手,浅把手会漏检,可压住边缘毛刺/噪声;
// 调小:浅凸起与噪声都易报出(误报变多)。mask 由模型输出放大而来时直边带量化台阶
// (台阶高≈量化步长),建议取 2 个量化步长左右(调用方见 CheckHandleByBigMask)。
// nMinArea:凸起最小面积(输入图像素²;<=0 时按 nMinDepth² 自动取值)
// 调大:小碎块被过滤,把手很小或被遮挡一部分时可能漏检;
// 调小:碎块残留为候选的几率变大(仍会被 nMinDepth 再过滤一遍)。
// nEdgeMargin:边缘外扩量(输入图像素;<=0 时自动取“最小深度的一半”)
// 把四条边缘线整体向外推该距离后再判凸起,等效于把凸起区域相应减小:
// 调大:边缘顺边毛刺/线条偏差被吞掉,凸起区域更小更干净;过大时浅把手漏检;
// 调小:更灵敏,但边缘毛刺容易连进凸起区域。
// 边缘拟合的细节参数(带宽容差/斜率上限)见 FitEdgeLine 注释。
// pBodyQuad:可选输出,拟合出的主体四边形(左上/右上/右下/左下,输入图坐标),仅 -s 存图观察用
// 返回:0 成功、1 掩膜为空、2 无有效前景
int CheckUtil::GetMaskProtrusions(const cv::Mat &binMask, std::vector<Mask_Protrusion_> &protrusions, int CheckUtil::GetMaskProtrusions(const cv::Mat &binMask, std::vector<Mask_Protrusion_> &protrusions,
int nJumpThresh, int nMinRun, int nMinDepth, int nMaxRun) int nMinDepth, int nMinArea, std::vector<cv::Point2f> *pBodyQuad, int nEdgeMargin)
{ {
protrusions.clear(); protrusions.clear();
if (binMask.empty()) if (binMask.empty())
@ -716,7 +880,17 @@ int CheckUtil::GetMaskProtrusions(const cv::Mat &binMask, std::vector<Mask_Protr
cv::Mat maskFull; cv::Mat maskFull;
cv::threshold(gray, maskFull, 0, 255, cv::THRESH_BINARY); cv::threshold(gray, maskFull, 0, 255, cv::THRESH_BINARY);
// 1.1 大掩膜先降采样再分析:凸起尺寸远大于降采样误差,可显著降低耗时 // 参数默认值按输入图尺度取
if (nMinDepth <= 0)
{
nMinDepth = std::max(6, std::min(maskFull.cols, maskFull.rows) / 150);
}
if (nMinArea <= 0)
{
nMinArea = std::max(64, nMinDepth * nMinDepth);
}
// 2、大掩膜先降采样再分析:凸起尺寸远大于降采样误差,可显著降低耗时
int nScale = 1; int nScale = 1;
while (nScale < 4 && std::max(maskFull.cols, maskFull.rows) / nScale > 2000) while (nScale < 4 && std::max(maskFull.cols, maskFull.rows) / nScale > 2000)
{ {
@ -731,12 +905,11 @@ int CheckUtil::GetMaskProtrusions(const cv::Mat &binMask, std::vector<Mask_Protr
{ {
mask = maskFull; mask = maskFull;
} }
// 阈值按降采样比例换算到工作图尺度 // 阈值按比例换算到工作图尺度
nJumpThresh = std::max(2, nJumpThresh / nScale); const int nMinDepthWork = std::max(2, nMinDepth / nScale);
nMinRun = std::max(3, nMinRun / nScale); const int nMinAreaWork = std::max(4, nMinArea / (nScale * nScale));
nMinDepth = std::max(2, nMinDepth / nScale);
// 2、只保留最大连通域,避免背景杂物参与边界统计 // 3、只保留最大连通域作为主体(过滤背景杂物)
cv::Mat labels, stats, centroids; cv::Mat labels, stats, centroids;
int nLabels = cv::connectedComponentsWithStats(mask, labels, stats, centroids, 8, CV_32S); int nLabels = cv::connectedComponentsWithStats(mask, labels, stats, centroids, 8, CV_32S);
if (nLabels <= 1) if (nLabels <= 1)
@ -754,8 +927,6 @@ int CheckUtil::GetMaskProtrusions(const cv::Mat &binMask, std::vector<Mask_Protr
nMainIdx = i; nMainIdx = i;
} }
} }
// 3、裁剪到主体外接矩形,缩小后续扫描范围
cv::Rect boxMain(stats.at<int>(nMainIdx, cv::CC_STAT_LEFT), stats.at<int>(nMainIdx, cv::CC_STAT_TOP), cv::Rect boxMain(stats.at<int>(nMainIdx, cv::CC_STAT_LEFT), stats.at<int>(nMainIdx, cv::CC_STAT_TOP),
stats.at<int>(nMainIdx, cv::CC_STAT_WIDTH), stats.at<int>(nMainIdx, cv::CC_STAT_HEIGHT)); stats.at<int>(nMainIdx, cv::CC_STAT_WIDTH), stats.at<int>(nMainIdx, cv::CC_STAT_HEIGHT));
boxMain &= cv::Rect(0, 0, mask.cols, mask.rows); boxMain &= cv::Rect(0, 0, mask.cols, mask.rows);
@ -763,201 +934,216 @@ int CheckUtil::GetMaskProtrusions(const cv::Mat &binMask, std::vector<Mask_Protr
{ {
return 2; return 2;
} }
cv::Mat sub = (labels(boxMain) == nMainIdx); // 0/255 cv::Mat mainMask;
const int H = sub.rows; cv::compare(labels(boxMain), nMainIdx, mainMask, cv::CMP_EQ); // 主体连通域(0/255)
const int W = sub.cols; const int H = boxMain.height;
if (nMaxRun <= 0) const int W = boxMain.width;
// 4、逐行/逐列扫描主体边界,收集四条边缘的边界点集(工作图坐标)
std::vector<cv::Point2f> ptsL, ptsR, ptsT, ptsB;
ptsL.reserve(H);
ptsR.reserve(H);
ptsT.reserve(W);
ptsB.reserve(W);
for (int y = 0; y < H; y++)
{ {
nMaxRun = std::max(100, std::min(H, W) / 3); const uchar *p = mainMask.ptr<uchar>(y);
int nXL = -1;
int nXR = -1;
for (int x = 0; x < W; x++)
{
if (p[x]) { nXL = x; break; }
}
for (int x = W - 1; x >= 0; x--)
{
if (p[x]) { nXR = x; break; }
}
if (nXL >= 0)
{
ptsL.emplace_back((float)(y + boxMain.y), (float)(nXL + boxMain.x)); // (行号, 左边界的x)
}
if (nXR >= 0)
{
ptsR.emplace_back((float)(y + boxMain.y), (float)(nXR + boxMain.x)); // (行号, 右边界的x)
}
} }
for (int x = 0; x < W; x++)
// 4、逐边抠凸起:
// 主体近似矩形,矩形边上的凸起会让该边的"边界值"先突变到外侧(进入凸起)、再突变回主体(离开凸起);
// 区间内的边界值相对"首尾边界值线性插值"得到的基线持续外偏,即判定为一个凸起。
for (int nSide = 0; nSide < 4; nSide++)
{ {
const bool bRowScan = (nSide < 2); // 左右边按行扫描,上下边按列扫描 int nYT = -1;
const int nLine = bRowScan ? H : W; // 扫描的行数/列数 int nYB = -1;
const int nDir = (nSide == 0 || nSide == 2) ? 1 : -1; // 凸起内边界值变小(左侧/上侧)取 +1,变大取 -1 for (int y = 0; y < H; y++)
// 4.1 从每条 行/列 的外侧向内找第一个前景像素,得到该边的边界值 profile
std::vector<int> posList; // 有前景的行号/列号
std::vector<int> valList; // 对应的边界值
posList.reserve(nLine);
valList.reserve(nLine);
for (int i = 0; i < nLine; i++)
{ {
int nV = -1; if (mainMask.ptr<uchar>(y)[x]) { nYT = y; break; }
if (nSide == 0) // 左:自左向右 }
{ for (int y = H - 1; y >= 0; y--)
const uchar *p = sub.ptr<uchar>(i); {
for (int x = 0; x < W; x++) if (mainMask.ptr<uchar>(y)[x]) { nYB = y; break; }
{ }
if (p[x]) if (nYT >= 0)
{ {
nV = x; ptsT.emplace_back((float)(x + boxMain.x), (float)(nYT + boxMain.y)); // (列号, 上边界的y)
break; }
} if (nYB >= 0)
} {
} ptsB.emplace_back((float)(x + boxMain.x), (float)(nYB + boxMain.y)); // (列号, 下边界的y)
else if (nSide == 1) // 右:自右向左
{
const uchar *p = sub.ptr<uchar>(i);
for (int x = W - 1; x >= 0; x--)
{
if (p[x])
{
nV = x;
break;
}
}
}
else if (nSide == 2) // 上:自上向下
{
for (int y = 0; y < H; y++)
{
if (sub.ptr<uchar>(y)[i])
{
nV = y;
break;
}
}
}
else // 下:自下向上
{
for (int y = H - 1; y >= 0; y--)
{
if (sub.ptr<uchar>(y)[i])
{
nV = y;
break;
}
}
}
if (nV >= 0)
{
posList.push_back(i);
valList.push_back(nV);
}
} }
}
const int n = (int)valList.size(); // 5、拟合四条边缘直线:左/右 x=f(y)、上/下 y=f(x)
if (n < nMinRun + 2) // 直边判定:|k|<=0.6(近轴对齐)且带内(±fTol)点数最多的直线;fTol 覆盖量化台阶+网格误差
const int nFitStep = std::max(1, nMinDepthWork / 2); // 量化台阶估计(工作图)
// nExp:边缘外扩量(工作图像素)。显式给定(nEdgeMargin>0)时把边缘线整体向外推该距离,
// 等效于减小凸起区域;未给定时按最小深度的一半自动取值
const int nExp = (nEdgeMargin > 0) ? std::max(1, nEdgeMargin / nScale) : nFitStep;
const double fTol = std::max(6.0, (double)(nFitStep + 2));
double fKL = 0, fBL = 0, fKR = 0, fBR = 0, fKT = 0, fBT = 0, fKB = 0, fBB = 0;
if (!FitEdgeLine(ptsL, fTol, 0.6, fKL, fBL) || !FitEdgeLine(ptsR, fTol, 0.6, fKR, fBR) ||
!FitEdgeLine(ptsT, fTol, 0.6, fKT, fBT) || !FitEdgeLine(ptsB, fTol, 0.6, fKB, fBB))
{
return 2;
}
// 主体中心(工作图坐标)与边缘合理性检查
const float fXc = boxMain.x + W * 0.5f;
const float fYc = boxMain.y + H * 0.5f;
if ((float)(fKR * fYc + fBR) - (float)(fKL * fYc + fBL) < 4.0f ||
(float)(fKB * fXc + fBB) - (float)(fKT * fXc + fBT) < 4.0f)
{
return 2;
}
// 可选输出:主体四边形(四角=相邻两条边缘直线的交点),供 -s 存图观察拟合效果;
// 左/右边缘: x = k*y + b;上/下边缘: y = k*x + b,联立求交(行列式≈0 说明两线平行,放弃)
if (pBodyQuad != NULL)
{
pBodyQuad->clear();
auto fnCross = [](double fkX, double fbX, double fkY, double fbY, cv::Point2f &pt) -> bool {
double fDet = 1.0 - fkX * fkY;
if (std::fabs(fDet) < 1e-6)
{
return false;
}
pt.x = (float)((fbX + fkX * fbY) / fDet);
pt.y = (float)(fkY * pt.x + fbY);
return true;
};
cv::Point2f ptTL, ptTR, ptBR, ptBL;
if (fnCross(fKL, fBL, fKT, fBT, ptTL) && fnCross(fKR, fBR, fKT, fBT, ptTR) &&
fnCross(fKR, fBR, fKB, fBB, ptBR) && fnCross(fKL, fBL, fKB, fBB, ptBL))
{ {
continue; // 工作图坐标 -> 输入图坐标
pBodyQuad->push_back(cv::Point2f(ptTL.x * nScale, ptTL.y * nScale)); // 左上
pBodyQuad->push_back(cv::Point2f(ptTR.x * nScale, ptTR.y * nScale)); // 右上
pBodyQuad->push_back(cv::Point2f(ptBR.x * nScale, ptBR.y * nScale)); // 右下
pBodyQuad->push_back(cv::Point2f(ptBL.x * nScale, ptBL.y * nScale)); // 左下
} }
}
// 4.2 配对"进入/离开"突变,扣除该边上的凸起 // 6、凸起 = 超出四条边缘(超过容差带 nExp)的掩膜像素;容差带吸收边缘量化毛刺,
int nUsedTo = -1; // 已统计过的凸起末端,避免同一个凸起重复输出 // 并可由 nEdgeMargin 显式加大(边缘线整体外推)来压制顺边毛刺/减小凸起区域
for (int s = 0; s + 1 < n; s++) cv::Mat protrMask = cv::Mat::zeros(mask.size(), CV_8UC1);
for (int y = boxMain.y; y < boxMain.y + H; y++)
{
const uchar *pSrc = mainMask.ptr<uchar>(y - boxMain.y);
uchar *pDst = protrMask.ptr<uchar>(y);
const float fLineL = (float)(fKL * y + fBL) - nExp;
const float fLineR = (float)(fKR * y + fBR) + nExp;
for (int x = boxMain.x; x < boxMain.x + W; x++)
{ {
if (s <= nUsedTo) if (!pSrc[x - boxMain.x])
{ {
continue; continue;
} }
// 进入凸起:边界值朝外侧突变 if (x < fLineL || x > fLineR)
if ((valList[s + 1] - valList[s]) * nDir >= -nJumpThresh)
{ {
pDst[x] = 255;
continue; continue;
} }
// 离开凸起:在其后 nMaxRun 范围内找第一个反向突变 if (y < (float)(fKT * x + fBT) - nExp || y > (float)(fKB * x + fBB) + nExp)
int e = -1;
for (int j = s + nMinRun; j + 1 < n && j - s <= nMaxRun; j++)
{ {
if ((valList[j + 1] - valList[j]) * nDir > nJumpThresh) pDst[x] = 255;
{
e = j;
break;
}
}
if (e < 0)
{
continue;
} }
}
}
// 4.3 区间内的边界值应持续超出"首尾边界值线性插值"得到的基线 // 7、按连通域切分成凸起区域:深度(相对四条边缘)/面积不足的毛刺过滤
const int kk = e - s; cv::Mat labelsP, statsP, centP;
int nDevOk = 0; int nP = cv::connectedComponentsWithStats(protrMask, labelsP, statsP, centP, 8, CV_32S);
float fMaxDev = 0.0f; if (nP <= 1)
int nLow = valList[s + 1]; {
int nHigh = valList[s + 1]; return 0; // 无凸起
for (int i = 1; i <= kk; i++) }
{
float fBase = valList[s] + (valList[e + 1] - valList[s]) * (float)i / (float)(kk + 1);
float fDev = (fBase - valList[s + i]) * nDir;
fMaxDev = std::max(fMaxDev, fDev);
if (fDev >= nMinDepth)
{
nDevOk++;
}
nLow = std::min(nLow, valList[s + i]);
nHigh = std::max(nHigh, valList[s + i]);
}
if (fMaxDev < nMinDepth)
{
continue;
}
// 凸起内大部分采样点都要明显外偏,避免把主体的斜边/圆角当成凸起
if (nDevOk < (int)(kk * 0.8f))
{
continue;
}
// 4.4 计算凸起外接矩形(先算在 sub 图上的位置) for (int i = 1; i < nP; i++)
const int nLen = posList[e] - posList[s + 1] + 1; // 沿该边的长度 {
cv::Rect roiSub; int nArea = statsP.at<int>(i, cv::CC_STAT_AREA);
if (nSide == 0 || nSide == 2) // 左/上:外侧取小值,主体边缘取大值 if (nArea < nMinAreaWork)
{
continue;
}
int nX = statsP.at<int>(i, cv::CC_STAT_LEFT);
int nY = statsP.at<int>(i, cv::CC_STAT_TOP);
int nW = statsP.at<int>(i, cv::CC_STAT_WIDTH);
int nH = statsP.at<int>(i, cv::CC_STAT_HEIGHT);
// 深度:组件像素相对四条边缘线的最大外伸(工作图像素)
float fDepth = 0.0f;
for (int yy = nY; yy < nY + nH; yy++)
{
const int *pLab = labelsP.ptr<int>(yy);
const float fLineL = (float)(fKL * yy + fBL);
const float fLineR = (float)(fKR * yy + fBR);
for (int xx = nX; xx < nX + nW; xx++)
{ {
int nOut = nLow; if (pLab[xx] != i)
int nInner = std::max(valList[s], valList[e + 1]);
if (nSide == 0)
{
roiSub = cv::Rect(nOut, posList[s + 1], nInner - nOut + 1, nLen);
}
else
{ {
roiSub = cv::Rect(posList[s + 1], nOut, nLen, nInner - nOut + 1); continue;
} }
// 到四条边缘的最大外伸(在边缘内侧为负,取 0 忽略)
float fDev = std::max(fLineL - xx, xx - fLineR);
fDev = std::max(fDev, (float)(fKT * xx + fBT) - yy);
fDev = std::max(fDev, yy - (float)(fKB * xx + fBB));
fDepth = std::max(fDepth, fDev);
} }
else // 右/下:外侧取大值,主体边缘取小值 }
{ if (fDepth < nMinDepthWork)
int nOut = nHigh; {
int nInner = std::min(valList[s], valList[e + 1]); continue;
if (nSide == 1) }
{
roiSub = cv::Rect(nInner, posList[s + 1], nOut - nInner + 1, nLen);
}
else
{
roiSub = cv::Rect(posList[s + 1], nInner, nLen, nOut - nInner + 1);
}
}
roiSub &= cv::Rect(0, 0, W, H);
if (roiSub.width <= 0 || roiSub.height <= 0)
{
continue;
}
Mask_Protrusion_ protr;
protr.nSide = nSide;
// 从工作图尺度还原到输入图尺度
protr.roi = cv::Rect((roiSub.x + boxMain.x) * nScale, (roiSub.y + boxMain.y) * nScale,
roiSub.width * nScale, roiSub.height * nScale);
protr.roi &= cv::Rect(0, 0, binMask.cols, binMask.rows);
if (protr.roi.width <= 0 || protr.roi.height <= 0)
{
continue;
}
protr.fLen = (float)nLen * nScale;
protr.fDepth = fMaxDev * nScale;
int nMaskArea = cv::countNonZero(sub(roiSub));
protr.fArea = (float)nMaskArea * nScale * nScale;
protr.fFillRatio = (float)nMaskArea / (float)(roiSub.width * roiSub.height);
protrusions.push_back(protr); // 特征 + 坐标(工作图 -> 输入图)
nUsedTo = e; // 该凸起区间不再重复统计 Mask_Protrusion_ protr;
protr.roi = cv::Rect(nX * nScale, nY * nScale, nW * nScale, nH * nScale);
protr.roi &= cv::Rect(0, 0, binMask.cols, binMask.rows);
if (protr.roi.width <= 0 || protr.roi.height <= 0)
{
continue;
}
cv::Point2f ptCen;
if (centP.type() == CV_32F)
{
ptCen = centP.at<cv::Point2f>(i);
}
else
{
cv::Vec2d vCen = centP.at<cv::Vec2d>(i);
ptCen = cv::Point2f((float)vCen[0], (float)vCen[1]);
} }
protr.ptCenter = cv::Point2f(ptCen.x * nScale, ptCen.y * nScale);
// 就近角:按主体中心分象限 -> 0 左上 1 右上 2 左下 3 右下
bool bRight = ptCen.x > fXc;
bool bBottom = ptCen.y > fYc;
protr.nCorner = bBottom ? (bRight ? 3 : 2) : (bRight ? 1 : 0);
protr.fAngle = (float)(std::atan((fKL + fKR) * 0.5) * 180.0 / CV_PI); // 左右边缘平均倾角(度,相对垂直方向)
protr.fLen = (float)std::max(nW, nH) * nScale;
protr.fDepth = fDepth * nScale;
protr.fArea = (float)nArea * nScale * nScale;
protr.fFillRatio = (float)nArea / (float)(nW * nH);
protrusions.push_back(protr);
} }
// 按面积从大到小排序(日志与匹配更稳定)
std::sort(protrusions.begin(), protrusions.end(),
[](const Mask_Protrusion_ &a, const Mask_Protrusion_ &b) { return a.fArea > b.fArea; });
return 0; return 0;
} }

@ -426,15 +426,17 @@ int ImageResultJudge::ResultJudge(std::shared_ptr<ImageAllResult> pImageResult)
} }
} }
// 标签缺陷(TBBQ):检测到标签直接判 NG std::string resultType = "OK";
if (config_qx_type == CONFIG_QX_NAME_cell_TBBQ)
// PIN_PianYi, 不受区域参数控制;
if (!Judge_Status && (config_qx_type == CONFIG_QX_NAME_cell_ZJ_PianYi || config_qx_type == CONFIG_QX_NAME_cell_ZJ_QueShi || config_qx_type == CONFIG_QX_NAME_cell_CGMo_QueShi))
{ {
bNG_Status = true; pQxLog->AddCheckstr(PrintLevel_3, "qx info", " ZJ_PianYi | ZJ_QueShi | CGMo_QueShi: region param not used -> report NG by base judge");
Judge_Status = true; Judge_Status = true;
bNG_Status = true;
} }
std::string resultType = "OK";
if (!Judge_Status) if (!Judge_Status)
{ {
QX_info->result = QX_RESULT_TYPE_NoJduge; QX_info->result = QX_RESULT_TYPE_NoJduge;

@ -22,12 +22,33 @@
// 待分类模型部署后,将 AI_CLASS_MODEL_ENABLE 置 1,即可恢复 AI_QX_Class_Thread 中的AI分类流程 // 待分类模型部署后,将 AI_CLASS_MODEL_ENABLE 置 1,即可恢复 AI_QX_Class_Thread 中的AI分类流程
#define AI_CLASS_MODEL_ENABLE 0 #define AI_CLASS_MODEL_ENABLE 0
// 把手判定参数: // 把手提取/判定参数(下面宏是“未配置 JSON 时”的默认值;现场可在参数 JSON 的 Handle_Detect.form.handle_param
// 每个非空的"把手区域"附近都应该有一个把手:凸起宽高与该区域外接矩形宽高的比值落在下面范围内才算匹配 // 下按注释标注的键名配置同名参数覆盖它们,按产品/现场情况调整,无需改代码重编译):
// 提取:用 outer 定位 mask(bigmask) 拟合主体(圆角矩形)的上/下/左/右四条边缘,把边缘外的凸起按连通域分离为把手候选
// (见 CheckUtil::GetMaskProtrusions;产品主体是圆角矩形、把手基本出现在四角附近)
// 判定:每个非空的"把手区域"附近都应该有一个把手:凸起宽高与该区域外接矩形宽高的比值落在下面范围内才算匹配
// 调整影响:
// HANDLE_SIZE_MIN/MAX_RATIO(JSON: handle_size_min / handle_size_max):凸起与参数把手区域外接矩形的宽高比允许区间
// 放宽(如 0.7~1.4):把手尺寸/摆放差异大时不易漏检匹配,但可能让邻近错误凸起也匹配上;
// 收紧(如 0.9~1.1):匹配更严格,把手实际尺寸与参数画得差异大时会误报"把手缺失"
#define HANDLE_SIZE_MIN_RATIO 0.8f #define HANDLE_SIZE_MIN_RATIO 0.8f
#define HANDLE_SIZE_MAX_RATIO 1.2f #define HANDLE_SIZE_MAX_RATIO 1.2f
// "附近"的搜索范围:把手区域外接矩形在宽/高方向各外扩该比例 // "附近"的搜索范围(JSON: handle_search_ratio):把手区域外接矩形在宽/高方向各外扩该比例
// 调大:位置约束变松,产品摆放偏移大也不易漏(但可能匹配到别的凸起);
// 调小:位置约束更严,偏移稍大就会判"附近没有把手"
#define HANDLE_NEAR_EXPAND_RATIO 5 #define HANDLE_NEAR_EXPAND_RATIO 5
// 把手提取:最小深度系数(× 定位模型量化步长)(JSON: handle_min_depth)
// 凸起区域算大了/过于灵敏(边缘顺边毛刺与把手连成一块、高度多算) → 调大(如 3~4);
// 浅把手漏检 → 调小(如 1.5)。它同时决定:①凸起深度门槛;②边缘容差带(容差带=最小深度/2)
#define HANDLE_MIN_DEPTH_STEP_COEF 2
// 把手提取:最小面积系数(× 最小深度²)(JSON: handle_min_area)。碎块多 → 调大;小把手漏检 → 调小
#define HANDLE_MIN_AREA_COEF 2
// 把手提取:边缘外扩量系数(× 量化步长)(JSON: handle_edge_margin,0=按"最小深度一半"自动):
// 把主体边缘线整体向外推,变相减小凸起区域。
// 凸起区域算大了(边缘顺边毛刺与把手连成一块、高度多算) → 调大(如 2~3);
// 浅把手漏检/想更灵敏 → 调小。
// 注意:凸起区域会同步变小,匹配用的宽高比(HANDLE_SIZE_*_RATIO)会偏小,不宜调得过大
#define HANDLE_EDGE_MARGIN_STEP_COEF 3
// 把手缺失检测开关见 Base层 Function_Support_Det::bDetHandle(CheckConfigDefine.h) // 把手缺失检测开关见 Base层 Function_Support_Det::bDetHandle(CheckConfigDefine.h)
// 分类映射配置文件(每行:label索引:缺陷名称;冒号后名称为该缺陷类型名称,行的顺序即缺陷类型索引) // 分类映射配置文件(每行:label索引:缺陷名称;冒号后名称为该缺陷类型名称,行的顺序即缺陷类型索引)
@ -855,7 +876,9 @@ int ImgCheckAnalysisy::CheckRun()
m_pdetlog->bPrintStr = true; m_pdetlog->bPrintStr = true;
} }
// m_pdetlog->bPrintStr = true; // m_pdetlog->bPrintStr = true;
m_fImgage_Scale_X = m_pBasicConfig->fImage_Scale_x;
m_fImgage_Scale_Y = m_pBasicConfig->fImage_Scale_y;
m_pdetlog->AddCheckstr(PrintLevel_0, "1、basic Info", "---------------------------1、basic Info---------------------------------"); m_pdetlog->AddCheckstr(PrintLevel_0, "1、basic Info", "---------------------------1、basic Info---------------------------------");
m_pdetlog->AddCheckstr(PrintLevel_0, "Version", "%s", GetVersion().c_str()); m_pdetlog->AddCheckstr(PrintLevel_0, "Version", "%s", GetVersion().c_str());
m_pdetlog->AddCheckstr(PrintLevel_0, "Updateconfig", "%d", m_bupdateconfig); m_pdetlog->AddCheckstr(PrintLevel_0, "Updateconfig", "%d", m_bupdateconfig);
@ -1047,9 +1070,11 @@ int ImgCheckAnalysisy::CheckRun()
/* 检测支架偏移(bDetSupport 开关关闭时不做支架判定) /* 检测支架偏移(bDetSupport 开关关闭时不做支架判定)
不使用参数绘制区域,结合两次定位的 mask 计算: 不使用参数绘制区域,结合两次定位的 mask 计算:
1、bigmask 最大连通域 = 产品区域 rp(近似矩形,四周可能有凸起) 1、产品边缘:优先复用把手提取对 bigmask 做的"四边抗凸起拟合"主体四边形
(CheckHandleByBigMask -> GetMaskProtrusions;边缘上的凸起(把手等)不会把边缘带偏),
拟合不可用/无效时回退 bigmask 最大连通域外接矩形 rp(近似矩形,四周可能有凸起)
2、smallmask 最大连通域 = 支架区域 rs(按定位裁剪区 m_outer_rroi 外接矩形映射回原图坐标) 2、smallmask 最大连通域 = 支架区域 rs(按定位裁剪区 m_outer_rroi 外接矩形映射回原图坐标)
3、按 edge_select 指定方向(left/right/up/down)计算 rs 中心到 rp 对应边缘的距离 3、按 edge_select 指定方向(left/right/up/down)计算 rs 中心到产品对应边缘的距离
4、计算值与标准值(x_standard/y_standard)做差取平均,平均差超过 offset(x_offset) → 偏移 */ 4、计算值与标准值(x_standard/y_standard)做差取平均,平均差超过 offset(x_offset) → 偏移 */
{ {
Function_Support_Det &supportDet = m_pbaseCheckFunction->supportDet; Function_Support_Det &supportDet = m_pbaseCheckFunction->supportDet;
@ -1109,7 +1134,7 @@ int ImgCheckAnalysisy::CheckRun()
} }
else else
{ {
// rp 边缘:产品最大连通域的外接矩形(含凸起) // rp 边缘(拟合不可用时的回退):产品最大连通域的外接矩形(含凸起)
cv::Rect rpRect = cv::boundingRect(rpContour); cv::Rect rpRect = cv::boundingRect(rpContour);
// rs 映射回原图坐标:smallmask 对应定位时裁剪的大轮廓区域(m_outer_rroi 外接矩形),按比例映射 // rs 映射回原图坐标:smallmask 对应定位时裁剪的大轮廓区域(m_outer_rroi 外接矩形),按比例映射
@ -1123,13 +1148,87 @@ int ImgCheckAnalysisy::CheckRun()
cvRound(rsSmallRect.width * fx), cvRound(rsSmallRect.width * fx),
cvRound(rsSmallRect.height * fy)); cvRound(rsSmallRect.height * fy));
// 3、rs 中心到 rp 上下左右边缘的距离 // 3、产品边缘:优先复用把手提取对 bigmask 做的"四边抗凸起拟合"主体四边形
// (CheckHandleByBigMask -> GetMaskProtrusions 输出):边缘上的凸起(把手等)不会把边缘带偏;
// 拟合不可用/无效时回退到产品最大连通域外接矩形 rpRect(含凸起)
bool bFitQuad = false; // 是否用上了拟合主体四边形
float fDL = 0, fDR = 0, fDT = 0, fDB = 0; // 支架中心到产品左/右/上/下边缘的距离(原图像素,后乘缩放系数)
cv::Point2f ptEdgeArr[4]; // 连线端点(边缘线上):0 left 1 right 2 up 3 down
cv::Point2f ptSegAArr[4]; // 边缘线段端点A
cv::Point2f ptSegBArr[4]; // 边缘线段端点B
float fCx = rsRect.x + rsRect.width * 0.5f; float fCx = rsRect.x + rsRect.width * 0.5f;
float fCy = rsRect.y + rsRect.height * 0.5f; float fCy = rsRect.y + rsRect.height * 0.5f;
float fDL = fCx - rpRect.x; // 到左边缘 if (m_HandleBodyQuad.size() == 4)
float fDR = (rpRect.x + rpRect.width) - fCx; // 到右边缘 {
float fDT = fCy - rpRect.y; // 到上边缘 // 四边形角点顺序:左上/右上/右下/左下;左/右边 x=k*y+b、上/下边 y=k*x+b(与 GetMaskProtrusions 约定一致);
float fDB = (rpRect.y + rpRect.height) - fCy; // 到下边缘 // 边长过小说明拟合退化,不用
const cv::Point2f &ptTL = m_HandleBodyQuad[0];
const cv::Point2f &ptTR = m_HandleBodyQuad[1];
const cv::Point2f &ptBR = m_HandleBodyQuad[2];
const cv::Point2f &ptBL = m_HandleBodyQuad[3];
if (fabs(ptBL.y - ptTL.y) > 2.0f && fabs(ptBR.y - ptTR.y) > 2.0f &&
fabs(ptTR.x - ptTL.x) > 2.0f && fabs(ptBR.x - ptBL.x) > 2.0f)
{
float fKL = (ptBL.x - ptTL.x) / (ptBL.y - ptTL.y); // 左边 x = k*y + b
float fBL = ptTL.x - fKL * ptTL.y;
float fKR = (ptBR.x - ptTR.x) / (ptBR.y - ptTR.y); // 右边 x = k*y + b
float fBR = ptTR.x - fKR * ptTR.y;
float fKT = (ptTR.y - ptTL.y) / (ptTR.x - ptTL.x); // 上边 y = k*x + b
float fBT = ptTL.y - fKT * ptTL.x;
float fKB = (ptBR.y - ptBL.y) / (ptBR.x - ptBL.x); // 下边 y = k*x + b
float fBB = ptBL.y - fKB * ptBL.x;
// 连线端点:取边缘线在支架中心行/列上的点;长度即中心到边缘的水平/垂直距离
float fXEdgeL = fKL * fCy + fBL;
float fXEdgeR = fKR * fCy + fBR;
float fYEdgeT = fKT * fCx + fBT;
float fYEdgeB = fKB * fCx + fBB;
fDL = fCx - fXEdgeL;
fDR = fXEdgeR - fCx;
fDT = fCy - fYEdgeT;
fDB = fYEdgeB - fCy;
// 拟合有效性:支架中心应位于四条拟合边内侧(四个方向距离均为正),否则放弃拟合
if (fDL > 0 && fDR > 0 && fDT > 0 && fDB > 0)
{
ptEdgeArr[0] = cv::Point2f(fXEdgeL, fCy);
ptEdgeArr[1] = cv::Point2f(fXEdgeR, fCy);
ptEdgeArr[2] = cv::Point2f(fCx, fYEdgeT);
ptEdgeArr[3] = cv::Point2f(fCx, fYEdgeB);
ptSegAArr[0] = ptTL; ptSegBArr[0] = ptBL; // left:整条拟合左边
ptSegAArr[1] = ptTR; ptSegBArr[1] = ptBR; // right:整条拟合右边
ptSegAArr[2] = ptTL; ptSegBArr[2] = ptTR; // up:整条拟合上边
ptSegAArr[3] = ptBL; ptSegBArr[3] = ptBR; // down:整条拟合下边
bFitQuad = true;
}
}
}
if (!bFitQuad)
{
// 回退:产品区域外接矩形(含凸起)的四条边
fDL = fCx - rpRect.x;
fDR = (rpRect.x + rpRect.width) - fCx;
fDT = fCy - rpRect.y;
fDB = (rpRect.y + rpRect.height) - fCy;
ptEdgeArr[0] = cv::Point2f((float)rpRect.x, fCy);
ptEdgeArr[1] = cv::Point2f((float)(rpRect.x + rpRect.width), fCy);
ptEdgeArr[2] = cv::Point2f(fCx, (float)rpRect.y);
ptEdgeArr[3] = cv::Point2f(fCx, (float)(rpRect.y + rpRect.height));
ptSegAArr[0] = cv::Point2f((float)rpRect.x, (float)rpRect.y);
ptSegBArr[0] = cv::Point2f((float)rpRect.x, (float)(rpRect.y + rpRect.height));
ptSegAArr[1] = cv::Point2f((float)(rpRect.x + rpRect.width), (float)rpRect.y);
ptSegBArr[1] = cv::Point2f((float)(rpRect.x + rpRect.width), (float)(rpRect.y + rpRect.height));
ptSegAArr[2] = cv::Point2f((float)rpRect.x, (float)rpRect.y);
ptSegBArr[2] = cv::Point2f((float)(rpRect.x + rpRect.width), (float)rpRect.y);
ptSegAArr[3] = cv::Point2f((float)rpRect.x, (float)(rpRect.y + rpRect.height));
ptSegBArr[3] = cv::Point2f((float)(rpRect.x + rpRect.width), (float)(rpRect.y + rpRect.height));
}
// 像素 -> mm
fDL *= m_fImgage_Scale_X;
fDR *= m_fImgage_Scale_X;
fDT *= m_fImgage_Scale_Y;
fDB *= m_fImgage_Scale_Y;
float fDistArr[4] = {fDL, fDR, fDT, fDB}; // 顺序固定:0 left 1 right 2 up 3 down
// 4、按 edge_select 指定方向(left/right/up/down)取计算值,与标准值做差取平均,平均差超过 offset → 偏移 // 4、按 edge_select 指定方向(left/right/up/down)取计算值,与标准值做差取平均,平均差超过 offset → 偏移
// left/right 用 x_standard,up/down 用 y_standard;offset 用 x_offset // left/right 用 x_standard,up/down 用 y_standard;offset 用 x_offset
@ -1141,26 +1240,26 @@ int ImgCheckAnalysisy::CheckRun()
for (size_t iselect = 0; iselect < supportDet.edge_select.size(); iselect++) for (size_t iselect = 0; iselect < supportDet.edge_select.size(); iselect++)
{ {
const std::string &strEdge = supportDet.edge_select[iselect]; const std::string &strEdge = supportDet.edge_select[iselect];
float fDist = 0; int gi = -1; // 方向索引:0 left 1 right 2 up 3 down
float fStandard = 0; float fStandard = 0;
if (strEdge == "left") if (strEdge == "left")
{ {
fDist = fDL; gi = 0;
fStandard = supportDet.x_standard; fStandard = supportDet.x_standard;
} }
else if (strEdge == "right") else if (strEdge == "right")
{ {
fDist = fDR; gi = 1;
fStandard = supportDet.x_standard; fStandard = supportDet.x_standard;
} }
else if (strEdge == "up") else if (strEdge == "up")
{ {
fDist = fDT; gi = 2;
fStandard = supportDet.y_standard; fStandard = supportDet.y_standard;
} }
else if (strEdge == "down") else if (strEdge == "down")
{ {
fDist = fDB; gi = 3;
fStandard = supportDet.y_standard; fStandard = supportDet.y_standard;
} }
else else
@ -1168,6 +1267,7 @@ int ImgCheckAnalysisy::CheckRun()
continue; continue;
} }
float fDist = fDistArr[gi];
float fDiff = fabs(fDist - fStandard); float fDiff = fabs(fDist - fStandard);
fSumDiff += fDiff; fSumDiff += fDiff;
nDiffNum++; nDiffNum++;
@ -1178,6 +1278,9 @@ int ImgCheckAnalysisy::CheckRun()
edgeDraw.fDist = fDist; edgeDraw.fDist = fDist;
edgeDraw.fStandard = fStandard; edgeDraw.fStandard = fStandard;
edgeDraw.bExceed = (fDiff > supportDet.x_offset); edgeDraw.bExceed = (fDiff > supportDet.x_offset);
edgeDraw.ptEdge = ptEdgeArr[gi];
edgeDraw.ptEdgeA = ptSegAArr[gi];
edgeDraw.ptEdgeB = ptSegBArr[gi];
m_SupportDrawInfo.edgeList.push_back(edgeDraw); m_SupportDrawInfo.edgeList.push_back(edgeDraw);
} }
sprintf(strTmp, "%s(%.1f/%.1f) ", strEdge.c_str(), fDist, fStandard); sprintf(strTmp, "%s(%.1f/%.1f) ", strEdge.c_str(), fDist, fStandard);
@ -1197,10 +1300,10 @@ int ImgCheckAnalysisy::CheckRun()
m_SupportDrawInfo.ptCenter = cv::Point2f(fCx, fCy); m_SupportDrawInfo.ptCenter = cv::Point2f(fCx, fCy);
m_pdetlog->AddCheckstr(PrintLevel_0, "支架检测", m_pdetlog->AddCheckstr(PrintLevel_0, "支架检测",
"rp [%d %d %d %d] rs [%d %d %d %d] scale (%.4f %.4f) center (%.1f %.1f) dL %.1f dR %.1f dT %.1f dB %.1f | select %s diff %s avgDiff %.1f limit %.1f ng %d", "rp [%d %d %d %d] rs [%d %d %d %d] scale (%.4f %.4f) center (%.1f %.1f) edge %s quad %ld dL %.1f dR %.1f dT %.1f dB %.1f | select %s diff %s avgDiff %.1f limit %.1f ng %d",
rpRect.x, rpRect.y, rpRect.width, rpRect.height, rpRect.x, rpRect.y, rpRect.width, rpRect.height,
rsRect.x, rsRect.y, rsRect.width, rsRect.height, rsRect.x, rsRect.y, rsRect.width, rsRect.height,
fx, fy, fCx, fCy, fDL, fDR, fDT, fDB, fx, fy, fCx, fCy, bFitQuad ? "fitquad" : "rpbox", (long)m_HandleBodyQuad.size(), fDL, fDR, fDT, fDB,
strSelect.c_str(), strDiff.c_str(), fAvgDiff, supportDet.x_offset, bOffset); strSelect.c_str(), strDiff.c_str(), fAvgDiff, supportDet.x_offset, bOffset);
if (bOffset) if (bOffset)
@ -1316,10 +1419,35 @@ int ImgCheckAnalysisy::CheckRun()
{ {
m_tag_roiList.clear(); m_tag_roiList.clear();
} }
// 标签能量/灰阶差(hj)计算用掩膜:所有标签区域填白(与 blob 检测用 AIMaskImg 的口径一致),
// 由 calc_blob_info_withstats 按“前景=标签区域、背景=外扩一圈”计算
long tagEnergy_s = CheckUtil::getcurTime();
cv::Mat tagMask;
if (!m_tag_roiList.empty() && !m_pImageAllResult->detImg.empty())
{
tagMask = cv::Mat::zeros(m_pImageAllResult->detImg.size(), CV_8UC1);
std::vector<std::vector<cv::Point>> tagPolys;
tagPolys.reserve(m_tag_roiList.size());
for (const auto &tagRoiMask : m_tag_roiList)
{
cv::Point2f ptfMask[4];
tagRoiMask.points(ptfMask);
std::vector<cv::Point> polyMask(4);
for (int k = 0; k < 4; k++)
{
polyMask[k] = cv::Point(cvRound(ptfMask[k].x), cvRound(ptfMask[k].y));
}
tagPolys.push_back(std::move(polyMask));
}
cv::fillPoly(tagMask, tagPolys, cv::Scalar(255));
}
for (const auto &tagRoi : m_tag_roiList) for (const auto &tagRoi : m_tag_roiList)
{ {
// tag roi 在原图坐标系,按坐标映射转换到检测图(detImg)坐标系 // tag roi 在原图坐标系,按坐标映射转换到检测图(detImg)坐标系
cv::Rect roi = MatAffineRect(m_matSrc2Det, tagRoi.boundingRect()); // cv::Rect roi = MatAffineRect(m_matSrc2Det, tagRoi.boundingRect());
cv::Rect roi = tagRoi.boundingRect();
roi &= cv::Rect(0, 0, m_pImageAllResult->detImg.cols, m_pImageAllResult->detImg.rows); roi &= cv::Rect(0, 0, m_pImageAllResult->detImg.cols, m_pImageAllResult->detImg.rows);
if (roi.width <= 0 || roi.height <= 0) if (roi.width <= 0 || roi.height <= 0)
{ {
@ -1336,6 +1464,16 @@ int ImgCheckAnalysisy::CheckRun()
float th = tagRoi.size.height; float th = tagRoi.size.height;
temerror.flen = (tw > th ? tw : th) * m_fImgage_Scale_X; temerror.flen = (tw > th ? tw : th) * m_fImgage_Scale_X;
temerror.fbreadth = (tw > th ? th : tw) * m_fImgage_Scale_Y; temerror.fbreadth = (tw > th ? th : tw) * m_fImgage_Scale_Y;
// 标签区域的能量/灰阶差(hj):与 blob 检测相同口径(前景=标签区域,背景=外扩一圈)
if (!tagMask.empty())
{
cv::Rect roiStat = roi;
cv::Scalar tagStat = calc_blob_info_withstats(m_pImageAllResult->detImg, tagMask, roiStat);
temerror.energy = (int)tagStat[1];
temerror.grayDis = (float)tagStat[2];
}
temerror.nconfig_qx_type = CONFIG_QX_NAME_cell_TBBQ; temerror.nconfig_qx_type = CONFIG_QX_NAME_cell_TBBQ;
temerror.qx_name = CONFIG_QX_NAME_Names[CONFIG_QX_NAME_cell_TBBQ]; temerror.qx_name = CONFIG_QX_NAME_Names[CONFIG_QX_NAME_cell_TBBQ];
temerror.result = QX_RESULT_TYPE_NG; temerror.result = QX_RESULT_TYPE_NG;
@ -1360,7 +1498,13 @@ int ImgCheckAnalysisy::CheckRun()
m_pDetResult->pQx_ErrorList->push_back(temerror); m_pDetResult->pQx_ErrorList->push_back(temerror);
m_pdetlog->AddCheckstr(PrintLevel_0, "Tag检测", "tag %d roi [%d %d %d %d] -> NG", temerror.Idx, roi.x, roi.y, roi.width, roi.height); m_pdetlog->AddCheckstr(PrintLevel_0, "Tag检测", "tag %d roi [%d %d %d %d] energy %d hj %.1f -> NG",
temerror.Idx, roi.x, roi.y, roi.width, roi.height, temerror.energy, temerror.grayDis);
}
if (!m_tag_roiList.empty())
{
long tagEnergy_e = CheckUtil::getcurTime();
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Tag", "tag energy calc use time %ld", tagEnergy_e - tagEnergy_s);
} }
} }
@ -2112,6 +2256,8 @@ int ImgCheckAnalysisy::ConfigCheck(cv::Mat img)
int ImgCheckAnalysisy::AI_Detect_Thread(const cv::Mat &img, cv::Mat &ResultImg) int ImgCheckAnalysisy::AI_Detect_Thread(const cv::Mat &img, cv::Mat &ResultImg)
{ {
std::string cur_time_s = CheckUtil::getCurTimeHMS();
std::shared_ptr<AIModel_Base> pAIDet; std::shared_ptr<AIModel_Base> pAIDet;
// printf("=====>>>>AI_Detect_Thread m_strCurDetChannel %s \n", m_strCurDetChannel.c_str()); // printf("=====>>>>AI_Detect_Thread m_strCurDetChannel %s \n", m_strCurDetChannel.c_str());
bool bUseDarkModel = false; bool bUseDarkModel = false;
@ -2221,12 +2367,12 @@ int ImgCheckAnalysisy::AI_Detect_Thread(const cv::Mat &img, cv::Mat &ResultImg)
if (m_pbaseCheckFunction->saveImg.bSaveAIDetImg && !ResultImg.empty()) if (m_pbaseCheckFunction->saveImg.bSaveAIDetImg && !ResultImg.empty())
{ {
std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/KK/" + DetImgInfo_shareP->strImgProductID + "/"; std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + DetImgInfo_shareP->strImgProductID + "/";
CheckUtil::CreateDir(strSaveDir); CheckUtil::CreateDir(strSaveDir);
std::string strSavePath_in = strSaveDir + m_strCurDetChannel + "_in.png"; std::string strSavePath_in = strSaveDir + cur_time_s + "_" + strCameraName + "_in.png";
std::string strSavePath_out = strSaveDir + m_strCurDetChannel + "_out.png"; std::string strSavePath_out = strSaveDir + cur_time_s + "_" + strCameraName + "_out.png";
cv::imwrite(strSavePath_in, inputImg); CheckUtil::SaveImgAsync(strSavePath_in, m_CheckResult_shareP->cutSrcimg);
cv::imwrite(strSavePath_out, ResultImg); CheckUtil::SaveImgAsync(strSavePath_out, ResultImg);
} }
// 临时存图 // 临时存图
@ -2547,56 +2693,47 @@ int ImgCheckAnalysisy::DrawResult_Step_1()
} }
} }
// 支架偏移检测绘制:支架中心十字(绿=正常/红=偏移NG)+ 支架到边缘的距离(绿=正常/红=超标准) // 支架偏移检测绘制(受参数 bDrawSupport 控制,默认关闭):
if (m_SupportDrawInfo.bValid && !m_CheckResult_shareP->resultimg.empty()) // 支架中心十字(绿=正常/红=偏移NG)+ 各方向"所计算的产品边缘"线段(抗凸起拟合直边,见 CheckRun 支架块)
// + 支架中心到该边缘的连线(连线长度=支架中心到所计算边缘的距离;绿=正常/红=超标准)+ 实测/标准(mm)文字
if (m_SupportDrawInfo.bValid && m_pbaseCheckFunction->supportDet.bDrawSupport && !m_CheckResult_shareP->resultimg.empty())
{ {
// 支架中心:原图坐标 -> 检测图(裁剪) -> 结果图 // 原图坐标 -> 检测图(裁剪) -> 结果图
cv::Point2f detPt = MatAffinePoint(m_matSrc2Det, m_SupportDrawInfo.ptCenter); auto srcToResult = [&](const cv::Point2f &pt) -> cv::Point {
cv::Point pCenter(cvRound(detPt.x * fs_resize_x), cvRound(detPt.y * fs_resize_y)); cv::Point2f detPt = MatAffinePoint(m_matSrc2Det, pt);
return cv::Point(cvRound(detPt.x * fs_resize_x), cvRound(detPt.y * fs_resize_y));
};
// 支架中心十字:NG 红色 / 正常绿色 // 支架中心十字:NG 红色 / 正常绿色
cv::Point pCenter = srcToResult(m_SupportDrawInfo.ptCenter);
cv::Scalar colorCross = m_SupportDrawInfo.bOffset ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 255, 0); cv::Scalar colorCross = m_SupportDrawInfo.bOffset ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 255, 0);
colorCross = CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, colorCross); colorCross = CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, colorCross);
const int nCrossLen = 40; // 结果图上十字半长(px) const int nCrossLen = 40; // 结果图上十字半长(px)
cv::line(m_CheckResult_shareP->resultimg, cv::Point(pCenter.x - nCrossLen, pCenter.y), cv::Point(pCenter.x + nCrossLen, pCenter.y), colorCross, 2); cv::line(m_CheckResult_shareP->resultimg, cv::Point(pCenter.x - nCrossLen, pCenter.y), cv::Point(pCenter.x + nCrossLen, pCenter.y), colorCross, 2);
cv::line(m_CheckResult_shareP->resultimg, cv::Point(pCenter.x, pCenter.y - nCrossLen), cv::Point(pCenter.x, pCenter.y + nCrossLen), colorCross, 2); cv::line(m_CheckResult_shareP->resultimg, cv::Point(pCenter.x, pCenter.y - nCrossLen), cv::Point(pCenter.x, pCenter.y + nCrossLen), colorCross, 2);
// 支架到边缘的距离:中心 -> 产品区域对应边缘连线 + 实测/标准(像素)文字 // 各方向:所计算边缘线段(抗凸起拟合直边/回退外接矩形边) + 中心到边缘点的连线(长度=点到所计算边缘) + 实测/标准文字
// 几何(边缘点/线段两端点)由 CheckRun 支架块算好存入 edgeDraw,此处只做坐标映射与绘制
char strTxt[64]; char strTxt[64];
for (size_t i = 0; i < m_SupportDrawInfo.edgeList.size(); i++) for (size_t i = 0; i < m_SupportDrawInfo.edgeList.size(); i++)
{ {
const Support_Draw_Info_::Edge_Draw_ &edgeDraw = m_SupportDrawInfo.edgeList[i]; const Support_Draw_Info_::Edge_Draw_ &edgeDraw = m_SupportDrawInfo.edgeList[i];
cv::Point2f ptEdge = m_SupportDrawInfo.ptCenter;
if (edgeDraw.strEdge == "left")
{
ptEdge.x = (float)m_SupportDrawInfo.rpRect.x;
}
else if (edgeDraw.strEdge == "right")
{
ptEdge.x = (float)(m_SupportDrawInfo.rpRect.x + m_SupportDrawInfo.rpRect.width);
}
else if (edgeDraw.strEdge == "up")
{
ptEdge.y = (float)m_SupportDrawInfo.rpRect.y;
}
else if (edgeDraw.strEdge == "down")
{
ptEdge.y = (float)(m_SupportDrawInfo.rpRect.y + m_SupportDrawInfo.rpRect.height);
}
else
{
continue;
}
cv::Point2f detEdge = MatAffinePoint(m_matSrc2Det, ptEdge); cv::Point pEdge = srcToResult(edgeDraw.ptEdge);
cv::Point pEdge(cvRound(detEdge.x * fs_resize_x), cvRound(detEdge.y * fs_resize_y)); cv::Point pEdgeA = srcToResult(edgeDraw.ptEdgeA);
cv::Point pEdgeB = srcToResult(edgeDraw.ptEdgeB);
// 超标准红色 / 正常绿色 // 超标准红色 / 正常绿色
cv::Scalar colorEdge = edgeDraw.bExceed ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 255, 0); cv::Scalar colorEdge = edgeDraw.bExceed ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 255, 0);
colorEdge = CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, colorEdge); colorEdge = CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, colorEdge);
// 1、所计算边缘线段:标出参与计算的产品边缘(细线;抗凸起拟合直边/回退边)
cv::line(m_CheckResult_shareP->resultimg, pEdgeA, pEdgeB, colorEdge, 1);
// 2、中心 -> 边缘点连线:长度即支架中心到所计算边缘的距离
cv::line(m_CheckResult_shareP->resultimg, pCenter, pEdge, colorEdge, 2); cv::line(m_CheckResult_shareP->resultimg, pCenter, pEdge, colorEdge, 2);
// 距离文字:实测/标准(原图像素) // 3、距离文字:实测/标准(mm)
snprintf(strTxt, sizeof(strTxt), "%s %.0f/%.0f", edgeDraw.strEdge.c_str(), edgeDraw.fDist, edgeDraw.fStandard); snprintf(strTxt, sizeof(strTxt), "%s %.0f/%.0f", edgeDraw.strEdge.c_str(), edgeDraw.fDist, edgeDraw.fStandard);
cv::putText(m_CheckResult_shareP->resultimg, strTxt, cv::putText(m_CheckResult_shareP->resultimg, strTxt,
cv::Point((pCenter.x + pEdge.x) / 2 + 6, (pCenter.y + pEdge.y) / 2 - 6), cv::Point((pCenter.x + pEdge.x) / 2 + 6, (pCenter.y + pEdge.y) / 2 - 6),
@ -2775,7 +2912,7 @@ int ImgCheckAnalysisy::AI_Edge(const cv::Mat &img, cv::RotatedRect &outerRoi, cv
// 无条件赋值:Tag_Loc 未检出或 bDetTag 关闭(tagroi 为空)时清空,避免残留上一张图的标签 // 无条件赋值:Tag_Loc 未检出或 bDetTag 关闭(tagroi 为空)时清空,避免残留上一张图的标签
tagroiList = m_pEdge_Align_Result->tagroi; tagroiList = m_pEdge_Align_Result->tagroi;
/*使用m_pEdge_Align_Result->bigmask检测把手是否缺失*/ /* 用 outer 定位 mask(bigmask) 分离凸起区域,检测把手是否缺失 */
{ {
long handle_s = CheckUtil::getcurTime(); long handle_s = CheckUtil::getcurTime();
CheckHandleByBigMask(m_pEdge_Align_Result->bigmask); CheckHandleByBigMask(m_pEdge_Align_Result->bigmask);
@ -2785,19 +2922,23 @@ int ImgCheckAnalysisy::AI_Edge(const cv::Mat &img, cv::RotatedRect &outerRoi, cv
return re; return re;
} }
// 用 bigmask 检测把手: // 用 outer 定位 mask(bigmask) 检测把手(产品主体是圆角矩形、把手基本出现在四角附近):
// 1、bigmask 主体近似矩形,把手是主体四周凸起的"类矩形块" // 1、分离凸起区域:拟合主体上/下/左/右四条边缘,把边缘外的掩膜按连通域切分成凸起区域
// 2、把主体四周的凸起抠出来(凸起在该边的边界值上表现为一次突变进入 + 一次反向突变离开) // (提取细节见 CheckUtil::GetMaskProtrusions,含就近角/质心/面积/深度/填充率)
// 3、参数里每个非空的"把手区域"附近都应该有一个把手:在区域附近找凸起, // 2、参数里每个非空的"把手区域"附近都应该有一个把手:在区域附近找凸起,
// 凸起宽高与区域外接矩形宽高相近(HANDLE_SIZE_MIN_RATIO ~ HANDLE_SIZE_MAX_RATIO)才算匹配 // 凸起宽高与区域外接矩形宽高相近(尺寸比区间见 handle_size_min/handle_size_max,可 JSON 配置)才算匹配
// 4、凸起本身不算 NG,只有"某个把手区域附近找不到匹配的凸起"才算把手缺失(NG 由 CheckRun 上报) // 3、凸起本身不算 NG,只有"某个把手区域附近找不到匹配的凸起"才算把手缺失(NG 由 CheckRun 上报)
// 4、-s(存过程图)时输出 {通道}_handle_det.jpg:绿色四边形=拟合主体、红框=凸起(带编号/角/深度)、
// 蓝框=参数把手区域、绿框=匹配到的把手,便于从图像观察问题
// 5、拟合出的主体四边形存 m_HandleBodyQuad:支架偏移检测复用其四边(抗凸起)做测距与绘制
int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &bigmask) int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &bigmask)
{ {
m_bHandleDetSucc = false; m_bHandleDetSucc = false;
m_bHandleFound = false; m_bHandleFound = false;
m_bHandleExpect = false; m_bHandleExpect = false;
m_HandleCandList.clear(); m_HandleProtrusionList.clear();
m_HandleRegionResultList.clear(); m_HandleRegionResultList.clear();
m_HandleBodyQuad.clear();
if (bigmask.empty()) if (bigmask.empty())
{ {
@ -2805,9 +2946,40 @@ int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &bigmask)
return 1; return 1;
} }
// 1、抠出 bigmask 四周凸起的矩形块 // 1、从 bigmask 分离凸起区域
std::vector<Mask_Protrusion_> protrusionList; // 最小深度 = 系数 × 量化步长:mask 由模型输出 resize 回原图,直边带量化台阶毛刺(台阶高≈模型 1 像素=量化步长);
int re = CheckUtil::GetMaskProtrusions(bigmask, protrusionList); // 系数优先取参数配置(JSON handle_param 下的 handle_min_depth/handle_min_area/handle_edge_margin),
// 未配置时用文件顶部宏的默认值;
// 凸起区域算大了/过于灵敏 → 首选调大 handle_edge_margin(把边缘线整体外推、变相减小凸起区域,
// 不影响深度门槛);也可调大 handle_min_depth(深度门槛+容差带一起变大);浅把手漏检 → 反向调小;
// 拿不到模型尺寸时传 0,由 GetMaskProtrusions 按掩膜尺寸自动取值
float fMinDepthCoef = HANDLE_MIN_DEPTH_STEP_COEF;
float fMinAreaCoef = HANDLE_MIN_AREA_COEF;
float fEdgeMarginCoef = HANDLE_EDGE_MARGIN_STEP_COEF;
if (m_pbaseCheckFunction != NULL)
{
const Function_Support_Det &supportDetCfg = m_pbaseCheckFunction->supportDet;
if (supportDetCfg.handle_min_depth_coef > 0) fMinDepthCoef = supportDetCfg.handle_min_depth_coef;
if (supportDetCfg.handle_min_area_coef > 0) fMinAreaCoef = supportDetCfg.handle_min_area_coef;
if (supportDetCfg.handle_edge_margin_coef >= 0) fEdgeMarginCoef = supportDetCfg.handle_edge_margin_coef; // 0=按“最小深度一半”自动
}
int nMinDepth = 0;
int nMinArea = 0;
int nEdgeMargin = 0;
if (AI_Factory && AI_Factory->Align_Outer && AI_Factory->Align_Outer->input_0.width > 0 && AI_Factory->Align_Outer->input_0.height > 0)
{
int nStepX = (int)std::ceil(bigmask.cols * 1.0 / AI_Factory->Align_Outer->input_0.width);
int nStepY = (int)std::ceil(bigmask.rows * 1.0 / AI_Factory->Align_Outer->input_0.height);
int nStep = std::max(1, std::max(nStepX, nStepY));
nMinDepth = (int)(fMinDepthCoef * nStep + 0.5f);
nMinArea = (int)(fMinAreaCoef * nMinDepth * nMinDepth + 0.5f);
if (fEdgeMarginCoef > 0)
{
nEdgeMargin = (int)(fEdgeMarginCoef * nStep + 0.5f);
}
}
// m_HandleBodyQuad:拟合出的主体四边形(左上/右上/右下/左下);-s 存图观察用,支架偏移检测复用其四边做抗凸起测距/绘制
int re = CheckUtil::GetMaskProtrusions(bigmask, m_HandleProtrusionList, nMinDepth, nMinArea, &m_HandleBodyQuad, nEdgeMargin);
if (re != 0) if (re != 0)
{ {
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "GetMaskProtrusions error %d", re); m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "GetMaskProtrusions error %d", re);
@ -2829,33 +3001,27 @@ int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &bigmask)
} }
m_bHandleExpect = (bDetHandle && !regionBoxList.empty()); m_bHandleExpect = (bDetHandle && !regionBoxList.empty());
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "protrusion num %ld handle region num %ld bDetHandle %s expect %s", m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "protrusion num %ld handle region num %ld bDetHandle %s expect %s",
protrusionList.size(), regionBoxList.size(), BOOL_TO_STR(bDetHandle), BOOL_TO_STR(m_bHandleExpect)); m_HandleProtrusionList.size(), regionBoxList.size(), BOOL_TO_STR(bDetHandle), BOOL_TO_STR(m_bHandleExpect));
// 3、凸起候选实测特征(面积/填充率,仅用于日志排查) // 3、凸起区域实测特征(就近角/面积/深度/填充率,用于日志排查)
for (size_t i = 0; i < protrusionList.size(); i++) for (size_t i = 0; i < m_HandleProtrusionList.size(); i++)
{ {
Handle_Candidate_ cand; const Mask_Protrusion_ &protr = m_HandleProtrusionList[i];
cand.protr = protrusionList[i];
// 左/右凸起:沿边为 y 方向,深度为 x 方向;上/下凸起相反
bool bHorz = (cand.protr.nSide == 0 || cand.protr.nSide == 1);
cand.nLen = bHorz ? cand.protr.roi.height : cand.protr.roi.width;
cand.nDepth = bHorz ? cand.protr.roi.width : cand.protr.roi.height;
m_HandleCandList.push_back(cand);
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle",
"protrusion %ld side %d roi [%d %d %d %d] len %d depth %d area %0.0f fill %0.2f", "protrusion %ld corner %d roi [%d %d %d %d] len %.0f depth %.0f area %0.0f fill %0.2f",
i, cand.protr.nSide, cand.protr.roi.x, cand.protr.roi.y, cand.protr.roi.width, cand.protr.roi.height, i, protr.nCorner, protr.roi.x, protr.roi.y, protr.roi.width, protr.roi.height,
cand.nLen, cand.nDepth, cand.protr.fArea, cand.protr.fFillRatio); protr.fLen, protr.fDepth, protr.fArea, protr.fFillRatio);
} }
// 4、逐个把手区域:在区域附近找宽高匹配的凸起(同一个凸起只匹配给一个区域) // 4、逐个把手区域:在区域附近找宽高匹配的凸起(同一个凸起只匹配给一个区域)
std::vector<bool> usedList(m_HandleCandList.size(), false); std::vector<bool> usedList(m_HandleProtrusionList.size(), false);
int nFound = 0; int nFound = 0;
for (size_t ir = 0; ir < regionBoxList.size(); ir++) for (size_t ir = 0; ir < regionBoxList.size(); ir++)
{ {
Handle_Region_Result_ result; Handle_Region_Result_ result;
result.nIdx = (int)ir + 1; result.nIdx = (int)ir + 1;
result.boxRegion = regionBoxList[ir]; result.boxRegion = regionBoxList[ir];
int nMatchIdx = MatchHandleRegion(m_HandleCandList, usedList, result.boxRegion, result.roiHandle); int nMatchIdx = MatchHandleRegion(m_HandleProtrusionList, usedList, result.boxRegion, result.roiHandle);
result.bFound = (nMatchIdx >= 0); result.bFound = (nMatchIdx >= 0);
if (result.bFound) if (result.bFound)
{ {
@ -2879,7 +3045,9 @@ int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &bigmask)
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, "Handle", "handle region num %ld found num %d -> %s", m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, "Handle", "handle region num %ld found num %d -> %s",
regionBoxList.size(), nFound, m_bHandleFound ? "handle ok" : "handle missing !!"); regionBoxList.size(), nFound, m_bHandleFound ? "handle ok" : "handle missing !!");
// 5、存过程图:凸起红框、把手区域蓝框、匹配上的把手绿框(仅调试时) // 5、存过程图(-s 时):用于从图像观察把手提取/匹配情况;
// 绿色四边形=拟合出的主体四条边缘;红框=分离出的凸起区域(带编号/就近角/深度);
// 蓝框=参数把手区域;绿框=匹配到的把手
if (DetImgInfo_shareP->bsaveProcessImg) if (DetImgInfo_shareP->bsaveProcessImg)
{ {
cv::Mat show; cv::Mat show;
@ -2891,9 +3059,25 @@ int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &bigmask)
{ {
show = bigmask.clone(); show = bigmask.clone();
} }
for (size_t i = 0; i < m_HandleCandList.size(); i++) // 主体四边形(拟合出的四条边缘)
if (m_HandleBodyQuad.size() == 4)
{
std::vector<cv::Point> vecQuad;
for (size_t i = 0; i < m_HandleBodyQuad.size(); i++)
{
vecQuad.push_back(cv::Point(cvRound(m_HandleBodyQuad[i].x), cvRound(m_HandleBodyQuad[i].y)));
}
cv::polylines(show, vecQuad, true, cv::Scalar(0, 255, 0), 8);
}
// 凸起区域:红框 + 编号/就近角/深度
char strTxt[128];
for (size_t i = 0; i < m_HandleProtrusionList.size(); i++)
{ {
cv::rectangle(show, m_HandleCandList[i].protr.roi, cv::Scalar(0, 0, 255), 8); const Mask_Protrusion_ &protr = m_HandleProtrusionList[i];
cv::rectangle(show, protr.roi, cv::Scalar(0, 0, 255), 8);
snprintf(strTxt, sizeof(strTxt), "#%d C%d d%.0f", (int)i, protr.nCorner, protr.fDepth);
cv::putText(show, strTxt, cv::Point(protr.roi.x, std::max(30, protr.roi.y - 8)),
cv::FONT_HERSHEY_SIMPLEX, 2.0, cv::Scalar(0, 255, 255), 4);
} }
for (size_t i = 0; i < m_HandleRegionResultList.size(); i++) for (size_t i = 0; i < m_HandleRegionResultList.size(); i++)
{ {
@ -3009,6 +3193,15 @@ int ImgCheckAnalysisy::CheckTagByBigMask(const cv::Mat &img, const cv::Mat &bigm
return 0; return 0;
} }
if (m_pbaseCheckFunction->saveImg.bSaveBQDetImg)
{
std::string cur_time_s = CheckUtil::getCurTimeHMS();
std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + DetImgInfo_shareP->strImgProductID + "/";
CheckUtil::CreateDir(strSaveDir);
std::string strSavePath_out = strSaveDir + cur_time_s + "_" + DetImgInfo_shareP->strCameraName + "_BQ_out.png";
CheckUtil::SaveImgAsync(strSavePath_out, mask);
}
// 调试存图(仅 -s 时保存) // 调试存图(仅 -s 时保存)
if (DetImgInfo_shareP && DetImgInfo_shareP->bsaveProcessImg) if (DetImgInfo_shareP && DetImgInfo_shareP->bsaveProcessImg)
{ {
@ -3047,10 +3240,12 @@ int ImgCheckAnalysisy::CheckTagByBigMask(const cv::Mat &img, const cv::Mat &bigm
} }
// 在某个把手区域附近找宽高匹配的凸起 // 在某个把手区域附近找宽高匹配的凸起
// 匹配条件:1、凸起中心落在把手区域附近(区域宽/高各外扩 HANDLE_NEAR_EXPAND_RATIO) // 匹配条件:1、凸起中心落在把手区域附近(区域宽/高各外扩 fNearRatio 倍)
// 2、凸起宽高与该区域外接矩形宽高的比值都在 [HANDLE_SIZE_MIN_RATIO, HANDLE_SIZE_MAX_RATIO](允许横竖互换) // 2、凸起宽高与该区域外接矩形宽高的比值都在 [fSizeMin, fSizeMax](允许横竖互换)
// 范围参数优先取参数配置(JSON handle_param 下的 handle_search_ratio/handle_size_min/handle_size_max),
// 未配置时用文件顶部宏的默认值
// 返回凸起在 candList 中的下标,-1 表示没找到 // 返回凸起在 candList 中的下标,-1 表示没找到
int ImgCheckAnalysisy::MatchHandleRegion(const std::vector<Handle_Candidate_> &candList, const std::vector<bool> &usedList, int ImgCheckAnalysisy::MatchHandleRegion(const std::vector<Mask_Protrusion_> &candList, const std::vector<bool> &usedList,
const cv::Rect &boxRegion, cv::Rect &roiHandle) const cv::Rect &boxRegion, cv::Rect &roiHandle)
{ {
if (boxRegion.width <= 0 || boxRegion.height <= 0) if (boxRegion.width <= 0 || boxRegion.height <= 0)
@ -3058,9 +3253,21 @@ int ImgCheckAnalysisy::MatchHandleRegion(const std::vector<Handle_Candidate_> &c
return -1; return -1;
} }
// 搜索范围:把手区域外扩(宽/高各外扩区域尺寸的一半),容忍产品摆放偏差 // 匹配范围参数:JSON 配置优先,未配置时用默认值
int nx = std::max(10, (int)(boxRegion.width * HANDLE_NEAR_EXPAND_RATIO)); float fNearRatio = HANDLE_NEAR_EXPAND_RATIO;
int ny = std::max(10, (int)(boxRegion.height * HANDLE_NEAR_EXPAND_RATIO)); float fSizeMin = HANDLE_SIZE_MIN_RATIO;
float fSizeMax = HANDLE_SIZE_MAX_RATIO;
if (m_pbaseCheckFunction != NULL)
{
const Function_Support_Det &supportDetCfg = m_pbaseCheckFunction->supportDet;
if (supportDetCfg.handle_search_ratio > 0) fNearRatio = supportDetCfg.handle_search_ratio;
if (supportDetCfg.handle_size_min_ratio > 0) fSizeMin = supportDetCfg.handle_size_min_ratio;
if (supportDetCfg.handle_size_max_ratio > 0) fSizeMax = supportDetCfg.handle_size_max_ratio;
}
// 搜索范围:把手区域外扩(宽/高各外扩搜索倍数倍),容忍产品摆放偏差
int nx = std::max(10, (int)(boxRegion.width * fNearRatio));
int ny = std::max(10, (int)(boxRegion.height * fNearRatio));
cv::Rect searchBox(boxRegion.x - nx, boxRegion.y - ny, boxRegion.width + 2 * nx, boxRegion.height + 2 * ny); cv::Rect searchBox(boxRegion.x - nx, boxRegion.y - ny, boxRegion.width + 2 * nx, boxRegion.height + 2 * ny);
cv::Point pRegionCenter(boxRegion.x + boxRegion.width / 2, boxRegion.y + boxRegion.height / 2); cv::Point pRegionCenter(boxRegion.x + boxRegion.width / 2, boxRegion.y + boxRegion.height / 2);
@ -3074,8 +3281,8 @@ int ImgCheckAnalysisy::MatchHandleRegion(const std::vector<Handle_Candidate_> &c
{ {
continue; continue;
} }
const Handle_Candidate_ &cand = candList[i]; const Mask_Protrusion_ &cand = candList[i];
const cv::Rect &roi = cand.protr.roi; const cv::Rect &roi = cand.roi;
if (roi.width <= 0 || roi.height <= 0) if (roi.width <= 0 || roi.height <= 0)
{ {
continue; continue;
@ -3097,12 +3304,12 @@ int ImgCheckAnalysisy::MatchHandleRegion(const std::vector<Handle_Candidate_> &c
// 宽高:与把手区域外接矩形的宽高相近(允许横竖互换) // 宽高:与把手区域外接矩形的宽高相近(允许横竖互换)
float fw = (float)roi.width; float fw = (float)roi.width;
float fh = (float)roi.height; float fh = (float)roi.height;
bool bSizeOK = (fw >= boxRegion.width * HANDLE_SIZE_MIN_RATIO && fw <= boxRegion.width * HANDLE_SIZE_MAX_RATIO && bool bSizeOK = (fw >= boxRegion.width * fSizeMin && fw <= boxRegion.width * fSizeMax &&
fh >= boxRegion.height * HANDLE_SIZE_MIN_RATIO && fh <= boxRegion.height * HANDLE_SIZE_MAX_RATIO); fh >= boxRegion.height * fSizeMin && fh <= boxRegion.height * fSizeMax);
if (!bSizeOK) if (!bSizeOK)
{ {
bSizeOK = (fw >= boxRegion.height * HANDLE_SIZE_MIN_RATIO && fw <= boxRegion.height * HANDLE_SIZE_MAX_RATIO && bSizeOK = (fw >= boxRegion.height * fSizeMin && fw <= boxRegion.height * fSizeMax &&
fh >= boxRegion.width * HANDLE_SIZE_MIN_RATIO && fh <= boxRegion.width * HANDLE_SIZE_MAX_RATIO); fh >= boxRegion.width * fSizeMin && fh <= boxRegion.width * fSizeMax);
} }
if (!bSizeOK) if (!bSizeOK)
{ {
@ -3119,17 +3326,17 @@ int ImgCheckAnalysisy::MatchHandleRegion(const std::vector<Handle_Candidate_> &c
if (nBestIdx < 0 && nNearIdx >= 0) if (nBestIdx < 0 && nNearIdx >= 0)
{ {
// 没匹配上时打印区域内最近的凸起,便于排查阈值 // 没匹配上时打印区域内最近的凸起,便于排查阈值
const cv::Rect &roi = candList[nNearIdx].protr.roi; const cv::Rect &roi = candList[nNearIdx].roi;
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle",
"region box [%d %d %d %d] no match, nearest protrusion roi [%d %d %d %d] w/h ratio %0.2f %0.2f", "region box [%d %d %d %d] no match, nearest protrusion corner %d roi [%d %d %d %d] w/h ratio %0.2f %0.2f",
boxRegion.x, boxRegion.y, boxRegion.width, boxRegion.height, boxRegion.x, boxRegion.y, boxRegion.width, boxRegion.height,
roi.x, roi.y, roi.width, roi.height, candList[nNearIdx].nCorner, roi.x, roi.y, roi.width, roi.height,
(float)roi.width / (float)boxRegion.width, (float)roi.height / (float)boxRegion.height); (float)roi.width / (float)boxRegion.width, (float)roi.height / (float)boxRegion.height);
} }
if (nBestIdx >= 0) if (nBestIdx >= 0)
{ {
roiHandle = candList[nBestIdx].protr.roi; roiHandle = candList[nBestIdx].roi;
} }
return nBestIdx; return nBestIdx;
} }

@ -923,6 +923,7 @@ struct Function_Support_Det
{ {
// 支架参数(Support_Detect) // 支架参数(Support_Detect)
bool bDetSupport; // 支架偏位检测开关(Support_Detect.isOpen) bool bDetSupport; // 支架偏位检测开关(Support_Detect.isOpen)
bool bDrawSupport; // 支架绘制开关(Support_Detect.form.support_param.bDrawSupport):是否绘制支架中心十字/距离线
float x_standard; // 水平方向标准距离(left/right 用) float x_standard; // 水平方向标准距离(left/right 用)
float y_standard; // 垂直方向标准距离(up/down 用) float y_standard; // 垂直方向标准距离(up/down 用)
std::vector<std::string> edge_select; // 参与计算的方向:up/down/left/right std::vector<std::string> edge_select; // 参与计算的方向:up/down/left/right
@ -935,6 +936,14 @@ struct Function_Support_Det
std::vector<std::vector<cv::Point>> handleRegions; std::vector<std::vector<cv::Point>> handleRegions;
std::vector<cv::Rect> handleBoxes; std::vector<cv::Rect> handleBoxes;
float handle_diff; // 把手差异阈值 float handle_diff; // 把手差异阈值
// 把手提取/匹配范围参数:JSON(Handle_Detect.form.handle_param)下同名键可选配置,未配置用 ImgCheckAnalysisy.cpp
// 顶部宏的默认值;各参数含义/调整影响见该文件参数注释。现场调这些参数不用改代码重编译
float handle_size_min_ratio; // handle_size_min:凸起宽高 / 区域宽高 比值下限;<=0=未配置(默认 0.8)
float handle_size_max_ratio; // handle_size_max:比值上限;<=0=未配置(默认 1.2)
float handle_search_ratio; // handle_search_ratio:区域附近搜索范围外扩倍数;<=0=未配置(默认 5)
float handle_min_depth_coef; // handle_min_depth:凸起最小深度系数(×量化步长);<=0=未配置(默认 2)
float handle_min_area_coef; // handle_min_area:凸起最小面积系数(×最小深度²);<=0=未配置(默认 2)
float handle_edge_margin_coef; // handle_edge_margin:边缘线外扩系数(×量化步长);<0=未配置(默认 3),0=按“最小深度一半”自动
// 标签参数(BQ_Detect) // 标签参数(BQ_Detect)
bool bDetTag; // 标签检测开关(BQ_Detect.isOpen) bool bDetTag; // 标签检测开关(BQ_Detect.isOpen)
float BQ_area; // 标签检测参数:面积 float BQ_area; // 标签检测参数:面积
@ -950,6 +959,7 @@ struct Function_Support_Det
void Init() void Init()
{ {
bDetSupport = false; bDetSupport = false;
bDrawSupport = false;
x_standard = 0; x_standard = 0;
y_standard = 0; y_standard = 0;
edge_select.clear(); edge_select.clear();
@ -960,6 +970,13 @@ struct Function_Support_Det
handleRegions.clear(); handleRegions.clear();
handleBoxes.clear(); handleBoxes.clear();
handle_diff = 20; handle_diff = 20;
// 范围参数默认“未配置”:0=未配置(用代码默认),edge_margin 用 -1 表示未配置(0=自动)
handle_size_min_ratio = 0;
handle_size_max_ratio = 0;
handle_search_ratio = 0;
handle_min_depth_coef = 0;
handle_min_area_coef = 0;
handle_edge_margin_coef = -1;
bDetTag = false; bDetTag = false;
BQ_area = 0; BQ_area = 0;
BQ_energy = 0; BQ_energy = 0;
@ -969,6 +986,7 @@ struct Function_Support_Det
void copy(Function_Support_Det tem) void copy(Function_Support_Det tem)
{ {
this->bDetSupport = tem.bDetSupport; this->bDetSupport = tem.bDetSupport;
this->bDrawSupport = tem.bDrawSupport;
this->x_standard = tem.x_standard; this->x_standard = tem.x_standard;
this->y_standard = tem.y_standard; this->y_standard = tem.y_standard;
this->edge_select.assign(tem.edge_select.begin(), tem.edge_select.end()); this->edge_select.assign(tem.edge_select.begin(), tem.edge_select.end());
@ -979,6 +997,12 @@ struct Function_Support_Det
this->handleRegions.assign(tem.handleRegions.begin(), tem.handleRegions.end()); this->handleRegions.assign(tem.handleRegions.begin(), tem.handleRegions.end());
this->handleBoxes.assign(tem.handleBoxes.begin(), tem.handleBoxes.end()); this->handleBoxes.assign(tem.handleBoxes.begin(), tem.handleBoxes.end());
this->handle_diff = tem.handle_diff; this->handle_diff = tem.handle_diff;
this->handle_size_min_ratio = tem.handle_size_min_ratio;
this->handle_size_max_ratio = tem.handle_size_max_ratio;
this->handle_search_ratio = tem.handle_search_ratio;
this->handle_min_depth_coef = tem.handle_min_depth_coef;
this->handle_min_area_coef = tem.handle_min_area_coef;
this->handle_edge_margin_coef = tem.handle_edge_margin_coef;
this->bDetTag = tem.bDetTag; this->bDetTag = tem.bDetTag;
this->BQ_area = tem.BQ_area; this->BQ_area = tem.BQ_area;
this->BQ_energy = tem.BQ_energy; this->BQ_energy = tem.BQ_energy;
@ -987,14 +1011,18 @@ struct Function_Support_Det
} }
void print(std::string str) void print(std::string str)
{ {
printf("%s>>bDetSupport %d bDetHandle %d bDetTag %d x_standard %f y_standard %f x_offset %f y_offset %f r_offset %f edge num %ld handleRegion num %ld handle_diff %f BQ[area %f energy %f len %f hj %f]\n", printf("%s>>bDetSupport %d bDrawSupport %d bDetHandle %d bDetTag %d x_standard %f y_standard %f x_offset %f y_offset %f r_offset %f edge num %ld handleRegion num %ld handle_diff %f handleRange[size %.2f~%.2f search %.1f depth %.1f area %.1f margin %.1f(0=自动,<0=默认)] BQ[area %f energy %f len %f hj %f]\n",
str.c_str(), bDetSupport, bDetHandle, bDetTag, x_standard, y_standard, x_offset, y_offset, r_offset, edge_select.size(), handleRegions.size(), handle_diff, BQ_area, BQ_energy, BQ_len, BQ_hj); str.c_str(), bDetSupport, bDrawSupport, bDetHandle, bDetTag, x_standard, y_standard, x_offset, y_offset, r_offset, edge_select.size(), handleRegions.size(), handle_diff,
handle_size_min_ratio, handle_size_max_ratio, handle_search_ratio, handle_min_depth_coef, handle_min_area_coef, handle_edge_margin_coef,
BQ_area, BQ_energy, BQ_len, BQ_hj);
} }
std::string GetInfo(std::string str) std::string GetInfo(std::string str)
{ {
char buffer[512]; char buffer[1024];
sprintf(buffer, "%s>>bDetSupport %d bDetHandle %d bDetTag %d x_standard %f y_standard %f x_offset %f y_offset %f r_offset %f edge num %ld handleRegion num %ld handle_diff %f BQ[area %f energy %f len %f hj %f]\n", sprintf(buffer, "%s>>bDetSupport %d bDrawSupport %d bDetHandle %d bDetTag %d x_standard %f y_standard %f x_offset %f y_offset %f r_offset %f edge num %ld handleRegion num %ld handle_diff %f handleRange[size %.2f~%.2f search %.1f depth %.1f area %.1f margin %.1f(0=自动,<0=默认)] BQ[area %f energy %f len %f hj %f]\n",
str.c_str(), bDetSupport, bDetHandle, bDetTag, x_standard, y_standard, x_offset, y_offset, r_offset, edge_select.size(), handleRegions.size(), handle_diff, BQ_area, BQ_energy, BQ_len, BQ_hj); str.c_str(), bDetSupport, bDrawSupport, bDetHandle, bDetTag, x_standard, y_standard, x_offset, y_offset, r_offset, edge_select.size(), handleRegions.size(), handle_diff,
handle_size_min_ratio, handle_size_max_ratio, handle_search_ratio, handle_min_depth_coef, handle_min_area_coef, handle_edge_margin_coef,
BQ_area, BQ_energy, BQ_len, BQ_hj);
std::string str123 = buffer; std::string str123 = buffer;
return str123; return str123;
} }
@ -1152,6 +1180,7 @@ struct Base_Function_SaveImg
bool bSaveAIDetImg; // mark 存图 bool bSaveAIDetImg; // mark 存图
bool bSaveClsImg; // 分类 存图 bool bSaveClsImg; // 分类 存图
bool bSaveAlginImg; // 定位 存图 bool bSaveAlginImg; // 定位 存图
bool bSaveBQDetImg; // 标签 存图
Base_Function_SaveImg() Base_Function_SaveImg()
{ {
@ -1163,6 +1192,7 @@ struct Base_Function_SaveImg
bSaveAIDetImg = false; bSaveAIDetImg = false;
bSaveClsImg = false; bSaveClsImg = false;
bSaveAlginImg = false; bSaveAlginImg = false;
bSaveBQDetImg = false;
} }
void copy(Base_Function_SaveImg tem) void copy(Base_Function_SaveImg tem)
@ -1172,17 +1202,18 @@ struct Base_Function_SaveImg
this->bSaveAIDetImg = tem.bSaveAIDetImg; this->bSaveAIDetImg = tem.bSaveAIDetImg;
this->bSaveClsImg = tem.bSaveClsImg; this->bSaveClsImg = tem.bSaveClsImg;
this->bSaveAlginImg = tem.bSaveAlginImg; this->bSaveAlginImg = tem.bSaveAlginImg;
this->bSaveBQDetImg = tem.bSaveBQDetImg;
} }
void print(std::string str) void print(std::string str)
{ {
printf("%s>>bOpen %d bSaveAIDetImg %d bSaveClsImg %d bSaveAlginImg %d \n", str.c_str(), printf("%s>>bOpen %d bSaveAIDetImg %d bSaveClsImg %d bSaveAlginImg %d bSaveBQDetImg %d\n", str.c_str(),
bOpen, bSaveAIDetImg, bSaveClsImg, bSaveAlginImg); bOpen, bSaveAIDetImg, bSaveClsImg, bSaveAlginImg, bSaveBQDetImg);
} }
std::string GetInfo(std::string str) std::string GetInfo(std::string str)
{ {
char buffer[256]; char buffer[256];
sprintf(buffer, "%s>>bOpen %d bSaveAIDetImg %d bSaveClsImg %d bSaveAlginImg %d \n", str.c_str(), sprintf(buffer, "%s>>bOpen %d bSaveAIDetImg %d bSaveClsImg %d bSaveAlginImg %d bSaveBQDetImg %d\n", str.c_str(),
bOpen, bSaveAIDetImg, bSaveClsImg, bSaveAlginImg); bOpen, bSaveAIDetImg, bSaveClsImg, bSaveAlginImg, bSaveBQDetImg);
std::string str123 = buffer; std::string str123 = buffer;
return str123; return str123;
} }

@ -873,6 +873,10 @@ int BaseFuntonConfigJson::GetFunction(Json::Value value)
{ {
_config.saveImg.bSaveAIDetImg = value_f["form"]["SaveImg_Config"]["AI_Det_Save"].asBool(); _config.saveImg.bSaveAIDetImg = value_f["form"]["SaveImg_Config"]["AI_Det_Save"].asBool();
} }
if (value_f["form"]["SaveImg_Config"]["BQ_Det_Save"])
{
_config.saveImg.bSaveBQDetImg = value_f["form"]["SaveImg_Config"]["BQ_Det_Save"].asBool();
}
} }
else else
{ {
@ -921,6 +925,12 @@ int BaseFuntonConfigJson::GetFunction(Json::Value value)
{ {
auto value_param = value_f["form"]["support_param"]; auto value_param = value_f["form"]["support_param"];
// 支架绘制开关:控制结果图上是否绘制支架中心十字/距离线(默认关,兼容旧参数)
if (value_param["bDrawSupport"].isBool())
{
_config.supportDet.bDrawSupport = value_param["bDrawSupport"].asBool();
}
if (value_param["x_standard"]) if (value_param["x_standard"])
{ {
_config.supportDet.x_standard = value_param["x_standard"].asFloat(); _config.supportDet.x_standard = value_param["x_standard"].asFloat();
@ -967,6 +977,37 @@ int BaseFuntonConfigJson::GetFunction(Json::Value value)
{ {
_config.supportDet.handle_diff = handle_p["handle_diff"].asFloat(); _config.supportDet.handle_diff = handle_p["handle_diff"].asFloat();
} }
// 把手提取/匹配范围参数(均可选:未配置时用 ImgCheckAnalysisy.cpp 顶部宏的默认值)
// handle_size_min/max :凸起宽高与把手区域外接矩形宽高的比值允许区间(默认 0.8 ~ 1.2)
// handle_search_ratio :把手区域附近搜索范围外扩倍数(默认 5)
// handle_min_depth :凸起最小深度系数(× 定位模型量化步长)(默认 2)
// handle_min_area :凸起最小面积系数(× 最小深度²)(默认 2)
// handle_edge_margin :主体边缘线外扩系数(× 量化步长),0=按“最小深度一半”自动(默认 3)
// 含义/调整影响详见 ImgCheckAnalysisy.cpp 顶部参数注释
if (handle_p["handle_size_min"])
{
_config.supportDet.handle_size_min_ratio = handle_p["handle_size_min"].asFloat();
}
if (handle_p["handle_size_max"])
{
_config.supportDet.handle_size_max_ratio = handle_p["handle_size_max"].asFloat();
}
if (handle_p["handle_search_ratio"])
{
_config.supportDet.handle_search_ratio = handle_p["handle_search_ratio"].asFloat();
}
if (handle_p["handle_min_depth"])
{
_config.supportDet.handle_min_depth_coef = handle_p["handle_min_depth"].asFloat();
}
if (handle_p["handle_min_area"])
{
_config.supportDet.handle_min_area_coef = handle_p["handle_min_area"].asFloat();
}
if (handle_p["handle_edge_margin"])
{
_config.supportDet.handle_edge_margin_coef = handle_p["handle_edge_margin"].asFloat();
}
bool bNewFormat = handle_p["handle_region1"].isArray(); bool bNewFormat = handle_p["handle_region1"].isArray();
for (int idx = 0; idx < HANDLE_REGION_MAX_NUM; idx++) for (int idx = 0; idx < HANDLE_REGION_MAX_NUM; idx++)
{ {

@ -5,6 +5,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include <string> #include <string>
#include <algorithm>
#include "CheckUtil.hpp" #include "CheckUtil.hpp"
std::string ExtractFileNameWithoutExtension(const std::string &strImgPath) std::string ExtractFileNameWithoutExtension(const std::string &strImgPath)
{ {
@ -101,6 +102,7 @@ deal::deal()
m_nCurUseCPUIDX = 0; m_nCurUseCPUIDX = 0;
m_nSaveDetprocessImg = 0; m_nSaveDetprocessImg = 0;
m_nPushImgCount = 0; m_nPushImgCount = 0;
m_nResultRecvCount = 0;
m_DetResult.Init(); m_DetResult.Init();
for (int i = 0; i < READ_IMG_THREAD_NUM; i++) for (int i = 0; i < READ_IMG_THREAD_NUM; i++)
{ {
@ -814,6 +816,8 @@ void deal::ResultThread(int id)
{ {
strlog += "/result"; strlog += "/result";
} }
// 确保日志目录存在(否则 writeLog 报 Failed to open file)
_sysmkdirs_1(strlog.substr(0, strlog.find_last_of('/')));
writeLog(strlog, m_DetResult.strlist); writeLog(strlog, m_DetResult.strlist);
mutex_DetResult_.unlock(); mutex_DetResult_.unlock();
@ -861,6 +865,7 @@ void deal::GetDealResultToQueu()
{ {
mutex_Result_list.lock(); mutex_Result_list.lock();
m_Result_list.push(checkResult); m_Result_list.push(checkResult);
m_nResultRecvCount++;
mutex_Result_list.unlock(); mutex_Result_list.unlock();
} }
else else
@ -903,6 +908,30 @@ bool deal::ReadSystemConfig(const std::string &strPath)
} }
int deal::GetDetImageInfo(std::string strProductID, std::string strSearchImg, std::vector<JC_IMAGE_INFO_> &jcImageInfoList) int deal::GetDetImageInfo(std::string strProductID, std::string strSearchImg, std::vector<JC_IMAGE_INFO_> &jcImageInfoList)
{ {
// -f 批量跑新格式: 产品目录下 input 子目录, 只取 _src 原图
// (如 1M10121544R3B2_t_face_bright_src.jpg, 从图片名解析产品ID和相机名)
if (fs::is_directory(strSearchImg + "/input"))
{
jcImageInfoList.erase(jcImageInfoList.begin(), jcImageInfoList.end());
std::vector<cv::String> srcImgPaths;
cv::glob(strSearchImg + "/input/*.jpg", srcImgPaths, false);
for (int idx = 0; idx < srcImgPaths.size(); idx++)
{
JC_IMAGE_INFO_ tem;
int re = GetImgInfo_BACKPLATE_SRC(srcImgPaths.at(idx), &tem);
if (re != 0)
{
continue;
}
if (tem.strProductID == "")
{
tem.strProductID = strProductID;
}
jcImageInfoList.push_back(tem);
}
return 0;
}
// 1、获取图片路径 // 1、获取图片路径
std::vector<cv::String> img_paths; std::vector<cv::String> img_paths;
std::string strs1 = strSearchImg + "/*.jpg"; std::string strs1 = strSearchImg + "/*.jpg";
@ -1000,6 +1029,63 @@ int deal::GetImgInfo_POL_ET_PNG(std::string strImgPath, JC_IMAGE_INFO_ *pImageIn
} }
return 0; return 0;
} }
// -f 批量跑新格式: 解析图片名组装产品信息
// 图片名示例: 1M10121544R3B2_t_face_bright_src.jpg
// 产品ID: 第一个 '_' 之前的部分 (1M10121544R3B2)
// 相机名: 直接匹配文件名中的相机名(t_face_bright / t_face_dark), 只处理后缀为 _src 的原图
// 通道名: 统一用 t_face
int deal::GetImgInfo_BACKPLATE_SRC(std::string strImgPath, JC_IMAGE_INFO_ *pImageInfo)
{
if (pImageInfo == NULL)
{
return 1;
}
std::string strName = ExtractFileNameWithoutExtension(strImgPath);
// 只处理后缀为 _src 的原图, 跳过 _roi 等其它图
const std::string strSrcSuffix = "_src";
if (strName.size() <= strSrcSuffix.size() ||
strName.compare(strName.size() - strSrcSuffix.size(), strSrcSuffix.size(), strSrcSuffix) != 0)
{
return 1;
}
// 直接匹配文件名中的相机名(t_face_bright / t_face_dark)
std::string strCamName = "";
if (strName.find("t_face_bright") != std::string::npos)
{
strCamName = "t_face_bright";
}
else if (strName.find("t_face_dark") != std::string::npos)
{
strCamName = "t_face_dark";
}
if (strCamName == "")
{
printf("GetImgInfo_BACKPLATE_SRC: no camera match, skip %s\n", strImgPath.c_str());
return 1;
}
// 产品ID = 文件名第一个 '_' 之前的部分
std::string strProductID = strName;
size_t nPos = strName.find('_');
if (nPos != std::string::npos)
{
strProductID = strName.substr(0, nPos);
}
pImageInfo->strProductID = strProductID;
pImageInfo->strCamID = strCamName;
pImageInfo->strchannelName = "t_face"; // 通道名统一用 t_face
pImageInfo->strName = strName;
pImageInfo->strPath = strImgPath;
printf("strImgPath %s strName %s strProductID %s strCam %s strChannel %s\n",
strImgPath.c_str(), strName.c_str(), strProductID.c_str(), strCamName.c_str(), pImageInfo->strchannelName.c_str());
return 0;
}
int deal::InsertDetResult(ReadImgInfo tem) int deal::InsertDetResult(ReadImgInfo tem)
{ {
std::lock_guard<std::mutex> lock(mtx_List[Mutex_Type_DetResult]); std::lock_guard<std::mutex> lock(mtx_List[Mutex_Type_DetResult]);
@ -1203,6 +1289,36 @@ int deal::DetImg()
// getchar(); // getchar();
m_nSystemCheckType = CHECK_TYPE_OFFLING; m_nSystemCheckType = CHECK_TYPE_OFFLING;
StartThread(THREAD_RUN_ALL); StartThread(THREAD_RUN_ALL);
// -8 循环执行:保持常驻,重复处理同一批产品
if (runConfig.bLoop)
{
while (true)
{
usleep(1000 * 1000);
}
}
// -f 批量(不加 -8 只执行一次):等待全部产品处理完成后退出
// DealImg 线程处理完一批后会清空 m_product_ID_List 并等待结果队列清空
while (true)
{
usleep(200 * 1000);
size_t nResultSize = 0;
{
std::lock_guard<std::mutex> lock(mutex_Result_list);
nResultSize = m_Result_list.size();
}
if (m_product_ID_List.empty() && nResultSize <= 0)
{
break;
}
}
// 留时间给结果线程把最后一组结果写完
usleep(2 * 1000 * 1000);
printf(">>> -f batch complete, exit \n");
fflush(NULL);
_exit(0);
return 0; return 0;
} }
int deal::ReJson_product(std::vector<Json_Det_Path> &jsonList, std::string strProductName) int deal::ReJson_product(std::vector<Json_Det_Path> &jsonList, std::string strProductName)
@ -1552,6 +1668,55 @@ int deal::LoadProductID(std::string strImgPath)
m_product_ID_List.erase(m_product_ID_List.begin(), m_product_ID_List.end()); m_product_ID_List.erase(m_product_ID_List.begin(), m_product_ID_List.end());
std::vector<cv::String> img_paths; std::vector<cv::String> img_paths;
// -f 批量跑新格式适配(参考 data/img/test-2 结构):
// <filePath>/<产品ID>_<时间戳>/input/<产品ID>_<相机名>_<位置>_src.jpg
// 也支持直接传入单个产品目录或产品目录下的 input 目录
{
std::string strPath = strImgPath;
while (!strPath.empty() && strPath[strPath.size() - 1] == '/')
{
strPath.erase(strPath.size() - 1);
}
fs::path rootPath(strPath);
if (!strPath.empty() && fs::is_directory(rootPath))
{
// 兼容直接传入 input 目录: 使用其上一级产品目录
if (rootPath.filename() == "input" && rootPath.has_parent_path())
{
rootPath = rootPath.parent_path();
}
std::vector<std::string> productDirList;
if (fs::is_directory(rootPath / "input"))
{
// 直接传入单个产品目录
productDirList.push_back(rootPath.string());
}
else
{
// 批量根目录: 含 input 子目录的子目录视为一个产品
for (const auto &entry : fs::directory_iterator(rootPath))
{
if (entry.is_directory() && fs::is_directory(entry.path() / "input"))
{
productDirList.push_back(entry.path().string());
}
}
}
if (!productDirList.empty())
{
std::sort(productDirList.begin(), productDirList.end());
for (const auto &strProductDir : productDirList)
{
m_product_ID_List.push_back(strProductDir);
std::cout << "new format product dir: " << strProductDir << std::endl;
}
return 0;
}
}
}
// tif 格式 // tif 格式
{ {
std::string str_bmp = strImgPath + "/*.jpg"; std::string str_bmp = strImgPath + "/*.jpg";
@ -1600,6 +1765,11 @@ int deal::LoadProductID(std::string strImgPath)
} }
} }
if (m_product_ID_List.empty())
{
printf("warning: no product dir/image found under %s\n", strImgPath.c_str());
}
return 0; return 0;
} }
int deal::DelImg_Cell_ET() int deal::DelImg_Cell_ET()
@ -1650,6 +1820,13 @@ int deal::Det_OneProduct_Cell_ET(std::string strProductName, int Idx)
{ {
std::string strProductID = GetFileName(strProductName); std::string strProductID = GetFileName(strProductName);
// -f 批量跑新格式: 产品目录名为 <产品ID>_<时间戳>(如 1M10121544R3B2_20261012.151444.066219)
// 取第一个 '_' 之前的部分作为产品ID; 旧格式目录名本身即产品ID(无 '_')不受影响
size_t nProductIDPos = strProductID.find('_');
if (nProductIDPos != std::string::npos)
{
strProductID = strProductID.substr(0, nProductIDPos);
}
// 获取图片路径 // 获取图片路径
std::vector<JC_IMAGE_INFO_> detImgInfoList; std::vector<JC_IMAGE_INFO_> detImgInfoList;
@ -1739,6 +1916,33 @@ int deal::Det_OneProduct_Cell_ET(std::string strProductName, int Idx)
} }
} }
// -f 批量逐个产品处理:等待本产品的检测结果全部返回后,再处理下一个产品,
// 避免多个产品同时积压在检测队列中(内存/检测器压力过大导致卡死)
if (AllImgNum > 0)
{
int nWaitCnt = 0;
while (true)
{
int nRecv = 0;
int nPush = 0;
{
std::lock_guard<std::mutex> lock(mutex_Result_list);
nRecv = m_nResultRecvCount;
nPush = m_nPushImgCount;
}
if (nRecv >= nPush)
{
break;
}
if (nWaitCnt % 250 == 0)
{
printf(">>> waiting %s check result %d/%d ...\n", strProductID.c_str(), nRecv, nPush);
}
nWaitCnt++;
usleep(20 * 1000);
}
}
m_DetResult.one_result_list.push_back(tem_one); m_DetResult.one_result_list.push_back(tem_one);
m_DetResult.det_num_all++; m_DetResult.det_num_all++;
// if (m_DetResult.det_num_all == 1) // if (m_DetResult.det_num_all == 1)

@ -551,6 +551,9 @@ private:
int GetImgInfo_POL_ET_PNG(std::string strImgPath, JC_IMAGE_INFO_ *pImageInfo); int GetImgInfo_POL_ET_PNG(std::string strImgPath, JC_IMAGE_INFO_ *pImageInfo);
// -f 批量跑新格式: 解析 <产品ID>_<相机名>_<位置>_src.jpg 图片名组装产品信息(相机名 t_face_bright/t_face_dark, 通道名统一 t_face)
int GetImgInfo_BACKPLATE_SRC(std::string strImgPath, JC_IMAGE_INFO_ *pImageInfo);
// 功能测试 // 功能测试
int Det_Funtion_Test(); int Det_Funtion_Test();
// 边缘测试 // 边缘测试
@ -649,6 +652,7 @@ public:
int m_nSaveDetprocessImg; int m_nSaveDetprocessImg;
int m_nPushImgCount; // 送检图片总数(拆分后) int m_nPushImgCount; // 送检图片总数(拆分后)
int m_nResultRecvCount; // 已收到检测结果总数(-f 逐个产品处理:等当前产品结果返回后再跑下一个)
cv::Rect m_CutRoi; cv::Rect m_CutRoi;
Det_File_Result_Info m_DetResult; Det_File_Result_Info m_DetResult;

@ -85,6 +85,8 @@ int main(int argc, char *argv[])
cout << "*** 5、./test_JBL_Check -falign filepath num : 批量测试定位算法,num 至多处理张数,结果:/home/aidlux/BOE/Align" << endl; cout << "*** 5、./test_JBL_Check -falign filepath num : 批量测试定位算法,num 至多处理张数,结果:/home/aidlux/BOE/Align" << endl;
cout << "*** 6、./test_JBL_Check -rjson filepath: 单张复测,filepath xx/xx/3A3K380001B1DK_20240408_152157_Main_0_2_L255" << endl; cout << "*** 6、./test_JBL_Check -rjson filepath: 单张复测,filepath xx/xx/3A3K380001B1DK_20240408_152157_Main_0_2_L255" << endl;
cout << "*** 6、./test_JBL_Check -rjsonall filepath: 一套图复测,filepath xx/xx/" << endl; cout << "*** 6、./test_JBL_Check -rjsonall filepath: 一套图复测,filepath xx/xx/" << endl;
cout << "*** 7、./test_BACKPLATE -8: 循环执行程序(常驻),不加 -8 只执行一次" << endl;
cout << "*** 8、./test_BACKPLATE -f filepath: 批量执行 filepath 下的套图(新格式: <产品ID>_<时间戳>/input/<产品ID>_<相机名>_xxx_src.jpg, 相机名 t_face_bright/t_face_dark, 通道名统一 t_face)" << endl;
cout << "******************************************************" << endl; cout << "******************************************************" << endl;
return 0; return 0;

Loading…
Cancel
Save