Compare commits

..

57 Commits

Author SHA1 Message Date
liusiyang bf853544ec update 完善新的缺陷分类
1 week ago
liusiyang e8fa455ae8 update 分类不做softmax
1 week ago
liusiyang 4e95e93a91 update 优化存图路径
1 week ago
liusiyang 8701839e58 update 支架边缘改为灰度卡控
1 week ago
liusiyang fd03ebe0ae update 优化边缘找点
2 weeks ago
liusiyang 68acff677b v1.3.1
2 weeks ago
liusiyang c109f19f4f update 优化支架搜边
2 weeks ago
liusiyang 2d1ce72a6f feat 精细化找支架边缘
2 weeks ago
liusiyang ac5855208c fix -f批量跑卡住
2 weeks ago
liusiyang 5f34acfdce update 支架边缘精细化
2 weeks ago
liusiyang a24995500b feat 支架缺失,偏移旋转角
3 weeks ago
liusiyang ec5933dba1 update 优化支架边缘和绘制
3 weeks ago
liusiyang b36ffffda6 fix 标签灰阶能量计算为0
3 weeks ago
liusiyang e37e1848bd feat 优化-f批量测试
3 weeks ago
liusiyang 9f8d85871d update 支架偏移改为mm
3 weeks ago
liusiyang 7422a31627 feat 添加标签存图
3 weeks ago
liusiyang 30a04fb6bd update 重构把手检测
3 weeks ago
liusiyang 2db9918acb update 优化存图
3 weeks ago
liusiyang d7b515a0da feat 添加异步存图
3 weeks ago
liusiyang 950152e788 update 支架绘制添加开关
3 weeks ago
liusiyang 3ba4f47434 update tag使用tagRoi.boundingRect(),标签过参数
3 weeks ago
liusiyang 331da0fabd update 不区分黑白缺陷
3 weeks ago
liusiyang b822a4f856 v1.3.0
3 weeks ago
liusiyang a9ec94a936 feat 开放分类映射配置文件
3 weeks ago
liusiyang f21d3d95df feat 开放模型路径配置文件
3 weeks ago
liusiyang 1ce1c502e8 update 优化支架相关绘制
3 weeks ago
liusiyang 512261dd60 uodate 整理参数列表
3 weeks ago
liusiyang 7540f887a4 update 优化存图
3 weeks ago
liusiyang 047fbadb94 v1.2.0
3 weeks ago
liusiyang 46093b2b24 update 修改AI检测流程
3 weeks ago
liusiyang 03d8bdb614 update 添加开关,关闭AIClass,默认返回TBMark
3 weeks ago
liusiyang aecff1681d v1.1.0
3 weeks ago
liusiyang 6357f12f79 update 完善缺陷列表映射
3 weeks ago
liusiyang 2e9abdf857 feat 添加暗场模型
4 weeks ago
liusiyang c9b98e02d6 feat 返回缺陷信息新增缺陷等级字段(待实现缺陷分级逻辑)
4 weeks ago
liusiyang 58563d817c update 支架偏移改为计算到产品边缘
4 weeks ago
liusiyang c8ffa74833 update 产品不进行旋转
4 weeks ago
liusiyang e0913f2ac7 feat 添加支架,标签开关
4 weeks ago
liusiyang b7b8c100ef feat 清理把手检测多余配置
4 weeks ago
liusiyang d8e953e160 update 整合通道,相机区分明暗场
4 weeks ago
liusiyang 0f54b521f6 update 暗场使用明场定位结果
4 weeks ago
liusiyang 954d2591ac update 使用双显卡
4 weeks ago
liusiyang 97cd938656 update 裁剪图不改变通道
4 weeks ago
liusiyang 60fdbe7ebc update 内定为使用裁剪图
1 month ago
liusiyang feb5895b30 feat 添加旋转矫正背板
1 month ago
liusiyang 25319d34a2 update 结果图不修改为灰度图
1 month ago
liusiyang 43bd675b80 fix 日志并发崩溃
1 month ago
liusiyang 13973e9794 update 初步优化把手检测
2 months ago
liusiyang 4b7e1677ff update 初步优化把手检测
2 months ago
liusiyang 4e6b88e825 update 调整cpu核绑定
2 months ago
liusiyang acb5a9d872 feat 优化edge和blob耗时
2 months ago
liusiyang 226a1ad2af feat 预热模型
2 months ago
liusiyang 3938c9b1da update 优化edge耗时
2 months ago
liusiyang 92c603b2a6 update 支架参数移至检测项
2 months ago
liusiyang 5a79d82d9e feat 使用单显卡,添加循环测试
2 months ago
liusiyang d355c530c2 update 整理1
2 months ago
liusiyang 615c71144b feat 安装的头库文件添加后缀
2 months ago

@ -12,6 +12,7 @@
#include <vector>
#include <thread>
#include <mutex>
#include <atomic>
#include "NvInfer.h"
#include "cuda_runtime_api.h"
#include "AI_Factory.h"
@ -213,8 +214,8 @@ private:
int m_nALLStreamNum;
private:
// 上次使用的GPU stream Idx;
int m_nLast_GPUStreamIdx;
// 上次使用的GPU stream Idx(多线程并发调用,需原子自增避免竞争);
std::atomic<int> m_nLast_GPUStreamIdx;
};
#endif

@ -154,6 +154,7 @@ public:
private:
public:
std::shared_ptr<AIModel_Base> Defect;
std::shared_ptr<AIModel_Base> Defect_Dark; // 暗场检测模型(05DefectDark.engine)
std::shared_ptr<AIModel_Base> Class;
std::shared_ptr<AIModel_Base> Tag_Loc;

@ -20,7 +20,7 @@ AIModel_Impl::AIModel_Impl()
m_pNode_output_1 = NULL;
m_pNode_output_2 = NULL;
m_DetGPUStream.clear();
m_nLast_GPUStreamIdx = 0;
m_nLast_GPUStreamIdx.store(0);
m_nALLStreamNum = 0;
}
AIModel_Impl::~AIModel_Impl()
@ -169,6 +169,19 @@ int AIModel_Impl::Init(AIModelRun_Config config)
}
m_bInitSuccess = true;
// warm-up:首次推理会触发 TensorRT/CUDA 懒初始化(kernel 加载、workspace 分配、首次 enqueueV3 建图等),
// 提前用一张空图预热,避免首张检测图耗时飙升。
{
cv::Mat warmIn = InitMat(m_pNode_input_0->channel, m_pNode_input_0->width, m_pNode_input_0->height);
cv::Mat warmOut;
int warmRe = AIDet(warmIn, warmOut);
if (warmRe != 0)
{
printf("%s warm-up error = %d\n", m_modelRun_Config.strName.c_str(), warmRe);
}
}
return 0;
}
cv::Mat AIModel_Impl::InitMat(int channel, int w, int h)
@ -479,7 +492,11 @@ int AIModel_Impl::AI_Det_In_1_Out_1(Node_Config *pConfig_in, Node_Config *pConfi
{
// printf("=== s1 ");
std::shared_ptr<Det_GPU_Stram> pdetStream;
GetStream(pdetStream);
if (GetStream(pdetStream) != 0 || !pdetStream)
{
printf("AI_Det_In_1_Out_1: GetStream error \n");
return 3;
}
// printf("=== s2 ");
std::lock_guard<std::mutex> lock(pdetStream->AI_mutex);
// printf(" ss g %d s %d -- ", pdetStream->nGPUIdx, pdetStream->cuda_stream->nstreamIdx);
@ -508,22 +525,40 @@ int AIModel_Impl::AI_Det_In_1_Out_1(Node_Config *pConfig_in, Node_Config *pConfi
int AIModel_Impl::GetStream(std::shared_ptr<Det_GPU_Stram> &pdetStream)
{
int sidx = m_nLast_GPUStreamIdx;
if (m_nALLStreamNum <= 0 || m_DetGPUStream.empty())
{
return 1;
}
sidx++;
if (sidx >= m_nALLStreamNum)
// 多线程并发时原子轮询分配 stream:
int sidx = m_nLast_GPUStreamIdx.load();
while (true)
{
int next = sidx + 1;
if (next >= m_nALLStreamNum)
{
sidx = 0;
next = 0;
}
if (m_nLast_GPUStreamIdx.compare_exchange_weak(sidx, next))
{
sidx = next;
break;
}
// CAS 失败时 sidx 已被更新为当前实际值,重新计算 next
}
pdetStream = m_DetGPUStream.at(sidx);
m_nLast_GPUStreamIdx = sidx;
pdetStream = m_DetGPUStream.at(sidx);
return 0;
}
int AIModel_Impl::AI_Det_In_1_Out_1_class(unsigned char *p_indata_0, float *fmaxScore)
{
std::shared_ptr<Det_GPU_Stram> pdetStream;
GetStream(pdetStream);
if (GetStream(pdetStream) != 0 || !pdetStream)
{
printf("AI_Det_In_1_Out_1_class: GetStream error \n");
*fmaxScore = 0.0f;
return -3;
}
// printf("=== s2 ");
std::lock_guard<std::mutex> lock(pdetStream->AI_mutex);
// printf(" ss g %d s %d -- ", pdetStream->nGPUIdx, pdetStream->cuda_stream->nstreamIdx);
@ -564,8 +599,8 @@ int AIModel_Impl::F2softmaxId(float *data, int class_num, float *fmaxScore)
std::vector<float> output(cls_num);
for (int i = 0; i < output.size(); i++)
{
float outi = (float)exp(data[i]) / (float)total;
output[i] = outi;
// float outi = (float)exp(data[i]) / (float)total;
output[i] = data[i]; // 不做softmax处理(模型已经做了)
// output.push_back(outi);
// std::cout<<"output["<<i<<"]="<<output[i]<<std::endl;
}

@ -10,6 +10,71 @@
#include "AIModel_Impl.h"
#include <fstream>
#include <map>
// 模型路径配置文件(键值对格式,示例:"01DefectLight":/home/aidlux/BOE/UseModel_ZB/BM/01DefectLight.engine)
static const std::string MODEL_PATH_CONFIG_FILE = "/home/aidlux/BOE/UseModel_ZB/BM.txt";
// 去掉字符串首尾的空白字符和引号
static std::string TrimStr(const std::string &str)
{
size_t begin = str.find_first_not_of(" \t\r\n\"");
if (begin == std::string::npos)
{
return "";
}
size_t end = str.find_last_not_of(" \t\r\n\"");
return str.substr(begin, end - begin + 1);
}
// 读取模型路径键值对配置文件(每行格式:模型名:模型路径,支持 "模型名":模型路径 以及 # 注释行)
static std::map<std::string, std::string> ReadModelPathConfig(const std::string &strFilePath)
{
std::map<std::string, std::string> mapConfig;
std::ifstream ifs(strFilePath.c_str());
if (!ifs.is_open())
{
printf("Open model config file failed, use default paths: %s\n", strFilePath.c_str());
return mapConfig;
}
std::string strLine;
while (std::getline(ifs, strLine))
{
// 跳过空行和注释行
std::string strTrim = TrimStr(strLine);
if (strTrim.empty() || strTrim[0] == '#')
{
continue;
}
size_t pos = strLine.find(':');
if (pos == std::string::npos)
{
continue;
}
std::string strKey = TrimStr(strLine.substr(0, pos));
std::string strValue = TrimStr(strLine.substr(pos + 1));
if (!strKey.empty() && !strValue.empty())
{
mapConfig[strKey] = strValue;
}
}
return mapConfig;
}
// 优先从配置中取模型路径,配置中没有则使用默认路径
static std::string GetModelPath(const std::map<std::string, std::string> &mapConfig,
const std::string &strKey, const std::string &strDefaultPath)
{
std::map<std::string, std::string>::const_iterator it = mapConfig.find(strKey);
if (it != mapConfig.end())
{
return it->second;
}
return strDefaultPath;
}
std::shared_ptr<AIModel_Base> AIModel_Base::GetInstance()
{
std::shared_ptr<AIModel_Base> instance = std::make_shared<AIModel_Impl>();
@ -37,25 +102,39 @@ int AIFactory::InitALLAIModle(GPU_Config gupconfig)
return 1;
}
// 读取模型路径配置文件,配置文件中有的路径优先,没有的用默认路径
std::map<std::string, std::string> mapModelPaths = ReadModelPathConfig(MODEL_PATH_CONFIG_FILE);
if (!Defect)
{
Defect = AIModel_Base::GetInstance();
AIModel_Base::AIModelRun_Config edge_config;
edge_config.gpuconfig.copy(gupconfig);
edge_config.strPath = "/home/aidlux/BOE/UseModel_ZB/BM/DefectLight.engine";
edge_config.strName = "CA_Det";
edge_config.strPath = GetModelPath(mapModelPaths, "01DefectLight", "/home/aidlux/BOE/UseModel_ZB/BM/01DefectLight.engine");
edge_config.strName = "DefectLight";
edge_config.inputType = AIModel_Base::Input_CHW;
edge_config.Stream_num = 2;
edge_config.Stream_num = 4;
Defect->Init(edge_config);
}
if (!Defect_Dark)
{
Defect_Dark = AIModel_Base::GetInstance();
AIModel_Base::AIModelRun_Config edge_config;
edge_config.gpuconfig.copy(gupconfig);
edge_config.strPath = GetModelPath(mapModelPaths, "05DefectDark", "/home/aidlux/BOE/UseModel_ZB/BM/05DefectDark.engine");
edge_config.strName = "DefectDark";
edge_config.inputType = AIModel_Base::Input_CHW;
edge_config.Stream_num = 4;
Defect_Dark->Init(edge_config);
}
if (!Class)
{
Class = AIModel_Base::GetInstance();
AIModel_Base::AIModelRun_Config jbl_config;
jbl_config.gpuconfig.copy(gupconfig);
jbl_config.strPath = "/home/aidlux/BOE/UseModel_ZB/Class_10.engine";
jbl_config.strPath = GetModelPath(mapModelPaths, "Class", "/home/aidlux/BOE/UseModel_ZB/BM/Class.engine");
jbl_config.inputType = AIModel_Base::Input_CHW;
jbl_config.strName = "CA_Class";
jbl_config.strName = "Class";
jbl_config.IsClass = true;
Class->Init(jbl_config);
}
@ -64,10 +143,10 @@ int AIFactory::InitALLAIModle(GPU_Config gupconfig)
Tag_Loc = AIModel_Base::GetInstance();
AIModel_Base::AIModelRun_Config edge_config;
edge_config.gpuconfig.copy(gupconfig);
edge_config.strPath = "/home/aidlux/BOE/UseModel_ZB/BM/BadLabel.engine";
edge_config.strName = "TA_Det";
edge_config.strPath = GetModelPath(mapModelPaths, "02BadLabel", "/home/aidlux/BOE/UseModel_ZB/BM/02BadLabel.engine");
edge_config.strName = "Tag_Loc";
edge_config.inputType = AIModel_Base::Input_CHW;
edge_config.Stream_num = 2;
edge_config.Stream_num = 4;
Tag_Loc->Init(edge_config);
}
if (!Align_Outer)
@ -75,9 +154,10 @@ int AIFactory::InitALLAIModle(GPU_Config gupconfig)
Align_Outer = AIModel_Base::GetInstance();
AIModel_Base::AIModelRun_Config edge_config;
edge_config.gpuconfig.copy(gupconfig);
edge_config.strPath = "/home/aidlux/BOE/UseModel_ZB/BM/ProductLoc.engine";
edge_config.strName = "Align";
edge_config.strPath = GetModelPath(mapModelPaths, "04ProductLoc", "/home/aidlux/BOE/UseModel_ZB/BM/04ProductLoc.engine");
edge_config.strName = "Align_Outer";
edge_config.inputType = AIModel_Base::Input_CHW;
edge_config.Stream_num = 2;
Align_Outer->Init(edge_config);
}
if (!Align_Inner)
@ -85,9 +165,10 @@ int AIFactory::InitALLAIModle(GPU_Config gupconfig)
Align_Inner = AIModel_Base::GetInstance();
AIModel_Base::AIModelRun_Config edge_config;
edge_config.gpuconfig.copy(gupconfig);
edge_config.strPath = "/home/aidlux/BOE/UseModel_ZB/BM/ClampLoc.engine";
edge_config.strName = "Mark";
edge_config.strPath = GetModelPath(mapModelPaths, "03ClampLoc", "/home/aidlux/BOE/UseModel_ZB/BM/03ClampLoc.engine");
edge_config.strName = "Align_Inner";
edge_config.inputType = AIModel_Base::Input_CHW;
edge_config.Stream_num = 2;
Align_Inner->Init(edge_config);
}
m_bInitSucc = true;

@ -63,9 +63,7 @@ file(GLOB SRC_LISTS
${PROJECT_SOURCE_DIR}/AIEngineModule/src/*.cu
)
add_library(TY_Check SHARED ${SRC_LISTS})
set_target_properties(TY_Check PROPERTIES
OUTPUT_NAME "ZB_BACKPLATE"
)
target_link_libraries(TY_Check
nvinfer
Config
@ -80,11 +78,14 @@ set(ModuleName "")
# 自定义安装前缀
set(CMAKE_INSTALL_PREFIX /usr/local/zb_backplate CACHE PATH "Install path prefix" FORCE)
set(HEADER_FILES include/ImgCheckBase.h include/ImgCheckConfig.h)
# 库文件添加 _backplate 后缀
set_target_properties(TY_Check PROPERTIES OUTPUT_NAME "TY_Check_backplate")
# 安装动态库
install(TARGETS TY_Check
LIBRARY DESTINATION lib # 安装到 CMAKE_INSTALL_PREFIX/lib
ARCHIVE DESTINATION lib/static
RUNTIME DESTINATION bin
PUBLIC_HEADER DESTINATION include) # 安装到 CMAKE_INSTALL_PREFIX/include
# 安装头文件
install(FILES ${HEADER_FILES} DESTINATION include)
# 安装头文件(添加 _backplate 后缀)
install(FILES include/ImgCheckBase.h DESTINATION include RENAME ImgCheckBase_backplate.h)
install(FILES include/ImgCheckConfig.h DESTINATION include RENAME ImgCheckConfig_backplate.h)

@ -9,35 +9,30 @@
#ifndef AICommonDefine_H_
#define AICommonDefine_H_
// AI缺陷分类缺陷的种类
// AI缺陷分类缺陷的种类(只含主检测+分类模型输出的类别;支架偏移/支架缺失/把手缺失为传统算法检出,
// 铜箔标签缺陷(TBBQ)同时可能由标签检测上报;顺序需与分类模型输出的 label 顺序一致)
// 判定规则:
// 1) DarkAoHen(暗场凹痕)只有暗场相机存在,明场相机分到该类时按 QiTa(其他)处理;
// 2) DarkAoHen 目前不做检测,分到该类按 OK 处理(不上报缺陷,仅记日志);
// 3) label 超出本表范围(AI_CLass_QX_NAME_count)时,按 TBMark(铜箔标记)处理。
enum AI_CLass_QX_NAME_
{
AI_CLass_QX_NAME_aotudian, // 凹凸点
AI_CLass_QX_NAME_other, // 其他
AI_CLass_QX_NAME_line, // 线状
AI_CLass_QX_NAME_zangwu, // 脏污
AI_CLass_QX_NAME_dianzhuang, // 点状
AI_CLass_QX_NAME_posun, // 破损
AI_CLass_QX_NAME_xianwei, // 纤维
AI_CLass_QX_NAME_shuizi, // 水渍
AI_CLass_QX_NAME_danban, // 淡斑
AI_CLass_QX_NAME_fuchen, // 浮尘
AI_CLass_QX_NAME_DarkAoHen, // 暗场凹痕(只有暗场检出)
AI_CLass_QX_NAME_TBMark, // 铜箔标记
AI_CLass_QX_NAME_QiPao, // 气泡
AI_CLass_QX_NAME_TBBQ, // 标签(铜箔面不良标签)
AI_CLass_QX_NAME_QiTa, // 其他
AI_CLass_QX_NAME_count,
};
// 缺陷项对应在参数中的名称
// 缺陷项对应在参数中的名称(顺序需与分类模型输出的label顺序一致)
static const std::string AI_CLass_QX_NAME_Names[] =
{
"aotudian",
"qx",
"line",
"zangwu",
"dianzhuang",
"posun",
"xianwei",
"shuizi",
"danban",
"fuchen",
"DarkAoHen",
"TBMark",
"QiPao",
"TBBQ",
"QiTa",
};
#endif

@ -103,10 +103,12 @@ public:
int ncamId; // 相机ID
BaseCheckFunction *pBaseCheckFunction;
std::string strChannel; // 通道
std::string strProductID; // 产品ID(定位存图文件名首位)
int nthresholdvalue; // 背景阈值
int nAIErodesize; // 边缘腐蚀强度
bool bSaveResultImg; // 保存结果图片
SaveProcessType saveProcessImg; // 保存过程图片
bool bDetTag; // 标签检测开关(关闭时不跑 Tag_Loc 模型)
bool bUseDrawRoi_Check; // 是否用绘制的ROI进行校验
cv::Rect drawRoi; // 绘制的 ROi;
cv::Mat drawMask; // 绘制的maksk
@ -117,11 +119,13 @@ public:
void Init()
{
pBaseCheckFunction = NULL;
strProductID = "";
ncamId = 0;
nthresholdvalue = 1;
nAIErodesize = 7;
bSaveResultImg = false;
saveProcessImg = Save_Close;
bDetTag = true;
bUseDrawRoi_Check = false;
drawRoi = cv::Rect(0, 0, 0, 0);
if (!drawMask.empty())
@ -160,7 +164,7 @@ public:
private:
int InitModel_Big();
int Get_Edge(int AIModel_type, const cv::Mat &img, DetConfig *pDetConfig, std::string strChannel, std::vector<cv::RotatedRect> &RoiList, Mat &outmask);
int Get_Edge(int AIModel_type, const cv::Mat &img, DetConfig *pDetConfig, std::string strChannel, std::vector<cv::RotatedRect> &RoiList, Mat &outmask, bool bResizeOut = false);
int creatsavedir();

@ -0,0 +1,54 @@
/*
// 定位结果共享(暗场相机复用明场相机的定位结果)
// 背景:bright/dark 两台相机拍同一产品,暗场图不做定位模型(Align_Outer/Align_Inner/Tag_Loc),
// 直接复用明场相机的定位结果(外/内轮廓 roi、标签 roi、bigmask、偏移映射)。
// 约定:复用方(暗场)相机名/通道名把 "dark" 替换为 "bright" 得到提供方(明场)的相机名/通道名,
// 同产品内按 (产品ID, 相机名, 通道名) 精确配对。
// 生命周期:产品送图开始时清理(防上一轮的脏数据);产品检测完成时清理(释放内存)。
*/
#ifndef AlignShare_H_
#define AlignShare_H_
#include <string>
#include <memory>
#include <mutex>
#include <map>
#include <opencv2/opencv.hpp>
#include "AI_Edge_Algin.h"
class AlignShare
{
public:
static AlignShare &GetInstance();
// 提供方(明场)定位成功:发布可复用的定位结果
void PublishSuccess(const std::string &strProduct, const std::string &strCamera, const std::string &strChannel,
const std::shared_ptr<AI_Edge_Algin::Edge_AI_Result> &pResult,
const cv::Size &imgSize);
// 提供方(明场)定位失败:发布失败标记,让等待方立即走回退逻辑,不必干等超时
void PublishFailed(const std::string &strProduct, const std::string &strCamera, const std::string &strChannel);
// 查询(不等待):返回是否已有条目;bSucc 表示条目是成功结果(true)还是失败标记(false)
bool TryGet(const std::string &strProduct, const std::string &strCamera, const std::string &strChannel,
bool &bSucc, std::shared_ptr<AI_Edge_Algin::Edge_AI_Result> &pResult, cv::Size &imgSize);
// 产品生命周期清理(送图开始 / 检测完成)
void ClearProduct(const std::string &strProduct);
private:
AlignShare() {}
struct AlignShareEntry_
{
bool bSucc = false; // 条目是否代表"定位成功"
std::shared_ptr<AI_Edge_Algin::Edge_AI_Result> pResult; // 定位结果(成功时有效)
cv::Size imgSize = cv::Size(0, 0); // 结果对应的原图尺寸
};
std::string MakeKey(const std::string &strProduct, const std::string &strCamera, const std::string &strChannel);
std::map<std::string, AlignShareEntry_> m_mapShare; // key: 产品ID|相机名|通道名
std::mutex m_mtx;
};
#endif

@ -223,6 +223,31 @@ struct Detect_ROI_Config
roiList_Src.push_back(adjustedPolygon);
roiList_Show.push_back(adjustedPolygon_size);
}
// 用 2x3 仿射矩阵(参数/原图 -> 检测图)更新多边形;点变换后已在检测图坐标系,只做缩放
void Update_Mat(const std::vector<cv::Point> &list, const cv::Mat &matParamToDet, float fScale_x, float fScale_y)
{
std::vector<cv::Point> adjustedPolygon;
std::vector<cv::Point> adjustedPolygon_size;
for (const auto &point : list)
{
cv::Point2f pt = CheckUtil::transformPoint(cv::Point2f((float)point.x, (float)point.y), matParamToDet);
cv::Point adjustedPoint(cvRound(pt.x), cvRound(pt.y));
if (adjustedPoint.y < 0)
{
adjustedPoint.y = 0;
}
cv::Point sizep;
sizep.x = adjustedPoint.x * fScale_x;
sizep.y = adjustedPoint.y * fScale_y;
adjustedPolygon.push_back(adjustedPoint);
adjustedPolygon_size.push_back(sizep);
}
// 将调整后的多边形添加到roiList_Src
roiList_Src.push_back(adjustedPolygon);
roiList_Show.push_back(adjustedPolygon_size);
}
// 判断点是否在指定idx的ROI内
bool isPointInROI(int idx, const cv::Point &point)
{

@ -25,6 +25,30 @@
#include <thread>
#include <opencv2/opencv.hpp>
using namespace std;
// 掩膜主体(圆角矩形)外的凸起区域(如把手)的描述
struct Mask_Protrusion_
{
int nCorner; // 就近角(产品主轴坐标系):0 左上 1 右上 2 左下 3 右下
cv::Rect roi; // 凸起外接矩形(输入图坐标系,已裁剪到图像范围内)
cv::Point2f ptCenter; // 凸起质心(输入图坐标系)
float fAngle; // 边缘倾角(度,日志排查用)
float fLen; // 凸起外接矩形长边(像素)
float fDepth; // 凸起相对主体直边的最大伸出深度(像素)
float fArea; // 凸起面积(掩膜像素数)
float fFillRatio; // 凸起外接矩形内掩膜像素占比
Mask_Protrusion_()
{
nCorner = 0;
ptCenter = cv::Point2f(0.0f, 0.0f);
fAngle = 0.0f;
fLen = 0.0f;
fDepth = 0.0f;
fArea = 0.0f;
fFillRatio = 0.0f;
}
};
class CheckUtil
{
public:
@ -33,6 +57,10 @@ public:
static std::string getCurTimeHMS();
static std::string Op_float2String(float nvalue);
static int64_t getSnowId();
// 当前日期(年月日, 8 位纯数字), 形如 "20261029", 便于存图文件名检索/排序
static std::string getCurrentDateYMD();
// 生成不重名的存图完整路径 = strDir + strPrefix + [ "_序号" ] + "_" + strName + strExt
static std::string GetUniqueImgPath(const std::string &strDir, const std::string &strPrefix, const std::string &strName, const std::string &strExt);
static bool JudgRect(cv::Rect roi, int img_w, int img_h);
static bool JudgRect_SZ(cv::Rect roi, int w, int h);
static bool compareIgnoreCase(const std::string &str1, const std::string &str2);
@ -58,16 +86,59 @@ public:
// 找图片的最大外轮廓
static std::vector<cv::Point> getLargestContourROI(const cv::Mat &binaryImg, bool &found);
// 从外轮廓掩膜(outer定位mask)中分离凸起区域(如把手):
// 主体是圆角矩形、把手基本出现在四角附近:逐行/逐列扫描掩膜边界,为每条边缘搜索带内点数最多的直线;
// 掩膜中超出四条边缘(超过容差带)的像素按连通域切分成凸起区域(含就近角/质心/面积/深度/填充率)。
// binMask:掩膜(非零为前景,兼容 0/1 与 0/255);protrusions:输出的凸起列表(按面积从大到小)
// 可调参数(应对实际情况):
// nMinDepth:凸起最小深度(输入图像素;<=0 时按掩膜短边/150 自动取值,下限 6)
// 调大:只报明显外凸的把手,浅把手会漏检,可压住边缘毛刺/噪声;
// 调小:浅凸起与噪声都易报出(误报变多)。mask 由模型输出放大而来时直边带量化台阶
// (台阶高≈量化步长),建议取 2 个量化步长左右(调用方见 CheckHandleByBigMask)。
// nMinArea:凸起最小面积(输入图像素²;<=0 时按 nMinDepth² 自动取值)
// 调大:小碎块被过滤,把手很小或被遮挡一部分时可能漏检;
// 调小:碎块残留为候选的几率变大(仍会被 nMinDepth 再过滤一遍)。
// nEdgeMargin:边缘外扩量(输入图像素;<=0 时自动取“最小深度的一半”)
// 把四条边缘线整体向外推该距离后再判凸起,等效于把凸起区域相应减小:
// 调大:边缘顺边毛刺/线条偏差被吞掉,凸起区域更小更干净;过大时浅把手漏检;
// 调小:更灵敏,但边缘毛刺容易连进凸起区域。
// 边缘拟合的细节参数(带宽容差/斜率上限)见 FitEdgeLine 注释。
// pBodyQuad:可选输出,拟合出的主体四边形(左上/右上/右下/左下,输入图坐标),仅 -s 存图观察用
// 返回:0 成功、1 掩膜为空、2 无有效前景
static int GetMaskProtrusions(const cv::Mat &binMask, std::vector<Mask_Protrusion_> &protrusions,
int nMinDepth = 0, int nMinArea = 0, std::vector<cv::Point2f> *pBodyQuad = NULL,
int nEdgeMargin = 0);
static std::string GetRectString(cv::Rect rect);
//创建目录
static int CreateDir(const std::string &dir);
// 异步存图
static int SaveImgAsync(const std::string &path, const cv::Mat &img);
// 点的距离是否过小
static bool bcalDis(cv::Point p1, cv::Point p2, int disT);
static double calDis(cv::Point p1, cv::Point p2);
static void PrintRect(cv::Rect roi, std::string str = "");
static int cutSmallImg(cv::Mat img, std::vector<cv::Rect> &samllRoiList, cv::Rect config_roi, int config_SmallImg_Width, int config_SmallImg_Height, int config_MinOverlap_Width, int config_MinOverlap_Height);
static cv::Point2f transformPoint(const cv::Point2f &point, const cv::Mat &transform_matrix);
// 依目标图像通道数返回可用的绘制颜色:
// 1 通道(灰度) -> 统一白色,保证可见
// 4 通道(BGRA) -> 补 alpha = 255
// 3 通道 -> 原样返回
static inline cv::Scalar GetDrawColor(const cv::Mat &img, const cv::Scalar &color)
{
if (img.channels() == 1)
{
return cv::Scalar(255);
}
if (img.channels() == 4)
{
return cv::Scalar(color[0], color[1], color[2], 255);
}
return color;
}
};
// 无参数重载:直接返回原字符串,避免 -Wformat-security 警告
static inline std::string str_Format(const std::string &format)

@ -13,7 +13,22 @@
// 检测分析线程类数目
#define IMGCHECKANALYSISY_NUM 2
// 检测线程绑定的 CPU 核带(核带下沉到库内部,调用方传入的 nCpu_start_Idx / nCpu_num 不再生效)
// 分配规则:第 k 个检测线程绑定 核 = CHECK_CPU_BAND_START + (k % CHECK_CPU_BAND_NUM)
// 当前 4 个检测线程落在 15,16,17,18(逻辑核 c 与 c+16 为超线程兄弟,故物理核为 15,0,1,2)
// 注意:CHECK_CPU_BAND_NUM 必须 >= 相机数 * IMGCHECKANALYSISY_NUM,否则取模回绕会导致两个检测线程绑同一核
#define CHECK_CPU_BAND_START 15
#define CHECK_CPU_BAND_NUM 8
// ============== 暗场相机复用明场相机的定位结果 ==============
// 相机名/通道名含 "dark" 的相机不做定位模型(Align_Outer/Align_Inner/Tag_Loc),
// 把 "dark" 替换为 "bright" 得到提供方(明场)的相机名/通道名,同产品内精确配对复用其定位结果。
// 复用等待有超时上限;明场定位失败/超时/无明场图时,暗场回退为自行定位(与旧流程一致)。
#define ALIGN_REUSE_ENABLE 1 // 0=关闭复用(全部相机自行定位)
#define ALIGN_REUSE_FOLLOWER_KEY "dark" // 复用方(暗场)关键字
#define ALIGN_REUSE_SOURCE_KEY "bright" // 提供方(明场)关键字
#define ALIGN_REUSE_WAIT_TIMEOUT_MS 5000 // 等待明场定位结果的超时(毫秒)
#define ALIGN_REUSE_POLL_MS 20 // 等待轮询间隔(毫秒)
// 相机ID 的相关定义
enum Camera_IDX

@ -14,6 +14,7 @@
#include <string.h>
#include <sys/time.h>
#include <vector>
#include <mutex>
#include "ImgCheckConfig.h"
#include "CheckErrorCodeDefine.hpp"
using namespace std;
@ -62,17 +63,29 @@ struct DetLog
bool bPrintStr; // 是否打印日志
int addLogLevel;
// 保护 logList:同一个 DetLog(如图片结果的日志)会被实例主线程(Run/CheckRun)
// 与任务线程(AI 推理/分类) 并发写入,无锁 push_back 会造成 vector 堆破坏。
std::mutex mtx_log;
DetLog()
{
Init();
}
void Init()
{
std::lock_guard<std::mutex> lock(mtx_log);
logList.clear();
bPrintStr = false;
addLogLevel = DET_LOG_LEVEL_3;
}
// 线程安全地获取日志快照
std::vector<std::string> GetLogs()
{
std::lock_guard<std::mutex> lock(mtx_log);
return logList;
}
template <typename... Args>
void AddCheckstr(PrintLevel_ step, std::string stepStr, const std::string &format, Args... args)
{
@ -105,6 +118,7 @@ struct DetLog
printf("%s\n", str.c_str());
}
std::lock_guard<std::mutex> lock(mtx_log);
logList.push_back(str);
}
template <typename... Args>
@ -141,16 +155,20 @@ struct DetLog
if (LogLevel <= addLogLevel)
{
std::lock_guard<std::mutex> lock(mtx_log);
logList.push_back(str);
}
}
void printLog(std::string str)
{
printf("===========================%s==============================\n", str.c_str());
{
std::lock_guard<std::mutex> lock(mtx_log);
for (auto strl : logList)
{
printf("%s\n", strl.c_str());
}
}
printf("==========================================================\n");
}
};

@ -62,6 +62,10 @@ public:
cv::Rect CropRoi = cv::Rect(0, 0, 0, 0);
// 检测的原始图片
cv::Mat detImg;
// 裁剪后的彩色检测图(保留原图通道,供裁剪图 _RE_CutImg 输出;灰度源图时为空)
cv::Mat detColorImg;
// 只做"裁剪"、不叠加任何 mask 的检测图(同 detImg 尺寸/坐标系)
cv::Mat detImgNoMask;
// AI 推理的mask图片
cv::Mat AIMaskImg;
// 结果图片

@ -4,6 +4,9 @@
#define _ImageDetConfig_HPP_
#include <string>
#include <memory>
#include <mutex>
#include <set>
#include <opencv2/opencv.hpp>
#include "ImgCheckConfig.h"
#include "CheckErrorCodeDefine.hpp"
@ -27,6 +30,7 @@ struct QX_ERROR_INFO_
float fbreadth;
int nconfig_qx_type;
std::string qx_name;
std::string qx_level; // 缺陷等级
int maxValue;
int whiteOrBlack;
float grayDis;
@ -53,6 +57,7 @@ struct QX_ERROR_INFO_
flen = 0;
nconfig_qx_type = 0;
qx_name = "";
qx_level = "";
maxValue = 0;
grayDis = 0;
fUpIou = 0;
@ -79,6 +84,7 @@ struct QX_ERROR_INFO_
<< ", Flen: " << flen
<< ", nconfig_qx_type: " << nconfig_qx_type
<< ", QX Name: " << qx_name
<< ", QX Level: " << qx_level
<< ", Max Value: " << maxValue
<< ", Gray Dis: " << grayDis
<< ", fUpIou: " << fUpIou
@ -92,6 +98,7 @@ struct One_Image_CheckResult_
cv::Rect CutRoi; // 当前检测图片 对应的裁切区域
cv::Rect Param_CropRoi; // 参数模版 对应的裁切区域
std::shared_ptr<std::vector<QX_ERROR_INFO_>> pQx_ErrorList; // 缺陷错误信息
cv::Mat detToSrcMat; // 检测图坐标 -> 原图坐标 的 2x3 仿射矩阵(空表示按 CutRoi 平移映射)
void print(std::string str = "")
{
std::cout << str << ": "
@ -280,11 +287,37 @@ struct Align_Result
printf("scale x %f y %f ,offt x %d y %d\n", fCropROI_Scale_ParmToDet_X, fCropROI_Scale_ParmToDet_Y, offt_x, offt_y);
}
};
// 送图状态(同一产品的所有相机共享):供"暗场等待明场定位结果"判断源图是否还会到来
struct AlignPushState
{
std::mutex mtx;
bool bPushEnd = false; // 本产品所有图片是否已送完
std::set<std::string> cameraImgSet; // 已有图的相机名
void AddCameraImg(const std::string &strCam)
{
std::lock_guard<std::mutex> lock(mtx);
cameraImgSet.insert(strCam);
}
void SetPushEnd()
{
std::lock_guard<std::mutex> lock(mtx);
bPushEnd = true;
}
// 该相机的图是否还有可能到来:送图未结束,或送图结束时该相机已有图
bool MayStillCome(const std::string &strCam)
{
std::lock_guard<std::mutex> lock(mtx);
return (!bPushEnd) || (cameraImgSet.count(strCam) > 0);
}
};
// 产品相关的 基础检测结果
struct ProductBaseResult
{
std::string strproductName = ""; // 产品 ID
std::shared_ptr<DetLog> detlog;
std::shared_ptr<AlignPushState> alignPushState = std::make_shared<AlignPushState>(); // 送图状态(暗场复用明场定位用)
};
// 相机相关的 基础检测结果
struct CameraBaseResult

@ -49,6 +49,68 @@ enum AT_THRESHOLD_TYPE_
// 全局静态变量, 记录图像灰度值异常累计数量
static int g_nImgBrightnessErrorCount = 0;
// 单个把手区域的判定结果
struct Handle_Region_Result_
{
int nIdx; // 区域序号(1开始)
cv::Rect boxRegion; // 区域外接矩形(检测图坐标),宽高用于卡控
bool bFound; // 该区域附近是否找到匹配的把手
cv::Rect roiHandle; // 匹配到的把手 roi(bFound=true 时有效)
Handle_Region_Result_()
{
nIdx = 0;
boxRegion = cv::Rect(0, 0, 0, 0);
bFound = false;
roiHandle = cv::Rect(0, 0, 0, 0);
}
};
// 支架偏移检测的绘制信息(CheckRun 支架块计算 -> DrawResult_Step_1 绘制)
// 坐标均为原图坐标,绘制时经 m_matSrc2Det 映射到检测图后再缩放到结果图
struct Support_Draw_Info_
{
// 参与判定的单个方向:实测/标准距离 + 是否超标准(超标准画红色)
struct Edge_Draw_
{
std::string strEdge; // left/right/up/down
float fDist; // 实测距离(原图像素)
float fStandard; // 标准距离(原图像素)
bool bExceed; // |fDist-fStandard| 超限差(x_offset) -> 红色
cv::Point2f ptEdge; // 连线端点:所计算边缘上、支架中心行/列处的点(原图坐标)
cv::Point2f ptEdgeA; // 所计算边缘线段端点A(原图坐标)
cv::Point2f ptEdgeB; // 所计算边缘线段端点B(原图坐标)
Edge_Draw_()
{
fDist = 0.0f;
fStandard = 0.0f;
bExceed = false;
ptEdge = cv::Point2f(0.0f, 0.0f);
ptEdgeA = cv::Point2f(0.0f, 0.0f);
ptEdgeB = cv::Point2f(0.0f, 0.0f);
}
};
bool bValid; // 本次是否有效计算出支架偏移几何
bool bOffset; // 是否判偏移 NG(十字颜色:红=NG)
cv::Rect rpRect; // 产品区域外接矩形(原图坐标)
cv::Rect rsRect; // 支架区域外接矩形(原图坐标)
cv::Point2f ptCenter; // 支架中心(原图坐标)
std::vector<Edge_Draw_> edgeList; // 各参与判定方向的绘制信息
Support_Draw_Info_()
{
Reset();
}
void Reset()
{
bValid = false;
bOffset = false;
rpRect = cv::Rect(0, 0, 0, 0);
rsRect = cv::Rect(0, 0, 0, 0);
ptCenter = cv::Point2f(0.0f, 0.0f);
edgeList.clear();
}
};
class ImgCheckAnalysisy : public ImgCheckBase
{
@ -117,6 +179,28 @@ private:
// 检测
int CheckRun();
int AI_Edge(const cv::Mat &img, cv::RotatedRect &outerRoi, cv::RotatedRect &innerRoi, std::vector<cv::RotatedRect> &tagroiList);
// ===== 暗场复用明场定位结果(见 Define_Base.h 的 ALIGN_REUSE_* 配置) =====
// 取当前图的产品ID/相机名(优先 productBaseResult/cameraBaseResult,兕底送图信息)
void GetProductCameraStr(std::string &strProduct, std::string &strCamera);
// 判断并在需要时等待"源相机(明场)"的定位结果;成功返回 true(结果已装入成员变量)
// 注意:标签检测不在此复用,由 CheckTagByBigMask 独立执行
bool TryReuseAlignFromSource(std::string &strSrcCamera, std::string &strSrcChannel, int &nWaitMs);
// 发布本图定位结果/失败标记(供同产品其它相机复用)
void PublishAlignShare(bool bSucc);
// 用 outer 定位 mask(bigmask) 检测把手:拟合主体上/下/左/右四条边缘,
// 把边缘外的凸起按连通域分离为把手候选(见 CheckUtil::GetMaskProtrusions),
// 再与参数的把手区域匹配;某个区域附近找不到匹配凸起 → 该区域把手缺失
// -s(存过程图)时输出 {通道}_handle_det.jpg:绿色四边形=拟合主体、红框=凸起(带编号/角/深度)、
// 蓝框=参数把手区域、绿框=匹配到的把手,便于从图像排查
int CheckHandleByBigMask(const cv::Mat &bigmask);
// 标签检测(独立步骤):用 (图像 + bigmask) 跑 Tag_Loc 模型,检出结果存 m_tag_roiList
// 明场传自身 bigmask;暗场不做定位模型,传复用明场的 bigmask
int CheckTagByBigMask(const cv::Mat &img, const cv::Mat &bigmask);
// 在某个把手区域附近找宽高匹配的凸起:返回凸起在 candList 中的下标(-1=没找到)
// 匹配参数(搜索范围 handle_search_ratio、大小比区间 handle_size_min/handle_size_max)
// 可 JSON(Handle_Detect.form.handle_param)配置,未配置时用 cpp 顶部宏默认值,见 ImgCheckAnalysisy.cpp 参数注释
int MatchHandleRegion(const std::vector<Mask_Protrusion_> &candList, const std::vector<bool> &usedList,
const cv::Rect &boxRegion, cv::Rect &roiHandle);
// 计算产品尺寸
int CalProductSize();
// 图片预处理
@ -237,6 +321,12 @@ private:
cv::Rect m_inner_roi;
cv::Rect m_Crop_Roi_paramImg; // 裁切在参数模板图上的 位置。
// 检测图裁剪坐标映射(不做旋转摆正):用 m_outer_rroi 外接区域裁剪,原图 -> 检测图为平移
cv::Mat m_matSrc2Det; // 原图 -> 检测图(裁剪) 2x3 仿射矩阵(纯平移)
cv::Mat m_matDet2Src; // 检测图(裁剪) -> 原图 2x3 仿射矩阵(纯平移)
cv::Mat m_matParam2Det; // 参数(模板)坐标 -> 检测图(裁剪) 2x3 仿射矩阵
cv::Rect m_deskewPadRoi; // 检测图在原图上的裁剪区域
std::string m_strCurDetChannel; // 当前处理的图片通道
// // 检测控制参数
@ -289,6 +379,16 @@ private:
cv::RotatedRect m_outer_rroi;
cv::RotatedRect m_inner_rroi;
std::vector<cv::RotatedRect> m_tag_roiList;
// 把手检测结果(从 outer 定位 mask 分离的凸起区域 + 各把手区域判定结果)
std::vector<Mask_Protrusion_> m_HandleProtrusionList; // 分离出的凸起区域(把手候选:roi/质心/深度/就近角等)
std::vector<Handle_Region_Result_> m_HandleRegionResultList; // 各把手区域(最多4个)的判定结果
std::vector<cv::Point2f> m_HandleBodyQuad; // 拟合出的主体四边形(TL/TR/BR/BL,原图坐标;支架偏移检测复用其四边抗凸起)
bool m_bHandleDetSucc; // 本次是否完成把手检测
bool m_bHandleFound; // 所有非空把手区域是否都找到了把手
bool m_bHandleExpect; // 是否开启了把手缺失检测(且有非空把手区域)
// 支架偏移检测的绘制信息(CheckRun 支架块填充,DrawResult_Step_1 绘制)
Support_Draw_Info_ m_SupportDrawInfo;
};
#endif

@ -44,23 +44,27 @@ struct VERSION_INFO
int ResultVersion = RESULT_VERSION;
int InterfaceVersion = 0;
};
// 检测错误代码
// 检测错误代码(与缺陷列表一致;支架偏移/支架缺失/把手缺失为传统算法检出,无AI分类)
enum ERROR_TYPE_
{
ERROR_TYPE_OK, // 1 疑是
ERROR_TYPE_aotudian, // 2 凹凸点
ERROR_TYPE_other, // 3 其他
ERROR_TYPE_line, // 4 线状
ERROR_TYPE_zangwu, // 5 脏污
ERROR_TYPE_edge, // 6 边缘
ERROR_TYPE_ymhs, // 7 研磨划伤
ERROR_TYPE_dianzhuang, // 8 点状
ERROR_TYPE_posun, // 9 破损
ERROR_TYPE_xianwei, // 10 纤维
ERROR_TYPE_shuizi, // 11 水渍
ERROR_TYPE_danban, // 12 淡斑
ERROR_TYPE_fuchen, // 13 浮尘
ERROR_TYPE_tag, // 14 标签
ERROR_TYPE_OK, // 0 好品
ERROR_TYPE_PIN_PoSun, // 1 PIN破损
ERROR_TYPE_PIN_QueShi, // 2 PIN缺失
ERROR_TYPE_BTB_DuanLie, // 3 BTB断裂
ERROR_TYPE_LianXi, // 4 连锡
ERROR_TYPE_QueXi, // 5 缺锡
ERROR_TYPE_YiWu, // 6 异物
ERROR_TYPE_PIN_PianYi, // 7 PIN偏移
ERROR_TYPE_C_BianXing, // 8 C型卡口异常
ERROR_TYPE_Panel, // 9 Panel
ERROR_TYPE_ZJ_PianYi, // 1 支架偏移
ERROR_TYPE_ZJ_QueShi, // 2 支架缺失
ERROR_TYPE_CGMo_QueShi, // 3 把手缺失
ERROR_TYPE_DarkAoHen, // 4 暗场凹痕
ERROR_TYPE_QiPao, // 5 气泡
ERROR_TYPE_TBBQ, // 6 铜箔标签
ERROR_TYPE_TBMark, // 7 铜箔标记
ERROR_TYPE_QiTa, // 8 其他
ERROR_TYPE_COUNT,
};
extern std::vector<std::string> QX_Result_Names;
@ -280,6 +284,7 @@ struct QXImageResult
int type; // 缺陷类型
std::string qx_Code; // 缺陷code
std::string strTypeName; // 缺陷名称
std::string qx_level; // 缺陷等级
cv::Mat srcImg; // 缺陷原始图片
cv::Mat resizeImg; // 缺陷缩略图
@ -344,6 +349,7 @@ struct QXImageResult
max_v = 0;
strTypeName = "";
qx_Code = "";
qx_level = "";
len = 0;
breadth = 0;
idx = 0;

@ -9,6 +9,7 @@
#include "AI_Edge_Algin.h"
#include "CheckErrorCodeDefine.hpp"
#include <thread>
#define EDGE_GPU 0
@ -184,8 +185,8 @@ AI_Edge_Algin::AI_Edge_Algin(std::shared_ptr<DetLog> &log_ref)
m_bModelSucc = false;
m_bModel_Mark_Succ = false;
m_bshowimg = false;
m_strRootPath_Big = "/home/aidlux/BOE/Algin/";
m_strRootPath_Mark = "/home/aidlux/BOE/MarkLine/";
m_strRootPath_Big = "/home/aidlux/BOE/Algin/BM/";
m_strRootPath_Mark = "/home/aidlux/BOE/MarkLine/BM/";
creatsavedir();
std::string m_strSavePath;
@ -231,8 +232,10 @@ int AI_Edge_Algin::Detect(const cv::Mat &img, DetConfig *pDetConfig, std::shared
return 1;
}
// 1、初步定位 找到产品大致区域
int re = 0;
// 1、初步定位 找到产品大致区域(三个模型输入输出尺寸不同,各自 resize;推理相互独立,并行执行)
int re0 = 0;
int re1 = 0;
int re2 = 0;
std::vector<cv::RotatedRect> Big_roi;
std::vector<cv::RotatedRect> Small_roi;
@ -241,35 +244,62 @@ int AI_Edge_Algin::Detect(const cv::Mat &img, DetConfig *pDetConfig, std::shared
cv::Mat Small_outimg;
cv::Mat Tag_outimg;
re = Get_Edge(0, img, pDetConfig, m_pDetConfig->strChannel, Big_roi, Big_outimg);
if (re != 0)
std::vector<std::thread> vecThreads;
re0 = Get_Edge(0, img, pDetConfig, m_pDetConfig->strChannel, Big_roi, Big_outimg, true);
cv::Rect Big_roi_rect = Big_roi[0].boundingRect() & cv::Rect(0, 0, img.cols, img.rows);
cv::Mat Big_outimg_src = img(Big_roi_rect).clone();
vecThreads.emplace_back([&]() { re1 = Get_Edge(1, Big_outimg_src, pDetConfig, m_pDetConfig->strChannel, Small_roi, Small_outimg, false); });
if (pDetConfig->bDetTag)
{
vecThreads.emplace_back([&]() { re2 = Get_Edge(2, Big_outimg_src, pDetConfig, m_pDetConfig->strChannel, Tag_roi, Tag_outimg, false); });
}
else
{
m_pdetlog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "bDetTag close, skip Tag_Loc");
}
for (auto &t : vecThreads)
{
m_pdetlog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "AICheck_Edge_Big----error %d ", re);
t.join();
}
if (re0 != 0)
{
m_pdetlog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "AICheck_Edge_Big----error %d ", re0);
if (m_pDetConfig->IsSaveProcessImg())
{
cv::imwrite(str_error, img);
}
return re;
return re0;
}
re = Get_Edge(1, img, pDetConfig, m_pDetConfig->strChannel, Small_roi, Small_outimg);
if (re != 0)
if (re1 != 0)
{
// Align_Inner(支架)未检出有效轮廓(4=无轮廓 / 6=轮廓点太少):
// 支架检测开启时不视为定位失败,继续带 smallmask(可能为空/全零) 返回,
// 由后续"支架缺失"判定(support_min_area)上报 ZJ_QueShi NG
bool bSupportDetOn = (pDetConfig->pBaseCheckFunction != NULL && pDetConfig->pBaseCheckFunction->supportDet.bDetSupport);
if (bSupportDetOn && (re1 == 4 || re1 == 6))
{
m_pdetlog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "AICheck_Edge_Small no contour(%d), treat as support missing ", re1);
re1 = 0;
}
else
{
m_pdetlog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "AICheck_Edge_Small----error %d ", re);
m_pdetlog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "AICheck_Edge_Small----error %d ", re1);
if (m_pDetConfig->IsSaveProcessImg())
{
cv::imwrite(str_error, img);
}
return re;
return re1;
}
re = Get_Edge(2, img, pDetConfig, m_pDetConfig->strChannel, Tag_roi, Tag_outimg);
if (re != 0)
}
if (re2 != 0)
{
m_pdetlog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "AICheck_Tag----error %d ", re);
m_pdetlog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "AICheck_Tag----error %d ", re2);
if (m_pDetConfig->IsSaveProcessImg())
{
cv::imwrite(str_error, img);
}
return re;
return re2;
}
m_pCheckResult_Aling->bigroi = Big_roi;
@ -278,7 +308,6 @@ int AI_Edge_Algin::Detect(const cv::Mat &img, DetConfig *pDetConfig, std::shared
m_pCheckResult_Aling->bigmask = Big_outimg;
m_pCheckResult_Aling->smallmask = Small_outimg;
m_pCheckResult_Aling->tagmask = Tag_outimg;
pCheckResult_Aling = m_pCheckResult_Aling;
return 0;
@ -348,7 +377,7 @@ int AI_Edge_Algin::InitModel_ALL()
return 0;
}
int AI_Edge_Algin::Get_Edge(int AIModel_type, const cv::Mat &img, DetConfig *pDetConfig, std::string strChannel, std::vector<cv::RotatedRect> &RoiList, Mat &outmask)
int AI_Edge_Algin::Get_Edge(int AIModel_type, const cv::Mat &img, DetConfig *pDetConfig, std::string strChannel, std::vector<cv::RotatedRect> &RoiList, Mat &outmask, bool bResizeOut)
{
std::shared_ptr<AIModel_Base> pBackPlate_Align;
switch (AIModel_type)
@ -370,15 +399,20 @@ int AI_Edge_Algin::Get_Edge(int AIModel_type, const cv::Mat &img, DetConfig *pDe
sz.width = pBackPlate_Align->input_0.width;
sz.height = pBackPlate_Align->input_0.height;
cv::Mat detImg;
cout<< pDetConfig->strChannel << ": " << "---Get_Edge-resize-" << to_string(AIModel_type) <<"-- ";
cout << "imgSize: " << img.size() << ", " << "detImgSize: " << detImg.size() << ", " << "szSize: " << sz << endl;
// cout<< pDetConfig->strChannel << ": " << "---Get_Edge-resize-" << to_string(AIModel_type) <<"-- ";
// cout << "imgSize: " << img.size() << ", " << "detImgSize: " << detImg.size() << ", " << "szSize: " << sz << endl;
cv::resize(img, detImg, sz);
int re = 0;
cv::Mat mask;
if (detImg.channels() != 1)
// 模型输入要求单通道;原图为彩色时按 BGR 顺序转灰度(与 imread(path,0) 的亮度一致)
if (detImg.channels() == 3)
{
cv::cvtColor(detImg, detImg, cv::COLOR_RGB2GRAY);
cv::cvtColor(detImg, detImg, cv::COLOR_BGR2GRAY);
}
else if (detImg.channels() == 4)
{
cv::cvtColor(detImg, detImg, cv::COLOR_BGRA2GRAY);
}
re = pBackPlate_Align->AIDet(detImg, mask);
@ -392,19 +426,13 @@ int AI_Edge_Algin::Get_Edge(int AIModel_type, const cv::Mat &img, DetConfig *pDe
if (pDetConfig->pBaseCheckFunction->saveImg.bSaveAlginImg)
{
creatsavedir();
static int sdk = 0;
std::string str = m_strSavePath_Big + std::to_string(sdk) + "_" + pDetConfig->strChannel + "_Img.png";
// 定位存图文件名:{产品ID}_{通道}_{模型序号(0大/1小/2Tag)}_Img(_mask).png
std::string str = m_strSavePath_Big + pDetConfig->strProductID + "_" + pDetConfig->strChannel + "_" + std::to_string(AIModel_type) + "_Img.png";
int sr = m_pImageStorage->addImage(str, detImg);
if (sr == 0)
{
str = m_strSavePath_Big + std::to_string(sdk) + "_" + pDetConfig->strChannel + "_Img_mask.png";
str = m_strSavePath_Big + pDetConfig->strProductID + "_" + pDetConfig->strChannel + "_" + std::to_string(AIModel_type) + "_Img_mask.png";
int sr = m_pImageStorage->addImage(str, mask, true);
sdk++;
if (sdk > 99999)
{
sdk = 0;
/* code */
}
}
}
@ -413,8 +441,15 @@ int AI_Edge_Algin::Get_Edge(int AIModel_type, const cv::Mat &img, DetConfig *pDe
cv::imwrite(strChannel +"_edge_"+ to_string(AIModel_type) +"_in.png", detImg);
cv::imwrite(strChannel +"_edge_"+ to_string(AIModel_type) +"_out_mask.png", mask);
}
// resize out_mask并返回
// resize out_mask并返回(仅大轮廓 mask 需要原图尺寸,后续用于把手检测;其余保持小图)
if (bResizeOut)
{
cv::resize(mask, outmask, img.size());
}
else
{
outmask = mask;
}
// 检查模型输入尺寸
if (sz.width <= 0 || sz.height <= 0)

@ -10,103 +10,9 @@
#include "CheckUtil.hpp"
#include "Define.h"
#include "AI_Factory.h"
#include "AlignShare.h"
#include <fstream>
std::vector<ReadFlawCode> m_FlawCodeList;
std::vector<std::string> QX_Result_Names =
{
"OK",
"aotudian",
"other",
"line",
"zangwu",
"edge",
"ymhs",
"dianzhuang",
"posun",
"xianwei",
"shuizi",
"danban",
"fuchen",
"tag",
};
std::vector<std::string> QX_Result_Code =
{
"P0000",
"MA507",
"MA508",
"MA506",
"MA504",
"MA503",
"MA502",
"MA505",
"MA501",
"P0001",
"P0002",
"P0003",
"P0004",
"P0005",
};
int ReadFlawCodeConfig(std::string json_path)
{
m_FlawCodeList.erase(m_FlawCodeList.begin(), m_FlawCodeList.end());
std::string strPath = json_path;
printf("ReadFlawCodeConfig path %s\n", strPath.c_str());
Json::CharReaderBuilder builder;
builder["collectComments"] = true;
Json::Value root;
std::string err;
std::ifstream ifs(strPath);
if (!ifs.is_open())
{
printf("error:file is open\n");
return 0;
}
if (!Json::parseFromStream(builder, ifs, &root, &err))
{
printf("error:parseFromStream\n");
return 0;
}
for (int i = 0; i < root.size(); i++)
{
// printf("Node idx %d /%d \n", i, root.size());
ReadFlawCode tem;
tem.flaw_name = root[i]["zh_name"].asString();
tem.flaw_code = root[i]["en_name"].asString();
string desc = root[i]["desc"].asString();
{
std::istringstream stream(desc);
std::string token;
// 使用 getline 按照分号分割
while (std::getline(stream, token, ';'))
{
tem.config_flaw_name.push_back(token);
}
}
m_FlawCodeList.push_back(tem);
}
for(int i = 0; i < m_FlawCodeList.size(); i++)
{
if(i >= QX_Result_Names.size()) {
QX_Result_Names.push_back(m_FlawCodeList.at(i).flaw_name);
QX_Result_Code.push_back(m_FlawCodeList.at(i).flaw_code);
}
else
{
QX_Result_Names.at(i) = m_FlawCodeList.at(i).flaw_name;
QX_Result_Code.at(i) = m_FlawCodeList.at(i).flaw_code;
}
}
return 0;
}
double calculateDistanceBetweenRectCenters(const cv::Rect &rect1, const cv::Rect &rect2, float fx, float fy)
{
// 计算矩形1的中心点
@ -505,14 +411,6 @@ int ALLImgCheckAnalysisy::ExitSystem()
int ALLImgCheckAnalysisy::InitCameraCheckAnalysisy()
{
string defect_list_file = m_pConfigManager->GetJsonPath();
if (defect_list_file == "") {
defect_list_file = "/var/aidlux/efs/model/defect_list.json";
}
else{
defect_list_file += "/defect_list.json";
}
ReadFlawCodeConfig(defect_list_file);
m_pCameraCheckAnalysisyList.clear();
for (const auto &config : m_pConfigManager->Config_instances_)
{
@ -553,7 +451,8 @@ int ALLImgCheckAnalysisy::InitData()
int ALLImgCheckAnalysisy::Det_Product(std::shared_ptr<Product> &product)
{
string cur_time_s = CheckUtil::getCurTimeHMS();
printf("[%s]>>>>>>>>>>>>>>>Det_Product****************det Start************\n", cur_time_s.c_str());
// 处理每个相机
while (true)
{
@ -746,6 +645,8 @@ int ALLImgCheckAnalysisy::PushInImg_New(std::shared_ptr<shareImage> p)
}
product = std::make_shared<Product>();
product->productBaseResult->strproductName = p->strImgProductID;
// 新产品周期开始:清理上一轮可能残留的定位共享数据(暗场复用明场定位用)
AlignShare::GetInstance().ClearProduct(p->strImgProductID);
m_ProductList.push_back(product);
std::string strTimg = CheckUtil::getCurTimeHMS();
product->productBaseResult->detlog->AddCheckstr(PrintLevel_0, "PushInImg", "add new product %s %s ", p->strImgProductID.c_str(), strTimg.c_str());
@ -763,8 +664,21 @@ int ALLImgCheckAnalysisy::PushInImg_New(std::shared_ptr<shareImage> p)
}
// 添加产品信息
pCamera->AddDetImage(p);
// 记录"该相机本产品已有图"(暗场等待明场定位时判断源图是否还会来)
if (product->productBaseResult->alignPushState)
{
product->productBaseResult->alignPushState->AddCameraImg(strcameraName);
}
}
product->UpdatePushStatus(p->Status);
// 送图结束:标记该产品不再有新图(暗场等待逻辑据此判断"源图不会再来了")
if (-1 == p->Status || IN_IMG_Status_End == p->Status || IN_IMG_Status_OneImg == p->Status)
{
if (product->productBaseResult->alignPushState)
{
product->productBaseResult->alignPushState->SetPushEnd();
}
}
if (-1 == p->Status || IN_IMG_Status_End == p->Status)
{
m_strTest += "\n";
@ -796,6 +710,12 @@ int ALLImgCheckAnalysisy::Run()
// }
SetSetComplet_New(product, re);
// 产品检测完成:清理定位共享数据(暗场复用明场定位用)
if (product && product->productBaseResult)
{
AlignShare::GetInstance().ClearProduct(product->productBaseResult->strproductName);
}
// 更新 产品队列
{
std::lock_guard<std::mutex> lock(mtx_ProductList); //

@ -0,0 +1,72 @@
#include "AlignShare.h"
AlignShare &AlignShare::GetInstance()
{
static AlignShare s_instance;
return s_instance;
}
std::string AlignShare::MakeKey(const std::string &strProduct, const std::string &strCamera, const std::string &strChannel)
{
return strProduct + "|" + strCamera + "|" + strChannel;
}
void AlignShare::PublishSuccess(const std::string &strProduct, const std::string &strCamera, const std::string &strChannel,
const std::shared_ptr<AI_Edge_Algin::Edge_AI_Result> &pResult,
const cv::Size &imgSize)
{
if (!pResult)
{
return;
}
std::lock_guard<std::mutex> lock(m_mtx);
AlignShareEntry_ &entry = m_mapShare[MakeKey(strProduct, strCamera, strChannel)];
entry.bSucc = true;
entry.pResult = pResult;
entry.imgSize = imgSize;
}
void AlignShare::PublishFailed(const std::string &strProduct, const std::string &strCamera, const std::string &strChannel)
{
std::lock_guard<std::mutex> lock(m_mtx);
AlignShareEntry_ &entry = m_mapShare[MakeKey(strProduct, strCamera, strChannel)];
// 已有成功结果时不覆盖(同一图只会发布一次,正常不会走到)
if (entry.bSucc)
{
return;
}
entry.bSucc = false;
entry.pResult.reset();
}
bool AlignShare::TryGet(const std::string &strProduct, const std::string &strCamera, const std::string &strChannel,
bool &bSucc, std::shared_ptr<AI_Edge_Algin::Edge_AI_Result> &pResult, cv::Size &imgSize)
{
std::lock_guard<std::mutex> lock(m_mtx);
auto it = m_mapShare.find(MakeKey(strProduct, strCamera, strChannel));
if (it == m_mapShare.end())
{
return false;
}
bSucc = it->second.bSucc;
pResult = it->second.pResult;
imgSize = it->second.imgSize;
return true;
}
void AlignShare::ClearProduct(const std::string &strProduct)
{
std::lock_guard<std::mutex> lock(m_mtx);
const std::string strPrefix = strProduct + "|";
for (auto it = m_mapShare.begin(); it != m_mapShare.end();)
{
if (it->first.compare(0, strPrefix.size(), strPrefix) == 0)
{
it = m_mapShare.erase(it);
}
else
{
++it;
}
}
}

@ -10,6 +10,7 @@
#include "ImgCheckAnalysisy.hpp"
#include "CheckUtil.hpp"
#include "Define.h"
#include <atomic>
CameraCheckAnalysisy::CameraCheckAnalysisy()
{
@ -67,9 +68,19 @@ int CameraCheckAnalysisy::WaitDetImg()
// 判断物料在左还是在右
string JudgeMaterialPosition(Mat img) {
if (img.empty() || img.channels() != 1) {
cerr << "Error: Input image is empty or not grayscale!" << endl;
if (img.empty()) {
cerr << "Error: Input image is empty!" << endl;
return "Unknown";
}
// 原图可能是彩色,内部统一转灰度计算(形参为副本,不会改动调用方图像)
if (img.channels() != 1) {
cv::Mat grayImg;
if (img.channels() == 4) {
cv::cvtColor(img, grayImg, cv::COLOR_BGRA2GRAY);
} else {
cv::cvtColor(img, grayImg, cv::COLOR_BGR2GRAY);
}
img = grayImg;
}
resize(img, img, Size(img.cols, img.rows * 0.001), 0, 0);
int rows = img.rows;
@ -352,8 +363,23 @@ int CameraCheckAnalysisy::GetRectAndPoint(const cv::Mat &img, cv::Rect &roi, cv:
// printf("DirectSign_UP::111111111111111111 re123 %d\n", re123);
// getchar();
// 阈值化 + 轮廓提取只支持单通道,彩色原图内部转灰度(不改动调用方图像)
cv::Mat grayImg;
if (img.channels() != 1)
{
if (img.channels() == 4)
{
cv::cvtColor(img, grayImg, cv::COLOR_BGRA2GRAY);
}
else
{
cv::cvtColor(img, grayImg, cv::COLOR_BGR2GRAY);
}
}
const cv::Mat &workImg = (img.channels() == 1) ? img : grayImg;
cv::Mat small, smallbin;
cv::resize(img, small, cv::Size(600, 1500));
cv::resize(workImg, small, cv::Size(600, 1500));
cv::threshold(small, smallbin, 15, 255,
cv::THRESH_BINARY);
@ -386,8 +412,8 @@ int CameraCheckAnalysisy::GetRectAndPoint(const cv::Mat &img, cv::Rect &roi, cv:
cv::Rect rSmall = cv::boundingRect(contours[idx]);
/* 5. map to original */
float sx = (float)img.cols / smallbin.cols;
float sy = (float)img.rows / smallbin.rows;
float sx = (float)workImg.cols / smallbin.cols;
float sy = (float)workImg.rows / smallbin.rows;
cv::Rect rectInSrc;
rectInSrc.x = int(rSmall.x * sx) - 50;
@ -408,17 +434,17 @@ int CameraCheckAnalysisy::GetRectAndPoint(const cv::Mat &img, cv::Rect &roi, cv:
rectInSrc.x = 0;
if (rectInSrc.y < 0)
rectInSrc.y = 0;
if (rectInSrc.x + rectInSrc.width > img.cols)
rectInSrc.width = img.cols - rectInSrc.x;
if (rectInSrc.y + rectInSrc.height > img.rows)
if (rectInSrc.x + rectInSrc.width > workImg.cols)
rectInSrc.width = workImg.cols - rectInSrc.x;
if (rectInSrc.y + rectInSrc.height > workImg.rows)
{
rectInSrc.height = img.rows - rectInSrc.y;
rectInSrc.height = workImg.rows - rectInSrc.y;
}
merg_p_up.y = A_line_up;
merg_p_up.x = img.cols;
merg_p_up.x = workImg.cols;
merg_p_down.y = A_line_down;
merg_p_down.x = img.cols;
merg_p_down.x = workImg.cols;
roi = rectInSrc;
if (bshowimg)
@ -675,10 +701,18 @@ int CameraCheckAnalysisy::InitRun()
int CameraCheckAnalysisy::InitCheckAnalysisy()
{
int re = 0;
// 全局递增的 CPU 核偏移:
static std::atomic<int> s_cpuOffset{0};
for (int i = 0; i < IMGCHECKANALYSISY_NUM; i++)
{
RunInfoST RunConfig;
RunConfig.nThreadIdx = i;
{
// 核带下沉到库内部:不再依赖调用方传入的 nCpu_start_Idx / nCpu_num
int offset = s_cpuOffset.fetch_add(1) % CHECK_CPU_BAND_NUM;
RunConfig.nCpu_start_Idx = CHECK_CPU_BAND_START + offset;
}
RunConfig.nCpu_num = 1;
m_pImgCheckAnalysisy[i] = ImgCheckBase::GetInstance();
re = m_pImgCheckAnalysisy[i]->UpdateConfig((void *)&RunConfig, CHECK_CONFIG_Run);
@ -780,7 +814,10 @@ int CameraCheckAnalysisy::CheckImgRun()
if (m_pCheck_Result->getImgPushComplate())
{
curcheckStep = Check_Step_Complete; // 进入到预图片检测阶段
m_pCheck_Result->SetCheckStep(curcheckStep);
// 注意:这里先不发布 Check_Step_Complete。
// 若提前发布,ALLImgCheckAnalysisy 线程会立刻把结果塞给 deal,
// 而本线程还在 InsertCameraLog() 里写 result->det_LogList,两边并发(读/写)竞争。
// 因此把 SetCheckStep(Check_Step_Complete) 挪到 InsertCameraLog() 之后。
}
else
{
@ -797,6 +834,8 @@ int CameraCheckAnalysisy::CheckImgRun()
m_pdetlog->AddCheckstr(PrintLevel_0, "CheckImgRun", "========came %s Check ALL end time %ld ms %s",
m_pCheck_Result->cameraBaseResult->strCameraName.c_str(), tend - m_pCheck_Result->time_start, strTimg.c_str());
InsertCameraLog();
// 日志拼接完成后再发布“检测完成”,避免 deal 提前读到正在写入的 det_LogList
m_pCheck_Result->SetCheckStep(Check_Step_Complete);
std::lock_guard<std::mutex> lock(mtx_WaiteImg);
m_bHaveImgeDet = false;
@ -841,8 +880,12 @@ int CameraCheckAnalysisy::InsertCameraLog()
}
// 1、把图片的相关日志 写到 返回 日志中。
// 注意:DetLog 可能仍被实例线程写入,使用 GetLogs() 做加锁快照,避免并发遍历。
{
std::vector<std::string> imgLogs = pImageResult->detlog->GetLogs();
m_CheckResult_shareP->det_LogList.insert(m_CheckResult_shareP->det_LogList.end(),
pImageResult->detlog->logList.begin(), pImageResult->detlog->logList.end());
imgLogs.begin(), imgLogs.end());
}
std::string str = "";
char buffer[256];
// 2、把缺陷信息 写的 返回日志中。
@ -873,8 +916,9 @@ int CameraCheckAnalysisy::InsertCameraLog()
if (QX_info->result == QX_RESULT_TYPE_NG)
{
std::shared_ptr<DetLog> pQxLog = pDetResult->pQx_ErrorList->at(qxidx).detlog;
std::vector<std::string> qxLogs = pQxLog->GetLogs();
m_CheckResult_shareP->det_LogList.insert(m_CheckResult_shareP->det_LogList.end(),
pQxLog->logList.begin(), pQxLog->logList.end());
qxLogs.begin(), qxLogs.end());
}
}
@ -890,8 +934,9 @@ int CameraCheckAnalysisy::InsertCameraLog()
if (QX_info->result == QX_RESULT_TYPE_YS)
{
std::shared_ptr<DetLog> pQxLog = pDetResult->pQx_ErrorList->at(qxidx).detlog;
std::vector<std::string> qxLogs = pQxLog->GetLogs();
m_CheckResult_shareP->det_LogList.insert(m_CheckResult_shareP->det_LogList.end(),
pQxLog->logList.begin(), pQxLog->logList.end());
qxLogs.begin(), qxLogs.end());
}
}
@ -907,8 +952,9 @@ int CameraCheckAnalysisy::InsertCameraLog()
if (QX_info->result == QX_RESULT_TYPE_OK)
{
std::shared_ptr<DetLog> pQxLog = pDetResult->pQx_ErrorList->at(qxidx).detlog;
std::vector<std::string> qxLogs = pQxLog->GetLogs();
m_CheckResult_shareP->det_LogList.insert(m_CheckResult_shareP->det_LogList.end(),
pQxLog->logList.begin(), pQxLog->logList.end());
qxLogs.begin(), qxLogs.end());
}
}
@ -924,8 +970,9 @@ int CameraCheckAnalysisy::InsertCameraLog()
if (QX_info->result == QX_RESULT_TYPE_NoJduge)
{
std::shared_ptr<DetLog> pQxLog = pDetResult->pQx_ErrorList->at(qxidx).detlog;
std::vector<std::string> qxLogs = pQxLog->GetLogs();
m_CheckResult_shareP->det_LogList.insert(m_CheckResult_shareP->det_LogList.end(),
pQxLog->logList.begin(), pQxLog->logList.end());
qxLogs.begin(), qxLogs.end());
}
}
}
@ -937,8 +984,11 @@ int CameraCheckAnalysisy::InsertCameraLog()
}
// 3、把相机的日志 写到 返回日志中。
{
std::vector<std::string> camLogs = m_pCheck_Result->detlog->GetLogs();
m_CheckResult_shareP->det_LogList.insert(m_CheckResult_shareP->det_LogList.end(),
m_pCheck_Result->detlog->logList.begin(), m_pCheck_Result->detlog->logList.end());
camLogs.begin(), camLogs.end());
}
{
sprintf(buffer, " =====================Product Log =============================");
@ -946,8 +996,11 @@ int CameraCheckAnalysisy::InsertCameraLog()
m_CheckResult_shareP->det_LogList.push_back(str);
}
// 4、把产品相关日志 写到 返回日志中。
{
std::vector<std::string> productLogs = m_pCheck_Result->productBaseResult->detlog->GetLogs();
m_CheckResult_shareP->det_LogList.insert(m_CheckResult_shareP->det_LogList.end(),
m_pCheck_Result->productBaseResult->detlog->logList.begin(), m_pCheck_Result->productBaseResult->detlog->logList.end());
productLogs.begin(), productLogs.end());
}
}
return 0;
}
@ -1065,20 +1118,28 @@ ImgCheckBase *CameraCheckAnalysisy::GetDealResult(int idx)
return NULL;
}
int CameraCheckAnalysisy::GetEdgePoint(const cv::Mat &img, Edge_Search_Config *pEdge_Search_Config, int &outP)
int CameraCheckAnalysisy::GetEdgePoint(const cv::Mat &imgSrc, Edge_Search_Config *pEdge_Search_Config, int &outP)
{
bool bdbuge = false;
bdbuge = bshowimg;
if (img.empty())
if (imgSrc.empty())
{
return -11;
}
if (img.channels() != 1)
// 边缘搜索仅支持单通道;彩色/BGRA 原图在此内部转灰度(不改动调用方图像)
cv::Mat grayImg;
if (imgSrc.channels() != 1)
{
// printf("*****************************channels\n");
return -12;
if (imgSrc.channels() == 4)
{
cv::cvtColor(imgSrc, grayImg, cv::COLOR_BGRA2GRAY);
}
else
{
cv::cvtColor(imgSrc, grayImg, cv::COLOR_BGR2GRAY);
}
}
const cv::Mat &img = (imgSrc.channels() == 1) ? imgSrc : grayImg;
// 检查roi;
if (!CheckUtil::RoiInImg(pEdge_Search_Config->roi, img))
{

@ -18,6 +18,18 @@ Json::Value CheckResultJson::toJsonValue()
root["Param_CropRoi"]["width"] = m_pOneImgDetResult->Param_CropRoi.width;
root["Param_CropRoi"]["height"] = m_pOneImgDetResult->Param_CropRoi.height;
}
// 检测图(裁剪) -> 原图 的 2x3 仿射矩阵(用于复测/外部分析还原缺陷小图)
if (!m_pOneImgDetResult->detToSrcMat.empty() &&
m_pOneImgDetResult->detToSrcMat.rows == 2 && m_pOneImgDetResult->detToSrcMat.cols == 3)
{
const double *m = m_pOneImgDetResult->detToSrcMat.ptr<double>(0);
root["detToSrc"]["m00"] = m[0];
root["detToSrc"]["m01"] = m[1];
root["detToSrc"]["m02"] = m[2];
root["detToSrc"]["m10"] = m[3];
root["detToSrc"]["m11"] = m[4];
root["detToSrc"]["m12"] = m[5];
}
for (int i = 0; i < m_pOneImgDetResult->pQx_ErrorList->size(); i++)
{
QX_ERROR_INFO_ *p = &m_pOneImgDetResult->pQx_ErrorList->at(i);
@ -38,6 +50,7 @@ Json::Value CheckResultJson::toJsonValue()
value["flen"] = p->flen;
value["nconfig_qx_type"] = p->nconfig_qx_type;
value["qx_name"] = p->qx_name;
value["qx_level"] = p->qx_level;
value["maxValue"] = p->maxValue;
value["grayDis"] = p->grayDis;
value["fUpIou"] = p->fUpIou;
@ -84,6 +97,15 @@ void CheckResultJson::toObjectFromValue(Json::Value root)
m_pOneImgDetResult->Param_CropRoi.height = root["Param_CropRoi"]["height"].asInt();
}
}
// 检测图(裁剪) -> 原图 的 2x3 仿射矩阵(旧 JSON 没有该字段时按 CutRoi 平移兼容)
{
auto value = root["detToSrc"];
if (value.isObject() && value.isMember("m00"))
{
m_pOneImgDetResult->detToSrcMat = (cv::Mat_<double>(2, 3) << value["m00"].asDouble(), value["m01"].asDouble(), value["m02"].asDouble(),
value["m10"].asDouble(), value["m11"].asDouble(), value["m12"].asDouble());
}
}
{
if (root.isMember("Qx"))
{
@ -117,6 +139,7 @@ void CheckResultJson::toObjectFromValue(Json::Value root)
tem.flen = errorJson["flen"].asFloat();
tem.nconfig_qx_type = errorJson["nconfig_qx_type"].asInt();
tem.qx_name = errorJson["qx_name"].asString();
tem.qx_level = errorJson["qx_level"].asString();
tem.maxValue = errorJson["maxValue"].asInt();
tem.grayDis = errorJson["grayDis"].asFloat();
tem.fUpIou = errorJson["fUpIou"].asFloat();

@ -19,6 +19,9 @@
#include <stdlib.h>
#include <fstream>
#include <thread>
#include <algorithm>
#include <cmath>
#include <fcntl.h>
#include "snowflake.hpp"
int _sysmkdir_2(const std::string &dir)
@ -67,8 +70,10 @@ std::string CheckUtil::getCurrentDate()
// 将 tv_sec 转换为 time_t 类型,代表自1970年1月1日起的秒数
time_t raw_time = tv.tv_sec;
// 使用 localtime 将 time_t 转换为 tm 结构体
struct tm *time_info = localtime(&raw_time);
// 使用 localtime_r 转换为 tm 结构体。
// 注意:localtime() 返回共享的静态缓冲区,多线程并发调用会互相覆盖(tm 内容串数据)。
struct tm time_buf;
struct tm *time_info = localtime_r(&raw_time, &time_buf);
// 使用字符串流格式化为 "YYYY-MM-DD"
std::ostringstream oss;
@ -78,20 +83,41 @@ std::string CheckUtil::getCurrentDate()
return oss.str(); // 返回日期字符串
}
std::string CheckUtil::getCurrentDateYMD()
{
struct timeval tv;
gettimeofday(&tv, NULL);
time_t raw_time = tv.tv_sec;
// localtime_r 为线程安全版本
struct tm time_buf;
struct tm *time_info = localtime_r(&raw_time, &time_buf);
// 固定 8 位 "YYYYMMDD", 便于文件名检索与排序
std::ostringstream oss;
oss << std::setfill('0')
<< std::setw(4) << (time_info->tm_year + 1900)
<< std::setw(2) << (time_info->tm_mon + 1)
<< std::setw(2) << time_info->tm_mday;
return oss.str();
}
std::string CheckUtil::getCurTimeHMS()
{
struct timeval tv;
gettimeofday(&tv, NULL); // 获取当前时间
time_t raw_time = tv.tv_sec;
struct tm *time_info = localtime(&raw_time);
// 使用 localtime_r(线程安全);localtime() 的静态缓冲会被其它线程并发覆盖
struct tm time_buf;
struct tm *time_info = localtime_r(&raw_time, &time_buf);
int millisec = tv.tv_usec / 1000; // 微秒转毫秒
std::ostringstream oss;
oss << std::setfill('0') << std::setw(2) << time_info->tm_hour << ":"
<< std::setfill('0') << std::setw(2) << time_info->tm_min << ":"
<< std::setfill('0') << std::setw(2) << time_info->tm_sec << "."
oss << std::setfill('0') << std::setw(2) << time_info->tm_hour << "-"
<< std::setfill('0') << std::setw(2) << time_info->tm_min << "-"
<< std::setfill('0') << std::setw(2) << time_info->tm_sec << "-"
<< std::setfill('0') << std::setw(3) << millisec;
return oss.str(); // 返回格式 "HH:MM:SS.mmm"
@ -118,6 +144,40 @@ int64_t CheckUtil::getSnowId()
return 0;
}
std::string CheckUtil::GetUniqueImgPath(const std::string &strDir, const std::string &strPrefix, const std::string &strName, const std::string &strExt)
{
if (strDir.empty())
{
return strDir + strPrefix + strName + strExt;
}
// 目录不存在时 O_CREAT 会失败, 先建目录(已存在时内部无副作用)
CreateDir(strDir);
// 去重序号插在前缀之后: <年月日>[_序号]_<通道>_<用途>.png
// 同一产品的每个 strName 每轮只会写一次, 所以各文件序号天然同步:
// 第 1 轮 -> <年月日>_<名称>.png、第 2 轮 -> <年月日>_1_<名称>.png、第 3 轮 -> <年月日>_2_<名称>.png ...
const std::string strHead = strPrefix.empty() ? std::string("") : (strPrefix + "_");
for (int i = 0; i < 100000; i++)
{
std::string strPath = strDir + strHead + (i == 0 ? std::string("") : std::to_string(i) + "_") + strName + strExt;
// O_EXCL: 文件不存在才能创建成功, 即是"原子占位", 并发安全
int fd = ::open(strPath.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0644);
if (fd >= 0)
{
::close(fd);
return strPath;
}
if (errno != EEXIST)
{
// 非"已存在"类错误(权限/路径等)不再重试, 直接返回让 imwrite 把错误暴露出来
printf("create img file[%s] error: %d %s\n", strPath.c_str(), errno, strerror(errno));
return strPath;
}
}
// 序号用尽的兜底(实际不会发生)
return strDir + strHead + std::to_string(CheckUtil::getcurTime()) + "_" + strName + strExt;
}
bool CheckUtil::JudgRect(cv::Rect roi, int img_w, int img_h)
{
if (img_w <= 0 || img_h <= 0)
@ -501,6 +561,32 @@ int CheckUtil::CreateDir(const std::string &dir)
return ret;
}
int CheckUtil::SaveImgAsync(const std::string &path, const cv::Mat &img)
{
if (path.empty() || img.empty())
{
return -1;
}
// 拷贝图像,避免异步线程执行时原图像被释放或修改
cv::Mat imgCopy = img.clone();
// 创建保存路径的父目录
std::string::size_type pos = path.find_last_of('/');
if (pos != std::string::npos)
{
std::string parentDir = path.substr(0, pos);
CheckUtil::CreateDir(parentDir);
}
// 启动后台线程异步存图
std::thread([path, imgCopy]() {
cv::imwrite(path, imgCopy);
}).detach();
return 0;
}
bool CheckUtil::bcalDis(cv::Point p1, cv::Point p2, int disT)
{
double dis = std::sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
@ -693,3 +779,410 @@ cv::Point2f CheckUtil::transformPoint(const cv::Point2f &point, const cv::Mat &t
return cv::Point2f(result_mat.at<double>(0), result_mat.at<double>(1));
}
// 迭代裁剪轮数:每轮"带内点 cv::fitLine(L2) 精修 + 容差收紧一半",3 轮足够收敛
#define FIT_EDGE_REFINE_ITERS 3
// 拟合边缘直线:val ≈ fK * idx + fB(点集为 (扫描序号, 边界值))
// 做法(简洁版,不再做斜率网格搜索):
// 1) cv::fitLine(DIST_HUBER) 初始化:Huber 是 M 估计,把手凸起/角部过渡等偏离点被自动降权,
// 不会像普通最小二乘那样被拉偏(旧实现靠粗/细斜率网格找带内最多点,代码长且难调);
// 2) 迭代裁剪 + cv::fitLine(DIST_L2) 精修:每轮只保留距当前直线 ±fUseTol 内的点做最小二乘,
// 容差从 fTol 逐轮收紧到 3px(与旧实现“带内点两轮收紧容差”等价,实测四条边结果与旧实现一致);
// 3) |k| > fMaxSlope(不是近轴直边,多半是角部斜过渡/另一侧边缘)→ 常量线兜底。
// 可调参数(应对实际情况):
// fTol:裁剪容差(工作图像素)。调大→浅把手更容易被并入主体(漏检)、
// 但更能压住量化台阶毛刺;调小→台阶噪声可能分裂支撑或拟合到非主体结构。
// 默认由调用方按“量化台阶+2px”传入(见 GetMaskProtrusions 第 5 步)。
// fMaxSlope:斜率上限。调大→允许产品倾斜更大;过大时角部斜过渡段可能被当成直边;
// 调小→产品倾斜稍大时退回常量线。
// 点集为空返回 false;点太少/拟合结果不合理时退回常量线(值取中位数)
static bool FitEdgeLine(const std::vector<cv::Point2f> &pts, double fTol, double fMaxSlope, double &fK, double &fB)
{
if (pts.empty())
{
return false;
}
// 常量线兜底:值取中位数(点太少或拟合结果不合理时使用)
fK = 0.0;
{
std::vector<float> valList;
valList.reserve(pts.size());
for (size_t i = 0; i < pts.size(); i++)
{
valList.push_back(pts[i].y);
}
std::nth_element(valList.begin(), valList.begin() + valList.size() / 2, valList.end());
fB = (double)valList[valList.size() / 2];
}
if (pts.size() < 8)
{
return true; // 点太少:用常量线兜底
}
// cv::fitLine 的输出(单位方向向量 + 线上一点)换算成 v = k*t + b
auto fnToKB = [](const cv::Vec4f &line, double &k, double &b) -> bool {
if (std::fabs((double)line[0]) < 1e-6)
{
return false; // 近垂直线:v 随 t 剧烈变化,无法用 k/b 表示
}
k = (double)line[1] / (double)line[0];
b = (double)line[3] - k * (double)line[2];
return true;
};
// 1、Huber 稳健初始化(1.345 = 高斯噪声下 ~95% 效率的标准 Huber 系数)
cv::Vec4f line;
cv::fitLine(pts, line, cv::DIST_HUBER, 1.345, 1e-3, 1e-3);
double fKFit = 0, fBFit = 0;
if (!fnToKB(line, fKFit, fBFit) || std::fabs(fKFit) > fMaxSlope)
{
return true; // 拟不出近轴直边:常量线兜底
}
// 2、迭代裁剪 + L2 精修(容差逐轮收紧;某轮点不够或结果不合理时保留上一轮结果)
double fUseTol = fTol;
for (int nIter = 0; nIter < FIT_EDGE_REFINE_ITERS; nIter++)
{
std::vector<cv::Point2f> vecInlier;
vecInlier.reserve(pts.size());
for (size_t i = 0; i < pts.size(); i++)
{
if (std::fabs((double)pts[i].y - (fKFit * pts[i].x + fBFit)) <= fUseTol)
{
vecInlier.push_back(pts[i]);
}
}
if (vecInlier.size() < 8)
{
break; // 支撑点太少:保留当前结果,不再收紧
}
cv::Vec4f lineRefit;
cv::fitLine(vecInlier, lineRefit, cv::DIST_L2, 0, 1e-3, 1e-3);
double fKNew = 0, fBNew = 0;
if (!fnToKB(lineRefit, fKNew, fBNew) || std::fabs(fKNew) > fMaxSlope)
{
break; // 精修结果不合理:保留上一轮
}
fKFit = fKNew;
fBFit = fBNew;
fUseTol = std::max(3.0, fUseTol * 0.5);
}
fK = fKFit;
fB = fBFit;
return true;
}
// 从外轮廓掩膜(outer定位mask,如bigmask)中分离凸起区域(如把手):
// 产品主体是圆角矩形、把手基本出现在四角附近,把手在掩膜上表现为超出主体四条边缘的外凸块:
// 1、取最大连通域为主体;
// 2、逐行/逐列扫描主体边界,得到 左/右(x=f(y))、上/下(y=f(x)) 四条边缘的边界点集;
// 3、对每条边缘做直线拟合(见 FitEdgeLine:cv::fitLine(Huber) 定初值 + 迭代裁剪 + cv::fitLine(L2) 精修,
// 允许产品小角度倾斜);
// 4、掩膜中超出四条边缘(超过容差带)的像素按连通域切分成凸起区域,
// 并按质心所在象限给出就近角(0 左上 1 右上 2 左下 3 右下);深度/面积不足的量化毛刺被过滤。
// 可调参数(应对实际情况):
// nMinDepth:凸起最小深度(输入图像素;<=0 时按掩膜短边/150 自动取值,下限 6)
// 调大:只报明显外凸的把手,浅把手会漏检,可压住边缘毛刺/噪声;
// 调小:浅凸起与噪声都易报出(误报变多)。mask 由模型输出放大而来时直边带量化台阶
// (台阶高≈量化步长),建议取 2 个量化步长左右(调用方见 CheckHandleByBigMask)。
// nMinArea:凸起最小面积(输入图像素²;<=0 时按 nMinDepth² 自动取值)
// 调大:小碎块被过滤,把手很小或被遮挡一部分时可能漏检;
// 调小:碎块残留为候选的几率变大(仍会被 nMinDepth 再过滤一遍)。
// nEdgeMargin:边缘外扩量(输入图像素;<=0 时自动取“最小深度的一半”)
// 把四条边缘线整体向外推该距离后再判凸起,等效于把凸起区域相应减小:
// 调大:边缘顺边毛刺/线条偏差被吞掉,凸起区域更小更干净;过大时浅把手漏检;
// 调小:更灵敏,但边缘毛刺容易连进凸起区域。
// 边缘拟合的细节参数(带宽容差/斜率上限)见 FitEdgeLine 注释。
// pBodyQuad:可选输出,拟合出的主体四边形(左上/右上/右下/左下,输入图坐标),仅 -s 存图观察用
// 返回:0 成功、1 掩膜为空、2 无有效前景
int CheckUtil::GetMaskProtrusions(const cv::Mat &binMask, std::vector<Mask_Protrusion_> &protrusions,
int nMinDepth, int nMinArea, std::vector<cv::Point2f> *pBodyQuad, int nEdgeMargin)
{
protrusions.clear();
if (binMask.empty())
{
return 1;
}
// 1、二值化(兼容 0/1 与 0/255 掩膜)
cv::Mat gray;
if (binMask.channels() != 1)
{
cv::cvtColor(binMask, gray, cv::COLOR_BGR2GRAY);
}
else
{
gray = binMask;
}
cv::Mat maskFull;
cv::threshold(gray, maskFull, 0, 255, cv::THRESH_BINARY);
// 参数默认值按输入图尺度取
if (nMinDepth <= 0)
{
nMinDepth = std::max(6, std::min(maskFull.cols, maskFull.rows) / 150);
}
if (nMinArea <= 0)
{
nMinArea = std::max(64, nMinDepth * nMinDepth);
}
// 2、大掩膜先降采样再分析:凸起尺寸远大于降采样误差,可显著降低耗时
int nScale = 1;
while (nScale < 4 && std::max(maskFull.cols, maskFull.rows) / nScale > 2000)
{
nScale *= 2;
}
cv::Mat mask;
if (nScale > 1)
{
cv::resize(maskFull, mask, cv::Size(maskFull.cols / nScale, maskFull.rows / nScale), 0, 0, cv::INTER_NEAREST);
}
else
{
mask = maskFull;
}
// 阈值按比例换算到工作图尺度
const int nMinDepthWork = std::max(2, nMinDepth / nScale);
const int nMinAreaWork = std::max(4, nMinArea / (nScale * nScale));
// 3、只保留最大连通域作为主体(过滤背景杂物)
cv::Mat labels, stats, centroids;
int nLabels = cv::connectedComponentsWithStats(mask, labels, stats, centroids, 8, CV_32S);
if (nLabels <= 1)
{
return 2;
}
int nMainIdx = 1;
int nMainArea = 0;
for (int i = 1; i < nLabels; i++)
{
int nArea = stats.at<int>(i, cv::CC_STAT_AREA);
if (nArea > nMainArea)
{
nMainArea = nArea;
nMainIdx = i;
}
}
cv::Rect boxMain(stats.at<int>(nMainIdx, cv::CC_STAT_LEFT), stats.at<int>(nMainIdx, cv::CC_STAT_TOP),
stats.at<int>(nMainIdx, cv::CC_STAT_WIDTH), stats.at<int>(nMainIdx, cv::CC_STAT_HEIGHT));
boxMain &= cv::Rect(0, 0, mask.cols, mask.rows);
if (boxMain.width <= 0 || boxMain.height <= 0)
{
return 2;
}
cv::Mat mainMask;
cv::compare(labels(boxMain), nMainIdx, mainMask, cv::CMP_EQ); // 主体连通域(0/255)
const int H = boxMain.height;
const int W = boxMain.width;
// 4、逐行/逐列扫描主体边界,收集四条边缘的边界点集(工作图坐标)
std::vector<cv::Point2f> ptsL, ptsR, ptsT, ptsB;
ptsL.reserve(H);
ptsR.reserve(H);
ptsT.reserve(W);
ptsB.reserve(W);
for (int y = 0; y < H; y++)
{
const uchar *p = mainMask.ptr<uchar>(y);
int nXL = -1;
int nXR = -1;
for (int x = 0; x < W; x++)
{
if (p[x]) { nXL = x; break; }
}
for (int x = W - 1; x >= 0; x--)
{
if (p[x]) { nXR = x; break; }
}
if (nXL >= 0)
{
ptsL.emplace_back((float)(y + boxMain.y), (float)(nXL + boxMain.x)); // (行号, 左边界的x)
}
if (nXR >= 0)
{
ptsR.emplace_back((float)(y + boxMain.y), (float)(nXR + boxMain.x)); // (行号, 右边界的x)
}
}
for (int x = 0; x < W; x++)
{
int nYT = -1;
int nYB = -1;
for (int y = 0; y < H; y++)
{
if (mainMask.ptr<uchar>(y)[x]) { nYT = y; break; }
}
for (int y = H - 1; y >= 0; y--)
{
if (mainMask.ptr<uchar>(y)[x]) { nYB = y; break; }
}
if (nYT >= 0)
{
ptsT.emplace_back((float)(x + boxMain.x), (float)(nYT + boxMain.y)); // (列号, 上边界的y)
}
if (nYB >= 0)
{
ptsB.emplace_back((float)(x + boxMain.x), (float)(nYB + boxMain.y)); // (列号, 下边界的y)
}
}
// 5、拟合四条边缘直线:左/右 x=f(y)、上/下 y=f(x)
// 稳健拟合(先用 cv::fitLine(L1) 定初值,再迭代裁剪 + cv::fitLine(L2) 精修),|k|>0.6(非近轴)时退回常量线
const int nFitStep = std::max(1, nMinDepthWork / 2); // 量化台阶估计(工作图)
// nExp:边缘外扩量(工作图像素)。显式给定(nEdgeMargin>0)时把边缘线整体向外推该距离,
// 等效于减小凸起区域;未给定时按最小深度的一半自动取值
const int nExp = (nEdgeMargin > 0) ? std::max(1, nEdgeMargin / nScale) : nFitStep;
const double fTol = std::max(6.0, (double)(nFitStep + 2));
double fKL = 0, fBL = 0, fKR = 0, fBR = 0, fKT = 0, fBT = 0, fKB = 0, fBB = 0;
if (!FitEdgeLine(ptsL, fTol, 0.6, fKL, fBL) || !FitEdgeLine(ptsR, fTol, 0.6, fKR, fBR) ||
!FitEdgeLine(ptsT, fTol, 0.6, fKT, fBT) || !FitEdgeLine(ptsB, fTol, 0.6, fKB, fBB))
{
return 2;
}
// 主体中心(工作图坐标)与边缘合理性检查
const float fXc = boxMain.x + W * 0.5f;
const float fYc = boxMain.y + H * 0.5f;
if ((float)(fKR * fYc + fBR) - (float)(fKL * fYc + fBL) < 4.0f ||
(float)(fKB * fXc + fBB) - (float)(fKT * fXc + fBT) < 4.0f)
{
return 2;
}
// 可选输出:主体四边形(四角=相邻两条边缘直线的交点),供 -s 存图观察拟合效果;
// 左/右边缘: x = k*y + b;上/下边缘: y = k*x + b,联立求交(行列式≈0 说明两线平行,放弃)
if (pBodyQuad != NULL)
{
pBodyQuad->clear();
auto fnCross = [](double fkX, double fbX, double fkY, double fbY, cv::Point2f &pt) -> bool {
double fDet = 1.0 - fkX * fkY;
if (std::fabs(fDet) < 1e-6)
{
return false;
}
pt.x = (float)((fbX + fkX * fbY) / fDet);
pt.y = (float)(fkY * pt.x + fbY);
return true;
};
cv::Point2f ptTL, ptTR, ptBR, ptBL;
if (fnCross(fKL, fBL, fKT, fBT, ptTL) && fnCross(fKR, fBR, fKT, fBT, ptTR) &&
fnCross(fKR, fBR, fKB, fBB, ptBR) && fnCross(fKL, fBL, fKB, fBB, ptBL))
{
// 工作图坐标 -> 输入图坐标
pBodyQuad->push_back(cv::Point2f(ptTL.x * nScale, ptTL.y * nScale)); // 左上
pBodyQuad->push_back(cv::Point2f(ptTR.x * nScale, ptTR.y * nScale)); // 右上
pBodyQuad->push_back(cv::Point2f(ptBR.x * nScale, ptBR.y * nScale)); // 右下
pBodyQuad->push_back(cv::Point2f(ptBL.x * nScale, ptBL.y * nScale)); // 左下
}
}
// 6、凸起 = 超出四条边缘(超过容差带 nExp)的掩膜像素;容差带吸收边缘量化毛刺,
// 并可由 nEdgeMargin 显式加大(边缘线整体外推)来压制顺边毛刺/减小凸起区域
cv::Mat protrMask = cv::Mat::zeros(mask.size(), CV_8UC1);
for (int y = boxMain.y; y < boxMain.y + H; y++)
{
const uchar *pSrc = mainMask.ptr<uchar>(y - boxMain.y);
uchar *pDst = protrMask.ptr<uchar>(y);
const float fLineL = (float)(fKL * y + fBL) - nExp;
const float fLineR = (float)(fKR * y + fBR) + nExp;
for (int x = boxMain.x; x < boxMain.x + W; x++)
{
if (!pSrc[x - boxMain.x])
{
continue;
}
if (x < fLineL || x > fLineR)
{
pDst[x] = 255;
continue;
}
if (y < (float)(fKT * x + fBT) - nExp || y > (float)(fKB * x + fBB) + nExp)
{
pDst[x] = 255;
}
}
}
// 7、按连通域切分成凸起区域:深度(相对四条边缘)/面积不足的毛刺过滤
cv::Mat labelsP, statsP, centP;
int nP = cv::connectedComponentsWithStats(protrMask, labelsP, statsP, centP, 8, CV_32S);
if (nP <= 1)
{
return 0; // 无凸起
}
for (int i = 1; i < nP; i++)
{
int nArea = statsP.at<int>(i, cv::CC_STAT_AREA);
if (nArea < nMinAreaWork)
{
continue;
}
int nX = statsP.at<int>(i, cv::CC_STAT_LEFT);
int nY = statsP.at<int>(i, cv::CC_STAT_TOP);
int nW = statsP.at<int>(i, cv::CC_STAT_WIDTH);
int nH = statsP.at<int>(i, cv::CC_STAT_HEIGHT);
// 深度:组件像素相对四条边缘线的最大外伸(工作图像素)
float fDepth = 0.0f;
for (int yy = nY; yy < nY + nH; yy++)
{
const int *pLab = labelsP.ptr<int>(yy);
const float fLineL = (float)(fKL * yy + fBL);
const float fLineR = (float)(fKR * yy + fBR);
for (int xx = nX; xx < nX + nW; xx++)
{
if (pLab[xx] != i)
{
continue;
}
// 到四条边缘的最大外伸(在边缘内侧为负,取 0 忽略)
float fDev = std::max(fLineL - xx, xx - fLineR);
fDev = std::max(fDev, (float)(fKT * xx + fBT) - yy);
fDev = std::max(fDev, yy - (float)(fKB * xx + fBB));
fDepth = std::max(fDepth, fDev);
}
}
if (fDepth < nMinDepthWork)
{
continue;
}
// 特征 + 坐标(工作图 -> 输入图)
Mask_Protrusion_ protr;
protr.roi = cv::Rect(nX * nScale, nY * nScale, nW * nScale, nH * nScale);
protr.roi &= cv::Rect(0, 0, binMask.cols, binMask.rows);
if (protr.roi.width <= 0 || protr.roi.height <= 0)
{
continue;
}
cv::Point2f ptCen;
if (centP.type() == CV_32F)
{
ptCen = centP.at<cv::Point2f>(i);
}
else
{
cv::Vec2d vCen = centP.at<cv::Vec2d>(i);
ptCen = cv::Point2f((float)vCen[0], (float)vCen[1]);
}
protr.ptCenter = cv::Point2f(ptCen.x * nScale, ptCen.y * nScale);
// 就近角:按主体中心分象限 -> 0 左上 1 右上 2 左下 3 右下
bool bRight = ptCen.x > fXc;
bool bBottom = ptCen.y > fYc;
protr.nCorner = bBottom ? (bRight ? 3 : 2) : (bRight ? 1 : 0);
protr.fAngle = (float)(std::atan((fKL + fKR) * 0.5) * 180.0 / CV_PI); // 左右边缘平均倾角(度,相对垂直方向)
protr.fLen = (float)std::max(nW, nH) * nScale;
protr.fDepth = fDepth * nScale;
protr.fArea = (float)nArea * nScale * nScale;
protr.fFillRatio = (float)nArea / (float)(nW * nH);
protrusions.push_back(protr);
}
// 按面积从大到小排序(日志与匹配更稳定)
std::sort(protrusions.begin(), protrusions.end(),
[](const Mask_Protrusion_ &a, const Mask_Protrusion_ &b) { return a.fArea > b.fArea; });
return 0;
}

@ -94,11 +94,8 @@ int ImageAllResult::AddDetImage(std::shared_ptr<shareImage> p)
float fw = RESIZE_IMAGE_WIDTH * 1.0f / detImg.cols;
sz.height = int(detImg.rows * fw);
// 保持原图通道(ReJson 模式下 detImg 本身就是裁剪图),不再灰度转假彩
cv::resize(detImg, resultImg, sz);
if (resultImg.channels() == 1)
{
cv::cvtColor(resultImg, resultImg, cv::COLOR_GRAY2BGR);
}
result->resultimg = resultImg;

@ -1,6 +1,72 @@
#include "ImageResultJudge.h"
// 检测图坐标 -> 原图坐标(detToSrcMat 非空时按矩阵映射; 否则退回 CutRoi 平移)
static cv::Rect DetRoiToSrcRoi(const cv::Mat &detToSrcMat, cv::Rect detRoi, const cv::Rect &cutRoi, const cv::Size &srcSize)
{
if (detToSrcMat.empty() || detToSrcMat.rows != 2 || detToSrcMat.cols != 3)
{
cv::Rect srcRoi = detRoi;
srcRoi.x += cutRoi.x;
srcRoi.y += cutRoi.y;
return srcRoi;
}
const double *m = detToSrcMat.ptr<double>(0);
cv::Point2f pts[4] = {
cv::Point2f((float)detRoi.x, (float)detRoi.y),
cv::Point2f((float)(detRoi.x + detRoi.width), (float)detRoi.y),
cv::Point2f((float)(detRoi.x + detRoi.width), (float)(detRoi.y + detRoi.height)),
cv::Point2f((float)detRoi.x, (float)(detRoi.y + detRoi.height))};
float fminX = 1e18f, fmaxX = -1e18f, fminY = 1e18f, fmaxY = -1e18f;
for (int i = 0; i < 4; i++)
{
float x = (float)(m[0] * pts[i].x + m[1] * pts[i].y + m[2]);
float y = (float)(m[3] * pts[i].x + m[4] * pts[i].y + m[5]);
if (x < fminX)
fminX = x;
if (x > fmaxX)
fmaxX = x;
if (y < fminY)
fminY = y;
if (y > fmaxY)
fmaxY = y;
}
cv::Rect srcRoi(cvFloor(fminX), cvFloor(fminY), cvCeil(fmaxX) - cvFloor(fminX), cvCeil(fmaxY) - cvFloor(fminY));
if (srcSize.width > 0 && srcSize.height > 0)
{
srcRoi &= cv::Rect(0, 0, srcSize.width, srcSize.height);
}
return srcRoi;
}
// 从原图取缺陷小图:detCutRoi 为检测图坐标下的截取框;输出按检测图坐标映射从原图取回(原图通道)
static bool WarpSrcCutImg(const cv::Mat &srcImg, const cv::Mat &detToSrcMat, const cv::Rect &detCutRoi, cv::Mat &outPatch)
{
outPatch.release();
if (srcImg.empty() || detToSrcMat.empty() || detToSrcMat.rows != 2 || detToSrcMat.cols != 3 ||
detCutRoi.width <= 0 || detCutRoi.height <= 0)
{
return false;
}
// detCutRoi 四角映射回原图,得到需要取的原图范围
cv::Rect srcBBox = DetRoiToSrcRoi(detToSrcMat, detCutRoi, cv::Rect(0, 0, 0, 0), srcImg.size());
if (srcBBox.width <= 0 || srcBBox.height <= 0)
{
return false;
}
// dst 为截取框局部坐标, src 为原图坐标(再平移到 srcBBox 内):
// src = detToSrcMat * (dst + detCutRoi.tl()) - srcBBox.tl()
cv::Mat mat = detToSrcMat.clone();
mat.at<double>(0, 2) += mat.at<double>(0, 0) * detCutRoi.x + mat.at<double>(0, 1) * detCutRoi.y - srcBBox.x;
mat.at<double>(1, 2) += mat.at<double>(1, 0) * detCutRoi.x + mat.at<double>(1, 1) * detCutRoi.y - srcBBox.y;
cv::warpAffine(srcImg(srcBBox), outPatch, mat, detCutRoi.size(), cv::INTER_LINEAR, cv::BORDER_REPLICATE);
return !outPatch.empty();
}
ImageResultJudge::ImageResultJudge(/* args */)
{
m_pCommonAnalysisyConfig = NULL;
@ -271,36 +337,17 @@ int ImageResultJudge::ResultJudge(std::shared_ptr<ImageAllResult> pImageResult)
allQxZoning[qx_name].push_back(singleQxZoningInfoTemp);
}
// 黑缺陷
if (QX_whiteBLACK == CONFIG_QX_WHITE)
{
// pQxLog->AddCheckstr(PrintLevel_3, DET_LOG_LEVEL_3, "param---", "qx :white");
if (hj >= 0 || Et >= 0)
{
pQxLog->AddCheckstr(PrintLevel_4, DET_LOG_LEVEL_3, "param ", "Error param idx %d / %d qx white hj %f and energ %f >0",
j + 1, pParam->useNum, hj, Et);
continue;
}
Et = std::abs(Et);
hj = std::abs(hj);
}
else
{
// pQxLog->AddCheckstr(PrintLevel_3, DET_LOG_LEVEL_3, "param---", "qx :blcak");
if (hj < 0 || Et < 0)
{
pQxLog->AddCheckstr(PrintLevel_4, DET_LOG_LEVEL_3, "param ", "Error param idx %d / %d qx blcak hj %f and energ %f < 0",
j + 1, pParam->useNum, hj, Et);
continue;
}
}
// 不区分亮暗缺陷: 参数(阈值)按正数配置; 实测灰阶/能量可能为负, 取绝对值后参与比较
int energyAbs = std::abs(energy);
float grayDisAbs = std::abs(grayDis);
Judge_Status = true;
float detArea = JudgArea;
if (energy >= Et &&
if (energyAbs >= Et &&
detArea >= At &&
grayDis >= hj &&
grayDisAbs >= hj &&
flen >= Len &&
fbreadth >= Breadth)
{
@ -330,15 +377,15 @@ int ImageResultJudge::ResultJudge(std::shared_ptr<ImageAllResult> pImageResult)
pQxLog->AddCheckstr(PrintLevel_4, DET_LOG_LEVEL_3, "Area", "%s -> %f %s %f ",
BOOL_TO_STR(detArea >= At), detArea, BOOL_TO_ThanLess(detArea >= At), At);
}
if (energy < Et)
if (energyAbs < Et)
{
pQxLog->AddCheckstr(PrintLevel_4, DET_LOG_LEVEL_3, "Energy", "%s -> %d %s %f ",
BOOL_TO_STR(energy >= Et), energy, BOOL_TO_ThanLess(energy >= Et), Et);
BOOL_TO_STR(energyAbs >= Et), energyAbs, BOOL_TO_ThanLess(energyAbs >= Et), Et);
}
if (grayDis < hj)
if (grayDisAbs < hj)
{
pQxLog->AddCheckstr(PrintLevel_4, DET_LOG_LEVEL_3, "HJ", "%s -> %f %s %f ",
BOOL_TO_STR(grayDis >= hj), grayDis, BOOL_TO_ThanLess(grayDis > hj), hj);
BOOL_TO_STR(grayDisAbs >= hj), grayDisAbs, BOOL_TO_ThanLess(grayDisAbs > hj), hj);
}
if (flen < Len)
{
@ -379,15 +426,22 @@ int ImageResultJudge::ResultJudge(std::shared_ptr<ImageAllResult> pImageResult)
}
}
// 标签缺陷:检测到标签直接判 NG
if (config_qx_type == CONFIG_QX_NAME_cell_tag)
std::string resultType = "OK";
// 不受区域参数控制的缺陷类型:区域参数未配置/未开启时,仍按"检出即 NG"上报
// ZJ_PianYi/ZJ_QueShi/CGMo_QueShi:传统算法检出
// TBBQ(铜箔标签):标签检测(或AI分类)给出,本身已按 BQ_Detect 开关/检出结果判定,不再受区域参数控制
if (!Judge_Status && (config_qx_type == CONFIG_QX_NAME_cell_ZJ_PianYi ||
config_qx_type == CONFIG_QX_NAME_cell_ZJ_QueShi ||
config_qx_type == CONFIG_QX_NAME_cell_CGMo_QueShi ||
config_qx_type == CONFIG_QX_NAME_cell_TBBQ))
{
bNG_Status = true;
pQxLog->AddCheckstr(PrintLevel_3, "qx info", " %s: region param not used -> report NG by base judge", qx_name.c_str());
Judge_Status = true;
bNG_Status = true;
}
std::string resultType = "OK";
if (!Judge_Status)
{
QX_info->result = QX_RESULT_TYPE_NoJduge;
@ -510,14 +564,30 @@ int ImageResultJudge::ResultJudge(std::shared_ptr<ImageAllResult> pImageResult)
// CheckUtil::printROI(CutRoi, "CutRoi");
cv::Size sz = cv::Size(QX_SAMLLIMG_WIDTH, QX_SAMLLIMG_HEIGHT);
if (pImageResult->detImg.channels() == 1)
// 缺陷小图直接取自原图裁剪区(保持原图通道 -> 真实彩色);按仿射矩阵从原图取回,
// ReJson/异常时退回检测图
{
cv::cvtColor(pImageResult->detImg(CutRoi), tem.srcImg, cv::COLOR_GRAY2BGR);
const cv::Mat &srcImg = pImageResult->result->in_shareImage->img;
bool bGot = false;
if (pImageResult->result->in_shareImage->Det_Mode != DET_MODE_ReJson)
{
bGot = WarpSrcCutImg(srcImg, pDetResult->detToSrcMat, CutRoi, tem.srcImg);
}
if (!bGot)
{
int nOffsetX = (pImageResult->result->in_shareImage->Det_Mode != DET_MODE_ReJson) ? pDetResult->CutRoi.x : 0;
int nOffsetY = (pImageResult->result->in_shareImage->Det_Mode != DET_MODE_ReJson) ? pDetResult->CutRoi.y : 0;
cv::Rect srcCanvasRoi(CutRoi.x + nOffsetX, CutRoi.y + nOffsetY, CutRoi.width, CutRoi.height);
if (!srcImg.empty() && CheckUtil::RoiInImg(srcCanvasRoi, srcImg))
{
tem.srcImg = srcImg(srcCanvasRoi).clone();
}
else
{
tem.srcImg = pImageResult->detImg(CutRoi).clone();
}
}
}
cv::resize(tem.srcImg, tem.resizeImg, sz);
int nqx_type = ConfigTypeToResultType(config_qx_type);
@ -528,9 +598,13 @@ int ImageResultJudge::ResultJudge(std::shared_ptr<ImageAllResult> pImageResult)
tem.max_v = maxValue;
tem.strTypeName = QX_Result_Names[nqx_type];
tem.qx_Code = QX_Result_Code[nqx_type];
tem.srcImgroi = roi;
tem.srcImgroi.x += pDetResult->CutRoi.x;
tem.srcImgroi.y += pDetResult->CutRoi.y;
tem.qx_level = QX_info->qx_level;
// 缺陷 roi 映射回原图(按仿射矩阵映射, 否则按 CutRoi 平移)
{
const cv::Mat &srcSizeImg = pImageResult->result->in_shareImage->img;
tem.srcImgroi = DetRoiToSrcRoi(pDetResult->detToSrcMat, roi, pDetResult->CutRoi,
srcSizeImg.empty() ? pImageResult->detImg.size() : srcSizeImg.size());
}
tem.len = flen;
tem.breadth = fbreadth;
tem.qx_type = 0;
@ -566,9 +640,7 @@ int ImageResultJudge::ResultJudge(std::shared_ptr<ImageAllResult> pImageResult)
GetAIDetImg(pImageResult, pCenter, tem.AI_in_Img, tem.AI_out_img);
m_CheckResult_shareP->qxImageResult.push_back(tem);
pQxLog->bPrintStr = true;
pQxLog->AddCheckstr(PrintLevel_4, DET_LOG_LEVEL_3, " result ", " ---name: %s ---code: %s ---srcImgroi.x: %d ---srcImgroi.y: %d\n", tem.strTypeName.c_str(), tem.qx_Code.c_str(), tem.srcImgroi.x, tem.srcImgroi.y);
pQxLog->bPrintStr = false;
}
else
{
@ -629,14 +701,30 @@ int ImageResultJudge::ResultJudge(std::shared_ptr<ImageAllResult> pImageResult)
// CheckUtil::printROI(CutRoi, "CutRoi");
cv::Size sz = cv::Size(QX_SAMLLIMG_WIDTH, QX_SAMLLIMG_HEIGHT);
if (pImageResult->detImg.channels() == 1)
// 缺陷小图直接取自原图裁剪区(保持原图通道 -> 真实彩色);按仿射矩阵从原图取回,
// ReJson/异常时退回检测图
{
const cv::Mat &srcImg = pImageResult->result->in_shareImage->img;
bool bGot = false;
if (pImageResult->result->in_shareImage->Det_Mode != DET_MODE_ReJson)
{
cv::cvtColor(pImageResult->detImg(CutRoi), tem.srcImg, cv::COLOR_GRAY2BGR);
bGot = WarpSrcCutImg(srcImg, pDetResult->detToSrcMat, CutRoi, tem.srcImg);
}
if (!bGot)
{
int nOffsetX = (pImageResult->result->in_shareImage->Det_Mode != DET_MODE_ReJson) ? pDetResult->CutRoi.x : 0;
int nOffsetY = (pImageResult->result->in_shareImage->Det_Mode != DET_MODE_ReJson) ? pDetResult->CutRoi.y : 0;
cv::Rect srcCanvasRoi(CutRoi.x + nOffsetX, CutRoi.y + nOffsetY, CutRoi.width, CutRoi.height);
if (!srcImg.empty() && CheckUtil::RoiInImg(srcCanvasRoi, srcImg))
{
tem.srcImg = srcImg(srcCanvasRoi).clone();
}
else
{
tem.srcImg = pImageResult->detImg(CutRoi).clone();
}
}
}
cv::resize(tem.srcImg, tem.resizeImg, sz);
int nqx_type = ConfigTypeToResultType(config_qx_type);
@ -647,9 +735,13 @@ int ImageResultJudge::ResultJudge(std::shared_ptr<ImageAllResult> pImageResult)
tem.max_v = maxValue;
tem.strTypeName = QX_Result_Names[nqx_type];
tem.qx_Code = QX_Result_Code[nqx_type];
tem.srcImgroi = roi;
tem.srcImgroi.x += pDetResult->CutRoi.x;
tem.srcImgroi.y += pDetResult->CutRoi.y;
tem.qx_level = QX_info->qx_level;
// 缺陷 roi 映射回原图(按仿射矩阵映射, 否则按 CutRoi 平移)
{
const cv::Mat &srcSizeImg = pImageResult->result->in_shareImage->img;
tem.srcImgroi = DetRoiToSrcRoi(pDetResult->detToSrcMat, roi, pDetResult->CutRoi,
srcSizeImg.empty() ? pImageResult->detImg.size() : srcSizeImg.size());
}
tem.len = flen;
tem.breadth = fbreadth;
tem.qx_type = 0;
@ -788,32 +880,9 @@ int ImageResultJudge::DrawResult(std::shared_ptr<ImageAllResult> pImageResult)
draw_str->txtP = cv::Point(draw_qx->cp.x + r, draw_qx->cp.y - r);
draw_str->txtsize = 0.5;
draw_str->txtbackgroundk = txtbackgroundk;
cv::Rect srcroi = pqx->srcImgroi;
cv::Point srccp = cv::Point(srcroi.x + srcroi.width * .5, srcroi.y + srcroi.height * 0.5);
char buffer[128];
{
std::string text = std::to_string(pqx->idx) + ":" + pqx->strTypeName;
draw_str->strlist.push_back(text);
}
{
sprintf(buffer, " center:%d %d", srccp.x, srccp.y);
std::string text = buffer;
draw_str->strlist.push_back(text);
}
{
sprintf(buffer, " len %0.2f A: %0.2f ", pqx->len, pqx->area);
std::string text = buffer;
draw_str->strlist.push_back(text);
}
{
sprintf(buffer, " breadth %0.2f A: %0.2f ", pqx->breadth, pqx->area);
std::string text = buffer;
draw_str->strlist.push_back(text);
}
{
sprintf(buffer, " E:%0.2f hj %0.2f", pqx->energy, pqx->hj);
std::string text = buffer;
// 只绘制 id:缺陷code (缺陷名可能中文乱码, 用code替代)
std::string text = std::to_string(pqx->idx) + ":" + pqx->qx_Code;
draw_str->strlist.push_back(text);
}
sendTask(draw_str);
@ -1028,75 +1097,13 @@ int ImageResultJudge::addInDrawBlob(int errortype, int blobidx, QX_ERROR_INFO_ *
int ImageResultJudge::ConfigTypeToResultType(int nconfigType)
{
bool bdesc = false;
int resultError_type = ERROR_TYPE_OK;
if(nconfigType < CONFIG_QX_NAME_Names.size())
{
string cqn = CONFIG_QX_NAME_Names[nconfigType];
for(int i = 0; i < m_FlawCodeList.size(); i++)
{
for(int j = 0; j < m_FlawCodeList.at(i).config_flaw_name.size(); j++)
{
if(cqn == m_FlawCodeList.at(i).config_flaw_name[j])
{
resultError_type = i;
bdesc = true;
break;
}
}
}
}
// 如果描述没有配置,用默认值
if(!bdesc)
{
switch (nconfigType)
// 缺陷类型(CONFIG_QX_NAME_)与结果列表(QX_Result_Names/QX_Result_Code)按配置顺序一一对应,直接按下标映射
if (nconfigType >= 0 && nconfigType < CONFIG_QX_NAME_count &&
nconfigType < (int)QX_Result_Names.size())
{
case CONFIG_QX_NAME_cell_aotudian:
resultError_type = ERROR_TYPE_aotudian;
break;
case CONFIG_QX_NAME_cell_other:
resultError_type = ERROR_TYPE_other;
break;
case CONFIG_QX_NAME_cell_line:
resultError_type = ERROR_TYPE_line;
break;
case CONFIG_QX_NAME_cell_zangwu:
resultError_type = ERROR_TYPE_zangwu;
break;
case CONFIG_QX_NAME_cell_edge:
resultError_type = ERROR_TYPE_edge;
break;
case CONFIG_QX_NAME_cell_ymhs:
resultError_type = ERROR_TYPE_ymhs;
break;
case CONFIG_QX_NAME_cell_dianzhuang:
resultError_type = ERROR_TYPE_dianzhuang;
break;
case CONFIG_QX_NAME_cell_posun:
resultError_type = ERROR_TYPE_posun;
break;
case CONFIG_QX_NAME_cell_xianwei:
resultError_type = ERROR_TYPE_xianwei;
break;
case CONFIG_QX_NAME_cell_shuizi:
resultError_type = ERROR_TYPE_shuizi;
break;
case CONFIG_QX_NAME_cell_danban:
resultError_type = ERROR_TYPE_danban;
break;
case CONFIG_QX_NAME_cell_fuchen:
resultError_type = ERROR_TYPE_fuchen;
break;
case CONFIG_QX_NAME_cell_tag:
resultError_type = ERROR_TYPE_tag;
break;
default:
break;
return nconfigType;
}
}
return resultError_type;
return ERROR_TYPE_OK;
}
int ImageResultJudge::sendTask(std::shared_ptr<DrawInfo> task)
@ -1143,7 +1150,7 @@ int ImageResultJudge::DrawResultTask(std::shared_ptr<ImageResultJudge::DrawInfo>
if (task->type == Draw_circle)
{
cv::circle(task->drawImg, task->cp, task->dr, task->color);
cv::circle(task->drawImg, task->cp, task->dr, CheckUtil::GetDrawColor(task->drawImg, task->color));
if (task->txtbackgroundk)
{
task->txtbackgroundk->txtDrawRoi.push_back(task->roi);
@ -1151,7 +1158,7 @@ int ImageResultJudge::DrawResultTask(std::shared_ptr<ImageResultJudge::DrawInfo>
}
if (task->type == Draw_rect)
{
cv::rectangle(task->drawImg, task->roi, task->color);
cv::rectangle(task->drawImg, task->roi, CheckUtil::GetDrawColor(task->drawImg, task->color));
if (task->txtbackgroundk)
{
task->txtbackgroundk->txtDrawRoi.push_back(task->roi);
@ -1215,7 +1222,7 @@ int ImageResultJudge::DrawResultTask(std::shared_ptr<ImageResultJudge::DrawInfo>
// 画文字
cv::putText(task->drawImg, txt, cv::Point(r.x, r.y + height),
fontFace, fontScale, task->color, thickness);
fontFace, fontScale, CheckUtil::GetDrawColor(task->drawImg, task->color), thickness);
height += lineHeight; // 下一行
}
// cv::rectangle(task->drawImg, rtxt, cv::Scalar(255, 0, 0));
@ -1223,7 +1230,7 @@ int ImageResultJudge::DrawResultTask(std::shared_ptr<ImageResultJudge::DrawInfo>
cv::Point p(r.x, r.y);
if (task->cp.x - r.x > 30 || task->cp.y - r.y > 30)
{
cv::line(task->drawImg, p, task->cp, cv::Scalar(80, 0, 200));
cv::line(task->drawImg, p, task->cp, CheckUtil::GetDrawColor(task->drawImg, cv::Scalar(80, 0, 200)));
}
// cv::circle(task->drawImg, task->cp, 3, task->color);

File diff suppressed because it is too large Load Diff

@ -32,11 +32,13 @@ set(ModuleName "")
# 自定义安装前缀
set(CMAKE_INSTALL_PREFIX /usr/local/polet CACHE PATH "Install path prefix" FORCE)
set(HEADER_FILES include/ConfigBase.h)
# 库文件添加 _backplate 后缀
set_target_properties(Config PROPERTIES OUTPUT_NAME "Config_backplate")
# 安装动态库
install(TARGETS Config
LIBRARY DESTINATION lib # 安装到 CMAKE_INSTALL_PREFIX/lib
ARCHIVE DESTINATION lib/static
RUNTIME DESTINATION bin
PUBLIC_HEADER DESTINATION include) # 安装到 CMAKE_INSTALL_PREFIX/include
# 安装头文件
install(FILES ${HEADER_FILES} DESTINATION include)
# 安装头文件(添加 _backplate 后缀)
install(FILES include/ConfigBase.h DESTINATION include RENAME ConfigBase_backplate.h)

@ -36,44 +36,31 @@ static const std::string WHITE_BLCAK_Names[] =
{
"black",
"white"};
// 检测缺陷的种类
// 检测缺陷的种类(前 7 项与 defect_list.json 前 7 条一一对应,勿随意调整顺序;
// 新增类型统一追加在末尾,结果名称/代码按名称对齐,不依赖下标)
enum CONFIG_QX_NAME_
{
CONFIG_QX_NAME_cell_aotudian, // 分类 凹凸点
CONFIG_QX_NAME_cell_other, // 分类 其他
CONFIG_QX_NAME_cell_line, // 分类 线状
CONFIG_QX_NAME_cell_zangwu, // 分类 脏污
CONFIG_QX_NAME_cell_edge, // 分类 边缘
CONFIG_QX_NAME_cell_ymhs, // 分类 异物
CONFIG_QX_NAME_cell_dianzhuang, // 分类 点状
CONFIG_QX_NAME_cell_posun, // 分类 破损
CONFIG_QX_NAME_cell_xianwei, // 分类 纤维
CONFIG_QX_NAME_cell_shuizi, // 分类 水渍
CONFIG_QX_NAME_cell_danban, // 分类 淡斑
CONFIG_QX_NAME_cell_fuchen, // 分类 浮尘
CONFIG_QX_NAME_cell_tag, // 分类 标签
CONFIG_QX_NAME_support_offset, // 支架偏移
CONFIG_QX_NAME_support_loss, // 支架缺失
CONFIG_QX_NAME_cell_ZJ_PianYi, // 分类 支架偏移
CONFIG_QX_NAME_cell_ZJ_QueShi, // 分类 支架缺失
CONFIG_QX_NAME_cell_CGMo_QueShi, // 分类 把手缺失
CONFIG_QX_NAME_cell_DarkAoHen, // 分类 暗场凹痕(AI分类,仅暗场;目前不检测)
CONFIG_QX_NAME_cell_QiPao, // 分类 气泡(AI分类)
CONFIG_QX_NAME_cell_TBBQ, // 分类 铜箔标签(标签检测 / AI分类)
CONFIG_QX_NAME_cell_TBMark, // 分类 铜箔标记(AI分类)
CONFIG_QX_NAME_cell_QiTa, // 分类 其他(AI分类)
CONFIG_QX_NAME_count,
};
// 缺陷项对应在参数中的名称
// 缺陷项对应在参数中的名称(Check_Param 的表单键名;顺序与上面对应)
static std::vector<std::string> CONFIG_QX_NAME_Names =
{
"aotudian",
"other",
"line",
"zangwu",
"edge",
"ymhs",
"dianzhuang",
"posun",
"xianwei",
"shuizi",
"danban",
"fuchen",
"tag",
"support_offset",
"support_loss",
"ZJ_PianYi",
"ZJ_QueShi",
"CGMo_QueShi",
"DarkAoHen",
"QiPao",
"TBBQ",
"TBMark",
"QiTa",
};
// 分析类型
@ -932,6 +919,147 @@ struct Function_Image_Align
return str123;
}
};
// 把手区域最大数量(参数里最多可画4个把手区域)
#define HANDLE_REGION_MAX_NUM 4
struct Function_Support_Det
{
// 支架参数(Support_Detect)
bool bDetSupport; // 支架偏位检测开关(Support_Detect.isOpen)
bool bDrawSupport; // 支架绘制开关(Support_Detect.form.support_param.bDrawSupport):是否绘制支架中心十字/距离线
bool bUseTraditionEdge; // 传统算法搜边开关(Support_Detect.form.support_param.bUseTraditionEdge):
// true=产品边缘用裁剪图(detImg)转灰度、二值化后搜边(不依赖定位模型 bigmask);false=用定位模型 bigmask
float x_standard; // 水平方向标准距离(left/right 用)
float y_standard; // 垂直方向标准距离(up/down 用)
std::vector<std::string> edge_select; // 参与计算的方向:up/down/left/right
float x_offset;
float y_offset;
float r_offset; // 支架旋转偏移阈值(度):支架旋转矩形长轴(对应左/右边缘)与产品左/右边缘夹角超过该值判偏移;<=0 不判定
float support_min_area; // 支架缺失判定:支架面积阈值(单位 mm²)。支架由 Align_Inner 模型(smallmask)检出,支架面积 < 该值判缺失;<=0 不判定
// 支架边缘原图精细化(Support_Detect.form.support_param,均可选;仅对 edge_select 选中的边缘生效):
// 沿边缘按步长采样,从边缘向产品内侧逐像素找第一个灰度 >= 阈值的像素作为新边缘点,再重新拟合边缘线
float edge_refine_search; // edge_refine_search:最多向内搜索的像素数;<=0=未配置(默认 80)
float edge_refine_step; // edge_refine_step:沿边采样步长(像素,不需要每个点都找);<=0=未配置(默认 40)
float edge_refine_gray; // edge_refine_gray:边缘点灰度阈值(0~255,灰度 >= 该值视为产品边缘);<=0=未配置(默认 240)
// 支架方向精细化(Support_Detect.form.support_param,均可选):模型 smallmask 精度低
// (1696×800 上采样回原图),minAreaRect 求支架方向角误差大;改为在原图(detImg)上拟合支架长边求方向角
float support_refine_search; // support_refine_search:取模型支架外接矩形最外侧多少列作为边缘搜索带;<=0=未配置(默认 30)
float support_refine_step; // support_refine_step:沿支架长边采样步长(像素);<=0=未配置(默认 20)
float support_refine_segments; // support_refine_segments:采样点分段数(每段稳健拟合一条线取角度中位数);<=0=未配置(默认 6)
// 把手参数(Handle_Detect)
bool bDetHandle; // 把手缺失检测开关(Handle_Detect.isOpen)
// 把手区域(最多 HANDLE_REGION_MAX_NUM 个,只保存非空的;区域宽高用于把手卡控)
std::vector<std::vector<cv::Point>> handleRegions;
std::vector<cv::Rect> handleBoxes;
float handle_diff; // 把手差异阈值
// 把手提取/匹配范围参数:JSON(Handle_Detect.form.handle_param)下同名键可选配置,未配置用 ImgCheckAnalysisy.cpp
// 顶部宏的默认值;各参数含义/调整影响见该文件参数注释。现场调这些参数不用改代码重编译
float handle_size_min_ratio; // handle_size_min:凸起宽高 / 区域宽高 比值下限;<=0=未配置(默认 0.8)
float handle_size_max_ratio; // handle_size_max:比值上限;<=0=未配置(默认 1.2)
float handle_search_ratio; // handle_search_ratio:区域附近搜索范围外扩倍数;<=0=未配置(默认 5)
float handle_min_depth_coef; // handle_min_depth:凸起最小深度系数(×量化步长);<=0=未配置(默认 2)
float handle_min_area_coef; // handle_min_area:凸起最小面积系数(×最小深度²);<=0=未配置(默认 2)
float handle_edge_margin_coef; // handle_edge_margin:边缘线外扩系数(×量化步长);<0=未配置(默认 3),0=按“最小深度一半”自动
// 标签参数(BQ_Detect)
bool bDetTag; // 标签检测开关(BQ_Detect.isOpen)
float BQ_area; // 标签检测参数:面积
float BQ_energy; // 标签检测参数:能量
float BQ_len; // 标签检测参数:长度
float BQ_hj; // 标签检测参数:灰阶
Function_Support_Det ()
{
Init();
}
void Init()
{
bDetSupport = false;
bDrawSupport = false;
bUseTraditionEdge = false;
x_standard = 0;
y_standard = 0;
edge_select.clear();
x_offset = 0;
y_offset = 0;
r_offset = 0;
support_min_area = 0;
edge_refine_search = 0;
edge_refine_step = 0;
edge_refine_gray = 0;
support_refine_search = 0;
support_refine_step = 0;
support_refine_segments = 0;
bDetHandle = false;
handleRegions.clear();
handleBoxes.clear();
handle_diff = 20;
// 范围参数默认“未配置”:0=未配置(用代码默认),edge_margin 用 -1 表示未配置(0=自动)
handle_size_min_ratio = 0;
handle_size_max_ratio = 0;
handle_search_ratio = 0;
handle_min_depth_coef = 0;
handle_min_area_coef = 0;
handle_edge_margin_coef = -1;
bDetTag = false;
BQ_area = 0;
BQ_energy = 0;
BQ_len = 0;
BQ_hj = 0;
}
void copy(Function_Support_Det tem)
{
this->bDetSupport = tem.bDetSupport;
this->bDrawSupport = tem.bDrawSupport;
this->bUseTraditionEdge = tem.bUseTraditionEdge;
this->x_standard = tem.x_standard;
this->y_standard = tem.y_standard;
this->edge_select.assign(tem.edge_select.begin(), tem.edge_select.end());
this->x_offset = tem.x_offset;
this->y_offset = tem.y_offset;
this->r_offset = tem.r_offset;
this->support_min_area = tem.support_min_area;
this->edge_refine_search = tem.edge_refine_search;
this->edge_refine_step = tem.edge_refine_step;
this->edge_refine_gray = tem.edge_refine_gray;
this->support_refine_search = tem.support_refine_search;
this->support_refine_step = tem.support_refine_step;
this->support_refine_segments = tem.support_refine_segments;
this->bDetHandle = tem.bDetHandle;
this->handleRegions.assign(tem.handleRegions.begin(), tem.handleRegions.end());
this->handleBoxes.assign(tem.handleBoxes.begin(), tem.handleBoxes.end());
this->handle_diff = tem.handle_diff;
this->handle_size_min_ratio = tem.handle_size_min_ratio;
this->handle_size_max_ratio = tem.handle_size_max_ratio;
this->handle_search_ratio = tem.handle_search_ratio;
this->handle_min_depth_coef = tem.handle_min_depth_coef;
this->handle_min_area_coef = tem.handle_min_area_coef;
this->handle_edge_margin_coef = tem.handle_edge_margin_coef;
this->bDetTag = tem.bDetTag;
this->BQ_area = tem.BQ_area;
this->BQ_energy = tem.BQ_energy;
this->BQ_len = tem.BQ_len;
this->BQ_hj = tem.BQ_hj;
}
void print(std::string str)
{
printf("%s>>bDetSupport %d bDrawSupport %d bUseTraditionEdge %d bDetHandle %d bDetTag %d x_standard %f y_standard %f x_offset %f y_offset %f r_offset %f support_min_area %f edgeRefine[search %.0f step %.0f gray %.0f] supportRefine[search %.0f step %.0f seg %.0f] edge num %ld handleRegion num %ld handle_diff %f handleRange[size %.2f~%.2f search %.1f depth %.1f area %.1f margin %.1f(0=自动,<0=默认)] BQ[area %f energy %f len %f hj %f]\n",
str.c_str(), bDetSupport, bDrawSupport, bUseTraditionEdge, bDetHandle, bDetTag, x_standard, y_standard, x_offset, y_offset, r_offset, support_min_area, edge_refine_search, edge_refine_step, edge_refine_gray, support_refine_search, support_refine_step, support_refine_segments, edge_select.size(), handleRegions.size(), handle_diff,
handle_size_min_ratio, handle_size_max_ratio, handle_search_ratio, handle_min_depth_coef, handle_min_area_coef, handle_edge_margin_coef,
BQ_area, BQ_energy, BQ_len, BQ_hj);
}
std::string GetInfo(std::string str)
{
char buffer[1024];
sprintf(buffer, "%s>>bDetSupport %d bDrawSupport %d bUseTraditionEdge %d bDetHandle %d bDetTag %d x_standard %f y_standard %f x_offset %f y_offset %f r_offset %f support_min_area %f edgeRefine[search %.0f step %.0f gray %.0f] supportRefine[search %.0f step %.0f seg %.0f] edge num %ld handleRegion num %ld handle_diff %f handleRange[size %.2f~%.2f search %.1f depth %.1f area %.1f margin %.1f(0=自动,<0=默认)] BQ[area %f energy %f len %f hj %f]\n",
str.c_str(), bDetSupport, bDrawSupport, bUseTraditionEdge, bDetHandle, bDetTag, x_standard, y_standard, x_offset, y_offset, r_offset, support_min_area, edge_refine_search, edge_refine_step, edge_refine_gray, support_refine_search, support_refine_step, support_refine_segments, edge_select.size(), handleRegions.size(), handle_diff,
handle_size_min_ratio, handle_size_max_ratio, handle_search_ratio, handle_min_depth_coef, handle_min_area_coef, handle_edge_margin_coef,
BQ_area, BQ_energy, BQ_len, BQ_hj);
std::string str123 = buffer;
return str123;
}
};
// 检测功能
struct CheckFunction
@ -988,6 +1116,7 @@ struct CheckFunction
return str123;
}
};
// 单通道检测功能
struct ChannelCheckFunction
{
@ -1080,9 +1209,10 @@ struct Base_Function_SaveImg
{
bool bOpen; // 是否开启
bool bSaveMarkImg; // mark 存图
bool bSaveAIDetImg; // mark 存图
bool bSaveClsImg; // 分类 存图
bool bSaveAlginImg; // 分类 存图
bool bSaveAlginImg; // 定位 存图
bool bSaveBQDetImg; // 标签 存图
Base_Function_SaveImg()
{
@ -1091,29 +1221,31 @@ struct Base_Function_SaveImg
void Init()
{
bOpen = false;
bSaveMarkImg = false;
bSaveAIDetImg = false;
bSaveClsImg = false;
bSaveAlginImg = false;
bSaveBQDetImg = false;
}
void copy(Base_Function_SaveImg tem)
{
this->bOpen = tem.bOpen;
this->bSaveMarkImg = tem.bSaveMarkImg;
this->bSaveAIDetImg = tem.bSaveAIDetImg;
this->bSaveClsImg = tem.bSaveClsImg;
this->bSaveAlginImg = tem.bSaveAlginImg;
this->bSaveBQDetImg = tem.bSaveBQDetImg;
}
void print(std::string str)
{
printf("%s>>bOpen %d bSaveMarkImg %d bSaveClsImg %d bSaveAlginImg %d \n", str.c_str(),
bOpen, bSaveMarkImg, bSaveClsImg, bSaveAlginImg);
printf("%s>>bOpen %d bSaveAIDetImg %d bSaveClsImg %d bSaveAlginImg %d bSaveBQDetImg %d\n", str.c_str(),
bOpen, bSaveAIDetImg, bSaveClsImg, bSaveAlginImg, bSaveBQDetImg);
}
std::string GetInfo(std::string str)
{
char buffer[256];
sprintf(buffer, "%s>>bOpen %d bSaveMarkImg %d bSaveClsImg %d bSaveAlginImg %d \n", str.c_str(),
bOpen, bSaveMarkImg, bSaveClsImg, bSaveAlginImg);
sprintf(buffer, "%s>>bOpen %d bSaveAIDetImg %d bSaveClsImg %d bSaveAlginImg %d bSaveBQDetImg %d\n", str.c_str(),
bOpen, bSaveAIDetImg, bSaveClsImg, bSaveAlginImg, bSaveBQDetImg);
std::string str123 = buffer;
return str123;
}
@ -1302,60 +1434,6 @@ struct Base_Function_Edge_Det
}
};
// 支架检测
struct Base_Function_Support_Det
{
bool bOpen; // 是否开启
std::vector<cv::Point> supportRegion;
cv::RotatedRect supportRect;
float x_offset;
float y_offset;
float r_offset;
std::vector<cv::Point> handleRegion;
cv::RotatedRect handleRect;
Base_Function_Support_Det()
{
Init();
}
void Init()
{
bOpen = false;
supportRegion.clear();
supportRect = cv::RotatedRect();
x_offset = 0;
y_offset = 0;
r_offset = 0;
handleRegion.clear();
handleRect = cv::RotatedRect();
}
void copy(Base_Function_Support_Det tem)
{
this->bOpen = tem.bOpen;
this->supportRegion.assign(tem.supportRegion.begin(), tem.supportRegion.end());
this->supportRect = tem.supportRect;
this->x_offset = tem.x_offset;
this->y_offset = tem.y_offset;
this->r_offset = tem.r_offset;
this->handleRegion.assign(tem.handleRegion.begin(), tem.handleRegion.end());
this->handleRect = tem.handleRect;
}
void print(std::string str)
{
printf("%s>>bOpen %d x_offset %f y_offset %f r_offset %f\n", str.c_str(),
bOpen, x_offset, y_offset, r_offset);
}
std::string GetInfo(std::string str)
{
char buffer[256];
sprintf(buffer, "%s>>bOpen %d x_offset %f y_offset %f r_offset %f\n", str.c_str(),
bOpen, x_offset, y_offset, r_offset);
std::string str123 = buffer;
return str123;
}
};
// 基础检测功能
struct BaseCheckFunction
{
@ -1363,7 +1441,7 @@ struct BaseCheckFunction
Base_Function_Edge_Det edgeDet;
Base_Function_SaveImg saveImg;
Base_Function_BigNG bigNG;
Base_Function_Support_Det supportDet;
Function_Support_Det supportDet; // 支架/把手/标签检测(Support_Detect/Handle_Detect/BQ_Detect)
BaseCheckFunction()
{

@ -10,6 +10,7 @@
#include "ConfigManager.h"
#include <regex>
#include "ConfigBase.h"
#include "CheckConfigDefine.h"
#include "../../AlgorithmModule/include/ImgCheckConfig.h"
ConfigManager::ConfigManager()
{
@ -20,39 +21,31 @@ ConfigManager::~ConfigManager()
{
}
std::vector<ReadFlawCode> m_FlawCodeList;
// 缺陷结果名称/代码默认值(下标与 CONFIG_QX_NAME_ 一一对应;使用英文缺陷名,与 CONFIG_QX_NAME_Names 一致;
// defect_list.json 中 desc 与 CONFIG_QX_NAME_Names 同名的条目会按名称覆盖这里的值)
std::vector<std::string> QX_Result_Names =
{
"OK",
"aotudian",
"other",
"line",
"zangwu",
"edge",
"ymhs",
"dianzhuang",
"posun",
"xianwei",
"shuizi",
"danban",
"fuchen",
"tag",
};
"ZJ_PianYi",
"ZJ_QueShi",
"CGMo_QueShi",
"DarkAoHen",
"QiPao",
"TBBQ",
"TBMark",
"QiTa",
};
std::vector<std::string> QX_Result_Code =
{
"P0000",
"MA507",
"MA508",
"MA506",
"MA504",
"MA503",
"MA502",
"MA505",
"MA501",
"P0001",
"P0002",
"P0003",
"P0004",
"P0005",
"M701201",
"M200214",
"M200215",
"M200230", // TODO 暗场凹痕:缺陷代码待平台确认
"M200231", // TODO 气泡:缺陷代码待平台确认
"M200218",
"M200219",
"M200232", // TODO 其他:缺陷代码待平台确认
};
int ReadFlawCodeConfig(std::string json_path)
@ -97,18 +90,36 @@ int ReadFlawCodeConfig(std::string json_path)
}
m_FlawCodeList.push_back(tem);
}
for(int i = 0; i < m_FlawCodeList.size(); i++)
// 结果名称/代码表按“缺陷名称”与程序内缺陷类型(CONFIG_QX_NAME_Names)对齐:
// defect_list.json 的 desc 字段(可用';'分隔多个名称)与 CONFIG_QX_NAME_Names 同名时覆盖名称/代码。
// 不再按下标覆盖:缺陷列表新增/减少/换序时不会把 A 类型的名称/代码写到 B 类型上。
while (QX_Result_Names.size() < (size_t)CONFIG_QX_NAME_count)
{
if(i >= QX_Result_Names.size()) {
QX_Result_Names.push_back(m_FlawCodeList.at(i).flaw_name);
QX_Result_Code.push_back(m_FlawCodeList.at(i).flaw_code);
QX_Result_Names.push_back("");
}
else
while (QX_Result_Code.size() < (size_t)CONFIG_QX_NAME_count)
{
QX_Result_Names.at(i) = m_FlawCodeList.at(i).flaw_name;
QX_Result_Code.at(i) = m_FlawCodeList.at(i).flaw_code;
QX_Result_Code.push_back("");
}
for (size_t i = 0; i < m_FlawCodeList.size(); i++)
{
for (int iqx = 0; iqx < CONFIG_QX_NAME_count; iqx++)
{
bool bMatch = false;
for (size_t k = 0; k < m_FlawCodeList.at(i).config_flaw_name.size(); k++)
{
if (m_FlawCodeList.at(i).config_flaw_name.at(k) == CONFIG_QX_NAME_Names.at(iqx))
{
bMatch = true;
break;
}
}
if (bMatch)
{
QX_Result_Names.at(iqx) = m_FlawCodeList.at(i).flaw_name;
QX_Result_Code.at(iqx) = m_FlawCodeList.at(i).flaw_code;
}
}
}
return 0;
}
@ -169,7 +180,7 @@ int ConfigManager::UpdateConfig()
ReadFlawCodeConfig(defect_list_file);
bool bFileName = false;
// std::regex pattern(R"(param_[0-9]\.json)");
std::regex pattern(R"(param_(\d+|left|right)\.json)");
std::regex pattern(R"(param_.*\.json)");
if (!fs::exists(m_strConfigRootPath))
{
std::cerr << "目录不存在: " << m_strConfigRootPath << std::endl;

@ -411,6 +411,8 @@ int ChannelFuntonConfigJson::GetFunction(Json::Value value, CheckFunction &funct
std::string strCode = value[i]["itemCode"].asString();
// std::cout << strCode << std::endl;
// 注:支架/把手/标签检测(Support_Detect/Handle_Detect/BQ_Detect)已移至Base层(baseCheckData)解析
// 大缺陷检测
if ("BigQX_Detect" == strCode)
{
@ -849,234 +851,274 @@ int BaseFuntonConfigJson::GetFunction(Json::Value value)
_config.markLine.Init();
}
}
if ("Support_QX_Detect" == strCode)
// 存图项(2026-10 平台将 Det_Image_Save 改名为 BM_Image_Save,两个名称都兼容)
if ("Det_Image_Save" == strCode || "BM_Image_Save" == strCode)
{
auto value_f = value;
// std::cout << value_f << std::endl;
// getchar();
_config.supportDet.bOpen = value_f["isOpen"].asBool();
if (_config.supportDet.bOpen)
{
// 支架参数
{
auto value_region = value_f["form"]["support_param"]["support_region"];
if (value_region.isArray())
{
for (int idx = 0; idx < value_region.size(); idx++)
_config.saveImg.bOpen = value_f["isOpen"].asBool();
if (_config.saveImg.bOpen)
{
cv::Point p;
p.x = value_region[idx][0].asInt();
p.y = value_region[idx][1].asInt();
_config.supportDet.supportRegion.emplace_back(p);
}
if (_config.supportDet.supportRegion.size() > 0)
{
_config.supportDet.supportRect = minAreaRect(_config.supportDet.supportRegion);
}
}
}
if (value_f["form"]["support_param"]["x_offset"])
{
_config.supportDet.x_offset = value_f["form"]["support_param"]["x_offset"].asFloat();
}
if (value_f["form"]["support_param"]["y_offset"])
if (value_f["form"]["SaveImg_Config"]["Cls_Save"])
{
_config.supportDet.y_offset = value_f["form"]["support_param"]["y_offset"].asFloat();
_config.saveImg.bSaveClsImg = value_f["form"]["SaveImg_Config"]["Cls_Save"].asBool();
}
if (value_f["form"]["support_param"]["r_offset"])
if (value_f["form"]["SaveImg_Config"]["Align_Big_Save"])
{
_config.supportDet.r_offset = value_f["form"]["support_param"]["r_offset"].asFloat();
_config.saveImg.bSaveAlginImg = value_f["form"]["SaveImg_Config"]["Align_Big_Save"].asBool();
}
// 把手参数
{
auto value_region = value_f["form"]["handle_param"]["handle_region"];
if (value_region.isArray())
if (value_f["form"]["SaveImg_Config"]["AI_Det_Save"])
{
for (int idx = 0; idx < value_region.size(); idx++)
{
cv::Point p;
p.x = value_region[idx][0].asInt();
p.y = value_region[idx][1].asInt();
_config.supportDet.handleRegion.emplace_back(p);
_config.saveImg.bSaveAIDetImg = value_f["form"]["SaveImg_Config"]["AI_Det_Save"].asBool();
}
if (_config.supportDet.handleRegion.size() > 0)
if (value_f["form"]["SaveImg_Config"]["BQ_Det_Save"])
{
_config.supportDet.handleRect = minAreaRect(_config.supportDet.handleRegion);
}
}
_config.saveImg.bSaveBQDetImg = value_f["form"]["SaveImg_Config"]["BQ_Det_Save"].asBool();
}
}
else
{
_config.supportDet.Init();
_config.saveImg.Init();
}
// _config.edgeDet.print("edgeDet");
// getchar();
}
if ("Det_Image_Save" == strCode)
if ("Big_NG" == strCode)
{
auto value_f = value;
// std::cout << value_f << std::endl;
// getchar();
_config.saveImg.bOpen = value_f["isOpen"].asBool();
if (_config.saveImg.bOpen)
_config.bigNG.bOpen = value_f["isOpen"].asBool();
if (_config.bigNG.bOpen)
{
if (value_f["form"]["SaveImg_Config"]["Mark_Save"])
// 大缺陷参数按缺陷列表循环解析(表单键名与CONFIG_QX_NAME_Names一致)
for (int i = 0; i < CONFIG_QX_NAME_count; i++)
{
_config.saveImg.bSaveMarkImg = value_f["form"]["SaveImg_Config"]["Mark_Save"].asBool();
}
if (value_f["form"]["SaveImg_Config"]["Cls_Save"])
std::string strqx_name = CONFIG_QX_NAME_Names[i];
if (value_f["form"][strqx_name]["open"])
{
_config.saveImg.bSaveClsImg = value_f["form"]["SaveImg_Config"]["Cls_Save"].asBool();
_config.bigNG.qx[i].bOpen = value_f["form"][strqx_name]["open"].asBool();
}
if (value_f["form"]["SaveImg_Config"]["Align_Big_Save"])
if (value_f["form"][strqx_name]["Area"])
{
_config.saveImg.bSaveAlginImg = value_f["form"]["SaveImg_Config"]["Align_Big_Save"].asBool();
_config.bigNG.qx[i].fArea = value_f["form"][strqx_name]["Area"].asFloat();
}
_config.bigNG.qx[i].strname = strqx_name;
}
}
else
{
_config.saveImg.Init();
_config.bigNG.Init();
}
// _config.edgeDet.print("edgeDet");
// getchar();
}
if ("Big_NG" == strCode)
// 支架偏位检测(Support_Detect,2026-10 从通道层 Support_QX_Detect 移至Base层并拆分独立)
if ("Support_Detect" == strCode)
{
auto value_f = value;
// std::cout << value_f << std::endl;
// getchar();
_config.bigNG.bOpen = value_f["isOpen"].asBool();
if (_config.bigNG.bOpen)
// 检测项开关即支架偏位检测开关
_config.supportDet.bDetSupport = value_f["isOpen"].asBool();
if (_config.supportDet.bDetSupport)
{
if (value_f["form"]["aotudian"]["open"])
auto value_param = value_f["form"]["support_param"];
// 支架绘制开关:控制结果图上是否绘制支架中心十字/距离线(默认关,兼容旧参数)
if (value_param["bDrawSupport"].isBool())
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_aotudian].bOpen = value_f["form"]["aotudian"]["open"].asBool();
_config.supportDet.bDrawSupport = value_param["bDrawSupport"].asBool();
}
if (value_f["form"]["aotudian"]["Area"])
// 传统算法搜边开关
if (value_param["bUseTraditionEdge"].isBool())
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_aotudian].fArea = value_f["form"]["aotudian"]["Area"].asFloat();
_config.supportDet.bUseTraditionEdge = value_param["bUseTraditionEdge"].asBool();
}
_config.bigNG.qx[CONFIG_QX_NAME_cell_aotudian].strname = "aotudian";
if (value_f["form"]["other"]["open"])
if (value_param["x_standard"])
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_other].bOpen = value_f["form"]["other"]["open"].asBool();
_config.supportDet.x_standard = value_param["x_standard"].asFloat();
}
if (value_f["form"]["other"]["Area"])
if (value_param["y_standard"])
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_other].fArea = value_f["form"]["other"]["Area"].asFloat();
_config.supportDet.y_standard = value_param["y_standard"].asFloat();
}
_config.bigNG.qx[CONFIG_QX_NAME_cell_other].strname = "other";
if (value_f["form"]["line"]["open"])
if (value_param["x_offset"])
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_line].bOpen = value_f["form"]["line"]["open"].asBool();
_config.supportDet.x_offset = value_param["x_offset"].asFloat();
}
if (value_f["form"]["line"]["Area"])
if (value_param["y_offset"])
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_line].fArea = value_f["form"]["line"]["Area"].asFloat();
_config.supportDet.y_offset = value_param["y_offset"].asFloat();
}
_config.bigNG.qx[CONFIG_QX_NAME_cell_line].strname = "line";
if (value_f["form"]["zangwu"]["open"])
if (value_param["r_offset"])
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_zangwu].bOpen = value_f["form"]["zangwu"]["open"].asBool();
_config.supportDet.r_offset = value_param["r_offset"].asFloat();
}
if (value_f["form"]["zangwu"]["Area"])
if (value_param["support_min_area"])
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_zangwu].fArea = value_f["form"]["zangwu"]["Area"].asFloat();
_config.supportDet.support_min_area = value_param["support_min_area"].asFloat();
}
_config.bigNG.qx[CONFIG_QX_NAME_cell_zangwu].strname = "zangwu";
if (value_f["form"]["edge"]["open"])
// 支架边缘原图精细化(可选):沿边缘采样,从边缘向内找第一个灰度 >= 阈值的像素作为新边缘点,
// edge_refine_search:最多向内搜索像素数(未配置默认 80)
// edge_refine_step :沿边采样步长(像素)(未配置默认 40,不需要每个点都找)
// edge_refine_gray :边缘点灰度阈值(0~255,灰度 >= 该值视为产品边缘)(未配置默认 240)
if (value_param["edge_refine_search"])
{
_config.supportDet.edge_refine_search = value_param["edge_refine_search"].asFloat();
}
if (value_param["edge_refine_step"])
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_edge].bOpen = value_f["form"]["edge"]["open"].asBool();
_config.supportDet.edge_refine_step = value_param["edge_refine_step"].asFloat();
}
if (value_f["form"]["edge"]["Area"])
if (value_param["edge_refine_gray"])
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_edge].fArea = value_f["form"]["edge"]["Area"].asFloat();
_config.supportDet.edge_refine_gray = value_param["edge_refine_gray"].asFloat();
}
_config.bigNG.qx[CONFIG_QX_NAME_cell_edge].strname = "edge";
if (value_f["form"]["ymhs"]["open"])
// 支架方向精细化(可选):模型 smallmask 精度低(1696×800 上采样回原图),
// minAreaRect 求支架方向角误差大,改为在 detImg 灰度原图上拟合支架长边求方向角。
// support_refine_search :取模型支架外接矩形最外侧多少列作为边缘搜索带(未配置默认 30)
// support_refine_step :沿支架长边采样步长(像素)(未配置默认 20)
// support_refine_segments:采样点分段数(每段稳健拟合一条线取角度中位数,未配置默认 6)
if (value_param["support_refine_search"])
{
_config.supportDet.support_refine_search = value_param["support_refine_search"].asFloat();
}
if (value_param["support_refine_step"])
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_ymhs].bOpen = value_f["form"]["ymhs"]["open"].asBool();
_config.supportDet.support_refine_step = value_param["support_refine_step"].asFloat();
}
if (value_f["form"]["ymhs"]["Area"])
if (value_param["support_refine_segments"])
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_ymhs].fArea = value_f["form"]["ymhs"]["Area"].asFloat();
_config.supportDet.support_refine_segments = value_param["support_refine_segments"].asFloat();
}
_config.bigNG.qx[CONFIG_QX_NAME_cell_ymhs].strname = "ymhs";
if (value_f["form"]["dianzhuang"]["open"])
auto value_qx = value_param["edge_select"];
if (value_qx.isArray())
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_dianzhuang].bOpen = value_f["form"]["dianzhuang"]["open"].asBool();
for (int idx = 0; idx < value_qx.size(); idx++)
{
std::string strEdge = value_qx[idx].asString();
_config.supportDet.edge_select.push_back(strEdge);
}
}
}
if (value_f["form"]["dianzhuang"]["Area"])
}
// 把手检测(Handle_Detect,2026-10 从通道层 Support_QX_Detect 移至Base层并拆分独立)
if ("Handle_Detect" == strCode)
{
auto value_f = value;
// 检测项开关即把手缺失检测开关
_config.supportDet.bDetHandle = value_f["isOpen"].asBool();
if (_config.supportDet.bDetHandle)
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_dianzhuang].fArea = value_f["form"]["dianzhuang"]["Area"].asFloat();
auto handle_p = value_f["form"]["handle_param"];
// 把手差异阈值
if (handle_p["handle_diff"])
{
_config.supportDet.handle_diff = handle_p["handle_diff"].asFloat();
}
_config.bigNG.qx[CONFIG_QX_NAME_cell_dianzhuang].strname = "dianzhuang";
if (value_f["form"]["posun"]["open"])
// 把手提取/匹配范围参数(均可选:未配置时用 ImgCheckAnalysisy.cpp 顶部宏的默认值)
// handle_size_min/max :凸起宽高与把手区域外接矩形宽高的比值允许区间(默认 0.8 ~ 1.2)
// handle_search_ratio :把手区域附近搜索范围外扩倍数(默认 5)
// handle_min_depth :凸起最小深度系数(× 定位模型量化步长)(默认 2)
// handle_min_area :凸起最小面积系数(× 最小深度²)(默认 2)
// handle_edge_margin :主体边缘线外扩系数(× 量化步长),0=按“最小深度一半”自动(默认 3)
// 含义/调整影响详见 ImgCheckAnalysisy.cpp 顶部参数注释
if (handle_p["handle_size_min"])
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_posun].bOpen = value_f["form"]["posun"]["open"].asBool();
_config.supportDet.handle_size_min_ratio = handle_p["handle_size_min"].asFloat();
}
if (value_f["form"]["posun"]["Area"])
if (handle_p["handle_size_max"])
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_posun].fArea = value_f["form"]["posun"]["Area"].asFloat();
_config.supportDet.handle_size_max_ratio = handle_p["handle_size_max"].asFloat();
}
_config.bigNG.qx[CONFIG_QX_NAME_cell_posun].strname = "posun";
if (value_f["form"]["xianwei"]["open"])
if (handle_p["handle_search_ratio"])
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_xianwei].bOpen = value_f["form"]["xianwei"]["open"].asBool();
_config.supportDet.handle_search_ratio = handle_p["handle_search_ratio"].asFloat();
}
if (value_f["form"]["xianwei"]["Area"])
if (handle_p["handle_min_depth"])
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_xianwei].fArea = value_f["form"]["xianwei"]["Area"].asFloat();
_config.supportDet.handle_min_depth_coef = handle_p["handle_min_depth"].asFloat();
}
_config.bigNG.qx[CONFIG_QX_NAME_cell_xianwei].strname = "xianwei";
if (value_f["form"]["shuizi"]["open"])
if (handle_p["handle_min_area"])
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_shuizi].bOpen = value_f["form"]["shuizi"]["open"].asBool();
_config.supportDet.handle_min_area_coef = handle_p["handle_min_area"].asFloat();
}
if (value_f["form"]["shuizi"]["Area"])
if (handle_p["handle_edge_margin"])
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_shuizi].fArea = value_f["form"]["shuizi"]["Area"].asFloat();
_config.supportDet.handle_edge_margin_coef = handle_p["handle_edge_margin"].asFloat();
}
_config.bigNG.qx[CONFIG_QX_NAME_cell_shuizi].strname = "shuizi";
if (value_f["form"]["danban"]["open"])
bool bNewFormat = handle_p["handle_region1"].isArray();
for (int idx = 0; idx < HANDLE_REGION_MAX_NUM; idx++)
{
std::string strKey = bNewFormat ? ("handle_region" + std::to_string(idx + 1))
: ((idx == 0) ? std::string("handle_region") : std::string(""));
if (strKey.empty())
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_danban].bOpen = value_f["form"]["danban"]["open"].asBool();
break;
}
if (value_f["form"]["danban"]["Area"])
// 该把手区域被禁用时跳过
if (handle_p[strKey + "_disabled"].isBool() && handle_p[strKey + "_disabled"].asBool())
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_danban].fArea = value_f["form"]["danban"]["Area"].asFloat();
continue;
}
_config.bigNG.qx[CONFIG_QX_NAME_cell_danban].strname = "danban";
if (value_f["form"]["fuchen"]["open"])
auto value_region = handle_p[strKey];
if (!value_region.isArray() || value_region.size() < 3)
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_fuchen].bOpen = value_f["form"]["fuchen"]["open"].asBool();
continue; // 空区域:表示该处不需要检测把手
}
if (value_f["form"]["fuchen"]["Area"])
std::vector<cv::Point> region;
for (int ip = 0; ip < value_region.size(); ip++)
{
_config.bigNG.qx[CONFIG_QX_NAME_cell_fuchen].fArea = value_f["form"]["fuchen"]["Area"].asFloat();
if (!value_region[ip].isArray() || value_region[ip].size() < 2)
{
continue;
}
_config.bigNG.qx[CONFIG_QX_NAME_cell_fuchen].strname = "fuchen";
cv::Point p;
p.x = value_region[ip][0].asInt();
p.y = value_region[ip][1].asInt();
region.emplace_back(p);
}
else
if (region.size() < 3)
{
_config.bigNG.Init();
continue;
}
_config.supportDet.handleRegions.push_back(region);
_config.supportDet.handleBoxes.push_back(cv::boundingRect(region));
}
}
}
// 标签检测(BQ_Detect,2026-10 从通道层 Support_QX_Detect 移至Base层并拆分独立)
if ("BQ_Detect" == strCode)
{
auto value_f = value;
// 检测项开关即标签检测开关
_config.supportDet.bDetTag = value_f["isOpen"].asBool();
if (_config.supportDet.bDetTag)
{
auto tag_p = value_f["form"]["tag_param"];
if (tag_p["BQ_area"])
{
_config.supportDet.BQ_area = tag_p["BQ_area"].asFloat();
}
if (tag_p["BQ_energy"])
{
_config.supportDet.BQ_energy = tag_p["BQ_energy"].asFloat();
}
if (tag_p["BQ_len"])
{
_config.supportDet.BQ_len = tag_p["BQ_len"].asFloat();
}
if (tag_p["BQ_hj"])
{
_config.supportDet.BQ_hj = tag_p["BQ_hj"].asFloat();
}
}
// _config.edgeDet.print("edgeDet");
// getchar();
}
return 0;
}

@ -185,12 +185,12 @@ std::string Extract_ALL::Extract_Product_Name(std::string strPath, int userflag)
// std::cout << strPath << std::endl;
fs::path p = fs::path(strPath);
fs::path lastDir = p.parent_path().filename();
fs::path lastDir = p.parent_path().parent_path().filename();
std::string name = lastDir.string();
if (name.length()<7)
if (name.empty())
{
lastDir = p.parent_path().parent_path().filename();
lastDir = p.parent_path().filename();
name = lastDir.string();
}

@ -5,6 +5,7 @@
#include <sys/stat.h>
#include <unistd.h>
#include <string>
#include <algorithm>
#include "CheckUtil.hpp"
std::string ExtractFileNameWithoutExtension(const std::string &strImgPath)
{
@ -101,6 +102,7 @@ deal::deal()
m_nCurUseCPUIDX = 0;
m_nSaveDetprocessImg = 0;
m_nPushImgCount = 0;
m_nResultRecvCount = 0;
m_DetResult.Init();
for (int i = 0; i < READ_IMG_THREAD_NUM; i++)
{
@ -371,28 +373,20 @@ int deal::preCheck()
setReadThreadStart();
// 遍历所有图片 开始读图处理
AllImgNum = 0;
// 每张图对应一个通道,独立读入送检;多张图(多通道)分别处理,不能合并为 AB 双相机
for (const auto pcam : product->camera_list)
{
std::shared_ptr<JC_IMAGE_INFO_> tem = std::make_shared<JC_IMAGE_INFO_>();
tem->strCamID = pcam->strCamName;
for (const auto pimage : pcam->image_list)
{
std::shared_ptr<JC_IMAGE_INFO_> tem = std::make_shared<JC_IMAGE_INFO_>();
tem->strCamID = pcam->strCamName;
tem->strchannelName = pimage->strchannelName;
tem->strName = pimage->strName;
tem->strProductID = pimage->strProductID;
if (tem->strPath == "")
{
tem->strPath = pimage->strPath;
}
else
{
tem->strPath_B = pimage->strPath;
}
}
InsertReadImgInfo(tem);
AllImgNum++;
// break;
}
}
// AllImgNum = 1;
{
@ -822,6 +816,8 @@ void deal::ResultThread(int id)
{
strlog += "/result";
}
// 确保日志目录存在(否则 writeLog 报 Failed to open file)
_sysmkdirs_1(strlog.substr(0, strlog.find_last_of('/')));
writeLog(strlog, m_DetResult.strlist);
mutex_DetResult_.unlock();
@ -869,6 +865,7 @@ void deal::GetDealResultToQueu()
{
mutex_Result_list.lock();
m_Result_list.push(checkResult);
m_nResultRecvCount++;
mutex_Result_list.unlock();
}
else
@ -911,6 +908,30 @@ bool deal::ReadSystemConfig(const std::string &strPath)
}
int deal::GetDetImageInfo(std::string strProductID, std::string strSearchImg, std::vector<JC_IMAGE_INFO_> &jcImageInfoList)
{
// -f 批量跑新格式: 产品目录下 input 子目录, 只取 _src 原图
// (如 1M10121544R3B2_t_face_bright_src.jpg, 从图片名解析产品ID和相机名)
if (fs::is_directory(strSearchImg + "/input"))
{
jcImageInfoList.erase(jcImageInfoList.begin(), jcImageInfoList.end());
std::vector<cv::String> srcImgPaths;
cv::glob(strSearchImg + "/input/*.jpg", srcImgPaths, false);
for (int idx = 0; idx < srcImgPaths.size(); idx++)
{
JC_IMAGE_INFO_ tem;
int re = GetImgInfo_BACKPLATE_SRC(srcImgPaths.at(idx), &tem);
if (re != 0)
{
continue;
}
if (tem.strProductID == "")
{
tem.strProductID = strProductID;
}
jcImageInfoList.push_back(tem);
}
return 0;
}
// 1、获取图片路径
std::vector<cv::String> img_paths;
std::string strs1 = strSearchImg + "/*.jpg";
@ -1008,6 +1029,63 @@ int deal::GetImgInfo_POL_ET_PNG(std::string strImgPath, JC_IMAGE_INFO_ *pImageIn
}
return 0;
}
// -f 批量跑新格式: 解析图片名组装产品信息
// 图片名示例: 1M10121544R3B2_t_face_bright_src.jpg
// 产品ID: 第一个 '_' 之前的部分 (1M10121544R3B2)
// 相机名: 直接匹配文件名中的相机名(t_face_bright / t_face_dark), 只处理后缀为 _src 的原图
// 通道名: 统一用 t_face
int deal::GetImgInfo_BACKPLATE_SRC(std::string strImgPath, JC_IMAGE_INFO_ *pImageInfo)
{
if (pImageInfo == NULL)
{
return 1;
}
std::string strName = ExtractFileNameWithoutExtension(strImgPath);
// 只处理后缀为 _src 的原图, 跳过 _roi 等其它图
const std::string strSrcSuffix = "_src";
if (strName.size() <= strSrcSuffix.size() ||
strName.compare(strName.size() - strSrcSuffix.size(), strSrcSuffix.size(), strSrcSuffix) != 0)
{
return 1;
}
// 直接匹配文件名中的相机名(t_face_bright / t_face_dark)
std::string strCamName = "";
if (strName.find("t_face_bright") != std::string::npos)
{
strCamName = "t_face_bright";
}
else if (strName.find("t_face_dark") != std::string::npos)
{
strCamName = "t_face_dark";
}
if (strCamName == "")
{
printf("GetImgInfo_BACKPLATE_SRC: no camera match, skip %s\n", strImgPath.c_str());
return 1;
}
// 产品ID = 文件名第一个 '_' 之前的部分
std::string strProductID = strName;
size_t nPos = strName.find('_');
if (nPos != std::string::npos)
{
strProductID = strName.substr(0, nPos);
}
pImageInfo->strProductID = strProductID;
pImageInfo->strCamID = strCamName;
pImageInfo->strchannelName = "t_face"; // 通道名统一用 t_face
pImageInfo->strName = strName;
pImageInfo->strPath = strImgPath;
printf("strImgPath %s strName %s strProductID %s strCam %s strChannel %s\n",
strImgPath.c_str(), strName.c_str(), strProductID.c_str(), strCamName.c_str(), pImageInfo->strchannelName.c_str());
return 0;
}
int deal::InsertDetResult(ReadImgInfo tem)
{
std::lock_guard<std::mutex> lock(mtx_List[Mutex_Type_DetResult]);
@ -1024,6 +1102,7 @@ int deal::SendImgToCheck(std::shared_ptr<JC_IMAGE_INFO_> pDetImageInfo, IN_IMG_S
}
if (status == IN_IMG_Status_End)
{
// 不再上下拆图:一张图对应一个产品,只给该产品发送一次结束标志
std::shared_ptr<shareImage> tem = std::make_shared<shareImage>();
tem->strImgProductID = pDetImageInfo->strProductID;
tem->Status = -1;
@ -1046,7 +1125,8 @@ int deal::SendImgToCheck(std::shared_ptr<JC_IMAGE_INFO_> pDetImageInfo, IN_IMG_S
tem->strImgProductID = pDetImageInfo->strProductID;
tem->imgstr = m_strCurDate;
tem->strCameraName = pDetImageInfo->strCamID;
tem->camera_ID = 0;
// 按相机区分相机ID(明场=0/暗场=1):两个相机通道名相同时,避免结果文件同名互相覆盖
tem->camera_ID = (pDetImageInfo->strCamID.find("dark") != std::string::npos) ? 1 : 0;
if (m_nSaveDetprocessImg == 1)
{
tem->otherValue = 9;
@ -1083,37 +1163,7 @@ int deal::SendImgToCheck(std::shared_ptr<JC_IMAGE_INFO_> pDetImageInfo, IN_IMG_S
int re = 0;
// 一张图拍多个产品:上下拆分成多张,子图产品ID加索引后缀,分别送检
std::vector<cv::Mat> splitList;
if (!pDetImageInfo->img.empty() && pDetImageInfo->img.rows >= 2)
{
int half = pDetImageInfo->img.rows / 2;
splitList.push_back(pDetImageInfo->img(cv::Rect(0, 0, pDetImageInfo->img.cols, half)).clone());
splitList.push_back(pDetImageInfo->img(cv::Rect(0, half, pDetImageInfo->img.cols, pDetImageInfo->img.rows - half)).clone());
}
if (splitList.size() > 1)
{
for (size_t i = 0; i < splitList.size(); i++)
{
std::shared_ptr<shareImage> sub = std::make_shared<shareImage>();
*sub = *tem;
sub->img = splitList[i];
sub->strImgProductID = tem->strImgProductID + "_" + std::to_string(i);
sub->Status = IN_IMG_Status_OneImg;
if (m_pALLImgCheckAnalysisy)
{
re = m_pALLImgCheckAnalysisy->SetDataRun_SharePtr(sub);
if (re != 0)
{
return re;
}
}
m_nPushImgCount++;
}
return 0;
}
// 不再上下拆图:一张图对应一个产品,整图直接送检(产品ID不加索引后缀)
if (m_pALLImgCheckAnalysisy)
{
re = m_pALLImgCheckAnalysisy->SetDataRun_SharePtr(tem);
@ -1203,7 +1253,12 @@ int deal::DetImg()
{
m_nReadThread_type = READ_THREAD_TYPE_READIMG;
StartThread(THREAD_RUN_Only_ReadImg);
// -8 循环执行:像 -f 批量一样,线程只启动一次,内部重复跑同一套图
do
{
preCheck();
usleep(10 * 1000);
} while (runConfig.bLoop);
return 0;
}
@ -1234,6 +1289,36 @@ int deal::DetImg()
// getchar();
m_nSystemCheckType = CHECK_TYPE_OFFLING;
StartThread(THREAD_RUN_ALL);
// -8 循环执行:保持常驻,重复处理同一批产品
if (runConfig.bLoop)
{
while (true)
{
usleep(1000 * 1000);
}
}
// -f 批量(不加 -8 只执行一次):等待全部产品处理完成后退出
// DealImg 线程处理完一批后会清空 m_product_ID_List 并等待结果队列清空
while (true)
{
usleep(200 * 1000);
size_t nResultSize = 0;
{
std::lock_guard<std::mutex> lock(mutex_Result_list);
nResultSize = m_Result_list.size();
}
if (m_product_ID_List.empty() && nResultSize <= 0)
{
break;
}
}
// 留时间给结果线程把最后一组结果写完
usleep(2 * 1000 * 1000);
printf(">>> -f batch complete, exit \n");
fflush(NULL);
_exit(0);
return 0;
}
int deal::ReJson_product(std::vector<Json_Det_Path> &jsonList, std::string strProductName)
@ -1260,7 +1345,7 @@ int deal::ReJson_product(std::vector<Json_Det_Path> &jsonList, std::string strPr
std::string json_str = buffer.str(); // 获取 JSON 文件的内容作为字符串
// 显示读取到的 JSON 字符串
// std::cout << "JSON content: " << json_str << std::endl;
cv::Mat cutimg = cv::imread(strimg, 0);
cv::Mat cutimg = cv::imread(strimg, cv::IMREAD_UNCHANGED);
int f0 = strJson.rfind('_');
int f1 = strJson.rfind('.');
int f2 = strJson.rfind('/');
@ -1495,6 +1580,8 @@ int deal::InitCPUIDX()
start += READ_IMG_THREAD_NUM;
m_CPUInfo[THREAD_CPU_Main_saveImg].set(start, Save_IMG_THREAD_NUM);
start += Save_IMG_THREAD_NUM;
// 注意:检测线程的核带已下沉到库内部(AlgorithmModule/include/Define_Base.h 的
// CHECK_CPU_BAND_START / CHECK_CPU_BAND_NUM),下面这项仅为兼容赋值,不再影响检测线程绑核。
m_CPUInfo[THREAD_CPU_CheckSo].set(start, 15);
return 0;
}
@ -1514,12 +1601,12 @@ void deal::Thread_ReadImg(int id)
printf("pImageInfo == nullptr=================id %d=============\n", id);
return;
}
// pImageInfo->img = cv::imread(pImageInfo->strPath, 0);
pImageInfo->img = cv::imread(pImageInfo->strPath, 0);
// 原图保持原始通道(彩色图不再强制转灰度),结果图/缺陷小图才能显示真实色彩
pImageInfo->img = cv::imread(pImageInfo->strPath, cv::IMREAD_UNCHANGED);
// cv::imwrite(pImageInfo->strchannelName + "_A_ssssde.png", pImageInfo->img);
if (pImageInfo->strPath_B != "")
{
pImageInfo->img_B = cv::imread(pImageInfo->strPath_B, 0);
pImageInfo->img_B = cv::imread(pImageInfo->strPath_B, cv::IMREAD_UNCHANGED);
// cv::imwrite(pImageInfo->strchannelName + "_B_sssde.png", pImageInfo->img_B);
}
@ -1581,6 +1668,55 @@ int deal::LoadProductID(std::string strImgPath)
m_product_ID_List.erase(m_product_ID_List.begin(), m_product_ID_List.end());
std::vector<cv::String> img_paths;
// -f 批量跑新格式适配(参考 data/img/test-2 结构):
// <filePath>/<产品ID>_<时间戳>/input/<产品ID>_<相机名>_<位置>_src.jpg
// 也支持直接传入单个产品目录或产品目录下的 input 目录
{
std::string strPath = strImgPath;
while (!strPath.empty() && strPath[strPath.size() - 1] == '/')
{
strPath.erase(strPath.size() - 1);
}
fs::path rootPath(strPath);
if (!strPath.empty() && fs::is_directory(rootPath))
{
// 兼容直接传入 input 目录: 使用其上一级产品目录
if (rootPath.filename() == "input" && rootPath.has_parent_path())
{
rootPath = rootPath.parent_path();
}
std::vector<std::string> productDirList;
if (fs::is_directory(rootPath / "input"))
{
// 直接传入单个产品目录
productDirList.push_back(rootPath.string());
}
else
{
// 批量根目录: 含 input 子目录的子目录视为一个产品
for (const auto &entry : fs::directory_iterator(rootPath))
{
if (entry.is_directory() && fs::is_directory(entry.path() / "input"))
{
productDirList.push_back(entry.path().string());
}
}
}
if (!productDirList.empty())
{
std::sort(productDirList.begin(), productDirList.end());
for (const auto &strProductDir : productDirList)
{
m_product_ID_List.push_back(strProductDir);
std::cout << "new format product dir: " << strProductDir << std::endl;
}
return 0;
}
}
}
// tif 格式
{
std::string str_bmp = strImgPath + "/*.jpg";
@ -1629,10 +1765,20 @@ int deal::LoadProductID(std::string strImgPath)
}
}
if (m_product_ID_List.empty())
{
printf("warning: no product dir/image found under %s\n", strImgPath.c_str());
}
return 0;
}
int deal::DelImg_Cell_ET()
{
// -8 循环执行:每次重新加载产品列表,重复跑同一批产品
if (runConfig.bLoop && runConfig.filePath != "")
{
LoadProductID(runConfig.filePath);
}
size_t totalSize = m_product_ID_List.size();
if (totalSize > 0)
@ -1674,6 +1820,13 @@ int deal::Det_OneProduct_Cell_ET(std::string strProductName, int Idx)
{
std::string strProductID = GetFileName(strProductName);
// -f 批量跑新格式: 产品目录名为 <产品ID>_<时间戳>(如 1M10121544R3B2_20261012.151444.066219)
// 取第一个 '_' 之前的部分作为产品ID; 旧格式目录名本身即产品ID(无 '_')不受影响
size_t nProductIDPos = strProductID.find('_');
if (nProductIDPos != std::string::npos)
{
strProductID = strProductID.substr(0, nProductIDPos);
}
// 获取图片路径
std::vector<JC_IMAGE_INFO_> detImgInfoList;
@ -1763,6 +1916,33 @@ int deal::Det_OneProduct_Cell_ET(std::string strProductName, int Idx)
}
}
// -f 批量逐个产品处理:等待本产品的检测结果全部返回后,再处理下一个产品,
// 避免多个产品同时积压在检测队列中(内存/检测器压力过大导致卡死)
if (AllImgNum > 0)
{
int nWaitCnt = 0;
while (true)
{
int nRecv = 0;
int nPush = 0;
{
std::lock_guard<std::mutex> lock(mutex_Result_list);
nRecv = m_nResultRecvCount;
nPush = m_nPushImgCount;
}
if (nRecv >= nPush)
{
break;
}
if (nWaitCnt % 250 == 0)
{
printf(">>> waiting %s check result %d/%d ...\n", strProductID.c_str(), nRecv, nPush);
}
nWaitCnt++;
usleep(20 * 1000);
}
}
m_DetResult.one_result_list.push_back(tem_one);
m_DetResult.det_num_all++;
// if (m_DetResult.det_num_all == 1)

@ -391,19 +391,33 @@ struct Det_File_Result_Info
}
for (size_t i = 0; i < qx_result_list.size(); i++)
{
float fr = qx_result_list.at(i).num * 1.0 / qx_all_num * 100;
// 除零保护:没有任何缺陷统计时百分比按 0 处理
float fr = (qx_all_num > 0) ? (qx_result_list.at(i).num * 100.0f / qx_all_num) : 0.0f;
std::string str_qx = "";
// printf("len %d \n", qx_result_list.at(i).qx_name.length());
for (int k = 0; k < (20 - qx_result_list.at(i).qx_name.length()); k++)
// 名称补空格对齐到 20 字节。
// 注意:qx_name.length() 返回 size_t(无符号),若写成 20 - length(),
// 当名称超过 20 字节时(中文缺陷名如"铜箔面不良标签"为 21 字节)会下溢成
// 巨大无符号值,导致循环近似死循环(-f 批量卡住的根因)。
size_t nNameLen = qx_result_list.at(i).qx_name.length();
if (nNameLen < 20)
{
str_qx += " ";
str_qx.append(20 - nNameLen, ' ');
}
str_qx += qx_result_list.at(i).qx_name;
char buffer123[64];
sprintf(buffer123, ":%d %.1f%% ", qx_result_list.at(i).num, fr);
std::string st1 = buffer123;
str_qx += st1;
int nlen = fr;
// 百分比条长度限制在 [0,100],避免异常值造成超长字符串
int nlen = static_cast<int>(fr);
if (nlen < 0)
{
nlen = 0;
}
if (nlen > 100)
{
nlen = 100;
}
for (int j = 0; j < nlen; j++)
{
@ -499,6 +513,7 @@ struct DealRunConfig
RUNTYPE_ det_Type = RUNTYPE_RUN_Pre; // 处理
std::string filePath = ""; // 处理文件路径 如果为空 处理当前的单张图片
bool bSaveProcessImg = false; // 是否中间图片图片
bool bLoop = false; // -8 循环执行(常驻)
void print(std::string str)
{
printf(">>>>>>>>>>>>>>> %s <<<<<<<<<<<<\n", str.c_str());
@ -550,6 +565,9 @@ private:
int GetImgInfo_POL_ET_PNG(std::string strImgPath, JC_IMAGE_INFO_ *pImageInfo);
// -f 批量跑新格式: 解析 <产品ID>_<相机名>_<位置>_src.jpg 图片名组装产品信息(相机名 t_face_bright/t_face_dark, 通道名统一 t_face)
int GetImgInfo_BACKPLATE_SRC(std::string strImgPath, JC_IMAGE_INFO_ *pImageInfo);
// 功能测试
int Det_Funtion_Test();
// 边缘测试
@ -648,6 +666,7 @@ public:
int m_nSaveDetprocessImg;
int m_nPushImgCount; // 送检图片总数(拆分后)
int m_nResultRecvCount; // 已收到检测结果总数(-f 逐个产品处理:等当前产品结果返回后再跑下一个)
cv::Rect m_CutRoi;
Det_File_Result_Info m_DetResult;

@ -85,6 +85,8 @@ int main(int argc, char *argv[])
cout << "*** 5、./test_JBL_Check -falign filepath num : 批量测试定位算法,num 至多处理张数,结果:/home/aidlux/BOE/Align" << endl;
cout << "*** 6、./test_JBL_Check -rjson filepath: 单张复测,filepath xx/xx/3A3K380001B1DK_20240408_152157_Main_0_2_L255" << endl;
cout << "*** 6、./test_JBL_Check -rjsonall filepath: 一套图复测,filepath xx/xx/" << endl;
cout << "*** 7、./test_BACKPLATE -8: 循环执行程序(常驻),不加 -8 只执行一次" << endl;
cout << "*** 8、./test_BACKPLATE -f filepath: 批量执行 filepath 下的套图(新格式: <产品ID>_<时间戳>/input/<产品ID>_<相机名>_xxx_src.jpg, 相机名 t_face_bright/t_face_dark, 通道名统一 t_face)" << endl;
cout << "******************************************************" << endl;
return 0;
@ -145,12 +147,21 @@ int main(int argc, char *argv[])
test.runConfig.print("config");
// getchar();
signal(SIGINT, handler);
test.start();
while (true)
// -8: 循环执行程序(常驻);默认:执行一次后退出
bool bLoop = false;
for (int i = 1; i < argc; i++)
{
usleep(10 * 1000);
if (string(argv[i]) == "-8")
{
bLoop = true;
break;
}
}
test.runConfig.bLoop = bLoop;
test.start();
return 0;
}

Loading…
Cancel
Save