Compare commits

..

3 Commits

@ -57,6 +57,10 @@ public:
static std::string getCurTimeHMS(); static std::string getCurTimeHMS();
static std::string Op_float2String(float nvalue); static std::string Op_float2String(float nvalue);
static int64_t getSnowId(); static int64_t getSnowId();
// 当前日期(年月日, 8 位纯数字), 形如 "20261029", 便于存图文件名检索/排序
static std::string getCurrentDateYMD();
// 生成不重名的存图完整路径 = strDir + strPrefix + [ "_序号" ] + "_" + strName + strExt
static std::string GetUniqueImgPath(const std::string &strDir, const std::string &strPrefix, const std::string &strName, const std::string &strExt);
static bool JudgRect(cv::Rect roi, int img_w, int img_h); static bool JudgRect(cv::Rect roi, int img_w, int img_h);
static bool JudgRect_SZ(cv::Rect roi, int w, int h); static bool JudgRect_SZ(cv::Rect roi, int w, int h);
static bool compareIgnoreCase(const std::string &str1, const std::string &str2); static bool compareIgnoreCase(const std::string &str1, const std::string &str2);

@ -21,6 +21,7 @@
#include <thread> #include <thread>
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <fcntl.h>
#include "snowflake.hpp" #include "snowflake.hpp"
int _sysmkdir_2(const std::string &dir) int _sysmkdir_2(const std::string &dir)
@ -69,8 +70,10 @@ std::string CheckUtil::getCurrentDate()
// 将 tv_sec 转换为 time_t 类型,代表自1970年1月1日起的秒数 // 将 tv_sec 转换为 time_t 类型,代表自1970年1月1日起的秒数
time_t raw_time = tv.tv_sec; time_t raw_time = tv.tv_sec;
// 使用 localtime 将 time_t 转换为 tm 结构体 // 使用 localtime_r 转换为 tm 结构体。
struct tm *time_info = localtime(&raw_time); // 注意:localtime() 返回共享的静态缓冲区,多线程并发调用会互相覆盖(tm 内容串数据)。
struct tm time_buf;
struct tm *time_info = localtime_r(&raw_time, &time_buf);
// 使用字符串流格式化为 "YYYY-MM-DD" // 使用字符串流格式化为 "YYYY-MM-DD"
std::ostringstream oss; std::ostringstream oss;
@ -80,13 +83,34 @@ std::string CheckUtil::getCurrentDate()
return oss.str(); // 返回日期字符串 return oss.str(); // 返回日期字符串
} }
std::string CheckUtil::getCurrentDateYMD()
{
struct timeval tv;
gettimeofday(&tv, NULL);
time_t raw_time = tv.tv_sec;
// localtime_r 为线程安全版本
struct tm time_buf;
struct tm *time_info = localtime_r(&raw_time, &time_buf);
// 固定 8 位 "YYYYMMDD", 便于文件名检索与排序
std::ostringstream oss;
oss << std::setfill('0')
<< std::setw(4) << (time_info->tm_year + 1900)
<< std::setw(2) << (time_info->tm_mon + 1)
<< std::setw(2) << time_info->tm_mday;
return oss.str();
}
std::string CheckUtil::getCurTimeHMS() std::string CheckUtil::getCurTimeHMS()
{ {
struct timeval tv; struct timeval tv;
gettimeofday(&tv, NULL); // 获取当前时间 gettimeofday(&tv, NULL); // 获取当前时间
time_t raw_time = tv.tv_sec; time_t raw_time = tv.tv_sec;
struct tm *time_info = localtime(&raw_time); // 使用 localtime_r(线程安全);localtime() 的静态缓冲会被其它线程并发覆盖
struct tm time_buf;
struct tm *time_info = localtime_r(&raw_time, &time_buf);
int millisec = tv.tv_usec / 1000; // 微秒转毫秒 int millisec = tv.tv_usec / 1000; // 微秒转毫秒
@ -120,6 +144,40 @@ int64_t CheckUtil::getSnowId()
return 0; return 0;
} }
std::string CheckUtil::GetUniqueImgPath(const std::string &strDir, const std::string &strPrefix, const std::string &strName, const std::string &strExt)
{
if (strDir.empty())
{
return strDir + strPrefix + strName + strExt;
}
// 目录不存在时 O_CREAT 会失败, 先建目录(已存在时内部无副作用)
CreateDir(strDir);
// 去重序号插在前缀之后: <年月日>[_序号]_<通道>_<用途>.png
// 同一产品的每个 strName 每轮只会写一次, 所以各文件序号天然同步:
// 第 1 轮 -> <年月日>_<名称>.png、第 2 轮 -> <年月日>_1_<名称>.png、第 3 轮 -> <年月日>_2_<名称>.png ...
const std::string strHead = strPrefix.empty() ? std::string("") : (strPrefix + "_");
for (int i = 0; i < 100000; i++)
{
std::string strPath = strDir + strHead + (i == 0 ? std::string("") : std::to_string(i) + "_") + strName + strExt;
// O_EXCL: 文件不存在才能创建成功, 即是"原子占位", 并发安全
int fd = ::open(strPath.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0644);
if (fd >= 0)
{
::close(fd);
return strPath;
}
if (errno != EEXIST)
{
// 非"已存在"类错误(权限/路径等)不再重试, 直接返回让 imwrite 把错误暴露出来
printf("create img file[%s] error: %d %s\n", strPath.c_str(), errno, strerror(errno));
return strPath;
}
}
// 序号用尽的兜底(实际不会发生)
return strDir + strHead + std::to_string(CheckUtil::getcurTime()) + "_" + strName + strExt;
}
bool CheckUtil::JudgRect(cv::Rect roi, int img_w, int img_h) bool CheckUtil::JudgRect(cv::Rect roi, int img_w, int img_h)
{ {
if (img_w <= 0 || img_h <= 0) if (img_w <= 0 || img_h <= 0)
@ -722,24 +780,30 @@ 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));
} }
// 迭代裁剪轮数:每轮"带内点 cv::fitLine(L2) 精修 + 容差收紧一半",3 轮足够收敛
#define FIT_EDGE_REFINE_ITERS 3
// 拟合边缘直线:val ≈ fK * idx + fB(点集为 (扫描序号, 边界值)) // 拟合边缘直线:val ≈ fK * idx + fB(点集为 (扫描序号, 边界值))
// 直边检测:在 |k| <= fMaxSlope 的斜率范围内搜索“带宽 ±fTol 内点数最多”的直线: // 做法(简洁版,不再做斜率网格搜索):
// 主体直边段的点最多且共线 → 被选中;把手段(点数更少)、角部过渡/另一侧边缘 // 1) cv::fitLine(DIST_HUBER) 初始化:Huber 是 M 估计,把手凸起/角部过渡等偏离点被自动降权,
// (斜率超出限制)等都不会获胜。随后在带内点集上做最小二乘精修(两轮收紧容差)。 // 不会像普通最小二乘那样被拉偏(旧实现靠粗/细斜率网格找带内最多点,代码长且难调);
// 2) 迭代裁剪 + cv::fitLine(DIST_L2) 精修:每轮只保留距当前直线 ±fUseTol 内的点做最小二乘,
// 容差从 fTol 逐轮收紧到 3px(与旧实现“带内点两轮收紧容差”等价,实测四条边结果与旧实现一致);
// 3) |k| > fMaxSlope(不是近轴直边,多半是角部斜过渡/另一侧边缘)→ 常量线兜底。
// 可调参数(应对实际情况): // 可调参数(应对实际情况):
// fTol:带宽容差(工作图像素)。调大→主体边缘带变宽,浅把手更容易被并入主体(漏检)、 // fTol:裁剪容差(工作图像素)。调大→浅把手更容易被并入主体(漏检)、
// 但更能压住量化台阶毛刺;调小→台阶噪声可能分裂支撑或拟合到非主体结构。 // 但更能压住量化台阶毛刺;调小→台阶噪声可能分裂支撑或拟合到非主体结构。
// 默认由调用方按“量化台阶+2px”传入(见 GetMaskProtrusions 第 5 步)。 // 默认由调用方按“量化台阶+2px”传入(见 GetMaskProtrusions 第 5 步)。
// fMaxSlope:直线搜索斜率上限。调大→允许产品倾斜更大;过大时角部斜过渡段可能 // fMaxSlope:斜率上限。调大→允许产品倾斜更大;过大时角部斜过渡段可能被当成直边;
// 被当成直边;调小→产品倾斜稍大时拟合失败(退回常量线)。 // 调小→产品倾斜稍大时退回常量线。
// 点集为空返回 false;点太少/无有效直线时退回常量线(值取中位数) // 点集为空返回 false;点太少/拟合结果不合理时退回常量线(值取中位数)
static bool FitEdgeLine(const std::vector<cv::Point2f> &pts, double fTol, double fMaxSlope, double &fK, double &fB) static bool FitEdgeLine(const std::vector<cv::Point2f> &pts, double fTol, double fMaxSlope, double &fK, double &fB)
{ {
if (pts.empty()) if (pts.empty())
{ {
return false; return false;
} }
// 常量线兜底:值取中位数 // 常量线兜底:值取中位数(点太少或拟合结果不合理时使用)
fK = 0.0; fK = 0.0;
{ {
std::vector<float> valList; std::vector<float> valList;
@ -751,88 +815,62 @@ static bool FitEdgeLine(const std::vector<cv::Point2f> &pts, double fTol, double
std::nth_element(valList.begin(), valList.begin() + valList.size() / 2, valList.end()); std::nth_element(valList.begin(), valList.begin() + valList.size() / 2, valList.end());
fB = (double)valList[valList.size() / 2]; fB = (double)valList[valList.size() / 2];
} }
if (pts.size() < 16) if (pts.size() < 8)
{ {
return true; // 点太少:用常量线兜底 return true; // 点太少:用常量线兜底
} }
// 1、粗搜索:斜率网格 × 抽样锚点,取带内点数最多的直线 // cv::fitLine 的输出(单位方向向量 + 线上一点)换算成 v = k*t + b
int nBestCnt = -1; auto fnToKB = [](const cv::Vec4f &line, double &k, double &b) -> bool {
double fBestK = 0.0; if (std::fabs((double)line[0]) < 1e-6)
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) return false; // 近垂直线:v 随 t 剧烈变化,无法用 k/b 表示
{
nCnt++;
}
} }
return nCnt; k = (double)line[1] / (double)line[0];
b = (double)line[3] - k * (double)line[2];
return true;
}; };
const int nCoarseN = (int)(fMaxSlope / 0.03 + 0.5); // 斜率粗网格:±nCoarseN×0.03
for (int nk = -nCoarseN; nk <= nCoarseN; nk++) // 1、Huber 稳健初始化(1.345 = 高斯噪声下 ~95% 效率的标准 Huber 系数)
{ cv::Vec4f line;
double fk = nk * 0.03; cv::fitLine(pts, line, cv::DIST_HUBER, 1.345, 1e-3, 1e-3);
for (size_t i = 0; i < pts.size(); i += nStride) double fKFit = 0, fBFit = 0;
{ if (!fnToKB(line, fKFit, fBFit) || std::fabs(fKFit) > fMaxSlope)
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; return true; // 拟不出近轴直边:常量线兜底
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、带内点最小二乘精修(两轮收紧容差) // 2、迭代裁剪 + L2 精修(容差逐轮收紧;某轮点不够或结果不合理时保留上一轮结果)
fK = fBestK;
fB = fBestB;
double fUseTol = fTol; double fUseTol = fTol;
for (int nIter = 0; nIter < 2; nIter++) for (int nIter = 0; nIter < FIT_EDGE_REFINE_ITERS; nIter++)
{ {
std::vector<cv::Point2f> inl; std::vector<cv::Point2f> vecInlier;
inl.reserve(pts.size()); vecInlier.reserve(pts.size());
for (size_t i = 0; i < pts.size(); i++) for (size_t i = 0; i < pts.size(); i++)
{ {
if (std::fabs((double)pts[i].y - (fK * pts[i].x + fB)) <= fUseTol) if (std::fabs((double)pts[i].y - (fKFit * pts[i].x + fBFit)) <= fUseTol)
{ {
inl.push_back(pts[i]); vecInlier.push_back(pts[i]);
} }
} }
if (inl.size() < 8) if (vecInlier.size() < 8)
{ {
break; break; // 支撑点太少:保留当前结果,不再收紧
} }
cv::Vec4f line; cv::Vec4f lineRefit;
cv::fitLine(inl, line, cv::DIST_L2, 0, 1e-3, 1e-3); cv::fitLine(vecInlier, lineRefit, cv::DIST_L2, 0, 1e-3, 1e-3);
if (std::fabs((double)line[0]) > 1e-6) double fKNew = 0, fBNew = 0;
if (!fnToKB(lineRefit, fKNew, fBNew) || std::fabs(fKNew) > fMaxSlope)
{ {
fK = (double)line[1] / (double)line[0]; break; // 精修结果不合理:保留上一轮
fB = (double)line[3] - fK * (double)line[2];
} }
fKFit = fKNew;
fBFit = fBNew;
fUseTol = std::max(3.0, fUseTol * 0.5); fUseTol = std::max(3.0, fUseTol * 0.5);
} }
fK = fKFit;
fB = fBFit;
return true; return true;
} }
@ -840,7 +878,8 @@ static bool FitEdgeLine(const std::vector<cv::Point2f> &pts, double fTol, double
// 产品主体是圆角矩形、把手基本出现在四角附近,把手在掩膜上表现为超出主体四条边缘的外凸块: // 产品主体是圆角矩形、把手基本出现在四角附近,把手在掩膜上表现为超出主体四条边缘的外凸块:
// 1、取最大连通域为主体; // 1、取最大连通域为主体;
// 2、逐行/逐列扫描主体边界,得到 左/右(x=f(y))、上/下(y=f(x)) 四条边缘的边界点集; // 2、逐行/逐列扫描主体边界,得到 左/右(x=f(y))、上/下(y=f(x)) 四条边缘的边界点集;
// 3、对每条边缘做直线拟合(见 FitEdgeLine:取带内点数最多的直线,允许产品小角度倾斜); // 3、对每条边缘做直线拟合(见 FitEdgeLine:cv::fitLine(Huber) 定初值 + 迭代裁剪 + cv::fitLine(L2) 精修,
// 允许产品小角度倾斜);
// 4、掩膜中超出四条边缘(超过容差带)的像素按连通域切分成凸起区域, // 4、掩膜中超出四条边缘(超过容差带)的像素按连通域切分成凸起区域,
// 并按质心所在象限给出就近角(0 左上 1 右上 2 左下 3 右下);深度/面积不足的量化毛刺被过滤。 // 并按质心所在象限给出就近角(0 左上 1 右上 2 左下 3 右下);深度/面积不足的量化毛刺被过滤。
// 可调参数(应对实际情况): // 可调参数(应对实际情况):
@ -990,7 +1029,7 @@ int CheckUtil::GetMaskProtrusions(const cv::Mat &binMask, std::vector<Mask_Protr
} }
// 5、拟合四条边缘直线:左/右 x=f(y)、上/下 y=f(x) // 5、拟合四条边缘直线:左/右 x=f(y)、上/下 y=f(x)
// 直边判定:|k|<=0.6(近轴对齐)且带内(±fTol)点数最多的直线;fTol 覆盖量化台阶+网格误差 // 稳健拟合(先用 cv::fitLine(L1) 定初值,再迭代裁剪 + cv::fitLine(L2) 精修),|k|>0.6(非近轴)时退回常量线
const int nFitStep = std::max(1, nMinDepthWork / 2); // 量化台阶估计(工作图) const int nFitStep = std::max(1, nMinDepthWork / 2); // 量化台阶估计(工作图)
// nExp:边缘外扩量(工作图像素)。显式给定(nEdgeMargin>0)时把边缘线整体向外推该距离, // nExp:边缘外扩量(工作图像素)。显式给定(nEdgeMargin>0)时把边缘线整体向外推该距离,
// 等效于减小凸起区域;未给定时按最小深度的一半自动取值 // 等效于减小凸起区域;未给定时按最小深度的一半自动取值

@ -82,7 +82,15 @@
// 即使拟合到凸起外边缘也不影响方向角 // 即使拟合到凸起外边缘也不影响方向角
#define SUPPORT_REFINE_SEGMENTS_DEFAULT 6 #define SUPPORT_REFINE_SEGMENTS_DEFAULT 6
// 搜索列带内相邻像素(3 行灰度均值)的灰度差小于该值视为无边缘(极性无关,支架比外侧亮/暗均可) // 搜索列带内相邻像素(3 行灰度均值)的灰度差小于该值视为无边缘(极性无关,支架比外侧亮/暗均可)
#define SUPPORT_REFINE_MIN_CONTRAST 15.0 #define SUPPORT_REFINE_MIN_CONTRAST 45.0
// ===== 稳健直线拟合(FitLineRobustTV)的偏离点剔除参数 =====
// FIT_LINE_MAD_SIGMA:MAD 倍数阈值。调大→保留更多采样点(噪声大时更稳,但离群点可能残留、直线被轻微拉偏);
// 调小→剔除更狠(凸起/缺口影响更小,但噪声大时可能误删真实边缘点)
#define FIT_LINE_MAD_SIGMA 3.0
// FIT_LINE_MIN_TOL:最小绝对容差(像素)。剔除阈值 = max(该值, FIT_LINE_MAD_SIGMA×1.4826×MAD),
// 避免边缘本身很直(MAD≈0)时把正常的像素抖动当成偏离点
#define FIT_LINE_MIN_TOL 2.0
// 分类映射配置文件(每行:label索引:缺陷名称;冒号后名称为该缺陷类型名称,行的顺序即缺陷类型索引) // 分类映射配置文件(每行:label索引:缺陷名称;冒号后名称为该缺陷类型名称,行的顺序即缺陷类型索引)
static const std::string CLS_LABEL_CONFIG_FILE = "/home/aidlux/BOE/UseModel_ZB/BM_cls.txt"; static const std::string CLS_LABEL_CONFIG_FILE = "/home/aidlux/BOE/UseModel_ZB/BM_cls.txt";
@ -311,86 +319,79 @@ static cv::Mat MatAffineCompose(const cv::Mat &mA, const cv::Mat &mB)
} }
// 稳健直线拟合:pts 为 (t, v) 采样点,拟合 v = k*t + b(左右边 t=y,v=x;上下边 t=x,v=y) // 稳健直线拟合:pts 为 (t, v) 采样点,拟合 v = k*t + b(左右边 t=y,v=x;上下边 t=x,v=y)
// 先最小二乘,再按“残差中位数的绝对偏差(MAD)”剔除离群点(把手凸起/边缘缺口/内部亮区等),重新拟合; // 三步(逻辑短、参数少,便于调整):
// 返回参与拟合的内点数,<4 表示拟合失败(调用方沿用原边缘) // 1) cv::fitLine(DIST_HUBER) 粗拟合:Huber 是 M 估计,把手凸起/边缘缺口/内部亮区等离群点被自动降权,
// 不会把初值拉偏(旧实现用普通最小二乘求初值,离群点一多整条线都会偏);
// 2) 按残差中位数的绝对偏差(MAD)剔除偏离点:|残差-中位数| > max(FIT_LINE_MIN_TOL, FIT_LINE_MAD_SIGMA×1.4826×MAD) 判为偏离点;
// 3) 内点用 cv::fitLine(DIST_L2) 精修一次。
// 返回参与最终拟合的点数(已剔除偏离点);0 表示无法拟合(点数<2 或近垂直线,不能写成 v=k*t+b)。
// 相比旧实现:不再要求“内点≥4 才算出线”,点数够就给结果,减少调用方“拟合失败→沿用旧边缘”的回退。
static int FitLineRobustTV(const std::vector<cv::Point2f> &pts, double &fK, double &fB) static int FitLineRobustTV(const std::vector<cv::Point2f> &pts, double &fK, double &fB)
{ {
auto fnFit = [](const std::vector<cv::Point2f> &p, double &k, double &b) -> bool { if (pts.size() < 2)
if (p.size() < 2) {
{ return 0;
return false; }
}
double fSumT = 0, fSumV = 0; // cv::fitLine 的输出(单位方向向量 + 线上一点)换算成 v = k*t + b
for (size_t i = 0; i < p.size(); i++) auto fnToKB = [](const cv::Vec4f &line, double &k, double &b) -> bool {
{ if (std::fabs((double)line[0]) < 1e-6)
fSumT += p[i].x;
fSumV += p[i].y;
}
const double fMeanT = fSumT / p.size();
const double fMeanV = fSumV / p.size();
double fSxx = 0, fSxy = 0;
for (size_t i = 0; i < p.size(); i++)
{
const double fDt = p[i].x - fMeanT;
fSxx += fDt * fDt;
fSxy += fDt * (p[i].y - fMeanV);
}
if (fSxx < 1e-6)
{ {
return false; return false; // 近垂直线:v 随 t 剧烈变化,无法用 k/b 表示
} }
k = fSxy / fSxx; k = (double)line[1] / (double)line[0];
b = fMeanV - k * fMeanT; b = (double)line[3] - k * (double)line[2];
return true; return true;
}; };
if (pts.size() < 4) // 1、Huber 粗拟合(1.345 = 高斯噪声下 ~95% 效率的标准 Huber 系数;离群点被降权)
cv::Vec4f line;
cv::fitLine(pts, line, cv::DIST_HUBER, 1.345, 1e-3, 1e-3);
double fKFit = 0, fBFit = 0;
if (!fnToKB(line, fKFit, fBFit))
{ {
return 0; return 0;
} }
double fKFit = 0, fBFit = 0;
if (!fnFit(pts, fKFit, fBFit)) // 2、按残差 MAD 剔除偏离点
std::vector<double> vecRes;
vecRes.reserve(pts.size());
for (size_t i = 0; i < pts.size(); i++)
{ {
return 0; vecRes.push_back(pts[i].y - (fKFit * pts[i].x + fBFit));
} }
std::vector<double> vecTmp = vecRes;
std::nth_element(vecTmp.begin(), vecTmp.begin() + vecTmp.size() / 2, vecTmp.end());
const double fMedRes = vecTmp[vecTmp.size() / 2];
for (size_t i = 0; i < vecTmp.size(); i++)
{
vecTmp[i] = std::fabs(vecTmp[i] - fMedRes);
}
std::nth_element(vecTmp.begin(), vecTmp.begin() + vecTmp.size() / 2, vecTmp.end());
const double fMad = vecTmp[vecTmp.size() / 2];
const double fTol = std::max((double)FIT_LINE_MIN_TOL, (double)FIT_LINE_MAD_SIGMA * 1.4826 * fMad);
std::vector<cv::Point2f> vecInlier; std::vector<cv::Point2f> vecInlier;
for (int nIter = 0; nIter < 2; nIter++) vecInlier.reserve(pts.size());
for (size_t i = 0; i < pts.size(); i++)
{ {
std::vector<double> vecRes; if (std::fabs(vecRes[i] - fMedRes) <= fTol)
vecRes.reserve(pts.size());
for (size_t i = 0; i < pts.size(); i++)
{
vecRes.push_back(pts[i].y - (fKFit * pts[i].x + fBFit));
}
std::vector<double> vecAbs = vecRes;
std::nth_element(vecAbs.begin(), vecAbs.begin() + vecAbs.size() / 2, vecAbs.end());
const double fMed = vecAbs[vecAbs.size() / 2];
for (size_t i = 0; i < vecAbs.size(); i++)
{
vecAbs[i] = std::fabs(vecAbs[i] - fMed);
}
std::nth_element(vecAbs.begin(), vecAbs.begin() + vecAbs.size() / 2, vecAbs.end());
const double fMad = vecAbs[vecAbs.size() / 2];
const double fThr = std::max(2.0, 3.0 * 1.4826 * fMad);
vecInlier.clear();
for (size_t i = 0; i < pts.size(); i++)
{
if (std::fabs(pts[i].y - (fKFit * pts[i].x + fBFit)) <= fThr)
{
vecInlier.push_back(pts[i]);
}
}
if (vecInlier.size() < 4 || !fnFit(vecInlier, fKFit, fBFit))
{ {
break; vecInlier.push_back(pts[i]);
} }
} }
if (vecInlier.size() < 4) if (vecInlier.size() < 2)
{ {
return 0; // 数据退化成两簇等极端情况:保留 Huber 结果,避免调用方回退到旧边缘
fK = fKFit;
fB = fBFit;
return (int)vecInlier.size();
} }
// 3、内点最小二乘精修(换算失败时保留 Huber 结果)
cv::fitLine(vecInlier, line, cv::DIST_L2, 0, 1e-3, 1e-3);
fnToKB(line, fKFit, fBFit);
fK = fKFit; fK = fKFit;
fB = fBFit; fB = fBFit;
return (int)vecInlier.size(); return (int)vecInlier.size();
@ -1903,21 +1904,19 @@ int ImgCheckAnalysisy::CheckRun()
// 外侧 -> 内侧逐像素扫,取灰度梯度最大的位置作为该行支架边缘点 // 外侧 -> 内侧逐像素扫,取灰度梯度最大的位置作为该行支架边缘点
int nEdge = -1; int nEdge = -1;
float fBestGrad = 0.0f;
for (int x = nX0; (nStepX > 0) ? (x + nStepX <= nX1) : (x + nStepX >= nX1); x += nStepX) for (int x = nX0; (nStepX > 0) ? (x + nStepX <= nX1) : (x + nStepX >= nX1); x += nStepX)
{ {
if (x < 1 || x + nStepX < 1 || x >= grayRef.cols - 1 || x + nStepX >= grayRef.cols - 1) if (x < 1 || x + nStepX < 1 || x >= grayRef.cols - 1 || x + nStepX >= grayRef.cols - 1)
{ {
continue; continue;
} }
const float fGrad = fabs(gray3(nYDet, x + nStepX) - gray3(nYDet, x)); const float fGrad = gray3(nYDet, x);
if (fGrad > fBestGrad) if (fGrad > (float)SUPPORT_REFINE_MIN_CONTRAST)
{ {
fBestGrad = fGrad;
nEdge = x; nEdge = x;
} }
} }
if (nEdge < 0 || fBestGrad < (float)SUPPORT_REFINE_MIN_CONTRAST) if (nEdge < 0)
{ {
continue; // 该行列带内没有明显灰度阶跃,本行不参与拟合 continue; // 该行列带内没有明显灰度阶跃,本行不参与拟合
} }
@ -3367,8 +3366,6 @@ 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;
@ -3478,10 +3475,10 @@ 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/" + DetImgInfo_shareP->strImgProductID + "/"; std::string strDatePre = CheckUtil::getCurrentDateYMD();
CheckUtil::CreateDir(strSaveDir); std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + DetImgInfo_shareP->strImgProductID + "/BM/";
std::string strSavePath_in = strSaveDir + cur_time_s + "_" + strCameraName + "_in.png"; std::string strSavePath_in = CheckUtil::GetUniqueImgPath(strSaveDir, strDatePre, strCameraName, "_in.png");
std::string strSavePath_out = strSaveDir + cur_time_s + "_" + strCameraName + "_out.png"; std::string strSavePath_out = CheckUtil::GetUniqueImgPath(strSaveDir, strDatePre, strCameraName, "_out.png");
CheckUtil::SaveImgAsync(strSavePath_in, m_CheckResult_shareP->cutSrcimg); CheckUtil::SaveImgAsync(strSavePath_in, m_CheckResult_shareP->cutSrcimg);
CheckUtil::SaveImgAsync(strSavePath_out, ResultImg); CheckUtil::SaveImgAsync(strSavePath_out, ResultImg);
} }
@ -4306,10 +4303,9 @@ int ImgCheckAnalysisy::CheckTagByBigMask(const cv::Mat &img, const cv::Mat &bigm
if (m_pbaseCheckFunction->saveImg.bSaveBQDetImg) if (m_pbaseCheckFunction->saveImg.bSaveBQDetImg)
{ {
std::string cur_time_s = CheckUtil::getCurTimeHMS(); std::string strDatePre = CheckUtil::getCurrentDateYMD();
std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + DetImgInfo_shareP->strImgProductID + "/"; std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + DetImgInfo_shareP->strImgProductID + "/BM/";
CheckUtil::CreateDir(strSaveDir); std::string strSavePath_out = CheckUtil::GetUniqueImgPath(strSaveDir, strDatePre, DetImgInfo_shareP->strCameraName, "_BQ_out.png");
std::string strSavePath_out = strSaveDir + cur_time_s + "_" + DetImgInfo_shareP->strCameraName + "_BQ_out.png";
CheckUtil::SaveImgAsync(strSavePath_out, mask); CheckUtil::SaveImgAsync(strSavePath_out, mask);
} }

Loading…
Cancel
Save