update 优化内存泄漏

dev_lsy
liusiyang 2 months ago
parent db455bd0cb
commit 92850246a4

@ -7,12 +7,8 @@
// 背景:产品沿长度方向运动/扫描时速率不恒定(尾端有拉伸),像素↔mm 不是单一
// 比例,实测标定出 5 段,每段斜率不同。
//
// 分段表(分段点 = 像素,**以产品顶边为 0 点**;斜率为 mm/px):
// 0 ~ 10928 0.016869
// 10928 ~ 17002 0.016463 (6074px ↔ 100mm)
// 17002 ~ 23167 0.016220 (6165px ↔ 100mm)
// 23167 ~ 29549 0.015669 (6382px ↔ 100mm)
// >= 29549 0.014337
// 分段表(分段点 = 像素,**以产品顶边为 0 点**;斜率为 mm/px)见函数内
// yPx1..yPx5 / pmm1..pmm5,改标定只需改那一段(此处不重复写数值,避免两处不同步)。
// 分段点处前后两段取值相同,函数连续,不会在分段点跳变。
//
// 共用方(必须共用同一张表,否则"产品高度"和"缺陷坐标"会各用一把尺子):
@ -23,29 +19,48 @@
// ============================================================================
inline float PixelYToMm(float fYPixel)
{
// ================= 标定参数:改标定只需改这一段 =================
// 各段斜率(mm/px)
const float pmm1 = 0.016869;
const float pmm2 = 0.016463;
const float pmm3 = 0.016220;
const float pmm4 = 0.015669;
const float pmm5 = 0.014337;
// 各段起始像素(以产品顶边为 0 点)
const float yPx1 = 0.0f;
const float yPx2 = 10928.0f;
const float yPx3 = 17002.0f;
const float yPx4 = 23167.0f;
const float yPx5 = 29549.0f;
// 各段像素长度(px)
const float dPx1 = yPx2 - yPx1;
const float dPx2 = yPx3 - yPx2;
const float dPx3 = yPx4 - yPx3;
const float dPx4 = yPx5 - yPx4;
// 各段末端的累计物理长度(mm)
const float d1 = 10928 * 0.016869;
const float d2 = 6074 * 0.016463;
const float d3 = 6165 * 0.016220;
const float d4 = 6382 * 0.015669;
const float d1 = dPx1 * pmm1;
const float d2 = d1 + dPx2 * pmm2;
const float d3 = d2 + dPx3 * pmm3;
const float d4 = d3 + dPx4 * pmm4;
if (fYPixel < 10928.0f)
if (fYPixel < yPx2)
{
return fYPixel * 0.016869;
return fYPixel * pmm1;
}
if (fYPixel < 17002.0f)
if (fYPixel < yPx3)
{
return (fYPixel - 10928.0f) * 0.016463 + d1;
return (fYPixel - yPx2) * pmm2 + d1;
}
if (fYPixel < 23167.0f)
if (fYPixel < yPx4)
{
return (fYPixel - 10928.0f - 6074.0f) * 0.016220 + d1 + d2;
return (fYPixel - yPx3) * pmm3 + d2;
}
if (fYPixel < 29549.0f)
if (fYPixel < yPx5)
{
return (fYPixel - 10928.0f - 6074.0f - 6165.0f) * 0.015669 + d1 + d2 + d3;
return (fYPixel - yPx4) * pmm4 + d3;
}
return (fYPixel - 10928.0f - 6074.0f - 6165.0f - 6382.0f) * 0.014337 + d1 + d2 + d3 + d4;
return (fYPixel - yPx5) * pmm5 + d4;
}
#endif

@ -1968,6 +1968,12 @@ int ImgCheckAnalysisy::Traditional_Detect_Thread(const cv::Mat &img, cv::Mat &Re
Base_Function_TraditionDet traditionParam = m_pbaseCheckFunction->traditionDet;
{
CHECK_PARAM cp;
// 四边裁边量必须显式赋值:CHECK_PARAM 是 POD,未赋值的字段是栈垃圾,
// 一旦走到「自动定位产品」(detArea 未配置/覆盖整幅图)就会用垃圾值算裁剪框
cp.nDiscardTop = 170;
cp.nDiscardBottom = 170;
cp.nDiscardLeft = 180;
cp.nDiscardRight = 460;
cp.nAreaLowFilter = 80;
cp.nBlockSize = traditionParam.nBlockSize;
cp.fZoomRatio = traditionParam.fZoomRatio;

@ -101,8 +101,11 @@ void CTcsCheck::LoadImages()
CreateDirectories(m_strDirOut);
}
cv::Rect CTcsCheck::GetBoundingRect(cv::Mat matBinary)
// 在给定二值图上取「最大连通域的外接框」(输入需为 CV_8UC1)
static cv::Rect LargestComponentRect(const cv::Mat &matBinary)
{
if (matBinary.empty()) return cv::Rect(0, 0, 0, 0);
cv::Mat labels;
cv::Mat stats;
cv::Mat centroids;
@ -131,6 +134,67 @@ cv::Rect CTcsCheck::GetBoundingRect(cv::Mat matBinary)
const int h = stats.at<int>(maxLabel, cv::CC_STAT_HEIGHT);
return cv::Rect(x, y, w, h);
}
// 产品定位用的「最大连通域外接框」。
//
// connectedComponentsWithStats 的输出 labels 是 CV_32S —— 4 字节/像素:
// 22754x60000 的整幅图 → labels 单次就要 5.5 GB,实测该分支进程峰值 9.3 GB;
// 4 个相机并发时这是最容易把机器内存打爆的地方(整机 OOM 时最后失败的分配
// 往往落在 TensorRT 内嵌的 LLVM JIT 上,日志表现为 “LLVM ERROR: out of memory”)。
//
// 因此先在 1/ds 缩略图上定位最大连通域,再在原图对应局部窗口(外扩 2*ds)上精修:
// 最大块在缩略图上的外接框与原图外接框最多相差 ds 像素,外扩后窗口必然完整包含它,
// 所以精修结果与「直接对全图做 CC」一致,而 labels 内存降到 1/ds² 量级。
cv::Rect CTcsCheck::GetBoundingRect(cv::Mat matBinary)
{
if (matBinary.empty() || matBinary.type() != CV_8UC1)
{
return cv::Rect(0, 0, 0, 0);
}
const int ds = 8; // 缩略图倍数
const long long kCoarseMinPixels = 4LL * 1024 * 1024; // 小于 4M 像素直接走原逻辑
const long long imgPixels = static_cast<long long>(matBinary.cols) * matBinary.rows;
if (imgPixels <= kCoarseMinPixels)
{
return LargestComponentRect(matBinary);
}
// 1) 粗定位:INTER_AREA 后 >0 即视为该单元内有残点
cv::Mat small;
cv::resize(matBinary, small,
cv::Size(std::max(1, matBinary.cols / ds), std::max(1, matBinary.rows / ds)), 0, 0, cv::INTER_AREA);
cv::Mat smallBin;
cv::threshold(small, smallBin, 0, 255, cv::THRESH_BINARY);
const cv::Rect smallRect = LargestComponentRect(smallBin);
if (smallRect.width <= 0 || smallRect.height <= 0)
{
return cv::Rect(0, 0, 0, 0);
}
// 2) 缩略图坐标 → 原图坐标,外扩 2*ds 覆盖降采样带来的边界误差
const int pad = 2 * ds;
cv::Rect coarse(smallRect.x * ds - pad, smallRect.y * ds - pad,
(smallRect.width + 2) * ds + 2 * pad, (smallRect.height + 2) * ds + 2 * pad);
coarse = coarse & cv::Rect(0, 0, matBinary.cols, matBinary.rows);
// 粗定位明显不可信(外接框面积不足全图 0.1%)时退回全图 CC,保证行为不变
const long long coarseArea = static_cast<long long>(smallRect.width) * smallRect.height * ds * ds;
if (coarse.width <= 0 || coarse.height <= 0 || coarseArea * 1000 < imgPixels)
{
return LargestComponentRect(matBinary);
}
// 3) 在原图局部窗口内精修,得到像素级精确的外接框
const cv::Rect local = LargestComponentRect(matBinary(coarse));
if (local.width <= 0 || local.height <= 0)
{
return LargestComponentRect(matBinary);
}
return cv::Rect(coarse.x + local.x, coarse.y + local.y, local.width, local.height);
}
cv::Rect CTcsCheck::GetCropArea(cv::Rect rtValid)
{
// 在原图坐标系中对 ROI 四边分别内收对应的 discard 宽度。
@ -427,6 +491,13 @@ int CTcsCheck::TraditionalDetect(const cv::Mat& img, cv::Rect detRoi, cv::Mat& b
// 确保 m_sizeImage 始终与 m_matLoad 同步
m_sizeImage = img.size();
// 先把上一张图的中间结果清掉:任何提前 return 都不允许下游(TraditionalClassify)
// 误用旧图的 mask / ROI(坐标会整体错位,且会多攥住一份 GB 级缓冲)
m_matBlur.release();
m_matBlob.release();
m_rtCrop = cv::Rect(0, 0, 0, 0);
m_vecDefectInfo.clear();
cv::Rect rtCrop = detRoi;
if(detRoi.size() == img.size())
{
@ -439,6 +510,7 @@ int CTcsCheck::TraditionalDetect(const cv::Mat& img, cv::Rect detRoi, cv::Mat& b
cv::Rect rtValid = GetBoundingRect(matBinary);
if (rtValid == cv::Rect(0, 0, 0, 0))
{
m_matLoad.release(); // 不再需要引用 detImg
blobImg = cv::Mat();
return -1;
}
@ -455,6 +527,7 @@ int CTcsCheck::TraditionalDetect(const cv::Mat& img, cv::Rect detRoi, cv::Mat& b
rtCrop = rtCrop & imgRect;
if (rtCrop.width <= 0 || rtCrop.height <= 0)
{
m_matLoad.release(); // 不再需要引用 detImg
blobImg = cv::Mat();
return -1;
}
@ -485,17 +558,36 @@ int CTcsCheck::TraditionalDetect(const cv::Mat& img, cv::Rect detRoi, cv::Mat& b
// logStep("8.自适应二值化");
// 9. 反向映射:只生成下游需要的全尺寸残点 mask(resultMaskImg / Blob 计算)。
// 直接 resize 到 ROI 上,省去中间 matCropSize 缓冲与 copyTo;
// m_matBlob 保持小图即可,全尺寸副本无任何消费者,已移除。
// 下游 GetALLBlob / BLobToDetResult 都以 detImg 为坐标基准,所以 mask 必须与 detImg 等大,
// 且 ROI 之外必须是 0。
// 写法上不再用 cv::Mat::zeros(全图)(那会先给整幅图做一次 memset,22754x60000 时约 1.3 GB),
// 而是创建一个不初始化的缓冲,只把 ROI 之外的条带清零 —— 结果完全等价,省一次全图 memset。
if (m_cpCfg.fZoomRatio > 0)
{
cv::Mat fullMask = cv::Mat::zeros(m_matLoad.size(), CV_8UC1);
cv::Mat fullMask;
fullMask.create(m_matLoad.size(), CV_8UC1); // create 不做初始化
const int y0 = rtCrop.y;
const int y1 = rtCrop.y + rtCrop.height;
const int x0 = rtCrop.x;
const int x1 = rtCrop.x + rtCrop.width;
if (y0 > 0) fullMask.rowRange(0, y0).setTo(0);
if (y1 < fullMask.rows) fullMask.rowRange(y1, fullMask.rows).setTo(0);
cv::Mat mid = fullMask.rowRange(y0, y1);
if (x0 > 0) mid.colRange(0, x0).setTo(0);
if (x1 < mid.cols) mid.colRange(x1, mid.cols).setTo(0);
cv::resize(m_matBlob, fullMask(rtCrop), cv::Size(rtCrop.width, rtCrop.height), 0, 0, cv::INTER_NEAREST);
blobImg = fullMask;
// logStep("9.反向映射(mask)");
}
// 检测期间只需要引用 detImg,结束即松开:
// 否则 CTcsCheck 会一直攥住整幅检测图(GB 级)不放,上层即使释放 detImg 也回收不掉。
// (分类阶段只用 m_matBlur / m_matBlob 这两张小图)
m_matLoad.release();
// 总耗时
{
auto tEnd = std::chrono::high_resolution_clock::now();
@ -518,8 +610,21 @@ int CTcsCheck::TraditionalClassify(const cv::Mat& blobImg)
// 在小图上分类可避免对全尺寸图做昂贵的 connectedComponentsWithStats。
if (m_matBlur.empty() || m_matBlob.empty())
{
// 回退:无小图缓存时,退化为全尺寸分类(旧行为)
// 回退路径:旧行为是对全尺寸 mask 直接做连通域分析,
// 而 connectedComponentsWithStats 的 labels 是 CV_32S(4 B/px):
// 22754x60000 的 mask 单次就要 5.5 GB,是明确的 OOM 风险点。
// 正常流程里 TraditionalDetect 必然已经填好小图缓存,这里只保留小图的兼容路径。
m_vecDefectInfo.clear();
if (m_matLoad.empty() || blobImg.empty()) return -1;
const double pxCount = static_cast<double>(blobImg.cols) * blobImg.rows;
if (pxCount > 16.0 * 1024 * 1024)
{
std::cout << "[TraditionalClassify] no small-image cache, mask "
<< blobImg.cols << "x" << blobImg.rows
<< " -> refuse full-size connectedComponentsWithStats (OOM risk)" << std::endl;
return -1;
}
ClassifyBlobs(m_matLoad, blobImg);
return static_cast<int>(m_vecDefectInfo.size());
}

Loading…
Cancel
Save