Compare commits

..

No commits in common. '28b36c70ffc516cf39532499ea27faa3b9eeb810' and 'd4b37dc077ccfadf5affeb4d106083ee5a5225ee' have entirely different histories.

@ -600,8 +600,8 @@ int AIModel_Impl::F2softmaxId(float *data, int class_num, float *fmaxScore)
std::vector<float> output(cls_num); std::vector<float> output(cls_num);
for (int i = 0; i < output.size(); i++) for (int i = 0; i < output.size(); i++)
{ {
// float outi = (float)exp(data[i]) / (float)total; float outi = (float)exp(data[i]) / (float)total;
output[i] = data[i]; // 不做softmax处理(模型已经做了) output[i] = outi;
// output.push_back(outi); // output.push_back(outi);
// std::cout<<"output["<<i<<"]="<<output[i]<<std::endl; // std::cout<<"output["<<i<<"]="<<output[i]<<std::endl;
} }

@ -20,7 +20,6 @@ enum AI_CLass_QX_NAME_
AI_CLass_QX_NAME_QueXi, // 缺锡 AI_CLass_QX_NAME_QueXi, // 缺锡
AI_CLass_QX_NAME_YiWu, // 异物 AI_CLass_QX_NAME_YiWu, // 异物
AI_CLass_QX_NAME_C_BianXing, // C型卡口异常 AI_CLass_QX_NAME_C_BianXing, // C型卡口异常
AI_CLass_QX_NAME_Panel, // 其他
AI_CLass_QX_NAME_count, AI_CLass_QX_NAME_count,
}; };
// 缺陷项对应在参数中的名称 // 缺陷项对应在参数中的名称
@ -33,7 +32,6 @@ static const std::string AI_CLass_QX_NAME_Names[] =
"QueXi", "QueXi",
"YiWu", "YiWu",
"C_BianXing", "C_BianXing",
"Panel",
}; };
#endif #endif

@ -218,11 +218,8 @@ private:
// 获取指定 残点(blob) 是由哪个(哪些)检测模型检出的 // 获取指定 残点(blob) 是由哪个(哪些)检测模型检出的
std::string GetBlobModelName(const cv::Rect &roi); std::string GetBlobModelName(const cv::Rect &roi);
// 计算 当前图 -> 模板图 / 模板图 -> 当前图 的仿射矩阵(基准矩形可为内定位/外定位) //自适应参数区域更新:仿射变换到模板坐标系(基准矩形可为内定位/外定位)
// 注意: 不再对原图做 warpAffine, 只做坐标变换 int Adapt_Config(cv::RotatedRect tplRefRect, cv::RotatedRect curRefRect, cv::Mat& det_img);
int CalAffineMats(const cv::RotatedRect &tplRefRect, const cv::RotatedRect &curRefRect);
// 把模板 Pin 轮廓(pinImg 裁剪坐标系)反变换到当前检测图(detImg)坐标系
int TransformTplPinContoursToCur();
std::shared_ptr<AIFactory> AI_Factory; std::shared_ptr<AIFactory> AI_Factory;
@ -319,12 +316,8 @@ private:
cv::RotatedRect m_outer_rroi; cv::RotatedRect m_outer_rroi;
cv::RotatedRect m_inner_rroi; cv::RotatedRect m_inner_rroi;
cv::RotatedRect m_tplInnerRect; // 模板内边缘矩形 cv::RotatedRect m_tplInnerRect; // 模板内边缘矩形
std::vector<std::vector<cv::Point>> m_tplPinContours; // 模板 pin blob 轮廓点集(pinImg 裁剪坐标系) std::vector<std::vector<cv::Point>> m_tplPinContours; // 模板 pin blob 轮廓点集
std::vector<std::vector<cv::Point>> m_tplPinContoursCur; // 模板 pin 轮廓(反变换到当前检测图坐标系)
std::vector<std::vector<cv::Point>> m_curPinContours; // 当前 pin blob 轮廓点集 std::vector<std::vector<cv::Point>> m_curPinContours; // 当前 pin blob 轮廓点集
cv::Mat m_affine_cur2tpl; // 当前图 -> 模板图
cv::Mat m_affine_tpl2cur; // 模板图 -> 当前图
bool m_bAffineValid = false; // 仿射矩阵是否有效
std::vector<std::pair<cv::Point2f, cv::Point2f>> m_pinFitLines; // 新方案: 两排Pin拟合直线段(当前检测图坐标系, 两端点) std::vector<std::pair<cv::Point2f, cv::Point2f>> m_pinFitLines; // 新方案: 两排Pin拟合直线段(当前检测图坐标系, 两端点)
std::vector<cv::Point2f> m_pinMissMarks; // 新方案: 缺失Pin位置(相邻Pin中心连线中点) std::vector<cv::Point2f> m_pinMissMarks; // 新方案: 缺失Pin位置(相邻Pin中心连线中点)
}; };

@ -364,6 +364,12 @@ int AI_Edge_Algin::Get_Edge(int AIModel_type, const cv::Mat &img, DetConfig *pDe
cout << "imgSize: " << img.size() << ", " << "detImgSize: " << detImg.size() << ", " << "szSize: " << sz << endl; cout << "imgSize: " << img.size() << ", " << "detImgSize: " << detImg.size() << ", " << "szSize: " << sz << endl;
cv::resize(img, detImg, sz); cv::resize(img, detImg, sz);
// 存图用: 通道转换(灰度化)之前的模型输入图(未开启存图时不克隆, 节省时间/内存)
cv::Mat saveInImg;
if (pDetConfig->pBaseCheckFunction->saveImg.bSaveAlginImg)
{
saveInImg = detImg.clone();
}
int re = 0; int re = 0;
cv::Mat mask; cv::Mat mask;
// 模型输入要求单通道;原图为彩色时按 BGR 顺序转灰度(与 imread(path,0) 的亮度一致) // 模型输入要求单通道;原图为彩色时按 BGR 顺序转灰度(与 imread(path,0) 的亮度一致)
@ -386,11 +392,14 @@ int AI_Edge_Algin::Get_Edge(int AIModel_type, const cv::Mat &img, DetConfig *pDe
if (pDetConfig->pBaseCheckFunction->saveImg.bSaveAlginImg) if (pDetConfig->pBaseCheckFunction->saveImg.bSaveAlginImg)
{ {
std::string cur_time_s = CheckUtil::getCurTimeHMS(); creatsavedir();
std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + pDetConfig->strProductID + "/"; std::string str = m_strSavePath_Big + pDetConfig->strProductID + "_" + pDetConfig->strChannel + "_" + std::to_string(AIModel_type) + "_Img.png";
CheckUtil::CreateDir(strSaveDir); int sr = m_pImageStorage->addImage(str, saveInImg);
std::string strSavePath_out = strSaveDir + cur_time_s + "_" + pDetConfig->strChannel + "_" + std::to_string(AIModel_type) + "_Align_out.png"; if (sr == 0)
CheckUtil::SaveImgAsync(strSavePath_out, mask); {
str = m_strSavePath_Big + pDetConfig->strProductID + "_" + pDetConfig->strChannel + "_" + std::to_string(AIModel_type) + "_Img_mask.png";
m_pImageStorage->addImage(str, mask, true);
}
} }
if (m_pDetConfig->bSaveResultImg) if (m_pDetConfig->bSaveResultImg)

@ -93,9 +93,9 @@ std::string CheckUtil::getCurTimeHMS()
int millisec = tv.tv_usec / 1000; // 微秒转毫秒 int millisec = tv.tv_usec / 1000; // 微秒转毫秒
std::ostringstream oss; std::ostringstream oss;
oss << std::setfill('0') << std::setw(2) << time_info->tm_hour << "-" 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_min << ":"
<< std::setfill('0') << std::setw(2) << time_info->tm_sec << "-" << std::setfill('0') << std::setw(2) << time_info->tm_sec << "."
<< std::setfill('0') << std::setw(3) << millisec; << std::setfill('0') << std::setw(3) << millisec;
return oss.str(); // 返回格式 "HH:MM:SS.mmm" return oss.str(); // 返回格式 "HH:MM:SS.mmm"

@ -180,9 +180,6 @@ static int GetClsTypeByLabel(int nLabel)
case 6: case 6:
cls_num = AI_CLass_QX_NAME_C_BianXing; cls_num = AI_CLass_QX_NAME_C_BianXing;
break; break;
case 7:
cls_num = AI_CLass_QX_NAME_Panel;
break;
default: default:
break; break;
} }
@ -547,101 +544,46 @@ cv::Scalar ImgCheckAnalysisy::calc_blob_info_withstats(cv::Mat &img, const cv::M
return cv::Scalar(worb, energy, hj); return cv::Scalar(worb, energy, hj);
} }
// 计算仿射矩阵(不做图像 warp): // 仿射变换到模板坐标系:把当前基准矩形(内定位/外定位)的顶点映射到模板对应基准矩形上
// 基准点用当前基准矩形(内定位优先/外定位回退)的三个角点 -> 模板对应基准矩形的三个角点 int ImgCheckAnalysisy::Adapt_Config(RotatedRect tplRefRect, RotatedRect curRefRect, Mat& det_img){
// m_affine_cur2tpl: 当前图坐标 -> 模板图坐标
// m_affine_tpl2cur: 模板图坐标 -> 当前图坐标
int ImgCheckAnalysisy::CalAffineMats(const RotatedRect &tplRefRect, const RotatedRect &curRefRect)
{
m_affine_cur2tpl.release();
m_affine_tpl2cur.release();
m_bAffineValid = false;
if (tplRefRect.size.width <= 0 || tplRefRect.size.height <= 0 ||
curRefRect.size.width <= 0 || curRefRect.size.height <= 0)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "CalAffineMats: invalid ref rect tpl[%f %f] cur[%f %f]",
tplRefRect.size.width, tplRefRect.size.height,
curRefRect.size.width, curRefRect.size.height);
return 1;
}
vector<cv::Point2f> cur_vertices = CheckUtil::sort_vertices(curRefRect); vector<cv::Point2f> cur_vertices = CheckUtil::sort_vertices(curRefRect);
vector<cv::Point2f> tpl_vertices = CheckUtil::sort_vertices(tplRefRect); vector<cv::Point2f> tpl_vertices = CheckUtil::sort_vertices(tplRefRect);
cv::Point2f cur_pts[3] = { cv::Point2f src_pts[3] = {
cur_vertices[0], // 左上 cur_vertices[0], // 对应左上
cur_vertices[1], // 右上 cur_vertices[1], // 对应右上
cur_vertices[2] // 左下 cur_vertices[2] // 对应左下
}; };
cv::Point2f tpl_pts[3] = { cv::Point2f dst_pts[3] = {
tpl_vertices[0], // 左上 tpl_vertices[0], // 左上
tpl_vertices[1], // 右上 tpl_vertices[1], // 右上
tpl_vertices[2] // 左下 tpl_vertices[2] // 左下
}; };
cv::Mat affine_mat = cv::getAffineTransform(src_pts, dst_pts);
m_affine_cur2tpl = cv::getAffineTransform(cur_pts, tpl_pts); // 仿射变换
m_affine_tpl2cur = cv::getAffineTransform(tpl_pts, cur_pts); cv::warpAffine(det_img, det_img, affine_mat, det_img.size());
m_bAffineValid = (!m_affine_cur2tpl.empty() && !m_affine_tpl2cur.empty());
if (!m_bAffineValid)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "CalAffineMats: getAffineTransform failed");
return 1;
}
// 仅用于日志: 分解当前->模板的旋转/缩放, 便于排查
const double *M = m_affine_cur2tpl.ptr<double>(0);
double fScaleX = std::sqrt(M[0] * M[0] + M[3] * M[3]);
double fScaleY = std::sqrt(M[1] * M[1] + M[4] * M[4]);
double fAngle = std::atan2(M[3], M[0]) * 180.0 / CV_PI;
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "CalAffineMats",
"scaleX %.4f scaleY %.4f angle %.2f deg t(%.2f %.2f)",
fScaleX, fScaleY, fAngle, M[2], M[5]);
return 0;
}
// 把模板 Pin 轮廓反变换到当前检测图(detImg)坐标系:
// m_tplPinContours 在 pinImg(模板外框裁剪图)坐标系
// -> 加模板外框原点得到模板图坐标
// -> m_affine_tpl2cur 得到当前图坐标
// -> 减 m_outer_roi 原点得到 detImg 坐标
int ImgCheckAnalysisy::TransformTplPinContoursToCur()
{
m_tplPinContoursCur.clear();
if (m_tplPinContours.empty()) // 将m_outer_rroi和m_inner_rroi变换到模板图像坐标系下
{ auto transformRotatedRect = [&affine_mat](cv::RotatedRect &rrect)
return 0;
}
if (!m_bAffineValid || m_affine_tpl2cur.empty())
{ {
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", const double *M = affine_mat.ptr<double>(0);
"TransformTplPinContoursToCur: affine invalid, template pin contours skipped");
return 1;
}
const cv::Rect tplBound = m_tplOuterRect.boundingRect(); cv::Point2f pts[4];
const double *M = m_affine_tpl2cur.ptr<double>(0); rrect.points(pts);
m_tplPinContoursCur.reserve(m_tplPinContours.size()); std::vector<cv::Point2f> transformed_pts(4);
for (const auto &contour : m_tplPinContours) for (int i = 0; i < 4; i++)
{
std::vector<cv::Point> mapped;
mapped.reserve(contour.size());
for (const auto &pt : contour)
{ {
// pinImg 坐标 -> 模板图坐标 float x = static_cast<float>(M[0] * pts[i].x + M[1] * pts[i].y + M[2]);
const double tplX = static_cast<double>(pt.x) + tplBound.x; float y = static_cast<float>(M[3] * pts[i].x + M[4] * pts[i].y + M[5]);
const double tplY = static_cast<double>(pt.y) + tplBound.y; transformed_pts[i] = cv::Point2f(x, y);
// 模板图坐标 -> 当前图坐标
const double curX = M[0] * tplX + M[1] * tplY + M[2];
const double curY = M[3] * tplX + M[4] * tplY + M[5];
// 当前图坐标 -> detImg 坐标
mapped.emplace_back(cvRound(curX - m_outer_roi.x), cvRound(curY - m_outer_roi.y));
} }
m_tplPinContoursCur.push_back(std::move(mapped)); rrect = cv::minAreaRect(transformed_pts);
} };
transformRotatedRect(m_outer_rroi);
transformRotatedRect(m_inner_rroi);
return 0; return 0;
} }
@ -661,8 +603,7 @@ int ImgCheckAnalysisy::CheckRun()
m_pdetlog->bPrintStr = true; m_pdetlog->bPrintStr = true;
} }
// m_pdetlog->bPrintStr = true; // m_pdetlog->bPrintStr = true;
m_fImgage_Scale_X = m_pBasicConfig->fImage_Scale_x;
m_fImgage_Scale_Y = m_pBasicConfig->fImage_Scale_y;
m_pdetlog->AddCheckstr(PrintLevel_0, "1、basic Info", "---------------------------1、basic Info---------------------------------"); m_pdetlog->AddCheckstr(PrintLevel_0, "1、basic Info", "---------------------------1、basic Info---------------------------------");
m_pdetlog->AddCheckstr(PrintLevel_0, "Version", "%s", GetVersion().c_str()); m_pdetlog->AddCheckstr(PrintLevel_0, "Version", "%s", GetVersion().c_str());
m_pdetlog->AddCheckstr(PrintLevel_0, "Updateconfig", "%d", m_bupdateconfig); m_pdetlog->AddCheckstr(PrintLevel_0, "Updateconfig", "%d", m_bupdateconfig);
@ -707,12 +648,6 @@ int ImgCheckAnalysisy::CheckRun()
float aouter = CheckUtil::getLongAxisAngle(m_outer_rroi); float aouter = CheckUtil::getLongAxisAngle(m_outer_rroi);
float ainner = CheckUtil::getLongAxisAngle(m_inner_rroi); float ainner = CheckUtil::getLongAxisAngle(m_inner_rroi);
// 计算内边缘宽高与标准差值
float dinnerLen = std::abs(m_inner_rroi.size.width - m_pbaseCheckFunction->ALign_Config.len) * m_fImgage_Scale_X;
float dinnerWidth = std::abs(m_inner_rroi.size.height - m_pbaseCheckFunction->ALign_Config.width) * m_fImgage_Scale_Y;
bool bSizeerr = (dinnerLen > m_pbaseCheckFunction->ALign_Config.len_diff || dinnerWidth > m_pbaseCheckFunction->ALign_Config.width_diff);
float diff_align = std::fabs(aouter - ainner); float diff_align = std::fabs(aouter - ainner);
diff_align = std::fmod(diff_align, 180.0f); // 模 180 度 diff_align = std::fmod(diff_align, 180.0f); // 模 180 度
@ -730,46 +665,46 @@ int ImgCheckAnalysisy::CheckRun()
} }
// 2. 使用 ALign_Config.x_offset / y_offset (mm) 卡控内外边缘中心偏移(mm 转像素) // 2. 使用 ALign_Config.x_offset / y_offset (mm) 卡控内外边缘中心偏移(mm 转像素)
float offX_mm = 0.0f; float offX_px = 0.0f;
float offY_mm = 0.0f; float offY_px = 0.0f;
if (m_pbaseCheckFunction != NULL) if (m_pbaseCheckFunction != NULL)
{ {
if (m_fImgage_Scale_X > 0.0f) if (m_fImgage_Scale_X > 0.0f)
{ {
offX_mm = m_pbaseCheckFunction->ALign_Config.x_offset; offX_px = m_pbaseCheckFunction->ALign_Config.x_offset / m_fImgage_Scale_X;
} }
if (m_fImgage_Scale_Y > 0.0f) if (m_fImgage_Scale_Y > 0.0f)
{ {
offY_mm = m_pbaseCheckFunction->ALign_Config.y_offset; offY_px = m_pbaseCheckFunction->ALign_Config.y_offset / m_fImgage_Scale_Y;
} }
} }
// 3. 内外边缘中心坐标偏移(像素) // 3. 内外边缘中心坐标偏移(像素)
float dx_mm = std::fabs(m_outer_rroi.center.x - m_inner_rroi.center.x) * m_fImgage_Scale_X; float dx_px = std::fabs(m_outer_rroi.center.x - m_inner_rroi.center.x);
float dy_mm = std::fabs(m_outer_rroi.center.y - m_inner_rroi.center.y) * m_fImgage_Scale_Y; float dy_px = std::fabs(m_outer_rroi.center.y - m_inner_rroi.center.y);
bool bAngleErr = diff_align > angleTh; bool bAngleErr = diff_align > angleTh;
bool bOffsetXErr = (offX_mm > 0.0f) && (dx_mm > offX_mm); bool bOffsetXErr = (offX_px > 0.0f) && (dx_px > offX_px);
bool bOffsetYErr = (offY_mm > 0.0f) && (dy_mm > offY_mm); bool bOffsetYErr = (offY_px > 0.0f) && (dy_px > offY_px);
bool bOffsetErr = bOffsetXErr || bOffsetYErr; bool bOffsetErr = bOffsetXErr || bOffsetYErr;
// 角度超差或中心偏移超差 -> 添加到缺陷列表 // 角度超差或中心偏移超差 -> 添加到缺陷列表
if (bAngleErr || bOffsetErr || bSizeerr) if (bAngleErr || bOffsetErr)
{ {
QX_ERROR_INFO_ alignErr; QX_ERROR_INFO_ alignErr;
alignErr.Idx = static_cast<int>(m_pDetResult->pQx_ErrorList->size()); alignErr.Idx = static_cast<int>(m_pDetResult->pQx_ErrorList->size());
// roi 用整张检测图区域(detImg 坐标系); detImg 现在取当前外定位包围框, 尺寸与之一致 // roi 用整张检测图区域(detImg 坐标系)
cv::Rect curBound = m_outer_rroi.boundingRect(); cv::Rect tplRect = m_tplOuterRect.boundingRect();
alignErr.roi = cv::Rect(0, 0, curBound.width, curBound.height); alignErr.roi = cv::Rect(0, 0, tplRect.width, tplRect.height);
alignErr.area = alignErr.roi.width * alignErr.roi.height; alignErr.area = alignErr.roi.width * alignErr.roi.height;
alignErr.JudgArea = alignErr.area * m_fImgage_Scale_X * m_fImgage_Scale_Y; alignErr.JudgArea = alignErr.area * m_fImgage_Scale_X * m_fImgage_Scale_Y;
alignErr.flen = curBound.width * m_fImgage_Scale_X; alignErr.flen = tplRect.width * m_fImgage_Scale_X;
alignErr.fbreadth = curBound.height * m_fImgage_Scale_Y; alignErr.fbreadth = tplRect.height * m_fImgage_Scale_Y;
// 缺陷列表中原 other(其他)已移除;内外边缘对齐超差暂按 C型卡口异常 上报(待确认) // 缺陷列表中原 other(其他)已移除;内外边缘对齐超差暂按 C型卡口异常 上报(待确认)
alignErr.nconfig_qx_type = CONFIG_QX_NAME_HunLiao; alignErr.nconfig_qx_type = CONFIG_QX_NAME_C_BianXing;
alignErr.qx_name = CONFIG_QX_NAME_Names[CONFIG_QX_NAME_HunLiao]; alignErr.qx_name = CONFIG_QX_NAME_Names[CONFIG_QX_NAME_C_BianXing];
alignErr.grayDis = diff_align; // 记录内外边缘夹角(度) alignErr.grayDis = diff_align; // 记录内外边缘夹角(度)
alignErr.detRegionidxList.push_back(0); alignErr.detRegionidxList.push_back(0);
@ -783,18 +718,13 @@ int ImgCheckAnalysisy::CheckRun()
} }
if (bOffsetXErr) if (bOffsetXErr)
{ {
m_pdetlog->AddCheckstr(PrintLevel_0, "Align Check", "center offset dx %.1f mm > %.1f mm", m_pdetlog->AddCheckstr(PrintLevel_0, "Align Check", "center offset dx %.1f px > %.1f px",
dx_mm, offX_mm); dx_px, offX_px);
} }
if (bOffsetYErr) if (bOffsetYErr)
{ {
m_pdetlog->AddCheckstr(PrintLevel_0, "Align Check", "center offset dy %.1f mm > %.1f mm", m_pdetlog->AddCheckstr(PrintLevel_0, "Align Check", "center offset dy %.1f px > %.1f px",
dy_mm, offY_mm); dy_px, offY_px);
}
if (bSizeerr)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Align Check", "inner size diff len %.1f mm, width %.1f mm > threshold",
dinnerLen, dinnerWidth);
} }
} }
@ -821,44 +751,21 @@ int ImgCheckAnalysisy::CheckRun()
} }
#else #else
// 检测PIN偏移/缺失对位置基准最敏感:PIN 位置的真实基准是产品内腔(内定位), // 检测PIN偏移/缺失对位置基准最敏感:PIN 位置的真实基准是产品内腔(内定位),
// 因此优先用内定位计算仿射(当前内边缘 -> 模板内边缘);模板/当前内定位无效时回退外定位。 // 因此优先用内定位做仿射变换(当前内边缘 -> 模板内边缘);
// 注意: 这里不再对原图做 warpAffine, 只计算矩阵, 并把模板 Pin 轮廓反变换到当前图坐标系。 // 模板内定位/当前内定位缺失或无效时,回退到外定位(与旧行为一致)
bool bUseInnerAlign = (m_tplInnerRect.size.width > 0 && m_tplInnerRect.size.height > 0 && bool bUseInnerAlign = (m_tplInnerRect.size.width > 0 && m_tplInnerRect.size.height > 0 &&
m_inner_rroi.size.width > 0 && m_inner_rroi.size.height > 0); m_inner_rroi.size.width > 0 && m_inner_rroi.size.height > 0);
if (bUseInnerAlign) if (bUseInnerAlign)
{ {
m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", "Affine align basis: Inner(inner_rroi -> tplInnerRect)");
"Affine align basis: Inner(inner_rroi -> tplInnerRect), template pins warp only, image NOT warped"); Adapt_Config(m_tplInnerRect, m_inner_rroi, m_CheckResult_shareP->in_shareImage->img);
CalAffineMats(m_tplInnerRect, m_inner_rroi);
} }
else else
{ {
m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", "Affine align basis: Outer(outer_rroi -> tplOuterRect)");
"Affine align basis: Outer(outer_rroi -> tplOuterRect), template pins warp only, image NOT warped"); Adapt_Config(m_tplOuterRect, m_outer_rroi, m_CheckResult_shareP->in_shareImage->img);
CalAffineMats(m_tplOuterRect, m_outer_rroi);
}
// 后续所有检测/裁剪都在当前图坐标系: 裁剪框取当前外定位的包围框(原图不 warp)
{
cv::Rect curOuterBound = m_outer_rroi.boundingRect();
cv::Rect curImgBound(0, 0, m_CheckResult_shareP->in_shareImage->img.cols,
m_CheckResult_shareP->in_shareImage->img.rows);
m_outer_roi = curOuterBound & curImgBound;
if (m_outer_roi.width <= 0 || m_outer_roi.height <= 0)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "Affine mode: current outer roi invalid [%d %d %d %d]",
curOuterBound.x, curOuterBound.y, curOuterBound.width, curOuterBound.height);
m_nErrorCode = CHECK_ERROR_Config_cutRoi;
m_nCheckResultErrorCode = m_nErrorCode;
return m_nErrorCode;
}
} }
m_outer_roi = m_tplOuterRect.boundingRect();
// 模板 Pin(pinImg 坐标) -> 模板图坐标 -> 当前图坐标 -> detImg 坐标
TransformTplPinContoursToCur();
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "2.1、pre detect",
"tpl pin contours -> current frame: %ld (affineValid %d)",
static_cast<long>(m_tplPinContoursCur.size()), static_cast<int>(m_bAffineValid));
#endif #endif
m_Crop_Roi_paramImg = m_outer_roi; m_Crop_Roi_paramImg = m_outer_roi;
m_pImageAllResult->pDetResult->CutRoi = m_outer_roi; m_pImageAllResult->pDetResult->CutRoi = m_outer_roi;
@ -1649,7 +1556,6 @@ int ImgCheckAnalysisy::ConfigCheck(cv::Mat img)
int ImgCheckAnalysisy::AI_Detect_Thread(const cv::Mat &img, cv::Mat &ResultImg) int ImgCheckAnalysisy::AI_Detect_Thread(const cv::Mat &img, cv::Mat &ResultImg)
{ {
std::string cur_time_s = CheckUtil::getCurTimeHMS();
// 依次使用多个检测模型,各模型输出 mask 取并集作为最终 outmask; // 依次使用多个检测模型,各模型输出 mask 取并集作为最终 outmask;
// Defect_QueXi 模型是否参与合并由基础参数 Merge_QueXi 开关控制 // Defect_QueXi 模型是否参与合并由基础参数 Merge_QueXi 开关控制
std::vector<std::shared_ptr<AIModel_Base>> pAIDetList; std::vector<std::shared_ptr<AIModel_Base>> pAIDetList;
@ -1779,10 +1685,12 @@ int ImgCheckAnalysisy::AI_Detect_Thread(const cv::Mat &img, cv::Mat &ResultImg)
// 目录结构:/home/aidlux/BOE/ZB_SOCKET/AI_Detect/<模型名称>/<产品ID>/ // 目录结构:/home/aidlux/BOE/ZB_SOCKET/AI_Detect/<模型名称>/<产品ID>/
if (m_pbaseCheckFunction->saveImg.bSaveAIDetImg && !modelMaskFull.empty()) if (m_pbaseCheckFunction->saveImg.bSaveAIDetImg && !modelMaskFull.empty())
{ {
std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + DetImgInfo_shareP->strImgProductID + "/"; std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/KK/" + strModelName + "/" + DetImgInfo_shareP->strImgProductID + "/";
CheckUtil::CreateDir(strSaveDir); CheckUtil::CreateDir(strSaveDir);
std::string strSavePath_out = strSaveDir + cur_time_s + "_" + m_strCurDetChannel + "_" + strModelName + "_out.png"; std::string strSavePath_in = strSaveDir + m_strCurDetChannel + "_in.png";
CheckUtil::SaveImgAsync(strSavePath_out, modelMaskFull); std::string strSavePath_out = strSaveDir + m_strCurDetChannel + "_out.png";
cv::imwrite(strSavePath_in, detInputImg);
cv::imwrite(strSavePath_out, modelMaskFull);
} }
// 4、mask 结果放入队列,用于 AIMaskDet 加速 blob 计算(整图 ROI) // 4、mask 结果放入队列,用于 AIMaskDet 加速 blob 计算(整图 ROI)
@ -1837,13 +1745,6 @@ int ImgCheckAnalysisy::AI_Detect_Thread(const cv::Mat &img, cv::Mat &ResultImg)
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "AI_Detect End"); m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "AI_Detect End");
if (m_pbaseCheckFunction->saveImg.bSaveAIDetImg )
{
std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + DetImgInfo_shareP->strImgProductID + "/";
CheckUtil::CreateDir(strSaveDir);
std::string strSavePath_in = strSaveDir + cur_time_s + "_" + m_strCurDetChannel + "_in.png";
CheckUtil::SaveImgAsync(strSavePath_in, m_CheckResult_shareP->cutSrcimg);
}
// 临时存图 // 临时存图
if (DetImgInfo_shareP->bsaveProcessImg) if (DetImgInfo_shareP->bsaveProcessImg)
{ {
@ -1930,8 +1831,8 @@ int ImgCheckAnalysisy::AI_QX_Class_Thread()
// 分类 label 映射:优先读映射配置文件(KK_cls.txt),读不到用默认顺序 // 分类 label 映射:优先读映射配置文件(KK_cls.txt),读不到用默认顺序
int cls_num = GetClsTypeByLabel(temClass); int cls_num = GetClsTypeByLabel(temClass);
std::string strclassName = AI_CLass_QX_NAME_Names[cls_num]; // std::string strclassName = AI_CLass_QX_NAME_Names[cls_num];
m_pdetlog->AddCheckstr(PrintLevel_1, "AI Class num", "%d = %s %f\n", cls_num, strclassName.c_str(), result->cls_score); // printf("AI Class num %d = %s %f\n", cls_num, strclassName.c_str(), result->cls_score);
// 分类存图 开启。 // 分类存图 开启。
if (m_pbaseCheckFunction->saveImg.bSaveClsImg) if (m_pbaseCheckFunction->saveImg.bSaveClsImg)
@ -2141,7 +2042,7 @@ int ImgCheckAnalysisy::DrawResult_Step_1()
cv::Point(cvRound(mark.x), cvRound(mark.y + 6.0f))}); cv::Point(cvRound(mark.x), cvRound(mark.y + 6.0f))});
} }
#else #else
const std::vector<std::vector<cv::Point>> &tplDrawPinContours = m_tplPinContoursCur; const std::vector<std::vector<cv::Point>> &tplDrawPinContours = m_tplPinContours;
#endif #endif
for (const auto &contour : tplDrawPinContours) for (const auto &contour : tplDrawPinContours)
{ {
@ -2189,7 +2090,7 @@ int ImgCheckAnalysisy::AIClassTypeToConfigType(int nAIQXType)
{ {
// 无对应缺陷类型时返回 CONFIG_QX_NAME_count(缺陷列表已移除 other) // 无对应缺陷类型时返回 CONFIG_QX_NAME_count(缺陷列表已移除 other)
int resultError_type = AI_CLass_QX_NAME_Panel; int resultError_type = CONFIG_QX_NAME_count;
switch (nAIQXType) switch (nAIQXType)
{ {
case AI_CLass_QX_NAME_PIN_PoSun: case AI_CLass_QX_NAME_PIN_PoSun:
@ -2213,9 +2114,6 @@ int ImgCheckAnalysisy::AIClassTypeToConfigType(int nAIQXType)
case AI_CLass_QX_NAME_C_BianXing: case AI_CLass_QX_NAME_C_BianXing:
resultError_type = CONFIG_QX_NAME_C_BianXing; resultError_type = CONFIG_QX_NAME_C_BianXing;
break; break;
case AI_CLass_QX_NAME_Panel:
resultError_type = CONFIG_QX_NAME_Panel;
break;
default: default:
break; break;
} }
@ -2288,18 +2186,18 @@ int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img)
return cv::Point2f(r.x + r.width * 0.5f, r.y + r.height * 0.5f); return cv::Point2f(r.x + r.width * 0.5f, r.y + r.height * 0.5f);
}; };
const int nTpl = static_cast<int>(m_tplPinContoursCur.size()); const int nTpl = static_cast<int>(m_tplPinContours.size());
const int nCur = static_cast<int>(m_curPinContours.size()); const int nCur = static_cast<int>(m_curPinContours.size());
if (nTpl > 0) if (nTpl > 0)
{ {
// 1、统计模板 pin 中心与外包矩形(已反变换到当前检测图坐标系) // 1、统计模板 pin 中心与外包矩形
std::vector<cv::Point2f> tplCenters(nTpl); std::vector<cv::Point2f> tplCenters(nTpl);
std::vector<cv::Rect> tplRects(nTpl); std::vector<cv::Rect> tplRects(nTpl);
for (int i = 0; i < nTpl; i++) for (int i = 0; i < nTpl; i++)
{ {
tplCenters[i] = getCenter(m_tplPinContoursCur[i]); tplCenters[i] = getCenter(m_tplPinContours[i]);
tplRects[i] = cv::boundingRect(m_tplPinContoursCur[i]); tplRects[i] = cv::boundingRect(m_tplPinContours[i]);
} }
// 2、当前 pin 中心与外包矩形 // 2、当前 pin 中心与外包矩形
@ -2312,13 +2210,19 @@ int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img)
} }
std::vector<bool> curUsed(nCur, false); std::vector<bool> curUsed(nCur, false);
// 3、偏移阈值直接使用基础参数 pin_x_offset / pin_y_offset (mm),与换算为 mm 的偏移量直接比较 // 3、偏移阈值由基础参数 pin_x_offset / pin_y_offset (mm) 换算为像素
float offsetThX = 0.0f; // mm float offsetThX = 0.0f;
float offsetThY = 0.0f; // mm float offsetThY = 0.0f;
if (m_pbaseCheckFunction != NULL) if (m_pbaseCheckFunction != NULL)
{ {
offsetThX = m_pbaseCheckFunction->pinDet.pin_x_offset; if (m_fImgage_Scale_X > 0.0f)
offsetThY = m_pbaseCheckFunction->pinDet.pin_y_offset; {
offsetThX = m_pbaseCheckFunction->pinDet.pin_x_offset / m_fImgage_Scale_X;
}
if (m_fImgage_Scale_Y > 0.0f)
{
offsetThY = m_pbaseCheckFunction->pinDet.pin_y_offset / m_fImgage_Scale_Y;
}
} }
// 上报缺失/偏移缺陷(缺陷类型可按需调整) // 上报缺失/偏移缺陷(缺陷类型可按需调整)
@ -2326,9 +2230,9 @@ int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img)
{ {
QX_ERROR_INFO_ pinErr; QX_ERROR_INFO_ pinErr;
pinErr.Idx = static_cast<int>(m_pDetResult->pQx_ErrorList->size()); pinErr.Idx = static_cast<int>(m_pDetResult->pQx_ErrorList->size());
cv::Rect r = cv::boundingRect(m_tplPinContoursCur[pinIdx]); cv::Rect r = cv::boundingRect(m_tplPinContours[pinIdx]);
pinErr.roi = r; pinErr.roi = r;
pinErr.area = static_cast<int>(cv::contourArea(m_tplPinContoursCur[pinIdx])); pinErr.area = static_cast<int>(cv::contourArea(m_tplPinContours[pinIdx]));
pinErr.JudgArea = pinErr.area * m_fImgage_Scale_X * m_fImgage_Scale_Y; pinErr.JudgArea = pinErr.area * m_fImgage_Scale_X * m_fImgage_Scale_Y;
int longSide = (r.width > r.height) ? r.width : r.height; int longSide = (r.width > r.height) ? r.width : r.height;
int shortSide = (r.width < r.height) ? r.width : r.height; int shortSide = (r.width < r.height) ? r.width : r.height;
@ -2387,21 +2291,19 @@ int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img)
curUsed[bestIdx] = true; curUsed[bestIdx] = true;
// x 或 y 方向偏移超过配置阈值 -> 偏移;偏移量按成像精度换算为 mm (mm = px * 成像精度) // x 或 y 方向偏移超过配置阈值 -> 偏移
float dx_mm = (tplCenters[i].x - curCenters[bestIdx].x) * m_fImgage_Scale_X; float dx = tplCenters[i].x - curCenters[bestIdx].x;
float dy_mm = (tplCenters[i].y - curCenters[bestIdx].y) * m_fImgage_Scale_Y; float dy = tplCenters[i].y - curCenters[bestIdx].y;
float absDx = std::fabs(dx_mm); float absDx = std::fabs(dx);
float absDy = std::fabs(dy_mm); float absDy = std::fabs(dy);
bool bOffset = (absDx > offsetThX || absDy > offsetThY); if (absDx > offsetThX || absDy > offsetThY)
if (bOffset)
{ {
nOffset++; nOffset++;
char buf[64]; char buf[64];
snprintf(buf, sizeof(buf), "offset dx %.2f dy %.2f mm", absDx, absDy); snprintf(buf, sizeof(buf), "offset dx %.1f dy %.1f", absDx, absDy);
reportPinError(i, tplCenters[i], buf, CONFIG_QX_NAME_PIN_PianYi); reportPinError(i, tplCenters[i], buf, CONFIG_QX_NAME_PIN_PianYi);
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "pin %d OFFSET dx %.1f dy %.1f", i, absDx, absDy);
} }
// 每个pin脚都打印dx/dy(mm),方便调整 pin_x_offset / pin_y_offset 参数
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "pin %d %s dx %.2f dy %.2f mm", i, bOffset ? "OFFSET" : "OK", absDx, absDy);
} }
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "tpl %d cur %d miss %d offset %d", nTpl, nCur, nMiss, nOffset); m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "tpl %d cur %d miss %d offset %d", nTpl, nCur, nMiss, nOffset);
@ -2442,7 +2344,7 @@ int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img)
CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 255)), cv::MARKER_CROSS, 16, 1); CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 255)), cv::MARKER_CROSS, 16, 1);
} }
#else #else
cv::drawContours(showimg, m_tplPinContoursCur, -1, CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 0)), 1); cv::drawContours(showimg, m_tplPinContours, -1, CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 0)), 1);
#endif #endif
imwrite("pin_contours.png", showimg); imwrite("pin_contours.png", showimg);
} }
@ -2719,8 +2621,8 @@ int ImgCheckAnalysisy::Pin_Direct_Match()
// 新方案(开关 PIN_USE_DIRECT_MATCH = 1): 不做模板对比, 只针对检测Pin分析 // 新方案(开关 PIN_USE_DIRECT_MATCH = 1): 不做模板对比, 只针对检测Pin分析
// 1) 方向扫描(0~180°, 步长1°): 取 "两排Pin沿法线投影组内方差之和" 最小的角度作为Pin排方向(对图像旋转不敏感), // 1) 方向扫描(0~180°, 步长1°): 取 "两排Pin沿法线投影组内方差之和" 最小的角度作为Pin排方向(对图像旋转不敏感),
// 再沿该法线投影, 在最大间隙处把Pin分成两排(两排插口; Pin不足4个则按一排处理); // 再沿该法线投影, 在最大间隙处把Pin分成两排(两排插口; Pin不足4个则按一排处理);
// 2) 每排做最小二乘直线拟合(cv::fitLine): pin中心到直线(沿法线方向)距离按成像精度换算为 mm, 超过 pin_y_offset(mm) 判 偏移; // 2) 每排做最小二乘直线拟合(cv::fitLine): pin中心到直线(沿法线方向)距离超过 pin_y_offset(换算像素) 判 偏移;
// 3) 每排内沿排方向排序: 相邻两pin中心连线距离按成像精度换算为 mm, 超过 pin_x_offset(mm) 判 两pin中间缺失一个pin。 // 3) 每排内沿排方向排序: 相邻两pin中心连线距离超过 pin_x_offset(换算像素) 判 两pin中间缺失一个pin。
int ImgCheckAnalysisy::Pin_Direct_Check() int ImgCheckAnalysisy::Pin_Direct_Check()
{ {
// 计算轮廓中心点(外接矩形中心) // 计算轮廓中心点(外接矩形中心)
@ -2752,13 +2654,19 @@ int ImgCheckAnalysisy::Pin_Direct_Check()
return 0; return 0;
} }
// 阈值直接使用 mm 参数(判据按 mm 比较): pin_x_offset -> 相邻pin间距(缺失)阈值; pin_y_offset -> 到拟合直线距离(偏移)阈值; <=0 时不判该项 // 阈值(mm 参数换算为像素): pin_x_offset -> 相邻pin间距(缺失)阈值; pin_y_offset -> 到拟合直线距离(偏移)阈值; <=0 时不判该项
float offsetThX = 0.0f; // mm float offsetThX = 0.0f;
float offsetThY = 0.0f; // mm float offsetThY = 0.0f;
if (m_pbaseCheckFunction != NULL) if (m_pbaseCheckFunction != NULL)
{ {
offsetThX = m_pbaseCheckFunction->pinDet.pin_x_offset; if (m_fImgage_Scale_X > 0.0f)
offsetThY = m_pbaseCheckFunction->pinDet.pin_y_offset; {
offsetThX = m_pbaseCheckFunction->pinDet.pin_x_offset / m_fImgage_Scale_X;
}
if (m_fImgage_Scale_Y > 0.0f)
{
offsetThY = m_pbaseCheckFunction->pinDet.pin_y_offset / m_fImgage_Scale_Y;
}
} }
// 沿给定方向投影排序, 在最大间隙处切分成两排(order[0..splitK] 一排, order[splitK+1..] 另一排) // 沿给定方向投影排序, 在最大间隙处切分成两排(order[0..splitK] 一排, order[splitK+1..] 另一排)
@ -2875,11 +2783,11 @@ int ImgCheckAnalysisy::Pin_Direct_Check()
rows.push_back(order); rows.push_back(order);
} }
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check", m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check",
"row dir %.1f deg rows %d th[gap %.2f line %.2f] mm", "row dir %.1f deg rows %d th[gap %.1f line %.1f] px",
bestTheta * 180.0f / static_cast<float>(CV_PI), bestTheta * 180.0f / static_cast<float>(CV_PI),
static_cast<int>(rows.size()), offsetThX, offsetThY); static_cast<int>(rows.size()), offsetThX, offsetThY);
// 上报缺陷(roi/area 由调用处给出; grayDis 记录对应度量: 到直线距离 / 相邻间距, 单位mm) // 上报缺陷(roi/area 由调用处给出; grayDis 记录对应度量: 到直线距离 / 相邻间距, 单位像素)
auto reportPinError = [&](const cv::Rect &roi, int area, double grayDis, int nconfig_qx_type, const std::string &detail) auto reportPinError = [&](const cv::Rect &roi, int area, double grayDis, int nconfig_qx_type, const std::string &detail)
{ {
QX_ERROR_INFO_ pinErr; QX_ERROR_INFO_ pinErr;
@ -2944,28 +2852,23 @@ int ImgCheckAnalysisy::Pin_Direct_Check()
} }
m_pinFitLines.push_back(std::make_pair(lineP0 + lineDir * tMin, lineP0 + lineDir * tMax)); m_pinFitLines.push_back(std::make_pair(lineP0 + lineDir * tMin, lineP0 + lineDir * tMax));
// 2、pin中心到直线(沿法线方向)距离超过 pin_y_offset -> 偏移;距离按成像精度换算为 mm (mm = px * 成像精度) // 2、pin中心到直线(沿法线方向)距离超过 pin_y_offset -> 偏移
if (offsetThY > 0.0f) if (offsetThY > 0.0f)
{ {
for (int i = 0; i < nRow; i++) for (int i = 0; i < nRow; i++)
{ {
// 垂直偏移向量(px)的分量分别换算为 mm 后求模 float absDist = std::fabs((rowPts[i] - lineP0).dot(lineNor));
float distPx = (rowPts[i] - lineP0).dot(lineNor); if (absDist > offsetThY)
float dx_mm = lineNor.x * distPx * m_fImgage_Scale_X;
float dy_mm = lineNor.y * distPx * m_fImgage_Scale_Y;
float absDist = std::sqrt(dx_mm * dx_mm + dy_mm * dy_mm);
int idx = rowIdx[i];
bool bOffset = (absDist > offsetThY);
if (bOffset)
{ {
int idx = rowIdx[i];
nOffset++; nOffset++;
char buf[96]; char buf[96];
snprintf(buf, sizeof(buf), "pin %d offset to row line %.2f mm > %.2f mm", idx, absDist, offsetThY); snprintf(buf, sizeof(buf), "pin %d offset to row line %.1f px > %.1f px", idx, absDist, offsetThY);
reportPinError(curRects[idx], static_cast<int>(cv::contourArea(m_curPinContours[idx])), reportPinError(curRects[idx], static_cast<int>(cv::contourArea(m_curPinContours[idx])),
absDist, CONFIG_QX_NAME_PIN_PianYi, buf); absDist, CONFIG_QX_NAME_PIN_PianYi, buf);
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "row %d pin %d OFFSET %.1f px > %.1f px",
static_cast<int>(r), idx, absDist, offsetThY);
} }
// 每个pin脚都打印到拟合直线的距离(mm),方便调整 pin_y_offset 参数
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "row %d pin %d %s line %.2f mm", static_cast<int>(r), idx, bOffset ? "OFFSET" : "OK", absDist);
} }
} }
@ -2984,16 +2887,11 @@ int ImgCheckAnalysisy::Pin_Direct_Check()
{ {
int ia = tOrder[i].second; int ia = tOrder[i].second;
int ib = tOrder[i + 1].second; int ib = tOrder[i + 1].second;
// 相邻pin中心连线距离(px)的分量分别换算为 mm 后求模 float gap = static_cast<float>(cv::norm(rowPts[ib] - rowPts[ia]));
cv::Point2f dv = rowPts[ib] - rowPts[ia]; if (gap > offsetThX)
float dx_mm = dv.x * m_fImgage_Scale_X;
float dy_mm = dv.y * m_fImgage_Scale_Y;
float gap = std::sqrt(dx_mm * dx_mm + dy_mm * dy_mm);
int idxa = rowIdx[ia];
int idxb = rowIdx[ib];
bool bMiss = (gap > offsetThX);
if (bMiss)
{ {
int idxa = rowIdx[ia];
int idxb = rowIdx[ib];
cv::Point2f mid = (rowPts[ia] + rowPts[ib]) * 0.5f; cv::Point2f mid = (rowPts[ia] + rowPts[ib]) * 0.5f;
m_pinMissMarks.push_back(mid); m_pinMissMarks.push_back(mid);
@ -3007,11 +2905,11 @@ int ImgCheckAnalysisy::Pin_Direct_Check()
static_cast<int>(cv::contourArea(m_curPinContours[idxb]))) / 2; static_cast<int>(cv::contourArea(m_curPinContours[idxb]))) / 2;
nMiss++; nMiss++;
char buf[96]; char buf[96];
snprintf(buf, sizeof(buf), "miss between pin %d-%d gap %.2f mm > %.2f mm", idxa, idxb, gap, offsetThX); snprintf(buf, sizeof(buf), "miss between pin %d-%d gap %.1f px > %.1f px", idxa, idxb, gap, offsetThX);
reportPinError(roiMiss, areaAvg, gap, CONFIG_QX_NAME_PIN_QueShi, buf); reportPinError(roiMiss, areaAvg, gap, CONFIG_QX_NAME_PIN_QueShi, buf);
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "row %d pin %d-%d MISS gap %.1f px > %.1f px",
static_cast<int>(r), idxa, idxb, gap, offsetThX);
} }
// 每对相邻pin都打印间距(mm),方便调整 pin_x_offset 参数
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "row %d pin %d-%d %s gap %.2f mm", static_cast<int>(r), idxa, idxb, bMiss ? "MISS" : "OK", gap);
} }
} }
} }

@ -31,6 +31,12 @@ int Pin_QX_Det::GetPinMask(const cv::Mat &img, DetConfigResult *pDetConfig, Mat
cout << "imgSize: " << img.size() << ", " << "detImgSize: " << detImg.size() << ", " << "szSize: " << sz << endl; cout << "imgSize: " << img.size() << ", " << "detImgSize: " << detImg.size() << ", " << "szSize: " << sz << endl;
cv::resize(img, detImg, sz); cv::resize(img, detImg, sz);
// 存图用: 通道转换(灰度化)之前的模型输入图(未开启存图时不克隆, 节省时间/内存)
cv::Mat saveInImg;
if (pDetConfig->pBaseCheckFunction != NULL && pDetConfig->pBaseCheckFunction->saveImg.bSavePinImg)
{
saveInImg = detImg.clone();
}
int re = 0; int re = 0;
if (detImg.channels() != 1) if (detImg.channels() != 1)
{ {
@ -49,18 +55,20 @@ int Pin_QX_Det::GetPinMask(const cv::Mat &img, DetConfigResult *pDetConfig, Mat
// 输入图存未做通道转化(灰度化)之前的图; 文件名首位用产品ID // 输入图存未做通道转化(灰度化)之前的图; 文件名首位用产品ID
if (pDetConfig->pBaseCheckFunction != NULL && pDetConfig->pBaseCheckFunction->saveImg.bSavePinImg) if (pDetConfig->pBaseCheckFunction != NULL && pDetConfig->pBaseCheckFunction->saveImg.bSavePinImg)
{ {
std::string cur_time_s = CheckUtil::getCurTimeHMS(); creatsavedir();
std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + pDetConfig->strProductID + "/"; std::string str = m_strSavePath_Pin + pDetConfig->strProductID + "_" + pDetConfig->strChannel + "_pin_in.png";
CheckUtil::CreateDir(strSaveDir); int sr = m_pImageStorage->addImage(str, saveInImg);
std::string strSavePath_out = strSaveDir + cur_time_s + "_" + pDetConfig->strChannel + "_pin_out.png"; if (sr == 0)
CheckUtil::SaveImgAsync(strSavePath_out, mask); {
str = m_strSavePath_Pin + pDetConfig->strProductID + "_" + pDetConfig->strChannel + "_pin_out_mask.png";
m_pImageStorage->addImage(str, mask, true);
}
} }
if (pDetConfig->bSaveResultImg) if (pDetConfig->bSaveResultImg)
{ {
CheckUtil::SaveImgAsync(pDetConfig->strChannel +"_pin_" +"in.png", detImg); cv::imwrite(pDetConfig->strChannel +"_pin_" +"in.png", detImg);
CheckUtil::SaveImgAsync(pDetConfig->strChannel +"_pin_" +"out_mask.png", mask); cv::imwrite(pDetConfig->strChannel +"_pin_" +"out_mask.png", mask);
} }
return 0; return 0;

@ -44,7 +44,6 @@ enum CONFIG_QX_NAME_
CONFIG_QX_NAME_BTB_DuanLie, // 分类 BTB断裂 CONFIG_QX_NAME_BTB_DuanLie, // 分类 BTB断裂
CONFIG_QX_NAME_LianXi, // 分类 连锡 CONFIG_QX_NAME_LianXi, // 分类 连锡
CONFIG_QX_NAME_QueXi, // 分类 缺锡 CONFIG_QX_NAME_QueXi, // 分类 缺锡
CONFIG_QX_NAME_HunLiao, // 分类 混料
CONFIG_QX_NAME_YiWu, // 分类 异物 CONFIG_QX_NAME_YiWu, // 分类 异物
CONFIG_QX_NAME_C_BianXing, // 分类 C型卡口异常 CONFIG_QX_NAME_C_BianXing, // 分类 C型卡口异常
CONFIG_QX_NAME_PIN_PianYi, // 分类 PIN偏移 CONFIG_QX_NAME_PIN_PianYi, // 分类 PIN偏移
@ -57,7 +56,6 @@ static std::vector<std::string> CONFIG_QX_NAME_Names =
"PIN_PoSun", "PIN_PoSun",
"PIN_QueShi", "PIN_QueShi",
"BTB_DuanLie", "BTB_DuanLie",
"QueXi",
"LianXi", "LianXi",
"HunLiao", "HunLiao",
"YiWu", "YiWu",
@ -1021,10 +1019,6 @@ struct Base_Function_ALign
float angle_diff; float angle_diff;
float x_offset; float x_offset;
float y_offset; float y_offset;
float len;
float width;
float len_diff;
float width_diff;
Base_Function_ALign() Base_Function_ALign()
@ -1037,10 +1031,6 @@ struct Base_Function_ALign
angle_diff = 0.0; angle_diff = 0.0;
x_offset = 0.0; x_offset = 0.0;
y_offset = 0.0; y_offset = 0.0;
len = 0.0;
width = 0.0;
len_diff = 0.0;
width_diff = 0.0;
} }
void copy(Base_Function_ALign tem) void copy(Base_Function_ALign tem)
@ -1049,21 +1039,17 @@ struct Base_Function_ALign
this->angle_diff = tem.angle_diff; this->angle_diff = tem.angle_diff;
this->x_offset = tem.x_offset; this->x_offset = tem.x_offset;
this->y_offset = tem.y_offset; this->y_offset = tem.y_offset;
this->len = tem.len;
this->width = tem.width;
this->len_diff = tem.len_diff;
this->width_diff = tem.width_diff;
} }
void print(std::string str) void print(std::string str)
{ {
printf("%s>>bOpen %d angle_diff %f x_offset %f y_offset %f len %f width %f len_diff %f width_diff %f\n", str.c_str(), printf("%s>>bOpen %d angle_diff %f x_offset %f y_offset %f \n", str.c_str(),
bOpen, angle_diff, x_offset, y_offset, len, width, len_diff, width_diff); bOpen, angle_diff, x_offset, y_offset);
} }
std::string GetInfo(std::string str) std::string GetInfo(std::string str)
{ {
char buffer[256]; char buffer[256];
sprintf(buffer, "%s>>bOpen %d angle_diff %f x_offset %f y_offset %f len %f width %f len_diff %f width_diff %f\n", str.c_str(), sprintf(buffer, "%s>>bOpen %d angle_diff %f x_offset %f y_offset %f \n", str.c_str(),
bOpen, angle_diff, x_offset, y_offset, len, width, len_diff, width_diff); bOpen, angle_diff, x_offset, y_offset);
std::string str123 = buffer; std::string str123 = buffer;
return str123; return str123;
} }

@ -790,23 +790,6 @@ int BaseFuntonConfigJson::GetFunction(Json::Value value)
{ {
_config.ALign_Config.y_offset = value_f["form"]["ALign_Config"]["y_offset"].asFloat(); _config.ALign_Config.y_offset = value_f["form"]["ALign_Config"]["y_offset"].asFloat();
} }
if (value_f["form"]["ALign_Config"]["len"])
{
_config.ALign_Config.len = value_f["form"]["ALign_Config"]["len"].asFloat();
}
if (value_f["form"]["ALign_Config"]["width"])
{
_config.ALign_Config.width = value_f["form"]["ALign_Config"]["width"].asFloat();
}
if (value_f["form"]["ALign_Config"]["len_diff"])
{
_config.ALign_Config.len_diff = value_f["form"]["ALign_Config"]["len_diff"].asFloat();
}
if (value_f["form"]["ALign_Config"]["width_diff"])
{
_config.ALign_Config.width_diff = value_f["form"]["ALign_Config"]["width_diff"].asFloat();
}
} }
else else
{ {

@ -5,7 +5,6 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include <string> #include <string>
#include <algorithm>
#include "CheckUtil.hpp" #include "CheckUtil.hpp"
std::string ExtractFileNameWithoutExtension(const std::string &strImgPath) std::string ExtractFileNameWithoutExtension(const std::string &strImgPath)
{ {
@ -823,8 +822,6 @@ void deal::ResultThread(int id)
{ {
strlog += "/result"; strlog += "/result";
} }
// 确保日志目录存在(否则 writeLog 报 Failed to open file)
_sysmkdirs_1(strlog.substr(0, strlog.find_last_of('/')));
writeLog(strlog, m_DetResult.strlist); writeLog(strlog, m_DetResult.strlist);
mutex_DetResult_.unlock(); mutex_DetResult_.unlock();
@ -914,30 +911,6 @@ bool deal::ReadSystemConfig(const std::string &strPath)
} }
int deal::GetDetImageInfo(std::string strProductID, std::string strSearchImg, std::vector<JC_IMAGE_INFO_> &jcImageInfoList) int deal::GetDetImageInfo(std::string strProductID, std::string strSearchImg, std::vector<JC_IMAGE_INFO_> &jcImageInfoList)
{ {
// -f 批量跑新格式: 产品目录下 input 子目录, 只取 _src 原图
// (如 1M10121554R2B3_buckle_down2_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_SOCKET_SRC(srcImgPaths.at(idx), &tem);
if (re != 0)
{
continue;
}
if (tem.strProductID == "")
{
tem.strProductID = strProductID;
}
jcImageInfoList.push_back(tem);
}
return 0;
}
// 1、获取图片路径 // 1、获取图片路径
std::vector<cv::String> img_paths; std::vector<cv::String> img_paths;
std::string strs1 = strSearchImg + "/*.jpg"; std::string strs1 = strSearchImg + "/*.jpg";
@ -1035,56 +1008,6 @@ int deal::GetImgInfo_POL_ET_PNG(std::string strImgPath, JC_IMAGE_INFO_ *pImageIn
} }
return 0; return 0;
} }
// -f 批量跑新格式: 解析图片名组装产品信息
// 图片名示例: 1M10121554R2B3_buckle_down2_src.jpg
// 产品ID: 第一个 '_' 之前的部分 (1M10121554R2B3)
// 相机名/通道名: 只处理文件名包含 buckle 且后缀为 _src 的原图
int deal::GetImgInfo_SOCKET_SRC(std::string strImgPath, JC_IMAGE_INFO_ *pImageInfo)
{
if (pImageInfo == NULL)
{
return 1;
}
std::string strName = ExtractFileNameWithoutExtension(strImgPath);
// 只处理文件名包含 buckle 的图片, 跳过 t_face_bright/t_face_dark 等其它相机
if (strName.find("buckle") == std::string::npos)
{
printf("GetImgInfo_SOCKET_SRC: no buckle in name, skip %s\n", strImgPath.c_str());
return 1;
}
// 只处理后缀为 _src 的原图, 跳过 _roi 等其它图
const std::string strSrcSuffix = "_src";
if (strName.size() <= strSrcSuffix.size() ||
strName.compare(strName.size() - strSrcSuffix.size(), strSrcSuffix.size(), strSrcSuffix) != 0)
{
return 1;
}
// 相机名同时作为通道名
std::string strCamName = "buckle";
// 产品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 = strCamName;
pImageInfo->strName = strName;
pImageInfo->strPath = strImgPath;
printf("strImgPath %s strName %s strProductID %s strCam %s\n",
strImgPath.c_str(), strName.c_str(), strProductID.c_str(), strCamName.c_str());
return 0;
}
int deal::InsertDetResult(ReadImgInfo tem) int deal::InsertDetResult(ReadImgInfo tem)
{ {
std::lock_guard<std::mutex> lock(mtx_List[Mutex_Type_DetResult]); std::lock_guard<std::mutex> lock(mtx_List[Mutex_Type_DetResult]);
@ -1281,36 +1204,6 @@ int deal::DetImg()
// getchar(); // getchar();
m_nSystemCheckType = CHECK_TYPE_OFFLING; m_nSystemCheckType = CHECK_TYPE_OFFLING;
StartThread(THREAD_RUN_ALL); 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; return 0;
} }
int deal::ReJson_product(std::vector<Json_Det_Path> &jsonList, std::string strProductName) int deal::ReJson_product(std::vector<Json_Det_Path> &jsonList, std::string strProductName)
@ -1660,55 +1553,6 @@ int deal::LoadProductID(std::string strImgPath)
m_product_ID_List.erase(m_product_ID_List.begin(), m_product_ID_List.end()); m_product_ID_List.erase(m_product_ID_List.begin(), m_product_ID_List.end());
std::vector<cv::String> img_paths; 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 格式 // tif 格式
{ {
std::string str_bmp = strImgPath + "/*.jpg"; std::string str_bmp = strImgPath + "/*.jpg";
@ -1757,11 +1601,6 @@ 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; return 0;
} }
int deal::DelImg_Cell_ET() int deal::DelImg_Cell_ET()
@ -1812,13 +1651,6 @@ int deal::Det_OneProduct_Cell_ET(std::string strProductName, int Idx)
{ {
std::string strProductID = GetFileName(strProductName); std::string strProductID = GetFileName(strProductName);
// -f 批量跑新格式: 产品目录名为 <产品ID>_<时间戳>(如 1M10121554R2B3_20261012.151453.582176)
// 取第一个 '_' 之前的部分作为产品ID; 旧格式目录名本身即产品ID(无 '_')不受影响
size_t nProductIDPos = strProductID.find('_');
if (nProductIDPos != std::string::npos)
{
strProductID = strProductID.substr(0, nProductIDPos);
}
// 获取图片路径 // 获取图片路径
std::vector<JC_IMAGE_INFO_> detImgInfoList; std::vector<JC_IMAGE_INFO_> detImgInfoList;

@ -551,9 +551,6 @@ private:
int GetImgInfo_POL_ET_PNG(std::string strImgPath, JC_IMAGE_INFO_ *pImageInfo); int GetImgInfo_POL_ET_PNG(std::string strImgPath, JC_IMAGE_INFO_ *pImageInfo);
// -f 批量跑新格式: 解析 <产品ID>_<相机名>_<位置>_src.jpg 图片名组装产品信息(相机名同时作为通道名)
int GetImgInfo_SOCKET_SRC(std::string strImgPath, JC_IMAGE_INFO_ *pImageInfo);
// 功能测试 // 功能测试
int Det_Funtion_Test(); int Det_Funtion_Test();
// 边缘测试 // 边缘测试

@ -86,7 +86,6 @@ int main(int argc, char *argv[])
cout << "*** 6、./test_JBL_Check -rjson filepath: 单张复测,filepath xx/xx/3A3K380001B1DK_20240408_152157_Main_0_2_L255" << 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 << "*** 6、./test_JBL_Check -rjsonall filepath: 一套图复测,filepath xx/xx/" << endl;
cout << "*** 7、./test_SOCKET -8: 循环执行程序(常驻),不加 -8 只执行一次" << endl; cout << "*** 7、./test_SOCKET -8: 循环执行程序(常驻),不加 -8 只执行一次" << endl;
cout << "*** 8、./test_SOCKET -f filepath: 批量执行 filepath 下的套图(新格式: <产品ID>_<时间戳>/input/<产品ID>_<相机名>_xxx_src.jpg)" << endl;
cout << "******************************************************" << endl; cout << "******************************************************" << endl;
return 0; return 0;

Loading…
Cancel
Save