|
|
|
@ -223,10 +223,21 @@ void CTcsCheck::ClassifyBlobs(const cv::Mat& blurCrop, const cv::Mat& imgBlob)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
m_vecDefectInfo.clear();
|
|
|
|
m_vecDefectInfo.clear();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 分步计时:记录每个步骤的耗时(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 << "[ClassifyBlobs] " << step << " 耗时(ms): " << elapsedMs << std::endl;
|
|
|
|
|
|
|
|
tPrev = tNow;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
cv::Mat labels;
|
|
|
|
cv::Mat labels;
|
|
|
|
cv::Mat stats;
|
|
|
|
cv::Mat stats;
|
|
|
|
cv::Mat centroids;
|
|
|
|
cv::Mat centroids;
|
|
|
|
const int nLabels = cv::connectedComponentsWithStats(imgBlob, labels, stats, centroids, 8, CV_32S);
|
|
|
|
const int nLabels = cv::connectedComponentsWithStats(imgBlob, labels, stats, centroids, 8, CV_32S);
|
|
|
|
|
|
|
|
// logStep("1.连通域分析");
|
|
|
|
if (nLabels <= 1) return;
|
|
|
|
if (nLabels <= 1) return;
|
|
|
|
|
|
|
|
|
|
|
|
struct BlobItem { int label; int area; };
|
|
|
|
struct BlobItem { int label; int area; };
|
|
|
|
@ -238,11 +249,13 @@ void CTcsCheck::ClassifyBlobs(const cv::Mat& blurCrop, const cv::Mat& imgBlob)
|
|
|
|
blobs.push_back({ label, area });
|
|
|
|
blobs.push_back({ label, area });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// logStep("2.面积过滤");
|
|
|
|
if (blobs.empty()) return;
|
|
|
|
if (blobs.empty()) return;
|
|
|
|
|
|
|
|
|
|
|
|
std::sort(blobs.begin(), blobs.end(), [](const BlobItem& a, const BlobItem& b) {
|
|
|
|
std::sort(blobs.begin(), blobs.end(), [](const BlobItem& a, const BlobItem& b) {
|
|
|
|
return a.area > b.area;
|
|
|
|
return a.area > b.area;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// logStep("3.面积排序");
|
|
|
|
|
|
|
|
|
|
|
|
// 大块缺陷的面积阈值
|
|
|
|
// 大块缺陷的面积阈值
|
|
|
|
const int largeAreaThreshold = std::max(1, m_cpCfg.nBlockSize * m_cpCfg.nBlockSize / 4);
|
|
|
|
const int largeAreaThreshold = std::max(1, m_cpCfg.nBlockSize * m_cpCfg.nBlockSize / 4);
|
|
|
|
@ -307,6 +320,14 @@ void CTcsCheck::ClassifyBlobs(const cv::Mat& blurCrop, const cv::Mat& imgBlob)
|
|
|
|
info.strDefectDesc = DEFECT_TYPE_DESC[defectType];
|
|
|
|
info.strDefectDesc = DEFECT_TYPE_DESC[defectType];
|
|
|
|
m_vecDefectInfo.push_back(info);
|
|
|
|
m_vecDefectInfo.push_back(info);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// logStep("4.缺陷分类决策");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// // 总耗时
|
|
|
|
|
|
|
|
// {
|
|
|
|
|
|
|
|
// auto tEnd = std::chrono::high_resolution_clock::now();
|
|
|
|
|
|
|
|
// double totalMs = std::chrono::duration_cast<std::chrono::milliseconds>(tEnd - tStart).count();
|
|
|
|
|
|
|
|
// std::cout << "[ClassifyBlobs] 总耗时(ms): " << totalMs << std::endl;
|
|
|
|
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
// ============================================================
|
|
|
|
@ -421,6 +442,8 @@ int CTcsCheck::TraditionalDetect(const cv::Mat& img, cv::Rect detRoi, cv::Mat& b
|
|
|
|
return -1;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 缓存裁剪区域,供 TraditionalClassify 将小图坐标映射回原图
|
|
|
|
|
|
|
|
m_rtCrop = rtCrop;
|
|
|
|
// logStep("4.安全裁剪");
|
|
|
|
// logStep("4.安全裁剪");
|
|
|
|
|
|
|
|
|
|
|
|
cv::Mat matCrop = m_matLoad(rtCrop);
|
|
|
|
cv::Mat matCrop = m_matLoad(rtCrop);
|
|
|
|
@ -468,17 +491,41 @@ int CTcsCheck::TraditionalDetect(const cv::Mat& img, cv::Rect detRoi, cv::Mat& b
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
// ============================================================
|
|
|
|
// TraditionalClassify — 独立分类接口
|
|
|
|
// TraditionalClassify — 独立分类接口
|
|
|
|
// 对残点二值图做连通域分析+缺陷分类决策树
|
|
|
|
// 优先在小图缓存(m_matBlur/m_matBlob)上做连通域分析+缺陷分类,
|
|
|
|
// 前提: 已调用 TraditionalDetect(blobImg 已与 m_matLoad 同尺寸)
|
|
|
|
// 再将结果坐标映射回原图,避免对全尺寸 mask 做昂贵计算。
|
|
|
|
// 使用没有任何处理的原图 m_matLoad 计算灰阶差
|
|
|
|
|
|
|
|
// 返回: 分类到的缺陷数量,<0 表示异常
|
|
|
|
// 返回: 分类到的缺陷数量,<0 表示异常
|
|
|
|
// ============================================================
|
|
|
|
// ============================================================
|
|
|
|
int CTcsCheck::TraditionalClassify(const cv::Mat& blobImg)
|
|
|
|
int CTcsCheck::TraditionalClassify(const cv::Mat& blobImg)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
|
|
|
|
// 全尺寸 mask 仅是小图 INTER_NEAREST 放大结果,连通域数量/形状完全等价,
|
|
|
|
|
|
|
|
// 在小图上分类可避免对全尺寸图做昂贵的 connectedComponentsWithStats。
|
|
|
|
|
|
|
|
if (m_matBlur.empty() || m_matBlob.empty())
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// 回退:无小图缓存时,退化为全尺寸分类(旧行为)
|
|
|
|
if (m_matLoad.empty() || blobImg.empty()) return -1;
|
|
|
|
if (m_matLoad.empty() || blobImg.empty()) return -1;
|
|
|
|
|
|
|
|
|
|
|
|
// 不再为全尺寸 mask 做一次 clone,直接使用 blobImg 分类
|
|
|
|
|
|
|
|
ClassifyBlobs(m_matLoad, blobImg);
|
|
|
|
ClassifyBlobs(m_matLoad, blobImg);
|
|
|
|
|
|
|
|
return static_cast<int>(m_vecDefectInfo.size());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto tStart = std::chrono::high_resolution_clock::now();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ClassifyBlobs(m_matBlur, m_matBlob);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 小图坐标/面积映射回原图坐标系,与下游 blobs.blobTab(全尺寸坐标)对齐
|
|
|
|
|
|
|
|
const double scaleX = (m_matBlob.cols > 0) ? static_cast<double>(m_rtCrop.width) / m_matBlob.cols : 1.0;
|
|
|
|
|
|
|
|
const double scaleY = (m_matBlob.rows > 0) ? static_cast<double>(m_rtCrop.height) / m_matBlob.rows : 1.0;
|
|
|
|
|
|
|
|
for (auto& info : m_vecDefectInfo)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
info.nDefectX = static_cast<int>(info.nDefectX * scaleX + 0.5) + m_rtCrop.x;
|
|
|
|
|
|
|
|
info.nDefectY = static_cast<int>(info.nDefectY * scaleY + 0.5) + m_rtCrop.y;
|
|
|
|
|
|
|
|
info.nDefectWidth = std::max(1, static_cast<int>(info.nDefectWidth * scaleX + 0.5));
|
|
|
|
|
|
|
|
info.nDefectHeight = std::max(1, static_cast<int>(info.nDefectHeight * scaleY + 0.5));
|
|
|
|
|
|
|
|
info.nDefectArea = std::max(1, static_cast<int>(info.nDefectArea * scaleX * scaleY + 0.5));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto tEnd = std::chrono::high_resolution_clock::now();
|
|
|
|
|
|
|
|
double totalMs = std::chrono::duration_cast<std::chrono::milliseconds>(tEnd - tStart).count();
|
|
|
|
|
|
|
|
std::cout << "[TraditionalClassify] 总耗时(ms): " << totalMs << std::endl;
|
|
|
|
|
|
|
|
|
|
|
|
return static_cast<int>(m_vecDefectInfo.size());
|
|
|
|
return static_cast<int>(m_vecDefectInfo.size());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|