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.

2946 lines
93 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;
}
// 应用内 OpenCV 线程池随首个调用线程(通常为先绑核的检测线程)初始化成单线程,parallel_for 实际串行,
// 全分辨率 warp 要 150~200ms;这里把输出按行切条、用独立线程并行 warp,
// 结果与整图 warpAffine 逐像素一致(已离线验证 maxdiff=0),耗时降到约 1/3
static void WarpAffineBandParallel(const cv::Mat &src, cv::Mat &dst, const cv::Mat &affine,
const cv::Size &size, int nThreads = 8, int interp = cv::INTER_LINEAR,
int borderType = cv::BORDER_REPLICATE)
{
dst.create(size, src.type());
nThreads = std::max(1, std::min(nThreads, size.height));
int bandH = (size.height + nThreads - 1) / nThreads;
int nBands = (size.height + bandH - 1) / bandH;
auto warpBand = [&](int b)
{
// 新建线程默认继承调用线程的 CPU 亲和性(检测线程已绑单核),这里放开到全核,让条带真正并行
cpu_set_t cpus;
CPU_ZERO(&cpus);
long ncpu = sysconf(_SC_NPROCESSORS_ONLN);
for (long i = 0; i < ncpu; i++)
{
CPU_SET(i, &cpus);
}
pthread_setaffinity_np(pthread_self(), sizeof(cpus), &cpus);
int y0 = b * bandH;
int h = std::min(bandH, size.height - y0);
// warpAffine 默认接收正向矩阵(src->dst)并内部求逆;条带只把输出平移项减去 y0
cv::Mat matBand = affine.clone();
matBand.at<double>(1, 2) -= y0;
cv::warpAffine(src, dst(cv::Rect(0, y0, size.width, h)), matBand,
cv::Size(size.width, h), interp, borderType);
};
std::vector<std::thread> threads;
threads.reserve(nBands);
int b = 0;
try
{
for (; b < nBands; b++)
{
threads.emplace_back(warpBand, b);
}
}
catch (const std::system_error &)
{
// 线程资源不足:剩余条带留在本线程串行补完
}
for (auto &t : threads)
{
t.join();
}
for (; b < nBands; b++)
{
warpBand(b);
}
}
// 暗场复用明场定位:把字符串里的 "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(不再沿用明场检出的标签)
{
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
{
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、旋转矩形四角 -> 轴对齐矩形四角 的仿射(把产品摆正;用顶点计算,避开 RotatedRect 角度约定的歧义)
std::vector<cv::Point2f> vtx = CheckUtil::sort_vertices(m_outer_rroi); // 近似 左上/右上/左下/右下
int detW = std::max(1, cvRound(cv::norm(vtx[1] - vtx[0])));
int detH = std::max(1, cvRound(cv::norm(vtx[2] - vtx[0])));
cv::Point2f srcTri[3] = {vtx[0], vtx[1], vtx[2]};
cv::Point2f dstTri[3] = {cv::Point2f(0, 0), cv::Point2f((float)detW, 0), cv::Point2f(0, (float)detH)};
m_matSrc2Det = cv::getAffineTransform(srcTri, dstTri);
cv::invertAffineTransform(m_matSrc2Det, m_matDet2Src);
// 2、只对旋转矩形附近区域做 warp,提高效率;先裁到该区域,warp 矩阵同步做平移补偿
cv::Rect padRoi = m_outer_rroi.boundingRect() & imgRect;
if (padRoi.width <= 0 || padRoi.height <= 0)
{
m_pdetlog->AddCheckstr(PrintLevel_1, "Error", "deskew 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;
}
cv::Mat matPad = m_matSrc2Det.clone();
matPad.at<double>(0, 2) += matPad.at<double>(0, 0) * padRoi.x + matPad.at<double>(0, 1) * padRoi.y;
matPad.at<double>(1, 2) += matPad.at<double>(1, 0) * padRoi.x + matPad.at<double>(1, 1) * padRoi.y;
// 3、检测图:单通道灰度(彩色原图按 BGR 顺序转灰度,与 imread(path,0) 的亮度一致),按摆正矩阵重采样;
// 彩色结果图在 ResizeImg 里按显示尺寸直接 warp(比全分辨率彩色 warp 省很多)
m_deskewPadRoi = padRoi;
cv::Mat colorPatch = image(padRoi);
cv::Mat grayPatch;
if (colorPatch.channels() == 1)
{
grayPatch = colorPatch;
}
else if (colorPatch.channels() == 3)
{
cv::cvtColor(colorPatch, grayPatch, cv::COLOR_BGR2GRAY);
}
else
{
cv::cvtColor(colorPatch, grayPatch, cv::COLOR_BGRA2GRAY);
}
WarpAffineBandParallel(grayPatch, m_pImageAllResult->detImg, matPad, cv::Size(detW, detH));
// 彩色原图时再输出一张同样摆正的彩色检测图:供裁剪图(_RE_CutImg)保存时保留原图通道
// (检测算法仍只用灰度 detImg)
if (colorPatch.channels() == 3 || colorPatch.channels() == 4)
{
WarpAffineBandParallel(colorPatch, m_pImageAllResult->detColorImg, matPad, cv::Size(detW, detH));
}
else
{
m_pImageAllResult->detColorImg.release();
}
// 4、检测图坐标系下 m_outer_roi 即整张检测图;原图坐标系下 CutRoi 仍取旋转矩形外接区域(兼容旧语义/JSON)
m_outer_roi = cv::Rect(0, 0, detW, detH);
m_Crop_Roi_paramImg = m_outer_rroi.boundingRect() & imgRect;
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、参数(模板)坐标 -> 检测图坐标 的映射:
// 有对齐时先把参数点映射到原图再摆正;无对齐时参数按产品坐标系处理(与摆正后的检测图同向),
// 直接按旋转矩形中心平移(θ=0 时与旧的裁剪平移完全一致,旋转/平移变化时区域跟随产品)
{
cv::RotatedRect &rroi = m_outer_rroi;
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 = (cv::Mat_<double>(2, 3) << 1, 0, -rroi.center.x + detW * 0.5, 0, 1, -rroi.center.y + detH * 0.5);
}
}
long time_calan_e = CheckUtil::getcurTime();
m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", "-------------------------deskew--------------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");
/* 检测支架偏移 */
{
Function_Support_Det &supportDet = m_pFuntion->function.f_supportDet;
if (supportDet.bOpen && supportDet.supportRect.size.width > 0 && supportDet.supportRect.size.height > 0)
{
cv::RotatedRect &inner = m_inner_rroi;
cv::RotatedRect &support = supportDet.supportRect;
// x、y 中心偏移
float dx = fabs(inner.center.x - support.center.x);
float dy = fabs(inner.center.y - support.center.y);
// 旋转偏移(矩形旋转 90 度等价,角度取最小差)
float dangle = fabs(inner.angle - support.angle);
if (dangle > 45.0f)
{
dangle = fabs(dangle - 90.0f);
}
bool bOffset = (dx > supportDet.x_offset) || (dy > supportDet.y_offset) || (dangle > supportDet.r_offset);
// 缺失判定:内边缘面积小于支架模板面积的 75%
float innerArea = inner.size.width * inner.size.height;
float supportArea = support.size.width * support.size.height;
bool bLoss = (supportArea > 0.0f) && (innerArea < supportArea * 0.75f);
if (bOffset || bLoss)
{
QX_ERROR_INFO_ temerror;
temerror.Idx = m_pDetResult->pQx_ErrorList->size();
// roi 在原图坐标系,按摆正仿射转换到检测图(detImg)坐标系
cv::Rect roi = MatAffineRect(m_matSrc2Det, inner.boundingRect());
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 = inner.size.width;
float h = inner.size.height;
temerror.flen = (w > h ? w : h) * m_fImgage_Scale_X;
temerror.fbreadth = (w > h ? h : w) * m_fImgage_Scale_Y;
if (bLoss)
{
temerror.nconfig_qx_type = CONFIG_QX_NAME_support_loss;
}
else
{
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);
m_pdetlog->AddCheckstr(PrintLevel_0, "支架检测", "%s %d roi [%d %d %d %d] dx %f dy %f dangle %f",
temerror.qx_name.c_str(), temerror.Idx, roi.x, roi.y, roi.width, roi.height, dx, dy, dangle);
}
}
}
/* 把手缺失检测: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检测 */
{
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_matSrc2Det.empty() &&
m_deskewPadRoi.width > 0 && m_deskewPadRoi.height > 0 &&
CheckUtil::RoiInImg(m_deskewPadRoi, srcImg))
{
// M_disp = 显示缩放 ∘ M_src2det,并把输入平移到原图裁剪区(padRoi)的局部坐标
cv::Mat matDisp = m_matSrc2Det.clone();
double fsx = sz.width * 1.0 / m_pImageAllResult->detImg.cols;
double fsy = sz.height * 1.0 / m_pImageAllResult->detImg.rows;
matDisp.at<double>(0, 0) *= fsx;
matDisp.at<double>(0, 1) *= fsx;
matDisp.at<double>(0, 2) *= fsx;
matDisp.at<double>(1, 0) *= fsy;
matDisp.at<double>(1, 1) *= fsy;
matDisp.at<double>(1, 2) *= fsy;
matDisp.at<double>(0, 2) += matDisp.at<double>(0, 0) * m_deskewPadRoi.x + matDisp.at<double>(0, 1) * m_deskewPadRoi.y;
matDisp.at<double>(1, 2) += matDisp.at<double>(1, 0) * m_deskewPadRoi.x + matDisp.at<double>(1, 1) * m_deskewPadRoi.y;
cv::warpAffine(srcImg(m_deskewPadRoi), m_pImageAllResult->resultImg, matDisp, sz, cv::INTER_LINEAR, cv::BORDER_REPLICATE);
}
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;
/* 定位获取三个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];
}
if (!m_pEdge_Align_Result->tagroi.empty())
{
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;
}