|
|
|
@ -225,9 +225,9 @@ void CTcsCheck::ClassifyBlobs(const cv::Mat& blurCrop, const cv::Mat& imgBlob)
|
|
|
|
const int w = stats.at<int>(label, cv::CC_STAT_WIDTH);
|
|
|
|
const int w = stats.at<int>(label, cv::CC_STAT_WIDTH);
|
|
|
|
const int h = stats.at<int>(label, cv::CC_STAT_HEIGHT);
|
|
|
|
const int h = stats.at<int>(label, cv::CC_STAT_HEIGHT);
|
|
|
|
|
|
|
|
|
|
|
|
// blob 区域均值
|
|
|
|
// blob 区域均值:仅在 bounding rect 局部计算,避免全图 mask
|
|
|
|
cv::Mat blobMask = (labels == label);
|
|
|
|
cv::Mat blobMask = (labels(cv::Rect(x, y, w, h)) == label);
|
|
|
|
double blobMean = cv::mean(blurCrop, blobMask)[0];
|
|
|
|
double blobMean = cv::mean(blurCrop(cv::Rect(x, y, w, h)), blobMask)[0];
|
|
|
|
|
|
|
|
|
|
|
|
// 所属局部块均值
|
|
|
|
// 所属局部块均值
|
|
|
|
const double cx = centroids.at<double>(label, 0);
|
|
|
|
const double cx = centroids.at<double>(label, 0);
|
|
|
|
@ -338,6 +338,16 @@ int CTcsCheck::TraditionalDetect(const cv::Mat& img, cv::Rect detRoi, cv::Mat& b
|
|
|
|
return -1;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 分步计时:记录每个步骤的耗时(ms)
|
|
|
|
|
|
|
|
auto tStart = std::chrono::high_resolution_clock::now();
|
|
|
|
|
|
|
|
auto tPrev = tStart;
|
|
|
|
|
|
|
|
auto logStep = [&](const std::string& step) {
|
|
|
|
|
|
|
|
auto tNow = std::chrono::high_resolution_clock::now();
|
|
|
|
|
|
|
|
double elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(tNow - tPrev).count();
|
|
|
|
|
|
|
|
std::cout << "[TraditionalDetect] " << step << " 耗时(ms): " << elapsedMs << std::endl;
|
|
|
|
|
|
|
|
tPrev = tNow;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// getchar();
|
|
|
|
// getchar();
|
|
|
|
// cv::Mat showImg = img.clone();
|
|
|
|
// cv::Mat showImg = img.clone();
|
|
|
|
// cv::rectangle(showImg, detRoi, cv::Scalar(255), 2);
|
|
|
|
// cv::rectangle(showImg, detRoi, cv::Scalar(255), 2);
|
|
|
|
@ -352,6 +362,7 @@ int CTcsCheck::TraditionalDetect(const cv::Mat& img, cv::Rect detRoi, cv::Mat& b
|
|
|
|
// 1. 全局阈值 → 产品区域定位
|
|
|
|
// 1. 全局阈值 → 产品区域定位
|
|
|
|
cv::Mat matBinary;
|
|
|
|
cv::Mat matBinary;
|
|
|
|
cv::threshold(m_matLoad, matBinary, m_cpCfg.nAreaLowFilter, 255, cv::THRESH_BINARY);
|
|
|
|
cv::threshold(m_matLoad, matBinary, m_cpCfg.nAreaLowFilter, 255, cv::THRESH_BINARY);
|
|
|
|
|
|
|
|
// logStep("1.全局阈值");
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 获取最大连通域外接矩形
|
|
|
|
// 2. 获取最大连通域外接矩形
|
|
|
|
cv::Rect rtValid = GetBoundingRect(matBinary);
|
|
|
|
cv::Rect rtValid = GetBoundingRect(matBinary);
|
|
|
|
@ -360,9 +371,11 @@ int CTcsCheck::TraditionalDetect(const cv::Mat& img, cv::Rect detRoi, cv::Mat& b
|
|
|
|
blobImg = cv::Mat();
|
|
|
|
blobImg = cv::Mat();
|
|
|
|
return -1;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// logStep("2.最大连通域外接矩形");
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 裁剪边缘
|
|
|
|
// 3. 裁剪边缘
|
|
|
|
rtCrop = GetCropArea(rtValid);
|
|
|
|
rtCrop = GetCropArea(rtValid);
|
|
|
|
|
|
|
|
// logStep("3.裁剪边缘");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 安全裁剪: 确保 rtCrop 不超出 m_matLoad 边界
|
|
|
|
// 安全裁剪: 确保 rtCrop 不超出 m_matLoad 边界
|
|
|
|
@ -375,53 +388,46 @@ int CTcsCheck::TraditionalDetect(const cv::Mat& img, cv::Rect detRoi, cv::Mat& b
|
|
|
|
return -1;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// logStep("4.安全裁剪");
|
|
|
|
|
|
|
|
|
|
|
|
cv::Mat matCrop = m_matLoad(rtCrop).clone();
|
|
|
|
cv::Mat matCrop = m_matLoad(rtCrop);
|
|
|
|
|
|
|
|
// logStep("5.ROI裁剪");
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 缩放
|
|
|
|
// 6. 缩放
|
|
|
|
const int outW = std::max(1, static_cast<int>(matCrop.cols * m_cpCfg.fZoomRatio));
|
|
|
|
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));
|
|
|
|
const int outH = std::max(1, static_cast<int>(matCrop.rows * m_cpCfg.fZoomRatio));
|
|
|
|
cv::Mat matResized;
|
|
|
|
cv::Mat matResized;
|
|
|
|
cv::resize(matCrop, matResized, cv::Size(outW, outH), 0, 0, cv::INTER_AREA);
|
|
|
|
cv::resize(matCrop, matResized, cv::Size(outW, outH), 0, 0, cv::INTER_AREA);
|
|
|
|
|
|
|
|
// logStep("6.缩放");
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 高斯模糊 — 缓存到 m_matBlur
|
|
|
|
// 7. 高斯模糊 — 缓存到 m_matBlur
|
|
|
|
cv::GaussianBlur(matResized, m_matBlur, cv::Size(5, 5), 0);
|
|
|
|
cv::GaussianBlur(matResized, m_matBlur, cv::Size(5, 5), 0);
|
|
|
|
// cv::imwrite("matResized.png", matResized);
|
|
|
|
// cv::imwrite("matResized.png", matResized);
|
|
|
|
// cv::imwrite("m_matBlur.png", m_matBlur);
|
|
|
|
// cv::imwrite("m_matBlur.png", m_matBlur);
|
|
|
|
// 6. 自适应二值化检测
|
|
|
|
// logStep("7.高斯模糊");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 8. 自适应二值化检测
|
|
|
|
m_matBlob = AdaptiveBinary(m_matBlur);
|
|
|
|
m_matBlob = AdaptiveBinary(m_matBlur);
|
|
|
|
blobImg = m_matBlob.clone();
|
|
|
|
blobImg = m_matBlob;
|
|
|
|
|
|
|
|
// logStep("8.自适应二值化");
|
|
|
|
|
|
|
|
|
|
|
|
// 7. 反向映射:将 crop+resize 后的残点图恢复为原始输入图尺寸
|
|
|
|
// 9. 反向映射:只生成下游需要的全尺寸残点 mask(resultMaskImg / Blob 计算)。
|
|
|
|
// 调用方拿到的是与输入 img 同尺寸的 mask,坐标体系一致
|
|
|
|
// 直接 resize 到 ROI 上,省去中间 matCropSize 缓冲与 copyTo;
|
|
|
|
|
|
|
|
// m_matBlob 保持小图即可,全尺寸副本无任何消费者,已移除。
|
|
|
|
if (m_cpCfg.fZoomRatio > 0)
|
|
|
|
if (m_cpCfg.fZoomRatio > 0)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
// 7a. 反向映射 blobImg(二值 mask,用 INTER_NEAREST)
|
|
|
|
cv::Mat fullMask = cv::Mat::zeros(m_matLoad.size(), CV_8UC1);
|
|
|
|
{
|
|
|
|
cv::resize(m_matBlob, fullMask(rtCrop), cv::Size(rtCrop.width, rtCrop.height), 0, 0, cv::INTER_NEAREST);
|
|
|
|
cv::Mat matCropSize;
|
|
|
|
blobImg = fullMask;
|
|
|
|
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)
|
|
|
|
// logStep("9.反向映射(mask)");
|
|
|
|
{
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
auto tEnd = std::chrono::high_resolution_clock::now();
|
|
|
|
cv::resize(m_matBlob, matCropSize, cv::Size(rtCrop.width, rtCrop.height), 0, 0, cv::INTER_NEAREST);
|
|
|
|
double totalMs = std::chrono::duration_cast<std::chrono::milliseconds>(tEnd - tStart).count();
|
|
|
|
cv::Mat fullBlob = cv::Mat::zeros(m_matLoad.size(), CV_8UC1);
|
|
|
|
// std::cout << "[TraditionalDetect] 总耗时(ms): " << totalMs << std::endl;
|
|
|
|
matCropSize.copyTo(fullBlob(rtCrop));
|
|
|
|
|
|
|
|
m_matBlob = fullBlob;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
return 0;
|
|
|
|
@ -430,15 +436,16 @@ int CTcsCheck::TraditionalDetect(const cv::Mat& img, cv::Rect detRoi, cv::Mat& b
|
|
|
|
// ============================================================
|
|
|
|
// ============================================================
|
|
|
|
// TraditionalClassify — 独立分类接口
|
|
|
|
// TraditionalClassify — 独立分类接口
|
|
|
|
// 对残点二值图做连通域分析+缺陷分类决策树
|
|
|
|
// 对残点二值图做连通域分析+缺陷分类决策树
|
|
|
|
// 前提: 已调用 TraditionalDetect(m_matBlur 已缓存)
|
|
|
|
// 前提: 已调用 TraditionalDetect(blobImg 已与 m_matLoad 同尺寸)
|
|
|
|
|
|
|
|
// 使用没有任何处理的原图 m_matLoad 计算灰阶差
|
|
|
|
// 返回: 分类到的缺陷数量,<0 表示异常
|
|
|
|
// 返回: 分类到的缺陷数量,<0 表示异常
|
|
|
|
// ============================================================
|
|
|
|
// ============================================================
|
|
|
|
int CTcsCheck::TraditionalClassify(const cv::Mat& blobImg)
|
|
|
|
int CTcsCheck::TraditionalClassify(const cv::Mat& blobImg)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (m_matBlur.empty() || blobImg.empty()) return -1;
|
|
|
|
if (m_matLoad.empty() || blobImg.empty()) return -1;
|
|
|
|
|
|
|
|
|
|
|
|
m_matBlob = blobImg.clone();
|
|
|
|
// 不再为全尺寸 mask 做一次 clone,直接使用 blobImg 分类
|
|
|
|
ClassifyBlobs(m_matBlur, blobImg);
|
|
|
|
ClassifyBlobs(m_matLoad, blobImg);
|
|
|
|
|
|
|
|
|
|
|
|
return static_cast<int>(m_vecDefectInfo.size());
|
|
|
|
return static_cast<int>(m_vecDefectInfo.size());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|