Compare commits

...

10 Commits

@ -102,7 +102,7 @@ int main(int argc, char *argv[])
GPU_Config gpu;
gpu.gpu_0 = true;
gpu.gpu_1 = true;
gpu.gpu_1 = false;
AI_Factory->InitALLAIModle(gpu);
std::shared_ptr<AIModel_Base> BOE_Edge_Detect = AI_Factory->BOE_Edge_Detect;

@ -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

@ -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)
{
sidx = 0;
int next = sidx + 1;
if (next >= m_nALLStreamNum)
{
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);

@ -43,9 +43,9 @@ int AIFactory::InitALLAIModle(GPU_Config gupconfig)
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.strName = "Defect";
edge_config.inputType = AIModel_Base::Input_CHW;
edge_config.Stream_num = 2;
edge_config.Stream_num = 4;
Defect->Init(edge_config);
}
if (!Class)
@ -53,9 +53,9 @@ int AIFactory::InitALLAIModle(GPU_Config gupconfig)
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 = "/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);
}
@ -65,9 +65,9 @@ int AIFactory::InitALLAIModle(GPU_Config gupconfig)
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.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)
@ -76,8 +76,9 @@ int AIFactory::InitALLAIModle(GPU_Config gupconfig)
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.strName = "Align_Outer";
edge_config.inputType = AIModel_Base::Input_CHW;
edge_config.Stream_num = 2;
Align_Outer->Init(edge_config);
}
if (!Align_Inner)
@ -86,8 +87,9 @@ int AIFactory::InitALLAIModle(GPU_Config gupconfig)
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.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)

@ -160,7 +160,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();

@ -25,6 +25,26 @@
#include <thread>
#include <opencv2/opencv.hpp>
using namespace std;
// 掩膜主体(近似矩形)四周凸起(如把手)的描述
struct Mask_Protrusion_
{
int nSide; // 所在边0 左 1 右 2 上 3 下
cv::Rect roi; // 凸起外接矩形(输入图坐标系,已裁剪到图像范围内)
float fLen; // 沿该边的长度(像素)
float fDepth; // 伸出主体的深度(像素)
float fArea; // 凸起面积roi 内掩膜像素数)
float fFillRatio; // roi 内掩膜像素占比(用于判定是否"类似矩形块"
Mask_Protrusion_()
{
nSide = 0;
fLen = 0.0f;
fDepth = 0.0f;
fArea = 0.0f;
fFillRatio = 0.0f;
}
};
class CheckUtil
{
public:
@ -58,6 +78,14 @@ public:
// 找图片的最大外轮廓
static std::vector<cv::Point> getLargestContourROI(const cv::Mat &binaryImg, bool &found);
// 抠出掩膜四周的凸起:主体近似矩形,凸起在该边表现为边界值的一次突变(进入)与一次反向突变(离开)
// binMask掩膜非零为前景兼容 0/1 与 0/255protrusions输出的凸起列表
// nJumpThresh边界突变阈值(输入图像素)nMinRun凸起最小长度(输入图像素)nMinDepth凸起最小深度(输入图像素)
// nMaxRun凸起最大长度(输入图像素)<=0 时取 掩膜短边/3
// 返回0 成功、1 掩膜为空、2 无有效前景
static int GetMaskProtrusions(const cv::Mat &binMask, std::vector<Mask_Protrusion_> &protrusions,
int nJumpThresh = 40, int nMinRun = 40, int nMinDepth = 40, int nMaxRun = 0);
static std::string GetRectString(cv::Rect rect);
//创建目录
static int CreateDir(const std::string &dir);

@ -13,7 +13,12 @@
// 检测分析线程类数目
#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
// 相机ID 的相关定义
enum Camera_IDX

@ -49,6 +49,38 @@ enum AT_THRESHOLD_TYPE_
// 全局静态变量, 记录图像灰度值异常累计数量
static int g_nImgBrightnessErrorCount = 0;
// 把手候选bigmask 凸起 + 实测特征(后续增加面积/灰阶等判定时直接用这些实测值)
struct Handle_Candidate_
{
Mask_Protrusion_ protr; // 凸起几何信息roi / 沿边长度 / 深度 / 面积 / 填充率)
int nLen; // 沿边长度(像素)
int nDepth; // 垂直深度(像素)
float fGrayDiff; // 凸起区域与周边背景的平均灰度差(预留灰阶判定)
Handle_Candidate_()
{
nLen = 0;
nDepth = 0;
fGrayDiff = 0.0f;
}
};
// 单个把手区域的判定结果
struct Handle_Region_Result_
{
int nIdx; // 区域序号1开始
cv::Rect boxRegion; // 区域外接矩形(检测图坐标),宽高用于卡控
bool bFound; // 该区域附近是否找到匹配的把手
cv::Rect roiHandle; // 匹配到的把手 roibFound=true 时有效)
Handle_Region_Result_()
{
nIdx = 0;
boxRegion = cv::Rect(0, 0, 0, 0);
bFound = false;
roiHandle = cv::Rect(0, 0, 0, 0);
}
};
class ImgCheckAnalysisy : public ImgCheckBase
{
@ -117,6 +149,13 @@ private:
// 检测
int CheckRun();
int AI_Edge(const cv::Mat &img, cv::RotatedRect &outerRoi, cv::RotatedRect &innerRoi, std::vector<cv::RotatedRect> &tagroiList);
// 用 bigmask 检测把手bigmask 主体近似矩形,把手是四周凸起的矩形块)
int CheckHandleByBigMask(const cv::Mat &img, const cv::Mat &bigmask);
// 在某个把手区域附近找宽高匹配的凸起:返回凸起在 candList 中的下标(-1=没找到)
int MatchHandleRegion(const std::vector<Handle_Candidate_> &candList, const std::vector<bool> &usedList,
const cv::Rect &boxRegion, const Handle_Check_Param &param, cv::Rect &roiHandle);
// 计算凸起区域与周边背景的平均灰度差灰阶判定的预留量测值img 需为单通道)
float CalProtrusionGrayDiff(const cv::Mat &grayImg, const cv::Rect &roi);
// 计算产品尺寸
int CalProductSize();
// 图片预处理
@ -289,6 +328,13 @@ private:
cv::RotatedRect m_outer_rroi;
cv::RotatedRect m_inner_rroi;
std::vector<cv::RotatedRect> m_tag_roiList;
// 把手检测结果bigmask 四周凸起 + 各把手区域判定结果)
std::vector<Handle_Candidate_> m_HandleCandList; // 从 bigmask 四周抠出的凸起候选 + 实测特征
std::vector<Handle_Region_Result_> m_HandleRegionResultList; // 各把手区域最多4个的判定结果
bool m_bHandleDetSucc; // 本次是否完成把手检测
bool m_bHandleFound; // 所有非空把手区域是否都找到了把手
bool m_bHandleExpect; // 是否开启了把手缺失检测(且有非空把手区域)
};
#endif

@ -9,6 +9,7 @@
#include "AI_Edge_Algin.h"
#include "CheckErrorCodeDefine.hpp"
#include <thread>
#define EDGE_GPU 0
@ -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,41 @@ 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;
vecThreads.emplace_back([&]() { re0 = Get_Edge(0, img, pDetConfig, m_pDetConfig->strChannel, Big_roi, Big_outimg, true); });
vecThreads.emplace_back([&]() { re1 = Get_Edge(1, img, pDetConfig, m_pDetConfig->strChannel, Small_roi, Small_outimg, false); });
vecThreads.emplace_back([&]() { re2 = Get_Edge(2, img, pDetConfig, m_pDetConfig->strChannel, Tag_roi, Tag_outimg, false); });
for (auto &t : vecThreads)
{
t.join();
}
if (re0 != 0)
{
m_pdetlog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "AICheck_Edge_Big----error %d ", re);
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)
{
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;
@ -348,7 +357,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,8 +379,8 @@ 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;
@ -413,8 +422,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并返回
cv::resize(mask, outmask, img.size());
// resize out_mask并返回仅大轮廓 mask 需要原图尺寸,后续用于把手检测;其余保持小图)
if (bResizeOut)
{
cv::resize(mask, outmask, img.size());
}
else
{
outmask = mask;
}
// 检查模型输入尺寸
if (sz.width <= 0 || sz.height <= 0)

@ -12,101 +12,6 @@
#include "AI_Factory.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的中心点
@ -332,7 +237,7 @@ int ALLImgCheckAnalysisy::InitAIFactory()
GPU_Config gpu;
gpu.gpu_0 = true;
gpu.gpu_1 = true;
gpu.gpu_1 = false;
AI_Factory->InitALLAIModle(gpu);
return 0;
@ -505,14 +410,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 +450,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)
{

@ -10,6 +10,7 @@
#include "ImgCheckAnalysisy.hpp"
#include "CheckUtil.hpp"
#include "Define.h"
#include <atomic>
CameraCheckAnalysisy::CameraCheckAnalysisy()
{
@ -675,10 +676,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);

@ -692,4 +692,272 @@ cv::Point2f CheckUtil::transformPoint(const cv::Point2f &point, const cv::Mat &t
cv::Mat result_mat = transform_matrix * point_mat;
return cv::Point2f(result_mat.at<double>(0), result_mat.at<double>(1));
}
int CheckUtil::GetMaskProtrusions(const cv::Mat &binMask, std::vector<Mask_Protrusion_> &protrusions,
int nJumpThresh, int nMinRun, int nMinDepth, int nMaxRun)
{
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);
// 1.1 大掩膜先降采样再分析:凸起尺寸远大于降采样误差,可显著降低耗时
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;
}
// 阈值按降采样比例换算到工作图尺度
nJumpThresh = std::max(2, nJumpThresh / nScale);
nMinRun = std::max(3, nMinRun / nScale);
nMinDepth = std::max(2, nMinDepth / nScale);
// 2、只保留最大连通域避免背景杂物参与边界统计
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;
}
}
// 3、裁剪到主体外接矩形缩小后续扫描范围
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 sub = (labels(boxMain) == nMainIdx); // 0/255
const int H = sub.rows;
const int W = sub.cols;
if (nMaxRun <= 0)
{
nMaxRun = std::max(100, std::min(H, W) / 3);
}
// 4、逐边抠凸起
// 主体近似矩形,矩形边上的凸起会让该边的"边界值"先突变到外侧(进入凸起)、再突变回主体(离开凸起)
// 区间内的边界值相对"首尾边界值线性插值"得到的基线持续外偏,即判定为一个凸起。
for (int nSide = 0; nSide < 4; nSide++)
{
const bool bRowScan = (nSide < 2); // 左右边按行扫描,上下边按列扫描
const int nLine = bRowScan ? H : W; // 扫描的行数/列数
const int nDir = (nSide == 0 || nSide == 2) ? 1 : -1; // 凸起内边界值变小(左侧/上侧)取 +1变大取 -1
// 4.1 从每条 行/列 的外侧向内找第一个前景像素,得到该边的边界值 profile
std::vector<int> posList; // 有前景的行号/列号
std::vector<int> valList; // 对应的边界值
posList.reserve(nLine);
valList.reserve(nLine);
for (int i = 0; i < nLine; i++)
{
int nV = -1;
if (nSide == 0) // 左:自左向右
{
const uchar *p = sub.ptr<uchar>(i);
for (int x = 0; x < W; x++)
{
if (p[x])
{
nV = x;
break;
}
}
}
else if (nSide == 1) // 右:自右向左
{
const uchar *p = sub.ptr<uchar>(i);
for (int x = W - 1; x >= 0; x--)
{
if (p[x])
{
nV = x;
break;
}
}
}
else if (nSide == 2) // 上:自上向下
{
for (int y = 0; y < H; y++)
{
if (sub.ptr<uchar>(y)[i])
{
nV = y;
break;
}
}
}
else // 下:自下向上
{
for (int y = H - 1; y >= 0; y--)
{
if (sub.ptr<uchar>(y)[i])
{
nV = y;
break;
}
}
}
if (nV >= 0)
{
posList.push_back(i);
valList.push_back(nV);
}
}
const int n = (int)valList.size();
if (n < nMinRun + 2)
{
continue;
}
// 4.2 配对"进入/离开"突变,扣除该边上的凸起
int nUsedTo = -1; // 已统计过的凸起末端,避免同一个凸起重复输出
for (int s = 0; s + 1 < n; s++)
{
if (s <= nUsedTo)
{
continue;
}
// 进入凸起:边界值朝外侧突变
if ((valList[s + 1] - valList[s]) * nDir >= -nJumpThresh)
{
continue;
}
// 离开凸起:在其后 nMaxRun 范围内找第一个反向突变
int e = -1;
for (int j = s + nMinRun; j + 1 < n && j - s <= nMaxRun; j++)
{
if ((valList[j + 1] - valList[j]) * nDir > nJumpThresh)
{
e = j;
break;
}
}
if (e < 0)
{
continue;
}
// 4.3 区间内的边界值应持续超出"首尾边界值线性插值"得到的基线
const int kk = e - s;
int nDevOk = 0;
float fMaxDev = 0.0f;
int nLow = valList[s + 1];
int nHigh = valList[s + 1];
for (int i = 1; i <= kk; i++)
{
float fBase = valList[s] + (valList[e + 1] - valList[s]) * (float)i / (float)(kk + 1);
float fDev = (fBase - valList[s + i]) * nDir;
fMaxDev = std::max(fMaxDev, fDev);
if (fDev >= nMinDepth)
{
nDevOk++;
}
nLow = std::min(nLow, valList[s + i]);
nHigh = std::max(nHigh, valList[s + i]);
}
if (fMaxDev < nMinDepth)
{
continue;
}
// 凸起内大部分采样点都要明显外偏,避免把主体的斜边/圆角当成凸起
if (nDevOk < (int)(kk * 0.8f))
{
continue;
}
// 4.4 计算凸起外接矩形(先算在 sub 图上的位置)
const int nLen = posList[e] - posList[s + 1] + 1; // 沿该边的长度
cv::Rect roiSub;
if (nSide == 0 || nSide == 2) // 左/上:外侧取小值,主体边缘取大值
{
int nOut = nLow;
int nInner = std::max(valList[s], valList[e + 1]);
if (nSide == 0)
{
roiSub = cv::Rect(nOut, posList[s + 1], nInner - nOut + 1, nLen);
}
else
{
roiSub = cv::Rect(posList[s + 1], nOut, nLen, nInner - nOut + 1);
}
}
else // 右/下:外侧取大值,主体边缘取小值
{
int nOut = nHigh;
int nInner = std::min(valList[s], valList[e + 1]);
if (nSide == 1)
{
roiSub = cv::Rect(nInner, posList[s + 1], nOut - nInner + 1, nLen);
}
else
{
roiSub = cv::Rect(posList[s + 1], nInner, nLen, nOut - nInner + 1);
}
}
roiSub &= cv::Rect(0, 0, W, H);
if (roiSub.width <= 0 || roiSub.height <= 0)
{
continue;
}
Mask_Protrusion_ protr;
protr.nSide = nSide;
// 从工作图尺度还原到输入图尺度
protr.roi = cv::Rect((roiSub.x + boxMain.x) * nScale, (roiSub.y + boxMain.y) * nScale,
roiSub.width * nScale, roiSub.height * nScale);
protr.roi &= cv::Rect(0, 0, binMask.cols, binMask.rows);
if (protr.roi.width <= 0 || protr.roi.height <= 0)
{
continue;
}
protr.fLen = (float)nLen * nScale;
protr.fDepth = fMaxDev * nScale;
int nMaskArea = cv::countNonZero(sub(roiSub));
protr.fArea = (float)nMaskArea * nScale * nScale;
protr.fFillRatio = (float)nMaskArea / (float)(roiSub.width * roiSub.height);
protrusions.push_back(protr);
nUsedTo = e; // 该凸起区间不再重复统计
}
}
return 0;
}

@ -566,9 +566,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
{

@ -12,6 +12,15 @@
#include <omp.h>
#include "AICommonDefine.h"
#include <algorithm>
// 把手判定参数:
// 每个非空的"把手区域"附近都应该有一个把手:凸起宽高与该区域外接矩形宽高的比值落在下面范围内才算匹配
#define HANDLE_SIZE_MIN_RATIO 0.8f
#define HANDLE_SIZE_MAX_RATIO 1.2f
// "附近"的搜索范围:把手区域外接矩形在宽/高方向各外扩该比例
#define HANDLE_NEAR_EXPAND_RATIO 0.5f
// 凸起填充率下限等参数的默认值在 Handle_Check_ParamCheckConfigDefine.h
// 用于排序轮廓的比较函数
static bool compareContourAreas(const vector<Point> &contour1, const vector<Point> &contour2)
{
@ -425,7 +434,7 @@ int ImgCheckAnalysisy::CheckRun()
{
m_pdetlog->bPrintStr = true;
}
m_pdetlog->bPrintStr = true;
// m_pdetlog->bPrintStr = true;
m_pdetlog->AddCheckstr(PrintLevel_0, "1、basic Info", "---------------------------1、basic Info---------------------------------");
m_pdetlog->AddCheckstr(PrintLevel_0, "Version", "%s", GetVersion().c_str());
@ -454,6 +463,22 @@ int ImgCheckAnalysisy::CheckRun()
}
long time_edge_s = CheckUtil::getcurTime();
m_pFuntion = GetChannelFuntion(m_strCurDetChannel);
int nfunction = 0;
if (m_pFuntion != NULL)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Detect function", "%s",
m_pFuntion->GetInfo("").c_str());
}
else
{
nfunction = 1;
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "m_pFuntion is NULL");
m_nErrorCode = 22;
m_nCheckResultErrorCode = m_nErrorCode;
return m_nErrorCode;
}
/*AI 边缘定位(内外边缘)*/
// 模型定位内外边缘
int reedge = AI_Edge(m_CheckResult_shareP->in_shareImage->img, m_outer_rroi, m_inner_rroi, m_tag_roiList);
@ -470,6 +495,8 @@ int ImgCheckAnalysisy::CheckRun()
/*投影对齐模板*/
// Adapt_Config(m_tplOuterRect, m_outer_rroi, m_CheckResult_shareP->in_shareImage->img);
m_outer_roi = m_outer_rroi.boundingRect();
// 旋转矩形 boundingRect 在图像边缘可能越界(如 x=-1裁剪到图像范围内避免 image(roi) 越界崩溃
m_outer_roi &= cv::Rect(0, 0, m_CheckResult_shareP->in_shareImage->img.cols, m_CheckResult_shareP->in_shareImage->img.rows);
m_Crop_Roi_paramImg = m_outer_roi;
m_pImageAllResult->pDetResult->CutRoi = m_outer_roi;
m_pImageAllResult->pDetResult->Param_CropRoi = m_Crop_Roi_paramImg;
@ -501,7 +528,7 @@ int ImgCheckAnalysisy::CheckRun()
/* 检测支架偏移 */
{
Base_Function_Support_Det &supportDet = m_pbaseCheckFunction->supportDet;
Function_Support_Det &supportDet = m_pFuntion->function.f_supportDet;
if (supportDet.bOpen && supportDet.supportRect.size.width > 0 && supportDet.supportRect.size.height > 0)
{
cv::RotatedRect &inner = m_inner_rroi;
@ -580,6 +607,69 @@ int ImgCheckAnalysisy::CheckRun()
}
}
/* 把手缺失检测bigmask 的凸起本身不算 NG只有"参数里的某个把手区域附近找不到把手"才算 NG每个缺失区域报一条 */
{
if (m_bHandleDetSucc && m_bHandleExpect && !m_bHandleFound)
{
for (size_t iRegion = 0; iRegion < m_HandleRegionResultList.size(); iRegion++)
{
Handle_Region_Result_ &regionResult = m_HandleRegionResultList[iRegion];
if (regionResult.bFound)
{
continue;
}
// 上报该把手区域的位置:原图坐标 -> 检测图(detImg)坐标
cv::Rect roi = regionResult.boxRegion;
roi.x -= m_outer_roi.x;
roi.y -= m_outer_roi.y;
roi &= cv::Rect(0, 0, m_pImageAllResult->detImg.cols, m_pImageAllResult->detImg.rows);
if (roi.width <= 0 || roi.height <= 0)
{
continue;
}
QX_ERROR_INFO_ temerror;
temerror.Idx = m_pDetResult->pQx_ErrorList->size();
temerror.roi = roi;
temerror.area = roi.width * roi.height;
temerror.JudgArea = roi.width * m_fImgage_Scale_X * roi.height * m_fImgage_Scale_Y;
temerror.JudgArea_second = temerror.JudgArea;
float w = roi.width;
float h = roi.height;
temerror.flen = (w > h ? w : h) * m_fImgage_Scale_X;
temerror.fbreadth = (w > h ? h : w) * m_fImgage_Scale_Y;
temerror.nconfig_qx_type = CONFIG_QX_NAME_handle_loss;
temerror.qx_name = CONFIG_QX_NAME_Names[CONFIG_QX_NAME_handle_loss];
temerror.result = QX_RESULT_TYPE_NG;
temerror.result_name = QX_RESULT_TYPE_Names[QX_RESULT_TYPE_NG];
// 计算把手区域中心所在的检测区域
cv::Point pCenter;
pCenter.x = roi.x + roi.width * 0.5;
pCenter.y = roi.y + roi.height * 0.5;
int nmaxregionIdx = 0;
for (int iregion = 0; iregion < m_DetRoiList.roiList_Src.size(); iregion++)
{
const std::vector<cv::Point> &polygon = m_DetRoiList.roiList_Src[iregion];
double result = cv::pointPolygonTest(polygon, pCenter, false);
if (result < 0)
{
continue;
}
nmaxregionIdx = iregion;
}
temerror.detRegionidxList.push_back(nmaxregionIdx);
m_pDetResult->pQx_ErrorList->push_back(temerror);
m_pdetlog->AddCheckstr(PrintLevel_0, "把手检测", "%s %d region %d roi [%d %d %d %d]",
temerror.qx_name.c_str(), temerror.Idx, regionResult.nIdx,
roi.x, roi.y, roi.width, roi.height);
}
}
}
/* Tag检测 */
{
for (const auto &tagRoi : m_tag_roiList)
@ -2047,91 +2137,301 @@ int ImgCheckAnalysisy::AI_Edge(const cv::Mat &img, cv::RotatedRect &outerRoi, cv
}
/*使用m_pEdge_Align_Result->bigmask检测把手是否缺失*/
// 截取把手大致区域
RotatedRect tpl_handle_rroi = m_pbaseCheckFunction->supportDet.handleRect;
Rect handle_rect = Rect(tpl_handle_rroi.boundingRect().x, 0, tpl_handle_rroi.boundingRect().width, m_pEdge_Align_Result->bigmask.rows);
Mat handle_roi = m_pEdge_Align_Result->bigmask(handle_rect & Rect(0, 0, m_pEdge_Align_Result->bigmask.cols, m_pEdge_Align_Result->bigmask.rows)).clone();
// 对handle_roi做一下开运算
Mat element = getStructuringElement(MORPH_RECT, Size(tpl_handle_rroi.boundingRect().width / 3, tpl_handle_rroi.boundingRect().height / 3));
morphologyEx(handle_roi, handle_roi, MORPH_OPEN, element);
{
long handle_s = CheckUtil::getcurTime();
CheckHandleByBigMask(img, m_pEdge_Align_Result->bigmask);
long handle_e = CheckUtil::getcurTime();
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "handle detect use time %ld", handle_e - handle_s);
}
return re;
}
// 用 bigmask 检测把手:
// 1、bigmask 主体近似矩形,把手是主体四周凸起的"类矩形块"
// 2、把主体四周的凸起抠出来凸起在该边的边界值上表现为一次突变进入 + 一次反向突变离开)
// 3、参数里每个非空的"把手区域"附近都应该有一个把手:在区域附近找凸起,
// 凸起宽高与区域外接矩形宽高相近HANDLE_SIZE_MIN_RATIO ~ HANDLE_SIZE_MAX_RATIO才算匹配
// 4、凸起本身不算 NG只有"某个把手区域附近找不到匹配的凸起"才算把手缺失NG 由 CheckRun 上报)
int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &img, const cv::Mat &bigmask)
{
m_bHandleDetSucc = false;
m_bHandleFound = false;
m_bHandleExpect = false;
m_HandleCandList.clear();
m_HandleRegionResultList.clear();
if (bigmask.empty())
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "bigmask is empty, skip handle detect");
return 1;
}
// imwrite("handle_roi.png", handle_roi);
// 1、抠出 bigmask 四周凸起的矩形块
std::vector<Mask_Protrusion_> protrusionList;
int re = CheckUtil::GetMaskProtrusions(bigmask, protrusionList);
if (re != 0)
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "GetMaskProtrusions error %d", re);
return re;
}
m_bHandleDetSucc = true;
// 2、把手判定参数 + 把手区域最多4个非空区域表示该处应该有一个把手
Handle_Check_Param handleParam;
std::vector<cv::Rect> regionBoxList;
if (m_pFuntion != NULL)
{
const Function_Support_Det &supportDet = m_pFuntion->function.f_supportDet;
handleParam = supportDet.handleParam;
for (size_t i = 0; i < supportDet.handleBoxes.size() && i < HANDLE_REGION_MAX_NUM; i++)
{
regionBoxList.push_back(supportDet.handleBoxes[i]);
}
}
m_bHandleExpect = (handleParam.bOpen && !regionBoxList.empty());
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "protrusion num %ld handle region num %ld expect %s",
protrusionList.size(), regionBoxList.size(), BOOL_TO_STR(m_bHandleExpect));
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "%s", handleParam.GetInfo("judge").c_str());
// 灰阶判定是预留项:只有开启时才做灰度换算,避免无谓开销
cv::Mat grayImg;
if (handleParam.bJudgeGray && !img.empty())
{
if (img.channels() != 1)
{
cv::cvtColor(img, grayImg, cv::COLOR_BGR2GRAY);
}
else
{
grayImg = img;
}
}
// handle_roi找到最大连通域和tpl_handle_rroi做面积形状比较差异过大判为把手缺失添加到缺陷并NG
// 3、凸起候选实测特征面积/填充率/灰度差,供日志与后续判定用)
for (size_t i = 0; i < protrusionList.size(); i++)
{
Base_Function_Support_Det &handleDet = m_pbaseCheckFunction->supportDet;
if (handleDet.bOpen && tpl_handle_rroi.size.width > 0 && tpl_handle_rroi.size.height > 0 && !handle_roi.empty())
Handle_Candidate_ cand;
cand.protr = protrusionList[i];
// 左/右凸起:沿边为 y 方向,深度为 x 方向;上/下凸起相反
bool bHorz = (cand.protr.nSide == 0 || cand.protr.nSide == 1);
cand.nLen = bHorz ? cand.protr.roi.height : cand.protr.roi.width;
cand.nDepth = bHorz ? cand.protr.roi.width : cand.protr.roi.height;
if (!grayImg.empty())
{
cand.fGrayDiff = CalProtrusionGrayDiff(grayImg, cand.protr.roi);
}
m_HandleCandList.push_back(cand);
// grayDiff 打印 -1 表示本次未开启灰阶判定(未计算)
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle",
"protrusion %ld side %d roi [%d %d %d %d] len %d depth %d area %0.0f fill %0.2f grayDiff %0.1f",
i, cand.protr.nSide, cand.protr.roi.x, cand.protr.roi.y, cand.protr.roi.width, cand.protr.roi.height,
cand.nLen, cand.nDepth, cand.protr.fArea, cand.protr.fFillRatio,
grayImg.empty() ? -1.0f : cand.fGrayDiff);
}
// 4、逐个把手区域在区域附近找宽高匹配的凸起同一个凸起只匹配给一个区域
std::vector<bool> usedList(m_HandleCandList.size(), false);
int nFound = 0;
for (size_t ir = 0; ir < regionBoxList.size(); ir++)
{
Handle_Region_Result_ result;
result.nIdx = (int)ir + 1;
result.boxRegion = regionBoxList[ir];
int nMatchIdx = MatchHandleRegion(m_HandleCandList, usedList, result.boxRegion, handleParam, result.roiHandle);
result.bFound = (nMatchIdx >= 0);
if (result.bFound)
{
std::vector<std::vector<cv::Point>> handle_contours;
cv::findContours(handle_roi, handle_contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
usedList[nMatchIdx] = true;
nFound++;
}
m_HandleRegionResultList.push_back(result);
bool bHandleLoss = false;
if (handle_contours.empty())
std::string strResult = "handle missing !!";
if (result.bFound)
{
strResult = "handle roi [" + std::to_string(result.roiHandle.x) + " " + std::to_string(result.roiHandle.y) + " " +
std::to_string(result.roiHandle.width) + " " + std::to_string(result.roiHandle.height) + "]";
}
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "handle region %d box [%d %d %d %d] -> %s",
result.nIdx, result.boxRegion.x, result.boxRegion.y, result.boxRegion.width, result.boxRegion.height,
strResult.c_str());
}
m_bHandleFound = (!regionBoxList.empty() && nFound == (int)regionBoxList.size());
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, "Handle", "handle region num %ld found num %d -> %s",
regionBoxList.size(), nFound, m_bHandleFound ? "handle ok" : "handle missing !!");
// 5、存过程图凸起红框、把手区域蓝框、匹配上的把手绿框仅调试时
if (DetImgInfo_shareP->bsaveProcessImg)
{
cv::Mat show;
if (bigmask.channels() == 1)
{
cv::cvtColor(bigmask, show, cv::COLOR_GRAY2BGR);
}
else
{
show = bigmask.clone();
}
for (size_t i = 0; i < m_HandleCandList.size(); i++)
{
cv::rectangle(show, m_HandleCandList[i].protr.roi, cv::Scalar(0, 0, 255), 8);
}
for (size_t i = 0; i < m_HandleRegionResultList.size(); i++)
{
cv::rectangle(show, m_HandleRegionResultList[i].boxRegion, cv::Scalar(255, 0, 0), 8);
if (m_HandleRegionResultList[i].bFound)
{
bHandleLoss = true; // 没有连通域,把手完全缺失
cv::rectangle(show, m_HandleRegionResultList[i].roiHandle, cv::Scalar(0, 255, 0), 12);
}
else
{
// 取最大连通域
std::sort(handle_contours.begin(), handle_contours.end(), compareContourAreas);
cv::RotatedRect cur_handle_rrect = cv::minAreaRect(handle_contours[0]);
double curArea = cv::contourArea(handle_contours[0]);
// 面积比较
double tplArea = tpl_handle_rroi.size.width * tpl_handle_rroi.size.height;
double areaRatio = (tplArea > 0.0) ? (curArea / tplArea) : 0.0;
// 形状比较(长宽比)
float tplW = std::max(tpl_handle_rroi.size.width, tpl_handle_rroi.size.height);
float tplH = std::min(tpl_handle_rroi.size.width, tpl_handle_rroi.size.height);
float curW = std::max(cur_handle_rrect.size.width, cur_handle_rrect.size.height);
float curH = std::min(cur_handle_rrect.size.width, cur_handle_rrect.size.height);
float tplRatio = (tplH > 0.0f) ? (tplW / tplH) : 0.0f;
float curRatio = (curH > 0.0f) ? (curW / curH) : 0.0f;
float ratioDiff = (tplRatio > 0.0f) ? fabs(curRatio - tplRatio) / tplRatio : 0.0f;
// 面积差异过大 或 形状差异过大 判为把手缺失
if (areaRatio < 0.6f || ratioDiff > 0.6f)
{
bHandleLoss = true;
}
}
cv::Mat showSmall;
cv::resize(show, showSmall, cv::Size(show.cols / 4, show.rows / 4));
std::vector<int> paramJpg = {cv::IMWRITE_JPEG_QUALITY, 90};
cv::imwrite(DetImgInfo_shareP->strChannel + "_handle_det.jpg", showSmall, paramJpg);
}
m_pdetlog->AddCheckstr(PrintLevel_0, "把手检测", "areaRatio %f ratioDiff %f", areaRatio, ratioDiff);
}
return 0;
}
if (bHandleLoss)
{
QX_ERROR_INFO_ temerror;
temerror.Idx = m_pDetResult->pQx_ErrorList->size();
// 在某个把手区域附近找宽高匹配的凸起
// 匹配条件1、凸起中心落在把手区域附近区域宽/高各外扩 HANDLE_NEAR_EXPAND_RATIO
// 2、凸起宽高与该区域外接矩形宽高的比值都在 [HANDLE_SIZE_MIN_RATIO, HANDLE_SIZE_MAX_RATIO](允许横竖互换)
// 3、填充率、预留的面积/灰阶等开关条件都满足
// 返回凸起在 candList 中的下标,-1 表示没找到
int ImgCheckAnalysisy::MatchHandleRegion(const std::vector<Handle_Candidate_> &candList, const std::vector<bool> &usedList,
const cv::Rect &boxRegion, const Handle_Check_Param &param, cv::Rect &roiHandle)
{
if (boxRegion.width <= 0 || boxRegion.height <= 0)
{
return -1;
}
// 把手模板 roi 在原图坐标系,转换为检测图(detImg)坐标系
cv::Rect roi = tpl_handle_rroi.boundingRect();
roi.x -= outerRoi.boundingRect().x;
roi.y -= outerRoi.boundingRect().y;
roi &= cv::Rect(0, 0, outerRoi.boundingRect().width, outerRoi.boundingRect().height);
temerror.roi = roi;
temerror.area = roi.width * roi.height;
temerror.JudgArea = roi.width * m_fImgage_Scale_X * roi.height * m_fImgage_Scale_Y;
temerror.JudgArea_second = temerror.JudgArea;
float w = tpl_handle_rroi.size.width;
float h = tpl_handle_rroi.size.height;
temerror.flen = (w > h ? w : h) * m_fImgage_Scale_X;
temerror.fbreadth = (w > h ? h : w) * m_fImgage_Scale_Y;
temerror.nconfig_qx_type = CONFIG_QX_NAME_support_loss;
temerror.qx_name = CONFIG_QX_NAME_Names[temerror.nconfig_qx_type];
temerror.result = QX_RESULT_TYPE_NG;
temerror.result_name = QX_RESULT_TYPE_Names[QX_RESULT_TYPE_NG];
temerror.detRegionidxList.push_back(0);
// 搜索范围:把手区域外扩(宽/高各外扩区域尺寸的一半),容忍产品摆放偏差
int nx = std::max(10, (int)(boxRegion.width * HANDLE_NEAR_EXPAND_RATIO));
int ny = std::max(10, (int)(boxRegion.height * HANDLE_NEAR_EXPAND_RATIO));
cv::Rect searchBox(boxRegion.x - nx, boxRegion.y - ny, boxRegion.width + 2 * nx, boxRegion.height + 2 * ny);
cv::Point pRegionCenter(boxRegion.x + boxRegion.width / 2, boxRegion.y + boxRegion.height / 2);
m_pDetResult->pQx_ErrorList->push_back(temerror);
int nBestIdx = -1;
double fBestDis = 1e18;
int nNearIdx = -1; // 区域内离区域中心最近的凸起(仅用于日志排查)
double fNearDis = 1e18;
for (size_t i = 0; i < candList.size(); i++)
{
if (i < usedList.size() && usedList[i])
{
continue;
}
const Handle_Candidate_ &cand = candList[i];
const cv::Rect &roi = cand.protr.roi;
if (roi.width <= 0 || roi.height <= 0)
{
continue;
}
m_pdetlog->AddCheckstr(PrintLevel_0, "把手检测", "handle loss NG roi [%d %d %d %d]", roi.x, roi.y, roi.width, roi.height);
}
// 位置:凸起中心要落在把手区域附近
cv::Point pCenter(roi.x + roi.width / 2, roi.y + roi.height / 2);
if (!searchBox.contains(pCenter))
{
continue;
}
double fDis = CheckUtil::calDis(pCenter, pRegionCenter);
if (fDis < fNearDis)
{
fNearDis = fDis;
nNearIdx = (int)i;
}
// 类似矩形块:凸起区域内掩膜填充率要高
if (param.bJudgeFill && cand.protr.fFillRatio < param.fFillMin)
{
continue;
}
// 预留:凸起面积
if (param.bJudgeArea && param.fAreaMax > param.fAreaMin &&
(cand.protr.fArea < param.fAreaMin || cand.protr.fArea > param.fAreaMax))
{
continue;
}
// 预留:凸起与背景的灰度差
if (param.bJudgeGray && param.fGrayMax > param.fGrayMin &&
(cand.fGrayDiff < param.fGrayMin || cand.fGrayDiff > param.fGrayMax))
{
continue;
}
// 宽高:与把手区域外接矩形的宽高相近(允许横竖互换)
float fw = (float)roi.width;
float fh = (float)roi.height;
bool bSizeOK = (fw >= boxRegion.width * HANDLE_SIZE_MIN_RATIO && fw <= boxRegion.width * HANDLE_SIZE_MAX_RATIO &&
fh >= boxRegion.height * HANDLE_SIZE_MIN_RATIO && fh <= boxRegion.height * HANDLE_SIZE_MAX_RATIO);
if (!bSizeOK)
{
bSizeOK = (fw >= boxRegion.height * HANDLE_SIZE_MIN_RATIO && fw <= boxRegion.height * HANDLE_SIZE_MAX_RATIO &&
fh >= boxRegion.width * HANDLE_SIZE_MIN_RATIO && fh <= boxRegion.width * HANDLE_SIZE_MAX_RATIO);
}
if (!bSizeOK)
{
continue;
}
if (fDis < fBestDis)
{
fBestDis = fDis;
nBestIdx = (int)i;
}
}
return re;
if (nBestIdx < 0 && nNearIdx >= 0)
{
// 没匹配上时打印区域内最近的凸起,便于排查阈值
const cv::Rect &roi = candList[nNearIdx].protr.roi;
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle",
"region box [%d %d %d %d] no match, nearest protrusion roi [%d %d %d %d] w/h ratio %0.2f %0.2f",
boxRegion.x, boxRegion.y, boxRegion.width, boxRegion.height,
roi.x, roi.y, roi.width, roi.height,
(float)roi.width / (float)boxRegion.width, (float)roi.height / (float)boxRegion.height);
}
if (nBestIdx >= 0)
{
roiHandle = candList[nBestIdx].protr.roi;
}
return nBestIdx;
}
// 计算凸起区域与周边背景的平均灰度差灰阶判定的预留量测值grayImg 需为单通道)
float ImgCheckAnalysisy::CalProtrusionGrayDiff(const cv::Mat &grayImg, const cv::Rect &roi)
{
if (grayImg.empty() || grayImg.channels() != 1 || roi.width <= 0 || roi.height <= 0)
{
return 0.0f;
}
// 凸起区域通常整个都在掩膜内,向外扩一圈才能取到背景
int nExpand = std::max(20, std::min(roi.width, roi.height) / 2);
cv::Rect roiBg(roi.x - nExpand, roi.y - nExpand, roi.width + 2 * nExpand, roi.height + 2 * nExpand);
roiBg &= cv::Rect(0, 0, grayImg.cols, grayImg.rows);
if (roiBg.width <= 0 || roiBg.height <= 0)
{
return 0.0f;
}
cv::Mat grayRoi = grayImg(roiBg);
cv::Mat maskIn = cv::Mat::zeros(roiBg.size(), CV_8UC1);
cv::Rect inter = roi & roiBg;
cv::rectangle(maskIn, inter - roiBg.tl(), cv::Scalar(255), cv::FILLED);
cv::Mat maskOut;
cv::bitwise_not(maskIn, maskOut);
if (cv::countNonZero(maskIn) <= 0 || cv::countNonZero(maskOut) <= 0)
{
return 0.0f;
}
double fMeanIn = cv::mean(grayRoi, maskIn)[0];
double fMeanOut = cv::mean(grayRoi, maskOut)[0];
double fDiff = fMeanIn - fMeanOut;
return (float)(fDiff >= 0 ? fDiff : -fDiff);
}
int ImgCheckAnalysisy::CalProductSize()

@ -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)

@ -54,6 +54,7 @@ enum CONFIG_QX_NAME_
CONFIG_QX_NAME_cell_tag, // 分类 标签
CONFIG_QX_NAME_support_offset, // 支架偏移
CONFIG_QX_NAME_support_loss, // 支架缺失
CONFIG_QX_NAME_handle_loss, // 把手缺失
CONFIG_QX_NAME_count,
};
// 缺陷项对应在参数中的名称
@ -74,6 +75,7 @@ static std::vector<std::string> CONFIG_QX_NAME_Names =
"tag",
"support_offset",
"support_loss",
"handle_loss",
};
// 分析类型
@ -932,6 +934,118 @@ struct Function_Image_Align
return str123;
}
};
// 把手区域最大数量参数里最多可画4个把手区域
#define HANDLE_REGION_MAX_NUM 4
// 把手缺失判定参数:
// 判定标准:参数里非空的"把手区域"附近必须存在一个凸起,且凸起宽高与区域宽高相近
// (比值范围见 ImgCheckAnalysisy 里的 HANDLE_SIZE_MIN_RATIO ~ HANDLE_SIZE_MAX_RATIO
// 面积/灰阶等参数保留开关,后续需要时再启用
struct Handle_Check_Param
{
bool bOpen; // 是否检测把手缺失false 时不做把手判定,也不报 NG
// 默认关闭,由 "Support_QX_Detect" 检测项开启时置为 true可用 handle_disabled 单独关闭)
bool bJudgeFill; // 是否用"类矩形块"填充率判定
float fFillMin; // 填充率下限
bool bJudgeArea; // 预留:凸起面积判定
float fAreaMin;
float fAreaMax;
bool bJudgeGray; // 预留:凸起与背景灰度差判定
float fGrayMin;
float fGrayMax;
Handle_Check_Param()
{
Init();
}
void Init()
{
bOpen = false;
bJudgeFill = true;
fFillMin = 0.75f;
bJudgeArea = false;
fAreaMin = 0;
fAreaMax = 0;
bJudgeGray = false;
fGrayMin = 0;
fGrayMax = 0;
}
void copy(Handle_Check_Param tem)
{
// 全为基本类型,直接赋值(避免后续加字段时漏拷)
*this = tem;
}
std::string GetInfo(std::string str)
{
char buffer[256];
sprintf(buffer, "%s>>bOpen %d fill[%d %0.2f] area[%d %0.0f %0.0f] gray[%d %0.0f %0.0f]\n",
str.c_str(), bOpen, bJudgeFill, fFillMin,
bJudgeArea, fAreaMin, fAreaMax, bJudgeGray, fGrayMin, fGrayMax);
std::string str123 = buffer;
return str123;
}
};
// 支架检测
struct Function_Support_Det
{
bool bOpen; // 是否开启
std::vector<cv::Point> supportRegion;
cv::RotatedRect supportRect;
float x_offset;
float y_offset;
float r_offset;
// 把手区域(最多 HANDLE_REGION_MAX_NUM 个,只保存非空的;区域宽高用于把手卡控)
std::vector<std::vector<cv::Point>> handleRegions;
std::vector<cv::Rect> handleBoxes;
Handle_Check_Param handleParam; // 把手缺失判定参数
Function_Support_Det ()
{
Init();
}
void Init()
{
bOpen = false;
supportRegion.clear();
supportRect = cv::RotatedRect();
x_offset = 0;
y_offset = 0;
r_offset = 0;
handleRegions.clear();
handleBoxes.clear();
handleParam.Init();
}
void copy(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->handleRegions.assign(tem.handleRegions.begin(), tem.handleRegions.end());
this->handleBoxes.assign(tem.handleBoxes.begin(), tem.handleBoxes.end());
this->handleParam.copy(tem.handleParam);
}
void print(std::string str)
{
printf("%s>>bOpen %d x_offset %f y_offset %f r_offset %f handleRegion num %ld\n", str.c_str(),
bOpen, x_offset, y_offset, r_offset, handleRegions.size());
printf("%s", handleParam.GetInfo("handleParam").c_str());
}
std::string GetInfo(std::string str)
{
char buffer[256];
sprintf(buffer, "%s>>bOpen %d x_offset %f y_offset %f r_offset %f handleRegion num %ld\n", str.c_str(),
bOpen, x_offset, y_offset, r_offset, handleRegions.size());
std::string str123 = buffer;
str123 += handleParam.GetInfo("handleParam");
return str123;
}
};
// 检测功能
struct CheckFunction
@ -942,6 +1056,7 @@ struct CheckFunction
Function_ShieldRegion f_ShieldRegion;
Function_EdgeROI f_EdgeROI;
Function_Image_Align f_Image_Align; // 图片特征对齐
Function_Support_Det f_supportDet;
CheckFunction()
{
Init();
@ -954,6 +1069,7 @@ struct CheckFunction
f_ShieldRegion.Init();
f_EdgeROI.Init();
f_Image_Align.Init();
f_supportDet.Init();
}
void copy(CheckFunction tem)
{
@ -964,6 +1080,7 @@ struct CheckFunction
this->f_ShieldRegion.copy(tem.f_ShieldRegion);
this->f_EdgeROI.copy(tem.f_EdgeROI);
this->f_Image_Align.copy(tem.f_Image_Align);
this->f_supportDet.copy(tem.f_supportDet);
}
void print(std::string str)
{
@ -974,6 +1091,7 @@ struct CheckFunction
f_ShieldRegion.print("ShieldRegion");
f_EdgeROI.print("EdgeROI");
f_Image_Align.print("Image_Align");
f_supportDet.print("supportDet");
}
std::string GetInfo(std::string str)
{
@ -985,9 +1103,11 @@ struct CheckFunction
str123 += f_ShieldRegion.GetInfo("ShieldRegion");
str123 += f_EdgeROI.GetInfo("EdgeROI");
str123 += f_Image_Align.GetInfo("Image_Align");
str123 += f_supportDet.GetInfo("supportDet");
return str123;
}
};
// 单通道检测功能
struct ChannelCheckFunction
{
@ -1302,60 +1422,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 +1429,6 @@ struct BaseCheckFunction
Base_Function_Edge_Det edgeDet;
Base_Function_SaveImg saveImg;
Base_Function_BigNG bigNG;
Base_Function_Support_Det supportDet;
BaseCheckFunction()
{
@ -1375,7 +1440,6 @@ struct BaseCheckFunction
edgeDet.Init();
saveImg.Init();
bigNG.Init();
supportDet.Init();
}
void copy(BaseCheckFunction tem)
{
@ -1383,7 +1447,6 @@ struct BaseCheckFunction
this->edgeDet.copy(tem.edgeDet);
this->saveImg.copy(tem.saveImg);
this->bigNG.copy(tem.bigNG);
this->supportDet.copy(tem.supportDet);
}
void print(std::string str)
{
@ -1392,7 +1455,6 @@ struct BaseCheckFunction
edgeDet.print("edgeDet");
saveImg.print("saveImg");
bigNG.print("bigNG");
supportDet.print("supportDet");
}
std::string GetInfo(std::string str)
{
@ -1401,7 +1463,6 @@ struct BaseCheckFunction
str123 += edgeDet.GetInfo("edgeDet");
str123 += saveImg.GetInfo("saveImg");
str123 += bigNG.GetInfo("bigNG");
str123 += supportDet.GetInfo("supportDet");
// str123 += "\n";
return str123;
}

@ -20,6 +20,8 @@ ConfigManager::~ConfigManager()
{
}
std::vector<ReadFlawCode> m_FlawCodeList;
std::vector<std::string> QX_Result_Names =
{
"OK",
@ -169,7 +171,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;

@ -410,7 +410,140 @@ int ChannelFuntonConfigJson::GetFunction(Json::Value value, CheckFunction &funct
{
std::string strCode = value[i]["itemCode"].asString();
// std::cout << strCode << std::endl;
// std::cout << strCode << std::endl;
// 支架偏移缺失检测
if ("Support_QX_Detect" == strCode)
{
auto value_f = value[i];
// std::cout << value_f << std::endl;
function.f_supportDet.bOpen = value_f["isOpen"].asBool();
if (function.f_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++)
{
cv::Point p;
p.x = value_region[idx][0].asInt();
p.y = value_region[idx][1].asInt();
function.f_supportDet.supportRegion.emplace_back(p);
}
if (function.f_supportDet.supportRegion.size() > 0)
{
function.f_supportDet.supportRect = minAreaRect(function.f_supportDet.supportRegion);
}
}
}
if (value_f["form"]["support_param"]["x_offset"])
{
function.f_supportDet.x_offset = value_f["form"]["support_param"]["x_offset"].asFloat();
}
if (value_f["form"]["support_param"]["y_offset"])
{
function.f_supportDet.y_offset = value_f["form"]["support_param"]["y_offset"].asFloat();
}
if (value_f["form"]["support_param"]["r_offset"])
{
function.f_supportDet.r_offset = value_f["form"]["support_param"]["r_offset"].asFloat();
}
// 把手参数
{
auto handle_p = value_f["form"]["handle_param"];
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())
{
break;
}
// 该把手区域被禁用时跳过
if (handle_p[strKey + "_disabled"].isBool() && handle_p[strKey + "_disabled"].asBool())
{
continue;
}
auto value_region = handle_p[strKey];
if (!value_region.isArray() || value_region.size() < 3)
{
continue; // 空区域:表示该处不需要检测把手
}
std::vector<cv::Point> region;
for (int ip = 0; ip < value_region.size(); ip++)
{
if (!value_region[ip].isArray() || value_region[ip].size() < 2)
{
continue;
}
cv::Point p;
p.x = value_region[ip][0].asInt();
p.y = value_region[ip][1].asInt();
region.emplace_back(p);
}
if (region.size() < 3)
{
continue;
}
function.f_supportDet.handleRegions.push_back(region);
function.f_supportDet.handleBoxes.push_back(cv::boundingRect(region));
}
}
// 把手缺失判定参数
{
auto handle_p = value_f["form"]["handle_param"];
Handle_Check_Param &handleParam = function.f_supportDet.handleParam;
// "Support_QX_Detect" 检测项开启时,默认做把手缺失检测
handleParam.bOpen = true;
if (handle_p["handle_disabled"].isBool())
{
handleParam.bOpen = !handle_p["handle_disabled"].asBool();
}
if (handle_p["handle_fill_disabled"].isBool())
{
handleParam.bJudgeFill = !handle_p["handle_fill_disabled"].asBool();
}
if (handle_p["handle_fill_min"].isNumeric())
{
handleParam.fFillMin = handle_p["handle_fill_min"].asFloat();
}
if (handle_p["handle_area_disabled"].isBool())
{
handleParam.bJudgeArea = !handle_p["handle_area_disabled"].asBool();
}
if (handle_p["handle_area_min"].isNumeric())
{
handleParam.fAreaMin = handle_p["handle_area_min"].asFloat();
}
if (handle_p["handle_area_max"].isNumeric())
{
handleParam.fAreaMax = handle_p["handle_area_max"].asFloat();
}
if (handle_p["handle_gray_disabled"].isBool())
{
handleParam.bJudgeGray = !handle_p["handle_gray_disabled"].asBool();
}
if (handle_p["handle_gray_min"].isNumeric())
{
handleParam.fGrayMin = handle_p["handle_gray_min"].asFloat();
}
if (handle_p["handle_gray_max"].isNumeric())
{
handleParam.fGrayMax = handle_p["handle_gray_max"].asFloat();
}
}
}
else
{
function.f_supportDet.Init();
}
}
// 大缺陷检测
if ("BigQX_Detect" == strCode)
{
@ -849,71 +982,6 @@ int BaseFuntonConfigJson::GetFunction(Json::Value value)
_config.markLine.Init();
}
}
if ("Support_QX_Detect" == 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++)
{
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"])
{
_config.supportDet.y_offset = value_f["form"]["support_param"]["y_offset"].asFloat();
}
if (value_f["form"]["support_param"]["r_offset"])
{
_config.supportDet.r_offset = value_f["form"]["support_param"]["r_offset"].asFloat();
}
// 把手参数
{
auto value_region = value_f["form"]["handle_param"]["handle_region"];
if (value_region.isArray())
{
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);
}
if (_config.supportDet.handleRegion.size() > 0)
{
_config.supportDet.handleRect = minAreaRect(_config.supportDet.handleRegion);
}
}
}
}
else
{
_config.supportDet.Init();
}
}
if ("Det_Image_Save" == strCode)
{
auto value_f = value;

@ -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();
}

@ -371,28 +371,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;
}
tem->strPath = pimage->strPath;
InsertReadImgInfo(tem);
AllImgNum++;
}
InsertReadImgInfo(tem);
AllImgNum++;
// break;
}
// AllImgNum = 1;
{
@ -1024,12 +1016,16 @@ 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;
if (m_pALLImgCheckAnalysisy)
// 一张图上下拆分成两个产品_0 上半、_1 下半),给两个产品分别发送结束标志
for (int pi = 0; pi < 2; pi++)
{
m_pALLImgCheckAnalysisy->SetDataRun_SharePtr(tem);
std::shared_ptr<shareImage> tem = std::make_shared<shareImage>();
tem->strImgProductID = pDetImageInfo->strProductID + "_" + std::to_string(pi);
tem->Status = -1;
if (m_pALLImgCheckAnalysisy)
{
m_pALLImgCheckAnalysisy->SetDataRun_SharePtr(tem);
}
}
return 0;
}
@ -1100,7 +1096,8 @@ int deal::SendImgToCheck(std::shared_ptr<JC_IMAGE_INFO_> pDetImageInfo, IN_IMG_S
*sub = *tem;
sub->img = splitList[i];
sub->strImgProductID = tem->strImgProductID + "_" + std::to_string(i);
sub->Status = IN_IMG_Status_OneImg;
// 同一产品(上半/下半)可能包含多个通道的图,子图作为中间图,待所有通道送完后再统一发送结束标志
sub->Status = IN_IMG_Status_Other;
if (m_pALLImgCheckAnalysisy)
{
re = m_pALLImgCheckAnalysisy->SetDataRun_SharePtr(sub);
@ -1203,7 +1200,12 @@ int deal::DetImg()
{
m_nReadThread_type = READ_THREAD_TYPE_READIMG;
StartThread(THREAD_RUN_Only_ReadImg);
preCheck();
// -8 循环执行:像 -f 批量一样,线程只启动一次,内部重复跑同一套图
do
{
preCheck();
usleep(10 * 1000);
} while (runConfig.bLoop);
return 0;
}
@ -1495,6 +1497,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;
}
@ -1633,6 +1637,11 @@ int deal::LoadProductID(std::string strImgPath)
}
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)

@ -499,6 +499,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());

@ -145,12 +145,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