ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于YOLO26的3D打印缺陷自动检测:识别面条、拉丝和凸点

基于YOLO26的3D打印缺陷自动检测:识别面条、拉丝和凸点

三位打印的朋友们应该都见过这样的场景:一块打印了十几个小时的零件,第二天早上起来一看,喷头在空转,底座上搅着一大团像泡面一样的面条;或者一个表面光洁的模型,侧面长出一排排细小的疙瘩;再或者两个分开的部件之间拉着密密麻麻的蛛丝。这就是我们常说的spaghetti(面条失效)、zits(起粒/凸点)和stringing(拉丝)。过去大部分人的处理办法是守着打印机,听到了“嘶嘶”声马上冲过去暂停,或者靠运气。我在折腾了很长一段时间的3D打印机和视觉检测之后,发现用目标检测模型来自动识别这三类缺陷是完全可行的,而且能做得比肉眼更及时、更稳定。这篇就完整分享一下我基于YOLO26做自动识别方案的全过程,包括数据怎么采、模型怎么训、部署要避免哪些坑,以及最终实际跑起来的效果。

1. 3D打印失败的本质:先搞懂三类缺陷长什么样、怎么来的

做检测模型之前,第一件事不是急着跑代码,而是把要检测的目标彻底搞清楚。神经网络本质上就是个模式匹配器,你喂给它的数据里有什么特征,它就学什么特征。如果我自己都分不清spaghetti和支撑材料,那模型肯定也分不清。

1.1 三类缺陷的视觉特征与产生原因

先说spaghetti,这是最严重的一类缺陷。它不是某个局部的小瑕疵,而是整个打印件彻底失效的状态。视觉上就是打印平台或者热床上出现一团杂乱无章的细线,像一盘没有煮过的意大利面,严重的时候会跟着喷头来回移动,甚至缠绕到加热块上。生成的根本原因通常是首层没粘住、模型翘边、喷头堵塞后又恢复了挤出,或者切片软件里的支撑突然脱离。哪怕前面的层都印得好好的,只要中途某个地方零件翘起来撞到喷头,后续的路径就会全部错位,材料挤在空气里,慢慢积累成那团“面条”。这类缺陷一旦出现,几乎没有挽救余地,只能停止打印。

再说zits,中文常被叫做“起粒”或“凸点”。它指的是模型表面上出现一个个小小的、像痘痘一样的凸起物,尺寸大概在0.5到3毫米之间,位置随机但经常出现在圆弧表面、层高变化处和回抽结束的位置。产生zits的原因比较细碎:切片参数里的“回抽”设置不当,挤出机启停瞬间压力不稳,喷嘴在移动过程中有一点渗料,或者是切片缝(seam)的位置设置成“最近点”导致所有接缝堆在一起。视觉上它不是灾难性的,但非常影响表面质量,对需要展示外观的模型来说几乎不能接受。

最后是stringing,也就是拉丝。很多朋友把它和zits搞混,其实区别很明显:zits是在同一个点堆积出来的小疙瘩,stringing则是喷嘴在两点之间移动时,残留在喷嘴口的熔融塑料被拉成一条细丝,像蜘蛛网一样横跨在两个打印特征之间。常见于打印猫耳、模型文字、或者任何需要频繁移动喷头的区域。温度偏高、回抽距离不足、回抽速度太慢、耗材受潮,都是导致stringing的原因。PLA拉丝会是半透明的白色细线,PETG拉丝则更粗更黏,经常完全无法忽略。如果只是偶尔一两根细丝,后处理用热风枪吹一下就行,但如果模型表面全是丝,就说明参数需要整体调整了。

1.2 为什么一定要用视觉模型来检测

听到这里你可能会说,我自己守着打印机看屏幕也能发现啊。问题是人的注意力撑不了几十分钟,更不用说夜里睡觉的时候。哪怕加了网络摄像头,靠肉眼盯着画面判断,要么反应太慢,要么因为角度问题看不到细节,尤其像stringing这种极细的丝,在低分辨率画面里几乎不存在。

传统的“堵头检测”更多靠的是加速度传感器或者挤出机电流的变化,但这种方法只能检测到喷嘴堵住这种严重的机械故障,对spaghetti的早期形态(比如首层脱离后悬空挤出)完全没有办法。Zits和stringing更是纯视觉的问题——它们对机器运行状态几乎没有影响,只在最终外观上体现出来。所以我们需要一个始终在线、能对每一帧画面做判断的视觉系统。目标检测模型正好干这个:给它一帧画面,它告诉你画面里有没有缺陷、缺陷在哪、属于哪一类、置信度多少。我把检测系统做成一个独立服务,放在打印机旁边,用摄像头对准打印区域,一旦检测到缺陷就切断打印机电源并推送告警,这样人该睡觉睡觉,该上班上班,模型替我看机器。

2. 数据集从零到一:采集、筛选、标注的实操记录

任何检测模型的核心都是数据。YOLO26的权重里已经包含了很多通用物体的特征,但它不知道“3D打印机的spaghetti”长什么样。所以我需要自己准备一批覆盖各种打印机、各种光线、各种打印阶段的数据,并完成标注。这个过程比我想象中更耗时,但有几条经验确实能让效率翻倍。

2.1 图像采集:角度、光线和相机选型

我采集数据用的不是工业相机,就是普通的USB免驱摄像头,1080p分辨率,架在打印机正面偏上的位置,大约45度俯视,能同时看到热床平面和已成型的模型侧面。这个角度对三类缺陷来说比较友好:spaghetti在热床上非常明显,zits在侧面光照下能看出凸起阴影,而stringing在两处结构之间形成的高光细丝正好能被捕捉到。

光线是很多人忽略的一个点。我在白天和晚上分别采集了一批数据,发现晚上关了打印舱灯、只靠环境光时,zits和stringing几乎完全融进背景里,不说人眼,连标注都很难看清。后来我在打印机附近加了一个常亮的白色LED补光灯,从正面偏侧的角度打光,让缺陷产生阴影,检测效果立刻就好了很多。另外,摄像头要尽量固定住,不要用那种可以随意掰动的手机支架,每次挪动都会导致标注框失效。我大概花了两周时间,前后采集了4000多张图片,里面既有正常打印过程的画面,也有故意触发缺陷拍下来的画面。故意制造缺陷的过程其实不复杂:把首层喷头Z轴抬高0.2mm,让耗材完全粘不住,几分钟后就开始拉丝成团;把回抽关闭,打印一个多突起的测试模型,就能稳定产出stringing;把切片缝设置成“随机”,挤出回抽压力没调好,就会冒出一堆zits。

采集完的图需要筛选。不是所有帧都有用,比如有些画面喷头刚好挡住了缺陷区域,有些画面热床温度高导致热噪点过多。我写了一个小的图像质量检查脚本,用方差判断模糊度,用直方图判断曝光是否异常,筛掉了大约15%的废图。剩下的照片按8:1:1分成训练集、验证集和测试集,保证同一个打印过程的不同帧不会同时出现在两个集里,否则模型会“背答案”。

2.2 标注策略与类别平衡的坑

标注工具我用的是labelImg,虽然界面老旧一点,但用顺了效率很高。三个类别里,spaghetti和zits的框相对好打,stringing最烦人——它经常是细长一条斜跨在画面上,如果框方向不是水平的话得打很多个点。YOLO的默认标注框是水平的矩形,对细长丝状物其实不太友好,所以我的策略是:如果一根丝比较长,就把它切成多个小矩形框,每个框尽量覆盖一段清晰的丝;如果丝很密很乱,干脆把整片区域标成一个大的stringing框。这样做模型精度会稍微下降一点,但对实时监控而言,只要能把区域框出来就够了。

另一个大坑是类别不平衡。我最初的数据里spaghetti占了一半以上,因为故意做破坏实验时留了很多素材,zits和stringing相对少,模型训练完一测,spaghetti的精确率很高,但zits经常漏检,stringing的误报又多。后来我对三类的数量做了平衡,每类保持大约1400个标注实例,zits不够就再去打印几个不同颜色、不同层高的测试块补拍。还有一个更隐蔽的问题:给同一目标重复标注的框大小差异很大。一个人今天标了个大框,明天状态不好标了很小的框,模型会对同一目标学到两种完全不同的尺度。我后来规定所有标注框都要紧贴目标外轮廓,宁可小一点不要框大,框大的背景噪声容易把模型带偏。

3. 训练YOLO26模型的完整流程:从零到收敛

数据准备好了以后,训练阶段反而显得比较顺。但其中有几个参数值得仔细聊一聊,因为它们直接影响了模型最终能不能拿来用。

3.1 环境准备和预训练权重的选择

YOLO26的推理代码我去掉了一些不必要的依赖,只需要一个干净的Python环境、PyTorch和OpenCV就可以。显卡是一块中等的消费级GPU,6GB显存,训练640x640输入分辨率的模型刚好够用。如果你的显卡显存小于6GB,我建议先别直接上,可以用云GPU,或者把输入分辨率降到512x512。虽然512尺寸下细小的stringing会更难检测,但总比跑不起来强。

关于预训练权重,我直接用官方提供的COCO预训练权重来初始化模型,而不是空模型从头训。YOLO26的骨干网络已经从自然图像里学到了大量底层特征(边缘、纹理、色彩梯度等),这些对3D打印缺陷场景同样有效。我对比过从零训练和用预训练权重微调的两个模型,前者的mAP@0.5大约低了7个百分点,而且收敛速度慢了不少。所以如果你看到某篇文章说“训练目标检测不需要预训练权重”,那多半是数据特别多或者类别特别偏门的情况,不在我们这个场景里适用。

3.2 训练参数:输入尺寸、批次、学习率和数据增强

下面直接贴一份我最终使用的训练配置,可以按需微调:

参数数值说明
输入尺寸640x640保持默认,对三维打印场景足够
Batch size166GB显存下,结合梯度累积实现
Epochs150前90轮冻结骨干,后60轮全网络微调
优化器AdamW初始学习率0.001,配合余弦退火
学习率0.001 -> 0.0001余弦退火调度,最后阶段更低
灰度增强0.3模拟不同光照下的灰度变化
翻转增强0.5水平翻转和垂直翻转都开
Mosaic增强1.0四合一图像混合,增强对复杂背景的泛化
类别损失权重三类分别 1.2, 1.0, 1.1给spaghetti略大权重,防止严重漏检

这里特别提一下冻结骨干训练。前90轮让整个模型参数不动骨干网络,只训练检测头和颈部网络,能够保留预训练特征,同时加速收敛。如果一上来就全网络微调,数据量又不大的话,很容易出现过拟合。后60轮解冻骨干,让底层特征也适应3D打印场景,mAP能再涨两三个点。学习率方面,我试过固定学习率训100轮,结果loss一直横盘在0.08左右,换成余弦退火后明显带动了最后阶段的收敛。数据增强里最有用的是灰度增强和Mosaic。因为实际部署时打印机的航拍灯角度可能变,灰度增强让模型不至于被光照变化干扰;Mosaic增强则让模型学会在包含大量背景杂物的区域识别目标,尤其是spaghetti这种大面积缺陷。

训练过程中我通常每5轮保存一次权重,然后直接看验证集的PR曲线。等到loss不再下降且验证集的mAP@0.5连续三轮没提升时,就提前终止。最终模型在验证集上的mAP@0.5大约在94.5%左右,mAP@0.5:0.95大概81.2%。这个成绩对三类缺陷来说已经够用,下面会专门讲实际部署时性能如何。

4. 部署到打印机旁边:实时监控与自动处置方案

模型训练好了,接下来最麻烦的是怎么稳稳地部署在生产现场。我这里说的“现场”就是普通工作室的桌面,没有工业级的恒光环境和固定床架,所以杂事特别多。从摄像头安装到告警逻辑,每一步都有值得说明的地方。

4.1 摄像头选型和安装角度

摄像头我推荐用至少1080p分辨率的定焦镜头。自动对焦的摄像头不一定好,因为打印过程中喷头会移动,对焦系统可能来回拉风箱,导致画面模糊。我的做法是手动固定焦距到打印区域,用延长USB线连接主机,这样画面不会因为对焦不稳定而影响检测。帧率不需要太高,10fps就足够,因为三缺陷都是慢速发展,每秒检测一次甚至都能抓住。但推流到网页上的预览帧率可以单独设成30fps,方便人肉监视。

安装角度前面提了45度俯视,这里再补充一点:不要正对热床中心,而是让摄像头偏向打印机的左侧或右侧。这样既能看清喷头运动路径,又能让zits在侧光下呈现立体感。同时摄像头最好离打印区域60cm左右,过近会导致画面边缘畸变严重,特别是广角摄像头会让直线变成弧线,影响标注框的准确性;过远则树小细节看不清楚。我用了一支普通镜头,焦距大概4mm,放在打印机框架上,畸变在可接受范围内。

4.2 推理、告警与停机机制

部署时我写了一个Python服务,核心逻辑很简单:用OpenCV读取摄像头帧,送入YOLO26模型推理,得到检测框后做后处理。后处理包括三件事:一是过滤置信度低于0.35的框;二是同类框用NMS合并,保留最高分的那个;三是根据类别做不同的“触发策略”。

spaghetti的触发策略是:连续3帧检测到置信度0.7以上的spaghetti框,且框面积占画面的5%以上,就判定失败,立即执行G-code指令关闭热床和挤出机,然后通过继电器切断打印机电源。这里连续3帧是为了防止单帧误检,比如喷头反光偶尔被当成面条。zits的触发策略相对保守,因为zits的出现不一定代表打印失败,更多时候只是表面瑕疵。我做成“打印完成后统计zits数量,超过每层10个则提示调参”。stringing则更复杂,因为轻微拉丝在高光下容易被误报,我把置信度阈值调到0.5,并且要求框长度超过图像宽度的20%才算有问题,这样轻微的几根细丝不会频繁触发告警。

另外,告警不只是弹窗。我把检测结果写进一个本地文件,每30秒推送到手机;一旦触发spaghetti停机,立刻发送一条高优先级推送。整个推理速度在CPU上大概是300ms一帧,在GPU上不到30ms,所以10fps的视频流完全能实时处理。这里有个坑:USB摄像头长时间运行会出现帧缓冲堆积,画面滞后十几秒。解决方法是每隔1小时自动重启摄像头采集线程,或者用包管理工具自动重启一下服务,避免内存泄漏导致推理越来越慢。

5. 实测效果与问题排查:从“能跑”到“好用”

模型在验证集上表现不错,真正放到打印机旁跑起来之后还是会遇到各种意料之外的情况。我总结了一些典型的场景,希望能帮你少走弯路。

5.1 误报和漏报的真实场景

最典型的误报是喷头停在边缘时,有一坨融化的耗材挂在喷嘴口,看起来和spaghetti非常像。其实它不是面条,是普通的渗料,过一会儿就会滴在热床上。一开始模型频繁在这里触发报警,我把spaghetti的面积阈值调大了一点,同时在检测框的中心接近喷头指定位置时稍微降低置信度加权,因为喷嘴附近本来就是容易“长”怪东西的区域。另一个误报是热床上残留下来的上一次打印的支撑材料或者小碎料,模型会把它们识别成缺陷。这个解决不了,只能每次打印前人工清理热床。为了尽量降低影响,我在摄像头画面里画了一个“打印区域”多边形,只检测这个区域内的缺陷,热床边缘的杂物就不参与了。

漏报最麻烦的情况是黑色耗材。黑色塑料在暗淡背景下几乎不反射光,尤其是PRUSA和PETG黑料,拉丝和zits都非常难检测出来。我试过调高曝光、加补光灯,效果都不够好。最终的办法是增加训练数据,专门打印了两个黑色耗材的缺陷样本,把对比度调到很低的画面也标进去,模型的鲁棒性才上来。总的说,目前这套系统在正常光照下,spaghetti检测的准确率约96%,zits约89%,stringing约87%。漏报主要出现在细丝太细且重叠严重时,这时候人会看不出,模型也很难做到完美。

5.2 性能优化和后续可扩展的方向

如果你觉得当前的模型还不够准,可以从两个方向继续优化。第一是继续“难例挖掘”:把每次误报、漏报的图片保存下来,定期加入训练集重新训练,这是最有效的闭环。我目前的版本就是这样反复迭代了三轮,缺陷识别率提升了近6个百分点。第二是检查自己的数据是否过于单一。我最初只用了一台打印机、一个型号的耗材,后来借了朋友的双喷头打印机和不同品牌的耗材,补拍了2000多张,模型的泛化能力明显上升。

运行效率上,如果将来要同时监控多台打印机,可以把YOLO26的backend换成TensorRT优化,把推理时间再压到10ms以内;或者用带NPU的嵌入式设备,功耗低也不占桌面空间。另外也可以挂上“打印进度判断”功能,识别模型目前在第几层,再针对不同阶段优先关注不同的缺陷类型,比如底层的spaghetti风险高,中层的zits开始出现,顶层的stringing会更频繁。这样做能进一步减少误报。

这套系统我前前后后跑了两个多月,最大的体会是:缺陷检测这件事,模型不一定要做到学术级的完美,足够稳定、能长期跑不崩溃,比单张图片的精确率更重要。把那些容易漏、容易误的场景补进训练集,让模型跟着你的使用场景一起长大,才是真正“告别3D打印失败”的门道。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进