|
|
/*
|
|
|
* @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 ®ion : 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 ®ion : 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;
|
|
|
}
|