You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4126 lines
135 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* @Author: your name
* @Date: 2022-04-20 15:50:00
* @LastEditTime: 2025-09-23 11:34:53
* @LastEditors: xiewenji 527774126@qq.com
* @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
* @FilePath: /ZCXD_MonitorPlatform/src/CoreLogicModule/src/CamDeal.cpp
*/
#include "ImgCheckAnalysisy.hpp"
#include "CheckUtil.hpp"
#include "Define.h"
#include <omp.h>
#include <map>
#include <algorithm>
#include <cctype>
#include "AICommonDefine.h"
#include <cstdlib>
#include <fstream>
// ===================== Pin 整体偏移估计参数 =====================
static const double PIN_SHIFT_MIN_SUPPORT_RATIO = 0.80; // 判定整体偏移所需的最小支持度(支持pin数 / 该排模板pin数)
static const float PIN_SHIFT_BIN_SIZE_PX = 4.0f; // 位移投票的网格尺寸(px), 同时作为峰值邻域半径
static const float PIN_SHIFT_MAX_GAP_RATIO = 0.5f; // 整体位移量级上限 = 相邻pin最小间距 * 该比例
// ======================================================================================
// 计算点相对旋转矩形“长边轴线”的带符号法向距离(检测图坐标系):
// 取矩形长边方向作为 Pin 排方向, 返回点沿短边(法线)方向相对矩形中心的投影;
// 正负号天然把分布在矩形两侧的 Pin 分成两组(上下两排分别估计整体偏移)。
static float PinRowSideOfRect(const cv::RotatedRect &rr, const cv::Point2f &pt)
{
const float th = static_cast<float>(rr.angle * CV_PI / 180.0);
// OpenCV RotatedRect(图像坐标系, y 轴向下): 宽度方向 (cos, -sin), 高度方向 (sin, cos)
const cv::Point2f widthDir(std::cos(th), -std::sin(th));
const cv::Point2f heightDir(std::sin(th), std::cos(th));
const cv::Point2f normalDir = (rr.size.width >= rr.size.height) ? heightDir : widthDir;
return (pt - rr.center).dot(normalDir);
}
// 规范化 Pin 检测方案字符串: 去掉首尾空白并转小写(兼容配置里的大小写/空格差异)
static std::string NormalizeDetMethodStr(const std::string &method)
{
size_t begin = method.find_first_not_of(" \t\r\n");
if (begin == std::string::npos)
{
return "";
}
size_t end = method.find_last_not_of(" \t\r\n");
std::string ret = method.substr(begin, end - begin + 1);
std::transform(ret.begin(), ret.end(), ret.begin(),
[](unsigned char c)
{ return static_cast<char>(std::tolower(c)); });
return ret;
}
// 应用 2x3 仿射矩阵(CV_64F): 模板图坐标 -> 目标图坐标
static cv::Point2f ApplyAffine2D(const cv::Mat &M, const cv::Point2f &pt)
{
return cv::Point2f(static_cast<float>(M.at<double>(0, 0) * pt.x + M.at<double>(0, 1) * pt.y + M.at<double>(0, 2)),
static_cast<float>(M.at<double>(1, 0) * pt.x + M.at<double>(1, 1) * pt.y + M.at<double>(1, 2)));
}
// 把 Pin 中心按"排"分组(最多两排), 用于内定位法线无法分排时的兜底:
// 用 minAreaRect 求 Pin 群主方向, 沿短边(法线)投影, 在最大间隙处切分;
// 间隙过小(单排)或切分后某组不足 2 个 Pin 时按一排处理。
static std::vector<std::vector<int>> SplitPinsIntoRows(const std::vector<cv::Point2f> &pts)
{
std::vector<std::vector<int>> rows;
const int n = static_cast<int>(pts.size());
std::vector<int> all(n);
for (int i = 0; i < n; i++)
{
all[i] = i;
}
if (n < 4)
{
rows.push_back(all);
return rows;
}
cv::RotatedRect rr = cv::minAreaRect(pts);
const double th = rr.angle * CV_PI / 180.0;
const cv::Point2f widthDir(static_cast<float>(std::cos(th)), static_cast<float>(-std::sin(th)));
const cv::Point2f heightDir(static_cast<float>(std::sin(th)), static_cast<float>(std::cos(th)));
cv::Point2f nor = (rr.size.width <= rr.size.height) ? widthDir : heightDir;
// 统一法线朝向, 避免 ±180 度只是翻转上下排的先后顺序
if (nor.y < 0.0f || (nor.y == 0.0f && nor.x < 0.0f))
{
nor = -nor;
}
std::vector<float> proj(n);
std::vector<int> order(n);
for (int i = 0; i < n; i++)
{
order[i] = i;
proj[i] = pts[i].dot(nor);
}
std::sort(order.begin(), order.end(), [&](int a, int b)
{ return proj[a] < proj[b]; });
int splitK = -1;
float bestGap = -1.0f;
for (int k = 1; k <= n - 3; k++)
{
float gap = proj[order[k + 1]] - proj[order[k]];
if (gap > bestGap)
{
bestGap = gap;
splitK = k;
}
}
// 两排间距不明显(单排)或切分后某组不足 2 个 Pin -> 按一排处理
if (splitK < 1 || splitK > n - 3 || bestGap < 5.0f)
{
rows.push_back(all);
return rows;
}
rows.push_back(std::vector<int>(order.begin(), order.begin() + splitK + 1));
rows.push_back(std::vector<int>(order.begin() + splitK + 1, order.end()));
return rows;
}
// 分类映射配置文件(每行:label索引:缺陷名称;冒号后名称为该缺陷类型名称,行的顺序即缺陷类型索引)
static const std::string CLS_LABEL_CONFIG_FILE = "/home/aidlux/BOE/UseModel_ZB/KK_cls.txt";
// 去掉字符串首尾的空白字符和引号
static std::string ClsTrimStr(const std::string &str)
{
size_t begin = str.find_first_not_of(" \t\r\n\"");
if (begin == std::string::npos)
{
return "";
}
size_t end = str.find_last_not_of(" \t\r\n\"");
return str.substr(begin, end - begin + 1);
}
// 组装程序内默认缺陷名称列表(AI_CLass_QX_NAME_Names)
static std::vector<std::string> BuildDefaultAIQXNames()
{
std::vector<std::string> vecNames;
for (int i = 0; i < AI_CLass_QX_NAME_count; i++)
{
vecNames.push_back(AI_CLass_QX_NAME_Names[i]);
}
return vecNames;
}
// 分类映射配置:缺陷名称列表(索引=行序) + 分类label->缺陷名称 映射
struct ClsLabelConfig
{
std::vector<std::string> vecNames; // 缺陷名称列表
std::map<int, std::string> mapLabelName; // 分类 label -> 缺陷名称
};
// 读取分类映射配置(每行:label索引:缺陷名称;冒号后名称按行序构成缺陷名称列表;# 后内容忽略)
static ClsLabelConfig ReadClsLabelConfig(const std::string &strFilePath)
{
ClsLabelConfig config;
std::ifstream ifs(strFilePath.c_str());
if (!ifs.is_open())
{
printf("Open cls label config failed, use default mapping: %s\n", strFilePath.c_str());
return config;
}
std::string strLine;
while (std::getline(ifs, strLine))
{
// # 后内容(含注释)忽略
size_t nPosComment = strLine.find('#');
if (nPosComment != std::string::npos)
{
strLine = strLine.substr(0, nPosComment);
}
size_t nPos = strLine.find(':');
if (nPos == std::string::npos)
{
// 无冒号:非标准行,跳过并提示(仅支持 label索引:缺陷名称 格式)
std::string strTrim = ClsTrimStr(strLine);
if (!strTrim.empty())
{
printf("Cls label config skip invalid line: %s\n", strTrim.c_str());
}
continue;
}
// label索引:缺陷名称(冒号后名称按行序构成缺陷名称列表)
std::string strKey = ClsTrimStr(strLine.substr(0, nPos));
std::string strValue = ClsTrimStr(strLine.substr(nPos + 1));
if (!strKey.empty() && !strValue.empty())
{
config.mapLabelName[atoi(strKey.c_str())] = strValue;
config.vecNames.push_back(strValue); // 行序即缺陷类型索引
}
}
return config;
}
// 获取分类映射配置(只读一次)
static const ClsLabelConfig &GetClsLabelConfig()
{
static const ClsLabelConfig config = ReadClsLabelConfig(CLS_LABEL_CONFIG_FILE);
return config;
}
// 获取缺陷名称列表:优先取配置文件的名称列表,读不到用程序内默认列表
static const std::vector<std::string> &GetAIQXNameList()
{
static const std::vector<std::string> vecDefaultNames = BuildDefaultAIQXNames();
const ClsLabelConfig &config = GetClsLabelConfig();
if (!config.vecNames.empty())
{
return config.vecNames;
}
return vecDefaultNames;
}
// 缺陷名称 -> AI_CLass 枚举(找不到返回 count 表示无效)
static int ClsNameToAItype(const std::string &strName)
{
const std::vector<std::string> &vecNames = GetAIQXNameList();
for (size_t i = 0; i < vecNames.size(); i++)
{
if (strName == vecNames[i])
{
return (int)i;
}
}
return AI_CLass_QX_NAME_count;
}
// 解析 label->缺陷名称 映射为 label->AI_CLass枚举(只读一次;无效名称仅提示一次)
static std::map<int, int> BuildLabelTypeMap()
{
const ClsLabelConfig &config = GetClsLabelConfig();
std::map<int, int> mapLabelType;
for (std::map<int, std::string>::const_iterator it = config.mapLabelName.begin(); it != config.mapLabelName.end(); ++it)
{
int nType = ClsNameToAItype(it->second);
if (nType < AI_CLass_QX_NAME_count)
{
mapLabelType[it->first] = nType;
}
else
{
printf("Cls label config unknown name: %s\n", it->second.c_str());
}
}
return mapLabelType;
}
// 获取分类 label 对应的 AI 缺陷类型:优先配置文件(只读一次),读不到用默认顺序
static int GetClsTypeByLabel(int nLabel)
{
static const std::map<int, int> mapLabelType = BuildLabelTypeMap();
std::map<int, int>::const_iterator it = mapLabelType.find(nLabel);
if (it != mapLabelType.end())
{
return it->second;
}
// 映射文件未读到、或该 label 未在文件中配置时,按程序内默认顺序兜底(避免文件缺失/不全导致异常)
int cls_num = AI_CLass_QX_NAME_count;
switch (nLabel)
{
case 0:
cls_num = AI_CLass_QX_NAME_PIN_PoSun;
break;
case 1:
cls_num = AI_CLass_QX_NAME_PIN_QueShi;
break;
case 2:
cls_num = AI_CLass_QX_NAME_BTB_DuanLie;
break;
case 3:
cls_num = AI_CLass_QX_NAME_LianXi;
break;
case 4:
cls_num = AI_CLass_QX_NAME_QueXi;
break;
case 5:
cls_num = AI_CLass_QX_NAME_YiWu;
break;
case 6:
cls_num = AI_CLass_QX_NAME_C_BianXing;
break;
case 7:
cls_num = AI_CLass_QX_NAME_Panel;
break;
default:
break;
}
return cls_num;
}
// 用于排序轮廓的比较函数
static bool compareContourAreas(const vector<Point> &contour1, const vector<Point> &contour2)
{
double i = contourArea(contour1);
double j = contourArea(contour2);
return (i > j);
}
ImgCheckAnalysisy::ImgCheckAnalysisy()
: m_pAI_Edge_Algin(m_pdetlog)
, m_Pin_QX_Det(m_pdetlog)
{
m_nErrorCode = CHECK_OK;
m_nThreadIdx = -1;
m_bInitSucc = false;
m_bExit = false;
m_nRun_Status = CHECK_THREAD_STATUS_IDLE;
m_fImgage_Scale_X = 0.03f;
m_fImgage_Scale_Y = 0.03f;
m_pBasicConfig = NULL;
m_strCurDetChannel = "";
m_bupdateconfig = false;
m_pbaseCheckFunction = &m_AnalysisyConfig.baseFunction;
m_strLastDate = "";
m_strRootPath_KK_cls = "/home/aidlux/BOE/Cls/KK/";
creatsavedir();
m_pImageStorage = ImageStorage::getInstance();
m_nConfigIdx = -1;
m_ImgBlobHFlagData = NULL;
}
ImgCheckAnalysisy::~ImgCheckAnalysisy()
{
ExitSystem();
if (m_ImgBlobHFlagData)
{
delete[] m_ImgBlobHFlagData;
m_ImgBlobHFlagData = NULL;
}
}
int ImgCheckAnalysisy::UpdateConfig(void *pconfig, int nConfigType)
{
int re = 0;
switch (nConfigType)
{
case CHECK_CONFIG_Run:
re = LoadRunConfig(pconfig);
if (re == 0)
{
// printf("---> LoadRunConfig Succ\n");
}
else
{
printf("---> LoadRunConfig Fail\n");
}
break;
case CHECK_CONFIG_Module:
re = LoadCheckConfig(pconfig);
if (re == 0)
{
// printf("---> LoadAnalysisConfig Succ\n");
}
else
{
printf("---> LoadAnalysisConfig Fail\n");
}
break;
default:
break;
}
return re;
}
int ImgCheckAnalysisy::RunStart(void *pconfig1)
{
// 1 、更新参数 并判断参数是否合法
int re = CHECK_OK;
re = SetNewConfig();
if (CHECK_OK != re)
{
m_nErrorCode = re;
return m_nErrorCode;
}
// printf("---> RunStart Start m_RunConfig.nThreadIdx %d \n", m_RunConfig.nThreadIdx);
m_nThreadIdx = m_RunConfig.nThreadIdx;
re = InitRun(m_RunConfig.nCpu_start_Idx);
if (CHECK_OK != re)
{
m_nErrorCode = re;
return m_nErrorCode;
}
runner = std::make_shared<AIMulThreadRunBase>();
runner->Start();
m_nErrorCode = CHECK_OK;
// printf("ImgCheckAnalysisy >>>> ImgCheckThread %d Start Succ \n", m_nThreadIdx);
return m_nErrorCode;
}
int ImgCheckAnalysisy::SetDataRun_SharePtr(std::shared_ptr<ImageAllResult> p)
{
// 设置正在检测
m_pImageAllResult = p;
DetImgInfo_shareP = p->result->in_shareImage;
m_CheckResult_shareP = p->result;
m_pImageAllResult->setStep(ImageAllResult::DetStep_Deting);
m_pdetlog = p->detlog;
m_pDetResult = p->pDetResult;
StartCheck();
m_nErrorCode = CHECK_OK;
return m_nErrorCode;
}
int ImgCheckAnalysisy::GetCheckReuslt(std::shared_ptr<ImageDetResult> &pResult)
{
m_CheckResult_shareP.reset();
DetImgInfo_shareP.reset();
SetIDLE();
// m_nErrorCode = CHECK_OK;
// printf("4 DetImgInfo_shareP count %ld m_nCheckResultErrorCode %d \n", DetImgInfo_shareP.use_count(), m_nCheckResultErrorCode);
return m_nCheckResultErrorCode;
}
int ImgCheckAnalysisy::CheckImg(std::shared_ptr<ImageDetconfig> p, std::shared_ptr<ImageDetResult> &pResult)
{
return 0;
}
int ImgCheckAnalysisy::ReJsonResul(std::shared_ptr<ImageDetconfig> p, std::shared_ptr<ImageDetResult> &pResult)
{
return 0;
}
int ImgCheckAnalysisy::InitRun(int nId)
{
int re = CHECK_OK;
if (m_bInitSucc)
{
return CHECK_OK;
}
InitModel();
AI_Factory = AIFactory::GetInstance();
m_nRun_Status = CHECK_THREAD_STATUS_IDLE;
re = StartThread(nId);
if (CHECK_OK != re)
{
return re;
}
m_bInitSucc = true;
return re;
}
int ImgCheckAnalysisy::GetStatus()
{
return m_nRun_Status;
}
std::string ImgCheckAnalysisy::GetVersion()
{
return std::string("BOE_1.3.1");
}
std::string ImgCheckAnalysisy::GetErrorInfo()
{
std::string str = GetErrorCodeInfo(m_nErrorCode);
printf("%s\n", str.c_str());
return str;
}
int ImgCheckAnalysisy::creatsavedir()
{
std::string curDate = CheckUtil::getCurrentDate();
if (curDate == m_strLastDate)
{
return 0;
}
m_strLastDate = curDate;
m_strRootPath_KK_cls += m_strLastDate + "/";
for (int i = 0; i < AI_CLass_QX_NAME_count; i++)
{
CheckUtil::CreateDir(m_strRootPath_KK_cls + std::to_string(i) + "/");
}
return 0;
}
void ImgCheckAnalysisy::SetTplInfo(const cv::RotatedRect &outerRect,
const cv::RotatedRect &innerRect,
const std::vector<std::vector<cv::Point>> &pinContours)
{
m_tplOuterRect = outerRect;
m_tplInnerRect = innerRect;
m_tplPinContours = pinContours;
}
int ImgCheckAnalysisy::LoadRunConfig(void *p)
{
if (p == NULL)
{
m_nErrorCode = CHECK_ERROR_Config_Null;
return m_nErrorCode;
}
RunInfoST *pconfig = (RunInfoST *)p;
m_RunConfig.copy(*pconfig);
return CHECK_OK;
}
int ImgCheckAnalysisy::LoadCheckConfig(void *p)
{
if (p == NULL)
{
m_nErrorCode = CHECK_ERROR_Config_Null;
return m_nErrorCode;
}
m_pConfig = (ConfigBase *)p;
m_nConfigIdx = m_pConfig->GetConfigIdx();
m_nErrorCode = CHECK_OK;
return m_nErrorCode;
}
// 开启检测
int ImgCheckAnalysisy::StartCheck()
{
m_nRun_Status = CHECK_THREAD_STATUS_READY;
return 0;
}
int ImgCheckAnalysisy::SetIDLE()
{
// 更新参数
SetNewConfig();
m_nRun_Status = CHECK_THREAD_STATUS_IDLE;
return 0;
}
int ImgCheckAnalysisy::StartThread(int nId)
{
// 开启检测线程
ptr_thread_Run = std::make_shared<std::thread>(std::bind(&ImgCheckAnalysisy::Run, this, nId));
if (!m_RunConfig.bRetest)
{
ptr_thread_AI = std::make_shared<std::thread>(std::bind(&ImgCheckAnalysisy::ThreadTask, this, nId + 1));
}
return 0;
}
int ImgCheckAnalysisy::StopThread()
{
m_bExit = true;
if (ptr_thread_Run != nullptr)
{
if (ptr_thread_Run->joinable())
{
ptr_thread_Run->join();
}
}
if (ptr_thread_AI != nullptr)
{
if (ptr_thread_AI->joinable())
{
ptr_thread_AI->join();
}
}
return 0;
}
int ImgCheckAnalysisy::ExitSystem()
{
StopThread();
return 0;
}
int ImgCheckAnalysisy::InitModel()
{
// 获取当前gpu号确定的 AI处理线程
m_OtherDet_Config.nDeviceId = m_RunConfig.nDeviceId;
m_pAI_Edge_Algin.Init(&m_OtherDet_Config);
m_pAI_Edge_Algin.InitModel_ALL();
return 0;
}
cv::Scalar ImgCheckAnalysisy::calc_blob_info_withstats(cv::Mat &img, const cv::Mat &mask, cv::Rect &stats, cv::Size k_size, int expand, double threshold)
{
// 解包 stats(x, y, w, h, area)
int x = stats.x, y = stats.y, w = stats.width, h = stats.height;
// 将图像转换为灰度图像
// 计算感兴趣区域 (ROI)
cv::Rect roi(x - expand, y - expand, w + 2 * expand, h + 2 * expand);
roi &= cv::Rect(0, 0, img.cols, img.rows); // 确保ROI在图像内
cv::Mat cimg = img(roi);
// cv::cvtColor(cimg, cimg, cv::COLOR_BGR2GRAY);
// return cv::Scalar(0, 0, 0);
cv::Mat cmask = mask(roi);
// getchar();
// 扩张掩膜
cv::Scalar mean_bk = cv::mean(cimg, ~cmask);
double fbk = mean_bk[0];
cv::Scalar mean_det = cv::mean(cimg, cmask);
double fdet = mean_det[0];
// 计算差异图像
cv::Mat diff = cv::abs(cimg - fbk);
// cv::imwrite("cimg.png",cimg);
// cv::imwrite("cmask.png",cmask);
// printf("%f %f - %f \n",fbk,fdet,fbk-fdet);
// getchar();
// diff = diff.mul(cmask > 0);
cv::Mat masked_image;
diff.copyTo(masked_image, cmask);
// 计算能量
double energy = cv::sum(masked_image)[0];
// 计算 hj(差异图像大于0的像素均值)
// double hj = std::abs(fbk - fdet);
// double hj = CheckUtil::CalHj(cimg, cmask, mean_bk.val[0]);
double hj = CheckUtil::CalHjWeighted(cimg, cmask, mean_bk.val[0], 2.0f);
int worb = 0;
if (fdet >= fbk)
{
worb = 1;
}
// cv::imwrite("cimg.png", cimg);
// cv::imwrite("cmask.png", cmask);
// cv::imwrite("diff.png", diff);
// cv::imwrite("masked_image.png", masked_image);
// printf("fbk %f fdet %f energy %f\n", fbk, fdet, energy);
// getchar();
return cv::Scalar(worb, energy, hj);
}
// 计算仿射矩阵(不做图像 warp):
// 基准点用当前基准矩形(内定位优先/外定位回退)的三个角点 -> 模板对应基准矩形的三个角点
// m_affine_cur2tpl: 当前图坐标 -> 模板图坐标
// m_affine_tpl2cur: 模板图坐标 -> 当前图坐标
int ImgCheckAnalysisy::CalAffineMats(const RotatedRect &tplRefRect, const RotatedRect &curRefRect)
{
m_affine_cur2tpl.release();
m_affine_tpl2cur.release();
m_bAffineValid = false;
if (tplRefRect.size.width <= 0 || tplRefRect.size.height <= 0 ||
curRefRect.size.width <= 0 || curRefRect.size.height <= 0)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "CalAffineMats: invalid ref rect tpl[%f %f] cur[%f %f]",
tplRefRect.size.width, tplRefRect.size.height,
curRefRect.size.width, curRefRect.size.height);
return 1;
}
vector<cv::Point2f> cur_vertices = CheckUtil::sort_vertices(curRefRect);
vector<cv::Point2f> tpl_vertices = CheckUtil::sort_vertices(tplRefRect);
cv::Point2f cur_pts[3] = {
cur_vertices[0], // 左上
cur_vertices[1], // 右上
cur_vertices[2] // 左下
};
cv::Point2f tpl_pts[3] = {
tpl_vertices[0], // 左上
tpl_vertices[1], // 右上
tpl_vertices[2] // 左下
};
m_affine_cur2tpl = cv::getAffineTransform(cur_pts, tpl_pts);
m_affine_tpl2cur = cv::getAffineTransform(tpl_pts, cur_pts);
m_bAffineValid = (!m_affine_cur2tpl.empty() && !m_affine_tpl2cur.empty());
if (!m_bAffineValid)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "CalAffineMats: getAffineTransform failed");
return 1;
}
// 仅用于日志: 分解当前->模板的旋转/缩放, 便于排查
const double *M = m_affine_cur2tpl.ptr<double>(0);
double fScaleX = std::sqrt(M[0] * M[0] + M[3] * M[3]);
double fScaleY = std::sqrt(M[1] * M[1] + M[4] * M[4]);
double fAngle = std::atan2(M[3], M[0]) * 180.0 / CV_PI;
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "CalAffineMats",
"scaleX %.4f scaleY %.4f angle %.2f deg t(%.2f %.2f)",
fScaleX, fScaleY, fAngle, M[2], M[5]);
return 0;
}
// 把模板 Pin 轮廓反变换到当前检测图(detImg)坐标系:
// m_tplPinContours 在 pinImg(模板外框裁剪图)坐标系
// -> 加模板外框原点得到模板图坐标
// -> m_affine_tpl2cur 得到当前图坐标
// -> 减 m_outer_roi 原点得到 detImg 坐标
int ImgCheckAnalysisy::TransformTplPinContoursToCur()
{
m_tplPinContoursCur.clear();
if (m_tplPinContours.empty())
{
return 0;
}
if (!m_bAffineValid || m_affine_tpl2cur.empty())
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Error",
"TransformTplPinContoursToCur: affine invalid, template pin contours skipped");
return 1;
}
const cv::Rect tplBound = m_tplOuterRect.boundingRect();
const double *M = m_affine_tpl2cur.ptr<double>(0);
m_tplPinContoursCur.reserve(m_tplPinContours.size());
for (const auto &contour : m_tplPinContours)
{
std::vector<cv::Point> mapped;
mapped.reserve(contour.size());
for (const auto &pt : contour)
{
// pinImg 坐标 -> 模板图坐标
const double tplX = static_cast<double>(pt.x) + tplBound.x;
const double tplY = static_cast<double>(pt.y) + tplBound.y;
// 模板图坐标 -> 当前图坐标
const double curX = M[0] * tplX + M[1] * tplY + M[2];
const double curY = M[3] * tplX + M[4] * tplY + M[5];
// 当前图坐标 -> detImg 坐标
mapped.emplace_back(cvRound(curX - m_outer_roi.x), cvRound(curY - m_outer_roi.y));
}
m_tplPinContoursCur.push_back(std::move(mapped));
}
return 0;
}
int ImgCheckAnalysisy::CheckRun()
{
// printf(">>>%s ================start \n", m_pImageAllResult->strBaseInfo.c_str());
long t1, t2, t3, t4, t5, t6, t7;
SetNewConfig();
t1 = CheckUtil::getcurTime();
CheckImgInit();
if (DetImgInfo_shareP->bsaveProcessImg)
{
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, "Version", "%s", GetVersion().c_str());
m_pdetlog->AddCheckstr(PrintLevel_0, "Updateconfig", "%d", m_bupdateconfig);
m_pdetlog->AddCheckstr(PrintLevel_0, "Start", "%s", m_pImageAllResult->strBaseInfo.c_str());
m_pdetlog->AddCheckstr(PrintLevel_0, "ImgageScale", "Scale_X = %f Scale_Y = %f", m_fImgage_Scale_X, m_fImgage_Scale_Y);
m_bupdateconfig = false;
// 返回结果状态初始化
m_CheckResult_shareP->checkStatus = 1;
m_CheckResult_shareP->nresult = -1;
m_CheckResult_shareP->basicResult.img_id = m_CheckResult_shareP->in_shareImage->img_id;
m_CheckResult_shareP->basicResult.imgtype = m_CheckResult_shareP->in_shareImage->imgtype;
m_CheckResult_shareP->basicResult.imgstr = m_CheckResult_shareP->in_shareImage->imgstr;
m_CheckResult_shareP->basicResult.strChannel = m_CheckResult_shareP->in_shareImage->strChannel;
m_strCurDetChannel = m_CheckResult_shareP->basicResult.strChannel;
/*参数检查*/
int rec = ConfigCheck(DetImgInfo_shareP->img);
if (rec != CHECK_OK)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "ConfigCheck is error type = %d", rec);
m_nErrorCode = rec;
m_nCheckResultErrorCode = m_nErrorCode;
return m_nErrorCode;
}
long time_edge_s = CheckUtil::getcurTime();
/*AI 边缘定位(内外边缘)*/
// 模型定位内外边缘
int reedge = AI_Edge(m_CheckResult_shareP->in_shareImage->img, m_outer_rroi, m_inner_rroi);
if (reedge != 0)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "AI_Edge is error type = %d", reedge);
m_nErrorCode = reedge;
m_nCheckResultErrorCode = m_nErrorCode;
return m_nErrorCode;
}
long time_edge_e = CheckUtil::getcurTime();
m_pdetlog->AddCheckstr(PrintLevel_0, "2、pre detect", "-------------------------AI_Edge------------succ---time %ld----\n", time_edge_e - time_edge_s);
// 计算内外边缘夹角
float aouter = CheckUtil::getLongAxisAngle(m_outer_rroi);
float ainner = CheckUtil::getLongAxisAngle(m_inner_rroi);
// 计算内边缘宽高与标准差值
float dinnerLen = std::abs(m_inner_rroi.size.width - m_pbaseCheckFunction->ALign_Config.len) * m_fImgage_Scale_X;
float dinnerWidth = std::abs(m_inner_rroi.size.height - m_pbaseCheckFunction->ALign_Config.width) * m_fImgage_Scale_Y;
bool bSizeerr = (dinnerLen > m_pbaseCheckFunction->ALign_Config.len_diff || dinnerWidth > m_pbaseCheckFunction->ALign_Config.width_diff);
float diff_align = std::fabs(aouter - ainner);
diff_align = std::fmod(diff_align, 180.0f); // 模 180 度
// 两条直线的夹角取锐角:min(diff, 180 - diff)
if (diff_align > 90.0f)
{
diff_align = 180.0f - diff_align;
}
// 1. 使用 ALign_Config.angle_diff 卡控内外边缘夹角(未配置时默认 10 度)
float angleTh = 10.0f;
if (m_pbaseCheckFunction != NULL && m_pbaseCheckFunction->ALign_Config.angle_diff > 0.0f)
{
angleTh = m_pbaseCheckFunction->ALign_Config.angle_diff;
}
// 2. 使用 ALign_Config.x_offset / y_offset (mm) 卡控内外边缘中心偏移(mm 转像素)
float offX_mm = 0.0f;
float offY_mm = 0.0f;
if (m_pbaseCheckFunction != NULL)
{
if (m_fImgage_Scale_X > 0.0f)
{
offX_mm = m_pbaseCheckFunction->ALign_Config.x_offset;
}
if (m_fImgage_Scale_Y > 0.0f)
{
offY_mm = m_pbaseCheckFunction->ALign_Config.y_offset;
}
}
// 3. 内外边缘中心坐标偏移(像素)
float dx_mm = std::fabs(m_outer_rroi.center.x - m_inner_rroi.center.x) * m_fImgage_Scale_X;
float dy_mm = std::fabs(m_outer_rroi.center.y - m_inner_rroi.center.y) * m_fImgage_Scale_Y;
bool bAngleErr = diff_align > angleTh;
bool bOffsetXErr = (offX_mm > 0.0f) && (dx_mm > offX_mm);
bool bOffsetYErr = (offY_mm > 0.0f) && (dy_mm > offY_mm);
bool bOffsetErr = bOffsetXErr || bOffsetYErr;
// 角度超差或中心偏移超差 -> 添加到缺陷列表
if (bAngleErr || bOffsetErr || bSizeerr)
{
QX_ERROR_INFO_ alignErr;
alignErr.Idx = static_cast<int>(m_pDetResult->pQx_ErrorList->size());
// roi 用整张检测图区域(detImg 坐标系); detImg 现在取当前外定位包围框, 尺寸与之一致
cv::Rect curBound = m_outer_rroi.boundingRect();
alignErr.roi = cv::Rect(0, 0, curBound.width, curBound.height);
alignErr.area = alignErr.roi.width * alignErr.roi.height;
alignErr.JudgArea = alignErr.area * m_fImgage_Scale_X * m_fImgage_Scale_Y;
alignErr.flen = curBound.width * m_fImgage_Scale_X;
alignErr.fbreadth = curBound.height * m_fImgage_Scale_Y;
// 缺陷列表中原 other(其他)已移除;内外边缘对齐超差暂按 C型卡口异常 上报(待确认)
alignErr.nconfig_qx_type = CONFIG_QX_NAME_HunLiao;
alignErr.qx_name = CONFIG_QX_NAME_Names[CONFIG_QX_NAME_HunLiao];
alignErr.grayDis = diff_align; // 记录内外边缘夹角(度)
alignErr.detRegionidxList.push_back(0);
alignErr.detlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Align Check",
"outer-inner angle diff %.2f deg", diff_align);
m_pDetResult->pQx_ErrorList->push_back(alignErr);
if (bAngleErr)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Align Check", "angle diff %.2f deg > %.2f", diff_align, angleTh);
}
if (bOffsetXErr)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Align Check", "center offset dx %.1f mm > %.1f mm",
dx_mm, offX_mm);
}
if (bOffsetYErr)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Align Check", "center offset dy %.1f mm > %.1f mm",
dy_mm, offY_mm);
}
if (bSizeerr)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Align Check", "inner size diff len %.1f mm, width %.1f mm > threshold",
dinnerLen, dinnerWidth);
}
}
/*投影对齐模板*/
// Pin 检测方案由参数 Det_Method 运行时选择(替代原 PIN_USE_DIRECT_MATCH 编译宏)
if (IsDirectPinMethod())
{
// 新方案(Det_Method == "line_fitting"): 不做仿射变换, 检测图直接按当前外定位结果裁剪;
// Pin分析在 Pin_Qx_Det -> Pin_Direct_Check 中完成(只分析检测Pin, 不做模板对比)
m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", "Align mode: Direct Pin Check (no affine)");
{
cv::Rect curOuterBound = m_outer_rroi.boundingRect();
cv::Rect curImgBound(0, 0, m_CheckResult_shareP->in_shareImage->img.cols,
m_CheckResult_shareP->in_shareImage->img.rows);
curOuterBound &= curImgBound;
if (curOuterBound.width <= 0 || curOuterBound.height <= 0)
{
cv::Rect rawBound = m_outer_rroi.boundingRect();
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "Direct Pin Match: current outer roi invalid [%d %d %d %d]",
rawBound.x, rawBound.y, rawBound.width, rawBound.height);
m_nErrorCode = CHECK_ERROR_Config_cutRoi;
m_nCheckResultErrorCode = m_nErrorCode;
return m_nErrorCode;
}
m_outer_roi = curOuterBound;
}
}
else if (IsPinMatrixMethod())
{
// 方案三(Det_Method == "Pin_Matrix"): 投影矩阵在 Pin_Qx_Det 中由当前帧 Pin 拟合(上下两排分别计算);
// 此处只需要用当前外定位包围框作为检测图裁剪区域(原图不 warp), 不依赖内/外定位仿射
m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect",
"Align mode: Pin Matrix (projection matrix fitted by pins, upper/lower rows separately, image NOT warped)");
{
cv::Rect curOuterBound = m_outer_rroi.boundingRect();
cv::Rect curImgBound(0, 0, m_CheckResult_shareP->in_shareImage->img.cols,
m_CheckResult_shareP->in_shareImage->img.rows);
curOuterBound &= curImgBound;
if (curOuterBound.width <= 0 || curOuterBound.height <= 0)
{
cv::Rect rawBound = m_outer_rroi.boundingRect();
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "Pin Matrix: current outer roi invalid [%d %d %d %d]",
rawBound.x, rawBound.y, rawBound.width, rawBound.height);
m_nErrorCode = CHECK_ERROR_Config_cutRoi;
m_nCheckResultErrorCode = m_nErrorCode;
return m_nErrorCode;
}
m_outer_roi = curOuterBound;
}
}
else
{
// 旧方案(Det_Method == "BTB_Marix" 或未配置): 不对原图做 warpAffine, 只计算矩阵, 并把模板 Pin 轮廓反变换到当前图坐标系。
bool bUseInnerAlign = (m_tplInnerRect.size.width > 0 && m_tplInnerRect.size.height > 0 &&
m_inner_rroi.size.width > 0 && m_inner_rroi.size.height > 0);
if (bUseInnerAlign)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect",
"Affine align basis: Inner(inner_rroi -> tplInnerRect), template pins warp only, image NOT warped");
CalAffineMats(m_tplInnerRect, m_inner_rroi);
}
else
{
m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect",
"Affine align basis: Outer(outer_rroi -> tplOuterRect), template pins warp only, image NOT warped");
CalAffineMats(m_tplOuterRect, m_outer_rroi);
}
// 后续所有检测/裁剪都在当前图坐标系: 裁剪框取当前外定位的包围框(原图不 warp)
{
cv::Rect curOuterBound = m_outer_rroi.boundingRect();
cv::Rect curImgBound(0, 0, m_CheckResult_shareP->in_shareImage->img.cols,
m_CheckResult_shareP->in_shareImage->img.rows);
m_outer_roi = curOuterBound & curImgBound;
if (m_outer_roi.width <= 0 || m_outer_roi.height <= 0)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "Affine mode: current outer roi invalid [%d %d %d %d]",
curOuterBound.x, curOuterBound.y, curOuterBound.width, curOuterBound.height);
m_nErrorCode = CHECK_ERROR_Config_cutRoi;
m_nCheckResultErrorCode = m_nErrorCode;
return m_nErrorCode;
}
}
// 模板 Pin(pinImg 坐标) -> 模板图坐标 -> 当前图坐标 -> detImg 坐标
TransformTplPinContoursToCur();
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "2.1、pre detect",
"tpl pin contours -> current frame: %ld (affineValid %d)",
static_cast<long>(m_tplPinContoursCur.size()), static_cast<int>(m_bAffineValid));
}
m_Crop_Roi_paramImg = m_outer_roi;
m_pImageAllResult->pDetResult->CutRoi = m_outer_roi;
m_pImageAllResult->pDetResult->Param_CropRoi = m_Crop_Roi_paramImg;
long time_calan_e = CheckUtil::getcurTime();
m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", "-------------------------CalAndAffine------------succ---time %ld----\n", time_calan_e - time_edge_s);
/*生成 检测的图片(检测用图固定为单通道灰度;彩色原图按 BGR 顺序转灰度,与 imread(path,0) 的亮度一致)*/
cv::Mat image;
image = m_CheckResult_shareP->in_shareImage->img;
if (image.channels() == 3)
{
cv::cvtColor(image(m_outer_roi), m_pImageAllResult->detImg, cv::COLOR_BGR2GRAY);
}
else if (image.channels() == 4)
{
cv::cvtColor(image(m_outer_roi), m_pImageAllResult->detImg, cv::COLOR_BGRA2GRAY);
}
else
{
m_pImageAllResult->detImg = image(m_outer_roi).clone();
}
/*多线程开启 AI 推理检测*/
long time_AI_s = CheckUtil::getcurTime();
m_AItask = std::make_shared<TaskInfo>();
m_AItask->taskname = Task_AI;
m_task.sendTask(m_AItask);
ImgPreDet();
/*更新检测区域*/
// Update_DetRoiList();
m_pdetlog->AddCheckstr(PrintLevel_0, "3、pre detect", "-------------------------pre Det--------------- \n");
/*实现PIN脚检测*/
{
long t41 = CheckUtil::getcurTime();
int reedge1111 = Pin_Qx_Det(m_pImageAllResult->detImg);
long t42 = CheckUtil::getcurTime();
m_pdetlog->AddCheckstr(PrintLevel_0, "4、detect", "-------------------------1、Pin_Qx_Det--------%ld-------\n", t42 - t41);
}
m_CheckResult_shareP->nresult = 0;
/*把临时可以绘制的结果都绘制出来。*/
DrawResult_Step_1();
long time_AI_e;
/*AI 推理生成*/
{
long t211 = CheckUtil::getcurTime();
// 对AI的结果进行 处理,等待 AI 推理全部完成。
rec = AIMaskDet();
if (rec != CHECK_OK)
{
m_nErrorCode = rec;
m_nCheckResultErrorCode = m_nErrorCode;
return m_nErrorCode;
}
time_AI_e = CheckUtil::getcurTime();
m_pdetlog->AddCheckstr(PrintLevel_0, "4、detect", "-------------------------2、AI Detect--------%ld wait AI complate %ld-------\n", time_AI_e - time_AI_s, time_AI_e - t211);
m_CheckResult_shareP->resultMaskImg = m_pImageAllResult->AIMaskImg;
}
{
t4 = CheckUtil::getcurTime();
rec = GetCheckResultBLob();
if (rec != CHECK_OK)
{
m_nErrorCode = rec;
m_nCheckResultErrorCode = m_nErrorCode;
return m_nErrorCode;
}
t5 = CheckUtil::getcurTime();
m_pdetlog->AddCheckstr(PrintLevel_0, "5、BLob ", "------------------------ --------%ld -------\n", t5 - t4);
}
// m_pdetlog->printLog(m_strCurDetChannel);
long te = CheckUtil::getcurTime();
m_pdetlog->bPrintStr = true;
m_pdetlog->AddCheckstr(PrintLevel_1, "result", " ALL use Time %ld edge %ld AI %ld blob %ld",
te - t1, time_edge_e - time_edge_s, time_AI_e - time_AI_s, t5 - t4);
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, "End", " Check Run");
return 0;
}
int ImgCheckAnalysisy::SetNewConfig()
{
if (m_nConfigIdx < 0)
{
return 1;
/* code */
}
if (m_pConfig->GetConfigUpdataStatus(ConfigType_Analysisy_Common_XL, m_nConfigIdx))
{
m_bupdateconfig = true;
// printf("************** ImgCheckAnalysisy::SetNewConfig m_nConfigIdx %d\n", m_nConfigIdx);
m_old_productROI = cv::Rect(0, 0, 0, 0);
m_pConfig->GetConfig(ConfigType_Analysisy_Common_XL, &m_AnalysisyConfig);
if (m_AnalysisyConfig.commonCheckConfig.nodeConfigArr.size() > 0)
{
m_pCommonAnalysisyConfig = &m_AnalysisyConfig.commonCheckConfig.nodeConfigArr.at(0);
m_pBasicConfig = &m_AnalysisyConfig.commonCheckConfig.baseConfig;
m_pRegionAnalysisyParam = &m_pCommonAnalysisyConfig->regionConfigArr.at(0);
UpdateImgageScale();
if (false)
{
printf("SetNewConfig m_nConfigIdx %d m_CheckConfig.strSkuName %s \n", m_nConfigIdx, m_AnalysisyConfig.strSkuName.c_str());
}
}
else
{
printf("m_AnalysisyConfig.commonCheckConfig.nodeConfigArr == 0 \n");
}
}
else
{
// printf("ConfigType_Analysisy_Common_XL no Update \n");
}
m_nErrorCode = CHECK_OK;
return CHECK_OK;
}
ChannelCheckFunction *ImgCheckAnalysisy::GetChannelFuntion(std::string strChannelName)
{
ChannelCheckFunction *p = NULL;
for (int i = 0; i < m_AnalysisyConfig.checkFunction.channelFunctionArr.size(); i++)
{
if (CheckUtil::compareIgnoreCase(m_AnalysisyConfig.checkFunction.channelFunctionArr[i].strChannelName, strChannelName))
{
p = &m_AnalysisyConfig.checkFunction.channelFunctionArr[i];
}
}
return p;
}
int ImgCheckAnalysisy::Run(int nId)
{
std::vector<int> vi;
vi.push_back(nId);
auto nRet = set_cpu_id(vi);
// printf("Check So %d bind cpu ret %d, %d\n", m_nThreadIdx, nRet, nId);
while (!m_bExit)
{
// 数据准备完成,开启检测
if (m_nRun_Status == CHECK_THREAD_STATUS_READY)
{
m_nRun_Status = CHECK_THREAD_STATUS_BUSY;
/* 检测 */
CheckRun();
m_nRun_Status = CHECK_THREAD_STATUS_COMPLETE;
m_pImageAllResult->setStep(ImageAllResult::DetStep_Complet);
m_nRun_Status = CHECK_THREAD_STATUS_IDLE;
// printf("*--------%d\n", m_nRun_Status);
}
else
{
usleep(1000);
}
// printf("*-");
usleep(1000);
}
return 0;
}
int ImgCheckAnalysisy::set_cpu_id(const std::vector<int> &cpu_set_vec)
{
// for cpu affinity
int nRet = 0;
#ifdef __linux
cpu_set_t _cur_cpu_set;
CPU_ZERO(&_cur_cpu_set);
for (auto _id : cpu_set_vec)
{
CPU_SET(_id, &_cur_cpu_set);
}
if (0 > pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &_cur_cpu_set))
{
perror("set cpu affinity failed: ");
printf("Warning: set cpu affinity failed ... ...\n");
nRet = -1;
}
#endif //__linux
return nRet;
}
int ImgCheckAnalysisy::CalBlob_Other()
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "CalBlob_Other", " Start");
float fs_x = m_fImgage_Scale_X;
float fs_y = m_fImgage_Scale_Y;
float fs_resize_x = m_pImageAllResult->fscale_detToresult_x;
float fs_resize_y = m_pImageAllResult->fscale_detToresult_y;
// 遍历每个检测blob
for (int i = 0; i < blobs.blobCount; i++)
{
// m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Blob", "%d/%d start", i, blobs.blobCount);
cv::Rect roi;
roi.x = blobs.blobTab[i].minx;
roi.y = blobs.blobTab[i].miny;
roi.width = blobs.blobTab[i].maxx - blobs.blobTab[i].minx + 1;
roi.height = blobs.blobTab[i].maxy - blobs.blobTab[i].miny + 1;
cv::Scalar result = calc_blob_info_withstats(m_pImageAllResult->detImg, m_pImageAllResult->AIMaskImg, roi);
int QX_whiteBLACK = CONFIG_QX_BLACK;
if (result[0] > 0)
{
QX_whiteBLACK = CONFIG_QX_WHITE;
}
double hj = result[2];
double energe = result[1];
blobs.blobTab[i].energy = energe;
blobs.blobTab[i].grayDis = hj;
float JudgArea = blobs.blobTab[i].area * fs_x * fs_y;
blobs.blobTab[i].JudgArea = JudgArea;
float flen = roi.width * fs_x;
float fwid = roi.height * fs_x;
if (roi.height * fs_y > flen)
{
flen = roi.height * fs_y;
fwid = roi.width * fs_y;
}
blobs.blobTab[i].len = flen;
blobs.blobTab[i].breadth = fwid;
int nerrortype = 0;
int checkFlage = 0;
float fmaxScore = 0;
int config_qx_type = 0;
if (blobs.blobTab[i].ErrType == ERR_TYPE_2)
{
QX_whiteBLACK = CONFIG_QX_BLACK; // 强制为 黑色
}
blobs.blobTab[i].whiteOrblack = QX_whiteBLACK;
// 精确计算长度
vector<float> re_len = Cal_QXLen(m_pImageAllResult->detImg(roi), config_qx_type, fs_x, fs_y);
if (re_len.size() > 2)
{
if(re_len[0] >= 0){
flen = re_len[0];
blobs.blobTab[i].len = flen;
}
if(re_len[1] >= 0){
fwid = re_len[1];
blobs.blobTab[i].breadth = fwid;
}
}
}
return 0;
}
int ImgCheckAnalysisy::GetClassImg(const cv::Mat &img, cv::Mat &AIdetImg, cv::Rect qx_roi, int detwidth, int detheight)
{
cv::Rect cutroi;
cv::Rect roi = qx_roi;
bool bresize = false;
{
int pc_x = roi.x + roi.width * 0.5;
int pc_y = roi.y + roi.height * 0.5;
if (roi.width < detwidth && roi.height < detheight)
{
cutroi.width = detwidth;
cutroi.x = pc_x - detwidth * 0.5;
cutroi.height = detheight;
cutroi.y = pc_y - detheight * 0.5;
}
else
{
// 宽 高
if (roi.width > roi.height)
{
cutroi.width = roi.width + 20;
cutroi.x = roi.x - 10;
float fsx = detheight * 1.0f / detwidth;
cutroi.height = cutroi.width * fsx;
cutroi.y = pc_y - cutroi.height * 0.5;
}
else
{
cutroi.height = roi.height + 20;
cutroi.y = roi.y - 10;
float fsy = detwidth * 1.0f / detheight;
cutroi.width = cutroi.height * fsy;
cutroi.x = pc_x - cutroi.width * 0.5;
}
bresize = true;
}
if (cutroi.x < 0)
{
cutroi.x = 0;
}
if (cutroi.y < 0)
{
cutroi.y = 0;
}
if (cutroi.x + cutroi.width >= img.cols)
{
cutroi.x = img.cols - cutroi.width;
if (cutroi.x < 0)
{
cutroi.x = 0;
if (cutroi.x + cutroi.width >= img.cols)
{
cutroi.width = img.cols;
}
}
}
if (cutroi.y + cutroi.height >= img.rows)
{
cutroi.y = img.rows - cutroi.height;
if (cutroi.y < 0)
{
cutroi.y = 0;
if (cutroi.y + cutroi.height >= img.rows)
{
cutroi.height = img.rows;
}
}
}
}
if (!CheckUtil::RoiInImg(cutroi, img))
{
return 1;
}
cv::Size sz;
sz.width = detwidth;
sz.height = detheight;
if (cutroi.width != sz.width || cutroi.height != sz.height)
{
cv::resize(img(cutroi), AIdetImg, sz);
}
else
{
AIdetImg = img(cutroi).clone();
}
if (1 != AIdetImg.channels())
{
cv::cvtColor(AIdetImg, AIdetImg, cv::COLOR_BGR2GRAY);
}
return 0;
}
vector<float> ImgCheckAnalysisy::Cal_QXLen(cv::Mat qx_maskImg, int qx_type, float fsc_x, float fsc_y)
{
vector<float> resLen(3);
float nlen = -1;
cv::Mat detimg = qx_maskImg;
resLen[0] = nlen;
// 寻找轮廓
vector<vector<Point>> contours;
cv::findContours(detimg, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
// 找到最大面积的轮廓
double maxArea = -1;
int maxAreaIdx = -1;
for (size_t i = 0; i < contours.size(); ++i)
{
double area = contourArea(contours[i]);
if (area > maxArea)
{
maxArea = area;
maxAreaIdx = i;
}
}
// 如果找到了最大面积的轮廓
if (maxAreaIdx >= 0)
{
// 使用minAreaRect找到最小外接矩形
cv::RotatedRect rect = cv::minAreaRect(contours[maxAreaIdx]);
// // 绘制最小外接矩形
// Point2f vertices[4];
// rect.points(vertices);
// for (int i = 0; i < 4; ++i)
// {
// line(detimg, vertices[i], vertices[(i + 1) % 4], Scalar(128, 255, 0), 2); // 绿色线
// }
// 输出结果
// 获取最小外接矩形的尺寸
float width = rect.size.width;
float height = rect.size.height;
// std::cout << "1 Width: " << width << ", Height: " << height << std::endl;
Point2f vertices[4];
rect.points(vertices);
vector<Point2f> newcont;
for (int i = 0; i < 4; ++i)
{
Point2f p;
p.x = vertices[i].x * fsc_x;
p.y = vertices[i].y * fsc_y;
newcont.push_back(p);
}
vector<vector<Point2f>> contours_New;
contours_New.push_back(newcont);
// Recreate the rotated rectangle with scaled vertices
RotatedRect scaledRect = minAreaRect(contours_New[0]);
// Calculate scaled width and height
width = scaledRect.size.width;
height = scaledRect.size.height;
// std::cout << "2 width: " << width << ", height: " << height << std::endl;
// float scale = 0.5; // 缩放比例
// rect.size.width *= fsc_x;
// rect.size.height *= fsc_y;
// std::cout << "fsc_x: " << fsc_x << ", fsc_y: " << fsc_y << std::endl;
// std::cout << "Width: " << width << ", Height: " << height << std::endl;
// 获取最小外接矩形的尺寸
// width = rect.size.width;
// height = rect.size.height;
if (width > height)
{
nlen = width;
resLen[0] = width;
resLen[1] = height;
/* code */
}
else
{
nlen = height;
resLen[0] = height;
resLen[1] = width;
}
}
// getchar();
return resLen;
}
int ImgCheckAnalysisy::GetCheckResultBLob()
{
long t1, t2, t3, t4, t5, t6, t7;
t1 = CheckUtil::getcurTime();
int re = GetALLBlob();
if (re != 0)
{
return re;
}
// 缺陷分类 多线程 实现。
long t11 = CheckUtil::getcurTime();
m_Classtask = std::make_shared<TaskInfo>();
m_Classtask->taskname = Task_Class;
m_task.sendTask(m_Classtask);
re = CalBlob_Other();
if (re != 0)
{
return re;
}
long t12 = CheckUtil::getcurTime();
// 等待分类 完成。
m_Classtask->waitComplate();
long t13 = CheckUtil::getcurTime();
// printf("=========== cls time %ld waite time %ld\n", t13 - t11, t13 - t12);
BLobToDetResult();
return 0;
}
void worker(const unsigned char *img, int w, int startRow, int endRow, unsigned char *rowFlags)
{
for (int y = startRow; y < endRow; y++)
{
const unsigned char *p = img + (size_t)y * w;
rowFlags[y] = 0;
for (int x = 0; x < w; x++)
{
if (p[x] != 0)
{
rowFlags[y] = 1;
break;
}
}
}
}
int ImgCheckAnalysisy::GetALLBlob()
{
std::string strBaseLog = "Blob";
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "image Blob Analysis Start");
if (m_pImageAllResult->AIMaskImg.empty())
{
return 1;
}
unsigned char *pGrayErrordata = (unsigned char *)m_pImageAllResult->AIMaskImg.data;
int width = m_pImageAllResult->AIMaskImg.cols;
int height = m_pImageAllResult->AIMaskImg.rows;
long t1 = CheckUtil::getcurTime();
memset(&blobs, 0x00, sizeof(ERROR_DOTS_BLOBS));
ERROR_DOTS_BLOBS blobs_v1;
memset(&blobs_v1, 0x00, sizeof(ERROR_DOTS_BLOBS));
ERROR_DOTS_BLOBS blobs_big;
memset(&blobs_big, 0x00, sizeof(ERROR_DOTS_BLOBS));
ERROR_DOTS_BLOBS blobs_big_2;
memset(&blobs_big_2, 0x00, sizeof(ERROR_DOTS_BLOBS));
printf("=====>>>> GetALLBlob m_strCurDetChannel %s \n", m_strCurDetChannel.c_str());
if (m_strCurDetChannel == "CA")
{
printf("=====>>>>GetALLBlob USE CA %s \n", m_strCurDetChannel.c_str());
GetBlobs_ALL_New(&blobs_v1, pGrayErrordata, m_ImgBlobHFlagData, width, height, 15);
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "blob 1 num %d", blobs_v1.blobCount);
if (blobs_v1.srcBlobCount >= _MAX_ERROR_DOT_BLOB)
{
GetBlobs_ALL_New(&blobs_big, pGrayErrordata, m_ImgBlobHFlagData, width, height, 200);
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "blob 2 num %d", blobs_big.blobCount);
if (blobs_big.srcBlobCount >= _MAX_ERROR_DOT_BLOB)
{
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "blob 3 num %d", blobs_big_2.blobCount);
GetBlobs_ALL_New(&blobs_big_2, pGrayErrordata, m_ImgBlobHFlagData, width, height, 500);
}
}
}
else
{
GetBlobs_oneLabe(&blobs_v1, pGrayErrordata, m_ImgBlobHFlagData, width, height, 15);
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "blob 1 num %d", blobs_v1.blobCount);
if (blobs_v1.srcBlobCount >= _MAX_ERROR_DOT_BLOB)
{
GetBlobs_oneLabe(&blobs_big, pGrayErrordata, m_ImgBlobHFlagData, width, height, 200);
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "blob 2 num %d", blobs_big.blobCount);
if (blobs_big.srcBlobCount >= _MAX_ERROR_DOT_BLOB)
{
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "blob 3 num %d", blobs_big_2.blobCount);
GetBlobs_oneLabe(&blobs_big_2, pGrayErrordata, m_ImgBlobHFlagData, width, height, 500);
}
}
}
long t2 = CheckUtil::getcurTime();
PushBlob(&blobs, &blobs_big_2);
PushBlob(&blobs, &blobs_big);
PushBlob(&blobs, &blobs_v1);
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "blob ALL num %d", blobs.blobCount);
// long t2 = CheckUtil::getcurTime();
// printf(" BLob time %ld \n", t2 - t1);
if (false || DetImgInfo_shareP->bsaveProcessImg)
{
int font_face = cv::FONT_HERSHEY_SIMPLEX;
double font_scale = 0.5;
int thickness = 1;
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strBaseLog, "Save Tem Img");
cv::Mat tm = m_pImageAllResult->AIMaskImg.clone(); // 保持 mask 单通道,不再灰度转假彩
for (int i = 0; i < blobs.blobCount; i++)
{
cv::Rect roi;
roi.x = blobs.blobTab[i].minx;
roi.y = blobs.blobTab[i].miny;
roi.width = blobs.blobTab[i].maxx - blobs.blobTab[i].minx + 1;
roi.height = blobs.blobTab[i].maxy - blobs.blobTab[i].miny + 1;
if (blobs.blobTab[i].ErrType == 0)
{
cv::rectangle(tm, roi, CheckUtil::GetDrawColor(tm, cv::Scalar(0, 0, 255)));
}
else
{
cv::rectangle(tm, roi, CheckUtil::GetDrawColor(tm, cv::Scalar(0, 255, 255)));
}
char buffer[128];
sprintf(buffer, "id:%d type:%d m %0.1f", i,blobs.blobTab[i].ErrType, blobs.blobTab[i].density);
std::string text = buffer;
cv::Point origin = cv::Point(roi.x, roi.y);
cv::putText(tm, text, origin, font_face, font_scale, CheckUtil::GetDrawColor(tm, cv::Scalar(0, 255, 0)), thickness, 1, 0);
// printf("type: %d %d %d %d %d %d %d\n", blobs.blobTab[i].ErrType, blobs.blobTab[i].area, blobs.blobTab[i].energy, roi.x, roi.y, roi.width, roi.height);
}
cv::imwrite(m_CheckResult_shareP->in_shareImage->strChannel + "_image_resize_blob.png", tm);
}
long te = CheckUtil::getcurTime();
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "image Blob Analysis End use time %ld blob %ld ms", te - t1, t2 - t1);
// getchar();
return 0;
}
int ImgCheckAnalysisy::AIMaskDet()
{
// 计算 mask 图片的一行 是否有 残点,用以加速 Blob的计算。
if (m_ImgBlobHFlagData)
{
delete[] m_ImgBlobHFlagData;
m_ImgBlobHFlagData = NULL;
}
m_ImgBlobHFlagData = new unsigned char[m_pImageAllResult->detImg.rows];
memset(m_ImgBlobHFlagData, 0, sizeof(unsigned char) * m_pImageAllResult->detImg.rows);
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
std::shared_ptr<AIMulThreadRunBase::AITask> AImaskResult;
{
std::lock_guard<std::mutex> lock(mtx_AIMaskImgBLobQueue);
if (m_AIMaskImgBLobQueue.size() > 0)
{
AImaskResult = std::move(m_AIMaskImgBLobQueue.front());
m_AIMaskImgBLobQueue.pop();
// printf("size ============== %ld\n", m_AIMaskImgBLobQueue.size());
}
}
// 有AI mask 的结果
if (AImaskResult)
{
// static int ss = 0;
cv::Mat &outimg = *(AImaskResult->output);
// cv::imwrite(std::to_string(ss++) + ".png", outimg);
unsigned char *pGrayErrordata = (unsigned char *)outimg.data;
int width = outimg.cols;
int height = outimg.rows;
int start_Y = AImaskResult->roi.y;
if (cv::countNonZero(outimg != 0) != 0)
{
// #pragma omp parallel for
for (int y = 0; y < height; y++)
{
if (m_ImgBlobHFlagData[y + start_Y] == 0)
{
unsigned char *p = pGrayErrordata + y * width;
for (int x = 0; x < width; x++)
{
if (p[x] != 0)
{
m_ImgBlobHFlagData[y + start_Y] = 1;
break;
}
}
}
}
}
}
else
{
/* code */
if (m_AItask->isComplate())
{
break;
}
}
}
// printf("===========================================1 \n");
m_AItask->waitComplate();
int rec = m_AItask->nresult;
if (rec != CHECK_OK)
{
return rec;
}
// printf("===========================================2 \n");
return 0;
}
int ImgCheckAnalysisy::Contours()
{
// std::string strBaseLog = "Contours";
// // m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "Contours Start");
// cv::Mat detimg;
// {
// // 膨胀
// cv::Mat se = getStructuringElement(0, Size(5, 5)); // 构造矩形结构元素
// cv::dilate(m_pdetlog->temImgList[TEM_IMG_IDX_AImask], detimg, se);
// }
// std::vector<std::vector<cv::Point>> contours;
// std::vector<cv::Vec4i> hierarchy;
// cv::findContours(detimg, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE); // 只找最外层轮廓
// // cv::cvtColor(m_pdetlog->temImgList[TEM_IMG_IDX_SrcCrop], m_pdetlog->temImgList[TEM_IMG_IDX_DrawSrc], cv::COLOR_GRAY2BGR);
// for (int i = 0; i < contours.size(); ++i)
// { // 绘制所有轮廓
// cv::drawContours(m_pdetlog->temImgList[TEM_IMG_IDX_DrawSrc], contours, i, cv::Scalar(255, 0, 255)); // thickness为-1时为填充整个轮廓
// }
// float fx = m_pdetlog->temImgList[TEM_IMG_IDX_Result].cols * 1.0f / m_pdetlog->temImgList[TEM_IMG_IDX_SrcCrop].cols;
// float fy = m_pdetlog->temImgList[TEM_IMG_IDX_Result].rows * 1.0f / m_pdetlog->temImgList[TEM_IMG_IDX_SrcCrop].rows;
// for (int i = 0; i < contours.size(); ++i)
// {
// for (int j = 0; j < contours.at(i).size(); ++j)
// {
// contours.at(i).at(j).x *= fx;
// contours.at(i).at(j).y *= fy;
// }
// }
// // cv::cvtColor(m_pdetlog->temImgList[TEM_IMG_IDX_Result], m_pdetlog->temImgList[TEM_IMG_IDX_Result], cv::COLOR_GRAY2BGR);
// for (int i = 0; i < contours.size(); ++i)
// { // 绘制所有轮廓
// cv::drawContours(m_pdetlog->temImgList[TEM_IMG_IDX_Result], contours, i, cv::Scalar(255, 0, 255)); // thickness为-1时为填充整个轮廓
// }
// cv::imwrite("TEM_IMG_IDX_Result.png", m_pdetlog->temImgList[TEM_IMG_IDX_Result]);
// cv::imwrite("deeeee.png", m_pdetlog->temImgList[TEM_IMG_IDX_Drawmask]);
// getchar();
// m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "Contours End");
return 0;
}
int ImgCheckAnalysisy::CheckImgInit()
{
// 1、初始化
m_pDetResult->pQx_ErrorList = std::make_shared<std::vector<QX_ERROR_INFO_>>();
m_pdetlog->Init();
m_pdetlog->addLogLevel = DET_LOG_LEVEL_3;
memset(&blobs, 0, sizeof(ERROR_DOTS_BLOBS));
m_nCheckResultErrorCode = 0;
m_Draw_qxImageResult.erase(m_Draw_qxImageResult.begin(), m_Draw_qxImageResult.end());
m_DetRoiList.Init();
return 0;
}
int ImgCheckAnalysisy::ConfigCheck(cv::Mat img)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "start", "Config Check");
int re = CHECK_OK;
if (img.empty())
{
m_nErrorCode = CHECK_ERROR_CheckImg_Empty;
m_pdetlog->AddCheckstr(PrintLevel_1, "Error", "check Img empty");
return m_nErrorCode;
}
// if (m_pCommonAnalysisyConfig->regionConfigArr.size() <= 0)
// {
// m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Error", "regionConfig Num = 0");
// m_nErrorCode = CHECK_ERROR_Config_Value;
// return m_nErrorCode;
// }
m_pdetlog->AddCheckstr(PrintLevel_0, "Succ", "Config Check Succ");
return re;
}
int ImgCheckAnalysisy::AI_Detect_Thread(const cv::Mat &img, cv::Mat &ResultImg)
{
std::string cur_time_s = CheckUtil::getCurTimeHMS();
// 依次使用多个检测模型,各模型输出 mask 取并集作为最终 outmask;
// Defect_QueXi 模型是否参与合并由基础参数 Merge_QueXi 开关控制
std::vector<std::shared_ptr<AIModel_Base>> pAIDetList;
pAIDetList.push_back(AI_Factory->Defect);
pAIDetList.push_back(AI_Factory->Defect_QueXi);
// 与 pAIDetList 一一对应的模型名称,用于区分残点由哪个模型检出
std::vector<std::string> pAIDetNameList;
pAIDetNameList.push_back(AI_DET_MODEL_NAME_Defect);
pAIDetNameList.push_back(AI_DET_MODEL_NAME_QueXi);
// Defect_QueXi 模型是否参与最终结果并集(开关关闭时: 模型照常推理, 但结果不合并到 ResultImg)
bool bMerge_QueXi = (m_pbaseCheckFunction != NULL) && m_pbaseCheckFunction->Merge_QueXi.bOpen;
std::string strBaseLog = "AI_Detect";
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "AI_Detect Start");
long t1, t2, t3;
t1 = CheckUtil::getcurTime();
if (img.empty())
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strBaseLog, " input img empty error");
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "AI_Detect End");
return 1;
}
ResultImg = cv::Mat::zeros(img.size(), CV_8UC1);
// 每个模型单独保存一份全图 mask,便于后续追溯残点由哪个模型检出
m_pImageAllResult->AI_Model_MaskList.clear();
std::vector<cv::Mat> modelMaskList(pAIDetList.size());
for (size_t m = 0; m < pAIDetList.size(); m++)
{
modelMaskList[m] = cv::Mat::zeros(img.size(), CV_8UC1);
}
// 整图检测模式下,所有模型的 ROI 都是整幅检测图
const cv::Rect fullRoi(0, 0, img.cols, img.rows);
t2 = CheckUtil::getcurTime();
// 模型实际输入图(整图 resize 到模型输入大小)缓存一份,供结果记录使用
cv::Mat firstModelInput;
// 依次用多个模型做检测:整图 resize 到模型输入大小 -> 推理 -> 输出 mask resize 回原图大小,
// 所有模型输出的 mask 取并集作为最终 outmask
for (size_t m = 0; m < pAIDetList.size(); m++)
{
std::shared_ptr<AIModel_Base> pAIDet = pAIDetList[m];
std::string strModelName = (m < pAIDetNameList.size()) ? pAIDetNameList[m] : std::string(AI_DET_MODEL_NAME_UnKnown);
// 当前模型的 mask 是否合并进最终结果: Defect 模型始终合并; Defect_QueXi 由 Merge_QueXi 开关控制
bool bMergeModel = (strModelName != AI_DET_MODEL_NAME_QueXi) || bMerge_QueXi;
int deal_image_width = pAIDet->input_0.width;
int deal_image_height = pAIDet->input_0.height;
if (deal_image_width <= 0 || deal_image_height <= 0)
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strBaseLog, " %s model input size error [%d %d]", strModelName.c_str(), deal_image_width, deal_image_height);
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "AI_Detect End");
return 2;
}
// 1、整图 resize 到模型输入大小
cv::Mat detInputImg;
cv::resize(img, detInputImg, cv::Size(deal_image_width, deal_image_height));
// 模型输入要求单通道;原图为彩色时按 BGR 顺序转灰度(与 imread(path,0) 的亮度一致)
if (detInputImg.channels() == 3)
{
cv::cvtColor(detInputImg, detInputImg, cv::COLOR_BGR2GRAY);
}
else if (detInputImg.channels() == 4)
{
cv::cvtColor(detInputImg, detInputImg, cv::COLOR_BGRA2GRAY);
}
if (m == 0)
{
firstModelInput = detInputImg;
}
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strBaseLog, " %s input img [%d*%d] -> model [%d*%d]",
strModelName.c_str(), img.cols, img.rows, deal_image_width, deal_image_height);
// 临时存图:保存模型实际输入图
if (DetImgInfo_shareP->bsaveProcessImg)
{
cv::imwrite(DetImgInfo_shareP->strChannel + "_AI_Det_" + strModelName + "_in.jpg", detInputImg);
}
// 2、整图推理
cv::Mat modelMask;
int re = pAIDet->AIDet(detInputImg, modelMask);
if (re != 0)
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strBaseLog, " %s AIDet error %d", strModelName.c_str(), re);
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "AI_Detect End");
return re;
}
// 3、输出 mask resize 回原图大小(最近邻,保证 mask 像素值不被插值改变)
cv::Mat modelMaskFull;
if (!modelMask.empty())
{
cv::resize(modelMask, modelMaskFull, img.size(), 0, 0, cv::INTER_NEAREST);
if (bMergeModel)
{
// 多个模型的 mask 取并集
cv::bitwise_or(ResultImg, modelMaskFull, ResultImg);
// 当前模型自己的 mask 单独记录一份(只记录参与并集的模型, 保证残点来源追溯与结果一致)
cv::bitwise_or(modelMaskList[m], modelMaskFull, modelMaskList[m]);
}
else
{
// Merge_QueXi 开关关闭: 不合并、不推加速队列、不记录残点来源
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog,
" %s Merge_QueXi off, mask not merged (pixel num %d)",
strModelName.c_str(), cv::countNonZero(modelMaskFull));
}
}
// AI 检测残点图存图:每个模型的结果都存,并增加一级 模型名称 目录,
// 用以区分该残点是由哪个模型检出的
// 目录结构:/home/aidlux/BOE/ZB_SOCKET/AI_Detect/<模型名称>/<产品ID>/
if (m_pbaseCheckFunction->saveImg.bSaveAIDetImg && !modelMaskFull.empty())
{
std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + DetImgInfo_shareP->strImgProductID + "/";
CheckUtil::CreateDir(strSaveDir);
std::string strSavePath_out = strSaveDir + cur_time_s + "_" + m_strCurDetChannel + "_" + strModelName + "_out.png";
CheckUtil::SaveImgAsync(strSavePath_out, modelMaskFull);
}
// 4、mask 结果放入队列,用于 AIMaskDet 加速 blob 计算(整图 ROI)
// 只推入参与并集的 mask, 保证行标志与最终结果 mask 一致
if (bMergeModel && !modelMaskFull.empty())
{
std::shared_ptr<AIMulThreadRunBase::AITask> maskTask = std::make_shared<AIMulThreadRunBase::AITask>();
maskTask->id = static_cast<int>(m);
maskTask->roi = fullRoi;
maskTask->output = std::make_shared<cv::Mat>(modelMaskFull);
std::lock_guard<std::mutex> lock(mtx_AIMaskImgBLobQueue);
m_AIMaskImgBLobQueue.push(maskTask);
}
}
t3 = CheckUtil::getcurTime();
float mean_AI = (t3 - t2) / (float)pAIDetList.size();
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strBaseLog, " AI Run Time: sum %ld pre %ld Run %ld mean One Full Img %f", t3 - t1, t2 - t1, t3 - t2, mean_AI);
// 保存各模型的 全图 mask,并输出各模型检出的残点像素数量,便于追溯
for (size_t m = 0; m < modelMaskList.size(); m++)
{
std::shared_ptr<ImageAllResult::AI_Model_MaskImg> temModelMask = std::make_shared<ImageAllResult::AI_Model_MaskImg>();
temModelMask->strModelName = (m < pAIDetNameList.size()) ? pAIDetNameList[m] : std::string(AI_DET_MODEL_NAME_UnKnown);
temModelMask->ModelMask = modelMaskList[m];
m_pImageAllResult->AI_Model_MaskList.push_back(temModelMask);
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, " Det Model: %s mask pixel num %d",
temModelMask->strModelName.c_str(), cv::countNonZero(temModelMask->ModelMask));
}
// 整图检测模式下所有模型共用同一个 ROI(整幅检测图),多个模型的 mask 已取并集,
// 按整图 ROI 记录一条 AI 检测结果,供 GetAIDetImg 按缺陷中心点取到 AI 输入图与并集 mask
bool bFind = false;
for (auto &item : m_pImageAllResult->AI_Qx_MaskList)
{
if (item->roi == fullRoi)
{
cv::bitwise_or(item->AI_mask, ResultImg, item->AI_mask);
bFind = true;
break;
}
}
if (!bFind)
{
std::shared_ptr<ImageAllResult::AI_Det_MaskImg> temAIresult = std::make_shared<ImageAllResult::AI_Det_MaskImg>();
temAIresult->roi = fullRoi;
temAIresult->AI_inImg = firstModelInput;
temAIresult->AI_mask = ResultImg.clone();
m_pImageAllResult->AI_Qx_MaskList.push_back(temAIresult);
}
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "AI_Detect End");
if (m_pbaseCheckFunction->saveImg.bSaveAIDetImg )
{
std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + DetImgInfo_shareP->strImgProductID + "/";
CheckUtil::CreateDir(strSaveDir);
std::string strSavePath_in = strSaveDir + cur_time_s + "_" + m_strCurDetChannel + "_in.png";
CheckUtil::SaveImgAsync(strSavePath_in, m_CheckResult_shareP->cutSrcimg);
}
// 临时存图
if (DetImgInfo_shareP->bsaveProcessImg)
{
cv::imwrite(DetImgInfo_shareP->strChannel + "_AI_mask.png", ResultImg);
}
// getchar();
return 0;
}
int ImgCheckAnalysisy::AI_QX_Class_Thread()
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "QX_Class", " Start");
std::shared_ptr<AIModel_Base> pAIDet;
// printf("=====>>>>AI_QX_Class_Thread m_strCurDetChannel %s \n", m_strCurDetChannel.c_str());
{
// printf("=====>>>>AI_QX_Class_Thread USE CA %s \n", m_strCurDetChannel.c_str());
pAIDet = AI_Factory->Class;
}
int deal_image_width = pAIDet->input_0.width;
int deal_image_height = pAIDet->input_0.height;
const int totalTasks = blobs.blobCount;
int submitted = 0;
int completed = 0;
int detblobIdx = 0;
int clsnum = 0;
// std::string str_Root = m_strRootPath + pDetConfig->strProductID + "_" + pDetConfig->strchannel + "_";
long t2 = CheckUtil::getcurTime();
while (completed < totalTasks)
{
// std::this_thread::sleep_for(std::chrono::milliseconds(1));
if (submitted < totalTasks)
{
ERROR_DOTS_BLOB_DATA *pblob = &blobs.blobTab[submitted];
if (pblob->ErrType == ERR_TYPE_2)
{
pblob->AIclasstype = CONFIG_QX_NAME_YiWu;
submitted++;
completed++;
continue;
}
else
{
// 如果任务还没提交完,且当前处理任务数 < 2,提交新任务
if (runner->GetProcessingCount() < 10)
{
clsnum++;
cv::Rect roi;
roi.x = pblob->minx;
roi.y = pblob->miny;
roi.width = pblob->maxx - pblob->minx + 1;
roi.height = pblob->maxy - pblob->miny + 1;
std::shared_ptr<AIMulThreadRunBase::AITask> task = std::make_shared<AIMulThreadRunBase::AITask>();
task->id = submitted;
int re = GetClassImg(m_pImageAllResult->detImg, task->input, roi, deal_image_width, deal_image_height);
if (re == 0)
{
task->bclass = true;
task->engine = pAIDet;
runner->SubmitTask(task);
}
else
{
completed++;
}
submitted++;
}
}
}
// 尝试取结果
std::shared_ptr<AIMulThreadRunBase::AITask> result;
if (runner->PopResult(result))
{
int temClass = result->cls_label;
// 分类 label 映射:优先读映射配置文件(KK_cls.txt),读不到用默认顺序
int cls_num = GetClsTypeByLabel(temClass);
std::string strclassName = AI_CLass_QX_NAME_Names[cls_num];
m_pdetlog->AddCheckstr(PrintLevel_1, "AI Class num", "%d = %s %f\n", cls_num, strclassName.c_str(), result->cls_score);
// 分类存图 开启。
if (m_pbaseCheckFunction->saveImg.bSaveClsImg)
{
std::string saveimgpaht = "";
{
saveimgpaht = m_strRootPath_KK_cls + std::to_string(cls_num) + "/" + std::to_string(CheckUtil::getcurTime()) + "_" + std::to_string(result->cls_score) + ".png";
}
{
m_pImageStorage->addImage(saveimgpaht, result->input);
}
}
int configqx = AIClassTypeToConfigType(cls_num);
ERROR_DOTS_BLOB_DATA *pblob = &blobs.blobTab[result->id];
pblob->AIclasstype = configqx;
completed++;
}
else
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
// printf("=====>>>>AI_QX_Class_Thread clsnum %d \n", clsnum);
return 0;
}
int ImgCheckAnalysisy::ThreadTask(int nId)
{
while (!m_bExit)
{
std::this_thread::sleep_for(std::chrono::milliseconds(20));
// 等待是否有任务
std::shared_ptr<TaskInfo> task = m_task.GetTask();
// 把任务发送给对应的任务处理函数进行处理
switch (task->taskname)
{
case Task_AI:
TaskFun_AIDet(task);
break;
case Task_Class:
TaskFun_QxClass(task);
break;
default:
break;
}
}
return 0;
}
int ImgCheckAnalysisy::ResizeImg()
{
cv::Size sz;
sz.width = RESIZE_IMAGE_WIDTH;
float fw = RESIZE_IMAGE_WIDTH * 1.0f / m_pImageAllResult->detImg.cols;
sz.height = int(m_pImageAllResult->detImg.rows * fw);
// 结果图画布取自"原图裁剪区",保持原图通道(彩色原图 -> 真实彩色,不再灰度转假彩);
// ReJson 复测模式下 in_shareImage->img 本身就是裁剪图,直接用检测图兜底
cv::Mat srcCanvas;
const cv::Mat &srcImg = m_CheckResult_shareP->in_shareImage->img;
if (m_CheckResult_shareP->in_shareImage->Det_Mode != DET_MODE_ReJson &&
!srcImg.empty() && CheckUtil::RoiInImg(m_outer_roi, srcImg))
{
srcCanvas = srcImg(m_outer_roi);
}
else
{
srcCanvas = m_pImageAllResult->detImg;
}
cv::resize(srcCanvas, m_pImageAllResult->resultImg, sz);
m_CheckResult_shareP->resultimg = m_pImageAllResult->resultImg;
m_pdetlog->AddCheckstr(PrintLevel_1, "showImg size", " [w %d,h %d]-> show img [w %d,h %d] ",
m_outer_roi.width, m_outer_roi.height, sz.width, sz.height);
return 0;
}
void ImgCheckAnalysisy::TaskFun_AIDet(std::shared_ptr<TaskInfo> task)
{
task->SetStatus(TaskStep_run);
long t1, t2;
// AI 推理生成
{
t1 = CheckUtil::getcurTime();
int rec = AI_Detect_Thread(m_pImageAllResult->detImg, m_pImageAllResult->AIMaskImg);
t2 = CheckUtil::getcurTime();
task->nresult = rec;
}
task->SetStatus(TaskStep_compate);
}
void ImgCheckAnalysisy::TaskFun_QxClass(std::shared_ptr<TaskInfo> task)
{
task->SetStatus(TaskStep_run);
long t1, t2;
// AI 推理生成
{
t1 = CheckUtil::getcurTime();
int rec = AI_QX_Class_Thread();
t2 = CheckUtil::getcurTime();
task->nresult = rec;
}
task->SetStatus(TaskStep_compate);
}
int ImgCheckAnalysisy::Update_DetRoiList()
{
float fx = m_pImageAllResult->fscale_detToresult_x;
float fy = m_pImageAllResult->fscale_detToresult_y;
int num = 0;
cv::Rect offsetroi = m_Crop_Roi_paramImg;
if (m_pEdge_Align_Result && m_pEdge_Align_Result->buseOfft)
{
for (const auto &region : m_pCommonAnalysisyConfig->regionConfigArr)
{
if (m_pEdge_Align_Result->H.empty())
{
m_DetRoiList.Update_1(region.basicInfo.pointArry, offsetroi, m_pEdge_Align_Result->offt_x, m_pEdge_Align_Result->offt_y, fx, fy);
}
else
{
m_DetRoiList.Update_Marit(region.basicInfo.pointArry, offsetroi, m_pEdge_Align_Result->H, fx, fy);
}
// m_DetRoiList.Update_1(region.basicInfo.pointArry, offsetroi, m_pEdge_Align_Result->offt_x, m_pEdge_Align_Result->offt_y, fx, fy);
}
}
else
{
for (const auto &region : m_pCommonAnalysisyConfig->regionConfigArr)
{
m_DetRoiList.Update(region.basicInfo.pointArry, m_Crop_Roi_paramImg, fx, fy);
}
}
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "info", "roiList_Src roi num %ld", m_DetRoiList.roiList_Src.size());
// m_DetRoiList.print("m_DetRoiList");
return 0;
}
int ImgCheckAnalysisy::DrawResult_Step_1()
{
// std::cout << m_pBasicConfig->bDrawPreRoi << std::endl;
if (m_pBasicConfig->bDrawPreRoi)
{
int n = 0;
// std::cout << m_DetRoiList.roiList_Show.size() << std::endl;
for (const auto &polygon : m_DetRoiList.roiList_Show)
{
// n++;
// if (n == 1)
// {
// continue;
// }
// std::cout << polygon << std::endl;
// 绘制多边形的边界(不填充),线宽为1;颜色按画布通道自适应
cv::polylines(m_CheckResult_shareP->resultimg, polygon, true,
CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, cv::Scalar(128, 128, 255)), 1); // true表示闭合多边形
}
// cv::imwrite(DetImgInfo_shareP->strChannel + "roi_src.png", m_pdetlog->temImgList[TEM_IMG_IDX_DrawSrc]);
// cv::imwrite(DetImgInfo_shareP->strChannel + "roi_ss.png", m_pdetlog->temImgList[TEM_IMG_IDX_Result]);
// getchar();
}
float fs_resize_x = m_pImageAllResult->resultImg.cols * 1.0f / m_pImageAllResult->detImg.cols;
float fs_resize_y = m_pImageAllResult->resultImg.rows * 1.0f / m_pImageAllResult->detImg.rows;
// 绘制模板 pin 轮廓(绿色)与当前 pin 轮廓(红色)
// Pin 绘制内容按当前检测方案选择: 新方案绘制拟合直线/缺失标记, 旧方案绘制反变换后的模板Pin
std::vector<std::vector<cv::Point>> directDrawPinContours;
const std::vector<std::vector<cv::Point>> *pTplDrawPinContours = &m_tplPinContoursCur;
if (IsDirectPinMethod())
{
// 新方案: 不做模板对比; 将两排Pin拟合直线段与缺失标记组织成 2点"轮廓", 复用下方循环缩放绘制(绿色)
for (const auto &seg : m_pinFitLines)
{
directDrawPinContours.push_back({cv::Point(cvRound(seg.first.x), cvRound(seg.first.y)),
cv::Point(cvRound(seg.second.x), cvRound(seg.second.y))});
}
for (const auto &mark : m_pinMissMarks)
{
// 缺失位置: 十字标记(两条短线)
directDrawPinContours.push_back({cv::Point(cvRound(mark.x - 6.0f), cvRound(mark.y)),
cv::Point(cvRound(mark.x + 6.0f), cvRound(mark.y))});
directDrawPinContours.push_back({cv::Point(cvRound(mark.x), cvRound(mark.y - 6.0f)),
cv::Point(cvRound(mark.x), cvRound(mark.y + 6.0f))});
}
pTplDrawPinContours = &directDrawPinContours;
}
for (const auto &contour : *pTplDrawPinContours)
{
std::vector<cv::Point> draw_pts;
draw_pts.reserve(contour.size());
for (const auto &pt : contour)
{
draw_pts.emplace_back(cvRound(pt.x * fs_resize_x), cvRound(pt.y * fs_resize_y));
}
cv::polylines(m_CheckResult_shareP->resultimg, draw_pts, true,
CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, cv::Scalar(255, 255, 0)), 1);
}
for (const auto &contour : m_curPinContours)
{
std::vector<cv::Point> draw_pts;
draw_pts.reserve(contour.size());
for (const auto &pt : contour)
{
draw_pts.emplace_back(cvRound(pt.x * fs_resize_x), cvRound(pt.y * fs_resize_y));
}
cv::polylines(m_CheckResult_shareP->resultimg, draw_pts, true,
CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, cv::Scalar(255, 0, 255)), 1);
}
// 绘制m_inner_rroi(蓝色)
{
cv::Point2f vertices[4];
m_inner_rroi.points(vertices);
std::vector<cv::Point> draw_pts;
draw_pts.reserve(4);
for (int i = 0; i < 4; i++)
{
draw_pts.emplace_back(cvRound((vertices[i].x - m_Crop_Roi_paramImg.x) * fs_resize_x),
cvRound((vertices[i].y - m_Crop_Roi_paramImg.y) * fs_resize_y));
}
cv::polylines(m_CheckResult_shareP->resultimg, draw_pts, true,
CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, cv::Scalar(255, 0, 0)), 1);
}
// cv::imwrite("sss.png", m_CheckResult_shareP->resultimg);
return 0;
}
int ImgCheckAnalysisy::AIClassTypeToConfigType(int nAIQXType)
{
// 无对应缺陷类型时返回 CONFIG_QX_NAME_count(缺陷列表已移除 other)
int resultError_type = AI_CLass_QX_NAME_Panel;
switch (nAIQXType)
{
case AI_CLass_QX_NAME_PIN_PoSun:
resultError_type = CONFIG_QX_NAME_PIN_PoSun;
break;
case AI_CLass_QX_NAME_PIN_QueShi:
resultError_type = CONFIG_QX_NAME_PIN_QueShi;
break;
case AI_CLass_QX_NAME_BTB_DuanLie:
resultError_type = CONFIG_QX_NAME_BTB_DuanLie;
break;
case AI_CLass_QX_NAME_LianXi:
resultError_type = CONFIG_QX_NAME_LianXi;
break;
case AI_CLass_QX_NAME_QueXi:
resultError_type = CONFIG_QX_NAME_QueXi;
break;
case AI_CLass_QX_NAME_YiWu:
resultError_type = CONFIG_QX_NAME_YiWu;
break;
case AI_CLass_QX_NAME_C_BianXing:
resultError_type = CONFIG_QX_NAME_C_BianXing;
break;
case AI_CLass_QX_NAME_Panel:
resultError_type = CONFIG_QX_NAME_Panel;
break;
default:
break;
}
return resultError_type;
}
int ImgCheckAnalysisy::UpdateImgageScale()
{
if (m_pBasicConfig->fImage_Scale_x > 0 && m_pBasicConfig->fImage_Scale_x < 1 &&
m_pBasicConfig->fImage_Scale_y > 0 && m_pBasicConfig->fImage_Scale_y < 1)
{
m_fImgage_Scale_X = m_pBasicConfig->fImage_Scale_x;
m_fImgage_Scale_Y = m_pBasicConfig->fImage_Scale_y;
}
return 0;
}
// 当前 Pin 检测方案是否为"直接分析"(新方案): 由参数 pinDet.Det_Method 在运行时决定
// "line_fitting" -> 新方案(不做模板对比); 其它/未配置 -> 模板对比方案
bool ImgCheckAnalysisy::IsDirectPinMethod() const
{
if (m_pbaseCheckFunction == NULL)
{
return false;
}
return NormalizeDetMethodStr(m_pbaseCheckFunction->pinDet.Det_Method) == "line_fitting";
}
// 当前 Pin 检测方案是否为"Pin 投影矩阵"方案: pinDet.Det_Method == "Pin_Matrix"(忽略大小写/空白)
// 该方案与 BTB_Marix 一样做模板 Pin 对比, 区别只在投影矩阵由 Pin 自身拟合(上下两排分别计算)
bool ImgCheckAnalysisy::IsPinMatrixMethod() const
{
if (m_pbaseCheckFunction == NULL)
{
return false;
}
return NormalizeDetMethodStr(m_pbaseCheckFunction->pinDet.Det_Method) == "pin_matrix";
}
// Pin_Matrix 方案: 过滤当前帧上明显的误检 blob(按模板 Pin 的包围框尺寸)
// 以模板 Pin 外接矩形的中位宽/高为基准(按当前/模板外框面积比缩放), 当前 blob 的宽/高需落在 [0.4, 2.5] 倍范围内;
// 模板 Pin 少于 3 个时不做过滤。
int ImgCheckAnalysisy::FilterCurPinBlobsByTplSize()
{
if (m_curPinContours.empty() || m_tplPinContours.size() < 3)
{
return 0;
}
std::vector<int> ws;
std::vector<int> hs;
ws.reserve(m_tplPinContours.size());
hs.reserve(m_tplPinContours.size());
for (size_t i = 0; i < m_tplPinContours.size(); i++)
{
cv::Rect r = cv::boundingRect(m_tplPinContours[i]);
if (r.width > 0 && r.height > 0)
{
ws.push_back(r.width);
hs.push_back(r.height);
}
}
if (ws.size() < 3)
{
return 0;
}
std::sort(ws.begin(), ws.end());
std::sort(hs.begin(), hs.end());
float medW = static_cast<float>(ws[ws.size() / 2]);
float medH = static_cast<float>(hs[hs.size() / 2]);
// 模板/当前外框面积比 -> 尺寸基准缩放(同一相机一般约为 1)
const double tplArea = static_cast<double>(m_tplOuterRect.size.width) * m_tplOuterRect.size.height;
const double curArea = static_cast<double>(m_outer_rroi.size.width) * m_outer_rroi.size.height;
if (tplArea > 0.0 && curArea > 0.0)
{
const float scaleRef = static_cast<float>(std::sqrt(curArea / tplArea));
medW *= scaleRef;
medH *= scaleRef;
}
const float kMinRatio = 0.4f; // 尺寸下限比例
const float kMaxRatio = 2.5f; // 尺寸上限比例
const size_t nBefore = m_curPinContours.size();
for (size_t i = m_curPinContours.size(); i-- > 0;)
{
cv::Rect r = cv::boundingRect(m_curPinContours[i]);
const bool bSizeOk = (r.width > 0 && r.height > 0) &&
(r.width >= medW * kMinRatio) && (r.width <= medW * kMaxRatio) &&
(r.height >= medH * kMinRatio) && (r.height <= medH * kMaxRatio);
if (!bSizeOk)
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Matrix",
"filter suspicious pin blob idx %d size[%d %d] (tpl median[%.1f %.1f])",
static_cast<int>(i), r.width, r.height, medW, medH);
m_curPinContours.erase(m_curPinContours.begin() + static_cast<long>(i));
}
}
if (m_curPinContours.size() != nBefore)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Matrix",
"filter pin blobs: %ld -> %ld (tpl median size [%.1f %.1f])",
static_cast<long>(nBefore), static_cast<long>(m_curPinContours.size()), medW, medH);
}
return 0;
}
// Det_Method == "Pin_Matrix": 与 BTB_Marix 一样做"模板 Pin vs 当前 Pin"的逐个比对(相交 -> 最近 -> dx/dy 超限),
// 区别只在投影矩阵的来源: BTB_Marix 用内/外定位矩形求仿射, 本方案直接由 Pin 拟合:
// 1) 内/外定位仿射只作为匹配初值(模板 Pin -> 当前检测图), 最终矩阵由 Pin 决定;
// 2) 模板 Pin 按当前帧内定位矩形的长边法线分成上下两排, 每排独立估计一个相似变换(旋转+等比缩放+平移):
// 一排 Pin 近似共线, 6 自由度全仿射在垂直排方向上病态, 因此用 4 自由度相似变换;
// 3) 估计准则 = "模板 Pin 投影后与当前 Pin 重合的数量最多": 先固定初值的旋转/缩放做平移投票,
// 再按最近邻一对一匹配 + RANSAC 拟合迭代求精, 取重合数最多的解;
// 4) 某排可匹配 Pin 少于 2 个(无法拟合)时, 该排退回内/外定位仿射投影, 保证缺失 Pin 的 roi 仍能落位。
// 输出的 m_tplPinContoursCur 即按每排矩阵投影后的模板 Pin 轮廓(当前检测图坐标系), 供后续比对/绘制使用。
int ImgCheckAnalysisy::ComputePinProjectionMats()
{
m_tplPinContoursCur.clear();
if (m_tplPinContours.empty())
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Matrix", "no template pin contours, skip");
return 0;
}
// 0、过滤明显误检 blob(仅本方案; line_fitting / BTB_Marix 行为不变)
FilterCurPinBlobsByTplSize();
const cv::Rect tplBound = m_tplOuterRect.boundingRect();
auto getCenter = [](const std::vector<cv::Point> &contour) -> cv::Point2f
{
cv::Rect r = cv::boundingRect(contour);
return cv::Point2f(r.x + r.width * 0.5f, r.y + r.height * 0.5f);
};
const int nTpl = static_cast<int>(m_tplPinContours.size());
const int nCur = static_cast<int>(m_curPinContours.size());
// 1、匹配初值: 内定位优先/外定位回退(仅用于建立对应关系, 不参与最终判断)
const bool bUseInnerAlign = (m_tplInnerRect.size.width > 0 && m_tplInnerRect.size.height > 0 &&
m_inner_rroi.size.width > 0 && m_inner_rroi.size.height > 0);
if (bUseInnerAlign)
{
CalAffineMats(m_tplInnerRect, m_inner_rroi);
}
else
{
CalAffineMats(m_tplOuterRect, m_outer_rroi);
}
// T0: 模板图坐标 -> detImg 坐标
cv::Mat T0;
if (m_bAffineValid && !m_affine_tpl2cur.empty())
{
T0 = m_affine_tpl2cur.clone();
T0.at<double>(0, 2) -= m_outer_roi.x;
T0.at<double>(1, 2) -= m_outer_roi.y;
}
else
{
// 防御: 定位矩形异常时退化为"模板外框中心 -> 当前外框中心"的平移
const cv::Rect curOuter = m_outer_rroi.boundingRect();
const double tx = (curOuter.x + curOuter.width * 0.5) - (tplBound.x + tplBound.width * 0.5) - m_outer_roi.x;
const double ty = (curOuter.y + curOuter.height * 0.5) - (tplBound.y + tplBound.height * 0.5) - m_outer_roi.y;
T0 = (cv::Mat_<double>(2, 3) << 1.0, 0.0, tx, 0.0, 1.0, ty);
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Matrix",
"affine init invalid, fallback to bbox translation t(%.1f %.1f) px", tx, ty);
}
// 2、模板 Pin 中心(模板图坐标) + 用初值投影到 detImg(仅用于分排)
std::vector<cv::Point2f> tplCenters(nTpl);
std::vector<cv::Point2f> tplCentersDet(nTpl);
for (int i = 0; i < nTpl; i++)
{
cv::Point2f c = getCenter(m_tplPinContours[i]);
tplCenters[i] = cv::Point2f(c.x + static_cast<float>(tplBound.x), c.y + static_cast<float>(tplBound.y));
tplCentersDet[i] = ApplyAffine2D(T0, tplCenters[i]);
}
std::vector<cv::Point2f> curCenters(nCur);
for (int j = 0; j < nCur; j++)
{
curCenters[j] = getCenter(m_curPinContours[j]);
}
// 3、模板 Pin 分两排: 当前帧内定位矩形的长边法线(与整体偏移估计一致); 内定位无效时按 Pin 自身几何兜底
std::vector<std::vector<int>> rows;
{
cv::RotatedRect innerRectDet = m_inner_rroi;
innerRectDet.center.x -= static_cast<float>(m_outer_roi.x);
innerRectDet.center.y -= static_cast<float>(m_outer_roi.y);
if (innerRectDet.size.width > 0 && innerRectDet.size.height > 0)
{
std::vector<std::vector<int>> sideIdx(2);
for (int i = 0; i < nTpl; i++)
{
sideIdx[(PinRowSideOfRect(innerRectDet, tplCentersDet[i]) < 0.0f) ? 0 : 1].push_back(i);
}
if (!sideIdx[0].empty() && !sideIdx[1].empty())
{
rows.push_back(sideIdx[0]);
rows.push_back(sideIdx[1]);
}
}
if (rows.empty())
{
rows = SplitPinsIntoRows(tplCentersDet);
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Matrix",
"inner rect unavailable, split pins by pin geometry instead");
}
}
// 4、重合判据: pin_x_offset(mm) 换算为像素(pin_x_offset 未配置时用 pin_y_offset)
float matchRadius = 0.0f;
if (m_pbaseCheckFunction != NULL)
{
if (m_pbaseCheckFunction->pinDet.pin_x_offset > 0.0f && m_fImgage_Scale_X > 0.0f)
{
matchRadius = m_pbaseCheckFunction->pinDet.pin_x_offset / m_fImgage_Scale_X;
}
else if (m_pbaseCheckFunction->pinDet.pin_y_offset > 0.0f && m_fImgage_Scale_Y > 0.0f)
{
matchRadius = m_pbaseCheckFunction->pinDet.pin_y_offset / m_fImgage_Scale_Y;
}
}
if (matchRadius <= 0.0f)
{
matchRadius = 20.0f; // 未配置阈值时的默认匹配半径
}
if (matchRadius < 2.0f)
{
matchRadius = 2.0f;
}
// 5、逐排拟合: 每排放到对应的当前 Pin 组里做"平移投票(固定初值旋转/缩放) -> 最近邻一对一匹配 -> RANSAC 相似变换",
// 取"模板 Pin 投影后与当前 Pin 重合数最多"的解; 两排的对应关系取总重合数最大的配对(兼容上下颠倒)
struct PinRowFit
{
cv::Mat M; // 模板图坐标 -> detImg 坐标
int nMatch = 0; // 投影后与当前 Pin 重合的数量
float radius = 0.0f;
bool bFromPins = false;
};
// 当前帧 Pin 也按同一内定位法线分两排; 内定位无效时按 Pin 自身几何兜底
std::vector<std::vector<int>> curRows;
{
cv::RotatedRect innerRectDet = m_inner_rroi;
innerRectDet.center.x -= static_cast<float>(m_outer_roi.x);
innerRectDet.center.y -= static_cast<float>(m_outer_roi.y);
if (innerRectDet.size.width > 0 && innerRectDet.size.height > 0)
{
std::vector<std::vector<int>> sideIdx(2);
for (int j = 0; j < nCur; j++)
{
sideIdx[(PinRowSideOfRect(innerRectDet, curCenters[j]) < 0.0f) ? 0 : 1].push_back(j);
}
if (!sideIdx[0].empty() && !sideIdx[1].empty())
{
curRows = sideIdx;
}
}
if (curRows.empty() && nCur > 0)
{
curRows = SplitPinsIntoRows(curCenters);
}
}
std::vector<int> allCurIdx(nCur);
for (int j = 0; j < nCur; j++)
{
allCurIdx[j] = j;
}
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Matrix",
"pin rows: tpl %d cur %d", static_cast<int>(rows.size()),
static_cast<int>(curRows.size()));
const double initAngle = std::atan2(T0.at<double>(1, 0), T0.at<double>(0, 0));
auto bReasonableMat = [&](const cv::Mat &M) -> bool
{
if (M.empty() || M.rows != 2 || M.cols != 3)
{
return false;
}
const double a = M.at<double>(0, 0), b = M.at<double>(0, 1);
const double c = M.at<double>(1, 0), d = M.at<double>(1, 1);
if (a * d - b * c <= 0.0)
{
return false; // 不允许镜像/退化
}
const double scale = std::sqrt(a * a + c * c);
if (scale < 0.5 || scale > 2.0)
{
return false;
}
double dAng = std::atan2(c, a) - initAngle;
while (dAng > CV_PI)
{
dAng -= 2.0 * CV_PI;
}
while (dAng < -CV_PI)
{
dAng += 2.0 * CV_PI;
}
return std::fabs(dAng) <= 30.0 * CV_PI / 180.0;
};
// 用模板 Pin 组 rowIdx 与当前 Pin 候选组 curIdxList 拟合一个相似变换(模板图坐标 -> detImg 坐标)
auto fitOneRow = [&](const std::vector<int> &rowIdx, const std::vector<int> &curIdxList, PinRowFit &fit)
{
const int nRow = static_cast<int>(rowIdx.size());
const int nCurSub = static_cast<int>(curIdxList.size());
fit = PinRowFit();
fit.M = T0.clone();
fit.radius = matchRadius;
// 匹配半径不能超过同排相邻 Pin 间距的一半, 否则会错配到相邻 Pin
{
float minGap = 0.0f;
for (int a = 0; a < nRow; a++)
{
for (int b = a + 1; b < nRow; b++)
{
float d = static_cast<float>(cv::norm(tplCenters[rowIdx[a]] - tplCenters[rowIdx[b]]));
if (minGap <= 0.0f || d < minGap)
{
minGap = d;
}
}
}
if (minGap > 0.0f && fit.radius > minGap * 0.5f)
{
fit.radius = minGap * 0.5f;
}
}
if (fit.radius < 1.0f)
{
fit.radius = 1.0f;
}
if (nRow < 2 || nCurSub < 2)
{
return;
}
// 5.1) 平移投票: 固定初值的旋转/缩放, 取"投影后重合数最多"的平移
const double l00 = T0.at<double>(0, 0), l01 = T0.at<double>(0, 1);
const double l10 = T0.at<double>(1, 0), l11 = T0.at<double>(1, 1);
const cv::Point2f t0(static_cast<float>(T0.at<double>(0, 2)), static_cast<float>(T0.at<double>(1, 2)));
auto countOverlap = [&](const cv::Mat &M) -> int
{
// 粗略重合计数(允许多对一): 只看"投影点附近是否有当前 Pin", 用于平移投票
int cnt = 0;
for (int a = 0; a < nRow; a++)
{
cv::Point2f p = ApplyAffine2D(M, tplCenters[rowIdx[a]]);
for (int k = 0; k < nCurSub; k++)
{
if (cv::norm(curCenters[curIdxList[k]] - p) <= fit.radius)
{
cnt++;
break;
}
}
}
return cnt;
};
int bestCnt = -1;
double bestTDist = 1e12;
cv::Point2f bestT = t0;
for (int a = 0; a < nRow; a++)
{
const cv::Point2f &tp = tplCenters[rowIdx[a]];
const double lx = l00 * tp.x + l01 * tp.y;
const double ly = l10 * tp.x + l11 * tp.y;
for (int k = 0; k < nCurSub; k++)
{
const cv::Point2f &curPt = curCenters[curIdxList[k]];
const cv::Point2f t(static_cast<float>(curPt.x - lx),
static_cast<float>(curPt.y - ly));
cv::Mat M = T0.clone();
M.at<double>(0, 2) = t.x;
M.at<double>(1, 2) = t.y;
const int cnt = countOverlap(M);
const double tDist = cv::norm(t - t0);
// 重合数最多; 并列时取离初值平移更近的(避免跨排/跨列错位)
if (cnt > bestCnt || (cnt == bestCnt && tDist < bestTDist))
{
bestCnt = cnt;
bestT = t;
bestTDist = tDist;
}
}
}
cv::Mat MCur = T0.clone();
MCur.at<double>(0, 2) = bestT.x;
MCur.at<double>(1, 2) = bestT.y;
// 5.2) 最近邻一对一匹配 + RANSAC 相似变换迭代求精, 取重合数最多的解
auto matchPins = [&](const cv::Mat &M, std::vector<std::pair<int, int>> &matches) -> int
{
std::vector<std::pair<double, std::pair<int, int>>> cands;
cands.reserve(static_cast<size_t>(nRow) * static_cast<size_t>(nCurSub));
for (int a = 0; a < nRow; a++)
{
cv::Point2f p = ApplyAffine2D(M, tplCenters[rowIdx[a]]);
for (int k = 0; k < nCurSub; k++)
{
const int j = curIdxList[k];
const double d = cv::norm(curCenters[j] - p);
if (d <= fit.radius)
{
cands.push_back(std::make_pair(d, std::make_pair(a, j)));
}
}
}
std::sort(cands.begin(), cands.end());
std::vector<char> usedT(nRow, 0);
std::vector<char> usedC(nCur, 0);
matches.clear();
for (size_t k = 0; k < cands.size(); k++)
{
const int a = cands[k].second.first;
const int j = cands[k].second.second;
if (usedT[a] || usedC[j])
{
continue;
}
usedT[a] = 1;
usedC[j] = 1;
matches.push_back(cands[k].second);
}
return static_cast<int>(matches.size());
};
std::vector<std::pair<int, int>> matches;
int bestOverlap = matchPins(MCur, matches);
cv::Mat bestM = MCur.clone();
for (int iter = 0; iter < 5 && bestOverlap < nRow; iter++)
{
if (static_cast<int>(matches.size()) < 2)
{
break;
}
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]);
}
// 一排 Pin 近似共线, 用 4 自由度相似变换(旋转+等比缩放+平移)避免全仿射病态
cv::Mat MNew = cv::estimateAffinePartial2D(srcPts, dstPts, cv::noArray(), cv::RANSAC, 3.0, 2000, 0.995, 10);
if (!bReasonableMat(MNew))
{
break;
}
const int cnt = matchPins(MNew, matches);
if (cnt > bestOverlap)
{
bestOverlap = cnt;
bestM = MNew.clone();
}
MCur = MNew;
}
if (bestOverlap >= 2 && bReasonableMat(bestM))
{
fit.M = bestM;
fit.nMatch = bestOverlap;
fit.bFromPins = true;
}
};
std::vector<PinRowFit> rowFits(rows.size());
if (rows.size() == 2 && curRows.size() == 2)
{
// 两侧都成功分排: 每排分别与两个当前排各拟合一次, 再取总重合数更大的配对(兼容上下颠倒)
PinRowFit fits[2][2];
for (int a = 0; a < 2; a++)
{
for (int b = 0; b < 2; b++)
{
fitOneRow(rows[a], curRows[b], fits[a][b]);
}
}
const int scoreDirect = fits[0][0].nMatch + fits[1][1].nMatch;
const int scoreSwap = fits[0][1].nMatch + fits[1][0].nMatch;
if (scoreSwap > scoreDirect)
{
rowFits[0] = fits[0][1];
rowFits[1] = fits[1][0];
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Matrix",
"row pairing swapped (direct %d vs swap %d)", scoreDirect, scoreSwap);
}
else
{
rowFits[0] = fits[0][0];
rowFits[1] = fits[1][1];
}
}
else
{
// 无法两侧都分排: 每排与全部当前 Pin 匹配
for (size_t r = 0; r < rows.size(); r++)
{
fitOneRow(rows[r], allCurIdx, rowFits[r]);
}
}
for (size_t r = 0; r < rowFits.size(); r++)
{
if (!rowFits[r].bFromPins)
{
// 该排可匹配 Pin 不足(无法拟合), 退回内/外定位仿射投影, 保证缺失 Pin 的 roi 仍能落位
rowFits[r].M = T0.clone();
rowFits[r].nMatch = 0;
}
}
// 6、按每排矩阵把模板 Pin 轮廓投影到 detImg 坐标系
std::vector<int> rowOfPin(nTpl, -1);
for (size_t r = 0; r < rows.size(); r++)
{
for (size_t k = 0; k < rows[r].size(); k++)
{
rowOfPin[rows[r][k]] = static_cast<int>(r);
}
}
m_tplPinContoursCur.resize(nTpl);
for (int i = 0; i < nTpl; i++)
{
const cv::Mat &M = (rowOfPin[i] >= 0) ? rowFits[rowOfPin[i]].M : T0;
std::vector<cv::Point> mapped;
mapped.reserve(m_tplPinContours[i].size());
for (size_t k = 0; k < m_tplPinContours[i].size(); k++)
{
const cv::Point2f srcPt(static_cast<float>(m_tplPinContours[i][k].x + tplBound.x),
static_cast<float>(m_tplPinContours[i][k].y + tplBound.y));
const cv::Point2f dstPt = ApplyAffine2D(M, srcPt);
mapped.emplace_back(cvRound(dstPt.x), cvRound(dstPt.y));
}
m_tplPinContoursCur[i] = std::move(mapped);
}
// 7、日志: 每排的矩阵(旋转/缩放/平移)与重合数
for (size_t r = 0; r < rows.size(); r++)
{
const cv::Mat &M = rowFits[r].M;
const double scale = std::sqrt(M.at<double>(0, 0) * M.at<double>(0, 0) +
M.at<double>(1, 0) * M.at<double>(1, 0));
const double angle = std::atan2(M.at<double>(1, 0), M.at<double>(0, 0)) * 180.0 / CV_PI;
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Matrix",
"row %d pins %d matched %d/%d scale %.4f angle %.2f deg t(%.1f %.1f) px %s",
static_cast<int>(r), static_cast<int>(rows[r].size()),
rowFits[r].nMatch, static_cast<int>(rows[r].size()), scale, angle,
M.at<double>(0, 2), M.at<double>(1, 2),
rowFits[r].bFromPins ? "" : "(fallback to align affine)");
}
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Matrix",
"tpl %d cur %d rows %d match radius %.1f px (pin_x_offset %.2f mm scale_x %.4f)",
nTpl, nCur, static_cast<int>(rows.size()), rowFits.empty() ? matchRadius : rowFits[0].radius,
m_pbaseCheckFunction != NULL ? m_pbaseCheckFunction->pinDet.pin_x_offset : 0.0f,
m_fImgage_Scale_X);
return 0;
}
int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img)
{
m_Edge_DetConfig.Init();
m_Edge_DetConfig.strChannel = m_strCurDetChannel;
m_Edge_DetConfig.strProductID = DetImgInfo_shareP->strImgProductID;
m_Edge_DetConfig.pBaseCheckFunction = m_pbaseCheckFunction;
m_Edge_DetConfig.bSaveResultImg = false;
if (DetImgInfo_shareP->bsaveProcessImg)
{
m_Edge_DetConfig.bSaveResultImg = true;
}
// m_pbaseCheckFunction->print("Pin_Qx_Det");
Mat pin_mask;
int re = m_Pin_QX_Det.GetPinMask(img, &m_Edge_DetConfig, pin_mask);
// 获取当前pinmask
vector<vector<Point>> pin_contours;
vector<Vec4i> hierarchy;
cv::findContours(pin_mask, pin_contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);
//缩放比例
const float pin_scale_x = (pin_mask.cols > 0) ? static_cast<float>(img.cols) / pin_mask.cols : 0.0f;
const float pin_scale_y = (pin_mask.rows > 0) ? static_cast<float>(img.rows) / pin_mask.rows : 0.0f;
m_curPinContours.clear();
m_curPinContours.reserve(pin_contours.size());
for (size_t i = 0; i < pin_contours.size(); i++)
{
std::vector<cv::Point> mapped;
mapped.reserve(pin_contours[i].size());
for (const auto &pt : pin_contours[i])
{
mapped.emplace_back(cvRound(pt.x * pin_scale_x),
cvRound(pt.y * pin_scale_y));
}
m_curPinContours.push_back(mapped);
}
if (IsDirectPinMethod())
{
// 新方案: 不做模板对比, 只分析检测Pin(自动分两排 -> 逐排拟合直线判偏移 + 相邻间距判缺失)
Pin_Direct_Check();
}
else
// 根据模板Pin和当前Pin轮廓进行对比,判断当前有无缺失和偏移
{
if (IsPinMatrixMethod())
{
// Det_Method == "Pin_Matrix": 投影矩阵由当前帧 Pin 拟合(上下两排分别计算),
// 把模板 Pin 投影到当前检测图坐标系; 后续比对与 BTB_Marix 完全一致
ComputePinProjectionMats();
}
// 计算轮廓中心点(外接矩形中心)
auto getCenter = [](const std::vector<cv::Point> &contour) -> cv::Point2f
{
cv::Rect r = cv::boundingRect(contour);
return cv::Point2f(r.x + r.width * 0.5f, r.y + r.height * 0.5f);
};
const int nTpl = static_cast<int>(m_tplPinContoursCur.size());
const int nCur = static_cast<int>(m_curPinContours.size());
if (nTpl > 0)
{
// 1、统计模板 pin 中心与外包矩形(已反变换到当前检测图坐标系)
std::vector<cv::Point2f> tplCenters(nTpl);
std::vector<cv::Rect> tplRects(nTpl);
for (int i = 0; i < nTpl; i++)
{
tplCenters[i] = getCenter(m_tplPinContoursCur[i]);
tplRects[i] = cv::boundingRect(m_tplPinContoursCur[i]);
}
// 2、当前 pin 中心与外包矩形
std::vector<cv::Point2f> curCenters(nCur);
std::vector<cv::Rect> curRects(nCur);
for (int i = 0; i < nCur; i++)
{
curCenters[i] = getCenter(m_curPinContours[i]);
curRects[i] = cv::boundingRect(m_curPinContours[i]);
}
std::vector<bool> curUsed(nCur, false);
// 整体偏移估计与消除(阈值见文件顶部 PIN_SHIFT_* 常量):
// Pin_Matrix 方案的投影矩阵已由 Pin 自身拟合(上下两排分别计算), 不需要二次偏移矫正
if (!IsPinMatrixMethod())
{
// inner 定位 ROI 转到 detImg 坐标系(detImg 由 m_outer_roi 裁剪得到)
cv::RotatedRect innerRectDet = m_inner_rroi;
innerRectDet.center.x -= static_cast<float>(m_outer_roi.x);
innerRectDet.center.y -= static_cast<float>(m_outer_roi.y);
// 1) 用 inner ROI 的长边法线方向区分两组 pin: 法向坐标 < 0 为一排, >= 0 为另一排
std::vector<std::vector<int>> shiftGroups;
std::vector<std::string> shiftGroupNames;
if (m_inner_rroi.size.width > 0 && m_inner_rroi.size.height > 0)
{
std::vector<std::vector<int>> sideIdx(2);
for (int i = 0; i < nTpl; i++)
{
sideIdx[(PinRowSideOfRect(innerRectDet, tplCenters[i]) < 0.0f) ? 0 : 1].push_back(i);
}
// 两排各自至少 2 个 pin 才能独立可靠估计; 否则回退为整体估计
if (sideIdx[0].size() >= 2 && sideIdx[1].size() >= 2)
{
shiftGroups.push_back(sideIdx[0]);
shiftGroupNames.push_back("upper row");
shiftGroups.push_back(sideIdx[1]);
shiftGroupNames.push_back("lower row");
}
else
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check",
"pin row split by inner roi invalid: upper %d lower %d, estimate as a whole",
static_cast<int>(sideIdx[0].size()), static_cast<int>(sideIdx[1].size()));
}
}
else
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check",
"inner roi invalid, pin offset estimate as a whole");
}
if (shiftGroups.empty())
{
// inner ROI 无效或无法分排: 回退为整体估计
std::vector<int> allIdx(nTpl);
for (int i = 0; i < nTpl; i++)
{
allIdx[i] = i;
}
shiftGroups.push_back(allIdx);
shiftGroupNames.push_back("all");
}
// 2) 逐排(逐组)估计整体位移并消除
if (nCur > 0)
{
for (size_t g = 0; g < shiftGroups.size(); g++)
{
const std::vector<int> &group = shiftGroups[g];
const int nGroup = static_cast<int>(group.size());
if (nGroup <= 0)
{
continue;
}
// 组内相邻 pin 最小间距: 用于限制整体偏移的量级(整体偏移不应接近一个 pin 间距, 否则无法与"错配一列"区分)
float minGap = 0.0f;
for (int a = 0; a < nGroup; a++)
{
for (int b = a + 1; b < nGroup; b++)
{
float d = static_cast<float>(cv::norm(tplCenters[group[a]] - tplCenters[group[b]]));
if (minGap <= 0.0f || d < minGap)
{
minGap = d;
}
}
}
// 2.1) 组内每个模板 pin 取最近检测 pin 的位移作为候选位移(最近检测 pin 必定存在, nCur > 0)
std::vector<cv::Point2f> disp(nGroup);
for (int a = 0; a < nGroup; a++)
{
int nearestIdx = 0;
double bestD = 1e12;
for (int j = 0; j < nCur; j++)
{
double d = cv::norm(curCenters[j] - tplCenters[group[a]]);
if (d < bestD)
{
bestD = d;
nearestIdx = j;
}
}
disp[a] = curCenters[nearestIdx] - tplCenters[group[a]];
}
// 2.2) 网格投票找主位移(峰值并列时优先取量级更小的)
const float binSize = PIN_SHIFT_BIN_SIZE_PX;
std::map<std::pair<int, int>, int> hist;
int bestCount = 0;
float bestX = 0.0f;
float bestY = 0.0f;
for (int a = 0; a < nGroup; a++)
{
std::pair<int, int> key(static_cast<int>(std::lround(disp[a].x / binSize)),
static_cast<int>(std::lround(disp[a].y / binSize)));
int cnt = ++hist[key];
float cx = key.first * binSize;
float cy = key.second * binSize;
if (cnt > bestCount ||
(cnt == bestCount && cx * cx + cy * cy < bestX * bestX + bestY * bestY))
{
bestCount = cnt;
bestX = cx;
bestY = cy;
}
}
// 2.3) 峰值邻域(±1 档)内的候选位移取均值, 同时统计支持度
double sumX = 0.0;
double sumY = 0.0;
int sumN = 0;
for (int a = 0; a < nGroup; a++)
{
if (std::fabs(disp[a].x - bestX) <= binSize &&
std::fabs(disp[a].y - bestY) <= binSize)
{
sumX += disp[a].x;
sumY += disp[a].y;
sumN++;
}
}
cv::Point2f rowShift(0.0f, 0.0f);
if (sumN > 0)
{
rowShift.x = static_cast<float>(sumX / sumN);
rowShift.y = static_cast<float>(sumY / sumN);
}
else
{
rowShift.x = bestX;
rowShift.y = bestY;
}
// 2.4) 支持度达标 且 位移量级小于 pin 间距的限定比例 -> 判定为该排整体偏移
const double supportRatio = static_cast<double>(sumN) / nGroup;
const float shiftLen = static_cast<float>(cv::norm(rowShift));
const bool bRowShift = (supportRatio >= PIN_SHIFT_MIN_SUPPORT_RATIO) &&
(minGap <= 0.0f || shiftLen < minGap * PIN_SHIFT_MAX_GAP_RATIO);
if (bRowShift)
{
// 该排模板 pin 轮廓整体平移, 使其与检测 pin 一致
const int shiftX = cvRound(rowShift.x);
const int shiftY = cvRound(rowShift.y);
for (int a = 0; a < nGroup; a++)
{
const int i = group[a];
for (auto &pt : m_tplPinContoursCur[i])
{
pt.x += shiftX;
pt.y += shiftY;
}
// 与前面的统计口径保持一致: 中心/外包框由平移后的轮廓重新计算
tplCenters[i] = getCenter(m_tplPinContoursCur[i]);
tplRects[i] = cv::boundingRect(m_tplPinContoursCur[i]);
}
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check",
"global offset removed(%s): template pins shifted dx %.2f dy %.2f mm (%.1f %.1f px), support %d/%d",
shiftGroupNames[g].c_str(),
rowShift.x * m_fImgage_Scale_X, rowShift.y * m_fImgage_Scale_Y,
rowShift.x, rowShift.y, sumN, nGroup);
}
else if (bestCount > 0)
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check",
"global offset not applied(%s): dx %.1f dy %.1f px, support %d/%d (ratio %.2f, pin gap %.1f px)",
shiftGroupNames[g].c_str(),
rowShift.x, rowShift.y, sumN, nGroup, supportRatio, minGap);
}
}
}
}
// 3、偏移阈值直接使用基础参数 pin_x_offset / pin_y_offset (mm),与换算为 mm 的偏移量直接比较
float offsetThX = 0.0f; // mm
float offsetThY = 0.0f; // mm
if (m_pbaseCheckFunction != NULL)
{
offsetThX = m_pbaseCheckFunction->pinDet.pin_x_offset;
offsetThY = m_pbaseCheckFunction->pinDet.pin_y_offset;
}
// 上报缺失/偏移缺陷(缺陷类型可按需调整)
auto reportPinError = [&](int pinIdx, const cv::Point2f &tplCenter, const std::string &reason, int nconfig_qx_type)
{
QX_ERROR_INFO_ pinErr;
pinErr.Idx = static_cast<int>(m_pDetResult->pQx_ErrorList->size());
cv::Rect r = cv::boundingRect(m_tplPinContoursCur[pinIdx]);
pinErr.roi = r;
pinErr.area = static_cast<int>(cv::contourArea(m_tplPinContoursCur[pinIdx]));
pinErr.JudgArea = pinErr.area * m_fImgage_Scale_X * m_fImgage_Scale_Y;
int longSide = (r.width > r.height) ? r.width : r.height;
int shortSide = (r.width < r.height) ? r.width : r.height;
pinErr.flen = longSide * m_fImgage_Scale_X;
pinErr.fbreadth = shortSide * m_fImgage_Scale_Y;
pinErr.nconfig_qx_type = nconfig_qx_type;
pinErr.qx_name = CONFIG_QX_NAME_Names[nconfig_qx_type];
pinErr.detRegionidxList.push_back(0);
pinErr.detlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check",
"pin %d center(%d,%d) %s",
pinIdx, static_cast<int>(tplCenter.x), static_cast<int>(tplCenter.y),
reason.c_str());
m_pDetResult->pQx_ErrorList->push_back(pinErr);
};
int nMiss = 0;
int nOffset = 0;
// 4、逐个模板 pin 找与其相交且未被占用的当前 pin
for (int i = 0; i < nTpl; i++)
{
int bestIdx = -1;
double bestDist = 1e12;
for (int j = 0; j < nCur; j++)
{
if (curUsed[j])
{
continue;
}
// 完全与模板没有相交 -> 不算候选
cv::Rect inter = tplRects[i] & curRects[j];
if (inter.width <= 0 || inter.height <= 0)
{
continue;
}
float dx = tplCenters[i].x - curCenters[j].x;
float dy = tplCenters[i].y - curCenters[j].y;
double d = std::sqrt(static_cast<double>(dx) * dx + static_cast<double>(dy) * dy);
if (d < bestDist)
{
bestDist = d;
bestIdx = j;
}
}
// 没有相交的当前 pin -> 缺失
if (bestIdx < 0)
{
nMiss++;
reportPinError(i, tplCenters[i], "miss", CONFIG_QX_NAME_PIN_QueShi);
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "pin %d MISS", i);
continue;
}
curUsed[bestIdx] = true;
// x 或 y 方向偏移超过配置阈值 -> 偏移;偏移量按成像精度换算为 mm (mm = px * 成像精度)
float dx_mm = (tplCenters[i].x - curCenters[bestIdx].x) * m_fImgage_Scale_X;
float dy_mm = (tplCenters[i].y - curCenters[bestIdx].y) * m_fImgage_Scale_Y;
float absDx = std::fabs(dx_mm);
float absDy = std::fabs(dy_mm);
bool bOffset = (absDx > offsetThX || absDy > offsetThY);
if (bOffset)
{
nOffset++;
char buf[64];
snprintf(buf, sizeof(buf), "offset dx %.2f dy %.2f mm", absDx, absDy);
reportPinError(i, tplCenters[i], buf, CONFIG_QX_NAME_PIN_PianYi);
}
// 每个pin脚都打印dx/dy(mm),方便调整 pin_x_offset / pin_y_offset 参数
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "pin %d %s dx %.2f dy %.2f mm", i, bOffset ? "OFFSET" : "OK", absDx, absDy);
}
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "tpl %d cur %d miss %d offset %d", nTpl, nCur, nMiss, nOffset);
}
}
if (m_Edge_DetConfig.bSaveResultImg)
{
// 画布优先用原图裁剪区(保持原图通道),ReJson 或异常时退回检测图
Mat showimg;
const cv::Mat &srcImg = m_CheckResult_shareP->in_shareImage->img;
if (m_CheckResult_shareP->in_shareImage->Det_Mode != DET_MODE_ReJson &&
!srcImg.empty() && CheckUtil::RoiInImg(m_outer_roi, srcImg))
{
showimg = srcImg(m_outer_roi).clone();
}
else
{
showimg = img.clone();
}
if (showimg.size() != img.size())
{
showimg = img.clone();
}
cv::drawContours(showimg, m_curPinContours, -1, CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 0, 255)), 1);
if (IsDirectPinMethod())
{
// 新方案: 绘制两排Pin的拟合直线(绿); 缺失位置以黄色十字标记
for (const auto &seg : m_pinFitLines)
{
cv::line(showimg, cv::Point(cvRound(seg.first.x), cvRound(seg.first.y)),
cv::Point(cvRound(seg.second.x), cvRound(seg.second.y)),
CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 0)), 1);
}
for (const auto &mark : m_pinMissMarks)
{
cv::drawMarker(showimg, cv::Point(cvRound(mark.x), cvRound(mark.y)),
CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 255)), cv::MARKER_CROSS, 16, 1);
}
}
else
{
cv::drawContours(showimg, m_tplPinContoursCur, -1, CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 0)), 1);
}
imwrite("pin_contours.png", showimg);
}
return 0;
}
#if 0 // ----- 旧配准匹配方案(Pin_Direct_Match)已停用, 仅保留代码参考; 如需启用需恢复 m_tplPinContoursCur 成员 -----
// 新方案(开关 PIN_USE_DIRECT_MATCH = 1): 不做仿射变换, 直接对模板Pin与检测Pin做配准后对比
// 输入: m_tplPinContours(模板裁剪坐标系) / m_curPinContours(当前检测图坐标系)
// 输出: m_tplPinContoursCur(配准到当前检测图坐标系的模板Pin轮廓) + 缺失/偏移缺陷上报
// 思路:
// 1) 平移投票: 统计所有 "检测pin中心 - 模板pin中心" 的位移, 取出现次数最多的位移作为全局平移,
// 即让最多数量Pin对重合的平移(对缺失pin/误检pin鲁棒, 最大程度一一对应);
// 2) 由近到远贪心做一对一匹配, 并用匹配对的位移中值迭代细化全局平移(抗异常);
// 3) 配不上检测pin的模板pin -> 缺失; 配上但 x/y 偏移超阈值的 -> 偏移。
int ImgCheckAnalysisy::Pin_Direct_Match()
{
// 计算轮廓中心点(外接矩形中心)
auto getCenter = [](const std::vector<cv::Point> &contour) -> cv::Point2f
{
cv::Rect r = cv::boundingRect(contour);
return cv::Point2f(r.x + r.width * 0.5f, r.y + r.height * 0.5f);
};
const int nTpl = static_cast<int>(m_tplPinContours.size());
const int nCur = static_cast<int>(m_curPinContours.size());
m_tplPinContoursCur.clear();
if (nTpl <= 0)
{
return 0;
}
std::vector<cv::Point2f> tplCenters(nTpl);
for (int i = 0; i < nTpl; i++)
{
tplCenters[i] = getCenter(m_tplPinContours[i]);
}
std::vector<cv::Point2f> curCenters(nCur);
for (int i = 0; i < nCur; i++)
{
curCenters[i] = getCenter(m_curPinContours[i]);
}
// 匹配半径: 取模板pin相邻最小间距的一半, 避免相邻pin被错配; pin数不足时用默认值
float minGap = 0.0f;
for (int i = 0; i < nTpl; i++)
{
for (int j = i + 1; j < nTpl; j++)
{
float d = static_cast<float>(cv::norm(tplCenters[i] - tplCenters[j]));
if (minGap <= 0.0f || d < minGap)
{
minGap = d;
}
}
}
float matchRadius = (minGap > 0.0f) ? (minGap * 0.5f) : 20.0f;
if (matchRadius < 2.0f)
{
matchRadius = 2.0f;
}
// 1、平移投票: 统计出现次数最多的 "检测pin - 模板pin" 位移(4px 一档, 取峰值邻域均值细化)
cv::Point2f T(0.0f, 0.0f);
if (nCur > 0)
{
const float binSize = 4.0f;
std::map<std::pair<int, int>, int> hist;
int bestCount = 0;
float bestX = 0.0f;
float bestY = 0.0f;
for (int i = 0; i < nTpl; i++)
{
for (int j = 0; j < nCur; j++)
{
float dx = curCenters[j].x - tplCenters[i].x;
float dy = curCenters[j].y - tplCenters[i].y;
std::pair<int, int> key(static_cast<int>(std::lround(dx / binSize)),
static_cast<int>(std::lround(dy / binSize)));
int cnt = ++hist[key];
float cx = key.first * binSize;
float cy = key.second * binSize;
// 峰值并列时优先取位移更小的(两图裁剪都以产品为中心,真实位移通常较小)
if (cnt > bestCount ||
(cnt == bestCount && cx * cx + cy * cy < bestX * bestX + bestY * bestY))
{
bestCount = cnt;
bestX = cx;
bestY = cy;
}
}
}
// 用峰值邻域(±1档)的所有位移取均值, 得到更精确的全局平移
double sumX = 0.0;
double sumY = 0.0;
int sumN = 0;
for (int i = 0; i < nTpl; i++)
{
for (int j = 0; j < nCur; j++)
{
float dx = curCenters[j].x - tplCenters[i].x;
float dy = curCenters[j].y - tplCenters[i].y;
if (std::fabs(dx - bestX) <= binSize && std::fabs(dy - bestY) <= binSize)
{
sumX += dx;
sumY += dy;
sumN++;
}
}
}
if (sumN > 0)
{
T.x = static_cast<float>(sumX / sumN);
T.y = static_cast<float>(sumY / sumN);
}
else
{
T.x = bestX;
T.y = bestY;
}
}
// 2、由近到远贪心做一对一匹配, 并用匹配对的位移中值迭代细化全局平移
std::vector<int> matchOfTpl(nTpl, -1);
for (int iter = 0; iter < 3; iter++)
{
// 收集满足匹配半径的候选对, 按距离升序
std::vector<std::pair<double, std::pair<int, int>>> cands; // (距离, (模板idx, 检测idx))
cands.reserve(static_cast<size_t>(nTpl) * static_cast<size_t>(nCur));
for (int i = 0; i < nTpl; i++)
{
cv::Point2f pred = tplCenters[i] + T; // 配准后模板pin在当前检测图中的预测位置
for (int j = 0; j < nCur; j++)
{
double d = cv::norm(curCenters[j] - pred);
if (d <= matchRadius)
{
cands.push_back(std::make_pair(d, std::make_pair(i, j)));
}
}
}
std::sort(cands.begin(), cands.end());
std::vector<char> tplUsed(nTpl, 0);
std::vector<char> curUsed(nCur, 0);
matchOfTpl.assign(nTpl, -1);
std::vector<float> resDisX;
std::vector<float> resDisY;
for (size_t k = 0; k < cands.size(); k++)
{
int i = cands[k].second.first;
int j = cands[k].second.second;
if (tplUsed[i] || curUsed[j])
{
continue;
}
tplUsed[i] = 1;
curUsed[j] = 1;
matchOfTpl[i] = j;
resDisX.push_back(curCenters[j].x - (tplCenters[i].x + T.x));
resDisY.push_back(curCenters[j].y - (tplCenters[i].y + T.y));
}
if (resDisX.empty())
{
break;
}
// 用位移中值(抗异常)更新全局平移, 使匹配对最大程度重合
std::sort(resDisX.begin(), resDisX.end());
std::sort(resDisY.begin(), resDisY.end());
float mx = resDisX[resDisX.size() / 2];
float my = resDisY[resDisY.size() / 2];
T.x += mx;
T.y += my;
if (std::fabs(mx) < 0.5f && std::fabs(my) < 0.5f)
{
break;
}
}
// 3、把模板pin轮廓按最终平移映射到当前检测图坐标系, 供匹配/绘制/上报使用
m_tplPinContoursCur.reserve(nTpl);
for (int i = 0; i < nTpl; i++)
{
std::vector<cv::Point> mapped;
mapped.reserve(m_tplPinContours[i].size());
for (const auto &pt : m_tplPinContours[i])
{
mapped.emplace_back(cvRound(pt.x + T.x), cvRound(pt.y + T.y));
}
m_tplPinContoursCur.push_back(mapped);
}
// 4、偏移阈值由基础参数 pin_x_offset / pin_y_offset (mm) 换算为像素
float offsetThX = 0.0f;
float offsetThY = 0.0f;
if (m_pbaseCheckFunction != NULL)
{
if (m_fImgage_Scale_X > 0.0f)
{
offsetThX = m_pbaseCheckFunction->pinDet.pin_x_offset / m_fImgage_Scale_X;
}
if (m_fImgage_Scale_Y > 0.0f)
{
offsetThY = m_pbaseCheckFunction->pinDet.pin_y_offset / m_fImgage_Scale_Y;
}
}
// 上报缺失/偏移缺陷(pin 位置取配准后的模板pin)
auto reportPinError = [&](int pinIdx, const cv::Point2f &tplCenter, const std::string &reason, int nconfig_qx_type)
{
QX_ERROR_INFO_ pinErr;
pinErr.Idx = static_cast<int>(m_pDetResult->pQx_ErrorList->size());
cv::Rect r = cv::boundingRect(m_tplPinContoursCur[pinIdx]);
pinErr.roi = r;
pinErr.area = static_cast<int>(cv::contourArea(m_tplPinContoursCur[pinIdx]));
pinErr.JudgArea = pinErr.area * m_fImgage_Scale_X * m_fImgage_Scale_Y;
int longSide = (r.width > r.height) ? r.width : r.height;
int shortSide = (r.width < r.height) ? r.width : r.height;
pinErr.flen = longSide * m_fImgage_Scale_X;
pinErr.fbreadth = shortSide * m_fImgage_Scale_Y;
pinErr.nconfig_qx_type = nconfig_qx_type;
pinErr.qx_name = CONFIG_QX_NAME_Names[nconfig_qx_type];
pinErr.detRegionidxList.push_back(0);
pinErr.detlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check",
"pin %d center(%d,%d) %s",
pinIdx, static_cast<int>(tplCenter.x), static_cast<int>(tplCenter.y),
reason.c_str());
m_pDetResult->pQx_ErrorList->push_back(pinErr);
};
int nMiss = 0;
int nOffset = 0;
for (int i = 0; i < nTpl; i++)
{
// 配不上检测pin的模板pin -> 缺失
if (matchOfTpl[i] < 0)
{
nMiss++;
reportPinError(i, tplCenters[i] + T, "miss", CONFIG_QX_NAME_PIN_QueShi);
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "pin %d MISS", i);
continue;
}
// x 或 y 方向偏移超过配置阈值 -> 偏移
int j = matchOfTpl[i];
float dx = curCenters[j].x - (tplCenters[i].x + T.x);
float dy = curCenters[j].y - (tplCenters[i].y + T.y);
float absDx = std::fabs(dx);
float absDy = std::fabs(dy);
if (absDx > offsetThX || absDy > offsetThY)
{
nOffset++;
char buf[64];
snprintf(buf, sizeof(buf), "offset dx %.1f dy %.1f", absDx, absDy);
reportPinError(i, tplCenters[i] + T, buf, CONFIG_QX_NAME_PIN_PianYi);
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "pin %d OFFSET dx %.1f dy %.1f", i, absDx, absDy);
}
}
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check",
"direct match: shift(%.1f, %.1f) tpl %d cur %d miss %d offset %d",
T.x, T.y, nTpl, nCur, nMiss, nOffset);
return 0;
}
#endif
// 新方案(Det_Method == "line_fitting"): 不做模板对比, 只针对检测Pin分析
// 1) 方向扫描(0~180°, 步长1°): 取 "两排Pin沿法线投影组内方差之和" 最小的角度作为Pin排方向(对图像旋转不敏感),
// 再沿该法线投影, 在最大间隙处把Pin分成两排(两排插口; Pin不足4个则按一排处理);
// 2) 每排做最小二乘直线拟合(cv::fitLine): pin中心到直线(沿法线方向)距离按成像精度换算为 mm, 超过 pin_y_offset(mm) 判 偏移;
// 3) 每排内沿排方向排序: 相邻两pin中心连线距离按成像精度换算为 mm, 超过 pin_x_offset(mm) 判 两pin中间缺失一个pin。
int ImgCheckAnalysisy::Pin_Direct_Check()
{
// 计算轮廓中心点(外接矩形中心)
auto getCenter = [](const std::vector<cv::Point> &contour) -> cv::Point2f
{
cv::Rect r = cv::boundingRect(contour);
return cv::Point2f(r.x + r.width * 0.5f, r.y + r.height * 0.5f);
};
const int nCur = static_cast<int>(m_curPinContours.size());
m_pinFitLines.clear();
m_pinMissMarks.clear();
if (nCur <= 0)
{
return 0;
}
std::vector<cv::Point2f> curCenters(nCur);
std::vector<cv::Rect> curRects(nCur);
for (int i = 0; i < nCur; i++)
{
curCenters[i] = getCenter(m_curPinContours[i]);
curRects[i] = cv::boundingRect(m_curPinContours[i]);
}
if (nCur < 2)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "direct check: pin %d < 2, skip", nCur);
return 0;
}
// 阈值直接使用 mm 参数(判据按 mm 比较): pin_x_offset -> 相邻pin间距(缺失)阈值; pin_y_offset -> 到拟合直线距离(偏移)阈值; <=0 时不判该项
float offsetThX = 0.0f; // mm
float offsetThY = 0.0f; // mm
if (m_pbaseCheckFunction != NULL)
{
offsetThX = m_pbaseCheckFunction->pinDet.pin_x_offset;
offsetThY = m_pbaseCheckFunction->pinDet.pin_y_offset;
}
// 沿给定方向投影排序, 在最大间隙处切分成两排(order[0..splitK] 一排, order[splitK+1..] 另一排)
// 两排各至少2个pin; pin不足4个时无法可靠分排, 返回 -1(按一排整体处理)
auto splitByNormal = [&](const cv::Point2f &nor, std::vector<float> &proj, std::vector<int> &order) -> int
{
order.resize(nCur);
proj.resize(nCur);
for (int i = 0; i < nCur; i++)
{
order[i] = i;
proj[i] = curCenters[i].dot(nor);
}
std::sort(order.begin(), order.end(), [&](int a, int b) { return proj[a] < proj[b]; });
int splitK = -1;
if (nCur >= 4)
{
float bestGap = -1.0f;
for (int k = 1; k <= nCur - 3; k++)
{
float gap = proj[order[k + 1]] - proj[order[k]];
if (gap > bestGap)
{
bestGap = gap;
splitK = k;
}
}
}
return splitK;
};
// 组内投影方差之和: 正确排方向上两排pin各自近似共线, 沿法线投影最集中, 该值最小
auto scoreOfRowDir = [&](const std::vector<float> &proj, const std::vector<int> &order, int splitK) -> double
{
double score = 0.0;
const int nGroup = (splitK >= 0) ? 2 : 1;
for (int g = 0; g < nGroup; g++)
{
int b = (g == 0) ? 0 : (splitK + 1);
int e = (g == 0) ? (splitK + 1) : nCur;
double m = 0.0;
for (int i = b; i < e; i++)
{
m += proj[order[i]];
}
m /= (e - b);
for (int i = b; i < e; i++)
{
double d = proj[order[i]] - m;
score += d * d;
}
}
return score;
};
// 1、方向扫描 0~180°(1°一档), 找到让"两排沿法线投影组内方差之和"最小的排方向(考虑图像旋转)
float bestTheta = 0.0f;
if (nCur >= 4)
{
double bestScore = -1.0;
std::vector<float> proj;
std::vector<int> order;
for (int deg = 0; deg < 180; deg++)
{
float th = static_cast<float>(deg * CV_PI / 180.0);
cv::Point2f nor(-std::sin(th), std::cos(th));
int splitK = splitByNormal(nor, proj, order);
double score = scoreOfRowDir(proj, order, splitK);
if (bestScore < 0.0 || score < bestScore)
{
bestScore = score;
bestTheta = th;
}
}
}
else
{
// pin 太少无法分排, 用PCA主方向兜底(按一排处理)
cv::Point2f meanPt(0.0f, 0.0f);
for (int i = 0; i < nCur; i++)
{
meanPt += curCenters[i];
}
meanPt.x /= nCur;
meanPt.y /= nCur;
double sxx = 0.0, sxy = 0.0, syy = 0.0;
for (int i = 0; i < nCur; i++)
{
double dx = curCenters[i].x - meanPt.x;
double dy = curCenters[i].y - meanPt.y;
sxx += dx * dx;
sxy += dx * dy;
syy += dy * dy;
}
bestTheta = static_cast<float>(0.5 * std::atan2(2.0 * sxy, sxx - syy));
}
// 用最优排方向重新投影, 把Pin分成两排
cv::Point2f dirRow(static_cast<float>(std::cos(bestTheta)), static_cast<float>(std::sin(bestTheta)));
cv::Point2f dirNor(-dirRow.y, dirRow.x);
std::vector<float> proj;
std::vector<int> order;
int splitK = splitByNormal(dirNor, proj, order);
std::vector<std::vector<int>> rows;
if (splitK >= 0)
{
rows.push_back(std::vector<int>(order.begin(), order.begin() + splitK + 1));
rows.push_back(std::vector<int>(order.begin() + splitK + 1, order.end()));
}
else
{
rows.push_back(order);
}
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check",
"row dir %.1f deg rows %d th[gap %.2f line %.2f] mm",
bestTheta * 180.0f / static_cast<float>(CV_PI),
static_cast<int>(rows.size()), offsetThX, offsetThY);
// 上报缺陷(roi/area 由调用处给出; grayDis 记录对应度量: 到直线距离 / 相邻间距, 单位mm)
auto reportPinError = [&](const cv::Rect &roi, int area, double grayDis, int nconfig_qx_type, const std::string &detail)
{
QX_ERROR_INFO_ pinErr;
pinErr.Idx = static_cast<int>(m_pDetResult->pQx_ErrorList->size());
pinErr.roi = roi;
pinErr.area = area;
pinErr.JudgArea = pinErr.area * m_fImgage_Scale_X * m_fImgage_Scale_Y;
int longSide = (roi.width > roi.height) ? roi.width : roi.height;
int shortSide = (roi.width < roi.height) ? roi.width : roi.height;
pinErr.flen = longSide * m_fImgage_Scale_X;
pinErr.fbreadth = shortSide * m_fImgage_Scale_Y;
pinErr.nconfig_qx_type = nconfig_qx_type;
pinErr.qx_name = CONFIG_QX_NAME_Names[nconfig_qx_type];
pinErr.grayDis = grayDis;
pinErr.detRegionidxList.push_back(0);
pinErr.detlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check", "%s", detail.c_str());
m_pDetResult->pQx_ErrorList->push_back(pinErr);
};
int nOffset = 0;
int nMiss = 0;
// 2、3、逐排: 拟合直线 -> 判偏移; 沿排排序 -> 判相邻缺失
for (size_t r = 0; r < rows.size(); r++)
{
const std::vector<int> &rowIdx = rows[r];
const int nRow = static_cast<int>(rowIdx.size());
if (nRow < 2)
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check",
"row %d pin num %d < 2, skip", static_cast<int>(r), nRow);
continue;
}
std::vector<cv::Point2f> rowPts(nRow);
for (int i = 0; i < nRow; i++)
{
rowPts[i] = curCenters[rowIdx[i]];
}
// 最小二乘直线拟合: lineDir 单位方向向量, lineP0 直线上一点, lineNor 单位法线
cv::Vec4f linePara;
cv::fitLine(rowPts, linePara, cv::DIST_L2, 0, 0.01, 0.01);
cv::Point2f lineDir(linePara[0], linePara[1]);
cv::Point2f lineP0(linePara[2], linePara[3]);
cv::Point2f lineNor(-lineDir.y, lineDir.x);
// 记录拟合线段(沿排方向延伸到该排两端, 供绘制/回溯)
float tMin = 0.0f;
float tMax = 0.0f;
for (int i = 0; i < nRow; i++)
{
float t = (rowPts[i] - lineP0).dot(lineDir);
if (i == 0 || t < tMin)
{
tMin = t;
}
if (i == 0 || t > tMax)
{
tMax = t;
}
}
m_pinFitLines.push_back(std::make_pair(lineP0 + lineDir * tMin, lineP0 + lineDir * tMax));
// 2、pin中心到直线(沿法线方向)距离超过 pin_y_offset -> 偏移;距离按成像精度换算为 mm (mm = px * 成像精度)
if (offsetThY > 0.0f)
{
for (int i = 0; i < nRow; i++)
{
// 垂直偏移向量(px)的分量分别换算为 mm 后求模
float distPx = (rowPts[i] - lineP0).dot(lineNor);
float dx_mm = lineNor.x * distPx * m_fImgage_Scale_X;
float dy_mm = lineNor.y * distPx * m_fImgage_Scale_Y;
float absDist = std::sqrt(dx_mm * dx_mm + dy_mm * dy_mm);
int idx = rowIdx[i];
bool bOffset = (absDist > offsetThY);
if (bOffset)
{
nOffset++;
char buf[96];
snprintf(buf, sizeof(buf), "pin %d offset to row line %.2f mm > %.2f mm", idx, absDist, offsetThY);
reportPinError(curRects[idx], static_cast<int>(cv::contourArea(m_curPinContours[idx])),
absDist, CONFIG_QX_NAME_PIN_PianYi, buf);
}
// 每个pin脚都打印到拟合直线的距离(mm),方便调整 pin_y_offset 参数
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "row %d pin %d %s line %.2f mm", static_cast<int>(r), idx, bOffset ? "OFFSET" : "OK", absDist);
}
}
// 3、沿排方向排序, 相邻两pin中心连线距离超过 pin_x_offset -> 两pin中间缺失一个pin
if (offsetThX > 0.0f)
{
std::vector<std::pair<float, int>> tOrder;
tOrder.reserve(nRow);
for (int i = 0; i < nRow; i++)
{
tOrder.push_back(std::make_pair((rowPts[i] - lineP0).dot(lineDir), i));
}
std::sort(tOrder.begin(), tOrder.end());
for (int i = 0; i + 1 < nRow; i++)
{
int ia = tOrder[i].second;
int ib = tOrder[i + 1].second;
// 相邻pin中心连线距离(px)的分量分别换算为 mm 后求模
cv::Point2f dv = rowPts[ib] - rowPts[ia];
float dx_mm = dv.x * m_fImgage_Scale_X;
float dy_mm = dv.y * m_fImgage_Scale_Y;
float gap = std::sqrt(dx_mm * dx_mm + dy_mm * dy_mm);
int idxa = rowIdx[ia];
int idxb = rowIdx[ib];
bool bMiss = (gap > offsetThX);
if (bMiss)
{
cv::Point2f mid = (rowPts[ia] + rowPts[ib]) * 0.5f;
m_pinMissMarks.push_back(mid);
// 缺失位置按两个相邻pin的平均尺寸上报
cv::Rect roiAvg = curRects[idxa];
roiAvg.width = (curRects[idxa].width + curRects[idxb].width + 1) / 2;
roiAvg.height = (curRects[idxa].height + curRects[idxb].height + 1) / 2;
cv::Rect roiMiss(cvRound(mid.x - roiAvg.width * 0.5f), cvRound(mid.y - roiAvg.height * 0.5f),
roiAvg.width, roiAvg.height);
int areaAvg = (static_cast<int>(cv::contourArea(m_curPinContours[idxa])) +
static_cast<int>(cv::contourArea(m_curPinContours[idxb]))) / 2;
nMiss++;
char buf[96];
snprintf(buf, sizeof(buf), "miss between pin %d-%d gap %.2f mm > %.2f mm", idxa, idxb, gap, offsetThX);
reportPinError(roiMiss, areaAvg, gap, CONFIG_QX_NAME_PIN_QueShi, buf);
}
// 每对相邻pin都打印间距(mm),方便调整 pin_x_offset 参数
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "row %d pin %d-%d %s gap %.2f mm", static_cast<int>(r), idxa, idxb, bMiss ? "MISS" : "OK", gap);
}
}
}
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check",
"direct check: pin %d rows %d miss %d offset %d",
nCur, static_cast<int>(rows.size()), nMiss, nOffset);
return 0;
}
// 获取指定 残点(blob) 是由哪个(哪些)检测模型检出的
// 做法:对各模型自己输出的全图 mask,在 blob 的 roi 内统计非零像素;
// 非零则说明该模型在该残点区域有检出。多个模型都检出时用 "+" 连接。
std::string ImgCheckAnalysisy::GetBlobModelName(const cv::Rect &roi)
{
std::string strModelName = "";
if (!m_pImageAllResult)
{
return AI_DET_MODEL_NAME_UnKnown;
}
for (auto &pModelMask : m_pImageAllResult->AI_Model_MaskList)
{
if (!pModelMask || pModelMask->ModelMask.empty())
{
continue;
}
cv::Rect imgRoi = cv::Rect(0, 0, pModelMask->ModelMask.cols, pModelMask->ModelMask.rows);
cv::Rect cutRoi = roi & imgRoi;
if (cutRoi.width <= 0 || cutRoi.height <= 0)
{
continue;
}
cv::Mat roiMask = pModelMask->ModelMask(cutRoi);
if (cv::countNonZero(roiMask) > 0)
{
if (strModelName.empty())
{
strModelName = pModelMask->strModelName;
}
else
{
strModelName += AI_DET_MODEL_NAME_JOIN + pModelMask->strModelName;
}
}
}
if (strModelName.empty())
{
strModelName = AI_DET_MODEL_NAME_UnKnown;
}
return strModelName;
}
int ImgCheckAnalysisy::BLobToDetResult()
{
long t1 = CheckUtil::getcurTime();
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "BLobToDetResult", " Start old qx num %ld", m_pDetResult->pQx_ErrorList->size());
// 遍历每个检测blob
for (int i = 0; i < blobs.blobCount; i++)
{
cv::Rect roi;
roi.x = blobs.blobTab[i].minx;
roi.y = blobs.blobTab[i].miny;
roi.width = blobs.blobTab[i].maxx - blobs.blobTab[i].minx + 1;
roi.height = blobs.blobTab[i].maxy - blobs.blobTab[i].miny + 1;
int config_qx_type = blobs.blobTab[i].AIclasstype;
if (config_qx_type < 0 || config_qx_type >= CONFIG_QX_NAME_count)
{
// AI 分类无对应缺陷类型(无效类别)时,不参与缺陷结果分析
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "BLobToDetResult",
"blob idx %d invalid AIclasstype %d skip", i, config_qx_type);
continue;
}
float JudgArea = blobs.blobTab[i].JudgArea;
float fsecondArea = JudgArea;
std::string qx_name = CONFIG_QX_NAME_Names[config_qx_type];
// 追溯该残点(blob) 是由哪个检测模型检出的
std::string strModelName = GetBlobModelName(roi);
if (true)
{
QX_ERROR_INFO_ temerror;
temerror.roi = roi;
temerror.Idx = m_pDetResult->pQx_ErrorList->size();
temerror.area = blobs.blobTab[i].area;
temerror.JudgArea = JudgArea;
temerror.JudgArea_second = fsecondArea;
temerror.energy = blobs.blobTab[i].energy;
temerror.flen = blobs.blobTab[i].len;
temerror.fbreadth = blobs.blobTab[i].breadth;
temerror.nconfig_qx_type = config_qx_type;
temerror.qx_name = qx_name;
temerror.strModelName = strModelName;
temerror.maxValue = blobs.blobTab[i].maxValue;
temerror.grayDis = blobs.blobTab[i].grayDis;
temerror.density = blobs.blobTab[i].density;
temerror.fUpIou = 0;
temerror.whiteOrBlack = blobs.blobTab[i].whiteOrblack;
{
cv::Point pCenter;
pCenter.x = roi.x + roi.width * 0.5;
pCenter.y = roi.y + roi.height * 0.5;
int nmaxregionIdx = 0;
for (int iregion = 0; iregion < m_DetRoiList.roiList_Src.size(); iregion++)
{
const std::vector<cv::Point> &polygon = m_DetRoiList.roiList_Src[iregion];
double result = cv::pointPolygonTest(polygon, pCenter, false);
if (result < 0)
{
continue;
}
nmaxregionIdx = iregion;
}
temerror.detRegionidxList.push_back(nmaxregionIdx);
}
// {
// cv::Point pCenter;
// pCenter.x = roi.x + roi.width * 0.5;
// pCenter.y = roi.y + roi.height * 0.5;
// for (int iregion = 0; iregion < m_DetRoiList.roiList_Src.size(); iregion++)
// {
// const std::vector<cv::Point> &polygon = m_DetRoiList.roiList_Src[iregion];
// double result = cv::pointPolygonTest(polygon, pCenter, false);
// if (result < 0)
// {
// continue;
// }
// temerror.detRegionidxList.push_back(iregion);
// }
// if (temerror.detRegionidxList.size() <= 0)
// {
// temerror.detRegionidxList.push_back(0);
// }
// }
m_pDetResult->pQx_ErrorList->push_back(temerror);
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "BLobToDetResult",
"blob idx %d Qx Idx %d qx %d %s detModel %s roi[%d %d %d %d]",
i, temerror.Idx, config_qx_type, qx_name.c_str(), strModelName.c_str(),
roi.x, roi.y, roi.width, roi.height);
// printf("- %s idx %d a %f v %d h %f l %f\n", DetImgInfo_shareP->strChannel.c_str(), i, JudgArea, blobs.blobTab[i].maxValue, blobs.blobTab[i].grayDis, flen);
}
}
long t2 = CheckUtil::getcurTime();
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "BLobToDetResult", " end qx num %ld use time %ld", m_pDetResult->pQx_ErrorList->size(), t2 - t1);
return 0;
}
int ImgCheckAnalysisy::AI_Edge(const cv::Mat &img, cv::RotatedRect &outerRoi, cv::RotatedRect &innerRoi)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "AI_Edge", "-------------------start--------------");
AI_Edge_Algin::DetConfig config;
if (DetImgInfo_shareP->bsaveProcessImg)
{
config.bSaveResultImg = true;
}
config.strChannel = DetImgInfo_shareP->strChannel;
config.strProductID = DetImgInfo_shareP->strImgProductID;
config.pBaseCheckFunction = m_pbaseCheckFunction;
int re = m_pAI_Edge_Algin.Detect(img, &config, m_pEdge_Align_Result);
if (re != 0)
{
printf("AI_Edge Is Error = %d \n", re);
}
outerRoi = m_pEdge_Align_Result->bigroi;
innerRoi = m_pEdge_Align_Result->smallroi;
// getchar();
return re;
}
int ImgCheckAnalysisy::CalProductSize()
{
float fw = m_outer_roi.width * m_fImgage_Scale_X;
float fh = m_outer_roi.height * m_fImgage_Scale_Y;
fw = std::ceil(fw * 10) / 10;
fh = std::ceil(fh * 10) / 10;
m_CheckResult_shareP->productWidht_mm = fw;
m_CheckResult_shareP->productHeight_mm = fh;
m_pdetlog->AddCheckstr(PrintLevel_1, "Det ROI", " [w %d,h %d] (piexl) -> [w %f h %f](mm),scale %f %f",
m_outer_roi.width, m_outer_roi.height, fw, fh, m_fImgage_Scale_X, m_fImgage_Scale_Y);
return 0;
}
int ImgCheckAnalysisy::ImgPreDet()
{
// 计算产品尺寸
CalProductSize();
if (m_pbaseCheckFunction->saveImg.bSaveClsImg)
{
creatsavedir();
}
// _RE_CutImg 裁剪图保持传图原图通道(彩色原图 -> 彩色裁剪图),不再直接使用灰度检测图;
// ReJson 复测模式的输入本身就是裁剪图,以及 ROI 越界等异常情况,退回检测图
{
const cv::Mat &srcImg = m_CheckResult_shareP->in_shareImage->img;
if (m_CheckResult_shareP->in_shareImage->Det_Mode != DET_MODE_ReJson &&
!srcImg.empty() && CheckUtil::RoiInImg(m_outer_roi, srcImg))
{
m_CheckResult_shareP->cutSrcimg = srcImg(m_outer_roi).clone();
}
else
{
m_CheckResult_shareP->cutSrcimg = m_pImageAllResult->detImg;
}
}
ResizeImg();
m_pImageAllResult->fscale_detToresult_x = m_pImageAllResult->resultImg.cols * 1.0f / m_pImageAllResult->detImg.cols;
m_pImageAllResult->fscale_detToresult_y = m_pImageAllResult->resultImg.rows * 1.0f / m_pImageAllResult->detImg.rows;
return 0;
}