# U-Net 光纤识别
U-Net 光纤识别技术文档项目IntelligentSorting 光纤色序识别系统模块基于 U-Net 的光纤分割推理版本v1.0日期2026-05目录概述图像分割原理U-Net 模型简介模型架构与参数训练流程推理流程软件使用说明常见问题与排查关键设计决策文件目录结构1. 概述1.1 项目背景光纤色序识别系统需要从产线相机拍摄的图像中识别出每根光纤的位置和颜色顺序。传统的基于颜色阈值 规则法在以下场景失败光纤颜色相近如深蓝/紫、深绿/黑光纤之间有窄黑缝颜色边界模糊不同光照条件下颜色漂移不同产品4 根/8 根/12 根光纤需要泛化引入 U-Net 深度学习分割模型把识别光纤位置这个问题从规则计算变成像素级语义分割由神经网络学习什么是光纤、光纤之间在哪里分隔。1.2 技术方案相机图 (3840×3040) ↓ 工人手动框选 ROI ↓ ROI 规范化 (调整宽高比和尺寸) ↓ U-Net 推理 → 二值 mask ↓ 连通域分析 → 数出 N 根光纤 ↓ 每根光纤中心采样 RGB ↓ LAB ΔE 距离匹配颜色库 ↓ 输出色序结果职责分工U-Net只负责在哪里有光纤语义分割LAB ΔE负责光纤是什么颜色颜色匹配两个模块解耦颜色库可以独立维护模型不需要识别具体颜色。2. 图像分割原理2.1 什么是语义分割图像分割是把一张图的每个像素都打上标签的任务。U-Net 做的是二值语义分割——每个像素只有两个标签0背景1或255光纤输出是一张和输入同样大小的黑白图白色区域代表模型认为是光纤的像素。2.2 与目标检测的区别任务输出应用分类整张图一个标签这是猫/狗目标检测几个矩形框在图中圈出每只猫语义分割每个像素一个标签每个像素属于猫还是背景光纤识别需要的是像素级精度——必须知道光纤的精确边界才能后续准确切分相邻光纤。所以选择分割而不是检测。2.3 模型输入输出输入尺寸512 × 128宽 × 高通道3BGR → RGB数据类型float32经过 ImageNet 标准归一化归一化公式(pixel/255 - mean) / std其中mean [0.485, 0.456, 0.406]std [0.229, 0.224, 0.225]Tensor 形状[1, 3, 128, 512]NCHW输出尺寸512 × 128与输入相同通道1数据类型float32logits未经 sigmoid 的原始分数Tensor 形状[1, 1, 128, 512]后处理对 logits 应用 sigmoidprob 1 / (1 exp(-logit))阈值化mask prob 0.85 ? 255 : 0缩放回原始 ROI 尺寸INTER_NEAREST3. U-Net 模型简介3.1 模型由来U-Net 由 Olaf Ronneberger 等人于 2015 年提出原始论文是用于生物医学图像分割细胞、组织切片。它有几个特点恰好契合光纤分割任务少量样本就能训练医学影像数据稀缺U-Net 设计上就是少数据 强增强的思路。光纤项目从 ~40 张训练图就能得到可用模型。像素级精度高U-Net 通过跳跃连接保留高分辨率细节能精确还原光纤之间的窄黑缝。结构简单可控参数量小本项目仅 0.48M推理快GPU 上 50ms适合产线实时识别。3.2 名字的由来U-Net 网络结构图长得像字母 “U”——输入 ─→ 下采样 ─→ 下采样 ─→ 下采样 ─→ 底层 ↓ 输出 ←─ 上采样 ←─ 上采样 ←─ 上采样 ←─ ─┘左侧压缩路径Encoder逐层降低分辨率、提取抽象特征。右侧扩展路径Decoder逐层恢复分辨率、重建像素级输出。两侧对称连接形如 U 字。3.3 关键设计跳跃连接Skip ConnectionU-Net 的精髓在于编码器每一层都直接连接到解码器对应层Encoder L1 ──────────────── Decoder L1 ↓ ↑ Encoder L2 ───────────── Decoder L2 ↓ ↑ Encoder L3 ──────── Decoder L3 ↓ ↑ Bottleneck这样做的好处解码器既能用到深层抽象特征光纤是不是光纤状物体又能用到浅层细节信息光纤的精确边缘在哪里对光纤识别来说光纤之间的窄黑缝就是浅层细节——如果不用跳跃连接模型会把相邻光纤糊成一团。4. 模型架构与参数4.1 网络结构本项目输入: [B, 3, 128, 512] │ ├─ Encoder1 (DoubleConv 3→16) [B, 16, 128, 512] │ └─ MaxPool 2×2 [B, 16, 64, 256] ├─ Encoder2 (DoubleConv 16→32) [B, 32, 64, 256] │ └─ MaxPool 2×2 [B, 32, 32, 128] ├─ Encoder3 (DoubleConv 32→64) [B, 64, 32, 128] │ └─ MaxPool 2×2 [B, 64, 16, 64] ├─ Encoder4 (DoubleConv 64→128) [B, 128, 16, 64] ← Bottleneck │ ├─ Decoder3 (Upsample Concat DoubleConv 128→64) ├─ Decoder2 (Upsample Concat DoubleConv 64→32) ├─ Decoder1 (Upsample Concat DoubleConv 32→16) │ └─ Head (Conv 1×1, 16→1) [B, 1, 128, 512] ← logits4.2 DoubleConv 块基础单元DoubleConv(in_c,out_c):Conv2d(in_c,out_c,kernel3,padding1)BatchNorm2d(out_c)ReLU Conv2d(out_c,out_c,kernel3,padding1)BatchNorm2d(out_c)ReLU每一层都是两次 3×3 卷积 BN ReLU。4.3 模型参数量项数值总参数量0.48M (480K)FP32 模型大小~2.5 MBGPU 推理时间10-50 msCPU 推理时间100-300 ms参数量极小部署友好。这个网络规模远小于通用的 ResNet/EfficientNet动辄 10M 参数是有意控制的——任务相对简单二值分割 输入小过深的网络反而会过拟合。5. 训练流程5.1 数据准备5.1.1 收集原图工人拍摄不同产品的光纤图分辨率不限项目中混用了 1920×1080 和 3840×3040。5.1.2 ROI 裁剪使用crop_roi_multi.py交互式裁剪每张原图框选光纤所在区域。要点方向一致所有 ROI 必须保持光纤竖向走线、左右并排Y 方向尽量取满从图顶贴到图底留 5-10 px 黑边X 方向紧贴光纤左右各留 5-10 px 黑边输出尺寸不必统一宽高比从 0.17 到 0.79 都可以裁剪结果保存到multi_train/roi/并自动记录到crop_log.json。5.1.3 Mask 标注使用label_mask_multi.py半自动标注自动初始化LAB-L 通道 Otsu 阈值生成初始 mask自动切缝基于列方向亮度暗谷自动切开粘连的连通域手动修正圆形画笔涂改 / 竖条模式快速补整根光纤每根光纤标成独立的竖向白色条光纤之间留 4-8 px 黑缝。输出保存到multi_train/mask/进度记录到label_log.json。5.1.4 样本检查通过seeRoi.py输出所有 ROI 的尺寸和宽高比剔除ratio 1.0 的异常图光纤横向走线与训练方向不一致标注质量极差components0的图模糊不清的图最终训练集约 40 张。5.2 数据增强为补充样本多样性训练时对每张图随机应用增强范围作用水平缩放0.4 - 1.8让模型见到不同密度/宽度的光纤排列水平翻转50%光纤左右对称安全亮度抖动±20%适应光照变化对比度抖动0.8 - 1.2适应曝光差异高斯噪声σ5模拟相机噪声不做的增强旋转光纤方向是有意义的旋转会破坏方向特征垂直翻转光纤上下端形状不同弹性形变会让光纤间黑缝弯曲5.3 损失函数BCE × 边界加权 Dice LossBCE二元交叉熵基础逐像素分类损失。边界加权通过形态学梯度dilate - erode找到 mask 边界像素给这些像素 5 倍权重。强迫模型重点学习光纤边缘这是连通域计数正确的关键。weightones(mask.shape)boundarydilate(mask,iter2)-erode(mask,iter2)weight[boundary0]5.0loss_bceBCE(pred,target)*weightDice Loss补充指标对类别不平衡背景多于光纤友好。dice1-(2×|pred ∩ target|1)/(|pred||target|1)最终 losstotal_loss weighted_BCE dice5.4 训练参数项值OptimizerAdamW, lr1e-3, weight_decay1e-4SchedulerReduceLROnPlateau (val_loss 5 epoch 不降则 lr×0.5)Batch size8Max epochs200Early stopval_loss 30 epoch 不改善Train/Val 切分8:2按光纤根数分层抽样输入尺寸512×1285.5 训练监控输出文件output/best.pthval_loss 最低时的 PyTorch 权重output/fiber_unet_multi.onnx训练完自动导出的 ONNXoutput/fiber_unet_multi_sim.onnxonnxsim 简化后的 ONNX部署用output/train_log.csv每个 epoch 的指标output/debug_val_epoch_*.png每 10 epoch 在 val 集上的预测可视化原图 | GT | Pred 三栏拼图output/split.json记录 train/val 划分健康指标train_iou持续上升到 0.85val_iou跟随 train_iou 上升差距 5%val_loss平稳下降后趋稳6. 推理流程6.1 整体流程1. 工人手动框 ROI 2. AdjustRoiAspectRatio (规范化 ROI 尺寸) 3. ExtractRoiMat (从原帧 byte[] 提取 ROI 区域) 4. FiberSegmenter.Segment (U-Net 推理) 5. CountAndExtractFibers (连通域分析) 6. 每根光纤中心采样 RGB 7. ColorMatcher.FindClosest (LAB ΔE 匹配颜色库)6.2 关键步骤详解6.2.1 ROI 规范化AdjustRoiAspectRatio工人拉的 ROI 形状不可控可能是横条、竖条、方形但 U-Net 输入固定为 4:1 比例512×128。强制 resize 会导致严重失真。规范化逻辑- 高度 1300 → 垂直裁剪到 ~1000 - 高度 700 → 垂直扩展到 ~700 - 宽高比 2.0 → 横向扩展到 2.0 - 已经是长条W/H ≥ 2→ 不变规范化后ROI 进入 U-Net 时的失真控制在可接受范围。6.2.2 U-Net 推理FiberSegmenter.Segment// 1. ROI resize 到 512×128Cv2.Resize(roi,resized,newSize(512,128),InterpolationFlags.Linear);// 2. BGR → RGB → 归一化 → NCHW tensorvarinputnewDenseTensorfloat(new[]{1,3,128,512});foreachpixel:input[0,0,y,x](R/255-0.485)/0.229input[0,1,y,x](G/255-0.456)/0.224input[0,2,y,x](B/255-0.406)/0.225// 3. ONNX Runtime 推理varresults_session.Run(inputs);varlogitsresults.AsTensorfloat();// 4. Sigmoid 阈值 (0.85)foreachpixel:prob1/(1exp(-logit))mask[y,x]prob0.85?255:0// 5. Resize 回原 ROI 尺寸Cv2.Resize(maskSmall,maskOrig,originalSize,InterpolationFlags.Nearest);6.2.3 连通域分析CountAndExtractFibers// OpenCV 8 邻域连通域标记Cv2.ConnectedComponentsWithStats(mask,labels,stats,centroids,PixelConnectivity.Connectivity8);// 过滤噪点foreachcomponent:if(areaminArea||widthminWidth)skip;// 按中心 X 从左到右排序,作为最终光纤序号输出FiberRegion[]每个元素包含Left/Right光纤的 X 起止Width光纤宽度CentroidX质心 XArea面积6.2.4 颜色采样对每根光纤在其 X 范围的中心 40% 区域采样sampleStartsegStartwidth*0.3sampleEndsegStartwidth*0.7为什么取中心 40%边缘像素颜色不稳定受 mask 边界影响圆柱光纤中央可能有高光但中心 40% 区域仍是主色在该 X 区间内 Y 方向所有像素都参与平均抗噪能力强输出RGB三元组进入ColorMatcher匹配颜色库。6.3 性能指标项值单次推理时间 (GPU)10-50 ms单次推理时间 (CPU)100-300 ms端到端识别时间200-500 ms12 根光纤识别准确率 95% (规范化 ROI)4-8 根光纤识别准确率80-90% (训练数据偏少)7. 软件使用说明7.1 训练数据准备步骤 1放原图把产线相机拍的图任意分辨率放到E:\Deeplearning\U-Net\multi_train\multi_images\步骤 2裁剪 ROIcdE:\Deeplearning\U-Net\multi_train\scripts python crop_roi_multi.py操作左键拖框选光纤区域保持竖向走线s保存并跳下一张n跳过当前图b回到上一张r重画q退出进度自动保存输出multi_train/roi/crop_log.json步骤 3标注 Maskpython label_mask_multi.py每张图的工作流进图后自动初始化mask看到大致正确的红色覆盖按数字键输入预期光纤根数回车按2自动切缝按B切到 mask 视图肉眼检查漏切的位置按V切到竖条模式鼠标移到黑缝位置右键单击切开误切的位置左键涂回去按D自动保存并跳下一张输出multi_train/mask/label_log.json7.2 训练python train_multi.py训练过程自动从label_log.json读取所有有效样本按 8:2 分层抽样切分 train/val跑 200 epoch早停通常在 80-150 epoch 触发自动保存最优模型 导出 ONNX输出multi_train/output/下的所有文件7.3 部署到 C# 端# 1. 拷贝 ONNX 到 C# 项目copy E:\Deeplearning\U-Net\multi_train\output\fiber_unet_multi_sim.onnx ^ E:\ZD\project\IntelligentSorting\IntelligentSorting\Models\fiber_unet_sim.onnx# 2. 重新编译 C# 项目并启动C# 启动后自动加载模型日志显示[Recognize] ✅ U-Net 模型加载成功: .../Models/fiber_unet_sim.onnx7.4 产线识别启动 IntelligentSorting 主程序接入相机或选择打开照片在画面上拖框选择 ROI包住所有光纤工人输入预期根数 N点击识别按钮查看识别结果每根光纤的颜色匹配情况8. 常见问题与排查8.1 识别根数比实际少症状12 根产品识别成 10 根相邻光纤被合并可能原因训练数据不足4/8 根产品训练样本少 10 张泛化差mask 阈值过低当前threshold0.85可以试 0.90 让光纤更瘦粘连模型对相邻颜色相近的光纤分不开排查查看E:\Deeplearning\diag\mask_*.png看模型预测出的 mask 是不是把相邻光纤糊成一团。8.2 识别根数比实际多症状12 根识别成 15 根光纤被切碎可能原因ROI 失真宽高比离 2:1 太远强制 resize 让光纤压成短色块光纤中央反光高亮反光被模型误判为两根光纤之间的间隙排查看[Adjust]日志确认规范化后 ROI 宽高比是 2.0查看 mask 图看光纤中央是否有黑色裂缝8.3 同一束光纤照片识别 OK、相机识别失败典型原因照片和相机两次拉的 ROI 尺寸差异大导致 U-Net 输入失真程度不同。解决确认AdjustRoiAspectRatio已启用看[Diag]日志中 ROI 规范化前后的尺寸。8.4 模型加载失败症状日志显示U-Net 加载失败,回退规则法排查检查IntelligentSorting/Models/fiber_unet_sim.onnx是否存在检查 ONNX Runtime 版本是否兼容项目用 1.x看完整错误信息8.5 训练 val_iou 上不去 0.80可能原因数据集污染有方向异常的图光纤横向走线、斜向走线标注质量差某些 mask 漏标或错切类别不平衡某种根数的光纤样本太少排查工具seeRoi.py列出所有 ROI 的宽高比ratio 1 的是异常output/debug_val_epoch_*.png肉眼看预测质量output/train_log.csv看 train_iou 和 val_iou 的差距差距 10% 说明过拟合9. 关键设计决策以下决策在项目演进过程中确立不可轻易推翻9.1 U-Net 只做分割颜色匹配用 LAB ΔE原因颜色识别交给 U-Net 需要每种颜色都有大量样本扩展性差。把位置和颜色解耦U-Net 学什么是光纤位置 形状颜色库可以随时增删颜色不需要重训模型9.2 每根光纤标成独立竖条 4-8px 黑缝原因连通域计数要靠光纤之间有黑色间隙才能数对。如果训练 mask 把相邻光纤连成一片模型推理时也会连永远数不对。9.3 训练时 BCE loss 加边界加权原因光纤边缘像素决定了相邻光纤能否分开比光纤内部像素重要 5 倍。9.4 连通域计数 → 每根独立采样不再等分原因上一版工人输入 N → 等分采样在光纤宽度不均时严重错位。改为先用 U-Net 找出每根光纤的实际位置再单独采样鲁棒得多。9.5 ROI 规范化必须做原因U-Net 输入 512×128 是 4:1 比例。如果 ROI 是接近方形W/H1强制 resize 会让光纤压扁失真。必须先把 ROI 调整到合理尺寸再喂模型。9.6 不旋转、不垂直翻转原因光纤的方向竖向走线是有意义的。如果训练时旋转 90°模型会学到两种方向的光纤都有效推理时反而困惑。10. 文件目录结构10.1 训练侧PythonE:\Deeplearning\U-Net\multi_train\ ├─ multi_images\ # 原图输入 ├─ roi\ # 裁剪后的 ROI输入给标注 ├─ mask\ # 标注后的 mask训练目标 ├─ _trash\ # 删除的样本备份 ├─ output\ # 训练产出 │ ├─ best.pth # 最优 PyTorch 权重 │ ├─ fiber_unet_multi.onnx # 导出的 ONNX │ ├─ fiber_unet_multi_sim.onnx # Simplify 后的 ONNX部署用 │ ├─ train_log.csv # 训练日志 │ ├─ split.json # train/val 划分 │ └─ debug_val_epoch_*.png # 调试可视化 ├─ scripts\ # Python 脚本 │ ├─ crop_roi_multi.py # 多图 ROI 裁剪 │ ├─ label_mask_multi.py # 多图 mask 标注 │ ├─ train_multi.py # 训练脚本 │ ├─ seeRoi.py # ROI 信息查看 │ └─ grid_view_rois.py # 网格化查看所有 ROI ├─ crop_log.json # 裁剪进度记录 └─ label_log.json # 标注进度记录10.2 部署侧C#E:\ZD\project\IntelligentSorting\IntelligentSorting\ ├─ Models\ │ └─ fiber_unet_sim.onnx # 部署用的 ONNX 模型 ├─ Core\Vision\ │ ├─ FiberSegmenter.cs # U-Net ONNX 推理封装 │ └─ ProductRecognitionService.cs # 整体识别流程 └─ ...10.3 诊断输出E:\Deeplearning\diag\ ├─ roi_{timestamp}.png # 推理时实际喂入模型的 ROI └─ mask_{timestamp}.png # 模型预测的 mask这两个文件用于排查为什么这次识别错了——肉眼看 ROI 是否清晰、mask 是否合理。11. 后续改进方向短期补充 4/5/8 根光纤训练样本目前总样本 40 张12 根占 22 张偏斜严重收集矮 ROIY 700和超高 ROIY 1500样本覆盖更广的推理场景中期引入测试集与 train/val 独立定期跑回归加 mask 后处理膨胀-腐蚀清理进一步合并碎片长期探索半监督学习用大量未标注产线图配合少量标注图训练考虑动态输入尺寸的 ONNX 模型避免 ROI 规范化的失真端到端从相机原图直接识别不依赖工人框 ROI附录 A关键代码位置速查功能文件关键方法U-Net 网络定义train_multi.pyFiberUNet,DoubleConvLoss 函数train_multi.pyweighted_bce_dice数据增强train_multi.pyFiberDataset.__getitem__ROI 自动初始化label_mask_multi.pyauto_init_mask自动切缝label_mask_multi.pyauto_split_seamsONNX 推理FiberSegmenter.csSegment连通域分析FiberSegmenter.csCountAndExtractFibersROI 规范化ProductRecognitionService.csAdjustRoiAspectRatio完整识别流程ProductRecognitionService.csRecognizeByEqualPartition附录 B术语表术语含义ROI(Region of Interest)工人手动框选的光纤区域Mask二值图像光纤位置255背景0Logits模型输出的原始分数未经 sigmoidSigmoid把 logits 压到 [0,1] 概率的函数IoU(Intersection over Union)预测 mask 和真实 mask 的重合度Dice类似 IoU 的另一种重合度指标BCE(Binary Cross Entropy)二元交叉熵损失连通域mask 中相互连接的白色像素群每根光纤对应一个连通域ΔELAB 色彩空间中两个颜色的距离 5 视为同色 12 视为不同色ONNX跨框架的神经网络模型格式PyTorch 训练后导出C# 用 ONNX Runtime 推理文档结束