Compare commits

...

11 Commits

@ -175,17 +175,24 @@ cv::Mat AIModel_Impl::InitMat(int channel, int w, int h)
{
if (w <= 0 || h <= 0 || channel <= 0 || channel > 3)
{
printf("InitMat: invalid params channel=%d w=%d h=%d\n", channel, w, h);
return cv::Mat();
}
cv::Mat dst;
if (channel == 1)
{
dst = cv::Mat(h, w, CV_8UC1, cv::Scalar(0));
dst = cv::Mat::zeros(h, w, CV_8UC1);
}
else
{
dst = cv::Mat(h, w, CV_8UC3, cv::Scalar(0, 0, 0));
dst = cv::Mat::zeros(h, w, CV_8UC3);
}
if (dst.empty() || dst.data == NULL)
{
printf("InitMat: cv::Mat::zeros failed, channel=%d w=%d h=%d\n", channel, w, h);
return cv::Mat();
}
return dst;
}

@ -46,6 +46,7 @@ ${PROJECT_SOURCE_DIR}/ConfigModule/include
${PROJECT_SOURCE_DIR}/Common/include
${PROJECT_SOURCE_DIR}/AIEngineModule/include
${PROJECT_SOURCE_DIR}/AIEngineModule/include_base
${PROJECT_SOURCE_DIR}/TcsCheckModule/include
)
link_directories(
/usr/local/lib/
@ -67,6 +68,7 @@ add_library(TY_Check SHARED ${SRC_LISTS})
target_link_libraries(TY_Check
nvinfer
Config
TcsCheck
${OpenCV_LIBS}
${CUDA_LIBRARIES}
)

@ -173,6 +173,7 @@ public:
std::vector<cv::Point> Det_region;
bool bSaveResultImg;
std::shared_ptr<DetLog> detlog;
DetConfigResult()
{
Init();
@ -186,6 +187,7 @@ public:
alginResult.Init();
Det_region.clear();
bSaveResultImg = false;
detlog = nullptr;
}
};

@ -36,6 +36,7 @@
#include "AI_Factory.h"
#include "ImageAllResult.h"
#include "Task.h"
#include "TcsCheck.h"
using namespace std;
using namespace cv;
@ -263,6 +264,9 @@ private:
Edge_QX_Det::DetConfigResult m_Edge_DetConfig;
std::vector<QXImageResult> m_Draw_qxImageResult; // 缺陷小图结果
// 传统检测模块
CTcsCheck m_tcsCheck;
std::vector<cv::Rect> SmallRoiList;
std::string m_strRootPath_TA_cls;
@ -287,6 +291,7 @@ private:
Rect m_old_productROI = Rect(0, 0, 0, 0);
std::vector<cv::Point> m_old_cur_edgeDet_region;
std::vector<cv::Point> m_old_cur_markLine_region;
std::vector<cv::Point> m_old_cur_traditional_region;
cv::Point m_old_cur_markLine_mark1;
cv::Point m_old_cur_markLine_mark2;
std::vector<RegionConfigST> m_old_cur_regionConfigArr;

@ -105,7 +105,7 @@ string JudgeMaterialPosition(Mat img) {
bool ifExchange(cv::Mat &img, const cv::Mat &img_B, std::string strcam ){
string img_position = JudgeMaterialPosition(img);
string img_B_position = JudgeMaterialPosition(img_B);
if (strcam == "TA"){
if (strcam.find("TA") != std::string::npos){
if (img_position == "Left" && img_B_position == "Right"){
return true;
}
@ -163,6 +163,9 @@ int CameraCheckAnalysisy::Detect_Pre()
int re = Mergimg(pImageResult->result->in_shareImage->img, pImageResult->result->in_shareImage->img_B,
pImageResult->result->in_shareImage->strCameraName, pImageResult->result->in_shareImage->strChannel);
// 拼接完成后 img_B 不再需要,立即释放内存
pImageResult->result->in_shareImage->img_B.release();
if (pImageResult->result->in_shareImage->Det_Mode == DET_MODE_MergeImg)
{
cv::imwrite(pImageResult->result->in_shareImage->strCameraName + "_MergeImg.png", pImageResult->result->in_shareImage->img);
@ -188,7 +191,7 @@ int CameraCheckAnalysisy::Mergimg(cv::Mat &img, const cv::Mat &img_B, std::strin
cv::Mat AllImg = cv::Mat::zeros(img.rows, img.cols * 2, img.type());
// cv::imwrite(strcam + strchannel + "_img.png", img);
// cv::imwrite(strcam + strchannel + "img_B.png", img_B);
if (strcam == "TA")
if (strcam.find("TA") != std::string::npos)
{
cv::Rect Left_Tem_Roi;
cv::Point Left_Tem_Point_Up;

@ -31,7 +31,9 @@ float pointToLineDistance(const cv::Point &pt, const cv::Vec4f &line)
float x = pt.x, y = pt.y;
return std::abs(vy * x - vx * y + (vx * y0 - vy * x0)) / std::sqrt(vx * vx + vy * vy);
}
bool FitLineWithOutlierRemoval(const std::vector<cv::Point> &inputPoints, cv::Vec4f &outputLine, int ransacIters = 100, float inlierThresh = 10)
bool FitLineWithOutlierRemoval(const std::vector<cv::Point> &inputPoints, cv::Vec4f &outputLine,
int ransacIters = 100, float inlierThresh = 10,
std::vector<cv::Point> *outInliers = nullptr)
{
if (inputPoints.size() < 2)
return false;
@ -80,10 +82,62 @@ bool FitLineWithOutlierRemoval(const std::vector<cv::Point> &inputPoints, cv::Ve
if (bestInlierPoints.size() < 2)
return false;
// 输出 inlier 点集,供后续曲线拟合使用
if (outInliers)
*outInliers = bestInlierPoints;
cv::fitLine(bestInlierPoints, outputLine, cv::DIST_L2, 0, 0.01, 0.01);
return true;
}
// 二次曲线拟合(最小二乘法)
// isHorizontal=true: 拟合 y = a*x² + b*x + ccoeffs = [a, b, c]
// isHorizontal=false: 拟合 x = a*y² + b*y + ccoeffs = [a, b, c]
bool FitQuadraticCurve(const std::vector<cv::Point> &points, cv::Vec3f &coeffs, bool isHorizontal)
{
if (points.size() < 3)
return false;
int n = (int)points.size();
cv::Mat A(n, 3, CV_64F);
cv::Mat B(n, 1, CV_64F);
for (int i = 0; i < n; i++)
{
double t = isHorizontal ? points[i].x : points[i].y;
double v = isHorizontal ? points[i].y : points[i].x;
A.at<double>(i, 0) = t * t;
A.at<double>(i, 1) = t;
A.at<double>(i, 2) = 1.0;
B.at<double>(i, 0) = v;
}
cv::Mat X;
if (!cv::solve(A, B, X, cv::DECOMP_SVD))
return false;
coeffs[0] = (float)X.at<double>(0, 0);
coeffs[1] = (float)X.at<double>(1, 0);
coeffs[2] = (float)X.at<double>(2, 0);
return true;
}
// 将点投影到二次曲线上(垂直投影:保持 x 不变求 y或保持 y 不变求 x
cv::Point ProjectPointToCurve(const cv::Point &pt, const cv::Vec3f &coeffs, bool isHorizontal)
{
float a = coeffs[0], b = coeffs[1], c = coeffs[2];
if (isHorizontal)
{
float y = a * pt.x * pt.x + b * pt.x + c;
return cv::Point(pt.x, cvRound(y));
}
else
{
float x = a * pt.y * pt.y + b * pt.y + c;
return cv::Point(cvRound(x), pt.y);
}
}
void drawFittedLine(cv::Mat &image, const cv::Vec4f &line, const cv::Scalar &color, int thickness = 2)
{
double scale = std::max(image.cols, image.rows) * 2.0;
@ -312,8 +366,130 @@ int Edge_QX_Det::Detect(const cv::Mat &img, DetConfigResult *pDetConfig)
else
return 1;
// 用RANSAC剔除崩边凹陷的异常点后拟合二次曲线使ROI贴合产品边缘的自然弧度
// 水平边缘(上下): 拟合 y = a*x² + b*x + c
// 垂直边缘(左右): 拟合 x = a*y² + b*y + c
// 若曲线拟合失败(点数不足), 则回退到直线投影
// 上边缘RANSAC获取inlier → 二次曲线拟合 → 投影到曲线
{
cv::Vec4f fittedLine_up;
std::vector<cv::Point> inliers_up;
cv::Vec3f curveCoeffs_up;
bool useCurve = false;
if (FitLineWithOutlierRemoval(up_edge, fittedLine_up, 100, 15, &inliers_up))
{
useCurve = FitQuadraticCurve(inliers_up, curveCoeffs_up, true);
}
for (auto& line : Up_line)
{
if (useCurve)
{
line.p1 = ProjectPointToCurve(line.p1, curveCoeffs_up, true);
line.p2 = ProjectPointToCurve(line.p2, curveCoeffs_up, true);
}
else
{
float vx = fittedLine_up[0], vy = fittedLine_up[1], x0 = fittedLine_up[2], y0 = fittedLine_up[3];
float denom = vx * vx + vy * vy;
float t1 = ((line.p1.x - x0) * vx + (line.p1.y - y0) * vy) / denom;
float t2 = ((line.p2.x - x0) * vx + (line.p2.y - y0) * vy) / denom;
line.p1 = cv::Point(cvRound(x0 + t1 * vx), cvRound(y0 + t1 * vy));
line.p2 = cv::Point(cvRound(x0 + t2 * vx), cvRound(y0 + t2 * vy));
}
}
}
// 下边缘
{
cv::Vec4f fittedLine_down;
std::vector<cv::Point> inliers_down;
cv::Vec3f curveCoeffs_down;
bool useCurve = false;
if (FitLineWithOutlierRemoval(down_edge, fittedLine_down, 100, 15, &inliers_down))
{
useCurve = FitQuadraticCurve(inliers_down, curveCoeffs_down, true);
}
for (auto& line : down_line)
{
if (useCurve)
{
line.p1 = ProjectPointToCurve(line.p1, curveCoeffs_down, true);
line.p2 = ProjectPointToCurve(line.p2, curveCoeffs_down, true);
}
else
{
float vx = fittedLine_down[0], vy = fittedLine_down[1], x0 = fittedLine_down[2], y0 = fittedLine_down[3];
float denom = vx * vx + vy * vy;
float t1 = ((line.p1.x - x0) * vx + (line.p1.y - y0) * vy) / denom;
float t2 = ((line.p2.x - x0) * vx + (line.p2.y - y0) * vy) / denom;
line.p1 = cv::Point(cvRound(x0 + t1 * vx), cvRound(y0 + t1 * vy));
line.p2 = cv::Point(cvRound(x0 + t2 * vx), cvRound(y0 + t2 * vy));
}
}
}
// 左边缘
{
cv::Vec4f fittedLine_left;
std::vector<cv::Point> inliers_left;
cv::Vec3f curveCoeffs_left;
bool useCurve = false;
if (FitLineWithOutlierRemoval(left_edge, fittedLine_left, 100, 15, &inliers_left))
{
useCurve = FitQuadraticCurve(inliers_left, curveCoeffs_left, false);
}
for (auto& line : left_line)
{
if (useCurve)
{
line.p1 = ProjectPointToCurve(line.p1, curveCoeffs_left, false);
line.p2 = ProjectPointToCurve(line.p2, curveCoeffs_left, false);
}
else
{
float vx = fittedLine_left[0], vy = fittedLine_left[1], x0 = fittedLine_left[2], y0 = fittedLine_left[3];
float denom = vx * vx + vy * vy;
float t1 = ((line.p1.x - x0) * vx + (line.p1.y - y0) * vy) / denom;
float t2 = ((line.p2.x - x0) * vx + (line.p2.y - y0) * vy) / denom;
line.p1 = cv::Point(cvRound(x0 + t1 * vx), cvRound(y0 + t1 * vy));
line.p2 = cv::Point(cvRound(x0 + t2 * vx), cvRound(y0 + t2 * vy));
}
}
}
// 右边缘
{
cv::Vec4f fittedLine_right;
std::vector<cv::Point> inliers_right;
cv::Vec3f curveCoeffs_right;
bool useCurve = false;
if (FitLineWithOutlierRemoval(right_edge, fittedLine_right, 100, 15, &inliers_right))
{
useCurve = FitQuadraticCurve(inliers_right, curveCoeffs_right, false);
}
for (auto& line : right_line)
{
if (useCurve)
{
line.p1 = ProjectPointToCurve(line.p1, curveCoeffs_right, false);
line.p2 = ProjectPointToCurve(line.p2, curveCoeffs_right, false);
}
else
{
float vx = fittedLine_right[0], vy = fittedLine_right[1], x0 = fittedLine_right[2], y0 = fittedLine_right[3];
float denom = vx * vx + vy * vy;
float t1 = ((line.p1.x - x0) * vx + (line.p1.y - y0) * vy) / denom;
float t2 = ((line.p2.x - x0) * vx + (line.p2.y - y0) * vy) / denom;
line.p1 = cv::Point(cvRound(x0 + t1 * vx), cvRound(y0 + t1 * vy));
line.p2 = cv::Point(cvRound(x0 + t2 * vx), cvRound(y0 + t2 * vy));
}
}
}
// 生成 检测的 roi。
int roi_wh = pDetConfig->pBaseCheckFunction->edgeDet.Det_Range;
cv::Rect imgBounds(0, 0, img.cols, img.rows); // 图像边界,用于裁剪越界 ROI
std::vector<Det_ROI_Config> up_det_roi;
std::vector<Det_ROI_Config> down_det_roi;
@ -327,7 +503,8 @@ int Edge_QX_Det::Detect(const cv::Mat &img, DetConfigResult *pDetConfig)
tem.plist.push_back(cv::Point(line.p2.x, line.p2.y + roi_wh));
tem.plist.push_back(cv::Point(line.p1.x, line.p1.y + roi_wh));
tem.roi = cv::boundingRect(tem.plist);
tem.roi = cv::boundingRect(tem.plist) & imgBounds;
if (tem.roi.width <= 0 || tem.roi.height <= 0) continue;
pDetConfig->edge_det_roi.push_back(tem);
up_det_roi.push_back(tem);
}
@ -339,7 +516,8 @@ int Edge_QX_Det::Detect(const cv::Mat &img, DetConfigResult *pDetConfig)
tem.plist.push_back(cv::Point(line.p2.x, line.p2.y - roi_wh));
tem.plist.push_back(cv::Point(line.p1.x, line.p1.y - roi_wh));
tem.roi = cv::boundingRect(tem.plist);
tem.roi = cv::boundingRect(tem.plist) & imgBounds;
if (tem.roi.width <= 0 || tem.roi.height <= 0) continue;
pDetConfig->edge_det_roi.push_back(tem);
down_det_roi.push_back(tem);
}
@ -351,7 +529,8 @@ int Edge_QX_Det::Detect(const cv::Mat &img, DetConfigResult *pDetConfig)
tem.plist.push_back(cv::Point(line.p2.x + roi_wh, line.p2.y));
tem.plist.push_back(cv::Point(line.p1.x + roi_wh, line.p1.y));
tem.roi = cv::boundingRect(tem.plist);
tem.roi = cv::boundingRect(tem.plist) & imgBounds;
if (tem.roi.width <= 0 || tem.roi.height <= 0) continue;
pDetConfig->edge_det_roi.push_back(tem);
left_det_roi.push_back(tem);
}
@ -363,7 +542,8 @@ int Edge_QX_Det::Detect(const cv::Mat &img, DetConfigResult *pDetConfig)
tem.plist.push_back(cv::Point(line.p2.x - roi_wh, line.p2.y));
tem.plist.push_back(cv::Point(line.p1.x - roi_wh, line.p1.y));
tem.roi = cv::boundingRect(tem.plist);
tem.roi = cv::boundingRect(tem.plist) & imgBounds;
if (tem.roi.width <= 0 || tem.roi.height <= 0) continue;
pDetConfig->edge_det_roi.push_back(tem);
right_det_roi.push_back(tem);
}
@ -563,11 +743,6 @@ int Edge_QX_Det::GetEdgePoint(const cv::Mat &img, Edge_Search_Config *pEdge_Sear
// 对每个搜索点进行 y方向搜索
for (int y = sy;; y = y + nCurPoint_Step)
{
if (y < 0 || y >= img.rows)
{
continue;
}
//
if (nCurPoint_Step > 0 && y > ey)
{
@ -577,7 +752,10 @@ int Edge_QX_Det::GetEdgePoint(const cv::Mat &img, Edge_Search_Config *pEdge_Sear
{
break;
}
offt = y * img.cols;
if (y < 0 || y >= img.rows)
{
continue;
}
int range_okNum = 0;
// 对一定范围的点进行判断
for (int k = nrange_start; k < nrange_end; k++)
@ -587,13 +765,13 @@ int Edge_QX_Det::GetEdgePoint(const cv::Mat &img, Edge_Search_Config *pEdge_Sear
{
continue;
}
offt += rangeX;
if (offt < 0 || offt >= img.cols * img.rows)
int cur_offt = y * img.cols + rangeX;
if (cur_offt < 0 || cur_offt >= img.cols * img.rows)
{
printf("rangeX %d nCurPoint_Step %d off %d x %d y %d sy %d ey %d %d %d\n", rangeX, nCurPoint_Step, offt, x, y, sy, ey, img.cols, img.rows);
continue;
}
if (pdata[offt] >= pEdge_Search_Config->nValueThreshold) // 找到
if (pdata[cur_offt] >= pEdge_Search_Config->nValueThreshold) // 找到
{
range_okNum++;
}
@ -648,13 +826,9 @@ int Edge_QX_Det::GetEdgePoint(const cv::Mat &img, Edge_Search_Config *pEdge_Sear
int cur_y = y;
bool bSucc = false;
// 对每个搜索点进行 y方向搜索
// 对每个搜索点进行 x方向搜索
for (int x = sx;; x = x + nCurPoint_Step)
{
if (x < 0 || x >= img.cols)
{
continue;
}
//
if (nCurPoint_Step > 0 && x > ex)
{
@ -664,6 +838,10 @@ int Edge_QX_Det::GetEdgePoint(const cv::Mat &img, Edge_Search_Config *pEdge_Sear
{
break;
}
if (x < 0 || x >= img.cols)
{
continue;
}
int range_okNum = 0;
// 对一定范围的点进行判断
@ -674,12 +852,12 @@ int Edge_QX_Det::GetEdgePoint(const cv::Mat &img, Edge_Search_Config *pEdge_Sear
{
continue;
}
offt = rangey * img.cols + x;
if (offt < 0 || offt >= img.cols * img.rows)
int cur_offt = rangey * img.cols + x;
if (cur_offt < 0 || cur_offt >= img.cols * img.rows)
{
printf("rangey %d off %d x %d y %d ey %d %d %d\n", rangey, offt, x, y, ey, img.cols, img.rows);
continue;
}
if (pdata[offt] >= pEdge_Search_Config->nValueThreshold) // 找到
if (pdata[cur_offt] >= pEdge_Search_Config->nValueThreshold) // 找到
{
range_okNum++;
}
@ -933,12 +1111,14 @@ int Edge_QX_Det::Det_qx(const cv::Mat &img, std::vector<Det_ROI_Config> roilist,
// 进行开操作(先腐蚀后膨胀)可以去除小白点
cv::morphologyEx(roiMask, roiMask, cv::MORPH_OPEN, kernel);
// getchar();
// cv::imwrite("detimg.png", img(DetRoi));
// cv::imwrite("detimg_mask.png", roiMask);
// getchar();
bool jiao_f_1 = false;
bool jiao_f_2 = false;
string jiao_str_1;
string jiao_str_2;
cv::Point jiao_p_1;
cv::Point jiao_p_2;
if (type == Det_ROI_Type_UP)
@ -947,6 +1127,8 @@ int Edge_QX_Det::Det_qx(const cv::Mat &img, std::vector<Det_ROI_Config> roilist,
jiao_f_2 = pDetConfig->pBaseCheckFunction->edgeDet.queJiao_RU_Open;
jiao_p_1 = cv::Point(0, 0);
jiao_p_2 = cv::Point(roiMask.cols, 0);
jiao_str_1 = "queJiao_LU";
jiao_str_2 = "queJiao_RU";
}
else if (type == Det_ROI_Type_DOWN)
{
@ -954,49 +1136,105 @@ int Edge_QX_Det::Det_qx(const cv::Mat &img, std::vector<Det_ROI_Config> roilist,
jiao_f_2 = pDetConfig->pBaseCheckFunction->edgeDet.queJiao_RD_Open;
jiao_p_1 = cv::Point(0, roiMask.rows);
jiao_p_2 = cv::Point(roiMask.cols, roiMask.rows);
jiao_str_1 = "queJiao_LD";
jiao_str_2 = "queJiao_RD";
}
else if (type == Det_ROI_Type_LEFT)
{
jiao_f_1 = pDetConfig->pBaseCheckFunction->edgeDet.queJiao_LU_Open;
jiao_f_2 = pDetConfig->pBaseCheckFunction->edgeDet.queJiao_LD_Open;
jiao_p_1 = cv::Point(0, 0);
jiao_p_2 = cv::Point(0, roiMask.rows);
jiao_str_1 = "queJiao_LU";
jiao_str_2 = "queJiao_LD";
}
else if (type == Det_ROI_Type_RIGHT)
{
jiao_f_1 = pDetConfig->pBaseCheckFunction->edgeDet.queJiao_RU_Open;
jiao_f_2 = pDetConfig->pBaseCheckFunction->edgeDet.queJiao_RU_Open;
jiao_f_2 = pDetConfig->pBaseCheckFunction->edgeDet.queJiao_RD_Open;
jiao_p_1 = cv::Point(roiMask.cols, 0);
jiao_p_2 = cv::Point(roiMask.cols, roiMask.rows);
jiao_str_1 = "queJiao_RU";
jiao_str_2 = "queJiao_RD";
}
// 寻找轮廓
vector<vector<Point>> contours;
cv::findContours(roiMask, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
// 找到最大面积的轮廓
double maxArea = -1;
int maxAreaIdx = -1;
// 缺角过滤配置日志
if (pDetConfig->detlog)
{
pDetConfig->detlog->AddCheckstr(PrintLevel_1, "EdgeQX_Det", "type=%d contours=%zu %s=%d %s=%d thres(w=%d,h=%d)",
type, contours.size(),
jiao_str_1.c_str(), jiao_f_1,
jiao_str_2.c_str(), jiao_f_2,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_width,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_height);
}
int jiao_filter_count = 0;
for (size_t i = 0; i < contours.size(); ++i)
{
cv::Rect rect = cv::boundingRect(contours[i]);
cv::Point pc(rect.x + rect.width / 2, rect.y + rect.height / 2);
if (jiao_f_1)
{
// printf("1=%d =========== %d %d\n\n", type, abs(pc.x - jiao_p_1.x), abs(pc.y - jiao_p_1.y));
if (abs(pc.x - jiao_p_1.x) < pDetConfig->pBaseCheckFunction->edgeDet.queJiao_width && abs(pc.y - jiao_p_1.y) < pDetConfig->pBaseCheckFunction->edgeDet.queJiao_height)
int dx1 = abs(pc.x - jiao_p_1.x);
int dy1 = abs(pc.y - jiao_p_1.y);
if (dx1 < pDetConfig->pBaseCheckFunction->edgeDet.queJiao_width && dy1 < pDetConfig->pBaseCheckFunction->edgeDet.queJiao_height)
{
if (pDetConfig->detlog)
{
pDetConfig->detlog->AddCheckstr(PrintLevel_2, "EdgeQX_Det", "type=%d %s contour[%zu] center=(%d,%d) coor=(%d,%d) dx=%d dy=%d thres(w=%d,h=%d) --> succ",
type, jiao_str_1.c_str(), i, pc.x, pc.y, jiao_p_1.x, jiao_p_1.y,
dx1, dy1,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_width,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_height);
}
jiao_filter_count++;
continue;
}
else
{
if (pDetConfig->detlog)
{
pDetConfig->detlog->AddCheckstr(PrintLevel_2, "EdgeQX_Det", "type=%d %s contour[%zu] center=(%d,%d) coor=(%d,%d) dx=%d dy=%d thres(w=%d,h=%d) --> fail",
type, jiao_str_1.c_str(), i, pc.x, pc.y, jiao_p_1.x, jiao_p_1.y,
dx1, dy1,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_width,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_height);
}
}
}
if (jiao_f_2)
{
// printf("2=%d============ %d %d\n\n", type, abs(pc.x - jiao_p_2.x), abs(pc.y - jiao_p_2.y));
if (abs(pc.x - jiao_p_2.x) < pDetConfig->pBaseCheckFunction->edgeDet.queJiao_width && abs(pc.y - jiao_p_2.y) < pDetConfig->pBaseCheckFunction->edgeDet.queJiao_height)
int dx2 = abs(pc.x - jiao_p_2.x);
int dy2 = abs(pc.y - jiao_p_2.y);
if (dx2 < pDetConfig->pBaseCheckFunction->edgeDet.queJiao_width && dy2 < pDetConfig->pBaseCheckFunction->edgeDet.queJiao_height)
{
if (pDetConfig->detlog)
{
pDetConfig->detlog->AddCheckstr(PrintLevel_2, "EdgeQX_Det", "type=%d %s contour[%zu] center=(%d,%d) coor=(%d,%d) dx=%d dy=%d thres(w=%d,h=%d) --> succ",
type, jiao_str_2.c_str(), i, pc.x, pc.y, jiao_p_2.x, jiao_p_2.y,
dx2, dy2,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_width,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_height);
}
jiao_filter_count++;
continue;
}
else
{
if (pDetConfig->detlog)
{
pDetConfig->detlog->AddCheckstr(PrintLevel_2, "EdgeQX_Det", "type=%d %s contour[%zu] center=(%d,%d) coor=(%d,%d) dx=%d dy=%d thres(w=%d,h=%d) --> fail",
type, jiao_str_2.c_str(), i, pc.x, pc.y, jiao_p_2.x, jiao_p_2.y,
dx2, dy2,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_width,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_height);
}
}
}
if (rect.width >= pedgeDet->QX_Widht_min && rect.width <= pedgeDet->QX_Widht_max &&
@ -1021,18 +1259,25 @@ int Edge_QX_Det::Det_qx(const cv::Mat &img, std::vector<Det_ROI_Config> roilist,
int Edge_QX_Det::applyMaskInROI(const cv::Mat &grayImg, const Det_ROI_Config &config, cv::Mat &result, int threshold)
{
// 0. 裁剪 ROI防止 RANSAC 投影后越界
cv::Rect safeRoi = config.roi & cv::Rect(0, 0, grayImg.cols, grayImg.rows);
if (safeRoi.width <= 0 || safeRoi.height <= 0)
{
result = cv::Mat();
return -1;
}
// 1. 获取 ROI 区域图像(不 clone只引用
cv::Mat roiGray = grayImg(config.roi);
cv::Mat roiGray = grayImg(safeRoi);
// cv::imwrite("roiGray.png", roiGray);
// 2. 二值化(用 compare 更快)
cv::Mat binary;
cv::compare(roiGray, threshold, binary, cv::CMP_LT); // binary = roiGray > 128 ? 255 : 0
// cv::imwrite("binary.png", binary);
// 3. 构建局部坐标的多边形(避免每次 new
// 3. 构建局部坐标的多边形(相对于裁剪后的 safeRoi
std::vector<cv::Point> localPts;
localPts.reserve(config.plist.size());
for (const auto &pt : config.plist)
localPts.emplace_back(pt.x - config.roi.x, pt.y - config.roi.y);
localPts.emplace_back(pt.x - safeRoi.x, pt.y - safeRoi.y);
// 4. 快速创建 mask 并填充
cv::Mat mask = cv::Mat::zeros(roiGray.size(), CV_8UC1);

@ -181,7 +181,7 @@ int ImgCheckAnalysisy::GetStatus()
std::string ImgCheckAnalysisy::GetVersion()
{
return std::string("BOE_1.7.91");
return std::string("BOE_1.0.0");
}
std::string ImgCheckAnalysisy::GetErrorInfo()
@ -312,6 +312,13 @@ cv::Scalar ImgCheckAnalysisy::calc_blob_info_withstats(cv::Mat &img, const cv::M
// 计算感兴趣区域 (ROI)
cv::Rect roi(x - expand, y - expand, w + 2 * expand, h + 2 * expand);
roi &= cv::Rect(0, 0, img.cols, img.rows); // 确保ROI在图像内
roi &= cv::Rect(0, 0, mask.cols, mask.rows); // 确保ROI在mask内
// 检查 mask 是否有效
if (mask.empty() || roi.width <= 0 || roi.height <= 0)
{
return cv::Scalar(0, 0, 0);
}
cv::Mat cimg = img(roi);
@ -521,6 +528,7 @@ int ImgCheckAnalysisy::Adapt_Config(Mat img, Rect cur_roi, bool b_update){
m_old_cur_markLine_mark1 = m_AnalysisyConfig.baseFunction.markLine.mark_local_1;
m_old_cur_markLine_mark2 = m_AnalysisyConfig.baseFunction.markLine.mark_local_2;
m_old_cur_regionConfigArr = m_AnalysisyConfig.commonCheckConfig.nodeConfigArr[0].regionConfigArr;
m_old_cur_traditional_region = m_pbaseCheckFunction->traditionDet.detArea;
}
Rect old_roi = m_old_productROI;
Point old_center(old_roi.x + old_roi.width / 2, old_roi.y + old_roi.height / 2);
@ -546,6 +554,8 @@ int ImgCheckAnalysisy::Adapt_Config(Mat img, Rect cur_roi, bool b_update){
cv::Point& cur_markLine_mark1 = m_AnalysisyConfig.baseFunction.markLine.mark_local_1;
cv::Point& cur_markLine_mark2 = m_AnalysisyConfig.baseFunction.markLine.mark_local_2;
std::vector<RegionConfigST>& cur_regionConfigArr = m_AnalysisyConfig.commonCheckConfig.nodeConfigArr[0].regionConfigArr;
cv::Rect& cur_traditional_rect = m_pbaseCheckFunction->traditionDet.detArea_ROI;
std::vector<cv::Point>& cur_traditional_region = m_pbaseCheckFunction->traditionDet.detArea;
/*进行修改*/
// rect
@ -575,7 +585,11 @@ int ImgCheckAnalysisy::Adapt_Config(Mat img, Rect cur_roi, bool b_update){
cur_regionConfigArr[i].basicInfo.pointArry[j] = Point((cur_regionConfigArr[i].basicInfo.pointArry[j].x - new_center.x) * scale_x + new_center.x, (cur_regionConfigArr[i].basicInfo.pointArry[j].y - new_center.y) * scale_y + new_center.y);
}
}
for(int i = 0; i < cur_traditional_region.size(); i++){
cur_traditional_region[i].x = m_old_cur_traditional_region[i].x + x_offset;
cur_traditional_region[i].y = m_old_cur_traditional_region[i].y + y_offset;
cur_traditional_region[i] = Point((cur_traditional_region[i].x - new_center.x) * scale_x + new_center.x, (cur_traditional_region[i].y - new_center.y) * scale_y + new_center.y);
}
/*show*/
// Mat show_img = img.clone();
// cv::rectangle(show_img, m_AnalysisyConfig.baseFunction.markLine.productROI, Scalar(255), 5);
@ -634,12 +648,7 @@ int ImgCheckAnalysisy::CheckRun()
m_strCurDetChannel = m_CheckResult_shareP->basicResult.strChannel;
/*自适应更新参数*/
// int64 t000 = cv::getTickCount();
// m_pdetlog->bPrintStr = true;
Adapt_Config(m_CheckResult_shareP->in_shareImage->img, m_CutRoi, m_pbaseCheckFunction->markLine.badapt_region);
// m_pdetlog->bPrintStr = false;
// int64 t111 = cv::getTickCount();
// cout << "-------------------------Adapt_Config------------succ---time ----" << (t111 - t000) * 1000 / cv::getTickFrequency() << "ms" << endl;
// 2、参数检查
int rec = ConfigCheck(DetImgInfo_shareP->img);
@ -852,6 +861,18 @@ int ImgCheckAnalysisy::CalBlob_Other()
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "CalBlob_Other", " Start");
// 检查 detImg 和 AIMaskImg 是否为空
if (m_pImageAllResult->detImg.empty())
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "CalBlob_Other", " detImg is empty, return");
return -1;
}
if (m_pImageAllResult->AIMaskImg.empty())
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "CalBlob_Other", " AIMaskImg is empty, return");
return -1;
}
float fs_x = m_fImgage_Scale_X;
float fs_y = m_fImgage_Scale_Y;
@ -1189,7 +1210,7 @@ int ImgCheckAnalysisy::GetALLBlob()
ERROR_DOTS_BLOBS blobs_big_2;
memset(&blobs_big_2, 0x00, sizeof(ERROR_DOTS_BLOBS));
printf("=====>>>> GetALLBlob m_strCurDetChannel %s \n", m_strCurDetChannel.c_str());
if (m_strCurDetChannel == "CA")
if (m_strCurDetChannel.find("CA") != std::string::npos)
{
printf("=====>>>>GetALLBlob USE CA %s \n", m_strCurDetChannel.c_str());
GetBlobs_ALL_New(&blobs_v1, pGrayErrordata, m_ImgBlobHFlagData, width, height, 15);
@ -1295,7 +1316,7 @@ int ImgCheckAnalysisy::AIMaskDet()
memset(m_ImgBlobHFlagData, 0, sizeof(unsigned char) * m_pImageAllResult->detImg.rows);
// 传统检测路径等待异步任务完成后计算HFlag
if(m_pBasicConfig->bTraditionalDetect)
if(m_pbaseCheckFunction->traditionDet.bOpen)
{
m_AItask->waitComplate();
int rec = m_AItask->nresult;
@ -1483,7 +1504,7 @@ int ImgCheckAnalysisy::AI_Detect_Thread(const cv::Mat &img, cv::Mat &ResultImg)
{
std::shared_ptr<AIModel_Base> pAIDet;
// printf("=====>>>>AI_Detect_Thread m_strCurDetChannel %s \n", m_strCurDetChannel.c_str());
if (m_strCurDetChannel == "CA")
if (m_strCurDetChannel.find("CA") != std::string::npos)
{
// printf("=====>>>>AI_Detect_Thread USE CA %s \n", m_strCurDetChannel.c_str());
pAIDet = AI_Factory->CELL_CA_Det;
@ -1615,18 +1636,46 @@ int ImgCheckAnalysisy::AI_Detect_Thread(const cv::Mat &img, cv::Mat &ResultImg)
int ImgCheckAnalysisy::Traditional_Detect_Thread(const cv::Mat &img, cv::Mat &ResultImg)
{
std::string strBaseLog = "Traditional_Detect";
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "Traditional_Detect Start (placeholder)");
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "Traditional_Detect Start");
// 输出与AI推理相同格式的二值mask图 (CV_8UC1)
ResultImg = cv::Mat::zeros(img.size(), CV_8UC1);
// 输入校验
if (img.empty())
{
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "Traditional_Detect FAILED (empty image)");
ResultImg = cv::Mat();
return -1;
}
Base_Function_TraditionDet traditionParam = m_pbaseCheckFunction->traditionDet; // 首次调用时初始化传统检测参数(从 m_AnalysisyConfig 映射)
{
CHECK_PARAM cp;
cp.nAreaLowFilter = 80;
cp.nBlockSize = traditionParam.nBlockSize;
cp.fZoomRatio = traditionParam.fZoomRatio;
cp.nFilterLow = traditionParam.nFilterLow;
cp.nFilterHigh = traditionParam.nFilterHigh;
cp.nAreaFilter = traditionParam.nAreaFilter;
cp.nCountFilter = traditionParam.nCountFilter;
m_tcsCheck.SetChecConfig(&cp);
}
// ===== TODO: 在此处调用传统检测算法库 =====
// 示例cv::threshold(img, ResultImg, 128, 255, cv::THRESH_BINARY);
// 输入: img (单通道灰度图, CV_8UC1)
// 输出: ResultImg (二值mask, CV_8UC1, 0/255)
// ==========================================
cv::Rect detroi = cv::boundingRect(traditionParam.detArea);
detroi.x -= m_Crop_Roi_paramImg.x;
detroi.y -= m_Crop_Roi_paramImg.y;
detroi = detroi & cv::Rect(0, 0, img.cols, img.rows);
if(!traditionParam.bdetArea)
{
detroi = cv::Rect(0, 0, img.cols, img.rows);
}
// 调用传统检测:输出残点二值图
int ret = m_tcsCheck.TraditionalDetect(img, detroi, ResultImg);
if (ret != 0)
{
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "Traditional_Detect FAILED (no product)");
return -1;
}
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "Traditional_Detect End (placeholder)");
m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, strBaseLog, "Traditional_Detect End");
return 0;
}
@ -1637,7 +1686,7 @@ int ImgCheckAnalysisy::AI_QX_Class_Thread()
std::shared_ptr<AIModel_Base> pAIDet;
// printf("=====>>>>AI_QX_Class_Thread m_strCurDetChannel %s \n", m_strCurDetChannel.c_str());
if (m_strCurDetChannel == "CA")
if (m_strCurDetChannel.find("CA") != std::string::npos)
{
// printf("=====>>>>AI_QX_Class_Thread USE CA %s \n", m_strCurDetChannel.c_str());
pAIDet = AI_Factory->CELL_CA_Cls;
@ -1765,7 +1814,7 @@ int ImgCheckAnalysisy::AI_QX_Class_Thread()
std::string saveimgpaht = "";
if (m_strCurDetChannel == "TA")
if (m_strCurDetChannel.find("TA") != std::string::npos)
{
saveimgpaht = m_strRootPath_TA_cls + std::to_string(cls_num) + "/" + std::to_string(CheckUtil::getcurTime()) + "_" + std::to_string(result->cls_score) + ".png";
}
@ -1798,8 +1847,32 @@ int ImgCheckAnalysisy::AI_QX_Class_Thread()
// ======================== 传统分类 ========================
int ImgCheckAnalysisy::Traditional_QX_Class_Thread()
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Traditional_Class", " Start (placeholder)");
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Traditional_Class", " Start");
// 1. 调用传统分类
if (m_pImageAllResult == nullptr || m_pImageAllResult->AIMaskImg.empty())
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Traditional_Class", " No mask image");
return -1;
}
int nDefectCount = m_tcsCheck.TraditionalClassify(m_pImageAllResult->AIMaskImg);
if (nDefectCount < 0)
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Traditional_Class", " Classify failed");
return -1;
}
// 2. TcsCheck 缺陷类型 → CONFIG_QX_NAME 映射表
static const int TcsDefectToConfigQX[] = {
CONFIG_QX_NAME_cell_other, // DEFECT_TYPE_OK = 0
CONFIG_QX_NAME_cell_dianzhuang, // DEFECT_TYPE_POINT = 1 (硬质颗粒 → 点状)
CONFIG_QX_NAME_cell_line, // DEFECT_TYPE_SCRATCH = 2 (划伤 → 线状)
CONFIG_QX_NAME_cell_zangwu, // DEFECT_TYPE_DIRTY = 3 (脏污)
CONFIG_QX_NAME_cell_danban, // DEFECT_TYPE_FADING_SPOTS = 4 (淡斑)
};
// 3. 将分类结果匹配到 blobs.blobTab基于位置/面积最近邻匹配)
int totalTasks = blobs.blobCount;
for (int i = 0; i < totalTasks; i++)
{
@ -1808,19 +1881,45 @@ int ImgCheckAnalysisy::Traditional_QX_Class_Thread()
if (pblob->ErrType == ERR_TYPE_2)
{
pblob->AIclasstype = CONFIG_QX_NAME_cell_ymhs;
continue;
}
// 在 TcsCheck 结果中找最佳匹配(中心距离最近)
int bestIdx = -1;
int bestDist2 = INT_MAX;
int blobCenterX = (pblob->minx + pblob->maxx) / 2;
int blobCenterY = (pblob->miny + pblob->maxy) / 2;
for (int j = 0; j < nDefectCount; j++)
{
const DEFECT_INFO& info = m_tcsCheck.m_vecDefectInfo[j];
int defCenterX = info.nDefectX + info.nDefectWidth / 2;
int defCenterY = info.nDefectY + info.nDefectHeight / 2;
int dx = blobCenterX - defCenterX;
int dy = blobCenterY - defCenterY;
int dist2 = dx * dx + dy * dy;
// 面积接近的优先(容差 50% 内)
int areaDiff = std::abs(pblob->area - info.nDefectArea);
if (areaDiff < info.nDefectArea / 2 && dist2 < bestDist2)
{
bestDist2 = dist2;
bestIdx = j;
}
}
if (bestIdx >= 0)
{
int defectType = m_tcsCheck.m_vecDefectInfo[bestIdx].nDefectType;
pblob->AIclasstype = TcsDefectToConfigQX[defectType];
}
else
{
// ===== TODO: 在此处调用传统分类算法库 =====
// 输入: pblob->minx/maxy/miny/maxy 定位的blob区域
// m_pImageAllResult->detImg 原始检测图
// 输出: pblob->AIclasstype (CONFIG_QX_NAME_cell_xxx)
// ==========================================
pblob->AIclasstype = CONFIG_QX_NAME_cell_other;
}
}
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Traditional_Class", " End (placeholder), classified %d blobs", totalTasks);
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Traditional_Class", " End, classified %d/%d blobs", nDefectCount, totalTasks);
return 0;
}
@ -1878,7 +1977,7 @@ void ImgCheckAnalysisy::TaskFun_AIDet(std::shared_ptr<TaskInfo> task)
t1 = CheckUtil::getcurTime();
int rec;
if(m_pBasicConfig->bTraditionalDetect)
if(m_pbaseCheckFunction->traditionDet.bOpen)
{
rec = Traditional_Detect_Thread(m_pImageAllResult->detImg, m_pImageAllResult->AIMaskImg);
}
@ -1902,7 +2001,7 @@ void ImgCheckAnalysisy::TaskFun_QxClass(std::shared_ptr<TaskInfo> task)
t1 = CheckUtil::getcurTime();
int rec;
if(m_pBasicConfig->bTraditionalDetect)
if(m_pbaseCheckFunction->traditionDet.bOpen)
{
rec = Traditional_QX_Class_Thread();
}
@ -2165,7 +2264,7 @@ int ImgCheckAnalysisy::Edge_Qx_Det(const cv::Mat &img)
cv::Point pCenter;
pCenter.x = temerror.roi.x + temerror.roi.width * 0.5;
pCenter.y = temerror.roi.y + temerror.roi.height * 0.5;
int nmaxregionIdx = 0;
int nmaxregionIdx = -1;
for (int iregion = 0; iregion < m_DetRoiList.roiList_Src.size(); iregion++)
{
const std::vector<cv::Point> &polygon = m_DetRoiList.roiList_Src[iregion];
@ -2177,7 +2276,10 @@ int ImgCheckAnalysisy::Edge_Qx_Det(const cv::Mat &img)
nmaxregionIdx = iregion;
}
temerror.detRegionidxList.push_back(nmaxregionIdx);
if (nmaxregionIdx >= 0)
{
temerror.detRegionidxList.push_back(nmaxregionIdx);
}
}
// {
@ -2258,7 +2360,7 @@ int ImgCheckAnalysisy::BLobToDetResult()
cv::Point pCenter;
pCenter.x = roi.x + roi.width * 0.5;
pCenter.y = roi.y + roi.height * 0.5;
int nmaxregionIdx = 0;
int nmaxregionIdx = -1;
for (int iregion = 0; iregion < m_DetRoiList.roiList_Src.size(); iregion++)
{
const std::vector<cv::Point> &polygon = m_DetRoiList.roiList_Src[iregion];
@ -2270,7 +2372,10 @@ int ImgCheckAnalysisy::BLobToDetResult()
nmaxregionIdx = iregion;
}
temerror.detRegionidxList.push_back(nmaxregionIdx);
if (nmaxregionIdx >= 0)
{
temerror.detRegionidxList.push_back(nmaxregionIdx);
}
}
// {

@ -19,7 +19,7 @@ message(STATUS "oPENCV Library status:")
message(STATUS ">version:${OpenCV_VERSION}")
message(STATUS "Include:${OpenCV_INCLUDE_DIRS}")
set(CMAKE_BUILD_TYPE "debug")
set(CMAKE_BUILD_TYPE "release")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) # C++17
set(CMAKE_CXX_EXTENSIONS OFF) # GNU
@ -65,6 +65,9 @@ MESSAGE("ExtractImageModule")
add_subdirectory(ConfigModule)
MESSAGE("ConfigModule")
add_subdirectory(TcsCheckModule)
MESSAGE("TcsCheckModule")
# CommonUtil
add_subdirectory(AlgorithmModule)
MESSAGE("AlgorithmModule")

@ -407,7 +407,6 @@ struct BasicConfig
float Product_Size_Height_mm; // 产品尺寸 高度 mm
float fImage_Scale_x; // 成像精度
float fImage_Scale_y; // 成像精度
bool bTraditionalDetect; // 使用传统算法检测
std::string strCamName; //
float density_R_mm; // 密度计算半径 像素
@ -428,7 +427,6 @@ struct BasicConfig
Product_Size_Height_mm = 1000;
fImage_Scale_x = 0.03;
fImage_Scale_y = 0.03;
bTraditionalDetect = false;
density_R_mm = 5;
strCamName = "";
strCamearName = EMPTY_CONFIG_NAME;
@ -452,7 +450,6 @@ struct BasicConfig
this->Product_Size_Height_mm = tem.Product_Size_Height_mm;
this->fImage_Scale_x = tem.fImage_Scale_x;
this->fImage_Scale_y = tem.fImage_Scale_y;
this->bTraditionalDetect = tem.bTraditionalDetect;
this->density_R_mm = tem.density_R_mm;
this->strCamearName = tem.strCamearName;
}
@ -461,7 +458,7 @@ struct BasicConfig
printf("============================↓↓↓↓↓↓%s↓↓ %s ↓↓↓↓↓=========================\n", str.c_str(), strCamearName.c_str());
printf("bCal_ImageScale %d Product_Size_Width =%f Product_Size_Height =%f \n", bCal_ImageScale, Product_Size_Width_mm, Product_Size_Height_mm);
printf("fImage_Scale_x =%f fImage_Scale_y=%f \n", fImage_Scale_x, fImage_Scale_y);
printf("bTraditionalDetect %d density_R_mm=%f \n", bTraditionalDetect, density_R_mm);
printf("density_R_mm=%f \n", density_R_mm);
// printf("height_min =%d height_max=%d \n", height_min, height_max);
printf("bDrawShieldRoi %d bShield_ZF %d DrawPreRoi %d fUP_IOU %f density_R_mm %f\n", bDrawShieldRoi, bShield_ZF, bDrawPreRoi, fUP_IOU, density_R_mm);
printf("============================↑↑↑↑↑↑%s↑↑↑↑↑↑=========================\n", str.c_str());
@ -1782,6 +1779,67 @@ struct Base_Function_SaveImg
}
};
//传统检测
struct Base_Function_TraditionDet
{
bool bOpen; // 是否开启
float nAreaLowFilter; // 定位阈值
int nBlockSize; // 分块大小
float fZoomRatio; // 缩放比例
float nAreaFilter; // 面积过滤
int nCountFilter; // 数量过滤
int nFilterLow; // 低灰度过滤
int nFilterHigh; // 高灰度过滤
cv::Rect detArea_ROI;
std::vector<cv::Point> detArea;
bool bdetArea; // 是否使用区域
Base_Function_TraditionDet()
{
Init();
}
void Init()
{
bOpen = false;
nAreaLowFilter = 0;
nBlockSize = 0;
fZoomRatio = 0;
nAreaFilter = 0;
nCountFilter = 0;
nFilterLow = 0;
nFilterHigh = 0;
bdetArea = false;
detArea_ROI = cv::Rect(0, 0, 0, 0);
detArea.clear();
}
void copy(Base_Function_TraditionDet tem)
{
this->bOpen = tem.bOpen;
this->nAreaLowFilter = tem.nAreaLowFilter;
this->nBlockSize = tem.nBlockSize;
this->fZoomRatio = tem.fZoomRatio;
this->nAreaFilter = tem.nAreaFilter;
this->nCountFilter = tem.nCountFilter;
this->nFilterLow = tem.nFilterLow;
this->nFilterHigh = tem.nFilterHigh;
this->bdetArea = tem.bdetArea;
this->detArea_ROI = tem.detArea_ROI;
this->detArea.assign(tem.detArea.begin(), tem.detArea.end());
}
void print(std::string str)
{
printf("%s>>bOpen %d nAreaLowFilter %f nBlockSize %d fZoomRatio %f nAreaFilter %f nCountFilter %d nFilterLow %d nFilterHigh %d \n", str.c_str(),
bOpen, nAreaLowFilter, nBlockSize, fZoomRatio, nAreaFilter, nCountFilter, nFilterLow, nFilterHigh);
}
std::string GetInfo(std::string str)
{
char buffer[256];
sprintf(buffer, "%s>>bOpen %d nAreaLowFilter %f nBlockSize %d fZoomRatio %f nAreaFilter %f nCountFilter %d nFilterLow %d nFilterHigh %d \n", str.c_str(),
bOpen, nAreaLowFilter, nBlockSize, fZoomRatio, nAreaFilter, nCountFilter, nFilterLow, nFilterHigh);
std::string str123 = buffer;
return str123;
}
};
// 大缺陷 NG
struct Big_NG
{
@ -1970,6 +2028,7 @@ struct BaseCheckFunction
Base_Function_MarkLine markLine;
Base_Function_Edge_Det edgeDet;
Base_Function_SaveImg saveImg;
Base_Function_TraditionDet traditionDet;
Base_Function_BigNG bigNG;
BaseCheckFunction()
{
@ -1980,6 +2039,7 @@ struct BaseCheckFunction
markLine.Init();
edgeDet.Init();
saveImg.Init();
traditionDet.Init();
bigNG.Init();
}
void copy(BaseCheckFunction tem)
@ -1987,6 +2047,7 @@ struct BaseCheckFunction
this->markLine.copy(tem.markLine);
this->edgeDet.copy(tem.edgeDet);
this->saveImg.copy(tem.saveImg);
this->traditionDet.copy(tem.traditionDet);
this->bigNG.copy(tem.bigNG);
}
void print(std::string str)
@ -1995,6 +2056,7 @@ struct BaseCheckFunction
markLine.print("markLine");
edgeDet.print("edgeDet");
saveImg.print("saveImg");
traditionDet.print("traditionDet");
bigNG.print("bigNG");
}
std::string GetInfo(std::string str)
@ -2003,6 +2065,7 @@ struct BaseCheckFunction
str123 += markLine.GetInfo("markLine");
str123 += edgeDet.GetInfo("edgeDet");
str123 += saveImg.GetInfo("saveImg");
str123 += traditionDet.GetInfo("traditionDet");
str123 += bigNG.GetInfo("bigNG");
// str123 += "\n";
return str123;

@ -134,7 +134,8 @@ 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)");
// 匹配 param_XXX.json (e.g. param_BCA.json, param_BTA.json, param_DCA.json, param_DTA.json)
std::regex pattern(R"(param_[A-Za-z0-9]+\.json)");
if (!fs::exists(m_strConfigRootPath))
{
std::cerr << "目录不存在: " << m_strConfigRootPath << std::endl;

@ -57,7 +57,6 @@ void CommonParamToCheckConfigJson::toObjectFromValue(Json::Value root)
_config.baseConfig.Product_Size_Height_mm = value["Product_Size_H"].asFloat();
_config.baseConfig.fImage_Scale_x = value["Image_Scale_X"].asFloat();
_config.baseConfig.fImage_Scale_y = value["Image_Scale_Y"].asFloat();
_config.baseConfig.bTraditionalDetect = value["bTraditionalDetect"].asFloat();
if (value["Density_R"])
{
_config.baseConfig.density_R_mm = value["Density_R"].asFloat();
@ -1309,6 +1308,70 @@ int BaseFuntonConfigJson::GetFunction(Json::Value value)
// _config.edgeDet.print("edgeDet");
// getchar();
}
if ("Tradition_Detect" == strCode)
{
auto value_f = value;
// std::cout << value_f << std::endl;
// getchar();
_config.traditionDet.bOpen = value_f["isOpen"].asBool();
if (_config.traditionDet.bOpen)
{
if (value_f["form"]["Tradition_Param"]["nBlockSize"])
{
_config.traditionDet.nBlockSize = value_f["form"]["Tradition_Param"]["nBlockSize"].asInt();
}
if (value_f["form"]["Tradition_Param"]["fZoomRatio"])
{
_config.traditionDet.fZoomRatio = value_f["form"]["Tradition_Param"]["fZoomRatio"].asFloat();
}
if (value_f["form"]["Tradition_Param"]["nAreaFilter"])
{
_config.traditionDet.nAreaFilter = value_f["form"]["Tradition_Param"]["nAreaFilter"].asFloat();
}
if (value_f["form"]["Tradition_Param"]["nCountFilter"])
{
_config.traditionDet.nCountFilter = value_f["form"]["Tradition_Param"]["nCountFilter"].asInt();
}
if (value_f["form"]["Tradition_Param"]["nFilterLow"])
{
_config.traditionDet.nFilterLow = value_f["form"]["Tradition_Param"]["nFilterLow"].asInt();
}
if (value_f["form"]["Tradition_Param"]["nFilterHigh"])
{
_config.traditionDet.nFilterHigh = value_f["form"]["Tradition_Param"]["nFilterHigh"].asInt();
}
// 2、读取区域点
{
auto value_region = value_f["form"]["Tradition_Param"]["detArea"];
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.traditionDet.detArea.emplace_back(p);
}
if (_config.traditionDet.detArea.size() > 0)
{
_config.traditionDet.detArea_ROI = boundingRect(_config.traditionDet.detArea);
}
}
}
if (value_f["form"]["Tradition_Param"]["bdetArea"])
{
_config.traditionDet.bdetArea = value_f["form"]["Tradition_Param"]["bdetArea"].asFloat();
}
}
else
{
_config.traditionDet.Init();
}
// _config.traditionDet.print("traditionDet");
// getchar();
}
if ("Big_NG" == strCode)
{
auto value_f = value;

@ -0,0 +1,43 @@
#
cmake_minimum_required (VERSION 3.5)
set(ModuleName "TcsCheckModule")
include(${PROJECT_SOURCE_DIR}/cmake/default_variabes.cmake)
include(${PROJECT_SOURCE_DIR}/cmake/cpp_c_flags.cmake)
include(${PROJECT_SOURCE_DIR}/cmake/print_archs.cmake)
#
include_directories(
/usr/local/include
${CMAKE_CURRENT_SOURCE_DIR}/include
${OpenCV_INCLUDE_DIRS}
)
link_directories(
/usr/local/lib/
)
# set使*.cpp
file(GLOB SRC_LISTS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
add_library(TcsCheck SHARED ${SRC_LISTS})
target_link_libraries(TcsCheck
${OpenCV_LIBS}
pthread
)
set(ModuleName "")
# make install /usr/local
#
set(CMAKE_INSTALL_PREFIX /usr/local/cellAOI CACHE PATH "Install path prefix" FORCE)
set(HEADER_FILES include/TcsCheck.h include/TcsConfig.h)
#
install(TARGETS TcsCheck
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)

@ -0,0 +1,138 @@
#ifndef _TCSCHECK_H
#define _TCSCHECK_H
#include <thread>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <sys/stat.h> // 必需mkdir 函数声明
#include <sys/types.h> // 可选:通常被 sys/stat.h 包含,但建议显式包含
#include "TcsConfig.h"
#include <opencv2/opencv.hpp>
struct CHECK_PARAM{
int nAreaLowFilter; // low filter of the product area
int nDiscardTop; //Discard top edge width of the effective area
int nDiscardBottom; //Discard bottom edge width of the effective area
int nDiscardLeft; //Discard left edge width of the effective area
int nDiscardRight; //Discard right edge width of the effective area
float fZoomRatio; // scaling ratio of the source image
int nFilterLow; // low threshold of the filter
int nFilterHigh; // high threshold of the filter
int nBlockSize; // the size of the block
int nAreaFilter; // blob filter for drawing, only draw blob with area >= nAreaFilter
int nCountFilter; // blob filter for counting, Only the first nCountFilters with areas arranged from largest to smallest
};
/*
>= 3 ?
SCRATCH ()
:
area < POINT ()
area >= :
greyDiff < 0 DIRTY ()
greyDiff > 0 FADING_SPOTS ()
*/
enum DEFECT_TYPE_DEFINE{
DEFECT_TYPE_OK = 0,
DEFECT_TYPE_POINT = 1,
DEFECT_TYPE_SCRATCH = 2, // white line
DEFECT_TYPE_DIRTY = 3,
DEFECT_TYPE_FADING_SPOTS = 4
};
inline const std::string DEFECT_TYPE_CODE[5] = {
"P0000",
"MA505",
"MA506",
"MA504",
"P0003"
};
inline const std::string DEFECT_TYPE_DESC[5] = {
"OK",
"硬质颗粒",
"划伤",
"脏污",
"淡斑"
};
struct DEFECT_INFO{
int nDefectType;
int nDefectArea;
int nDefectX;
int nDefectY;
int nDefectWidth;
int nDefectHeight;
double dGreyDiff; // 灰阶差blob均值 - 所属局部块均值
std::string strDefectCode;
std::string strDefectDesc;
};
class CTcsCheck{
public:
int m_nInitStart;
int m_bSystemExit;
CHECK_PARAM m_cpCfg;
int m_nInitEnd;
pthread_mutex_t m_mutex;
std::vector<cv::String> m_fileList;
std::vector<DEFECT_INFO> m_vecDefectInfo;
CTcsCheck();
~CTcsCheck();
void SetCheckDir(std::string dirIn,std::string dirOut);
void SetChecConfig(CHECK_PARAM* cp);
void ProcessImages(bool bDrawResult);
// ========== 独立检测/分类接口 ==========
// 传统检测,输出残点二值图 0=成功, -1=无产品/输入为空
int TraditionalDetect(const cv::Mat& img, cv::Rect detRoi, cv::Mat& blobImg);
// 传统分类,结果写入 m_vecDefectInfo
int TraditionalClassify(const cv::Mat& blobImg);
private:
std::string m_strDirIn;
std::string m_strDirOut;
cv::Mat m_matLoad;
cv::Mat m_matBlob;
cv::Mat m_matBlur; // 模糊图,供 TraditionalClassify 使用
cv::Mat m_matDraw;
cv::Size m_sizeImage;
std::string m_strCurFile;
bool CreateDirectories(std::string path, mode_t mode = 0755) ;
void LoadImages();
std::string GetFileName(const std::string& path) const;
cv::Rect GetBoundingRect(cv::Mat matBinary);
cv::Rect GetCropArea(cv::Rect rtValid);
cv::Mat AdaptiveBinary(cv::Mat matBlur);
void Process(bool bDraw = false);
// 纯分类:对二值图做连通域分析+缺陷分类,结果写入 m_vecDefectInfo
void ClassifyBlobs(const cv::Mat& blurCrop, const cv::Mat& imgBlob);
// 纯绘制:基于 m_vecDefectInfo 绘制缺陷标注
cv::Mat DrawBlobInfoImage(const cv::Mat& imgCrop, const cv::Mat& imgBlob);
void DetectWithAdaptiveBinary(bool bDraw = false);
void DetectWithAdaptiveBinaryOptimized(bool bDraw = false);
void DetectWithDoH(bool bDraw = false, double dSigma = 1.0 ,int nThreshold = 50);
void DetectWithLoG(bool bDraw = false, double dSigma = 1.0, int nThreshold = 50);
};
#endif

@ -0,0 +1,8 @@
#ifndef _TCSCONFIG_H
#define _TCSCONFIG_H
#include <opencv2/opencv.hpp>
#endif

@ -0,0 +1,769 @@
#include "TcsCheck.h"
class CLock
{
public:
CLock(pthread_mutex_t * attr) : m_attr(attr) {
pthread_mutex_lock(m_attr);
};
~CLock() {
pthread_mutex_unlock(m_attr);
};
protected:
pthread_mutex_t * m_attr;
};
CTcsCheck::CTcsCheck()
: m_nInitStart(0)
, m_bSystemExit(0)
, m_nInitEnd(0)
{
m_cpCfg.nAreaLowFilter = 80;
m_cpCfg.nBlockSize = 100;
m_cpCfg.nDiscardTop = 170;
m_cpCfg.nDiscardBottom = 170;
m_cpCfg.nDiscardLeft = 180;
m_cpCfg.nDiscardRight = 460;
m_cpCfg.fZoomRatio = 0.25;
m_cpCfg.nFilterLow = 15;
m_cpCfg.nFilterHigh = 15;
m_cpCfg.nAreaFilter = 10;
m_cpCfg.nCountFilter = 50;
}
CTcsCheck::~CTcsCheck()
{
}
void CTcsCheck::SetCheckDir(std::string dirIn,std::string dirOut)
{
m_strDirIn = dirIn;
m_strDirOut = dirOut;
}
void CTcsCheck::SetChecConfig(CHECK_PARAM* cp)
{
memcpy(&m_cpCfg,cp,sizeof(CHECK_PARAM));
}
std::string CTcsCheck::GetFileName(const std::string& path) const
{
std::size_t pos = path.find_last_of("/\\");
std::string name = (pos == std::string::npos) ? path : path.substr(pos + 1);
std::size_t dot = name.find_last_of('.');
if (dot == std::string::npos) {
return name;
}
return name.substr(0, dot);
}
// 递归创建多级目录
bool CTcsCheck::CreateDirectories(std::string path, mode_t mode )
{
std::string fullPath = path;
size_t pos = 0;
while ((pos = fullPath.find_first_of("/", pos + 1)) != std::string::npos) {
std::string subPath = fullPath.substr(0, pos);
if (subPath.empty()) continue;
if (mkdir(subPath.c_str(), mode) != 0 && errno != EEXIST) {
std::cerr << "创建子目录失败: " << subPath << " - " << strerror(errno) << std::endl;
return false;
}
}
// 创建最后一级目录
if (mkdir(path.c_str(), mode) != 0 && errno != EEXIST) {
std::cerr << "创建最终目录失败: " << path << " - " << strerror(errno) << std::endl;
return false;
}
std::cout << "多级目录创建成功: " << path << std::endl;
return true;
}
void CTcsCheck::LoadImages()
{
if (access(m_strDirIn.c_str(),F_OK) == 0)
{
cv::glob(m_strDirIn, m_fileList);
}
else
{
std::cout << "Dir " << m_strDirIn << " is NOT existed!" << std::endl;
}
std::cout << "Create OUT directory : " << m_strDirOut << std::endl;
CreateDirectories(m_strDirOut);
}
cv::Rect CTcsCheck::GetBoundingRect(cv::Mat matBinary)
{
cv::Mat labels;
cv::Mat stats;
cv::Mat centroids;
const int nLabels = cv::connectedComponentsWithStats(matBinary, labels, stats, centroids, 8, CV_32S);
if (nLabels <= 1) {
return cv::Rect(0,0,0,0);
}
int maxArea = 0;
int maxLabel = -1;
for (int label = 1; label < nLabels; ++label) {
const int area = stats.at<int>(label, cv::CC_STAT_AREA);
if (area > maxArea) {
maxArea = area;
maxLabel = label;
}
}
if (maxLabel < 0) {
return cv::Rect(0,0,0,0);
}
const int x = stats.at<int>(maxLabel, cv::CC_STAT_LEFT);
const int y = stats.at<int>(maxLabel, cv::CC_STAT_TOP);
const int w = stats.at<int>(maxLabel, cv::CC_STAT_WIDTH);
const int h = stats.at<int>(maxLabel, cv::CC_STAT_HEIGHT);
return cv::Rect(x, y, w, h);
}
cv::Rect CTcsCheck::GetCropArea(cv::Rect rtValid)
{
// 在原图坐标系中对 ROI 四边分别内收对应的 discard 宽度。
// 若尺寸不足以同时内收,则保持原 ROI不做"部分边"裁剪)。
const int outW = rtValid.width - m_cpCfg.nDiscardLeft - m_cpCfg.nDiscardRight;
const int outH = rtValid.height - m_cpCfg.nDiscardTop - m_cpCfg.nDiscardBottom;
if (outW <= 0 || outH <= 0) {
return rtValid;
}
cv::Rect innerRect(rtValid.x + m_cpCfg.nDiscardLeft, rtValid.y + m_cpCfg.nDiscardTop, outW, outH);
const cv::Rect imageRect(0, 0, m_sizeImage.width, m_sizeImage.height);
innerRect = innerRect & imageRect;
if (innerRect.width <= 0 || innerRect.height <= 0) {
return rtValid;
}
return innerRect;
}
cv::Mat CTcsCheck::AdaptiveBinary(cv::Mat matBlur)
{
// 按 localBlockSize_ 分块,使用每个块的局部均值做动态阈值。
// 当前规则:落在 [avg-lower_, avg+upper_] 内置 0超出置 255。
cv::Mat result = cv::Mat::zeros(matBlur.size(), CV_8UC1);
m_cpCfg.nBlockSize = std::min(m_cpCfg.nBlockSize, matBlur.cols);
m_cpCfg.nBlockSize = std::min(m_cpCfg.nBlockSize, matBlur.rows);
for (int y = 0; y < matBlur.rows; y += m_cpCfg.nBlockSize) {
for (int x = 0; x < matBlur.cols; x += m_cpCfg.nBlockSize) {
// const int blockW = std::min(m_cpCfg.nBlockSize, matBlur.cols - x);
// const int blockH = std::min(m_cpCfg.nBlockSize, matBlur.rows - y);
cv::Rect blockRect(x, y, m_cpCfg.nBlockSize, m_cpCfg.nBlockSize);
if(x + m_cpCfg.nBlockSize > matBlur.cols)
{
blockRect = cv::Rect(matBlur.cols - m_cpCfg.nBlockSize, y, m_cpCfg.nBlockSize, m_cpCfg.nBlockSize);
}
if(y + m_cpCfg.nBlockSize > matBlur.rows)
{
blockRect = cv::Rect(x, matBlur.rows - m_cpCfg.nBlockSize, m_cpCfg.nBlockSize, m_cpCfg.nBlockSize);
}
if(x + m_cpCfg.nBlockSize > matBlur.cols && y + m_cpCfg.nBlockSize > matBlur.rows)
{
blockRect = cv::Rect(matBlur.cols - m_cpCfg.nBlockSize, matBlur.rows - m_cpCfg.nBlockSize, m_cpCfg.nBlockSize, m_cpCfg.nBlockSize);
}
cv::Mat block = matBlur(blockRect);
const double avg = cv::mean(block)[0];
const double minGrey = std::max(0.0, avg - m_cpCfg.nFilterLow);
const double maxGrey = std::min(255.0, avg + m_cpCfg.nFilterHigh);
cv::Mat blockBin;
cv::inRange(block, minGrey, maxGrey, blockBin);
cv::bitwise_not(blockBin, blockBin);
blockBin.copyTo(result(blockRect));
}
}
return result;
}
// ============================================================
// ClassifyBlobs — 纯分类函数
// 对残点二值图做连通域分析 + 缺陷分类,结果写入 m_vecDefectInfo
// ============================================================
void CTcsCheck::ClassifyBlobs(const cv::Mat& blurCrop, const cv::Mat& imgBlob)
{
m_vecDefectInfo.clear();
cv::Mat labels;
cv::Mat stats;
cv::Mat centroids;
const int nLabels = cv::connectedComponentsWithStats(imgBlob, labels, stats, centroids, 8, CV_32S);
if (nLabels <= 1) return;
struct BlobItem { int label; int area; };
std::vector<BlobItem> blobs;
blobs.reserve(std::max(0, nLabels - 1));
for (int label = 1; label < nLabels; ++label) {
const int area = stats.at<int>(label, cv::CC_STAT_AREA);
if (area > m_cpCfg.nAreaFilter) {
blobs.push_back({ label, area });
}
}
if (blobs.empty()) return;
std::sort(blobs.begin(), blobs.end(), [](const BlobItem& a, const BlobItem& b) {
return a.area > b.area;
});
// 大块缺陷的面积阈值
const int largeAreaThreshold = std::max(1, m_cpCfg.nBlockSize * m_cpCfg.nBlockSize / 4);
const int topK = std::min(m_cpCfg.nCountFilter, static_cast<int>(blobs.size()));
for (int i = 0; i < topK; ++i) {
const int label = blobs[i].label;
const int area = blobs[i].area;
// bounding rect
const int x = stats.at<int>(label, cv::CC_STAT_LEFT);
const int y = stats.at<int>(label, cv::CC_STAT_TOP);
const int w = stats.at<int>(label, cv::CC_STAT_WIDTH);
const int h = stats.at<int>(label, cv::CC_STAT_HEIGHT);
// blob 区域均值
cv::Mat blobMask = (labels == label);
double blobMean = cv::mean(blurCrop, blobMask)[0];
// 所属局部块均值
const double cx = centroids.at<double>(label, 0);
const double cy = centroids.at<double>(label, 1);
int blockX = std::max(0, std::min(static_cast<int>(cx) / m_cpCfg.nBlockSize, (blurCrop.cols - 1) / m_cpCfg.nBlockSize));
int blockY = std::max(0, std::min(static_cast<int>(cy) / m_cpCfg.nBlockSize, (blurCrop.rows - 1) / m_cpCfg.nBlockSize));
const int roiX = blockX * m_cpCfg.nBlockSize;
const int roiY = blockY * m_cpCfg.nBlockSize;
const int roiW = std::min(m_cpCfg.nBlockSize, blurCrop.cols - roiX);
const int roiH = std::min(m_cpCfg.nBlockSize, blurCrop.rows - roiY);
const double localMean = cv::mean(blurCrop(cv::Rect(roiX, roiY, roiW, roiH)))[0];
const double greyDiff = blobMean - localMean;
// ============================================================
// 缺陷分类决策树
// 长宽比 >= 3 → SCRATCH (划伤)
// 紧凑 + 面积小 → POINT (硬质颗粒)
// 紧凑 + 大面积+暗 → DIRTY (脏污)
// 紧凑 + 大面积+亮 → FADING (淡斑)
// ============================================================
float aspectRatio = static_cast<float>(std::max(w, h)) / std::max(1, std::min(w, h));
int defectType = DEFECT_TYPE_OK;
if (aspectRatio >= 3.0f) {
defectType = DEFECT_TYPE_SCRATCH;
} else if (area < largeAreaThreshold) {
defectType = DEFECT_TYPE_POINT;
} else if (greyDiff < 0) {
defectType = DEFECT_TYPE_DIRTY;
} else {
defectType = DEFECT_TYPE_FADING_SPOTS;
}
DEFECT_INFO info;
info.nDefectType = defectType;
info.nDefectArea = area;
info.nDefectX = x;
info.nDefectY = y;
info.nDefectWidth = w;
info.nDefectHeight = h;
info.dGreyDiff = greyDiff;
info.strDefectCode = DEFECT_TYPE_CODE[defectType];
info.strDefectDesc = DEFECT_TYPE_DESC[defectType];
m_vecDefectInfo.push_back(info);
}
}
// ============================================================
// DrawBlobInfoImage — 纯绘制函数
// 基于 m_vecDefectInfo 在 crop 图上绘制缺陷标注
// ============================================================
cv::Mat CTcsCheck::DrawBlobInfoImage(const cv::Mat& imgCrop, const cv::Mat& imgBlob)
{
cv::Mat cropColor;
cv::cvtColor(imgCrop, cropColor, cv::COLOR_GRAY2BGR);
for (const auto& info : m_vecDefectInfo) {
const cv::Rect blobRect(info.nDefectX, info.nDefectY, info.nDefectWidth, info.nDefectHeight);
// 根据缺陷类型使用不同颜色绘制
cv::Scalar drawColor;
switch (info.nDefectType) {
case DEFECT_TYPE_POINT: drawColor = cv::Scalar(0, 255, 0); break; // 绿色
case DEFECT_TYPE_SCRATCH: drawColor = cv::Scalar(0, 0, 255); break; // 红色
case DEFECT_TYPE_DIRTY: drawColor = cv::Scalar(255, 0, 0); break; // 蓝色
case DEFECT_TYPE_FADING_SPOTS: drawColor = cv::Scalar(255, 255, 0); break; // 青色
default: drawColor = cv::Scalar(0, 255, 0); break;
}
// 在 blob 区域内找轮廓
cv::Mat roiBlob = imgBlob(blobRect & cv::Rect(0, 0, imgBlob.cols, imgBlob.rows));
std::vector<std::vector<cv::Point>> contours;
cv::findContours(roiBlob.clone(), contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
// 偏移轮廓坐标到原图位置
for (auto& cnt : contours) {
for (auto& pt : cnt) {
pt.x += info.nDefectX;
pt.y += info.nDefectY;
}
}
cv::drawContours(cropColor, contours, -1, drawColor, 2);
cv::rectangle(cropColor, blobRect, cv::Scalar(0, 0, 255), 1);
// 文本标注
std::ostringstream oss;
oss << info.strDefectCode << " A:" << info.nDefectArea
<< " dG:" << std::fixed << std::setprecision(1) << info.dGreyDiff;
int textX = std::max(0, info.nDefectX);
int textY = std::max(15, info.nDefectY - 3);
cv::putText(cropColor, oss.str(), cv::Point(textX, textY),
cv::FONT_HERSHEY_SIMPLEX, 0.45, drawColor, 1);
}
return cropColor;
}
// ============================================================
// 独立检测接口
// 对单张图做:阈值定位→裁剪→缩放→高斯模糊→自适应二值化
// 输出残点二值图 blobImg内部缓存 m_matBlur 供后续分类
// 返回: 0=成功, -1=无产品或输入异常
// ============================================================
int CTcsCheck::TraditionalDetect(const cv::Mat& img, cv::Rect detRoi, cv::Mat& blobImg)
{
if (img.empty())
{
blobImg = cv::Mat();
return -1;
}
// getchar();
// cv::Mat showImg = img.clone();
// cv::rectangle(showImg, detRoi, cv::Scalar(255), 2);
// cv::imwrite("detRoi.png", showImg);
m_matLoad = img;
// 确保 m_sizeImage 始终与 m_matLoad 同步
m_sizeImage = img.size();
cv::Rect rtCrop = detRoi;
if(detRoi.size() == img.size())
{
// 1. 全局阈值 → 产品区域定位
cv::Mat matBinary;
cv::threshold(m_matLoad, matBinary, m_cpCfg.nAreaLowFilter, 255, cv::THRESH_BINARY);
// 2. 获取最大连通域外接矩形
cv::Rect rtValid = GetBoundingRect(matBinary);
if (rtValid == cv::Rect(0, 0, 0, 0))
{
blobImg = cv::Mat();
return -1;
}
// 3. 裁剪边缘
rtCrop = GetCropArea(rtValid);
}
// 安全裁剪: 确保 rtCrop 不超出 m_matLoad 边界
{
const cv::Rect imgRect(0, 0, m_matLoad.cols, m_matLoad.rows);
rtCrop = rtCrop & imgRect;
if (rtCrop.width <= 0 || rtCrop.height <= 0)
{
blobImg = cv::Mat();
return -1;
}
}
cv::Mat matCrop = m_matLoad(rtCrop).clone();
// 4. 缩放
const int outW = std::max(1, static_cast<int>(matCrop.cols * m_cpCfg.fZoomRatio));
const int outH = std::max(1, static_cast<int>(matCrop.rows * m_cpCfg.fZoomRatio));
cv::Mat matResized;
cv::resize(matCrop, matResized, cv::Size(outW, outH), 0, 0, cv::INTER_AREA);
// 5. 高斯模糊 — 缓存到 m_matBlur
cv::GaussianBlur(matResized, m_matBlur, cv::Size(5, 5), 0);
// cv::imwrite("matResized.png", matResized);
// cv::imwrite("m_matBlur.png", m_matBlur);
// 6. 自适应二值化检测
m_matBlob = AdaptiveBinary(m_matBlur);
blobImg = m_matBlob.clone();
// 7. 反向映射:将 crop+resize 后的残点图恢复为原始输入图尺寸
// 调用方拿到的是与输入 img 同尺寸的 mask坐标体系一致
if (m_cpCfg.fZoomRatio > 0)
{
// 7a. 反向映射 blobImg二值 mask用 INTER_NEAREST
{
cv::Mat matCropSize;
cv::resize(blobImg, matCropSize, cv::Size(rtCrop.width, rtCrop.height), 0, 0, cv::INTER_NEAREST);
cv::Mat fullMask = cv::Mat::zeros(m_matLoad.size(), CV_8UC1);
matCropSize.copyTo(fullMask(rtCrop));
blobImg = fullMask;
}
// 7b. 同步反向映射 m_matBlur灰度图供 TraditionalClassify 使用,用 INTER_LINEAR
{
cv::Mat matCropSize;
cv::resize(m_matBlur, matCropSize, cv::Size(rtCrop.width, rtCrop.height), 0, 0, cv::INTER_LINEAR);
cv::Mat fullBlur = cv::Mat::zeros(m_matLoad.size(), m_matBlur.type());
matCropSize.copyTo(fullBlur(rtCrop));
m_matBlur = fullBlur;
}
// 7c. 同步反向映射 m_matBlob二值 mask用 INTER_NEAREST
{
cv::Mat matCropSize;
cv::resize(m_matBlob, matCropSize, cv::Size(rtCrop.width, rtCrop.height), 0, 0, cv::INTER_NEAREST);
cv::Mat fullBlob = cv::Mat::zeros(m_matLoad.size(), CV_8UC1);
matCropSize.copyTo(fullBlob(rtCrop));
m_matBlob = fullBlob;
}
}
return 0;
}
// ============================================================
// TraditionalClassify — 独立分类接口
// 对残点二值图做连通域分析+缺陷分类决策树
// 前提: 已调用 TraditionalDetectm_matBlur 已缓存)
// 返回: 分类到的缺陷数量,<0 表示异常
// ============================================================
int CTcsCheck::TraditionalClassify(const cv::Mat& blobImg)
{
if (m_matBlur.empty() || blobImg.empty()) return -1;
m_matBlob = blobImg.clone();
ClassifyBlobs(m_matBlur, blobImg);
return static_cast<int>(m_vecDefectInfo.size());
}
void CTcsCheck::ProcessImages(bool bDrawResult)
{
LoadImages();
std::cout << "TOTAL file count = " << m_fileList.size() << std::endl;
for (int i = 0; i < m_fileList.size(); i ++ )
{
m_matLoad = cv::imread(m_fileList[i],cv::IMREAD_GRAYSCALE);
m_strCurFile = GetFileName(m_fileList[i]);
m_sizeImage = m_matLoad.size();
Process(bDrawResult);
}
}
void CTcsCheck::DetectWithAdaptiveBinary(bool bDraw )
{
cv::Mat matBinary,matCrop,matResized,matBlur,matDraw;
auto tStart = std::chrono::high_resolution_clock::now();
cv::threshold(m_matLoad,matBinary,m_cpCfg.nAreaLowFilter,255,cv::THRESH_BINARY);
// Obtain Key Region Range
cv::Rect rtValid = GetBoundingRect(matBinary);
if (rtValid == cv::Rect(0,0,0,0))
{
std::cout << "NO product" << std::endl;
return ;
}
// crop image
cv::Rect rtCrop = GetCropArea(rtValid);
matCrop = m_matLoad(rtCrop).clone();
// image zoom
const int outW = std::max(1, static_cast<int>(matBinary.cols * m_cpCfg.fZoomRatio));
const int outH = std::max(1, static_cast<int>(matBinary.rows * m_cpCfg.fZoomRatio));
cv::resize(matCrop, matResized, cv::Size(outW, outH), 0, 0, cv::INTER_AREA);
// image blur
cv::GaussianBlur(matResized, matBlur, cv::Size(5, 5), 0);
// 二值化
m_matBlob = AdaptiveBinary(matBlur);
// 分类:对残点二值图做连通域分析+缺陷分类
ClassifyBlobs(matBlur, m_matBlob);
auto tEnd = std::chrono::high_resolution_clock::now();
double elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(tEnd - tStart).count();
std::cout << "耗时(ms): " << elapsedMs << std::endl;
if (bDraw)
{
matDraw = DrawBlobInfoImage(matResized, m_matBlob);
std::string strOutFile,strDebug;
strOutFile = m_strDirOut + "/" + m_strCurFile + "_Binary.png";
cv::imwrite(strOutFile,matBinary);
strOutFile = m_strDirOut + "/" +m_strCurFile + "_Crop.png";
cv::imwrite(strOutFile,matResized);
strOutFile = m_strDirOut + "/" +m_strCurFile + "_Blob.png";
cv::imwrite(strOutFile,m_matBlob);
strOutFile = m_strDirOut + "/" +m_strCurFile + "_Draw.png";
cv::imwrite(strOutFile,matDraw);
}
}
void CTcsCheck::DetectWithDoH(bool bDraw,double dSigma,int nThreshold )
{
cv::Mat matBinary,matCrop,matResized,matBlur,matDraw;
auto tStart = std::chrono::high_resolution_clock::now();
cv::threshold(m_matLoad,matBinary,m_cpCfg.nAreaLowFilter,255,cv::THRESH_BINARY);
// Obtain Key Region Range
cv::Rect rtValid = GetBoundingRect(matBinary);
if (rtValid == cv::Rect(0,0,0,0))
{
std::cout << "NO product" << std::endl;
return ;
}
// crop image
cv::Rect rtCrop = GetCropArea(rtValid);
matCrop = m_matLoad(rtCrop).clone();
// image zoom
const int outW = std::max(1, static_cast<int>(matBinary.cols * m_cpCfg.fZoomRatio));
const int outH = std::max(1, static_cast<int>(matBinary.rows * m_cpCfg.fZoomRatio));
cv::resize(matCrop, matResized, cv::Size(outW, outH), 0, 0, cv::INTER_AREA);
cv::Mat matFloat,matDoh,matNorm;
// 转换为浮点型以进行精确的导数计算
matResized.convertTo(matFloat, CV_64F);
// 2. 高斯平滑(可选,用于去噪和定义尺度)
double sigma = dSigma; // 根据斑点大小调整
cv::Mat smoothed;
cv::GaussianBlur(matFloat, matBlur, cv::Size(0, 0), sigma);
// 3. 计算二阶导数 Ixx, Iyy, Ixy
cv::Mat Ixx, Iyy, Ixy;
cv::Sobel(matBlur, Ixx, CV_64F, 2, 0, 3); // 对 x 求二阶导
cv::Sobel(matBlur, Iyy, CV_64F, 0, 2, 3); // 对 y 求二阶导
cv::Sobel(matBlur, Ixy, CV_64F, 1, 1, 3); // 先对 x 求导,再对 y 求导
// 4. 计算 DoH 响应det(H) = Ixx * Iyy - Ixy^2
cv::multiply(Ixx, Iyy, matDoh);
cv::subtract(matDoh, Ixy.mul(Ixy), matDoh);
// 5. 后处理:寻找局部极大值作为斑点
cv::normalize(matDoh, matNorm, 0, 255, cv::NORM_MINMAX, CV_8U);
auto tEnd = std::chrono::high_resolution_clock::now();
double elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(tEnd - tStart).count();
std::cout << "耗时(ms): " << elapsedMs << std::endl;
// 6. 绘制并显示结果
if ( bDraw)
{
// 寻找局部极大值(简化示例,实际应用需更严谨的邻域比较)
std::vector<cv::KeyPoint> keypoints;
int threshold_value = nThreshold; // 需要根据图像调整的阈值
for (int i = 1; i < matDoh.rows - 1; ++i) {
for (int j = 1; j < matDoh.cols - 1; ++j) {
double val = matDoh.at<double>(i, j);
if (val > threshold_value)
//&& val > matDoh.at<double>(i - 1, j) && val > matDoh.at<double>(i + 1, j) &&
//val > matDoh.at<double>(i, j - 1) && val > matDoh.at<double>(i, j + 1))
{
// 此处可更精确地计算斑点的尺度和响应强度
keypoints.push_back(cv::KeyPoint(j, i, 2 * sigma));
}
}
}
cv::cvtColor(matNorm, matDraw, cv::COLOR_GRAY2BGR);
cv::drawKeypoints(matDraw, keypoints, matDraw, cv::Scalar(0, 0, 255), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
std::string strOutFile;
strOutFile = m_strDirOut + "/" + m_strCurFile + "_Resize.png";
cv::imwrite(strOutFile,matResized);
strOutFile = m_strDirOut + "/" + m_strCurFile + "_Norm.png";
cv::imwrite(strOutFile,matNorm);
strOutFile = m_strDirOut + "/" + m_strCurFile + "_Draw.png";
cv::imwrite(strOutFile,matDraw);
}
}
void CTcsCheck::DetectWithLoG(bool bDraw, double dSigma, int nThreshold)
{
// ===================================================================
// LoG (Laplacian of Gaussian) 斑点检测
// 原理: LoG = trace(H) = Ixx + Iyy是 Hessian 矩阵的迹
// 相比 DoH (det(H)=Ixx*Iyy-Ixy²)
// - 速度: 只需1次 LaplacianDoH 需要3次 Sobel + 乘减运算 (~3x 加速)
// - 精度: LoG 是旋转不变的各向同性斑点检测器,对圆形缺陷响应最优
// ===================================================================
cv::Mat matBinary, matCrop, matResized, matBlur, matDraw;
auto tStart = std::chrono::high_resolution_clock::now();
// 1. 全局阈值 -> 定位产品区域
cv::threshold(m_matLoad, matBinary, m_cpCfg.nAreaLowFilter, 255, cv::THRESH_BINARY);
// 2. 获取有效区域外接矩形
cv::Rect rtValid = GetBoundingRect(matBinary);
if (rtValid == cv::Rect(0, 0, 0, 0))
{
std::cout << "NO product" << std::endl;
return;
}
// 3. 裁剪边缘
cv::Rect rtCrop = GetCropArea(rtValid);
matCrop = m_matLoad(rtCrop).clone();
// 4. 缩放
const int outW = std::max(1, static_cast<int>(matBinary.cols * m_cpCfg.fZoomRatio));
const int outH = std::max(1, static_cast<int>(matBinary.rows * m_cpCfg.fZoomRatio));
cv::resize(matCrop, matResized, cv::Size(outW, outH), 0, 0, cv::INTER_AREA);
// 5. 转浮点型 + 高斯平滑(尺度选择)
cv::Mat matFloat;
matResized.convertTo(matFloat, CV_64F);
cv::GaussianBlur(matFloat, matBlur, cv::Size(0, 0), dSigma);
// 6. 核心: Laplacian 响应(等价于 Ixx + Iyy
// 相比 DoH 省去了 Ixy 计算和乘减步骤
cv::Mat matLoG;
cv::Laplacian(matBlur, matLoG, CV_64F, 3);
// 7. 取绝对值(同时检测亮斑和暗斑)
matLoG = cv::abs(matLoG);
// 8. 归一化到 [0,255]
cv::Mat matNorm;
cv::normalize(matLoG, matNorm, 0, 255, cv::NORM_MINMAX, CV_8U);
// 保存斑点图供外部使用
m_matBlob = matNorm.clone();
auto tEnd = std::chrono::high_resolution_clock::now();
double elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(tEnd - tStart).count();
std::cout << "LoG 耗时(ms): " << elapsedMs << std::endl;
// 9. 可视化
if (bDraw)
{
// 非极大值抑制 + 阈值筛选关键点
std::vector<cv::KeyPoint> keypoints;
for (int i = 1; i < matLoG.rows - 1; ++i)
{
for (int j = 1; j < matLoG.cols - 1; ++j)
{
double val = matLoG.at<double>(i, j);
// 局部极大值检测3×3邻域
if (val > nThreshold &&
val >= matLoG.at<double>(i - 1, j) &&
val >= matLoG.at<double>(i + 1, j) &&
val >= matLoG.at<double>(i, j - 1) &&
val >= matLoG.at<double>(i, j + 1))
{
keypoints.push_back(cv::KeyPoint(j, i, 2.0f * static_cast<float>(dSigma)));
}
}
}
cv::cvtColor(matNorm, matDraw, cv::COLOR_GRAY2BGR);
cv::drawKeypoints(matDraw, keypoints, matDraw, cv::Scalar(0, 0, 255), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
std::string strOutFile;
strOutFile = m_strDirOut + "/" + m_strCurFile + "_Resize.png";
cv::imwrite(strOutFile, matResized);
strOutFile = m_strDirOut + "/" + m_strCurFile + "_Norm.png";
cv::imwrite(strOutFile, matNorm);
strOutFile = m_strDirOut + "/" + m_strCurFile + "_Draw.png";
cv::imwrite(strOutFile, matDraw);
}
}
void CTcsCheck::DetectWithAdaptiveBinaryOptimized(bool bDraw)
{
// ===================================================================
// DetectWithAdaptiveBinary 优化版
// 与原始版保持相同的 OpenCV SIMD 加速路径, 避免 naive 像素循环
//
// 当下真正有效的优化方向 (非本次实现):
// - cv::UMat: 启用 OpenCL GPU 加速, 代码改动 2 行 (Mat → UMat)
// - 多线程并行: 用 cv::parallel_for_ 处理分块
// - 缩小 nBlockSize 减少块数 (精度/速度 trade-off)
// ===================================================================
cv::Mat matBinary, matCrop, matResized, matBlur, matDraw;
auto tStart = std::chrono::high_resolution_clock::now();
cv::threshold(m_matLoad, matBinary, m_cpCfg.nAreaLowFilter, 255, cv::THRESH_BINARY);
// Obtain Key Region Range
cv::Rect rtValid = GetBoundingRect(matBinary);
if (rtValid == cv::Rect(0, 0, 0, 0))
{
std::cout << "NO product" << std::endl;
return;
}
// crop image
cv::Rect rtCrop = GetCropArea(rtValid);
matCrop = m_matLoad(rtCrop).clone();
// image zoom
const int outW = std::max(1, static_cast<int>(matBinary.cols * m_cpCfg.fZoomRatio));
const int outH = std::max(1, static_cast<int>(matBinary.rows * m_cpCfg.fZoomRatio));
cv::resize(matCrop, matResized, cv::Size(outW, outH), 0, 0, cv::INTER_AREA);
// image blur
cv::GaussianBlur(matResized, matBlur, cv::Size(5, 5), 0);
// 二值化 — 使用原始 AdaptiveBinary (OpenCV SIMD 内部加速)
m_matBlob = AdaptiveBinary(matBlur);
// 分类:对残点二值图做连通域分析+缺陷分类
ClassifyBlobs(matBlur, m_matBlob);
auto tEnd = std::chrono::high_resolution_clock::now();
double elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(tEnd - tStart).count();
std::cout << "AdaptiveBinaryOpt 耗时(ms): " << elapsedMs << std::endl;
if (bDraw)
{
matDraw = DrawBlobInfoImage(matResized, m_matBlob);
std::string strOutFile;
strOutFile = m_strDirOut + "/" + m_strCurFile + "_Binary.png";
cv::imwrite(strOutFile, matBinary);
strOutFile = m_strDirOut + "/" + m_strCurFile + "_Crop.png";
cv::imwrite(strOutFile, matResized);
strOutFile = m_strDirOut + "/" + m_strCurFile + "_Blob.png";
cv::imwrite(strOutFile, m_matBlob);
strOutFile = m_strDirOut + "/" + m_strCurFile + "_Draw.png";
cv::imwrite(strOutFile, matDraw);
}
}
void CTcsCheck::Process(bool bDraw)
{
//默认使用优化版 AdaptiveBinary积分图加速 + findNonZero 快速定位
//DetectWithAdaptiveBinaryOptimized(bDraw);
// 备选方法:
DetectWithAdaptiveBinary(bDraw);
// DetectWithLoG(bDraw, 2.0, 48);
// DetectWithDoH(bDraw, 2.0, 48);
}

@ -15,6 +15,7 @@ ${CMAKE_CURRENT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/AlgorithmModule/include
${PROJECT_SOURCE_DIR}/ConfigModule/include
${PROJECT_SOURCE_DIR}/ExtractImageModule/include
${PROJECT_SOURCE_DIR}/TcsCheckModule/include
)
link_directories(
@ -22,6 +23,8 @@ link_directories(
/usr/local/cuda/lib64
)
file(GLOB SRC_LISTS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
#
list(FILTER SRC_LISTS EXCLUDE REGEX ".*/tcs_test\\.cpp$")
add_executable("test_CellAOI" ${SRC_LISTS})
@ -32,6 +35,7 @@ target_link_libraries("test_CellAOI"
TY_Check
Config
ExtractImage
TcsCheck
#/usr/local/cuda-12.1/targets/x86_64-linux/lib/libcudart.so
${OpenCV_LIBS}
)
@ -39,4 +43,16 @@ target_link_libraries("test_CellAOI"
set_target_properties("test_CellAOI" PROPERTIES
BUILD_RPATH "\$ORIGIN"
)
# TCS
add_executable("test_CellAOI_Tcs" tcs_test.cpp)
target_link_libraries("test_CellAOI_Tcs"
TcsCheck
${OpenCV_LIBS}
pthread
)
set_target_properties("test_CellAOI_Tcs" PROPERTIES
BUILD_RPATH "\$ORIGIN"
)
set(ModuleName "")

@ -207,13 +207,15 @@ std::string Extract_ALL::Extract_Camera_Name(std::string strPath, int userflag)
fs::path p = fs::path(strPath);
std::string filename = p.stem();
// 文件名包含 CA 或 TA 即判定对应相机
// 支持旧格式: CA_20250508074237.ytimage
// 支持新格式: A_CAA.png, M_CAB.png (A_/M_ 仅是打光方式同属一个camera)
if (filename.find("TA") != std::string::npos)
return "TA";
else if (filename.find("CA") != std::string::npos)
return "CA";
// 文件名包含 BCA/BTA/DCA/DTA/CA/TA 即判定对应相机
if (filename.find("BCA") != std::string::npos)
return "BCA";
else if (filename.find("DCA") != std::string::npos)
return "DCA";
else if (filename.find("BTA") != std::string::npos)
return "BTA";
else if (filename.find("DTA") != std::string::npos)
return "DTA";
return "";
}
@ -247,37 +249,45 @@ std::string Extract_ALL::Extract_Channel_Name(std::string strPath, int userflag)
fs::path p = fs::path(strPath);
std::string filename = p.stem();
// 新格式: {Prefix}_{Type}{AB}, 如 A_CAA, M_TAB
// 新格式: {Prefix}_{Camera}{AB}, 如 A_BCAA, M_DTAB, A_BCAB
// 通道名 = 前缀 (A/M), 表示不同打光方式
// 支持4相机: BCAA, BCAB, BTAA, BTAB, DCAA, DCAB, DTAA, DTAB
size_t underscorePos = filename.find('_');
if (underscorePos != std::string::npos)
{
std::string prefix = filename.substr(0, underscorePos);
std::string rest = filename.substr(underscorePos + 1);
if (rest == "CAA" || rest == "CAB" || rest == "TAA" || rest == "TAB")
// 4相机格式
if (rest == "BCAA" || rest == "BCAB" || rest == "BTAA" || rest == "BTAB" ||
rest == "DCAA" || rest == "DCAB" || rest == "DTAA" || rest == "DTAB" )
{
return prefix; // "A" or "M"
}
}
// 旧格式兼容: CA_xxx.ytimage, TA_xxx.ytimage
if (filename.find("TAA") != std::string::npos)
strchannelName = "TA";
else if (filename.find("TAB") != std::string::npos)
strchannelName = "TA";
else if (filename.find("CAA") != std::string::npos)
strchannelName = "CA";
else if (filename.find("CAB") != std::string::npos)
strchannelName = "CA";
else if (filename.find("CA") != std::string::npos)
strchannelName = "CA";
else if (filename.find("TA") != std::string::npos)
strchannelName = "TA";
// 4相机格式兼容: BCA_xxx, BTA_xxx, DCA_xxx, DTA_xxx
if (filename.find("BCAA") != std::string::npos || filename.find("BCAB") != std::string::npos)
strchannelName = "BCA";
else if (filename.find("BTAA") != std::string::npos || filename.find("BTAB") != std::string::npos)
strchannelName = "BTA";
else if (filename.find("DCAA") != std::string::npos || filename.find("DCAB") != std::string::npos)
strchannelName = "DCA";
else if (filename.find("DTAA") != std::string::npos || filename.find("DTAB") != std::string::npos)
strchannelName = "DTA";
else if (filename.find("BCA") != std::string::npos)
strchannelName = "BCA";
else if (filename.find("BTA") != std::string::npos)
strchannelName = "BTA";
else if (filename.find("DCA") != std::string::npos)
strchannelName = "DCA";
else if (filename.find("DTA") != std::string::npos)
strchannelName = "DTA";
return strchannelName;
}
int Extract_ALL::Read_Image_List(std::string strPath, std::string strproduct)
{
std::string strSearchImgPath = strPath + "/*.png";
std::string strSearchImgPath = strPath + "/*.jpg";
Read_Camera_Product_Image(strSearchImgPath, 0, strproduct);
strSearchImgPath = strPath + "/*.ytimage";
@ -288,12 +298,12 @@ int Extract_ALL::Read_Image_List(std::string strPath, std::string strproduct)
int Extract_ALL::Read_Image_List(std::string strPath_left, std::string strPath_right, std::string strproduct)
{
std::string strSearchImgPath = strPath_left + "/*.png";
std::string strSearchImgPath = strPath_left + "/*.jpg";
Read_Camera_Product_Image(strSearchImgPath, 0, strproduct);
strSearchImgPath = strPath_right + "/*.png";
strSearchImgPath = strPath_right + "/*.jpg";
Read_Camera_Product_Image(strSearchImgPath, 0, strproduct);
// strSearchImgPath = strPath + "/*.png";
// strSearchImgPath = strPath + "/*.jpg";
// Read_Camera_Product_Image(strSearchImgPath, 1);
return 0;

@ -105,8 +105,10 @@ enum START_SYSTEM_STEP_
};
struct SystemConfigParam
{
std::string Analysis_Config_path;
std::string Analysis_Config_path_Cam2;
std::string Analysis_Config_path; // Cam1: BCA
std::string Analysis_Config_path_Cam2; // Cam2: BTA
std::string Analysis_Config_path_Cam3; // Cam3: DCA
std::string Analysis_Config_path_Cam4; // Cam4: DTA
std::string Check_Config_path;
std::string config_Root_Path;
@ -125,6 +127,9 @@ struct SystemConfigParam
bool valid()
{
if (Analysis_Config_path.size() &&
Analysis_Config_path_Cam2.size() &&
Analysis_Config_path_Cam3.size() &&
Analysis_Config_path_Cam4.size() &&
Check_Config_path.size() &&
preCheckImg_Path.size() &&
config_Root_Path.size() &&

@ -5,6 +5,7 @@
#include <sys/stat.h>
#include <unistd.h>
#include <string>
#include <filesystem>
#include "CheckUtil.hpp"
std::string ExtractFileNameWithoutExtension(const std::string &strImgPath)
{
@ -1083,8 +1084,6 @@ bool deal::ReadSystemConfig(const std::string &strPath)
}
m_system_param.Use_CPU_StartIdx = root["Use_CPU_StartIdx"].asInt();
// path
m_system_param.Analysis_Config_path = root["Analysis_Config_path"].asString();
m_system_param.Analysis_Config_path_Cam2 = root["Analysis_Config_path_Cam2"].asString();
m_system_param.config_Root_Path = root["Config_Root_Path"].asString();
m_system_param.Check_Config_path = root["Check_Config_path"].asString();
@ -1093,8 +1092,83 @@ bool deal::ReadSystemConfig(const std::string &strPath)
m_system_param.preCHeck_defect = root["preCHeck_defect"].asInt();
m_nCurUseCPUIDX = m_system_param.Use_CPU_StartIdx;
// 从 Config_Root_Path 目录下自动扫描匹配 param_*.json 文件,映射到各相机
ScanConfigPaths();
return m_system_param.valid();
}
int deal::ScanConfigPaths()
{
std::string configRoot = m_system_param.config_Root_Path;
if (configRoot.empty())
{
printf("ScanConfigPaths: Config_Root_Path is empty\n");
return -1;
}
// 确保路径以 / 结尾
if (configRoot.back() != '/')
{
configRoot += '/';
}
// 相机名称与对应存储指针的映射: Cam1=BCA, Cam2=BTA, Cam3=DCA, Cam4=DTA
struct CamPathMapping
{
std::string camName;
std::string *targetPath;
};
CamPathMapping mappings[] = {
{"BCA", &m_system_param.Analysis_Config_path},
{"BTA", &m_system_param.Analysis_Config_path_Cam2},
{"DCA", &m_system_param.Analysis_Config_path_Cam3},
{"DTA", &m_system_param.Analysis_Config_path_Cam4},
};
// 先清空所有路径
for (auto &m : mappings)
{
*m.targetPath = "";
}
if (!std::filesystem::exists(configRoot))
{
printf("ScanConfigPaths: directory does not exist: %s\n", configRoot.c_str());
return -1;
}
int foundCount = 0;
for (const auto &entry : std::filesystem::directory_iterator(configRoot))
{
if (!entry.is_regular_file())
continue;
std::string filename = entry.path().filename().string();
// 匹配 param_XXX.json 格式
if (filename.size() < 11) // "param_X.json" 最少 11 字符
continue;
if (filename.substr(0, 6) != "param_" || filename.substr(filename.size() - 5) != ".json")
continue;
// 提取相机名称: param_BCA.json → BCA
std::string camName = filename.substr(6, filename.size() - 11);
for (auto &m : mappings)
{
if (camName == m.camName)
{
*m.targetPath = entry.path().string();
printf("ScanConfigPaths: auto-discovered %s config → %s\n",
m.camName.c_str(), m.targetPath->c_str());
foundCount++;
break;
}
}
}
printf("ScanConfigPaths: found %d param config file(s) in %s\n", foundCount, configRoot.c_str());
return foundCount > 0 ? 0 : -1;
}
int deal::GetJcImageInfo(std::string strpath, std::vector<JC_IMAGE_INFO_> &jcImageInfoList)
{
LoadOfflineCheckImg(strpath);
@ -1117,7 +1191,7 @@ int deal::GetDetImageInfo(std::string strProductID, std::string strSearchImg, st
std::string strs1 = strSearchImg + "/*.ytimage";
if(!runConfig.bdecode)
{
strs1 = strSearchImg + "/*.png";
strs1 = strSearchImg + "/*.jpg";
cv::glob(strs1, img_paths, true);
}
cv::glob(strs1, img_paths, true);
@ -1162,14 +1236,22 @@ int deal::GetImgInfo_POL_ET(std::string strImgPath, JC_IMAGE_INFO_ *pImageInfo)
std::string strImageChannel;
// 使用 find 检查是否包含 "CA" 或 "TA"
if (strName.find("CA") != std::string::npos || strName.find("CF") != std::string::npos)
// 使用 find 检查是否包含 4 个相机标识 (BCA, BTA, DCA, DTA) 及兼容旧 CA/TA
if (strName.find("BCA") != std::string::npos)
{
strImageChannel = "CA";
strImageChannel = "BCA";
}
else if (strName.find("TA") != std::string::npos || strName.find("TFT") != std::string::npos)
else if (strName.find("BTA") != std::string::npos)
{
strImageChannel = "TA";
strImageChannel = "BTA";
}
else if (strName.find("DCA") != std::string::npos)
{
strImageChannel = "DCA";
}
else if (strName.find("DTA") != std::string::npos)
{
strImageChannel = "DTA";
}
else
{
@ -1582,13 +1664,21 @@ int deal::ReJson_product(std::vector<Json_Det_Path> &jsonList, std::string strPr
std::cout << str1 << std::endl;
std::cout << str2 << std::endl;
if (str1.find("CA") != std::string::npos)
if (str1.find("BCA") != std::string::npos)
{
str2 = "BCA";
}
else if (str1.find("BTA") != std::string::npos)
{
str2 = "BTA";
}
else if (str1.find("DCA") != std::string::npos)
{
str2 = "CA";
str2 = "DCA";
}
else if (str1.find("TA") != std::string::npos)
else if (str1.find("DTA") != std::string::npos)
{
str2 = "TA";
str2 = "DTA";
}
else
{
@ -2286,11 +2376,27 @@ int deal::Det_OneProduct_Cell_ET(std::string strProductName, int Idx)
int AllImgNum = detImgInfoList.size();
// 遍历所有图片 开始读图处理
for (int i = 0; i < detImgInfoList.size(); i++)
// 按相机分组,同相机内每两张配对 (BCAA+BCAB 拼接送检,与 preCheck 一致)
std::map<std::string, std::vector<int>> camGroups;
for (int i = 0; i < (int)detImgInfoList.size(); i++)
{
std::shared_ptr<JC_IMAGE_INFO_> tem = std::make_shared<JC_IMAGE_INFO_>();
tem->copy(detImgInfoList.at(i));
InsertReadImgInfo(tem);
camGroups[detImgInfoList[i].strCamID].push_back(i);
}
AllImgNum = 0;
for (auto& kv : camGroups)
{
auto& indices = kv.second;
for (size_t i = 0; i < indices.size(); i += 2)
{
std::shared_ptr<JC_IMAGE_INFO_> tem = std::make_shared<JC_IMAGE_INFO_>();
tem->copy(detImgInfoList.at(indices[i]));
if (i + 1 < indices.size())
{
tem->strPath_B = detImgInfoList.at(indices[i + 1]).strPath;
}
InsertReadImgInfo(tem);
AllImgNum++;
}
}
m_DetResult.startTime_S = getcurTime();
IN_IMG_Status_ temstatus = IN_IMG_Status_Start;

@ -578,6 +578,8 @@ private:
void GetDealResultToQueu();
// 加载系统配置文件
bool ReadSystemConfig(const std::string &strPath);
// 从 Config_Root_Path 自动扫描匹配 param_*.json 文件
int ScanConfigPaths();
int GetJcImageInfo(std::string strpath, std::vector<JC_IMAGE_INFO_> &jcImageInfoList);
int ReadTestImgaData();

@ -0,0 +1,32 @@
#include "TcsCheck.h"
int main(int argc, char* argv[])
{
CHECK_PARAM cp;
cp.nAreaLowFilter = 80;
cp.nBlockSize = 100;
cp.nDiscardTop = 200;
cp.nDiscardBottom = 200;
cp.nDiscardLeft = 300;
cp.nDiscardRight = 600;
cp.fZoomRatio = 0.25;
cp.nFilterLow = 15;
cp.nFilterHigh = 15;
cp.nAreaFilter = 10;
cp.nCountFilter = 50;
CTcsCheck detect;
detect.SetChecConfig(&cp);
std::string strDirIn = "/home/aidlux/cbh/CellAoiTcs/TestImage/G3A00M62293829AL03";
std::string strDirOut = "/home/aidlux/cbh/CellAoiTcs/OutImage";
detect.SetCheckDir(strDirIn, strDirOut);
std::cout << "Image start process" << std::endl;
detect.ProcessImages(true);
std::cout << "Image finished " << std::endl;
return 0;
}
Loading…
Cancel
Save