|
|
|
|
@ -20,7 +20,7 @@
|
|
|
|
|
|
|
|
|
|
// AI缺陷分类模型开关:当前暂无分类模型(Class.engine),分类流程代码先预留;
|
|
|
|
|
// 待分类模型部署后,将 AI_CLASS_MODEL_ENABLE 置 1,即可恢复 AI_QX_Class_Thread 中的AI分类流程
|
|
|
|
|
#define AI_CLASS_MODEL_ENABLE 0
|
|
|
|
|
#define AI_CLASS_MODEL_ENABLE 1
|
|
|
|
|
|
|
|
|
|
// 把手提取/判定参数(下面宏是“未配置 JSON 时”的默认值;现场可在参数 JSON 的 Handle_Detect.form.handle_param
|
|
|
|
|
// 下按注释标注的键名配置同名参数覆盖它们,按产品/现场情况调整,无需改代码重编译):
|
|
|
|
|
@ -107,25 +107,13 @@ static std::string ClsTrimStr(const std::string &str)
|
|
|
|
|
return str.substr(begin, end - begin + 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 组装程序内默认缺陷名称列表(AI_CLass_QX_NAME_Names)
|
|
|
|
|
static std::vector<std::string> BuildDefaultAIQXNames()
|
|
|
|
|
{
|
|
|
|
|
std::vector<std::string> vecNames;
|
|
|
|
|
for (int i = 0; i < AI_CLass_QX_NAME_count; i++)
|
|
|
|
|
{
|
|
|
|
|
vecNames.push_back(AI_CLass_QX_NAME_Names[i]);
|
|
|
|
|
}
|
|
|
|
|
return vecNames;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 分类映射配置:缺陷名称列表(索引=行序) + 分类label->缺陷名称 映射
|
|
|
|
|
// 分类映射配置:分类label -> 缺陷名称
|
|
|
|
|
struct ClsLabelConfig
|
|
|
|
|
{
|
|
|
|
|
std::vector<std::string> vecNames; // 缺陷名称列表
|
|
|
|
|
std::map<int, std::string> mapLabelName; // 分类 label -> 缺陷名称
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 读取分类映射配置(每行:label索引:缺陷名称;冒号后名称按行序构成缺陷名称列表;# 后内容忽略)
|
|
|
|
|
// 读取分类映射配置(每行:label索引:缺陷名称;# 后内容忽略)
|
|
|
|
|
static ClsLabelConfig ReadClsLabelConfig(const std::string &strFilePath)
|
|
|
|
|
{
|
|
|
|
|
ClsLabelConfig config;
|
|
|
|
|
@ -156,13 +144,12 @@ static ClsLabelConfig ReadClsLabelConfig(const std::string &strFilePath)
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
// label索引:缺陷名称(冒号后名称按行序构成缺陷名称列表)
|
|
|
|
|
// label索引:缺陷名称
|
|
|
|
|
std::string strKey = ClsTrimStr(strLine.substr(0, nPos));
|
|
|
|
|
std::string strValue = ClsTrimStr(strLine.substr(nPos + 1));
|
|
|
|
|
if (!strKey.empty() && !strValue.empty())
|
|
|
|
|
{
|
|
|
|
|
config.mapLabelName[atoi(strKey.c_str())] = strValue;
|
|
|
|
|
config.vecNames.push_back(strValue); // 行序即缺陷类型索引
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return config;
|
|
|
|
|
@ -175,27 +162,16 @@ static const ClsLabelConfig &GetClsLabelConfig()
|
|
|
|
|
return config;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取缺陷名称列表:优先取配置文件的名称列表,读不到用程序内默认列表
|
|
|
|
|
static const std::vector<std::string> &GetAIQXNameList()
|
|
|
|
|
{
|
|
|
|
|
static const std::vector<std::string> vecDefaultNames = BuildDefaultAIQXNames();
|
|
|
|
|
const ClsLabelConfig &config = GetClsLabelConfig();
|
|
|
|
|
if (!config.vecNames.empty())
|
|
|
|
|
{
|
|
|
|
|
return config.vecNames;
|
|
|
|
|
}
|
|
|
|
|
return vecDefaultNames;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 缺陷名称 -> AI_CLass 枚举(找不到返回 count 表示无效)
|
|
|
|
|
// 注意:按程序内 AI_CLass_QX_NAME_Names 的枚举顺序匹配,而不是映射文件的行序,
|
|
|
|
|
// 避免 BM_cls.txt 行数/顺序与枚举不一致时类别错位
|
|
|
|
|
static int ClsNameToAItype(const std::string &strName)
|
|
|
|
|
{
|
|
|
|
|
const std::vector<std::string> &vecNames = GetAIQXNameList();
|
|
|
|
|
for (size_t i = 0; i < vecNames.size(); i++)
|
|
|
|
|
for (int i = 0; i < AI_CLass_QX_NAME_count; i++)
|
|
|
|
|
{
|
|
|
|
|
if (strName == vecNames[i])
|
|
|
|
|
if (strName == AI_CLass_QX_NAME_Names[i])
|
|
|
|
|
{
|
|
|
|
|
return (int)i;
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return AI_CLass_QX_NAME_count;
|
|
|
|
|
@ -221,32 +197,31 @@ static std::map<int, int> BuildLabelTypeMap()
|
|
|
|
|
return mapLabelType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取分类 label 对应的 AI 缺陷类型:优先配置文件(只读一次),读不到用默认顺序
|
|
|
|
|
static int GetClsTypeByLabel(int nLabel)
|
|
|
|
|
// 获取分类 label 对应的 AI 缺陷类型:优先配置文件(只读一次),读不到按 label 顺序兜底
|
|
|
|
|
// 规则:
|
|
|
|
|
// ① label 超出 [0, AI_CLass_QX_NAME_count)(含推理失败返回的负值)→ 视为 TBMark;
|
|
|
|
|
// ② DarkAoHen(暗场凹痕)只有暗场相机存在:明场相机分到该类时按 QiTa(其他)处理。
|
|
|
|
|
static int GetClsTypeByLabel(int nLabel, bool bIsDark)
|
|
|
|
|
{
|
|
|
|
|
// ① 超出程序的类别表(模型类别数与程序不一致)→ 统一按铜箔标记处理
|
|
|
|
|
if (nLabel < 0 || nLabel >= AI_CLass_QX_NAME_count)
|
|
|
|
|
{
|
|
|
|
|
return AI_CLass_QX_NAME_TBMark;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 未被映射文件覆盖时,按程序内 AI_CLass_QX_NAME_Names 的顺序与 label 对应
|
|
|
|
|
int cls_num = nLabel;
|
|
|
|
|
static const std::map<int, int> mapLabelType = BuildLabelTypeMap();
|
|
|
|
|
std::map<int, int>::const_iterator it = mapLabelType.find(nLabel);
|
|
|
|
|
if (it != mapLabelType.end())
|
|
|
|
|
{
|
|
|
|
|
return it->second;
|
|
|
|
|
cls_num = it->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 映射文件未读到、或该 label 未在文件中配置时,按程序内默认顺序兜底(避免文件缺失/不全导致异常)
|
|
|
|
|
int cls_num = AI_CLass_QX_NAME_count;
|
|
|
|
|
switch (nLabel)
|
|
|
|
|
// ② 暗场凹痕只有暗场检出:明场分到该类时按"其他"处理
|
|
|
|
|
if (cls_num == AI_CLass_QX_NAME_DarkAoHen && !bIsDark)
|
|
|
|
|
{
|
|
|
|
|
case 0:
|
|
|
|
|
cls_num = AI_CLass_QX_NAME_CGMark;
|
|
|
|
|
break;
|
|
|
|
|
case 1:
|
|
|
|
|
cls_num = AI_CLass_QX_NAME_BQBMark;
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
cls_num = AI_CLass_QX_NAME_TBMark;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
cls_num = AI_CLass_QX_NAME_CGMark;
|
|
|
|
|
break;
|
|
|
|
|
cls_num = AI_CLass_QX_NAME_QiTa;
|
|
|
|
|
}
|
|
|
|
|
return cls_num;
|
|
|
|
|
}
|
|
|
|
|
@ -3520,8 +3495,34 @@ int ImgCheckAnalysisy::AI_QX_Class_Thread()
|
|
|
|
|
pAIDet = AI_Factory->Class;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int deal_image_width = pAIDet->input_0.width;
|
|
|
|
|
int deal_image_height = pAIDet->input_0.height;
|
|
|
|
|
// 明/暗场判定:相机名含 "dark" 视为暗场(与 ALIGN_REUSE_*、暗场检测模型判定一致)
|
|
|
|
|
std::string strProductName, strCameraName;
|
|
|
|
|
GetProductCameraStr(strProductName, strCameraName);
|
|
|
|
|
const bool bIsDark = !ReplaceDarkWithBright(strCameraName).empty();
|
|
|
|
|
|
|
|
|
|
int deal_image_width = pAIDet ? pAIDet->input_0.width : 0;
|
|
|
|
|
int deal_image_height = pAIDet ? pAIDet->input_0.height : 0;
|
|
|
|
|
|
|
|
|
|
// 分类模型不可用(未加载/未初始化/输入尺寸异常):全部按 TBMark 兑底(与无分类模型时行为一致);
|
|
|
|
|
// 否则裁剪/推理会失败,且 blob 的 AIclasstype 保持默认值 0 会被当成“支架偏移”上报
|
|
|
|
|
if (!pAIDet || !pAIDet->m_bInitSuccess || deal_image_width <= 0 || deal_image_height <= 0)
|
|
|
|
|
{
|
|
|
|
|
int defaultqx = AIClassTypeToConfigType(AI_CLass_QX_NAME_TBMark);
|
|
|
|
|
for (int i = 0; i < blobs.blobCount; i++)
|
|
|
|
|
{
|
|
|
|
|
blobs.blobTab[i].AIclasstype = defaultqx;
|
|
|
|
|
}
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, "QX_Class",
|
|
|
|
|
" class model not ready(m_bInitSuccess %d, input [w %d h %d]), all %d qx -> TBMark(%d)",
|
|
|
|
|
pAIDet ? (int)pAIDet->m_bInitSuccess : 0, deal_image_width, deal_image_height, blobs.blobCount, defaultqx);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 先统一置为无效类型:分类未提交/失败(裁剪失败、label 非法等)的 blob 不会落到某个真实缺陷类型上
|
|
|
|
|
for (int i = 0; i < blobs.blobCount; i++)
|
|
|
|
|
{
|
|
|
|
|
blobs.blobTab[i].AIclasstype = CONFIG_QX_NAME_count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int totalTasks = blobs.blobCount;
|
|
|
|
|
int submitted = 0;
|
|
|
|
|
@ -3538,8 +3539,8 @@ int ImgCheckAnalysisy::AI_QX_Class_Thread()
|
|
|
|
|
ERROR_DOTS_BLOB_DATA *pblob = &blobs.blobTab[submitted];
|
|
|
|
|
if (pblob->ErrType == ERR_TYPE_2)
|
|
|
|
|
{
|
|
|
|
|
// ERR_TYPE_2不参与分类,按默认AI类别(把手标记)处理
|
|
|
|
|
pblob->AIclasstype = CONFIG_QX_NAME_cell_CGMark;
|
|
|
|
|
// ERR_TYPE_2 不参与分类,按默认类别(铜箔标记)处理
|
|
|
|
|
pblob->AIclasstype = AIClassTypeToConfigType(AI_CLass_QX_NAME_TBMark);
|
|
|
|
|
submitted++;
|
|
|
|
|
completed++;
|
|
|
|
|
continue;
|
|
|
|
|
@ -3567,6 +3568,9 @@ int ImgCheckAnalysisy::AI_QX_Class_Thread()
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "QX_Class",
|
|
|
|
|
"blob idx %d crop class img failed(re %d) roi[%d %d %d %d] -> skip",
|
|
|
|
|
submitted, re, roi.x, roi.y, roi.width, roi.height);
|
|
|
|
|
completed++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -3582,8 +3586,24 @@ int ImgCheckAnalysisy::AI_QX_Class_Thread()
|
|
|
|
|
|
|
|
|
|
int temClass = result->cls_label;
|
|
|
|
|
|
|
|
|
|
// 分类 label 映射:优先读映射配置文件(BM_cls.txt),读不到用默认顺序
|
|
|
|
|
int cls_num = GetClsTypeByLabel(temClass);
|
|
|
|
|
// label 越界(含推理失败返回的负值):按规则统一视为 TBMark
|
|
|
|
|
if (temClass < 0 || temClass >= AI_CLass_QX_NAME_count)
|
|
|
|
|
{
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "QX_Class",
|
|
|
|
|
"blob idx %d invalid class label %d (valid 0~%d), use TBMark",
|
|
|
|
|
result->id, temClass, AI_CLass_QX_NAME_count - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 分类 label 映射:优先读映射配置文件(BM_cls.txt),读不到按 label 顺序;
|
|
|
|
|
// 明场分到暗场凹痕(DarkAoHen)时按 QiTa 处理
|
|
|
|
|
int cls_num = GetClsTypeByLabel(temClass, bIsDark);
|
|
|
|
|
|
|
|
|
|
// 明细日志:原始 label -> 最终类别(仅 -s / 详细日志级别输出)
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "QX_Class",
|
|
|
|
|
"blob idx %d label %d -> %s score %.3f (dark %d)",
|
|
|
|
|
result->id, temClass,
|
|
|
|
|
(cls_num >= 0 && cls_num < AI_CLass_QX_NAME_count) ? AI_CLass_QX_NAME_Names[cls_num].c_str() : "invalid",
|
|
|
|
|
result->cls_score, (int)bIsDark);
|
|
|
|
|
|
|
|
|
|
// std::string strclassName = AI_CLass_QX_NAME_Names[cls_num];
|
|
|
|
|
// printf("AI Class num %d = %s %f\n", cls_num, strclassName.c_str(), result->cls_score);
|
|
|
|
|
@ -3856,21 +3876,25 @@ int ImgCheckAnalysisy::DrawResult_Step_1()
|
|
|
|
|
int ImgCheckAnalysisy::AIClassTypeToConfigType(int nAIQXType)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
// 只有主检测+分类的缺陷在映射表中;支架偏移/支架缺失/把手缺失(传统检出)、铜箔标签(标签检测)不走AI分类
|
|
|
|
|
int resultError_type = CONFIG_QX_NAME_cell_CGMark;
|
|
|
|
|
// AI 分类类别 -> 缺陷类型;支架偏移/支架缺失/把手缺失为传统算法检出,不在本表内
|
|
|
|
|
// 未知类别(含 AI_CLass_QX_NAME_count 及越界值)统一按铜箔标记处理
|
|
|
|
|
int resultError_type = CONFIG_QX_NAME_cell_TBMark;
|
|
|
|
|
switch (nAIQXType)
|
|
|
|
|
{
|
|
|
|
|
case AI_CLass_QX_NAME_CGMark:
|
|
|
|
|
resultError_type = CONFIG_QX_NAME_cell_CGMark;
|
|
|
|
|
break;
|
|
|
|
|
case AI_CLass_QX_NAME_BQBMark:
|
|
|
|
|
resultError_type = CONFIG_QX_NAME_cell_BQBMark;
|
|
|
|
|
case AI_CLass_QX_NAME_DarkAoHen:
|
|
|
|
|
resultError_type = CONFIG_QX_NAME_cell_DarkAoHen;
|
|
|
|
|
break;
|
|
|
|
|
case AI_CLass_QX_NAME_TBMark:
|
|
|
|
|
resultError_type = CONFIG_QX_NAME_cell_TBMark;
|
|
|
|
|
break;
|
|
|
|
|
case AI_CLass_QX_NAME_count: // 无效类别(分类映射配置中不存在):不参与缺陷分析
|
|
|
|
|
resultError_type = CONFIG_QX_NAME_count;
|
|
|
|
|
case AI_CLass_QX_NAME_QiPao:
|
|
|
|
|
resultError_type = CONFIG_QX_NAME_cell_QiPao;
|
|
|
|
|
break;
|
|
|
|
|
case AI_CLass_QX_NAME_TBBQ:
|
|
|
|
|
resultError_type = CONFIG_QX_NAME_cell_TBBQ;
|
|
|
|
|
break;
|
|
|
|
|
case AI_CLass_QX_NAME_QiTa:
|
|
|
|
|
resultError_type = CONFIG_QX_NAME_cell_QiTa;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
@ -3910,12 +3934,20 @@ int ImgCheckAnalysisy::BLobToDetResult()
|
|
|
|
|
int config_qx_type = blobs.blobTab[i].AIclasstype;
|
|
|
|
|
if (config_qx_type < 0 || config_qx_type >= CONFIG_QX_NAME_count)
|
|
|
|
|
{
|
|
|
|
|
// AI 分类无对应缺陷类型(无效类别)时,不参与缺陷结果分析
|
|
|
|
|
// AI 分类无对应缺陷类型(无效类别/分类失败)时,不参与缺陷结果分析
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "BLobToDetResult",
|
|
|
|
|
"blob idx %d invalid AIclasstype %d skip", i, config_qx_type);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 暗场凹痕目前不做检测:分到该类的 blob 直接按 OK 处理(不产生缺陷条目,仅记日志)
|
|
|
|
|
if (config_qx_type == CONFIG_QX_NAME_cell_DarkAoHen)
|
|
|
|
|
{
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "BLobToDetResult",
|
|
|
|
|
"blob idx %d AIclasstype DarkAoHen -> OK (not detected yet), skip", i);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float JudgArea = blobs.blobTab[i].JudgArea;
|
|
|
|
|
float fsecondArea = JudgArea;
|
|
|
|
|
std::string qx_name = CONFIG_QX_NAME_Names[config_qx_type];
|
|
|
|
|
|