资讯详情

资讯详情

OpenCV形态学实战:结构元素与开闭运算调参指南

1. 形态学到底在解决什么问题1.1 从一次二值化翻车说起前阵子帮一个做表面检测的朋友调一套视觉流程图像是金属件表面的灰度图目标是数清楚有几个凹坑。阈值分割那一步看着挺漂亮Otsu 一跑前景背景分得干干净净可把结果图放大一看问题全出来了工件边缘有一圈毛刺状的碎点反光区域散着一片一两个像素的白噪点凹坑内部还有几个因为油污导致的空洞。二值图在显示器上肉眼看着差不多可一送进连通域分析findContours直接返回四百多个轮廓其中三百多个是噪点。这时候再回头去调阈值、换自适应阈值、加高斯滤波效果都很有限——因为噪声和目标的灰度分布本来就重叠阈值切不干净。真正把这张图救回来的是形态学先一个 3×3 椭圆核开运算把孤立白点吃掉再用 7×7 核闭运算把凹坑内部的空洞填上轮廓数量立刻从四百多降到七个跟实际凹坑数完全对得上。整个过程没有改一行分割逻辑只是在二值结果后面挂了两步形态学。这就是OpenCV 形态学最典型的使用场景它不负责把图像变好看也不负责增强对比度它负责的是在二值或者近似二值的图上按形状规则做增删改。腐蚀让前景瘦一圈膨胀让前景胖一圈开运算砍掉突出的毛刺闭运算补上凹进去的缺口。判断标准只有一个——这个像素周围的结构元素范围内前景和背景的分布是什么样。适合谁看如果你正在做缺陷检测、字符识别、车道线提取、细胞计数、文档去噪这类需要干净的掩膜的活形态学基本绕不开。如果你只是刚学 OpenCV把erode、dilate当成两个神秘函数调着玩那这篇里的参数选取和踩坑记录应该能帮你少走不少弯路。1.2 形态学和卷积滤波的本质区别很多人第一次接触形态学会觉得它就是个特殊的滤波这个理解会害人。卷积滤波均值、高斯、中值做的是加权求和或者排序取中值输出像素值是邻域像素值的线性或序统计组合结果是灰度的连续变化图像整体变模糊或变干净。形态学做的是集合运算腐蚀取邻域最小值膨胀取邻域最大值输出值一定来自邻域里的某个原始像素不会凭空造出新的灰度级。这个差别带来的直接后果是形态学对形状敏感对灰度梯度不敏感。一张前景 200、背景 20 的二值图腐蚀之后剩下的像素还是 200只是数量变少了边缘位置发生了整数像素的移动不会有过渡带。而高斯模糊之后边缘会变成 180、150、120 这样的渐变你再去阈值边缘位置反而飘了。用生活化的说法高斯滤波像给照片磨皮把细节糊掉形态学像拿刻刀修橡皮章该削的地方削掉该补的地方补上章面的材质灰度值不变。理解这一点你就明白为什么形态学几乎总是出现在阈值分割之后而不是之前。在灰度图上直接做腐蚀效果是整幅图变暗、亮区域收缩除了让图像发闷没什么用而在二值图上做腐蚀才是真正的去掉边缘一层。当然形态学也能作用在灰度图上OpenCV 完全支持 CV_8U、CV_16U、CV_32F灰度形态学在顶帽变换做背景估计时非常有用这个后面第 3 章会细说。还有一个容易混淆的点形态学的运算顺序不可交换。开运算是先腐蚀后膨胀闭运算是先膨胀后腐蚀两者结果完全不同。我见过有人把MORPH_OPEN和MORPH_CLOSE的顺序写反调试了半天以为是核选错了。实际上核没问题顺序错了效果就南辕北辙——开运算去白点闭运算填黑洞方向搞反等于把问题放大。2. 结构元素形态学里最容易被忽略的关键参数2.1 形状、尺寸、锚点三要素怎么定OpenCV 里造结构元素的函数是cv2.getStructuringElement(shape, ksize, anchor)也可以用 NumPy 自己拼一个任意矩阵丢进去。三个参数里shape决定了探测窗口长什么样ksize决定窗口多大anchor决定窗口中心钉在哪个位置。三种内置形状的适用场景差别很大形状常量矩阵形态3×3 示例适用场景副作用MORPH_RECT全 1 的方块大核、需要性能、规则网格结构会把斜向边缘削成直角角点损失明显MORPH_ELLIPSE近似圆形自然图像、细胞、圆形缺陷计算稍慢小尺寸下其实是个十字加四角MORPH_CROSS十字形细线、骨架、文字笔画只处理上下左右斜向结构几乎不动这里有个细节必须说清楚MORPH_ELLIPSE在 3×3 的时候并不是圆实际生成的是0 1 0 1 1 1 0 1 0跟MORPH_CROSS一模一样。到 5×5 才开始出现斜角。所以如果你为了更圆润专门用椭圆核配 3×3其实跟十字核没区别白写代码。想要圆润效果核尺寸至少给到 5 以上。MORPH_RECT是我在大核场景下的默认选择原因不是效果好而是性能。矩形核在 OpenCV 内部可以做行列分离优化一个 21×21 的矩形腐蚀实际计算量和两个 1×21 的一维腐蚀相当比 21×21 的椭圆核快一个数量级。这个第 6 章会展开讲。至于MORPH_CROSS它在我处理文档图像时用得最多。字符的笔画是竖和横为主用十字核做闭运算能把断裂的笔画接上同时不会把相邻的两个字糊在一起——因为字的间距方向是斜的十字核不碰。换成矩形核稍微大一点两个字符就粘成一坨了。2.2 核尺寸怎么算别凭感觉填核尺寸不是拍脑袋填的它跟你要处理的目标尺寸有直接换算关系。核心原则开运算能去掉的最大亮结构尺寸略小于核尺寸闭运算能填上的最大暗结构尺寸略小于核尺寸。具体点说如果二值图上散落的噪点直径是 2 到 3 像素开运算的核直径给到 5 就能全清掉给 3 只能清掉 1 到 2 像素的剩下的还在。反过来如果噪点直径有 8 像素你用 5 的核去开噪点只是缩小了一点位置还在等于白干。填充孔洞也是同理。一个直径为 D 的圆孔闭运算核尺寸要大于 D 才能填满。如果孔是 12 像素宽核至少 13实际工程里我会给 15 留点余量因为孔洞边缘受阈值影响往往比看到的更大。实操中我会先在图上量三个数据噪点最大尺寸、孔洞最大尺寸、目标最小尺寸。然后开运算核尺寸 噪点最大尺寸 2取奇数闭运算核尺寸 孔洞最大尺寸 2取奇数同时必须满足核尺寸 目标最小尺寸否则目标本身会被吃掉第三条是保命线。有次我处理 PCB 焊盘焊盘宽度只有 9 像素孔洞有 8 像素按公式核得给到 10一跑焊盘全没了。这时候就不能靠单一闭运算得改用先开运算去噪再用填充算法补内孔的思路——floodFill从外部灌水或者对轮廓做cv2.drawContours填充。形态学不是万能的遇到核尺寸和目标尺寸打架一定优先换方案别硬怼参数。还有一种偷懒但有效的做法iterations参数。cv2.dilate(img, kernel, iterations3)等价于连续做三次膨胀。对于矩形核尺寸 3×3 迭代 N 次效果近似于 (2N1)×(2N1) 的大核但计算量小很多。不过要注意对椭圆核迭代和直接用大核结果并不等价椭圆迭代多了会越来越接近方形形状会跑偏。所以我的习惯是矩形核用迭代椭圆核直接给大尺寸。2.3 锚点和边界两个让结果整体偏移的坑anchor参数默认是(-1, -1)表示取核的几何中心。3×3 核中心在 (1,1)没问题。但如果你手搓了一个 4×4 的偶数尺寸核中心就落在 (2,2)实际锚点会偏向一侧腐蚀结果整体会往某个方向偏半个像素。这类偏移在单次处理时看不出来等你在流水线上做多帧配准误差就累积了。所以我的规矩是核尺寸一律取奇数省得跟锚点较劲。少数情况下锚点必须手动改。比如你要做单向腐蚀只让前景往下收缩、上方保持不动可以构造一个1 1 1 0 0 0 0 0 0这样的核锚点设在 (1,1)效果就是只检查上方邻域。这种定向结构元素在提取单方向线条时非常有用比先做全向再过滤要干净。边界处理是更大的坑。OpenCV 的erode、dilate默认borderType cv2.BORDER_CONSTANT填充值是morphologyDefaultBorderValue()——对腐蚀来说是正无穷对膨胀来说是负无穷。翻译成人话图像边缘之外被当成全是背景。这意味着腐蚀时图像最外圈的像素会被削掉膨胀时最外圈会被撑开。如果你的目标紧贴图像边界腐蚀一次边缘就吃掉了目标的一部分后面的尺寸测量全都不准。解决办法有两个。一是处理前先copyMakeBorder往外扩一圈处理完再裁回来扩的宽度不小于核半径。二是如果目标不会贴边那就随它去知道有这么回事就行。我一般用第一种多写两行代码换个心里踏实。注意如果你把borderType改成BORDER_REPLICATE边缘会复制原值腐蚀后边界像素保留。看起来更合理但对于本来就有前景贴边的情况边缘会被当成前景保留下来噪点清不干净。两种方式各有利弊得看你的目标是否贴边。3. 六大基础算子逐个拆解3.1 腐蚀与膨胀底层就是取最小值和最大值腐蚀的本质用一句话讲结构元素覆盖的所有像素里只要有一个是背景中心点就变成背景。等价于取邻域最小值。膨胀反过来只要有一个是前景中心点就变成前景等价于取邻域最大值。这个定义解释了很多现象。腐蚀会让前景面积缩小、内部小空洞变大、两个靠近的物体分离如果间隙小于核尺寸膨胀会让前景面积扩大、内部小空洞被填上、两个靠近的物体粘连如果间隙小于核尺寸。注意这两组效果是对称的——腐蚀会放大暗结构膨胀会放大亮结构。灰度图上腐蚀的效果是整幅图变暗亮的细节被压下去膨胀是整幅图变亮暗的细节被抬上来。用这个特性可以做局部极值统计比如用大核腐蚀一遍再和原图比对就能找出局部暗点这在检测微小凹坑时比阈值法稳定得多。代码上没什么花头kernel cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) eroded cv2.erode(binary, kernel, iterations1) dilated cv2.dilate(binary, kernel, iterations1)但有三个参数容易忽略。第一个是iterations前面说过了矩形核可以靠它省算力。第二个是anchor默认中心一般不用改。第三个是可选参数borderType和borderValue默认值已经讨论过。还有一个cv2.morphologyEx的mask参数很少有人用但在局部处理场景下非常好使你只想在 ROI 区域内做形态学其他区域保持原样把 ROI 的二值图当 mask 传进去就行。这比先裁剪再贴回去要省事得多也不会出现拼接缝。3.2 开运算和闭运算顺序错了等于白干开运算 先腐蚀后膨胀闭运算 先膨胀后腐蚀。这两个名字起得挺形象开运算像开门把小的、突出的东西放走闭运算像关门把小的、缺口的东西补上。为什么这个顺序是固定的拿开运算举例先腐蚀前景整体瘦一圈直径小于核尺寸的孤立亮点直接被吃没了再膨胀把剩下的大块前景重新长回去。因为大块物体腐蚀之后还是连通的膨胀能长回原样边缘会有轻微圆角化而已经被吃掉的小点没有种子可以长回来所以永久消失了。如果顺序反过来先膨胀后腐蚀那是闭运算小亮点先被撑大再缩回来等于没动。工程的判断口诀前景白上有小碎点 → 开运算去掉比核小的白斑前景上有小黑洞 → 闭运算填掉比核小的黑斑细线断裂、笔画断开 → 闭运算把断口接上物体之间有细丝相连 → 开运算把细丝剪断这两个算子在 OpenCV 里都有现成常量cv2.MORPH_OPEN和cv2.MORPH_CLOSE不用手写两次调用。手写容易出错而且morphologyEx内部对中间结果做了优化比自己串两次erode/dilate略快。有个副作用要提前知道开运算会圆角化。一个矩形的四个直角开运算之后会变圆圆角半径大约等于核半径。如果你后续要做最小外接矩形、角点检测这个圆角化会带来几个像素的误差。要减少这个影响核尺寸就压到最小必要值别图省事给个大核。闭运算同样会圆角化内凹的角只是方向相反。3.3 梯度、顶帽、黑帽把差值用起来形态学梯度是dilate(img) - erode(img)得到的是前景的边界带带宽约等于核尺寸。它和 Canny 的区别在于Canny 找的是灰度突变梯度找的是二值形状的轮廓。在二值图上用形态学梯度提取边缘比 Canny 更稳定不会因为阈值参数漂移而出双边缘。顶帽变换Top-hat原图 - 开运算(原图)得到的是比结构元素小的亮细节。这个算子在光照不均匀的场景下是神器。比如拍摄一张有阴影的纸张直接全局阈值阴影区域全黑文字没了。先用一个比文字笔画大得多的核比如 25×25做开运算得到的是背景光照的估计原图减去它阴影就被拉平了剩下的就是文字。这本质上是一种背景估计和校正。黑帽变换Black-hat闭运算(原图) - 原图得到的是比结构元素小的暗细节比如浅色背景上的深色划痕、亮面金属上的暗点。我做表面缺陷检测时黑帽几乎是标配第一步因为它能把暗缺陷从亮背景里提出来而且对光照渐变不敏感。这几个算子的选择逻辑可以用一张表概括目标算子核尺寸选取去掉小白点开运算略大于噪点填上小黑洞闭运算略大于孔洞提取边界带梯度决定边界厚度提取小亮细节 / 光照校正顶帽大于目标特征提取小暗细节黑帽大于目标特征提示顶帽和黑帽的核尺寸决定的是多大的特征会被保留。核给 15×15那么宽度小于 15 的亮细节都会出现在结果里宽度大于 15 的会被当成背景减掉。所以核尺寸要卡在目标特征尺寸和背景结构尺寸之间这是个区间不是单点实际调的时候在这个区间里试三五次就能定下来。4. 实战从一张脏图到可用的二值掩膜4.1 预处理链条怎么排拿到一张原始图到最终干净的掩膜中间的顺序不是随便排的。我的固定套路是这样的灰度化如果原图是彩色的必要时先转 HSV 取某个通道轻度滤波去随机噪声中值 3×3 或双边别用大核会把小目标一起糊掉光照校正可选用大核开运算做背景估计原图减去背景阈值分割Otsu 或自适应阈值形态学去噪开运算清白点形态学补洞闭运算填黑洞连通域筛选按面积、长宽比过滤掉残留的碎块注意第 5 步和第 6 步的顺序必须先开运算后闭运算。原因很实在——如果先闭运算那些本该死掉的小白点会被膨胀撑大接着腐蚀虽然缩回来但位置可能已经和旁边的大目标粘连了再想分开就得用更激进的核容易伤到真目标。先开运算把噪点杀干净再闭运算补洞中间不会产生新的粘连。还有一个细节闭运算填洞之前最好确认洞是不是真的该填。有些孔洞是真实特征比如零件上的通孔填了就是误检。这种情况要在闭运算前加一步判断比如按孔洞面积阈值区分小的填大的留。形态学本身不区分语义它只认形状语义判断得靠你加逻辑。4.2 完整代码与参数记录下面是我在某次工件凹坑检测里用的实际代码去掉业务部分只留核心流程import cv2 import numpy as np # 1. 读图转灰度 gray cv2.imread(part.png, cv2.IMREAD_GRAYSCALE) # 2. 中值滤波去椒盐噪声核小一点 gray cv2.medianBlur(gray, 3) # 3. 光照校正25x25 开运算估背景原图减去 bg_kernel cv2.getStructuringElement(cv2.MORPH_RECT, (25, 25)) background cv2.morphologyEx(gray, cv2.MORPH_OPEN, bg_kernel) flat cv2.subtract(gray, background) flat cv2.normalize(flat, None, 0, 255, cv2.NORM_MINMAX) # 4. Otsu 阈值分割这里用反相让凹坑为白 _, binary cv2.threshold(flat, 0, 255, cv2.THRESH_BINARY_INV cv2.THRESH_OTSU) # 5. 开运算去孤立噪点噪点最大 3px核给 5 k_open cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) opened cv2.morphologyEx(binary, cv2.MORPH_OPEN, k_open) # 6. 闭运算填内部空洞最大孔洞 11px核给 13 k_close cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (13, 13)) closed cv2.morphologyEx(opened, cv2.MORPH_CLOSE, k_close) # 7. 连通域按面积过滤 num, labels, stats, _ cv2.connectedComponentsWithStats(closed, 8) mask np.zeros_like(closed) for i in range(1, num): area stats[i, cv2.CC_STAT_AREA] w stats[i, cv2.CC_STAT_WIDTH] h stats[i, cv2.CC_STAT_HEIGHT] if 80 area 3000 and 0.6 w / h 1.8: mask[labels i] 255几个参数为什么这么定我逐条过一遍。中值滤波用 3 不用 5是因为凹坑最小直径大概 6 像素5×5 中值会把它直接抹掉。光照校正的 25×25 核是根据工件图像宽度约 640 反推的核大概占宽度 4%能覆盖光照渐变的一个周期又不会把大缺陷当成背景减掉。开运算的 5×5 对应噪点最大 3 像素留 2 像素余量。闭运算的 13×13 对应最大孔洞 11 像素同样留 2 像素。最后面积范围 80 到 3000是用十个样本统计出来的下限卡掉残留碎点上限卡掉整片反光区域。跑完之后轮廓数从原始二值图的 300 多个降到 9 个人工核对实际凹坑是 9 个一个不多一个不少。4.3 三个高频场景的现成配方场景一提取表格里的横线。表格结构提取是个经典需求横线竖线的分离用形态学最直接。横线的特点是长度长、高度只有 1 到 3 像素用一个(40, 1)的矩形核做开运算只有连续 40 像素以上的水平结构能存活竖线和文字全被吃掉。竖线同理核换成(1, 40)。h_kernel cv2.getStructuringElement(cv2.MORPH_RECT, (40, 1)) horiz_lines cv2.morphologyEx(binary, cv2.MORPH_OPEN, h_kernel) v_kernel cv2.getStructuringElement(cv2.MORPH_RECT, (1, 40)) vert_lines cv2.morphologyEx(binary, cv2.MORPH_OPEN, v_kernel)那个 40 怎么来的量一下表格最短横线的像素长度取它的 60% 到 80%。取太短会误伤长文字行取太长会漏掉短横线。这种细长核绝对不能加 iterations因为迭代会把 1 像素的高度方向也放大横线会变成横条。场景二填充轮廓内部孔洞。前面说过floodFill的方法这里补一个形态学版本先把轮廓画成实心cv2.drawContours时thickness-1再和原掩膜做差得到的就是所有内部孔洞然后按面积过滤决定填不填。这个方法比闭运算精确因为闭运算只能填小于核尺寸的洞而 flushFill 方案不受尺寸限制也不会改变外轮廓的圆角。场景三文字区域增强。文档扫描件经常有笔画断裂尤其是低分辨率或者传真件。用 3×3 十字核做一次闭运算笔画就接上了。核千万别用矩形矩形会把相邻字符粘连。做完闭运算再来一次开运算去孤立噪点核尺寸同样 3×3。这两步的花费极小但识别率提升往往有十几个百分点。5. 常见问题与排查技巧实录5.1 问题速查表下面这张表是我这几年攒下来的遇到现象对不上的时候先查一遍能省掉大半调试时间。现象最可能的原因排查动作解决方式处理后目标整体消失核尺寸大于目标尺寸打印核尺寸和目标最小宽度对比减小核或改用填充算法边缘被削掉一圈边界填充为背景检查目标是否贴边先copyMakeBorder再处理噪点清不干净核尺寸小于噪点量噪点实际直径核尺寸加大到噪点直径 2相邻目标粘连闭运算核过大看粘连处是否有核尺寸级别的桥换十字核或减小核结果整体偏移偶数核锚点偏移检查ksize是否为偶数改成奇数尺寸孔洞越填越大用了开运算而非闭运算确认算子类型开运算放大暗结构改用闭运算灰度图做完没变化数据类型或核太小打印img.dtype和核确认是 8U/32F核至少 3×3三通道图颜色错乱通道被独立处理看是否在 BGR 上直接做转 HSV 或转灰度再做5.2 几个反直觉的坑第一个坑开运算之后再做闭运算孔洞反而变多了。这听起来违反直觉实际是这么回事开运算会放大暗结构原本 5 像素的孔洞可能被放大到 7 像素如果闭运算的核只有 5就填不上了。所以开闭两步的核尺寸不能简单取一样闭运算的核必须大于开运算放大后的孔洞尺寸。我的做法是闭运算核比开运算核大 2 到 4。第二个坑彩色图直接做形态学颜色边缘发花。cv2.morphologyEx支持三通道输入但它是逐通道独立的。R 通道腐蚀完、G 通道膨胀完三个通道的形状边界不一致合成回彩色就是彩色毛边。正确做法是先转灰度或 HSV 只用单通道做形态学得到掩膜后再用掩膜去操作原图。这个错误我在早期项目里犯过调了两天才发现是通道问题。第三个坑iterations和核尺寸混用导致结果不可复现。有人写kernel(5,5), iterations3以为是 5×5 做三次实际上确实是三次但把三次的等效尺寸当成 15×15 去和别人的 15×15 单次结果对比发现对不上就开始怀疑人生。前面说过矩形核迭代 N 次的等效尺寸是(k-1)*N15×5 迭代 3 次等效 13×13 而不是 15×15。椭圆核连这个公式都不成立。所以团队协作时我要求统一约定要么只用大核要么只用迭代不许混着来。第四个坑形态学在浮点图上的默认边界值。CV_32F的图腐蚀的默认边界填充值是一个很大的正数膨胀是一个很大的负数原理上没问题但如果你的浮点数据范围是 0 到 1 而不是 0 到 255某些版本里边界效应会溢出成 inf结果出现 NaN。稳妥做法是浮点图处理前先归一化到合理范围或者显式传borderValue。第五个坑morphologyEx的mask参数和borderType冲突。同时传 mask 和非常规 borderType 时不同 OpenCV 版本的行为有差异边缘可能处理不干净。如果非要在 ROI 内做形态学我会选更笨但更稳的办法裁剪、处理、贴回不用 mask 参数。实操心得调试形态学的时候把每一步的中间结果都存成 PNG 看一眼比盯着数值打印有效十倍。尤其是核的形状print(kernel)看一眼矩阵很多时候问题一眼就看出来了——我遇到过有人传了(0, 5)当尺寸OpenCV 不报错生成一个空核所有操作变成空转图像一点不变查了半天以为是版本问题。6. 性能和工程化的一些经验6.1 大核优先用矩形靠可分离性提速前面提过矩形结构元素的腐蚀和膨胀可以行列分离执行。一次 k×k 的矩形腐蚀等价于先做 1×k 的水平腐蚀再做 k×1 的垂直腐蚀计算复杂度从 O(k²) 降到 O(k)。OpenCV 内部对MORPH_RECT做了这个优化核越大差距越明显。实测在一张 1920×1080 的二值图上31×31 的矩形腐蚀耗时大约是 31×31 椭圆核的十分之一。所以选核顺序应该是能用矩形就用矩形不能再用椭圆十字核单独考虑。什么情况下不能用矩形一是需要各向同性的收缩效果圆形目标测量二是矩形核的直角效应会破坏斜向边缘。这两种情况才值得为椭圆核付性能代价。另一个提速手段是降采样处理。如果你的目标尺寸都在 20 像素以上完全可以把图缩小一半再做形态学核尺寸也减半处理完再放大回来。缩小一半面积变成四分之一再加上核尺寸减半总耗时能降到原来的十分之一左右。代价是精度损失约 1 个像素对粗定位场景完全可接受。我在一条产线的实时检测里就是这么干的1080p 降到 540p 处理单帧从 18ms 降到 3ms节拍直接从 20fps 提到 60fps。6.2 批量和内存上的注意事项cv2.morphologyEx每次调用都会分配新的输出矩阵在循环里频繁调用会有内存分配开销。如果是固定流程反复处理同尺寸图像可以预先分配输出矩阵用cv2.morphologyEx(src, op, kernel, dstpreallocated)传 dst减少反复分配。这个优化在小图上不明显在 4K 图上能省几毫秒。还有一点形态学的中间结果对内存带宽压力不小因为它每次都要读写整幅图。如果一条流水线上有好几步形态学尽量合并核而不是串多次调用。比如先 3×3 开运算再 3×3 闭运算如果两个核形状相同可以在数学上合并成一次更大的操作来近似但合并后形状会略有差异得实测确认效果可以接受。我一般只在流程非常固定的场合这么干调试阶段还是老老实实分开写方便定位问题。代码组织上我习惯把形态学参数抽成配置而不是写死在流程里。原因很现实换个批次的工件、换台相机、换个光照条件最优核尺寸都会变。写成配置之后现场调参不用改代码重新编译改个 JSON 重启就行。参数命名也讲究点别叫kernel1、kernel2叫denoise_open_kernel、fill_close_kernel三个月后回来看还能看懂。最后分享一个我最近才用顺手的小技巧把形态学的每一步结果拼成一张对比图存下来文件名带上参数。做参数扫描的时候批量跑一遍生成几十张图肉眼扫一遍就能定出合适的核尺寸区间比一次次调代码看结果快得多。这个办法听起来土但在没有标注数据、只能靠人眼看效果的场景里效率提升非常明显。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →