|
|
|
@ -44,7 +44,12 @@ void CTcsCheck::SetCheckDir(std::string dirIn,std::string dirOut)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void CTcsCheck::SetChecConfig(CHECK_PARAM* cp)
|
|
|
|
void CTcsCheck::SetChecConfig(CHECK_PARAM* cp)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
memcpy(&m_cpCfg,cp,sizeof(CHECK_PARAM));
|
|
|
|
// CHECK_PARAM 内含 std::string,不能用 memcpy:memcpy 会把源对象 string 内部的
|
|
|
|
|
|
|
|
// _M_data 指针一并拷过来,于是拷贝出的 string 认为自己持有堆内存,
|
|
|
|
|
|
|
|
// 析构时会 free 到源对象的栈缓冲(短串)或重复释放(长串),随机崩溃。
|
|
|
|
|
|
|
|
// 整体赋值(拷贝构造/赋值)才是正确做法。
|
|
|
|
|
|
|
|
if (cp == nullptr) return;
|
|
|
|
|
|
|
|
m_cpCfg = *cp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
std::string CTcsCheck::GetFileName(const std::string& path) const
|
|
|
|
std::string CTcsCheck::GetFileName(const std::string& path) const
|
|
|
|
{
|
|
|
|
{
|
|
|
|
@ -145,33 +150,82 @@ cv::Rect CTcsCheck::GetCropArea(cv::Rect rtValid)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return innerRect;
|
|
|
|
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)
|
|
|
|
cv::Mat CTcsCheck::AdaptiveBinary(cv::Mat matBlur)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
// 按 localBlockSize_ 分块,使用每个块的局部均值做动态阈值。
|
|
|
|
// 按 nBlockSize 分块,用每个块的局部背景基准做动态阈值。
|
|
|
|
// 当前规则:落在 [avg-lower_, avg+upper_] 内置 0,超出置 255。
|
|
|
|
// 规则:落在 [ref-low, ref+high] 内置 0,超出置 255。
|
|
|
|
|
|
|
|
// 注意 ref 取「大窗口中值背景」而非块内均值,避免大缺陷把阈值中心带偏导致整块变白。
|
|
|
|
|
|
|
|
if (matBlur.empty()) return cv::Mat();
|
|
|
|
cv::Mat result = cv::Mat::zeros(matBlur.size(), CV_8UC1);
|
|
|
|
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);
|
|
|
|
// 块边长不超过图像尺寸;同步回 m_cpCfg,保证 ClassifyBlobs 的块网格一致
|
|
|
|
for (int y = 0; y < matBlur.rows; y += m_cpCfg.nBlockSize) {
|
|
|
|
const int blockSize = std::max(1, std::min(m_cpCfg.nBlockSize, std::min(matBlur.cols, matBlur.rows)));
|
|
|
|
for (int x = 0; x < matBlur.cols; x += m_cpCfg.nBlockSize) {
|
|
|
|
m_cpCfg.nBlockSize = blockSize;
|
|
|
|
// 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);
|
|
|
|
const cv::Mat matBg = EstimateBackgroundMap(matBlur, blockSize);
|
|
|
|
if(x + m_cpCfg.nBlockSize > matBlur.cols)
|
|
|
|
|
|
|
|
{
|
|
|
|
for (int y = 0; y < matBlur.rows; y += blockSize) {
|
|
|
|
blockRect = cv::Rect(matBlur.cols - m_cpCfg.nBlockSize, y, m_cpCfg.nBlockSize, m_cpCfg.nBlockSize);
|
|
|
|
for (int x = 0; x < matBlur.cols; x += blockSize) {
|
|
|
|
}
|
|
|
|
// 末列/末行窗口贴边对齐(与相邻窗口重叠),不产生残缺块
|
|
|
|
if(y + m_cpCfg.nBlockSize > matBlur.rows)
|
|
|
|
const int bx = std::min(x, std::max(0, matBlur.cols - blockSize));
|
|
|
|
{
|
|
|
|
const int by = std::min(y, std::max(0, matBlur.rows - blockSize));
|
|
|
|
blockRect = cv::Rect(x, matBlur.rows - m_cpCfg.nBlockSize, m_cpCfg.nBlockSize, m_cpCfg.nBlockSize);
|
|
|
|
const cv::Rect blockRect(bx, by, blockSize, blockSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
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 minGrey = std::max(0.0, avg - m_cpCfg.nFilterLow);
|
|
|
|
const double maxGrey = std::min(255.0, avg + m_cpCfg.nFilterHigh);
|
|
|
|
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)
|
|
|
|
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 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_in = strSaveDir + m_cpCfg.productChannel +"_"+ roi_ext + "_"+ "in" + ".png";
|
|
|
|
std::string strSavePath_out = strSaveDir + m_cpCfg.productChannel +"_"+ roi_ext + "_"+ "out" + ".png";
|
|
|
|
std::string strSavePath_out = strSaveDir + m_cpCfg.productChannel +"_"+ roi_ext + "_"+ "out" + ".png";
|
|
|
|
|