1. 炉前烟尘识别到底在解决什么问题第一次接触这个需求是在一个铜熔炼车间炉长拉着我看炉口说“你帮我看看现在这个烟算浓还是淡”。我当时愣了一下——这玩意儿肉眼判断全凭经验老师傅说浓就浓说淡就淡换个人来看结论可能完全相反。更麻烦的是环保数据要留痕加料时机要卡点全靠人盯炉口一个班下来眼睛都花了还容易漏看。熔炼炉烟尘AI视觉识别要干的事情说白了就是用摄像头替代人眼把炉前烟尘状态实时分成浓烟、淡烟、无烟三类输出结构化信号给中控系统。它解决的核心痛点有三个第一人工判断主观性强、标准不统一第二炉前环境高温高尘人不能长时间靠近第三烟尘状态直接关联加料节奏、燃烧效率和排放合规需要秒级响应。这套方案适合谁参考做工业视觉落地的算法工程师、冶金/铸造行业的自动化改造负责人、以及计算机视觉方向想找真实场景练手的学生。我后面会从方案选型、数据采集、模型训练、部署调优到现场踩坑完整拆一遍代码和参数都给到能直接抄的程度。注意工业现场和实验室跑demo完全是两码事。实验室里准确率99%的模型到炉口可能连60%都保不住原因后面会细说。2. 整体方案设计与技术选型思路2.1 为什么是“三分类”而不是“浓度回归”很多人第一反应是做个回归模型输出一个0到1的烟雾浓度值看起来更精细。我一开始也这么想但实际跑下来发现两个致命问题。第一标注一致性无法保证。让十个老师傅给同一段视频打浓度分分数能差出0.3。你说0.6算浓还是淡没有客观基准回归目标的噪声太大模型学出来的东西不可信。第二业务侧根本不需要连续值。中控系统要的是决策信号浓烟了要不要加大引风淡烟了要不要准备加料无烟了是不是可以进入下一工序三个离散状态足够驱动逻辑反而更稳定、更好验证。所以三分类是业务需求倒推出来的最优解不是技术上的偷懒。分类边界清晰标注可仲裁模型输出可以直接映射到PLC动作。2.2 视觉方案选型可见光还是红外炉前环境有两个极端一是强光炉火本身是巨大光源普通相机容易过曝二是烟尘遮挡可见光在浓烟下穿透力有限。我对比过三种方案方案优点缺点适用场景工业可见光相机成本低、色彩信息丰富受炉火强光干扰大、浓烟下失效无烟/淡烟区分红外热成像穿透烟尘能力强、不受可见光影响成本高、分辨率低、纹理信息少浓烟/无烟区分可见光红外融合互补性强、鲁棒性最好标定复杂、数据同步要求高高要求场景最终我选了工业可见光相机窄带滤光片的方案。原因很实际预算有限而且三分类里“淡烟”这个中间态主要靠纹理和透明度判断红外图像在这块信息不足。滤光片选的是850nm窄带把炉火里最强的可见光波段压下去同时保留烟尘的散射特征。相机安装位置也有讲究。我试过正对炉口、侧45度、顶部俯视三个角度最后定在侧上方30度、距离炉口约3.5米。正对容易被喷溅物打坏镜头俯视看不到烟的扩散形态侧上方既能拍到烟柱轮廓又能避开大部分飞溅。2.3 模型选型轻量级CNN还是Transformer2024年了Transformer在视觉领域确实火但工业部署要考虑推理延迟和硬件成本。炉前监测要求单帧推理小于50ms才能做到真正的实时。我最终选了MobileNetV3-Small做主干输入分辨率224x224参数量约2.5M在Jetson Xavier NX上单帧推理约18ms。为什么不用ResNet50因为现场部署的是边缘设备功耗和散热都受限大模型跑起来风扇狂转车间温度又高稳定性反而下降。Transformer方案我也试过ViT-Tiny准确率比MobileNetV3高约1.5个百分点但推理时间翻倍到40ms左右而且对训练数据量要求更高。在工业场景里稳定性和延迟比那1.5个点重要得多。实操心得不要盲目追新架构。工业落地的第一原则是“够用就好”模型小、推理快、容易维护比刷榜重要。3. 数据采集与标注的实操细节3.1 现场采集怎么拍到有用的数据数据采集这一步我踩的坑最多。第一次去现场架好相机录了三天回来一看80%的帧都是无效的——要么炉门关着要么画面里全是火花要么曝光过度一片白。后来总结出一套采集规范采集时段覆盖完整生产周期加料、熔化、扒渣、出料每个阶段都要有。浓烟主要出现在加料和扒渣阶段无烟出现在出料后淡烟是过渡态。每类至少采集2000帧有效图像且要覆盖不同班次、不同炉料、不同天气车间光照受外界影响。我最终攒了约12000帧原始数据三分类大致均衡。同步记录工艺参数加料时间、引风频率、炉温。这些后面用来做数据校验比如“引风开到80%还出现浓烟”就是异常样本要单独分析。固定相机参数曝光、增益、白平衡全部手动锁定。自动模式在不同光照下会漂移导致模型学到的是相机参数变化而不是烟尘变化。采集脚本我用的是OpenCV每500ms抓一帧同时写时间戳import cv2 import time import os cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) # 手动锁定曝光具体值现场调 cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0.25) cap.set(cv2.CAP_PROP_EXPOSURE, -6) save_dir raw_data os.makedirs(save_dir, exist_okTrue) count 0 while True: ret, frame cap.read() if not ret: break ts time.strftime(%Y%m%d_%H%M%S) cv2.imwrite(f{save_dir}/{ts}_{count:06d}.jpg, frame) count 1 time.sleep(0.5)3.2 标注规范让三个人标出一样的结果标注是三分类项目里最容易被低估的环节。我一开始让两个实习生标结果一致性只有70%左右。后来制定了明确的标注规则一致性提到92%以上。规则核心是看三个维度烟柱的不透明度、扩散范围、边缘清晰度。浓烟烟柱完全不透明看不到炉口背景扩散范围超过炉口宽度2倍边缘模糊。淡烟烟柱半透明能隐约看到炉口轮廓扩散范围在炉口宽度1到2倍之间边缘有一定清晰度。无烟画面中无明显烟柱或仅有极淡的热气流扰动炉口背景清晰可见。边界样本处理原则拿不准的归到相邻更严重的类别。比如浓烟和淡烟之间拿不准标浓烟。因为业务上漏报浓烟的代价比误报大得多。标注工具我用的是LabelImg改的分类版本每张图只打一个类别标签。12000张图两个人标了约一周交叉校验了两轮。注意标注时一定要把图像分成“训练集”和“验证集”两个文件夹分别标避免同一段视频的相邻帧同时出现在训练和验证里否则验证准确率会虚高。4. 模型训练与调优的完整流程4.1 数据增强模拟现场的各种干扰工业现场的图像变化比公开数据集复杂得多。我用了一套组合增强策略在训练时动态应用import albumentations as A from albumentations.pytorch import ToTensorV2 train_transform A.Compose([ A.RandomResizedCrop(224, 224, scale(0.7, 1.0)), A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(brightness_limit0.3, contrast_limit0.3, p0.7), A.HueSaturationValue(hue_shift_limit10, sat_shift_limit20, val_shift_limit20, p0.5), A.GaussNoise(var_limit(10.0, 50.0), p0.3), A.MotionBlur(blur_limit5, p0.2), A.CoarseDropout(max_holes8, max_height16, max_width16, p0.3), A.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ToTensorV2() ])这里有几个针对性的设计RandomResizedCrop模拟相机焦距变化和烟尘远近差异。RandomBrightnessContrast模拟炉火强弱和曝光波动。MotionBlur模拟烟尘快速流动时的运动模糊。CoarseDropout模拟飞溅物短暂遮挡镜头。验证集只用Resize和Normalize不做增强保证评估结果反映真实分布。4.2 训练配置与类别权重三分类虽然均衡但实际生产中浓烟样本偏少因为浓烟时段短所以我在损失函数里加了类别权重。权重按类别频率的倒数计算import torch import torch.nn as nn from torchvision.models import mobilenet_v3_small # 假设训练集三类样本数为 [4000, 4500, 3500] class_counts [4000, 4500, 3500] total sum(class_counts) weights [total / (3 * c) for c in class_counts] # 约 [1.0, 0.89, 1.14] class_weights torch.tensor(weights, dtypetorch.float32).to(device) model mobilenet_v3_small(pretrainedTrue) model.classifier[3] nn.Linear(1024, 3) model model.to(device) criterion nn.CrossEntropyLoss(weightclass_weights, label_smoothing0.1) optimizer torch.optim.AdamW(model.parameters(), lr1e-3, weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max30)训练超参batch size 64epoch 30初始学习率1e-3余弦退火。在RTX 3060上大约训练40分钟。关键技巧label_smoothing0.1。工业数据标注难免有噪声标签平滑能防止模型对边界样本过度自信实测验证集准确率提升约2个百分点。4.3 训练结果与混淆矩阵分析最终模型在验证集上的表现类别精确率召回率F1浓烟0.960.940.95淡烟0.910.930.92无烟0.970.960.96整体准确率约94%。混淆矩阵显示主要错误集中在淡烟和浓烟的边界以及淡烟和无烟的边界。这符合预期因为这两个边界本身就是连续过渡的。我针对边界样本做了第二轮专项采集在炉口加装了辅助光源让淡烟的纹理更清晰补充了约1500张边界样本重新训练淡烟F1提升到0.94。实操心得不要只看整体准确率。工业场景里漏报浓烟的代价最大所以浓烟的召回率要单独盯。我最终把浓烟召回率调到0.97以上才敢上线。5. 边缘部署与实时推理优化5.1 模型导出与量化训练完的PyTorch模型要部署到Jetson Xavier NX上直接跑FP32推理延迟约35ms不够理想。我做了两步优化第一步导出ONNXdummy_input torch.randn(1, 3, 224, 224).to(device) torch.onnx.export( model, dummy_input, smoke_cls.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}}, opset_version11 )第二步用TensorRT做FP16量化trtexec --onnxsmoke_cls.onnx \ --saveEnginesmoke_cls_fp16.engine \ --fp16 \ --workspace1024 \ --minShapesinput:1x3x224x224 \ --optShapesinput:1x3x224x224 \ --maxShapesinput:1x3x224x224量化后单帧推理降到12ms加上预处理和后处理端到端约20ms满足50ms的实时要求。精度损失不到0.5个百分点完全可接受。5.2 推理服务与平滑策略单帧分类结果会有抖动比如浓烟和淡烟之间来回跳。直接输出给PLC会导致执行机构频繁动作。我加了一个滑动窗口投票from collections import deque import numpy as np class SmoothPredictor: def __init__(self, window_size5, threshold0.6): self.window deque(maxlenwindow_size) self.threshold threshold def update(self, probs): self.window.append(probs) avg np.mean(self.window, axis0) pred int(np.argmax(avg)) conf float(avg[pred]) if conf self.threshold: return -1 # 不确定保持上一状态 return pred窗口大小5对应约100ms的平滑。阈值0.6低于这个值就输出“保持”避免频繁切换。实测下来状态切换频率从每秒3到4次降到每分钟2到3次中控侧反馈很稳。5.3 与PLC的通信对接推理服务输出三分类结果后通过Modbus TCP写给PLC。我用的是pymodbusfrom pymodbus.client import ModbusTcpClient client ModbusTcpClient(192.168.1.10, port502) client.connect() # 寄存器地址0x00010无烟1淡烟2浓烟 def write_state(state): client.write_register(0x0001, state)PLC侧根据状态执行逻辑浓烟触发引风加大淡烟准备加料无烟进入下一工序。整个链路从图像采集到PLC动作端到端延迟约80ms。注意Modbus寄存器地址和数据类型一定要和PLC工程师对齐我因为地址偏移搞错了一次导致引风逻辑反了炉子差点出问题。上线前务必做联调测试。6. 现场常见问题与排查速查6.1 模型在现场掉点怎么办这是最典型的问题。实验室94%现场可能只有80%出头。原因通常有三个第一光照变化。车间白天和晚上光照差异大虽然锁了相机参数但外界光还是会漏进来。解决办法是在炉口加装遮光罩同时训练时加入更强的亮度增强。第二镜头污染。炉前飞溅物和粉尘会逐渐糊住镜头图像变模糊。我加了每4小时自动吹扫的气路镜头前加保护玻璃每周更换一次。同时训练时加入高斯模糊增强让模型对轻度污染有鲁棒性。第三炉料变化。换了一种炉料后烟的颜色和形态可能不同。解决办法是持续采集新数据做增量训练。我一般每两周收集一批新样本微调最后一层分类头。6.2 常见问题速查表现象可能原因排查方法解决措施准确率骤降镜头污染查看原始图像清晰度吹扫/更换保护玻璃浓烟漏报曝光过度检查图像是否过曝调低曝光或加滤光片状态频繁切换平滑窗口太小观察输出日志增大窗口或提高阈值推理延迟高未量化检查推理引擎转TensorRT FP16通信失败地址错误用Modbus调试工具核对寄存器地址夜间误报多光照不足对比白天夜间图像补光或增强训练数据6.3 独家避坑技巧技巧一保留“不确定”状态。三分类之外我实际部署时加了一个隐式的第四态——置信度低于阈值时输出“保持”。这比强行分类要安全得多避免模型在边界上乱跳。技巧二用工艺参数做交叉校验。如果引风频率已经开到90%还报浓烟大概率是模型误判可以触发人工复核。这个逻辑帮我过滤了约30%的误报。技巧三模型版本管理要严格。每次更新模型都要记录训练数据版本、超参、验证指标。我有一次直接覆盖了线上模型结果新模型在某个班次表现很差想回滚都找不到旧版本。后来用MLflow做版本管理再没出过这个问题。技巧四现场调试带个便携屏。炉前环境嘈杂抱着笔记本蹲在炉口调参很不现实。我后来带了一个带HDMI的便携屏和无线键鼠在安全区域远程调试效率高很多。7. 后续可扩展的方向这套三分类框架跑通后其实可以往几个方向延伸。一是烟尘浓度分级细化比如把浓烟再分成“浓”和“极浓”对应不同的引风档位。二是多炉口联动一个车间多个炉子统一调度引风资源。三是结合炉温预测烟尘状态和炉内反应阶段强相关可以用来辅助判断熔化进度。我目前在做的是把分类结果和能耗数据关联分析看看能不能找到“烟尘状态-引风能耗-炉温”的最优控制曲线。初步数据看淡烟阶段适当降低引风频率能耗能降约8%同时不影响排放指标。这个方向还在验证有结果再单独写一篇。代码和配置我都整理在了一个仓库里核心就是MobileNetV3加TensorRT量化加滑动窗口平滑没有花哨的东西但现场跑了半年多稳定性没问题。工业视觉落地简单可靠比先进重要这是我最大的体会。