简介基于Python与OpenCV的材料缺陷检测程序是一份面向计算机视觉入门者、毕业设计或课程设计学习者的完整实现包重点演示从图像读取、灰度化、阈值分割到缺陷区域标注与结果可视化的处理链路。压缩包内共55个文件包含Python主控脚本、20张JPG原始图与效果图、12份XML参数配置、3张流程示意图以及CSV数据记录等整体大小约986KB并以多个迭代目录区分不同版本便于对照分析算法差异。配套的README文档和流程图详细说明了运行方式与设计思路多组结果对比图如原图、阈值图、缺陷图可帮助读者直观理解每一步图像处理的效果与参数影响。该资源已有139人学习下载适合作为工程实训、大作业或初期项目立项的参考素材也可直接在其基础上拓展更复杂的缺陷检测算法。1. 材料缺陷检测用 Python 加 OpenCV 到底够不够先算成本再看效果金属材料表面的划痕、凹坑、裂纹过去靠老师傅在产线旁拿肉眼扫一张料翻过来覆过去看一小时下来眼睛就花了。换到自动化检测买整机视觉系统动辄几十万材料规格一换还得重新调小批量非标产线根本承受不住。用 Python 加 OpenCV 自己搭一套材料缺陷检测程序两三百行代码就能覆盖大部分能看出来的表面缺陷换材料只改参数适合中小批量产线、非标材料抽检和采购整机前的方案验证。这篇笔记按算法选型、环境搭建、最小程序、参数调优、避坑、进阶可视化一路展开目标是让读完的人能照着复现一套能跑的检测程序而不是停留在概念上。2. 算法选型先于代码材料缺陷检测程序的方案设计2.1 缺陷类型与算法的对应关系五类缺陷先选路径材料缺陷检测的第一步不是装 OpenCV而是把手头样品的缺陷照片整理清楚。不同缺陷在图像上呈现的纹理不一样选错算法后面调参怎么调都别扭。我一般按形状把缺陷分成五类细长划痕、块状凹坑、圆形气泡、不规则裂纹、灰度污点。划痕是长条状适合用边缘检测加直线检测凹坑是闭合边缘围出来的块适合用阈值分割加轮廓面积筛选气泡在底光照射下通常是边界清晰的圆斑可以用圆度来过滤焊接裂纹又细又带分叉直接用轮廓筛选会断成一截截需要形态学闭运算加骨架提取灰度污点则是最基础的情况一个合理的阈值就能分开。对应关系大致如下表落地时按这个表先搭调参顺序缺陷类型图像特征首选算法路径可替代路径划痕长条、灰度低于背景Canny HoughLinesP形态学开运算 骨架提取凹坑/麻点块状、闭合轮廓OTSU 阈值 轮廓面积筛选霍夫圆变换气泡圆斑、边界光滑阈值 圆度筛选斑点检测器裂纹细长、分叉、易断裂形态学闭运算 骨架提取Canny 轮廓长宽比筛选污点灰度差异明显自适应阈值全局阈值这张表解决的是从哪下手的问题。我见过不少项目直接用边缘检测处理裂纹结果边缘断成一节节线性特征全丢也有拿霍夫圆检测凹坑的凹坑边缘稍不规则就漏检后面被迫换回轮廓分析。所以第一步先确定自己的缺陷属于哪一类、每个缺陷大概占多少像素再决定算法路径这比先装环境重要得多。2.2 灰度化、去噪、对比度增强预处理链路的顺序与参数缺陷检测的预处理链路基本固定读图转灰度、去噪、增强对比度、二值化。不要一上来就上深度学习那套复杂增强表面缺陷检测这类场景灰度图已经包含绝大多数判别信息颜色通道反而会引入光照色温干扰。去噪这一步最容易被忽略。材料表面的加工纹理、灰尘颗粒在二值化后都会变成伪缺陷。常见做法是先用中值滤波或高斯滤波把这些细颗粒抹掉。中值滤波对椒盐噪点更友好高斯滤波对高斯噪声更有效。核的大小直接影响结果3x3 保留细节但去噪有限5x5 去噪更强但有可能把细划痕抹平。对比度增强用直方图均衡化就够了它能自动拉伸灰度分布让暗背景下的浅划痕变得更醒目。但如果原图光照已经很均匀这步可以省略全局均衡对均匀光照反而可能放大背景噪点。import cv2 import numpy as np def preprocess(image_path): # 读成灰度图表面缺陷的判别主要靠灰度梯度转灰度还能省掉颜色空间转换开销 gray cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) # 中值滤波去颗粒噪点核大小选 5兼顾去噪和细小划痕保留 # 想保留更细的缺陷就改回 3想压掉纹理干扰就提到 7 blurred cv2.medianBlur(gray, 5) # 直方图均衡化自动拉大前景背景差异 # 光照不均匀的场景建议改 CLAHE避免全局均衡把渐变背景也放大 enhanced cv2.equalizeHist(blurred) return enhanced这段代码本身没什么特别的参数选择却很有讲究。medianBlur 的核大小必须是奇数5x5 对五百万像素级图像上的常见划痕是安全值。如果材料表面有规律纹理比如车削纹、拉丝纹会干扰检测我一般还会在 medianBlur 之后加一道高斯低通把周期纹理滤掉。代价是缺陷边缘会变模糊所以要在去噪和保边之间找平衡点这个平衡点只能靠你自己的样本去试。直方图均衡化不是万能的。碰上一边亮一边暗的材料表面全局的 equalizeHist 会把暗部噪声放大误报率飙升。这种场景要用 CLAHE也就是限制对比度的自适应直方图均衡它分块处理暗部和亮部各算各的映射。实际项目里CLAHE 的 clipLimit 参数从 2.0 到 4.0 之间调tileGridSize 一般用 8x8。这块没有标准答案同一批材料在不同光源下表现差异很大属于典型的凭经验调参多留几组样本做对比就好。2.3 阈值分割与形态学操作把缺陷从背景中抠成掩膜预处理之后的核心步骤是把缺陷从背景中分离成黑白掩膜。这里有两种选择全局阈值和自适应阈值。全局阈值适合光照均匀、背景灰度稳定的样本比如底光照射的金属表面自适应阈值适合光照渐变或背景灰度在空间上有变化的样本比如自然光下的铸件侧面。def get_binary_mask(enhanced): # 全局阈值OTSU 自动计算分割阈值适合背景灰度集中、直方图接近双峰的图 _, otsu_mask cv2.threshold( enhanced, 0, 255, cv2.THRESH_BINARY_INV cv2.THRESH_OTSU ) # 自适应阈值对光照渐变鲁棒blockSize 必须为奇数 # C 值表示从局部均值中减去的量C 越大检出的前景越少越保守 adaptive_mask cv2.adaptiveThreshold( enhanced, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 41, 15 ) # 形态学开运算去掉孤立噪点闭运算连接断裂的缺陷 kernel np.ones((3, 3), np.uint8) cleaned cv2.morphologyEx(adaptive_mask, cv2.MORPH_OPEN, kernel, iterations2) closed cv2.morphologyEx(cleaned, cv2.MORPH_CLOSE, kernel, iterations3) return closedadaptiveThreshold 的 blockSize 决定局部窗口大小窗口越大自适应力度越弱越接近全局阈值窗口越小对局部亮度变化越敏感但也容易把细微灰度波动判成前景。C 值直接控制灵敏度C 值大则提取的前景更少。我的经验是 blockSize 取 31 到 51 之间C 取 10 到 20具体值靠一两张典型缺陷图调试。形态学操作的 iterations 也不要一味加大超过 3 次就可能把相邻缺陷粘连成一个整体导致缺陷计数不准。开运算和闭运算的顺序一般是先开后闭先消噪点再补空洞这个顺序别反过来。到这里分离出干净的缺陷掩膜之后下一步就是找轮廓和筛选。记住一个原则二值掩膜的质量决定了整个检测程序的天花板后面所有筛选逻辑都只是补救措施。3. 环境搭建到最小程序跑通OpenCV 缺陷检测的完整链路3.1 Python 虚拟环境与 OpenCV 安装版本和包的坑OpenCV 安装本身不难难的是把环境弄干净。见过太多人在自己电脑上以前装过某个旧版本后面项目要新版本直接 pip install 覆盖结果 import cv2 拿到的还是旧版排查半天才发现是多个位置都装了一遍。所以第一步为每个检测项目建独立虚拟环境。python -m venv defect_env source defect_env/bin/activate # Windows PowerShell 下执行 defect_env\Scripts\Activate.ps1 pip install opencv-python numpy pip install matplotlib # 用来显示中间结果不是必需这里有个关键选择到底装 opencv-python 还是 opencv-contrib-python两者差异在于 contrib 包额外包含了 ximgproc、xfeatures2d 等扩展模块。如果你的缺陷检测会用到骨架提取也就是 cv2.ximgproc.thinning或者用到 SIFT 特征匹配那必须装 contrib 版本。只做阈值分割、边缘检测和轮廓筛选的话装标准版 opencv-python 就够了包体积更小加载也快。我个人的习惯是直接装 contrib功能全不至于后面用到一个扩展模块才发现没有再返工。两个版本不能同时装会互相覆盖import cv2 时也不知道加载的是哪个。OpenCV 版本方面4.x 系列对 Python 3.7 以上都支持得很好API 差异主要是个别函数参数形式改过。注意 cv2.findContours 在 OpenCV 3 之后返回值的数量和形式都变了代码里写contours, hierarchy cv2.findContours(...)对应的是新版返回。不要照抄老教程的写法否则解包时直接报错。pip 安装失败或编译有问题时先别折腾源码编译搜一下对应系统的预编译轮子装轮子通常比 cmake 源码编译快得多。3.2 最小检测程序一张缺陷图从读到输出的完整代码装完环境后把一段最小检测程序跑通比讨论任何算法都重要。下面这段代码能在一张缺陷图上输出缺陷数量、位置和大小粘贴即用已经适配新版 OpenCV 的 API。import cv2 import numpy as np def detect_defects(image_path, min_area50): # 1. 读图并转灰度 img cv2.imread(image_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 2. 高斯模糊消除采集噪声核大小 5x5 blurred cv2.GaussianBlur(gray, (5, 5), 0) # 3. OTSU 反阈值提取暗色缺陷区域 # THRESH_BINARY_INV 让缺陷区域为白色前景方便 findContours 提取 _, thresh cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV cv2.THRESH_OTSU) # 4. 开运算去掉 3x3 级别的孤立噪点 thresh cv2.morphologyEx( thresh, cv2.MORPH_OPEN, np.ones((3, 3), np.uint8), iterations1 ) # 5. 找外轮廓只取最外层避免缺陷内部孔洞被重复计数 contours, _ cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 6. 按面积过滤低于 min_area 的算噪声 defects [] for c in contours: area cv2.contourArea(c) if area min_area: x, y, w, h cv2.boundingRect(c) defects.append({ contour: c, area: area, bbox: (x, y, w, h) }) return img, defects if __name__ __main__: img, defects detect_defects(sample.jpg, min_area50) for d in defects: x, y, w, h d[bbox] cv2.rectangle(img, (x, y), (x w, y h), (0, 0, 255), 2) print(f检出缺陷数量: {len(defects)}) cv2.imwrite(result.jpg, img)逻辑说明整个流程按读图 - 灰度 - 模糊 - 阈值 - 形态学 - 轮廓 - 筛选的顺序走这个顺序是所有表面缺陷检测程序的基本框架。findContours 的检索模式用 RETR_EXTERNAL 只取最外层轮廓不会因为缺陷内部有高亮纹理导致同一个缺陷被重复计数。轮廓筛选的逻辑只有一条——面积大于 min_area 才算是缺陷min_area 的单位是像素相机离材料表面越近、分辨率越高这个值就要相应调大。50 像素只是一个起步值实际生产中需要在调试阶段统计一批噪声颗粒的典型面积再写入配置。3.3 VS Code 调试配置让中间结果可视化代码能跑通之后迟早要面对调试。VS Code 里配置 Python 环境的关键是让解释器指向刚才创建的虚拟环境。打开 VS Code按 CtrlShiftP 输入 Python: Select Interpreter选择 defect_env 即可。此时在终端运行脚本要确保终端里已经执行过 activate。如果不小心把系统全局的 Python 当作解释器就会撞上后面要说的 ModuleNotFoundError。调试配置方面我一般会给项目加一个 .vscode/launch.json把 justMyCode 设为 false这样不管代码在哪个环境里执行断点都能正常触发。具体配置如下{ version: 0.2.0, configurations: [ { name: Python: 当前文件, type: python, request: launch, program: ${file}, console: internalConsole, env: { PYTHONPATH: ${workspaceFolder} } } ] }调试 OpenCV 脚本时有个窍门cv2.imshow 在调试模式下手动弹窗会阻塞线程不如直接往磁盘写中间结果图。我在调试阶段固定往 out_debug 目录写灰度图和掩膜图再配合 imwrite 快速看每一步的输出。与其猜哪里错了不如把每一步的中间结果存下来看这比任何断点都直观。4. 把精度调上去Canny 阈值、直线检测和轮廓特征筛选的实操参数4.1 Canny 边缘检测的三个关键参数与调参顺序材料表面缺陷检测里Canny 是最常被滥用的算法。很多人直接调cv2.Canny(blurred, 50, 150)看效果参数倒是能出结果但换一张光照不同的图就全乱了。要真正控制 Canny 的输出需要理解它的两个阈值和梯度算子。Canny 的核心理念是滞后阈值梯度幅值高于高阈值的像素一定算边缘低于低阈值的肯定不算介于两者之间的像素只有当它连着高阈值边缘时才被算入。所以低阈值和高阈值的比值决定了边缘的延展性。比值越小边缘越长比值越大边缘越碎。我一般保持低阈值和高阈值等于 1:2 到 1:3然后整体平移两个值来适应光照强度变化。# threshold1 低阈值、threshold2 高阈值apertureSize 是 Sobel 梯度窗口 edges cv2.Canny(blurred, threshold160, threshold2180, apertureSize3)对细划痕来说高阈值 180 会切掉很多灰度过渡比较柔和的弱边缘导致划痕断成两截。这时候把整体往下放比如 30 和 90弱边缘就能进来了代价是噪声也会被当成边缘。所以 Canny 前面必须跟高斯模糊sigma 值一般在 1.0 到 2.0 之间sigma 太大边缘定位精度会下降。实践上我建议这样调先用一个典型缺陷样本让低阈值从高往低扫每次降 20把结果连起来看找到缺陷刚好连续、背景噪声还没大面积出现的区间取该区间的中间值。不要信某个固定的万能参数那不是工程实践是刻舟求剑。4.2 直线检测与骨架提取细长划痕与裂纹的两种方案划痕和裂纹这类细长缺陷光靠 Canny 得到一堆边缘像素不够还得把它拟合成几何对象。HoughLinesP 是检测直线的标配它输出的是一条条线段比一堆像素点更容易做统计和过滤。# 输入必须是边缘图参数含义与普通阈值不同先确保 edges 来自 Canny lines cv2.HoughLinesP( edges, rho1, thetanp.pi / 180, threshold60, minLineLength40, maxLineGap20 )说下 HoughLinesP 参数rho 是距离分辨率一般固定为 1 像素theta 是角度分辨率固定为 pi/180也就是 1 度一个扇区再细分计算量翻倍但收益很低threshold 是一条直线至少要有多少个像素点投票支持数值越大检测越保守minLineLength 是最短线段长度小于这个长度的线段会被丢掉用来滤掉噪声段maxLineGap 是同一根直线上允许断裂的最大间隔间隔小于这个值会合并成一条线。这几个参数里minLineLength 和 maxLineGap 对划痕检测影响最大。划痕本身断断续续时maxLineGap 给到 20 到 30 能把断口拼接起来背景有纹路干扰时minLineLength 提到 80 以上就把短噪声线滤掉了。如果缺陷不是直线而是带弧度的裂纹HoughLinesP 就不好使了这时候换骨架提取。骨架提取是对二值图做细化不断剥离边缘像素最后留下一条单像素宽的中心线。骨架提取后的中心线长度可以近似为缺陷长度端点数量可以反映裂纹分叉程度。# 骨架提取需要 opencv-contrib-python 提供的 ximgproc 模块 # 输入二值图前景为白色背景为黑色 skeleton cv2.ximgproc.thinning( binary_mask, thinningTypecv2.ximgproc.THINNING_GUOHALL )THINNING_GUOHALL 和 THINNING_ZHANGSUEN 是两种细化算法前者保留下来的中心线更平滑对曲线裂纹更友好后者速度略快但会在拐角处产生毛刺。我默认使用 GUOHALL。值得提醒的是骨架提取对输入二值图的质量非常敏感掩膜里残留一个小凸起细化后就会多出一根短枝直接影响端点统计。所以骨架提取之前形态学开运算必须做到位把孤立噪点清干净。4.3 轮廓特征筛选面积、长宽比、圆度与填充率的组合策略把候选区域提取出来后最后一道筛选决定了误报率。面积当然是最基础的维度但对于形态差异很大的缺陷类型光用面积不够。我的做法是一组特征组合卡阈值面积、长宽比、圆度、填充率四个特征配合使用比单独一项可靠得多。def filter_contours(contours, min_area50, max_areaNone): results [] for c in contours: area cv2.contourArea(c) if area min_area: continue if max_area and area max_area: continue # 最小外接矩形长宽比用来识别细长形缺陷划痕/裂纹 x, y, w, h cv2.boundingRect(c) aspect_ratio max(w, h) / float(min(w, h) 1e-6) # 圆度 4π * 面积 / 周长^2越接近 1 越像圆 perimeter cv2.arcLength(c, True) circularity 4 * np.pi * area / (perimeter * perimeter 1e-6) # 填充率 轮廓面积 / 外接矩形面积反映形状是否紧凑 fill_ratio area / float(w * h 1e-6) results.append({ contour: c, area: area, aspect_ratio: aspect_ratio, circularity: circularity, fill_ratio: fill_ratio }) return results特征组合可以很灵活划痕的长宽比通常在 5 以上同时填充率很低气泡的圆度很高面积相对固定凹坑的特征比较宽泛面积范围和圆度区间都比气泡宽。把这些特征的阈值组合写成字典比如{min_aspect: 5, min_area: 50}在调试阶段把候选区域的这些特征值打印出来对比真缺陷和假缺陷的特征分布你会直观看到哪个特征能分开它们然后把阈值回填进代码。这比对着图像蒙参数靠谱得多。这里加一个 OpenCV 图像处理项目里的通用经验不管特征选得多好二值图质量永远是第一位。如果掩膜里一块噪声和一块真实凹坑面积接近任何特征都很难区分它们。所以别再花太多时间设计筛选规则回到预处理环节把掩膜做干净效果来得更快。5. 避坑指南材料缺陷检测开发中的 5 个高频翻车现场5.1 No module named cv2装了却导入失败的排查现象pip 显示 OpenCV 已安装但运行脚本时 Python 报 ModuleNotFoundError: No module named cv2。更多时候出现在两个环境并存时终端里激活了虚拟环境又去跑系统 Python或者在不同项目里装过不同版本。原因Python 的可执行文件和 site-packages 不在同一个环境里。pip 安装的是 A 环境的包运行的却是 B 环境的解释器。另一种情况是 Anaconda、系统 Python、虚拟环境三个解释器混在一起VS Code 里选了解释器但终端没有激活环境于是导入失败。解决先弄清楚当前用的是哪个 Python 解释器再确认包安装在哪里。终端执行which python和python -m pip show opencv-python把两个路径对比一下。如果路径对不上就退出终端重新 activate 环境再不行就把当前环境的 cv2 卸载后重装。这个错误几乎不会是真的没装上按环境路径排查最有效率。另外不要把 OpenCV 包装在 conda base 环境里当成默认项目环境一定要独立。5.2 光照不均让 OTSU 失效自适应阈值和 CLAHE 的补救现象同一张材料照片左边亮右边暗OTSU 全局阈值跑完之后暗部区域整片被判成缺陷明暗交界处全是噪点。缺陷本身明明清晰可辨程序却输出几十上百个伪缺陷框。原因OTSU 全局阈值假定图像背景灰度是单峰的暗部背景的灰度值可能比亮部的缺陷还低一个全局阈值不可能同时适配整个画面。这是表面缺陷检测的第一道坎。解决把全局阈值换成 adaptiveThreshold让每个局部窗口都有独立的阈值。窗口 blockSize 取 41、C 取 15 是常见起步值如果暗部噪声还是压不下来把 blockSize 调大到 51 或 61。或者先用 CLAHE 做分块直方图均衡再把结果交给 OTSU。这两条路可以组合使用实际项目里 CLAHE 加自适应阈值是光照不均匀场景下的标配组合。5.3 Canny 对噪声敏感少一步模糊就多一堆误检现象用cv2.Canny(gray, 80, 200)检测划痕输出图像上布满了密密麻麻的小线段真正的划痕反而被淹没在其中。原因Canny 对局部灰度梯度非常敏感材料表面的机加工纹理、抛光痕迹、灰尘投影在高斯模糊没压住的情况下梯度值也能达到高阈值被当成边缘。解决Canny 之前必须先做高斯模糊sigma 给到 1.5 到 2.0同时把 Canny 阈值抬高一点。如果模糊后边缘还是乱就检查是不是把彩色图直接传进 Canny 了——彩色图三通道梯度会叠加噪声更严重必须先把图像转成灰度。此外Canny 结果出来之后接 HoughLinesP 时threshold 参数大于 80 能滤掉绝大多数短线噪声。5.4 误报率在产线实拍图上飙升样本收集与特征分布分析现象缺陷检测程序在样张上效果不错换到产线实拍图后误报率从 2% 飙到 30%。生产现场没人愿意看一个天天狼来了的报警系统。原因开发时用的样张都是挑过的干净图产线实拍图有油污、指纹、灰尘、水渍这些在灰度图上全是类缺陷。算法的筛选阈值是按干净样本调的对真实环境没有泛化能力。解决把产线上的缺陷图和非缺陷图分别收集两百张以上在图上标出缺陷区域看灰度分布区间。再对检测结果里被打上缺陷框的区域做统计观察这些区域在面积、长宽比、圆度上的分布真缺陷和伪缺陷通常会在某个特征上有明显分群。然后针对这个分群设计过滤规则。没有批量的真实坏样本参数就调不准这是算法工程里的基本现实。收集坏样本这件事优先级很高别在没数据的时候就盲目调参数。5.5 检测速度达不到产线节拍ROI 裁剪、缩图与算法降级现象程序用 1920x1080 的相机图跑单张处理时间 300 毫秒产线节拍要求 100 毫秒以内程序直接被喊停。原因所有操作都在全分辨率图上做还用了 adaptiveThreshold 这种计算量偏大的方法。找轮廓阶段虽然 RETR_EXTERNAL 在树结构上只取外层但如果掩膜噪声点多轮廓数量本身就可能上万遍历这部分的消耗也很大。解决第一ROI 裁剪先粗略找出缺陷可能出现的热区只对热区做完整检测。第二把输入图缩到 1280 宽度缺陷的像素面积会缩小最小面积阈值按比例同步缩小即可。第三把自适应阈值换回 OTSU能满足就尽量用它OTSU 是一次全局计算开销远小于逐窗口的自适应阈值。第四如果还不行再考虑把关键链路用 OpenCV 的 C 接口重写或者给预编译的 OpenCV 换 GPU 版本但 GPU 版本对多相机项目收益才明显单相机项目先优化算法本身。6. 进阶技巧骨架提取量化裂纹长度与 blend 可视化6.1 骨架提取量化裂纹长度像素计数与标定换算骨架提取不仅能看形态还能给细长裂纹提供量化指标。细化后得到单像素宽的中心线白色像素总数就是裂纹的近似长度单位是像素。配合一个已知尺寸的参照物做标定就能换算出真实毫米长度。# 先做形态学闭运算把断裂的裂纹连接起来再做细化 closed cv2.morphologyEx(binary_mask, cv2.MORPH_CLOSE, np.ones((3, 3), np.uint8), iterations2) skeleton cv2.ximgproc.thinning(closed, thinningTypecv2.ximgproc.THINNING_GUOHALL) # 裂纹长度像素单位 crack_length_px cv2.countNonZero(skeleton)换算成毫米需要在同一相机高度和焦距下拍一个已知长度的标定物计算每毫米对应的像素数。这点做好之后程序输出的缺陷数据就不再是有缺陷/没缺陷而是有一条 12.3 毫米的裂纹这个指标对质量部门的吸引力完全不同。6.2 用 blend 把掩膜叠加到原图最直观的检测结果呈现给现场同事看检测结果直接把二值掩膜丢过去没人看得懂。用 addWeighted 把掩膜和原图做混合缺陷区域显示为半透明高亮其他区域保留材质细节一眼就能定位问题在哪。mask_bgr cv2.cvtColor(binary_mask, cv2.COLOR_GRAY2BGR) mask_bgr[np.where(binary_mask 0)] (0, 0, 255) # 缺陷区域标红 blended cv2.addWeighted(img, 0.7, mask_bgr, 0.3, 0)addWeighted 的前两个参数是原图和权重后两个是掩膜和权重最后的 0 是亮度偏置。权重比控制在 0.3 到 0.5 之间太高会盖住材料纹理太低缺陷不明显。如果相机是分块拍摄大尺寸材料多张图拼接时接缝角落会产生重影可以在拼接阶段对边缘做羽化混合角落过渡自然了跨图缺陷检测才不会在接缝处漏掉。我自己的习惯是调试阶段每个环节都存一张叠加图从灰度图、掩膜到最终检测框串起来一看就知道问题出在哪一步。养成这个习惯后返工次数明显少了。希望帮到你。本文还有配套的精品资源点击获取