feat 优化传统检测

dev_lsy
liusiyang 4 days ago
parent 5d56785769
commit e138c826a5

@ -126,6 +126,8 @@ private:
cv::Rect GetBoundingRect(cv::Mat matBinary);
cv::Rect GetCropArea(cv::Rect rtValid);
cv::Mat AdaptiveBinary(cv::Mat matBlur);
// 局部背景基准图(大窗口中值),用于分块动态阈值的阈值中心
cv::Mat EstimateBackgroundMap(const cv::Mat& matGray, int blockSize);
void Process(bool bDraw = false);

@ -44,7 +44,12 @@ void CTcsCheck::SetCheckDir(std::string dirIn,std::string dirOut)
}
void CTcsCheck::SetChecConfig(CHECK_PARAM* cp)
{
memcpy(&m_cpCfg,cp,sizeof(CHECK_PARAM));
// CHECK_PARAM 内含 std::string不能用 memcpymemcpy 会把源对象 string 内部的
// _M_data 指针一并拷过来,于是拷贝出的 string 认为自己持有堆内存,
// 析构时会 free 到源对象的栈缓冲(短串)或重复释放(长串),随机崩溃。
// 整体赋值(拷贝构造/赋值)才是正确做法。
if (cp == nullptr) return;
m_cpCfg = *cp;
}
std::string CTcsCheck::GetFileName(const std::string& path) const
{
@ -145,33 +150,82 @@ cv::Rect CTcsCheck::GetCropArea(cv::Rect rtValid)
}
return innerRect;
}
// ============================================================
// EstimateBackgroundMap — 局部背景基准图(抗大缺陷污染)
//
// 旧实现用「块内灰度均值」当阈值中心,缺陷一旦覆盖块内较大面积,
// 均值就会被缺陷灰度带偏:
// · 均值落在「背景峰」与「缺陷峰」之间的空隙 → 容忍带内一个像素都没有
// → 反相后整块变白(大面积误检,缺陷其实没占满块);
// · 均值贴近缺陷灰度 → 缺陷被判成背景、背景反被判成缺陷(极性翻转)。
//
// 这里改用「中值」估计背景:中值一定是窗口内真实存在的灰度值,
// 只要缺陷不占到窗口一半以上就不会被带偏。窗口取 3 倍块边长(覆盖 3×3 个块),
// 因此单块甚至相邻几块内的大缺陷都污染不了该块的背景基准。
//
// 为控制耗时,先在 1/ds 缩略图上做中值(等效窗口 = ds × 粗粒度核),
// 再插值放大回原尺寸 —— 背景本身是低频的,放大误差可忽略。
// 实测 1500×3750全分辨率大核中值 ~146ms降采样后 ~10ms。
// ============================================================
cv::Mat CTcsCheck::EstimateBackgroundMap(const cv::Mat& matGray, int blockSize)
{
cv::Mat gray8;
if (matGray.type() == CV_8UC1) {
gray8 = matGray;
} else {
matGray.convertTo(gray8, CV_8UC1);
}
if (gray8.empty()) return cv::Mat();
const int bgWinScale = 3; // 背景估计窗口 = 3 倍块边长
const int kMaxSafe = 201; // medianBlur 大核上限(本机 OpenCV 4.5 实测 >301 断言失败)
// 降采样倍数:保证粗粒度核不超过 kMaxSafe
int ds = 4;
while (bgWinScale * blockSize / ds > kMaxSafe) ds *= 2;
// 缩略图INTER_AREA 即块内均值,缺陷只会让个别单元变成离群点,中值可剔除
cv::Mat small;
cv::resize(gray8, small,
cv::Size(std::max(1, gray8.cols / ds), std::max(1, gray8.rows / ds)), 0, 0, cv::INTER_AREA);
int k = bgWinScale * std::max(1, blockSize / ds);
if (k % 2 == 0) ++k;
if (k < 3) k = 3; // 允许 k 大于缩略图尺寸medianBlur 会按边界复制处理
cv::Mat smallBg;
cv::medianBlur(small, smallBg, k);
cv::Mat matBg;
cv::resize(smallBg, matBg, gray8.size(), 0, 0, cv::INTER_LINEAR);
return matBg;
}
cv::Mat CTcsCheck::AdaptiveBinary(cv::Mat matBlur)
{
// 按 localBlockSize_ 分块,使用每个块的局部均值做动态阈值。
// 当前规则:落在 [avg-lower_, avg+upper_] 内置 0超出置 255。
// 按 nBlockSize 分块,用每个块的局部背景基准做动态阈值。
// 规则:落在 [ref-low, ref+high] 内置 0超出置 255。
// 注意 ref 取「大窗口中值背景」而非块内均值,避免大缺陷把阈值中心带偏导致整块变白。
if (matBlur.empty()) return cv::Mat();
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);
}
// 块边长不超过图像尺寸;同步回 m_cpCfg保证 ClassifyBlobs 的块网格一致
const int blockSize = std::max(1, std::min(m_cpCfg.nBlockSize, std::min(matBlur.cols, matBlur.rows)));
m_cpCfg.nBlockSize = blockSize;
// 局部背景基准图(大窗口中值)
const cv::Mat matBg = EstimateBackgroundMap(matBlur, blockSize);
for (int y = 0; y < matBlur.rows; y += blockSize) {
for (int x = 0; x < matBlur.cols; x += blockSize) {
// 末列/末行窗口贴边对齐(与相邻窗口重叠),不产生残缺块
const int bx = std::min(x, std::max(0, matBlur.cols - blockSize));
const int by = std::min(y, std::max(0, matBlur.rows - blockSize));
const cv::Rect blockRect(bx, by, blockSize, blockSize);
cv::Mat block = matBlur(blockRect);
const double avg = cv::mean(block)[0];
// 背景基准:取该块范围内背景图的均值(抹平中值图的量化台阶)
const double avg = matBg.empty() ? cv::mean(block)[0] : cv::mean(matBg(blockRect))[0];
const double minGrey = std::max(0.0, avg - m_cpCfg.nFilterLow);
const double maxGrey = std::min(255.0, avg + m_cpCfg.nFilterHigh);
@ -183,7 +237,7 @@ cv::Mat CTcsCheck::AdaptiveBinary(cv::Mat matBlur)
// 传统检测残点图存图
if (m_cpCfg.bDebugsaveImg)
{
std::string roi_ext = "[" + std::to_string(x) + "," + std::to_string(y) + "]";
std::string roi_ext = "[" + std::to_string(bx) + "," + std::to_string(by) + "]";
std::string strSaveDir = "/home/aidlux/BOE/CELL_AOI/AI_Detect/" + m_cpCfg.productId + "/";
std::string strSavePath_in = strSaveDir + m_cpCfg.productChannel +"_"+ roi_ext + "_"+ "in" + ".png";
std::string strSavePath_out = strSaveDir + m_cpCfg.productChannel +"_"+ roi_ext + "_"+ "out" + ".png";

Loading…
Cancel
Save