简介面向图像分割与计算机视觉领域的研究者与学生面部眼镜图像分割数据集提供约16000张带像素级标注的图片标签分为背景和眼镜两类适合直接训练语义分割模型也可用于人脸解析、眼镜佩戴检测等任务。其中训练集约11200张、测试集约4800张每张原图均配有对应mask压缩包共2000个文件其中1998个为PNG图像另有类别说明txt与可视化Python脚本整体约849MB。目前已有91人学习可支撑科研实验、毕业设计或课程实践也适合作为入门分割任务的练习数据。目录结构简洁images与masks文件夹一一对应便于直接接入U-Net、DeepLab等常见分割框架配套脚本可随机抽取一张样本将原始图片、GT图像以及GT在原图上叠加后的蒙版效果一并保存到当前目录帮助快速核对标注质量、直观理解数据组织方式。1. 做面部图像分割的人迟早会撞上眼镜这个硬骨头人脸相关的图像分割项目里最常见的需求是把人脸、头发、皮肤、背景分开眼镜往往只是顺带提一嘴的“可选类别”。但真正上手做过的都知道眼镜大概是面部语义分割里最不好伺候的对象——半透明镜片、反光、细镜腿、大面积遮挡每一个都足够让模型翻车。这个面部眼镜图像分割数据集约16000张数据和标签好处在于规模在一个很舒服的位置不像几十万张的医学图像分割数据集那样需要重型资源也比几千张的小样本扎实得多适合用来验证分割思路、跑基线、甚至直接作为产品原型的训练底子。这个标题的关键词有两个一是“图像分割”二是“标签”。很多入门的人拿到数据集先找训练代码但实际决定项目上限的恰恰是后者——标签怎么组织的、掩码准不准、类别边界有没有脏数据这些直接影响你后面在UNet、DeepLabV3或者yolov8-seg上的所有努力。这篇文章就按我实际做类似数据集的习惯来写先读数据再验数据然后训练最后把眼镜分割特有的坑一个个排掉。2. 拿到数据集先别急着训练看懂目录结构和标签格式2.1 16000张对分割任务意味着什么做图像分割的人对数据集规模都有个直觉一两千张只能做实验验证流程七八千张勉强能训出一个能看的模型超过一万张才开始有“数据红利”。这个数据集约16000张在人脸相关数据集里属于中等偏上的体量。对比一下常见的参考系Pascal VOC大约一万张出头Cityscapes只有五千张带精细标注。也就是说这个体量做单类语义分割眼镜 vs 背景是够的但也不是可以随便浪费的。另外一个容易被忽略的点是“数据和标签”这个说法。很多数据集只给原图标签要自己画那就需要耗费大量时间做人工标注和边界修整。而这里明确说“数据和标签”都包含意味着标注文件已经存在你需要做的主要是格式适配而不是从零标注。这是一个很大的时间节省尤其是对刚开始做分割的人来说标注工具都未必玩得转直接拿着现成标签跑训练学习的焦点能集中在模型和训练本身。2.2 先遍历目录三分钟看清家底拿到任何数据集我习惯写几行代码把目录结构、文件数量、命名规则一次性摸清楚。不要急着双击打开图片一张张看那样效率太低而且看不出整体规律。先扫描一遍心里有个底。import os from collections import Counter data_root face_glasses_dataset extensions Counter() sample_files {} for root, dirs, files in os.walk(data_root): for f in files: ext os.path.splitext(f)[1].lower() extensions[ext] 1 # 记录每种格式的前3个样例文件路径 if ext not in sample_files: sample_files[ext] [] if len(sample_files[ext]) 3: sample_files[ext].append(os.path.join(root, f)) print(文件格式统计:, dict(extensions)) for ext, samples in sample_files.items(): print(f{ext} 示例:) for s in samples: print( , s)这段代码的逻辑很简单遍历整个数据集目录按扩展名统计文件类型顺便记录每种类型的前几个样例路径。跑完之后你会立刻知道这个数据集里有没有原图和标签之外的垃圾文件比如缩略图缓存、说明文档、隐藏的系统文件。如果发现除了jpg/png之外还有奇怪的格式比如tif、bmp那就要注意了——混用格式在训练时容易踩坑需要统一转格式或者写专门的数据加载逻辑。参数说明data_root改成你实际解压后的路径extensions这个Counter对象可以帮你快速看清数据家底sample_files字典记录样例路径方便你接下来人工抽查。这里我用的是os.walk因为数据集通常嵌套多层目录只用os.listdir的话可能漏掉深层文件。2.3 标签格式决定你后面所有代码怎么写图像分割数据集的标签常见的有几种组织方式决定了你要写什么样的数据加载器。第一种是PNG掩码文件每个像素的灰度值代表类别编号比如0是背景、1是眼镜这是最标准的语义分割标签格式训练时直接读进来做one-hot或者交叉熵损失就行。第二种是彩色PNG看起来是一张涂了颜色的图实际上需要做颜色映射比如红色眼镜、黑色背景才能转成掩码。第三种是JSON或者XML里的多边形坐标这种通常来自标注工具LabelMe、CVAT导出需要先渲染成掩码。我拿到数据集之后一般先随机挑几张原图和对应标签做一次可视化叠加确认标签和图像是不是对得上。这一步极其重要我之前就遇到过某个数据集里原图的人脸方向和掩码的人脸方向是镜像的训练出来效果一塌糊涂折腾了两天才发现是数据本身的问题。另一个值得关注的点是标签的单通道还是三通道。很多初学者拿到三通道彩色标签直接用PIL读进来当训练目标然后用交叉熵损失报维度错误就一脸懵。正确做法是先检查标签图像的mode灰度图是L或I彩色图是RGB。如果是RGB需要写一个颜色映射函数转成单通道索引图像素值0、1、2分别对应背景和各个类别。写一个通用转码脚本放在项目里后面每次实验都不用重复写。from PIL import Image import numpy as np def color_mask_to_index(mask_path, color_mapNone): 将彩色标签图转换为单通道索引掩码。 color_map: dict, 例如 {(0,0,0): 0, (255,0,0): 1} img Image.open(mask_path).convert(RGB) arr np.array(img) if color_map is None: # 如果没提供映射表尝试从图片中自动提取颜色 colors np.unique(arr.reshape(-1, 3), axis0) color_map {tuple(c): i for i, c in enumerate(colors)} h, w arr.shape[:2] mask np.zeros((h, w), dtypenp.uint8) for color, idx in color_map.items(): mask[(arr color).all(axis-1)] idx return mask # 调用示例 target color_mask_to_index(labels/0001.png) print(掩码类别值分布:, np.unique(target))这段代码的核心逻辑就是把RGB三元组映射成类别索引。参数color_map是颜色到类别编号的字典例如黑色背景映射为0红色眼镜映射为1。如果不传这个参数代码会自动提取图中所有颜色为每一种颜色分配一个独立的索引——这个自动模式适合快速检查但正式训练时建议显式传入color_map因为自动分配的颜色顺序不稳定可能导致同一类别在不同图片上索引不一致。自动提取颜色在验证标签是否干净时也很适合如果一张标签图颜色数量远远超过预期类别数说明标签有噪点或者格式有误。正常情况下眼镜分割的标签应该只有两种颜色纯色背景纯色目标如果出现第三种颜色大概率是反锯齿边缘或者标注软件留下的彩边需要做二值化处理。3. 训练前先做数据体检掩码对不上图后面全是白干3.1 原图与掩码能不能完整对应直接拿数据集开训是一件很有诱惑力的事尤其是当你已经配好了环境、写好了模型手痒得不行。但我劝你忍一忍。图像分割任务的数据体检比分类任务严格得多——分类任务里标签错了顶多影响一个样本的监督信号分割任务里掩码错位几像素训练出来的边界就会“糊”一圈而且这种问题在Loss曲线里不太看得出来。先跑一个最基本的检查遍历所有原图和掩码确认图片尺寸一致、能否打开、像素值范围是否正常。有些数据集的原图是1920x1080掩码却是960x540这种必须提前发现。from PIL import Image import numpy as np def validate_pairs(img_dir, label_dir, idx_path): 检查原图和标签的完整性。返回问题样本列表。 problems [] with open(idx_path, r) as f: ids [line.strip() for line in f if line.strip()] for pid in ids: img_path f{img_dir}/{pid}.jpg lbl_path f{label_dir}/{pid}.png try: img Image.open(img_path) lbl Image.open(lbl_path) except Exception as e: problems.append((pid, f打开失败: {e})) continue if img.size ! lbl.size: problems.append((pid, f尺寸不一致: img {img.size} vs lbl {lbl.size})) continue img_arr np.array(img.convert(RGB)) lbl_arr np.array(lbl) if img_arr.size 0 or lbl_arr.size 0: problems.append((pid, 空数据)) # 标签值域检查语义分割的标签应该从0开始的小整数 if lbl_arr.max() 10: problems.append((pid, f标签值域异常: max{lbl_arr.max()})) return problems problems validate_pairs(images/, labels/, train.txt) print(f共检查出 {len(problems)} 个问题样本) for p in problems[:10]: print(p)代码逻辑不复杂三个检查点文件能否打开、尺寸是否一致、标签值域是否在合理范围。第三个检查点尤其重要因为很多标注导出的PNG是16位深度的读进来像素值可能是0到65535直接拿去训练会出大问题。如果发现这类情况需要在数据加载时用np.clip或者astype(np.uint8)转一下。切片语法说明img_arr.size是Numpy数组的元素总数不是图片的宽高尺寸这里用来判断数据是否为空。lbl_arr.max()是检查标签像素最大值如果出现超过10的值说明标签不是常规的类别索引需要进一步排查。除了尺寸和值域还需要检查一个分割任务特有的问题原图和掩码是不是“对齐”的。有时候图没问题标签也没问题但标签整体偏移了几个像素。这个问题靠代码检测不出来必须人工抽看。写一个简单的脚本把图片和掩码半透明叠加起来生成到输出目录然后用眼睛扫一遍至少看三五十张。3.2 类别分布和样本均衡度单类分割任务眼镜 vs 背景里类别不平衡是最典型的坑。眼镜在整张人脸图片里通常只占很小比例如果一张图是1600x1200眼镜区域可能只有几万像素占比百分之二三。训练时模型会倾向于把所有像素预测为背景因为这样Loss也能降得很低。体检时把整个数据集的类别占比统计出来写进实验记录文件这是一个好习惯。后面训练的时候每当怀疑模型预测不对劲先看看占比数字再判断是数据问题还是模型问题能省很多排查时间。import numpy as np from PIL import Image def compute_class_ratio(label_files): 统计整个数据集的类别像素占比 total_pixels 0 class_pixels {} for lf in label_files: lbl np.array(Image.open(lf)) total_pixels lbl.size for cls in np.unique(lbl): count int((lbl cls).sum()) class_pixels[cls] class_pixels.get(cls, 0) count print(类别像素统计:) for cls in sorted(class_pixels.keys()): ratio class_pixels[cls] / total_pixels * 100 print(f 类别 {cls}: {class_pixels[cls]} 像素, 占比 {ratio:.2f}%) return class_pixels # label_files 需要提前通过 glob 或读取索引文件获取这段代码统计每个类别在整个数据集里占了多少像素。输出的占比数字是后面很多决策的依据如果眼镜类别占比不到5%你在训练时就应该考虑给眼镜类别的Loss加权或者采用Ohem在线困难样本挖掘策略。我见过不少项目直接忽略这一步最后模型输出的掩码全是背景还以为是模型架构出了问题其实从统计上看模型这么做“完全正确”——因为把所有像素预测成背景的正确率高达95%以上。3.3 眼镜分割特有的脏数据人脸眼镜分割这个具体任务有自己的脏数据特征拿到数据集后一定要有意识地找这些问题。最常见的眼镜标注脏数据是反光误标。镜片上的高光反射里经常包含着周围环境的颜色如果标注人员不够仔细会把反光区域漏标或者把镜片外的反光误标成眼镜。人眼看着可能觉得无所谓但在语义分割训练时这些不一致的标注会让模型学到错误的模式最终表现为镜片边缘的预测结果时好时坏同一张图的预测结果在不同训练轮次之间抖动明显。第二种是透明镜片区域的标签不统一。有些标注员把透明镜片完整标出来有些只标了镜框这取决于数据集标注时给定的指导标准。如果你拿到的数据集混合了这两种风格训练时模型会非常困惑——同一个位置前一张图是前景后一张图是背景模型只能学出一个模糊的概率。第三种是遮挡边界。戴眼镜的人脸可能被头发、帽子、手遮挡遮挡部分的眼镜应该标还是不标这个没有标准答案但你得知道数据集采用的是哪种方案。快速判断方法是抽看十几张遮挡比较严重的样本。如果数据集的标签只标了可见部分那没问题如果标了不可见部分模型会学到“猜测”遮挡区域的眼镜是什么形状——这在真实场景里会产生奇怪的预测。体检完成之后把所有发现的问题记录下来按数据集的格式约定去判断哪些是真正的损坏需要修复或剔除哪些只是不同标注风格最好记录下来训练时注意。这一步很枯燥但比起训练到一个星期后才发现数据有问题前期花两三个小时做体检是最值的。4. 用UNet跑通第一个基线配置、训练与监控4.1 为什么分割基线选UNet而不直接上DeepLabV3做图像分割选第一个模型的时候我最常听到的问题是现在不是流行Transformer吗怎么还用UNet我的回答是如果你手里有一份16000张的眼镜分割数据集任务是快速验证数据质量和建立基线UNet是最稳的选择。它结构简单、显存占用小、训练收敛快一个batch size开到16甚至32都不成问题。而你用DeepLabV3或者SegFormer跑同样的数据可能得花两三倍的时间调参最后的精度提升未必明显——对一个单类分割任务来说UNet的特征提取能力已经足够。如果一定要用yolov8-seg来训练也是可行的但注意yolov8-seg输出的是多边形轮廓而不是逐像素掩码训练前需要把掩码转成多边形标注格式。这个过程会有信息损失尤其是细长的镜腿部分转成多边形后可能变得很粗糙。所以我的建议是先用UNet把数据链路走通确认数据质量没问题再考虑换更复杂的模型。这也符合做项目的基本顺序——先用最简单可靠的工具验证数据再在模型上做文章。4.2 最小可训练配置一份能直接跑的UNet训练脚本下面给出一份可以直接参照的PyTorch训练脚本骨架。数据加载部分假设你已经把标签统一成单通道索引图0背景1眼镜原图和掩码分别放在images和labels目录下。import torch import torch.nn as nn from torch.utils.data import Dataset, DataLoader from PIL import Image import numpy as np class GlassesDataset(Dataset): def __init__(self, img_dir, lbl_dir, ids_path, img_size(512, 512)): with open(ids_path, r) as f: self.ids [line.strip() for line in f if line.strip()] self.img_dir img_dir self.lbl_dir lbl_dir self.img_size img_size def __len__(self): return len(self.ids) def __getitem__(self, idx): pid self.ids[idx] img Image.open(f{self.img_dir}/{pid}.jpg).convert(RGB) lbl Image.open(f{self.lbl_dir}/{pid}.png) # 统一缩放到固定尺寸保持原图与掩码同步 img img.resize(self.img_size, Image.BILINEAR) lbl lbl.resize(self.img_size, Image.NEAREST) img_np np.array(img, dtypenp.float32) / 255.0 lbl_np np.array(lbl, dtypenp.int64) # 转成 CHW 格式并转为张量 img_t torch.from_numpy(img_np.transpose(2, 0, 1)) lbl_t torch.from_numpy(lbl_np) return img_t, lbl_t这份数据类有三个关键参数img_size是统一缩放尺寸我习惯先缩放到512x512跑通流程Image.BILINEAR和Image.NEAREST分别是原图和标签的缩放差值方式标签必须用最近邻不能上双线性——镜片边缘会插值出中间灰值直接破坏标签的语义。img_np做了除以255的归一化这是标准做法。训练循环里用交叉熵损失还是Dice损失对眼镜分割这种类别不平衡严重的任务我建议两者结合。交叉熵对边界像素敏感Dice Loss对类别不平衡不敏感按比例相加得到一个混合损失。weight参数设为{0: 1.0, 1: 5.0}给眼镜类别更高权重让模型有动力去学习前景区域。# 训练核心逻辑 import torch.optim as optim device torch.device(cuda if torch.cuda.is_available() else cpu) model UNet(in_channels3, num_classes2).to(device) optimizer optim.AdamW(model.parameters(), lr1e-4, weight_decay1e-5) ce_weights torch.tensor([1.0, 5.0]).to(device) # 背景 vs 眼镜 ce_criterion nn.CrossEntropyLoss(weightce_weights) def dice_loss(pred, target, smooth1.0): pred_probs torch.softmax(pred, dim1)[:, 1] # 取眼镜类的概率 target target.float() intersection (pred_probs * target).sum() return 1 - (2.0 * intersection smooth) / (pred_probs.sum() target.sum() smooth) for epoch in range(50): model.train() for batch_idx, (img_t, lbl_t) in enumerate(train_loader): img_t, lbl_t img_t.to(device), lbl_t.to(device) pred model(img_t) loss_ce ce_criterion(pred, lbl_t) loss_dice dice_loss(pred, lbl_t) loss loss_ce loss_dice optimizer.zero_grad() loss.backward() optimizer.step() if batch_idx % 20 0: print(fEpoch {epoch} Batch {batch_idx} | CE: {loss_ce.item():.4f} | Dice: {loss_dice.item():.4f})这段代码是完整的训练循环骨架。UNet模型定义就不展开写了用任何现成的UNet实现都可以。损失函数是交叉熵和Dice Loss的加权和两个部分等权重相加。dice_loss函数里通过softmax取出第1类眼镜的概率与目标掩码计算Dice系数。smooth参数的作用是防止分母为0这也是一个值得留意的细节——当某张图完全没有眼镜全是背景时预测和目标的交集为0没有smooth会直接算出负无穷。训练超参数方面一般遵循我自己的经验值学习率1e-4到3e-4之间用AdamW比Adam更稳weight_decay设置1e-5防止过拟合。batch size取决于显卡显存512x512输入时8GB显存开8基本到极限了。训练轮数不用太多这个数据量50轮左右就能看到明显的收敛趋势20轮之后Dice分数应该能到0.9以上如果数据没问题的话。4.3 训练时盯住哪几个指标别只看Loss很多人在训练分割模型时的习惯是紧紧盯着Loss曲线Loss跌就开心Loss涨就焦虑。但Loss绝对值本身没有太多参考意义尤其是交叉熵和Dice混合的Loss数值大小受两个loss的权重比例影响很大。真正应该关注的是验证集上的Dice分数和mIoU这两者才是衡量分割质量的真实指标。如果你是初学者建议每两个epoch在验证集上跑一次评估把预测结果可视化保存下来形成一张三联图原图、真值掩码、预测掩码。肉眼看的价值比任何指标都大——Dice分数高但边界糙得不行是常见的事因为Dice对内部区域的敏感度远高于边界区域。而可视化能立刻让你看出模型是否学到了正确的眼镜形状、边缘是否锐利、反光区域是否漏检。另外训练过程的监控还有一个容易被忽视的角落训练集和验证集的表现差距。如果训练集Dice已经到0.98验证集只有0.85说明过拟合已经开始了。这个时候先别急着加数据增强先检查验证集里有没有那种特别奇葩的样本——比如戴上墨镜的人、眼镜反光到完全看不出镜片形状的图。如果验证集里混入了这种样本模型的Dice分数会被拉低一大截但这个“低”其实是合理的。5. 眼镜分割的五个常见坑现象、原因、解决5.1 训练Loss很平滑预测结果却全是一片黑现象Loss曲线漂亮地下降但模型预测出的掩码几乎全是背景只有零星几个碎片。原因这是类别不平衡的典型症状。眼镜像素占比可能只有3%到5%模型发现预测全背景就能把交叉熵Loss压到很低。Dice Loss因为对前景和背景的比例不敏感会继续推动模型去预测前景但如果你用的损失函数不对只有交叉熵这个问题就会非常严重。解决换用加权交叉熵或者混合Dice Loss给前景类别一个5到10倍的权重。我的经验值是如果前景占比低于5%weight设成10之前不用犹豫。如果加权后还是不行检查一下是不是标签通道顺序和网络输出不对应——你可能在可视化时看的是背景通道。5.2 镜腿边缘每隔几像素就断一下现象预测掩码中镜腿部分断断续续或者细小的镜腿区域完全没有被预测出来。原因分割模型在处理细长结构时天然有劣势因为下采样会把小目标的信息稀释掉。UNet的解码器虽然能恢复部分空间信息但镜腿这种只有几个像素宽的细长区域在编码器的最后一层特征图上可能只剩不到一个像素。问题在某些时候会出现在数据增强上——如果你用了random resized crop那么镜腿可能在裁剪过程中被切掉大半。解决第一个思路是把输入分辨率从512提到768或者1024这是最直接有效的手段。第二个思路是检查你的数据增强管线不要用那种随机裁剪比例太激进的方式。第三个思路是增加一张“边界权重图”对镜腿附近的像素给予更高Loss权重——这个操作在医学图像分割里叫boundary loss用来应对细小结构非常好用。5.3 透明镜片区域和背景糊在一起现象模型对镜片内部的预测置信度低镜片区域和背景在掩码上呈现出灰色的中间状态或者一片区域内预测结果破碎。原因透明镜片的问题在于它的外观颜色与背景差异极小。如果你的数据集中混合了“标注透明镜片”和“只标镜框”两种风格模型在透明镜片区域会接收互相矛盾的监督信号产生模糊的边界。解决首先做一个风格统一步骤把所有标签按同一标准修正一遍——要么全标透明区域要么全不标。这个可以写一个半自动脚本抽几百张有透明镜片的样本用颜色相似度和边缘检测辅助修正然后人工检查。如果数据量太大不想全改可以考虑在标签上把透明镜片区域单独建一个类别0背景、1镜框、2镜片用多类分割代替二类分割让模型自己学会区分镜框和镜片。5.4 验证集Dice挺高但闭着眼睛的人脸预测效果差现象模型在绝大多数正常图片上表现不错但只要人闭着眼睛、或者戴墨镜、或者用手扶眼镜效果就明显变差。原因数据不平衡的另一个维度——你的数据集里大部分样本是“睁开眼睛正常眼镜”的状态特殊状态样本占比太小。模型见过的大部分训练样本里眼镜周围是人脸皮肤它能靠颜色纹理判断眼镜位置。闭眼状态下眼睑的纹理变化、墨镜导致的纯黑区域都会让模型进入没见过的不熟悉分布。解决先统计数据集里闭眼、墨镜、侧脸的样本有多少。如果超过10%说明数据本身覆盖够了问题出在模型训练时没有对这些样本给予足够关注可以针对性地上采样或者做CutMix这类增强。如果不到3%更实际的做法是把这些样本单独抽出来做二次微调——先在大数据集上训一个通用模型然后用特殊状态样本做几十轮微调这样不会让普通样本反过来干扰特殊状态的学习。5.5 换用yolov8-seg训练时掩码边缘全是锯齿现象用UNet切割出来的掩码边缘很干净切到yolov8-seg训练后预测结果的多边形轮廓边缘粗糙甚至有尖刺。原因yolov8-seg训练时用的标签格式是COCO风格的多边形坐标不是像素级掩码。很多转换脚本直接把掩码的每一行边缘像素转成点相邻点之间相隔太密模型学到的轮廓起伏就大。另外预测的时候后处理里有一个mask threshold参数默认值0.5如果镜片边缘的置信度普遍低于这个阈值输出的多边形就会被截断。解决在转换标签时先对掩码做一次形态学腐蚀/膨胀去掉边缘的孤立像素点再提取多边形轮廓训练时把mask threshold降到0.3到0.4并配合更大尺度的测试时增强多尺度推理然后取平均。如果还是锯齿最后用一次高斯滤波光滑多边形坐标注意平滑窗宽度设为3或者5太大容易把镜腿压没。6. 把精度再往上顶一顶三个马上能用的技巧前面几章已经走通了从数据体检到训练基线的完整流程这一章写几个我反复用到的精度提升技巧每一个都是在数据量不变、算力不变的前提下下的功夫。第一个技巧是形态学后处理。模型输出的掩码在边缘处经常有孤立的噪点、小孔洞、碎块。在推理流程的最后加一个后处理先用开运算去掉小噪点再用闭运算填补镜片内部的孔洞。cv2的morphologyEx就是干这个的kernel大小设5x5就够太大会把细镜腿也抹掉。这一步通常能让Dice分数提升0.5到1.5个百分点几乎零成本。第二个技巧是裁剪脸部区域单独分割。16000张数据集适合做一个粗到细的两阶段流程先训练一个检测模型定位人脸区域然后把人脸区域裁剪出来只在这个区域上跑分割或者训练一个专门针对眼部区域的分割模型。这样做的好处是缩小了背景占比网络的计算资源全部用在脸部和眼镜上。这种两阶段做法在某些人脸解析产品里已经是标配了。第三个技巧是评估指标的多样化。不要只看Dice和mIoU加上一个边界F1分数boundary F1它只关注掩码边界与真值边界的重合度能直接反映边缘质量。眼镜分割这个任务里精细的镜腿边界往往比面积重合更重要——毕竟产品判断“这副眼镜戴没戴正”看的就是边界位置。建议在测试集上跑一次边界指标的评估如果发现Dice高但边界F1低那就明确知道下一步该往边界方向调。拿到一份约16000张的眼镜分割数据集最忌讳两件事一是拿到就开训不做体检二是出了效果不好就归咎于数据集垃圾。按我的习惯体检花一下午时间训练出一个可用的UNet基线整体不会超过两天。后面在这个基线上做模型替换、后处理调优、类别扩展路就顺了。眼镜分割这个方向的数据积累正在越来越丰富趁早把手上的数据吃透后面再换模型、换场景都有底子。希望帮到你。本文还有配套的精品资源点击获取
