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

2980 lines
94 KiB

This file contains ambiguous Unicode characters!

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

/*
* @Author: your name
* @Date: 2022-04-20 15:50:00
* @LastEditTime: 2025-09-23 11:34:53
* @LastEditors: xiewenji 527774126@qq.com
* @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
* @FilePath: /ZCXD_MonitorPlatform/src/CoreLogicModule/src/CamDeal.cpp
*/
#include "ImgCheckAnalysisy.hpp"
#include "CheckUtil.hpp"
#include "Define.h"
#include <omp.h>
#include "AICommonDefine.h"
#include "AlignShare.h"
#include <algorithm>
#include <cctype>
// 把手判定参数:
// 每个非空的"把手区域"附近都应该有一个把手:凸起宽高与该区域外接矩形宽高的比值落在下面范围内才算匹配
#define HANDLE_SIZE_MIN_RATIO 0.8f
#define HANDLE_SIZE_MAX_RATIO 1.2f
// "附近"的搜索范围:把手区域外接矩形在宽/高方向各外扩该比例
#define HANDLE_NEAR_EXPAND_RATIO 5
// 把手缺失检测开关见 Function_Support_Det::bDetHandle(CheckConfigDefine.h)
// 用于排序轮廓的比较函数
static bool compareContourAreas(const vector<Point> &contour1, const vector<Point> &contour2)
{
double i = contourArea(contour1);
double j = contourArea(contour2);
return (i > j);
}
// 2x3 仿射矩阵小工具:变换点、矩形外接、组合矩阵(检测图裁剪坐标映射用)
static cv::Point2f MatAffinePoint(const cv::Mat &mat32, const cv::Point2f &pt)
{
if (mat32.empty() || mat32.rows != 2 || mat32.cols != 3)
{
return pt;
}
const double *m = mat32.ptr<double>(0);
return cv::Point2f((float)(m[0] * pt.x + m[1] * pt.y + m[2]),
(float)(m[3] * pt.x + m[4] * pt.y + m[5]));
}
// 矩形四角经仿射变换后取外接矩形
static cv::Rect MatAffineRect(const cv::Mat &mat32, const cv::Rect &roi)
{
std::vector<cv::Point2f> pts(4);
pts[0] = MatAffinePoint(mat32, cv::Point2f((float)roi.x, (float)roi.y));
pts[1] = MatAffinePoint(mat32, cv::Point2f((float)(roi.x + roi.width), (float)roi.y));
pts[2] = MatAffinePoint(mat32, cv::Point2f((float)(roi.x + roi.width), (float)(roi.y + roi.height)));
pts[3] = MatAffinePoint(mat32, cv::Point2f((float)roi.x, (float)(roi.y + roi.height)));
float fminX = pts[0].x, fmaxX = pts[0].x, fminY = pts[0].y, fmaxY = pts[0].y;
for (int i = 1; i < 4; i++)
{
if (pts[i].x < fminX)
fminX = pts[i].x;
if (pts[i].x > fmaxX)
fmaxX = pts[i].x;
if (pts[i].y < fminY)
fminY = pts[i].y;
if (pts[i].y > fmaxY)
fmaxY = pts[i].y;
}
int x0 = cvFloor(fminX);
int y0 = cvFloor(fminY);
int x1 = cvCeil(fmaxX);
int y1 = cvCeil(fmaxY);
return cv::Rect(x0, y0, x1 - x0, y1 - y0);
}
// 组合仿射:先应用 mB 再应用 mA,即 mA ∘ mB,结果仍为 2x3
static cv::Mat MatAffineCompose(const cv::Mat &mA, const cv::Mat &mB)
{
if (mA.empty() || mA.rows != 2 || mA.cols != 3)
{
return mB.clone();
}
if (mB.empty() || mB.rows != 2 || mB.cols != 3)
{
return mA.clone();
}
cv::Mat hA = cv::Mat::eye(3, 3, CV_64F);
cv::Mat hB = cv::Mat::eye(3, 3, CV_64F);
mA.copyTo(hA(cv::Rect(0, 0, 3, 2)));
mB.copyTo(hB(cv::Rect(0, 0, 3, 2)));
cv::Mat hC = hA * hB;
return hC(cv::Rect(0, 0, 3, 2)).clone();
}
ImgCheckAnalysisy::ImgCheckAnalysisy()
: m_pAI_Edge_Algin(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_TA_cls = "/home/aidlux/BOE/Cls/TA/";
m_strRootPath_CA_cls = "/home/aidlux/BOE/Cls/CA/";
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.7.91");
}
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_TA_cls += m_strLastDate + "/";
m_strRootPath_CA_cls += m_strLastDate + "/";
for (int i = 0; i < AI_CLass_QX_NAME_count; i++)
{
CheckUtil::CreateDir(m_strRootPath_TA_cls + std::to_string(i) + "/");
CheckUtil::CreateDir(m_strRootPath_CA_cls + std::to_string(i) + "/");
}
return 0;
}
void ImgCheckAnalysisy::SetTplInfo(const cv::RotatedRect &outerRect)
{
m_tplOuterRect = outerRect;
}
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);
}
int ImgCheckAnalysisy::Adapt_Config(RotatedRect tplOuterRect, RotatedRect curOuterRect, Mat& det_img){
vector<cv::Point2f> cur_vertices = CheckUtil::sort_vertices(curOuterRect);
vector<cv::Point2f> tpl_vertices = CheckUtil::sort_vertices(tplOuterRect);
cv::Point2f src_pts[3] = {
cur_vertices[0], // 对应左上
cur_vertices[1], // 对应右上
cur_vertices[2] // 对应左下
};
cv::Point2f dst_pts[3] = {
tpl_vertices[0], // 左上
tpl_vertices[1], // 右上
tpl_vertices[2] // 左下
};
cv::Mat affine_mat = cv::getAffineTransform(src_pts, dst_pts);
// 仿射变换
cv::warpAffine(det_img, det_img, affine_mat, det_img.size());
// 将m_outer_rroi和m_inner_rroi变换到模板图像坐标系下
auto transformRotatedRect = [&affine_mat](cv::RotatedRect &rrect)
{
const double *M = affine_mat.ptr<double>(0);
cv::Point2f pts[4];
rrect.points(pts);
std::vector<cv::Point2f> transformed_pts(4);
for (int i = 0; i < 4; i++)
{
float x = static_cast<float>(M[0] * pts[i].x + M[1] * pts[i].y + M[2]);
float y = static_cast<float>(M[3] * pts[i].x + M[4] * pts[i].y + M[5]);
transformed_pts[i] = cv::Point2f(x, y);
}
rrect = cv::minAreaRect(transformed_pts);
};
transformRotatedRect(m_outer_rroi);
transformRotatedRect(m_inner_rroi);
return 0;
}
// 暗场复用明场定位:把字符串里的 "dark"(大小写不敏感)替换为 "bright";未命中返回空串
static std::string ReplaceDarkWithBright(const std::string &strIn)
{
const std::string strKey = ALIGN_REUSE_FOLLOWER_KEY; // "dark"
const std::string strSrc = ALIGN_REUSE_SOURCE_KEY; // "bright"
if (strIn.size() < strKey.size())
{
return "";
}
for (size_t i = 0; i + strKey.size() <= strIn.size(); i++)
{
size_t k = 0;
for (; k < strKey.size(); k++)
{
if (::tolower((unsigned char)strIn[i + k]) != (unsigned char)strKey[k])
{
break;
}
}
if (k == strKey.size())
{
std::string strOut = strIn;
strOut.replace(i, strKey.size(), strSrc);
return strOut;
}
}
return "";
}
// ===== 暗场复用明场定位结果(ALIGN_REUSE_* 配置) =====
// 取当前图的产品ID/相机名:优先用 ImageAllResult 关联的基础结果,兕底用送图信息
void ImgCheckAnalysisy::GetProductCameraStr(std::string &strProduct, std::string &strCamera)
{
strProduct = "";
strCamera = "";
if (m_pImageAllResult)
{
if (m_pImageAllResult->productBaseResult)
{
strProduct = m_pImageAllResult->productBaseResult->strproductName;
}
if (m_pImageAllResult->cameraBaseResult)
{
strCamera = m_pImageAllResult->cameraBaseResult->strCameraName;
}
}
if (m_CheckResult_shareP && m_CheckResult_shareP->in_shareImage)
{
if (strProduct.empty())
{
strProduct = m_CheckResult_shareP->in_shareImage->strImgProductID;
}
if (strCamera.empty())
{
strCamera = m_CheckResult_shareP->in_shareImage->strCameraName;
}
}
}
// 发布本图定位结果(成功)或失败标记(供同产品其它相机复用)
void ImgCheckAnalysisy::PublishAlignShare(bool bSucc)
{
std::string strProduct;
std::string strCamera;
GetProductCameraStr(strProduct, strCamera);
if (strProduct.empty() || strCamera.empty())
{
return;
}
if (bSucc)
{
if (!m_pEdge_Align_Result)
{
return;
}
cv::Size sz(0, 0);
if (m_CheckResult_shareP && m_CheckResult_shareP->in_shareImage)
{
sz = m_CheckResult_shareP->in_shareImage->img.size();
}
AlignShare::GetInstance().PublishSuccess(strProduct, strCamera, m_strCurDetChannel, m_pEdge_Align_Result, sz);
}
else
{
AlignShare::GetInstance().PublishFailed(strProduct, strCamera, m_strCurDetChannel);
}
}
// 暗场相机不做定位模型:等待并复用明场相机的定位结果
// 返回 true 时:m_pEdge_Align_Result / m_outer_rroi / m_inner_rroi 已用明场结果填充(标签检测独立执行)
// 返回 false:非复用相机,或复用失败(超时/源失败/无源图/尺寸不一致),调用方走自有定位
bool ImgCheckAnalysisy::TryReuseAlignFromSource(std::string &strSrcCamera, std::string &strSrcChannel, int &nWaitMs)
{
nWaitMs = -1;
#if !ALIGN_REUSE_ENABLE
return false;
#else
std::string strProduct;
std::string strCamera;
GetProductCameraStr(strProduct, strCamera);
// 是否复用由相机名决定(明/暗场是相机属性):相机名必须能按 dark->bright 替换
std::string strSrcCam = ReplaceDarkWithBright(strCamera);
if (strSrcCam.empty())
{
return false;
}
// 通道名可能不带 dark/bright(同一面的明/暗场相机共用一个通道名),此时沿用本相机通道名;
// 若带 dark 则同样替换,保证与源相机发布结果时的 key 一致
std::string strSrcChn = ReplaceDarkWithBright(m_strCurDetChannel);
if (strSrcChn.empty())
{
strSrcChn = m_strCurDetChannel;
}
strSrcCamera = strSrcCam;
strSrcChannel = strSrcChn;
if (!m_pImageAllResult || !m_pImageAllResult->productBaseResult || !m_pImageAllResult->productBaseResult->alignPushState)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Align reuse", "reuse from %s|%s failed(no push state), do align by self", strSrcCam.c_str(), strSrcChn.c_str());
return false;
}
m_pdetlog->AddCheckstr(PrintLevel_0, "Align reuse", "wait %s|%s (timeout %d ms)", strSrcCam.c_str(), strSrcChn.c_str(), ALIGN_REUSE_WAIT_TIMEOUT_MS);
AlignShare &alignShare = AlignShare::GetInstance();
std::shared_ptr<AI_Edge_Algin::Edge_AI_Result> pSrc;
cv::Size srcSize;
const long t1 = CheckUtil::getcurTime();
bool bGot = false;
bool bSucc = false;
std::string strFailReason = "timeout";
while (true)
{
if (alignShare.TryGet(strProduct, strSrcCam, strSrcChn, bSucc, pSrc, srcSize))
{
bGot = true;
if (!bSucc)
{
strFailReason = "source align failed";
}
break;
}
// 源图不会再来了(送图已结束,且源相机没有图):立即回退,不干等超时
if (!m_pImageAllResult->productBaseResult->alignPushState->MayStillCome(strSrcCam))
{
strFailReason = "no source image";
break;
}
if (CheckUtil::getcurTime() - t1 > ALIGN_REUSE_WAIT_TIMEOUT_MS)
{
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(ALIGN_REUSE_POLL_MS));
}
nWaitMs = (int)(CheckUtil::getcurTime() - t1);
if (!bGot || !bSucc)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Align reuse", "reuse from %s|%s failed(wait %d ms, %s), do align by self",
strSrcCam.c_str(), strSrcChn.c_str(), nWaitMs, strFailReason.c_str());
return false;
}
if (!pSrc || pSrc->bigroi.empty())
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Align reuse", "reuse from %s|%s failed(wait %d ms, src roi empty), do align by self",
strSrcCam.c_str(), strSrcChn.c_str(), nWaitMs);
return false;
}
// 图像尺寸必须与源图一致,否则源坐标系不可用
if (srcSize.width > 0 && srcSize.height > 0 && m_CheckResult_shareP && m_CheckResult_shareP->in_shareImage)
{
const cv::Mat &img = m_CheckResult_shareP->in_shareImage->img;
if (img.cols != srcSize.width || img.rows != srcSize.height)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Align reuse", "reuse from %s|%s failed, size %dx%d != cur %dx%d, do align by self",
strSrcCam.c_str(), strSrcChn.c_str(), srcSize.width, srcSize.height, img.cols, img.rows);
return false;
}
}
// 复用明场定位结果:ROI/掩码/映射都用明场的(标签检测由 CheckTagByBigMask 独立执行,不用明场的标签)
m_pEdge_Align_Result = pSrc;
m_outer_rroi = pSrc->bigroi[0];
if (!pSrc->smallroi.empty())
{
m_inner_rroi = pSrc->smallroi[0];
}
return true;
#endif
}
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_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);
PublishAlignShare(false); // 本图不会产出定位结果,通知可能的等待方(暗场复用)
m_nErrorCode = rec;
m_nCheckResultErrorCode = m_nErrorCode;
return m_nErrorCode;
}
long time_edge_s = CheckUtil::getcurTime();
m_pFuntion = GetChannelFuntion(m_strCurDetChannel);
int nfunction = 0;
if (m_pFuntion != NULL)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Detect function", "%s",
m_pFuntion->GetInfo("").c_str());
}
else
{
nfunction = 1;
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "m_pFuntion is NULL");
PublishAlignShare(false); // 本图不会产出定位结果,通知可能的等待方(暗场复用)
m_nErrorCode = 22;
m_nCheckResultErrorCode = m_nErrorCode;
return m_nErrorCode;
}
/*AI 边缘定位(内外边缘)*/
// 暗场相机不做定位模型,等待复用明场相机的定位结果(见 ALIGN_REUSE_* 配置);
// 非复用相机、或复用失败(源超时/源失败/无源图/尺寸不一致)时,仍走原有定位流程
int reedge = 0;
int nAlignReuseWaitMs = -1;
std::string strAlignSrcCamera;
std::string strAlignSrcChannel;
long time_edge_e;
if (TryReuseAlignFromSource(strAlignSrcCamera, strAlignSrcChannel, nAlignReuseWaitMs))
{
// 复用成功:m_pEdge_Align_Result / m_outer_rroi / m_inner_rroi 已用明场结果填充(标签检测独立执行,不复用明场标签)
PublishAlignShare(true);
time_edge_e = CheckUtil::getcurTime();
m_pdetlog->AddCheckstr(PrintLevel_0, "2、pre detect",
"-------------------AI_Edge reuse from %s|%s--wait %d ms----succ---time %ld----\n",
strAlignSrcCamera.c_str(), strAlignSrcChannel.c_str(), nAlignReuseWaitMs, time_edge_e - time_edge_s);
// 定位模型不跑,但把手检测仍要执行:暗场图像 + 明场 bigmask
{
long handle_s = CheckUtil::getcurTime();
CheckHandleByBigMask(m_pEdge_Align_Result->bigmask);
long handle_e = CheckUtil::getcurTime();
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "handle detect use time %ld", handle_e - handle_s);
}
// 标签检测(独立步骤):暗场图像 + 明场 bigmask(不再沿用明场检出的标签);bDetTag 关闭时跳过
{
if (m_pFuntion->function.f_supportDet.bDetTag)
{
long tag_s = CheckUtil::getcurTime();
int reTag = CheckTagByBigMask(m_CheckResult_shareP->in_shareImage->img, m_pEdge_Align_Result->bigmask);
long tag_e = CheckUtil::getcurTime();
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Tag", "tag detect use time %ld", tag_e - tag_s);
if (reTag != 0)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "CheckTagByBigMask is error type = %d", reTag);
m_nErrorCode = reTag;
m_nCheckResultErrorCode = m_nErrorCode;
return m_nErrorCode;
}
}
else
{
m_tag_roiList.clear();
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Tag", "bDetTag close, skip tag detect");
}
}
}
else
{
reedge = AI_Edge(m_CheckResult_shareP->in_shareImage->img, m_outer_rroi, m_inner_rroi, m_tag_roiList);
if (reedge != 0)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "AI_Edge is error type = %d", reedge);
PublishAlignShare(false); // 定位失败:通知可能的等待方(暗场复用),让其回退自行定位
m_nErrorCode = reedge;
m_nCheckResultErrorCode = m_nErrorCode;
return m_nErrorCode;
}
PublishAlignShare(true);
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);
}
/*生成检测图:用 m_outer_rroi(旋转矩形)的外接区域直接从原图裁剪(不再旋转摆正,省掉旋转重采样耗时);
原图坐标 -> 检测图坐标 的映射为纯平移,存入 m_matSrc2Det / m_matDet2Src,后续坐标换算仍走这对矩阵*/
cv::Mat image = m_CheckResult_shareP->in_shareImage->img;
cv::Rect imgRect(0, 0, image.cols, image.rows);
// 1、检测图裁剪区域 = 旋转矩形外接区域(与原图求交,防越界)
cv::Rect padRoi = m_outer_rroi.boundingRect() & imgRect;
if (padRoi.width <= 0 || padRoi.height <= 0)
{
m_pdetlog->AddCheckstr(PrintLevel_1, "Error", "crop roi error [%d %d %d %d]", padRoi.x, padRoi.y, padRoi.width, padRoi.height);
m_nErrorCode = CHECK_ERROR_CheckImg_Empty;
m_nCheckResultErrorCode = m_nErrorCode;
return m_nErrorCode;
}
int detW = padRoi.width;
int detH = padRoi.height;
// 2、原图 -> 检测图 为纯平移(无旋转):检测图坐标 = 原图坐标 - 裁剪原点
m_matSrc2Det = (cv::Mat_<double>(2, 3) << 1, 0, -(double)padRoi.x, 0, 1, -(double)padRoi.y);
m_matDet2Src = (cv::Mat_<double>(2, 3) << 1, 0, (double)padRoi.x, 0, 1, (double)padRoi.y);
// 3、检测图:单通道灰度(彩色原图按 BGR 顺序转灰度,与 imread(path,0) 的亮度一致),直接裁剪;
// 彩色原图时再输出一张同样裁剪的彩色检测图,供裁剪图(_RE_CutImg)保存时保留原图通道
// (检测算法仍只用灰度 detImg)
m_deskewPadRoi = padRoi;
cv::Mat colorPatch = image(padRoi);
if (colorPatch.channels() == 1)
{
colorPatch.copyTo(m_pImageAllResult->detImg);
m_pImageAllResult->detColorImg.release();
}
else if (colorPatch.channels() == 3)
{
cv::cvtColor(colorPatch, m_pImageAllResult->detImg, cv::COLOR_BGR2GRAY);
colorPatch.copyTo(m_pImageAllResult->detColorImg);
}
else
{
cv::cvtColor(colorPatch, m_pImageAllResult->detImg, cv::COLOR_BGRA2GRAY);
colorPatch.copyTo(m_pImageAllResult->detColorImg);
}
// 4、检测图坐标系下 m_outer_roi 即整张检测图;原图坐标系下 CutRoi 仍取旋转矩形外接区域(兼容旧语义/JSON)
m_outer_roi = cv::Rect(0, 0, detW, detH);
m_Crop_Roi_paramImg = padRoi;
m_pImageAllResult->pDetResult->CutRoi = m_Crop_Roi_paramImg;
m_pImageAllResult->pDetResult->Param_CropRoi = m_Crop_Roi_paramImg;
// 检测图坐标 -> 原图坐标 的映射,供结果解析时取回缺陷小图/换算坐标
m_pImageAllResult->pDetResult->detToSrcMat = m_matDet2Src.clone();
// 5、参数(模板)坐标 -> 检测图坐标 的映射:
// 有对齐时先把参数点映射到原图再平移到检测图;无对齐时参数按原图坐标系处理,
// 直接按裁剪原点平移(与旧的裁剪平移完全一致)
{
if (m_pEdge_Align_Result && m_pEdge_Align_Result->buseOfft)
{
if (!m_pEdge_Align_Result->H.empty())
{
m_matParam2Det = MatAffineCompose(m_matSrc2Det, m_pEdge_Align_Result->H);
}
else
{
cv::Mat matOfft = (cv::Mat_<double>(2, 3) << 1, 0, (double)m_pEdge_Align_Result->offt_x, 0, 1, (double)m_pEdge_Align_Result->offt_y);
m_matParam2Det = MatAffineCompose(m_matSrc2Det, matOfft);
}
}
else
{
m_matParam2Det = m_matSrc2Det.clone();
}
}
long time_calan_e = CheckUtil::getcurTime();
m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", "-------------------------crop--------------succ--- det [%d %d] src bbox [%d %d %d %d] time %ld----\n",
detW, detH, m_Crop_Roi_paramImg.x, m_Crop_Roi_paramImg.y, m_Crop_Roi_paramImg.width, m_Crop_Roi_paramImg.height,
time_calan_e - time_edge_s);
/*多线程开启 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");
/* 检测支架偏移(bDetSupport 开关关闭时不做支架判定)
不使用参数绘制区域,结合两次定位的 mask 计算:
1、bigmask 最大连通域 = 产品区域 rp(近似矩形,四周可能有凸起)
2、smallmask 最大连通域 = 支架区域 rs(按定位裁剪区 m_outer_rroi 外接矩形映射回原图坐标)
3、按 edge_select 指定方向(left/right/up/down)计算 rs 中心到 rp 对应边缘的距离
4、计算值与标准值(x_standard/y_standard)做差取平均,平均差超过 offset(x_offset) → 偏移 */
{
Function_Support_Det &supportDet = m_pFuntion->function.f_supportDet;
if (supportDet.bOpen && supportDet.bDetSupport)
{
const cv::Mat &bigmask = m_pEdge_Align_Result->bigmask;
const cv::Mat &smallmask = m_pEdge_Align_Result->smallmask;
if (bigmask.empty() || smallmask.empty())
{
m_pdetlog->AddCheckstr(PrintLevel_0, "支架检测", "mask empty (big %d small %d), skip support offset", bigmask.empty(), smallmask.empty());
}
else
{
// 1、mask 二值化(兼容 0/1 与 0/255 掩膜)
cv::Mat bigBin, smallBin;
{
cv::Mat grayMask;
if (bigmask.channels() != 1)
{
cv::cvtColor(bigmask, grayMask, cv::COLOR_BGR2GRAY);
}
else
{
grayMask = bigmask;
}
double dMin = 0;
double dMax = 0;
cv::minMaxLoc(grayMask, &dMin, &dMax);
cv::threshold(grayMask, bigBin, dMax > 1 ? dMax * 0.5 : 0, 255, cv::THRESH_BINARY);
}
{
cv::Mat grayMask;
if (smallmask.channels() != 1)
{
cv::cvtColor(smallmask, grayMask, cv::COLOR_BGR2GRAY);
}
else
{
grayMask = smallmask;
}
double dMin = 0;
double dMax = 0;
cv::minMaxLoc(grayMask, &dMin, &dMax);
cv::threshold(grayMask, smallBin, dMax > 1 ? dMax * 0.5 : 0, 255, cv::THRESH_BINARY);
}
// 2、最大连通域轮廓:bigmask -> 产品区域 rp(原图坐标);smallmask -> 支架区域 rs(smallmask 局部坐标)
bool bFoundRp = false;
std::vector<cv::Point> rpContour = CheckUtil::getLargestContourROI(bigBin, bFoundRp);
bool bFoundRs = false;
std::vector<cv::Point> rsContour = CheckUtil::getLargestContourROI(smallBin, bFoundRs);
if (!bFoundRp || rpContour.size() < 3 || !bFoundRs || rsContour.size() < 3)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "支架检测", "contour invalid (rp %d %ld, rs %d %ld), skip support offset",
bFoundRp, rpContour.size(), bFoundRs, rsContour.size());
}
else
{
// rp 边缘:产品最大连通域的外接矩形(含凸起)
cv::Rect rpRect = cv::boundingRect(rpContour);
// rs 映射回原图坐标:smallmask 对应定位时裁剪的大轮廓区域(m_outer_rroi 外接矩形),按比例映射
cv::Rect bigRect = m_outer_rroi.boundingRect();
float fx = bigRect.width * 1.0f / smallmask.cols;
float fy = bigRect.height * 1.0f / smallmask.rows;
cv::Rect rsSmallRect = cv::boundingRect(rsContour);
cv::Rect rsRect(
cvRound(bigRect.x + rsSmallRect.x * fx),
cvRound(bigRect.y + rsSmallRect.y * fy),
cvRound(rsSmallRect.width * fx),
cvRound(rsSmallRect.height * fy));
// 3、rs 中心到 rp 上下左右边缘的距离
float fCx = rsRect.x + rsRect.width * 0.5f;
float fCy = rsRect.y + rsRect.height * 0.5f;
float fDL = fCx - rpRect.x; // 到左边缘
float fDR = (rpRect.x + rpRect.width) - fCx; // 到右边缘
float fDT = fCy - rpRect.y; // 到上边缘
float fDB = (rpRect.y + rpRect.height) - fCy; // 到下边缘
// 4、按 edge_select 指定方向(left/right/up/down)取计算值,与标准值做差取平均,平均差超过 offset → 偏移
// left/right 用 x_standard,up/down 用 y_standard;offset 用 x_offset
float fSumDiff = 0;
int nDiffNum = 0;
std::string strSelect = "";
std::string strDiff = "";
char strTmp[64];
for (size_t iselect = 0; iselect < supportDet.edge_select.size(); iselect++)
{
const std::string &strEdge = supportDet.edge_select[iselect];
float fDist = 0;
float fStandard = 0;
if (strEdge == "left")
{
fDist = fDL;
fStandard = supportDet.x_standard;
}
else if (strEdge == "right")
{
fDist = fDR;
fStandard = supportDet.x_standard;
}
else if (strEdge == "up")
{
fDist = fDT;
fStandard = supportDet.y_standard;
}
else if (strEdge == "down")
{
fDist = fDB;
fStandard = supportDet.y_standard;
}
else
{
continue;
}
float fDiff = fabs(fDist - fStandard);
fSumDiff += fDiff;
nDiffNum++;
sprintf(strTmp, "%s(%.1f/%.1f) ", strEdge.c_str(), fDist, fStandard);
strSelect += strTmp;
sprintf(strTmp, "%.1f ", fDiff);
strDiff += strTmp;
}
// edge_select 为空或无效时 nDiffNum=0,结果不参与判定(仅打日志)
float fAvgDiff = (nDiffNum > 0) ? (fSumDiff / nDiffNum) : 0.0f;
bool bOffset = (nDiffNum > 0) && (fAvgDiff > supportDet.x_offset);
m_pdetlog->AddCheckstr(PrintLevel_0, "支架检测",
"rp [%d %d %d %d] rs [%d %d %d %d] scale (%.4f %.4f) center (%.1f %.1f) dL %.1f dR %.1f dT %.1f dB %.1f | select %s diff %s avgDiff %.1f limit %.1f ng %d",
rpRect.x, rpRect.y, rpRect.width, rpRect.height,
rsRect.x, rsRect.y, rsRect.width, rsRect.height,
fx, fy, fCx, fCy, fDL, fDR, fDT, fDB,
strSelect.c_str(), strDiff.c_str(), fAvgDiff, supportDet.x_offset, bOffset);
if (bOffset)
{
QX_ERROR_INFO_ temerror;
temerror.Idx = m_pDetResult->pQx_ErrorList->size();
// roi 在原图坐标系,按坐标映射转换到检测图(detImg)坐标系
cv::Rect roi = MatAffineRect(m_matSrc2Det, rsRect);
roi &= cv::Rect(0, 0, m_pImageAllResult->detImg.cols, m_pImageAllResult->detImg.rows);
temerror.roi = roi;
temerror.area = roi.width * roi.height;
temerror.JudgArea = roi.width * m_fImgage_Scale_X * roi.height * m_fImgage_Scale_Y;
temerror.JudgArea_second = temerror.JudgArea;
float w = rsRect.width;
float h = rsRect.height;
temerror.flen = (w > h ? w : h) * m_fImgage_Scale_X;
temerror.fbreadth = (w > h ? h : w) * m_fImgage_Scale_Y;
temerror.nconfig_qx_type = CONFIG_QX_NAME_support_offset;
temerror.qx_name = CONFIG_QX_NAME_Names[temerror.nconfig_qx_type];
temerror.result = QX_RESULT_TYPE_NG;
temerror.result_name = QX_RESULT_TYPE_Names[QX_RESULT_TYPE_NG];
// 计算支架中心所在的检测区域
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);
m_pDetResult->pQx_ErrorList->push_back(temerror);
}
}
}
}
}
/* 把手缺失检测:bigmask 的凸起本身不算 NG,只有"参数里的某个把手区域附近找不到把手"才算 NG(每个缺失区域报一条) */
{
if (m_bHandleDetSucc && m_bHandleExpect && !m_bHandleFound)
{
for (size_t iRegion = 0; iRegion < m_HandleRegionResultList.size(); iRegion++)
{
Handle_Region_Result_ &regionResult = m_HandleRegionResultList[iRegion];
if (regionResult.bFound)
{
continue;
}
// 上报该把手区域的位置:参数(模板)坐标 -> 检测图(detImg)坐标
cv::Rect roi = MatAffineRect(m_matParam2Det, regionResult.boxRegion);
roi &= cv::Rect(0, 0, m_pImageAllResult->detImg.cols, m_pImageAllResult->detImg.rows);
if (roi.width <= 0 || roi.height <= 0)
{
continue;
}
QX_ERROR_INFO_ temerror;
temerror.Idx = m_pDetResult->pQx_ErrorList->size();
temerror.roi = roi;
temerror.area = roi.width * roi.height;
temerror.JudgArea = roi.width * m_fImgage_Scale_X * roi.height * m_fImgage_Scale_Y;
temerror.JudgArea_second = temerror.JudgArea;
float w = roi.width;
float h = roi.height;
temerror.flen = (w > h ? w : h) * m_fImgage_Scale_X;
temerror.fbreadth = (w > h ? h : w) * m_fImgage_Scale_Y;
temerror.nconfig_qx_type = CONFIG_QX_NAME_handle_loss;
temerror.qx_name = CONFIG_QX_NAME_Names[CONFIG_QX_NAME_handle_loss];
temerror.result = QX_RESULT_TYPE_NG;
temerror.result_name = QX_RESULT_TYPE_Names[QX_RESULT_TYPE_NG];
// 计算把手区域中心所在的检测区域
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);
m_pDetResult->pQx_ErrorList->push_back(temerror);
m_pdetlog->AddCheckstr(PrintLevel_0, "把手检测", "%s %d region %d roi [%d %d %d %d]",
temerror.qx_name.c_str(), temerror.Idx, regionResult.nIdx,
roi.x, roi.y, roi.width, roi.height);
}
}
}
/* Tag检测(bDetTag 关闭时不上报标签 NG) */
{
if (!m_pFuntion->function.f_supportDet.bDetTag)
{
m_tag_roiList.clear();
}
for (const auto &tagRoi : m_tag_roiList)
{
// tag roi 在原图坐标系,按坐标映射转换到检测图(detImg)坐标系
cv::Rect roi = MatAffineRect(m_matSrc2Det, tagRoi.boundingRect());
roi &= cv::Rect(0, 0, m_pImageAllResult->detImg.cols, m_pImageAllResult->detImg.rows);
if (roi.width <= 0 || roi.height <= 0)
{
continue;
}
QX_ERROR_INFO_ temerror;
temerror.Idx = m_pDetResult->pQx_ErrorList->size();
temerror.roi = roi;
temerror.area = roi.width * roi.height;
temerror.JudgArea = roi.width * m_fImgage_Scale_X * roi.height * m_fImgage_Scale_Y;
temerror.JudgArea_second = temerror.JudgArea;
float tw = tagRoi.size.width;
float th = tagRoi.size.height;
temerror.flen = (tw > th ? tw : th) * m_fImgage_Scale_X;
temerror.fbreadth = (tw > th ? th : tw) * m_fImgage_Scale_Y;
temerror.nconfig_qx_type = CONFIG_QX_NAME_cell_tag;
temerror.qx_name = CONFIG_QX_NAME_Names[CONFIG_QX_NAME_cell_tag];
temerror.result = QX_RESULT_TYPE_NG;
temerror.result_name = QX_RESULT_TYPE_Names[QX_RESULT_TYPE_NG];
// 计算标签中心所在的检测区域
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);
m_pDetResult->pQx_ErrorList->push_back(temerror);
m_pdetlog->AddCheckstr(PrintLevel_0, "Tag检测", "tag %d roi [%d %d %d %d] -> NG", temerror.Idx, roi.x, roi.y, roi.width, roi.height);
}
}
m_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::shared_ptr<AIModel_Base> pAIDet;
// printf("=====>>>>AI_Detect_Thread m_strCurDetChannel %s \n", m_strCurDetChannel.c_str());
{
// printf("=====>>>>AI_Detect_Thread USE CA %s \n", m_strCurDetChannel.c_str());
pAIDet = AI_Factory->Defect;
}
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();
SmallRoiList.clear();
SmallRoiList.erase(SmallRoiList.begin(), SmallRoiList.end());
cv::Rect cutRoi;
cutRoi.x = 0;
cutRoi.y = 0;
cutRoi.width = img.cols - 0;
cutRoi.height = img.rows - 0;
int deal_image_width = pAIDet->input_0.width;
int deal_image_height = pAIDet->input_0.height;
int re = CheckUtil::cutSmallImg(img, SmallRoiList, cutRoi, deal_image_width, deal_image_height, 0, 0);
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strBaseLog, " cutSmallImg Num %d", SmallRoiList.size());
if (re != 0 || SmallRoiList.size() <= 0)
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strBaseLog, " cutSmallImg error %d", re);
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "AI_Detect End");
return re;
/* code */
}
// 临时存图
if (DetImgInfo_shareP->bsaveProcessImg)
{
cv::Mat imshow = img.clone(); // 保持输入通道,不再灰度转假彩
for (int i = 0; i < SmallRoiList.size(); i++)
{
cv::rectangle(imshow, SmallRoiList.at(i), CheckUtil::GetDrawColor(imshow, cv::Scalar(0, 0, 255)), 3);
}
cv::imwrite(DetImgInfo_shareP->strChannel + "_AI_Det_ROI.jpg", imshow);
}
ResultImg = cv::Mat::zeros(img.size(), CV_8UC1);
const int totalTasks = SmallRoiList.size();
int submitted = 0;
int completed = 0;
// std::string str_Root = m_strRootPath + pDetConfig->strProductID + "_" + pDetConfig->strchannel + "_";
t2 = CheckUtil::getcurTime();
while (completed < totalTasks)
{
// 如果任务还没提交完,且当前处理任务数 < 2,提交新任务
if (submitted < totalTasks && runner->GetProcessingCount() < 10)
{
std::shared_ptr<AIMulThreadRunBase::AITask> task = std::make_shared<AIMulThreadRunBase::AITask>();
task->id = completed;
task->roi = SmallRoiList.at(submitted);
task->input = img(SmallRoiList.at(submitted)).clone();
task->output = std::make_shared<cv::Mat>();
task->engine = pAIDet;
runner->SubmitTask(task);
submitted++;
}
// 尝试取结果
std::shared_ptr<AIMulThreadRunBase::AITask> result;
if (runner->PopResult(result))
{
{
std::lock_guard<std::mutex> lock(mtx_AIMaskImgBLobQueue);
m_AIMaskImgBLobQueue.push(result);
}
cv::Mat &outimg = *(result->output);
if (!outimg.empty())
{
outimg.copyTo(ResultImg(result->roi), outimg);
}
{
std::shared_ptr<ImageAllResult::AI_Det_MaskImg> temAIresult = std::make_shared<ImageAllResult::AI_Det_MaskImg>();
temAIresult->roi = result->roi;
temAIresult->AI_inImg = result->input;
temAIresult->AI_mask = outimg;
m_pImageAllResult->AI_Qx_MaskList.push_back(temAIresult);
}
completed++;
}
else
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
t3 = CheckUtil::getcurTime();
float mean_AI = (t3 - t2) / SmallRoiList.size();
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strBaseLog, " AI Run Time: sum %ld pre %ld Run %ld mean One Small Img %f", t3 - t1, t2 - t1, t3 - t2, mean_AI);
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "AI_Detect End");
// 临时存图
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_cell_ymhs;
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;
int cls_num = 0;
switch (temClass)
{
case 0:
cls_num = AI_CLass_QX_NAME_aotudian;
break;
case 1:
cls_num = AI_CLass_QX_NAME_other;
break;
case 2:
cls_num = AI_CLass_QX_NAME_line;
break;
case 3:
cls_num = AI_CLass_QX_NAME_zangwu;
break;
case 4:
cls_num = AI_CLass_QX_NAME_dianzhuang;
break;
case 5:
cls_num = AI_CLass_QX_NAME_posun;
break;
case 6:
cls_num = AI_CLass_QX_NAME_xianwei;
break;
case 7:
cls_num = AI_CLass_QX_NAME_shuizi;
break;
case 8:
cls_num = AI_CLass_QX_NAME_danban;
break;
case 9:
cls_num = AI_CLass_QX_NAME_fuchen;
break;
default:
cls_num = AI_CLass_QX_NAME_zangwu;
break;
}
// std::string strclassName = AI_CLass_QX_NAME_Names[cls_num];
// printf("AI Class num %d = %s %f\n", cls_num, strclassName.c_str(), result->cls_score);
// 分类存图 开启。
if (m_pbaseCheckFunction->saveImg.bSaveAlginImg)
{
std::string saveimgpaht = "";
if (m_strCurDetChannel == "TA")
{
saveimgpaht = m_strRootPath_TA_cls + std::to_string(cls_num) + "/" + std::to_string(CheckUtil::getcurTime()) + "_" + std::to_string(result->cls_score) + ".png";
}
else
{
saveimgpaht = m_strRootPath_CA_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);
// 结果图画布取自原图裁剪区(平移映射),保持原图通道(彩色原图 -> 真实彩色,不再灰度转假彩);
// 异常情况(如未走裁剪流程)直接用检测图兜底
const cv::Mat &srcImg = m_CheckResult_shareP->in_shareImage->img;
if (m_CheckResult_shareP->in_shareImage->Det_Mode != DET_MODE_ReJson &&
!srcImg.empty() &&
m_deskewPadRoi.width > 0 && m_deskewPadRoi.height > 0 &&
CheckUtil::RoiInImg(m_deskewPadRoi, srcImg))
{
cv::resize(srcImg(m_deskewPadRoi), m_pImageAllResult->resultImg, sz);
}
else
{
cv::resize(m_pImageAllResult->detImg, 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;
// 参数多边形 -> 检测图(裁剪坐标系) 的映射在裁剪时已算好(m_matParam2Det)
for (const auto &region : m_pCommonAnalysisyConfig->regionConfigArr)
{
m_DetRoiList.Update_Mat(region.basicInfo.pointArry, m_matParam2Det, 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;
// 绘制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++)
{
cv::Point2f detPt = MatAffinePoint(m_matSrc2Det, vertices[i]);
draw_pts.emplace_back(cvRound(detPt.x * fs_resize_x),
cvRound(detPt.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);
}
if (m_pbaseCheckFunction && m_pbaseCheckFunction->markLine.region.size() > 1)
{
std::vector<cv::Point> product_roi_show;
for (const auto &pt : m_pbaseCheckFunction->markLine.region)
{
// 参数(模板)坐标 -> 检测图(裁剪) -> 结果图
cv::Point2f detPt = MatAffinePoint(m_matParam2Det, cv::Point2f((float)pt.x, (float)pt.y));
cv::Point draw_pt;
draw_pt.x = cvRound(detPt.x * fs_resize_x);
draw_pt.y = cvRound(detPt.y * fs_resize_y);
product_roi_show.push_back(draw_pt);
}
cv::polylines(
m_CheckResult_shareP->resultimg,
product_roi_show,
true,
CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, cv::Scalar(0, 255, 0)),
1);
}
if (m_pEdge_Align_Result.get() != NULL)
{
if (m_pbaseCheckFunction->markLine.bDraw)
{
for (size_t i = 0; i < m_pEdge_Align_Result->markresulList.size(); i++)
{
// if (m_pEdge_Align_Result->markresulList[i].status == Mark_Result_Status_OK)
{
const cv::Point &markPt = m_pEdge_Align_Result->markresulList[i].det_Local_DetImg;
cv::Point2f detPt = MatAffinePoint(m_matSrc2Det, cv::Point2f((float)markPt.x, (float)markPt.y));
cv::Point p;
p.x = cvRound(detPt.x * fs_resize_x);
p.y = cvRound(detPt.y * fs_resize_y);
cv::circle(m_CheckResult_shareP->resultimg, p, 5,
CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, cv::Scalar(0, 255, 0)));
}
}
}
}
// cv::imwrite("sss.png", m_CheckResult_shareP->resultimg);
return 0;
}
int ImgCheckAnalysisy::AIClassTypeToConfigType(int nAIQXType)
{
int resultError_type = CONFIG_QX_NAME_cell_aotudian;
switch (nAIQXType)
{
case AI_CLass_QX_NAME_aotudian:
resultError_type = CONFIG_QX_NAME_cell_aotudian;
break;
case AI_CLass_QX_NAME_other:
resultError_type = CONFIG_QX_NAME_cell_other;
break;
case AI_CLass_QX_NAME_line:
resultError_type = CONFIG_QX_NAME_cell_line;
break;
case AI_CLass_QX_NAME_zangwu:
resultError_type = CONFIG_QX_NAME_cell_zangwu;
break;
case AI_CLass_QX_NAME_dianzhuang:
resultError_type = CONFIG_QX_NAME_cell_dianzhuang;
break;
case AI_CLass_QX_NAME_posun:
resultError_type = CONFIG_QX_NAME_cell_posun;
break;
case AI_CLass_QX_NAME_xianwei:
resultError_type = CONFIG_QX_NAME_cell_xianwei;
break;
case AI_CLass_QX_NAME_shuizi:
resultError_type = CONFIG_QX_NAME_cell_shuizi;
break;
case AI_CLass_QX_NAME_danban:
resultError_type = CONFIG_QX_NAME_cell_danban;
break;
case AI_CLass_QX_NAME_fuchen:
resultError_type = CONFIG_QX_NAME_cell_fuchen;
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;
}
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;
float JudgArea = blobs.blobTab[i].JudgArea;
float fsecondArea = JudgArea;
std::string qx_name = CONFIG_QX_NAME_Names[config_qx_type];
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.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);
// 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, vector<cv::RotatedRect> &tagroiList)
{
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.pBaseCheckFunction = m_pbaseCheckFunction;
config.bDetTag = m_pFuntion->function.f_supportDet.bDetTag; // 标签检测开关:关闭时不跑 Tag_Loc
/* 定位获取三个roi */
int re = m_pAI_Edge_Algin.Detect(img, &config, m_pEdge_Align_Result);
if (re != 0)
{
printf("AI_Edge Is Error = %d \n", re);
}
if (!m_pEdge_Align_Result->bigroi.empty())
{
outerRoi = m_pEdge_Align_Result->bigroi[0];
}
if (!m_pEdge_Align_Result->smallroi.empty())
{
innerRoi = m_pEdge_Align_Result->smallroi[0];
}
// 无条件赋值:Tag_Loc 未检出或 bDetTag 关闭(tagroi 为空)时清空,避免残留上一张图的标签
tagroiList = m_pEdge_Align_Result->tagroi;
/*使用m_pEdge_Align_Result->bigmask检测把手是否缺失*/
{
long handle_s = CheckUtil::getcurTime();
CheckHandleByBigMask(m_pEdge_Align_Result->bigmask);
long handle_e = CheckUtil::getcurTime();
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "handle detect use time %ld", handle_e - handle_s);
}
return re;
}
// 用 bigmask 检测把手:
// 1、bigmask 主体近似矩形,把手是主体四周凸起的"类矩形块"
// 2、把主体四周的凸起抠出来(凸起在该边的边界值上表现为一次突变进入 + 一次反向突变离开)
// 3、参数里每个非空的"把手区域"附近都应该有一个把手:在区域附近找凸起,
// 凸起宽高与区域外接矩形宽高相近(HANDLE_SIZE_MIN_RATIO ~ HANDLE_SIZE_MAX_RATIO)才算匹配
// 4、凸起本身不算 NG,只有"某个把手区域附近找不到匹配的凸起"才算把手缺失(NG 由 CheckRun 上报)
int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &bigmask)
{
m_bHandleDetSucc = false;
m_bHandleFound = false;
m_bHandleExpect = false;
m_HandleCandList.clear();
m_HandleRegionResultList.clear();
if (bigmask.empty())
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "bigmask is empty, skip handle detect");
return 1;
}
// 1、抠出 bigmask 四周凸起的矩形块
std::vector<Mask_Protrusion_> protrusionList;
int re = CheckUtil::GetMaskProtrusions(bigmask, protrusionList);
if (re != 0)
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "GetMaskProtrusions error %d", re);
return re;
}
m_bHandleDetSucc = true;
// 2、把手检测开关 + 把手区域(最多4个,非空区域表示该处应该有一个把手)
bool bDetHandle = false;
std::vector<cv::Rect> regionBoxList;
if (m_pFuntion != NULL)
{
const Function_Support_Det &supportDet = m_pFuntion->function.f_supportDet;
bDetHandle = supportDet.bDetHandle;
for (size_t i = 0; i < supportDet.handleBoxes.size() && i < HANDLE_REGION_MAX_NUM; i++)
{
regionBoxList.push_back(supportDet.handleBoxes[i]);
}
}
m_bHandleExpect = (bDetHandle && !regionBoxList.empty());
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "protrusion num %ld handle region num %ld bDetHandle %s expect %s",
protrusionList.size(), regionBoxList.size(), BOOL_TO_STR(bDetHandle), BOOL_TO_STR(m_bHandleExpect));
// 3、凸起候选实测特征(面积/填充率,仅用于日志排查)
for (size_t i = 0; i < protrusionList.size(); i++)
{
Handle_Candidate_ cand;
cand.protr = protrusionList[i];
// 左/右凸起:沿边为 y 方向,深度为 x 方向;上/下凸起相反
bool bHorz = (cand.protr.nSide == 0 || cand.protr.nSide == 1);
cand.nLen = bHorz ? cand.protr.roi.height : cand.protr.roi.width;
cand.nDepth = bHorz ? cand.protr.roi.width : cand.protr.roi.height;
m_HandleCandList.push_back(cand);
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle",
"protrusion %ld side %d roi [%d %d %d %d] len %d depth %d area %0.0f fill %0.2f",
i, cand.protr.nSide, cand.protr.roi.x, cand.protr.roi.y, cand.protr.roi.width, cand.protr.roi.height,
cand.nLen, cand.nDepth, cand.protr.fArea, cand.protr.fFillRatio);
}
// 4、逐个把手区域:在区域附近找宽高匹配的凸起(同一个凸起只匹配给一个区域)
std::vector<bool> usedList(m_HandleCandList.size(), false);
int nFound = 0;
for (size_t ir = 0; ir < regionBoxList.size(); ir++)
{
Handle_Region_Result_ result;
result.nIdx = (int)ir + 1;
result.boxRegion = regionBoxList[ir];
int nMatchIdx = MatchHandleRegion(m_HandleCandList, usedList, result.boxRegion, result.roiHandle);
result.bFound = (nMatchIdx >= 0);
if (result.bFound)
{
usedList[nMatchIdx] = true;
nFound++;
}
m_HandleRegionResultList.push_back(result);
std::string strResult = "handle missing !!";
if (result.bFound)
{
strResult = "handle roi [" + std::to_string(result.roiHandle.x) + " " + std::to_string(result.roiHandle.y) + " " +
std::to_string(result.roiHandle.width) + " " + std::to_string(result.roiHandle.height) + "]";
}
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "handle region %d box [%d %d %d %d] -> %s",
result.nIdx, result.boxRegion.x, result.boxRegion.y, result.boxRegion.width, result.boxRegion.height,
strResult.c_str());
}
m_bHandleFound = (!regionBoxList.empty() && nFound == (int)regionBoxList.size());
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, "Handle", "handle region num %ld found num %d -> %s",
regionBoxList.size(), nFound, m_bHandleFound ? "handle ok" : "handle missing !!");
// 5、存过程图:凸起红框、把手区域蓝框、匹配上的把手绿框(仅调试时)
if (DetImgInfo_shareP->bsaveProcessImg)
{
cv::Mat show;
if (bigmask.channels() == 1)
{
cv::cvtColor(bigmask, show, cv::COLOR_GRAY2BGR);
}
else
{
show = bigmask.clone();
}
for (size_t i = 0; i < m_HandleCandList.size(); i++)
{
cv::rectangle(show, m_HandleCandList[i].protr.roi, cv::Scalar(0, 0, 255), 8);
}
for (size_t i = 0; i < m_HandleRegionResultList.size(); i++)
{
cv::rectangle(show, m_HandleRegionResultList[i].boxRegion, cv::Scalar(255, 0, 0), 8);
if (m_HandleRegionResultList[i].bFound)
{
cv::rectangle(show, m_HandleRegionResultList[i].roiHandle, cv::Scalar(0, 255, 0), 12);
}
}
cv::Mat showSmall;
cv::resize(show, showSmall, cv::Size(show.cols / 4, show.rows / 4));
std::vector<int> paramJpg = {cv::IMWRITE_JPEG_QUALITY, 90};
cv::imwrite(DetImgInfo_shareP->strChannel + "_handle_det.jpg", showSmall, paramJpg);
}
return 0;
}
// 标签检测(独立步骤):用 (图像 + 定位结果) 跑 Tag_Loc 模型
// 裁剪区域与定位流程里 Tag_Loc 的口径完全一致(定位得到的产品外接旋转矩形):
// 明场相机用自己的定位结果;暗场相机没做定位模型,用复用明场的结果 —— 保证暗/明场对同一摆位切出同样区域。
// 注意:标签模型对裁剪尺度很敏感(裁剪差 3px 就会让检出个数变化),不能另用 bigmask 重推裁剪框。
// bigmask 仅在定位外接矩形异常时作兜底(最大轮廓外接框)。
// 检出的标签 roi 存 m_tag_roiList(裁剪图坐标系,与定位流程 tagroi 的约定一致),由 CheckRun 统一上报 NG。
int ImgCheckAnalysisy::CheckTagByBigMask(const cv::Mat &img, const cv::Mat &bigmask)
{
m_tag_roiList.clear();
if (!AI_Factory || !AI_Factory->Tag_Loc)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "Tag_Loc model is null");
return 1;
}
if (img.empty())
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "tag check img is empty");
return 2;
}
// 1、裁剪区域:与定位流程 Tag_Loc 的裁剪口径一致(产品外接旋转矩形,暗场为复用明场的值)
cv::Rect roiCrop = m_outer_rroi.boundingRect() & cv::Rect(0, 0, img.cols, img.rows);
if (roiCrop.width <= 0 || roiCrop.height <= 0)
{
// 异常兜底:定位外接矩形无效时,用 bigmask 最大轮廓外接框
if (bigmask.empty())
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "tag check roi invalid and bigmask is empty");
return 3;
}
cv::Mat binMask;
{
cv::Mat grayMask;
if (bigmask.channels() != 1)
{
cv::cvtColor(bigmask, grayMask, cv::COLOR_BGR2GRAY);
}
else
{
grayMask = bigmask;
}
double dMin = 0;
double dMax = 0;
cv::minMaxLoc(grayMask, &dMin, &dMax);
// 兼容 0/1 与 0/255 掩膜:取半强度作阈值(重采样后的边缘取到真实边界)
cv::threshold(grayMask, binMask, dMax > 1 ? dMax * 0.5 : 0, 255, cv::THRESH_BINARY);
}
bool bFound = false;
std::vector<cv::Point> contour = CheckUtil::getLargestContourROI(binMask, bFound);
if (!bFound || contour.size() < 3)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "tag check bigmask contour too few");
return 4;
}
roiCrop = cv::minAreaRect(contour).boundingRect() & cv::Rect(0, 0, img.cols, img.rows);
if (roiCrop.width <= 0 || roiCrop.height <= 0)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "tag check crop roi error [%d %d %d %d]", roiCrop.x, roiCrop.y, roiCrop.width, roiCrop.height);
return 5;
}
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Tag", "tag check use bigmask roi [%d %d %d %d]",
roiCrop.x, roiCrop.y, roiCrop.width, roiCrop.height);
}
// 2、裁剪 -> resize 到 Tag_Loc 模型输入尺寸 -> 灰度
std::shared_ptr<AIModel_Base> pTag = AI_Factory->Tag_Loc;
cv::Size sz(pTag->input_0.width, pTag->input_0.height);
if (sz.width <= 0 || sz.height <= 0)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "tag check invalid model input size");
return 6;
}
cv::Mat detImg;
cv::resize(img(roiCrop), detImg, sz);
if (detImg.channels() == 3)
{
cv::cvtColor(detImg, detImg, cv::COLOR_BGR2GRAY);
}
else if (detImg.channels() == 4)
{
cv::cvtColor(detImg, detImg, cv::COLOR_BGRA2GRAY);
}
// 3、推理
cv::Mat mask;
int re = pTag->AIDet(detImg, mask);
if (re != 0)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "Tag_Loc AIDet error %d", re);
return 100 + re;
}
if (mask.empty())
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Tag", "tag mask empty, no tag");
return 0;
}
// 调试存图(仅 -s 时保存)
if (DetImgInfo_shareP && DetImgInfo_shareP->bsaveProcessImg)
{
cv::imwrite(DetImgInfo_shareP->strChannel + "_tag_in.png", detImg);
cv::imwrite(DetImgInfo_shareP->strChannel + "_tag_out_mask.png", mask);
}
// 4、mask 轮廓 -> 还原到裁剪图坐标 -> RotatedRect(与定位流程 tagroi 的坐标系一致)
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(mask.clone(), contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
const float scale_x = (float)roiCrop.width / (float)sz.width;
const float scale_y = (float)roiCrop.height / (float)sz.height;
for (size_t i = 0; i < contours.size(); i++)
{
if (contours[i].size() < 3 || cv::contourArea(contours[i]) <= 0)
{
continue;
}
std::vector<cv::Point2f> srcContour;
srcContour.reserve(contours[i].size());
for (const auto &pt : contours[i])
{
srcContour.emplace_back(pt.x * scale_x, pt.y * scale_y);
}
m_tag_roiList.emplace_back(cv::minAreaRect(srcContour));
}
// 面积从大到小排序(与定位流程 tagroi 的顺序一致)
std::sort(m_tag_roiList.begin(), m_tag_roiList.end(),
[](const cv::RotatedRect &a, const cv::RotatedRect &b)
{ return a.size.width * a.size.height > b.size.width * b.size.height; });
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Tag", "tag region num %ld crop [%d %d %d %d] model [%d %d]",
m_tag_roiList.size(), roiCrop.x, roiCrop.y, roiCrop.width, roiCrop.height, sz.width, sz.height);
return 0;
}
// 在某个把手区域附近找宽高匹配的凸起
// 匹配条件:1、凸起中心落在把手区域附近(区域宽/高各外扩 HANDLE_NEAR_EXPAND_RATIO)
// 2、凸起宽高与该区域外接矩形宽高的比值都在 [HANDLE_SIZE_MIN_RATIO, HANDLE_SIZE_MAX_RATIO](允许横竖互换)
// 返回凸起在 candList 中的下标,-1 表示没找到
int ImgCheckAnalysisy::MatchHandleRegion(const std::vector<Handle_Candidate_> &candList, const std::vector<bool> &usedList,
const cv::Rect &boxRegion, cv::Rect &roiHandle)
{
if (boxRegion.width <= 0 || boxRegion.height <= 0)
{
return -1;
}
// 搜索范围:把手区域外扩(宽/高各外扩区域尺寸的一半),容忍产品摆放偏差
int nx = std::max(10, (int)(boxRegion.width * HANDLE_NEAR_EXPAND_RATIO));
int ny = std::max(10, (int)(boxRegion.height * HANDLE_NEAR_EXPAND_RATIO));
cv::Rect searchBox(boxRegion.x - nx, boxRegion.y - ny, boxRegion.width + 2 * nx, boxRegion.height + 2 * ny);
cv::Point pRegionCenter(boxRegion.x + boxRegion.width / 2, boxRegion.y + boxRegion.height / 2);
int nBestIdx = -1;
double fBestDis = 1e18;
int nNearIdx = -1; // 区域内离区域中心最近的凸起(仅用于日志排查)
double fNearDis = 1e18;
for (size_t i = 0; i < candList.size(); i++)
{
if (i < usedList.size() && usedList[i])
{
continue;
}
const Handle_Candidate_ &cand = candList[i];
const cv::Rect &roi = cand.protr.roi;
if (roi.width <= 0 || roi.height <= 0)
{
continue;
}
// 位置:凸起中心要落在把手区域附近
cv::Point pCenter(roi.x + roi.width / 2, roi.y + roi.height / 2);
if (!searchBox.contains(pCenter))
{
continue;
}
double fDis = CheckUtil::calDis(pCenter, pRegionCenter);
if (fDis < fNearDis)
{
fNearDis = fDis;
nNearIdx = (int)i;
}
// 宽高:与把手区域外接矩形的宽高相近(允许横竖互换)
float fw = (float)roi.width;
float fh = (float)roi.height;
bool bSizeOK = (fw >= boxRegion.width * HANDLE_SIZE_MIN_RATIO && fw <= boxRegion.width * HANDLE_SIZE_MAX_RATIO &&
fh >= boxRegion.height * HANDLE_SIZE_MIN_RATIO && fh <= boxRegion.height * HANDLE_SIZE_MAX_RATIO);
if (!bSizeOK)
{
bSizeOK = (fw >= boxRegion.height * HANDLE_SIZE_MIN_RATIO && fw <= boxRegion.height * HANDLE_SIZE_MAX_RATIO &&
fh >= boxRegion.width * HANDLE_SIZE_MIN_RATIO && fh <= boxRegion.width * HANDLE_SIZE_MAX_RATIO);
}
if (!bSizeOK)
{
continue;
}
if (fDis < fBestDis)
{
fBestDis = fDis;
nBestIdx = (int)i;
}
}
if (nBestIdx < 0 && nNearIdx >= 0)
{
// 没匹配上时打印区域内最近的凸起,便于排查阈值
const cv::Rect &roi = candList[nNearIdx].protr.roi;
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle",
"region box [%d %d %d %d] no match, nearest protrusion roi [%d %d %d %d] w/h ratio %0.2f %0.2f",
boxRegion.x, boxRegion.y, boxRegion.width, boxRegion.height,
roi.x, roi.y, roi.width, roi.height,
(float)roi.width / (float)boxRegion.width, (float)roi.height / (float)boxRegion.height);
}
if (nBestIdx >= 0)
{
roiHandle = candList[nBestIdx].protr.roi;
}
return nBestIdx;
}
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)保留原图通道:源图为彩色时用彩色裁剪图,否则回退灰度检测图
m_CheckResult_shareP->cutSrcimg = m_pImageAllResult->detColorImg.empty()
? m_pImageAllResult->detImg
: m_pImageAllResult->detColorImg;
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;
}