Compare commits

..

4 Commits

@ -67,7 +67,7 @@ public:
} }
void Init() void Init()
{ {
buseOfft = false; buseOfft = true;
nresult = 0; nresult = 0;
offt_x = 0; offt_x = 0;
offt_y = 0; offt_y = 0;

@ -33,6 +33,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);

@ -98,6 +98,18 @@ struct One_Image_CheckResult_
cv::Rect CutRoi; // 当前检测图片 对应的裁切区域 cv::Rect CutRoi; // 当前检测图片 对应的裁切区域
cv::Rect Param_CropRoi; // 参数模版 对应的裁切区域 cv::Rect Param_CropRoi; // 参数模版 对应的裁切区域
std::shared_ptr<std::vector<QX_ERROR_INFO_>> pQx_ErrorList; // 缺陷错误信息 std::shared_ptr<std::vector<QX_ERROR_INFO_>> pQx_ErrorList; // 缺陷错误信息
bool bBlurNG; // 图像模糊判定: true = 检测图像模糊(判 NG)
float fBlurScore; // 图像清晰度(拉普拉斯方差), 越大越清晰
One_Image_CheckResult_()
{
CutRoi = cv::Rect(0, 0, 0, 0);
Param_CropRoi = cv::Rect(0, 0, 0, 0);
pQx_ErrorList = std::make_shared<std::vector<QX_ERROR_INFO_>>();
bBlurNG = false;
fBlurScore = 0.0f;
}
void print(std::string str = "") void print(std::string str = "")
{ {
std::cout << str << ": " std::cout << str << ": "

@ -125,6 +125,9 @@ private:
// 图片预处理 // 图片预处理
int ImgPreDet(); int ImgPreDet();
// 图像模糊度(清晰度)检测: 在 AI 检测之前执行, 返回 1 表示图像模糊(判 NG), 0 正常
int BlurCheck(const cv::Mat &img);
// 设置新的检测参数 // 设置新的检测参数
int SetNewConfig(); int SetNewConfig();

@ -386,10 +386,11 @@ int AI_Edge_Algin::Get_Edge(int AIModel_type, const cv::Mat &img, DetConfig *pDe
if (pDetConfig->pBaseCheckFunction->saveImg.bSaveAlginImg) if (pDetConfig->pBaseCheckFunction->saveImg.bSaveAlginImg)
{ {
std::string cur_time_s = CheckUtil::getCurTimeHMS(); // 前缀用日期(年月日): 同一产品当天的所有图前缀一致; 文件已存在时在日期后追加序号, 不覆盖旧图
std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + pDetConfig->strProductID + "/"; std::string strDatePre = CheckUtil::getCurrentDateYMD();
CheckUtil::CreateDir(strSaveDir); std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + pDetConfig->strProductID + "/KK/";
std::string strSavePath_out = strSaveDir + cur_time_s + "_" + pDetConfig->strChannel + "_" + std::to_string(AIModel_type) + "_Align_out.png"; std::string strName = pDetConfig->strChannel + "_" + std::to_string(AIModel_type) + "_Align_out";
std::string strSavePath_out = CheckUtil::GetUniqueImgPath(strSaveDir, strDatePre, strName, ".png");
CheckUtil::SaveImgAsync(strSavePath_out, mask); CheckUtil::SaveImgAsync(strSavePath_out, mask);
} }

@ -623,7 +623,8 @@ int ALLImgCheckAnalysisy::SetProductResult(std::shared_ptr<Product> product)
std::shared_ptr<CheckResult> pCheckResult = pcam->ImageALLDetResultList.at(i)->result; std::shared_ptr<CheckResult> pCheckResult = pcam->ImageALLDetResultList.at(i)->result;
if (pCheckResult->qxImageResult.size() > 0) // 图像 NG(包括图像模糊在 AI 检测之前直接判定的 NG) 也要计入产品级结果
if (pCheckResult->qxImageResult.size() > 0 || pCheckResult->nresult > ERROR_TYPE_OK)
{ {
bNG = true; bNG = true;
} }
@ -691,7 +692,7 @@ int ALLImgCheckAnalysisy::PushInImg_New(std::shared_ptr<shareImage> p)
// 对传入图片做一步判断,如果高大于宽,则顺时针旋转90° // 对传入图片做一步判断,如果高大于宽,则顺时针旋转90°
if (p->Det_Mode != DET_MODE_ReJson) if (p->Det_Mode != DET_MODE_ReJson)
{ {
if (!p->img.empty() && p->img.rows > p->img.cols) if (!p->img.empty())
{ {
cv::Mat imgRot; cv::Mat imgRot;
cv::rotate(p->img, imgRot, cv::ROTATE_90_CLOCKWISE); cv::rotate(p->img, imgRot, cv::ROTATE_90_CLOCKWISE);
@ -700,7 +701,7 @@ int ALLImgCheckAnalysisy::PushInImg_New(std::shared_ptr<shareImage> p)
p->img.cols, p->img.rows, imgRot.cols, imgRot.rows); p->img.cols, p->img.rows, imgRot.cols, imgRot.rows);
p->img = imgRot; p->img = imgRot;
} }
if (!p->img_B.empty() && p->img_B.rows > p->img_B.cols) if (!p->img_B.empty())
{ {
cv::Mat imgBRot; cv::Mat imgBRot;
cv::rotate(p->img_B, imgBRot, cv::ROTATE_90_CLOCKWISE); cv::rotate(p->img_B, imgBRot, cv::ROTATE_90_CLOCKWISE);

@ -18,6 +18,11 @@ Json::Value CheckResultJson::toJsonValue()
root["Param_CropRoi"]["width"] = m_pOneImgDetResult->Param_CropRoi.width; root["Param_CropRoi"]["width"] = m_pOneImgDetResult->Param_CropRoi.width;
root["Param_CropRoi"]["height"] = m_pOneImgDetResult->Param_CropRoi.height; root["Param_CropRoi"]["height"] = m_pOneImgDetResult->Param_CropRoi.height;
} }
{
// 图像模糊度(清晰度)检测结果: 在 AI 检测之前计算
root["blurScore"] = m_pOneImgDetResult->fBlurScore;
root["isBlur"] = m_pOneImgDetResult->bBlurNG;
}
for (int i = 0; i < m_pOneImgDetResult->pQx_ErrorList->size(); i++) for (int i = 0; i < m_pOneImgDetResult->pQx_ErrorList->size(); i++)
{ {
QX_ERROR_INFO_ *p = &m_pOneImgDetResult->pQx_ErrorList->at(i); QX_ERROR_INFO_ *p = &m_pOneImgDetResult->pQx_ErrorList->at(i);
@ -86,6 +91,17 @@ void CheckResultJson::toObjectFromValue(Json::Value root)
m_pOneImgDetResult->Param_CropRoi.height = root["Param_CropRoi"]["height"].asInt(); m_pOneImgDetResult->Param_CropRoi.height = root["Param_CropRoi"]["height"].asInt();
} }
} }
{
// 图像模糊度(清晰度)检测结果; 旧版本 json 可能没有该字段
if (root.isMember("blurScore"))
{
m_pOneImgDetResult->fBlurScore = root["blurScore"].asFloat();
}
if (root.isMember("isBlur"))
{
m_pOneImgDetResult->bBlurNG = root["isBlur"].asBool();
}
}
{ {
if (root.isMember("Qx")) if (root.isMember("Qx"))
{ {

@ -19,6 +19,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <fstream> #include <fstream>
#include <thread> #include <thread>
#include <fcntl.h>
#include "snowflake.hpp" #include "snowflake.hpp"
int _sysmkdir_2(const std::string &dir) int _sysmkdir_2(const std::string &dir)
@ -80,6 +81,25 @@ 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;
@ -122,6 +142,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)

@ -147,6 +147,14 @@ int ImageResultJudge::ResultJudge(std::shared_ptr<ImageAllResult> pImageResult)
std::shared_ptr<CheckResult> m_CheckResult_shareP = pImageResult->result; // 返回结果 std::shared_ptr<CheckResult> m_CheckResult_shareP = pImageResult->result; // 返回结果
int qxNum = pDetResult->pQx_ErrorList->size(); int qxNum = pDetResult->pQx_ErrorList->size();
// 图像模糊: 检测阶段已在 AI 检测之前判定, 这里保证结果保持 NG(复测 ReJson 读取 json 后同样生效)
if (pDetResult->bBlurNG)
{
m_CheckResult_shareP->nresult = 1;
pImageLog->AddCheckstr(PrintLevel_0, "Blur Check",
"image blurred -> NG (variance %.2f)", pDetResult->fBlurScore);
}
// map<string, vector<iregionQxZoningInfo>> allQxZoning; // map<string, vector<iregionQxZoningInfo>> allQxZoning;
map<string, vector<singleQxZoningInfo>> allQxZoning; map<string, vector<singleQxZoningInfo>> allQxZoning;

@ -18,11 +18,16 @@
#include <fstream> #include <fstream>
// ===================== Pin 整体偏移估计参数 ===================== // ===================== Pin 整体偏移估计参数 =====================
static const double PIN_SHIFT_MIN_SUPPORT_RATIO = 0.80; // 判定整体偏移所需的最小支持度(支持pin数 / 该排模板pin数) static const double PIN_SHIFT_MIN_SUPPORT_RATIO = 0.40; // 判定整体偏移所需的最小支持度(支持pin数 / 该排模板pin数)
static const float PIN_SHIFT_BIN_SIZE_PX = 4.0f; // 位移投票的网格尺寸(px), 同时作为峰值邻域半径 static const float PIN_SHIFT_BIN_SIZE_PX = 4.0f; // 位移投票的网格尺寸(px), 同时作为峰值邻域半径
static const float PIN_SHIFT_MAX_GAP_RATIO = 0.5f; // 整体位移量级上限 = 相邻pin最小间距 * 该比例 static const float PIN_SHIFT_MAX_GAP_RATIO = 0.8f; // 整体位移量级上限 = 相邻pin最小间距 * 该比例
// ====================================================================================== // ======================================================================================
// ===================== 图像模糊度(清晰度)检测参数 =====================
// 计算清晰度前把检测图最长边缩放到该尺寸(只缩小不放大), 使阈值与图像分辨率无关
static const int BLUR_CHECK_MAX_SIZE = 1000;
// ======================================================================
// 计算点相对旋转矩形“长边轴线”的带符号法向距离(检测图坐标系): // 计算点相对旋转矩形“长边轴线”的带符号法向距离(检测图坐标系):
// 取矩形长边方向作为 Pin 排方向, 返回点沿短边(法线)方向相对矩形中心的投影; // 取矩形长边方向作为 Pin 排方向, 返回点沿短边(法线)方向相对矩形中心的投影;
// 正负号天然把分布在矩形两侧的 Pin 分成两组(上下两排分别估计整体偏移)。 // 正负号天然把分布在矩形两侧的 Pin 分成两组(上下两排分别估计整体偏移)。
@ -59,6 +64,57 @@ static cv::Point2f ApplyAffine2D(const cv::Mat &M, const cv::Point2f &pt)
static_cast<float>(M.at<double>(1, 0) * pt.x + M.at<double>(1, 1) * pt.y + M.at<double>(1, 2))); static_cast<float>(M.at<double>(1, 0) * pt.x + M.at<double>(1, 1) * pt.y + M.at<double>(1, 2)));
} }
// 最小二乘拟合相似变换(旋转+等比缩放+平移): src(模板点) -> dst(当前点), 返回 CV_64F 2x3
// 用全部匹配点做闭式解(不剔外点), 使投影后的残差均值为 0 —— 即模板 Pin 整体居中于当前 Pin;
// 注意: 这一步很关键, 只做"硬贴某一个 pin 对"的平移会让整排留下几像素的系统偏移
static cv::Mat FitSimilarityLS(const std::vector<cv::Point2f> &src, const std::vector<cv::Point2f> &dst)
{
const size_t n = src.size();
if (n < 2 || dst.size() != n)
{
return cv::Mat();
}
double mxs = 0.0, mys = 0.0, mxd = 0.0, myd = 0.0;
for (size_t i = 0; i < n; i++)
{
mxs += src[i].x;
mys += src[i].y;
mxd += dst[i].x;
myd += dst[i].y;
}
mxs /= n;
mys /= n;
mxd /= n;
myd /= n;
double sdp = 0.0; // Σ p·q
double scp = 0.0; // Σ p×q
double den = 0.0; // Σ |p|^2
for (size_t i = 0; i < n; i++)
{
const double px = src[i].x - mxs;
const double py = src[i].y - mys;
const double qx = dst[i].x - mxd;
const double qy = dst[i].y - myd;
sdp += px * qx + py * qy;
scp += px * qy - py * qx;
den += px * px + py * py;
}
if (den <= 1e-12)
{
return cv::Mat();
}
const double scale = std::sqrt(sdp * sdp + scp * scp) / den;
const double theta = std::atan2(scp, sdp);
const double a = scale * std::cos(theta);
const double b = -scale * std::sin(theta);
const double tx = mxd - (a * mxs + b * mys);
const double ty = myd - (-b * mxs + a * mys);
return (cv::Mat_<double>(2, 3) << a, b, tx, -b, a, ty);
}
// 把 Pin 中心按"排"分组(最多两排), 用于内定位法线无法分排时的兜底: // 把 Pin 中心按"排"分组(最多两排), 用于内定位法线无法分排时的兜底:
// 用 minAreaRect 求 Pin 群主方向, 沿短边(法线)投影, 在最大间隙处切分; // 用 minAreaRect 求 Pin 群主方向, 沿短边(法线)投影, 在最大间隙处切分;
// 间隙过小(单排)或切分后某组不足 2 个 Pin 时按一排处理。 // 间隙过小(单排)或切分后某组不足 2 个 Pin 时按一排处理。
@ -689,6 +745,8 @@ int ImgCheckAnalysisy::CalAffineMats(const RotatedRect &tplRefRect, const Rotate
m_affine_tpl2cur = cv::getAffineTransform(tpl_pts, cur_pts); m_affine_tpl2cur = cv::getAffineTransform(tpl_pts, cur_pts);
m_bAffineValid = (!m_affine_cur2tpl.empty() && !m_affine_tpl2cur.empty()); m_bAffineValid = (!m_affine_cur2tpl.empty() && !m_affine_tpl2cur.empty());
m_pEdge_Align_Result->H = m_affine_tpl2cur;
if (!m_bAffineValid) if (!m_bAffineValid)
{ {
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "CalAffineMats: getAffineTransform failed"); m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "CalAffineMats: getAffineTransform failed");
@ -751,6 +809,98 @@ int ImgCheckAnalysisy::TransformTplPinContoursToCur()
return 0; return 0;
} }
// 图像模糊度(清晰度)检测: 必须在 AI 检测之前执行。
// 1) 检测图 detImg 转灰度(本身就是单通道, 这里做兼容);
// 2) 最长边缩放到 BLUR_CHECK_MAX_SIZE(只缩小不放大), 保证阈值与图像分辨率无关;
// 3) 用拉普拉斯算子(CV_64F)响应的方差作为清晰度指标: 方差越小越模糊;
// 4) 方差 < 阈值(基础参数 blurDet.fThreshold, 对应界面参数 blur_threshold) -> 判为模糊图片,
// 参数 blurDet.bNG(界面 bNG)打开时直接判 NG。
// 返回: 1 图像模糊且需要判 NG; 0 正常 / 功能未开启 / 图像无效
int ImgCheckAnalysisy::BlurCheck(const cv::Mat &img)
{
if (m_pbaseCheckFunction == NULL || !m_pbaseCheckFunction->blurDet.bOpen ||
m_pbaseCheckFunction->blurDet.fThreshold <= 0.0f)
{
return 0;
}
// 每帧重置模糊判定结果
if (m_pDetResult)
{
m_pDetResult->bBlurNG = false;
m_pDetResult->fBlurScore = 0.0f;
}
if (img.empty() || img.cols < 5 || img.rows < 5)
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Blur Check", "input img invalid, skip");
return 0;
}
// 1、转灰度(detImg 为单通道, 其他通道情况做兼容)
cv::Mat grayImg;
if (1 == img.channels())
{
grayImg = img;
}
else if (3 == img.channels())
{
cv::cvtColor(img, grayImg, cv::COLOR_BGR2GRAY);
}
else if (4 == img.channels())
{
cv::cvtColor(img, grayImg, cv::COLOR_BGRA2GRAY);
}
else
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Blur Check", "unsupported channels %d, skip", img.channels());
return 0;
}
// 2、按最长边缩放(只缩小不放大)
cv::Mat checkImg = grayImg;
const int nMaxSide = std::max(grayImg.cols, grayImg.rows);
if (nMaxSide > BLUR_CHECK_MAX_SIZE)
{
const double fScale = static_cast<double>(BLUR_CHECK_MAX_SIZE) / static_cast<double>(nMaxSide);
cv::resize(grayImg, checkImg, cv::Size(), fScale, fScale, cv::INTER_AREA);
}
// 3、拉普拉斯方差(越大越清晰)
cv::Mat lapImg;
cv::Laplacian(checkImg, lapImg, CV_64F, 3);
cv::Scalar meanVal;
cv::Scalar stdVal;
cv::meanStdDev(lapImg, meanVal, stdVal);
const float fScore = static_cast<float>(stdVal[0] * stdVal[0]);
const float fThreshold = m_pbaseCheckFunction->blurDet.fThreshold;
const bool bNGOpen = m_pbaseCheckFunction->blurDet.bNG;
const bool bBlur = (fScore < fThreshold);
if (m_pDetResult)
{
m_pDetResult->fBlurScore = fScore;
m_pDetResult->bBlurNG = (bBlur && bNGOpen);
}
m_pdetlog->AddCheckstr(PrintLevel_0, "Blur Check",
"variance %.2f %s threshold %.2f -> %s (bNG %d, img [%d %d] -> check [%d %d])",
fScore, bBlur ? "<" : ">=", fThreshold, bBlur ? "BLUR" : "OK",
static_cast<int>(bNGOpen),
grayImg.cols, grayImg.rows, checkImg.cols, checkImg.rows);
if (bBlur && bNGOpen)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Blur Check", "image blurred -> judge NG");
return 1;
}
if (bBlur)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Blur Check", "image blurred, but bNG is off -> not NG");
}
return 0;
}
int ImgCheckAnalysisy::CheckRun() int ImgCheckAnalysisy::CheckRun()
{ {
@ -1011,6 +1161,15 @@ int ImgCheckAnalysisy::CheckRun()
{ {
m_pImageAllResult->detImg = image(m_outer_roi).clone(); m_pImageAllResult->detImg = image(m_outer_roi).clone();
} }
/*图像模糊度(清晰度)检测: 必须在 AI 检测之前完成, 模糊图片直接判 NG*/
long time_blur_s = CheckUtil::getcurTime();
const bool bBlurNG = (BlurCheck(m_pImageAllResult->detImg) == 1);
long time_blur_e = CheckUtil::getcurTime();
m_pdetlog->AddCheckstr(PrintLevel_0, "2.2、pre detect",
"-------------------------Blur Check------------%s---time %ld----\n",
bBlurNG ? "NG" : "OK", time_blur_e - time_blur_s);
/*多线程开启 AI 推理检测*/ /*多线程开启 AI 推理检测*/
long time_AI_s = CheckUtil::getcurTime(); long time_AI_s = CheckUtil::getcurTime();
m_AItask = std::make_shared<TaskInfo>(); m_AItask = std::make_shared<TaskInfo>();
@ -1019,7 +1178,7 @@ int ImgCheckAnalysisy::CheckRun()
ImgPreDet(); ImgPreDet();
/*更新检测区域*/ /*更新检测区域*/
// Update_DetRoiList(); Update_DetRoiList();
m_pdetlog->AddCheckstr(PrintLevel_0, "3、pre detect", "-------------------------pre Det--------------- \n"); m_pdetlog->AddCheckstr(PrintLevel_0, "3、pre detect", "-------------------------pre Det--------------- \n");
@ -1033,6 +1192,12 @@ int ImgCheckAnalysisy::CheckRun()
m_CheckResult_shareP->nresult = 0; m_CheckResult_shareP->nresult = 0;
// 图像模糊: 在 AI 检测之前已判定, 直接判 NG(nresult=1 与区域参数判断出的 NG 语义一致)
if (bBlurNG)
{
m_CheckResult_shareP->nresult = 1;
}
/*把临时可以绘制的结果都绘制出来。*/ /*把临时可以绘制的结果都绘制出来。*/
DrawResult_Step_1(); DrawResult_Step_1();
long time_AI_e; long time_AI_e;
@ -1779,7 +1944,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();
// 依次使用多个检测模型,各模型输出 mask 取并集作为最终 outmask; // 依次使用多个检测模型,各模型输出 mask 取并集作为最终 outmask;
// Defect_QueXi 模型是否参与合并由基础参数 Merge_QueXi 开关控制 // Defect_QueXi 模型是否参与合并由基础参数 Merge_QueXi 开关控制
std::vector<std::shared_ptr<AIModel_Base>> pAIDetList; std::vector<std::shared_ptr<AIModel_Base>> pAIDetList;
@ -1909,9 +2073,11 @@ int ImgCheckAnalysisy::AI_Detect_Thread(const cv::Mat &img, cv::Mat &ResultImg)
// 目录结构:/home/aidlux/BOE/ZB_SOCKET/AI_Detect/<模型名称>/<产品ID>/ // 目录结构:/home/aidlux/BOE/ZB_SOCKET/AI_Detect/<模型名称>/<产品ID>/
if (m_pbaseCheckFunction->saveImg.bSaveAIDetImg && !modelMaskFull.empty()) if (m_pbaseCheckFunction->saveImg.bSaveAIDetImg && !modelMaskFull.empty())
{ {
std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + DetImgInfo_shareP->strImgProductID + "/"; // 前缀用日期(年月日): 同一产品当天的所有图前缀一致; 文件已存在时在日期后追加序号, 不覆盖旧图
CheckUtil::CreateDir(strSaveDir); std::string strDatePre = CheckUtil::getCurrentDateYMD();
std::string strSavePath_out = strSaveDir + cur_time_s + "_" + m_strCurDetChannel + "_" + strModelName + "_out.png"; std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + DetImgInfo_shareP->strImgProductID + "/KK/";
std::string strName = m_strCurDetChannel + "_" + strModelName + "_out";
std::string strSavePath_out = CheckUtil::GetUniqueImgPath(strSaveDir, strDatePre, strName, ".png");
CheckUtil::SaveImgAsync(strSavePath_out, modelMaskFull); CheckUtil::SaveImgAsync(strSavePath_out, modelMaskFull);
} }
@ -1969,9 +2135,11 @@ int ImgCheckAnalysisy::AI_Detect_Thread(const cv::Mat &img, cv::Mat &ResultImg)
if (m_pbaseCheckFunction->saveImg.bSaveAIDetImg ) if (m_pbaseCheckFunction->saveImg.bSaveAIDetImg )
{ {
std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + DetImgInfo_shareP->strImgProductID + "/"; // 前缀用日期(年月日): 同一产品当天的所有图前缀一致; 文件已存在时在日期后追加序号, 不覆盖旧图
CheckUtil::CreateDir(strSaveDir); std::string strDatePre = CheckUtil::getCurrentDateYMD();
std::string strSavePath_in = strSaveDir + cur_time_s + "_" + m_strCurDetChannel + "_in.png"; std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + DetImgInfo_shareP->strImgProductID + "/KK/";
std::string strName = m_strCurDetChannel + "_in";
std::string strSavePath_in = CheckUtil::GetUniqueImgPath(strSaveDir, strDatePre, strName, ".png");
CheckUtil::SaveImgAsync(strSavePath_in, m_CheckResult_shareP->cutSrcimg); CheckUtil::SaveImgAsync(strSavePath_in, m_CheckResult_shareP->cutSrcimg);
} }
// 临时存图 // 临时存图
@ -2658,7 +2826,7 @@ int ImgCheckAnalysisy::ComputePinProjectionMats()
}; };
// 用模板 Pin 组 rowIdx 与当前 Pin 候选组 curIdxList 拟合一个相似变换(模板图坐标 -> detImg 坐标) // 用模板 Pin 组 rowIdx 与当前 Pin 候选组 curIdxList 拟合一个相似变换(模板图坐标 -> detImg 坐标)
auto fitOneRow = [&](const std::vector<int> &rowIdx, const std::vector<int> &curIdxList, PinRowFit &fit) auto fitOneRow = [&](const std::vector<int> &rowIdx, const std::vector<int> &curIdxList, int rowNo, PinRowFit &fit)
{ {
const int nRow = static_cast<int>(rowIdx.size()); const int nRow = static_cast<int>(rowIdx.size());
const int nCurSub = static_cast<int>(curIdxList.size()); const int nCurSub = static_cast<int>(curIdxList.size());
@ -2789,8 +2957,22 @@ int ImgCheckAnalysisy::ComputePinProjectionMats()
return static_cast<int>(matches.size()); return static_cast<int>(matches.size());
}; };
// 匹配残差(平方和): 用于"重合数相同"时挑出真正居中的解
auto residualOf = [&](const cv::Mat &M, const std::vector<std::pair<int, int>> &matchList) -> double
{
double sum = 0.0;
for (size_t k = 0; k < matchList.size(); k++)
{
const cv::Point2f p = ApplyAffine2D(M, tplCenters[rowIdx[matchList[k].first]]);
const cv::Point2f d = curCenters[matchList[k].second] - p;
sum += static_cast<double>(d.x) * d.x + static_cast<double>(d.y) * d.y;
}
return sum;
};
std::vector<std::pair<int, int>> matches; std::vector<std::pair<int, int>> matches;
int bestOverlap = matchPins(MCur, matches); int bestOverlap = matchPins(MCur, matches);
double bestErr = residualOf(MCur, matches);
cv::Mat bestM = MCur.clone(); cv::Mat bestM = MCur.clone();
for (int iter = 0; iter < 5 && bestOverlap < nRow; iter++) for (int iter = 0; iter < 5 && bestOverlap < nRow; iter++)
{ {
@ -2814,19 +2996,67 @@ int ImgCheckAnalysisy::ComputePinProjectionMats()
break; break;
} }
const int cnt = matchPins(MNew, matches); const int cnt = matchPins(MNew, matches);
if (cnt > bestOverlap) const double err = residualOf(MNew, matches);
// 重合数更多, 或重合数相同但残差更小 -> 采用(避免停留在"只硬贴某一个 pin 对"的投票解上)
if (cnt > bestOverlap || (cnt == bestOverlap && err < bestErr))
{ {
bestOverlap = cnt; bestOverlap = cnt;
bestErr = err;
bestM = MNew.clone(); bestM = MNew.clone();
} }
MCur = MNew; MCur = MNew;
} }
// 最终用全部重合对再做一次最小二乘(闭式解)相似变换:
// 让模板投影相对当前 Pin 的残差均值为 0(整体居中), 消除"整排几像素的系统偏移"
if (bestOverlap >= 2)
{
matchPins(bestM, matches);
if (matches.size() >= 2)
{
std::vector<cv::Point2f> srcPts;
std::vector<cv::Point2f> dstPts;
srcPts.reserve(matches.size());
dstPts.reserve(matches.size());
for (size_t k = 0; k < matches.size(); k++)
{
srcPts.push_back(tplCenters[rowIdx[matches[k].first]]);
dstPts.push_back(curCenters[matches[k].second]);
}
cv::Mat MRefine = FitSimilarityLS(srcPts, dstPts);
// 重合数不下降才采用(避免把错配点带进最小二乘)
if (bReasonableMat(MRefine) && matchPins(MRefine, matches) >= bestOverlap)
{
bestM = MRefine.clone();
bestOverlap = static_cast<int>(matches.size());
}
}
}
if (bestOverlap >= 2 && bReasonableMat(bestM)) if (bestOverlap >= 2 && bReasonableMat(bestM))
{ {
fit.M = bestM; fit.M = bestM;
fit.nMatch = bestOverlap; fit.nMatch = bestOverlap;
fit.bFromPins = true; fit.bFromPins = true;
// 诊断: 模板投影相对当前 Pin 的残差(均值应接近 0, rms 反映重合程度)
matchPins(bestM, matches);
double sumX = 0.0, sumY = 0.0, sumSq = 0.0;
for (size_t k = 0; k < matches.size(); k++)
{
const cv::Point2f p = ApplyAffine2D(bestM, tplCenters[rowIdx[matches[k].first]]);
const cv::Point2f d = curCenters[matches[k].second] - p;
sumX += d.x;
sumY += d.y;
sumSq += static_cast<double>(d.x) * d.x + static_cast<double>(d.y) * d.y;
}
const int nM = static_cast<int>(matches.size());
if (nM > 0)
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Matrix",
"row %d residual: mean(%.2f %.2f) px rms %.2f px n %d",
rowNo, sumX / nM, sumY / nM, std::sqrt(sumSq / nM), nM);
}
} }
}; };
@ -2839,7 +3069,7 @@ int ImgCheckAnalysisy::ComputePinProjectionMats()
{ {
for (int b = 0; b < 2; b++) for (int b = 0; b < 2; b++)
{ {
fitOneRow(rows[a], curRows[b], fits[a][b]); fitOneRow(rows[a], curRows[b], a, fits[a][b]);
} }
} }
const int scoreDirect = fits[0][0].nMatch + fits[1][1].nMatch; const int scoreDirect = fits[0][0].nMatch + fits[1][1].nMatch;
@ -2862,7 +3092,7 @@ int ImgCheckAnalysisy::ComputePinProjectionMats()
// 无法两侧都分排: 每排与全部当前 Pin 匹配 // 无法两侧都分排: 每排与全部当前 Pin 匹配
for (size_t r = 0; r < rows.size(); r++) for (size_t r = 0; r < rows.size(); r++)
{ {
fitOneRow(rows[r], allCurIdx, rowFits[r]); fitOneRow(rows[r], allCurIdx, static_cast<int>(r), rowFits[r]);
} }
} }

@ -49,10 +49,11 @@ int Pin_QX_Det::GetPinMask(const cv::Mat &img, DetConfigResult *pDetConfig, Mat
// 输入图存未做通道转化(灰度化)之前的图; 文件名首位用产品ID // 输入图存未做通道转化(灰度化)之前的图; 文件名首位用产品ID
if (pDetConfig->pBaseCheckFunction != NULL && pDetConfig->pBaseCheckFunction->saveImg.bSavePinImg) if (pDetConfig->pBaseCheckFunction != NULL && pDetConfig->pBaseCheckFunction->saveImg.bSavePinImg)
{ {
std::string cur_time_s = CheckUtil::getCurTimeHMS(); // 前缀用日期(年月日): 同一产品当天的所有图前缀一致; 文件已存在时在日期后追加序号, 不覆盖旧图
std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + pDetConfig->strProductID + "/"; std::string strDatePre = CheckUtil::getCurrentDateYMD();
CheckUtil::CreateDir(strSaveDir); std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + pDetConfig->strProductID + "/KK/";
std::string strSavePath_out = strSaveDir + cur_time_s + "_" + pDetConfig->strChannel + "_pin_out.png"; std::string strName = pDetConfig->strChannel + "_pin_out";
std::string strSavePath_out = CheckUtil::GetUniqueImgPath(strSaveDir, strDatePre, strName, ".png");
CheckUtil::SaveImgAsync(strSavePath_out, mask); CheckUtil::SaveImgAsync(strSavePath_out, mask);
} }

@ -1187,6 +1187,46 @@ struct Base_Function_Merge_QueXi
}; };
// 基础检测功能 图像模糊度(清晰度)检测
// 执行时机: AI 残点检测之前, 对送检图(检测图 detImg)计算模糊度, 模糊图片可直接判 NG
// 界面参数: itemCode = "Blur_Detect", form.Blur_Config = { bNG, blur_threshold }
struct Base_Function_Blur
{
bool bOpen; // 是否开启
bool bNG; // 模糊图片是否直接判 NG
float fThreshold; // 模糊度阈值(拉普拉斯方差下限): 方差低于该值判为模糊
Base_Function_Blur()
{
Init();
}
void Init()
{
bOpen = true; // 默认开启(参数里没有该配置项时也做模糊判定)
bNG = true; // 默认模糊即 NG
fThreshold = 800.0f; // 默认阈值(实测: 清晰图 ~8100, sigma=3 模糊 ~880, sigma=5 模糊 ~250)
}
void copy(Base_Function_Blur tem)
{
this->bOpen = tem.bOpen;
this->bNG = tem.bNG;
this->fThreshold = tem.fThreshold;
}
void print(std::string str)
{
printf("%s>>bOpen %d bNG %d fThreshold %f \n", str.c_str(), bOpen, bNG, fThreshold);
}
std::string GetInfo(std::string str)
{
char buffer[256];
sprintf(buffer, "%s>>bOpen %d bNG %d fThreshold %f \n", str.c_str(), bOpen, bNG, fThreshold);
std::string str123 = buffer;
return str123;
}
};
// 基础检测功能 大缺陷 NG // 基础检测功能 大缺陷 NG
struct Base_Function_BigNG struct Base_Function_BigNG
{ {
@ -1295,6 +1335,7 @@ struct BaseCheckFunction
Base_Function_Pin_Det pinDet; Base_Function_Pin_Det pinDet;
Base_Function_SaveImg saveImg; Base_Function_SaveImg saveImg;
Base_Function_Merge_QueXi Merge_QueXi; Base_Function_Merge_QueXi Merge_QueXi;
Base_Function_Blur blurDet;
Base_Function_BigNG bigNG; Base_Function_BigNG bigNG;
BaseCheckFunction() BaseCheckFunction()
{ {
@ -1306,6 +1347,7 @@ struct BaseCheckFunction
pinDet.Init(); pinDet.Init();
saveImg.Init(); saveImg.Init();
Merge_QueXi.Init(); Merge_QueXi.Init();
blurDet.Init();
bigNG.Init(); bigNG.Init();
} }
void copy(BaseCheckFunction tem) void copy(BaseCheckFunction tem)
@ -1314,6 +1356,7 @@ struct BaseCheckFunction
this->pinDet.copy(tem.pinDet); this->pinDet.copy(tem.pinDet);
this->saveImg.copy(tem.saveImg); this->saveImg.copy(tem.saveImg);
this->Merge_QueXi.copy(tem.Merge_QueXi); this->Merge_QueXi.copy(tem.Merge_QueXi);
this->blurDet.copy(tem.blurDet);
this->bigNG.copy(tem.bigNG); this->bigNG.copy(tem.bigNG);
} }
void print(std::string str) void print(std::string str)
@ -1323,6 +1366,7 @@ struct BaseCheckFunction
pinDet.print("pinDet"); pinDet.print("pinDet");
saveImg.print("saveImg"); saveImg.print("saveImg");
Merge_QueXi.print("Merge_QueXi"); Merge_QueXi.print("Merge_QueXi");
blurDet.print("blurDet");
bigNG.print("bigNG"); bigNG.print("bigNG");
} }
std::string GetInfo(std::string str) std::string GetInfo(std::string str)
@ -1332,6 +1376,7 @@ struct BaseCheckFunction
str123 += pinDet.GetInfo("pinDet"); str123 += pinDet.GetInfo("pinDet");
str123 += saveImg.GetInfo("saveImg"); str123 += saveImg.GetInfo("saveImg");
str123 += Merge_QueXi.GetInfo("Merge_QueXi"); str123 += Merge_QueXi.GetInfo("Merge_QueXi");
str123 += blurDet.GetInfo("blurDet");
str123 += bigNG.GetInfo("bigNG"); str123 += bigNG.GetInfo("bigNG");
// str123 += "\n"; // str123 += "\n";
return str123; return str123;

@ -887,6 +887,43 @@ int BaseFuntonConfigJson::GetFunction(Json::Value value)
_config.Merge_QueXi.bOpen = value_f["isOpen"].asBool(); _config.Merge_QueXi.bOpen = value_f["isOpen"].asBool();
} }
// 图像模糊度(清晰度)检测: AI 检测之前执行, 模糊图片可判 NG
if ("Blur_Detect" == strCode)
{
auto value_f = value;
// std::cout << value_f << std::endl;
// getchar();
_config.blurDet.bOpen = value_f["isOpen"].asBool();
if (_config.blurDet.bOpen)
{
if (value_f["form"]["Blur_Config"]["bNG"])
{
_config.blurDet.bNG = value_f["form"]["Blur_Config"]["bNG"].asBool();
}
// 模糊度阈值: blur_threshold(界面) / threshold 兼容; 未配置或 <= 0 时保留默认阈值
float fThreshold = 0.0f;
if (value_f["form"]["Blur_Config"]["blur_threshold"])
{
fThreshold = value_f["form"]["Blur_Config"]["blur_threshold"].asFloat();
}
else if (value_f["form"]["Blur_Config"]["threshold"])
{
fThreshold = value_f["form"]["Blur_Config"]["threshold"].asFloat();
}
if (fThreshold > 0.0f)
{
_config.blurDet.fThreshold = fThreshold;
}
}
else
{
// 关闭: 阈值置 0, 不做模糊判定
_config.blurDet.bOpen = false;
_config.blurDet.fThreshold = 0.0f;
}
// _config.blurDet.print("blurDet");
}
if ("Big_NG" == strCode) if ("Big_NG" == strCode)
{ {
auto value_f = value; auto value_f = value;

Loading…
Cancel
Save