资讯动态

瞳孔虹膜分割数据集工程实践:基于UNet的训练与验证要点

发布时间:2026/9/12 22:13:29 来源:尧图企业网站定制
简介面向计算机视觉与医学图像分析的学习者及研究人员提供一套完整的瞳孔与虹膜分割数据集。图像为640×640分辨率的人眼照片mask为0、1、2三类灰度标注图对应背景、瞳孔与虹膜区域可直接用于语义分割模型的训练与测试。数据按训练集与测试集划分训练集含394张原图及394张对应mask测试集含112张原图及112张mask总计506组图像与标注另外附带一个Python可视化脚本可随机抽取样本展示原图、GT标签及叠加蒙版效果。压缩包为7z格式共1014个文件以jpg原图、png标注图和py脚本为主整体大小约20.72MB目录结构清晰、即下即用。目前已有266人学习下载适合刚接触图像分割或需要开展瞳孔、虹膜区域提取实验的开发者快速上手。1. 数据集的边界就是模型的边界瞳孔虹膜分割和常见的语义分割有个本质区别它不是多类别问题而是像素级的二分类问题——每个像素要么属于虹膜要么不属于。但这个“简单”设定在真实数据上并不好做虹膜内侧和瞳孔之间没有清晰边缘外侧又被眼睑、睫毛和反射光干扰如果数据集里对“虹膜和阴影的边界在哪”没有统一标注口径训练出来的模型在验证集上再漂亮换一批摄像头拍的照片就崩。这类数据集的典型使用场景是虹膜识别系统的前端预处理、眼动追踪的瞳孔定位以及医疗辅助诊断里的瞳孔反应分析。模型通常不需要理解“这是一个圆环”而是要在不规则的遮挡下把虹膜区域完整抠出来所以数据集的质量直接决定下游算法的上限。本文会用一套可行的工程方案把瞳孔虹膜分割数据集的目录组织、训练集与测试集的划分依据、UNet 加载方式、增强策略和验证指标串起来讲清楚适合正在手动整理数据集或者刚接触分割任务的工程师参考。2. 训练集与测试集的职责划分从目录结构到标签格式2.1 先定好目录约定再谈模型拿到一个瞳孔虹膜分割数据集时第一件事不是看图片而是确认目录结构是否支持“按路径直接加载”。常见做法是把原始图像和标签图分目录存放命名规则保持一致这样写 DataLoader 时只需要替换前缀路径不需要做路径映射。一个可靠的布局长这样iris_seg_dataset/ ├── images/ │ ├── train/ │ │ ├── subject_001_left_001.png │ │ ├── subject_001_left_002.png │ │ └── ... │ └── test/ │ ├── subject_010_left_001.png │ └── ... ├── masks/ │ ├── train/ │ │ ├── subject_001_left_001.png │ │ └── ... │ └── test/ │ ├── subject_010_left_001.png │ └── ... ├── train.txt ├── test.txt └── dataset_info.jsontrain.txt 和 test.txt 存的是不含扩展名的文件名列表每行一个。这样做的价值在于训练和测试共享同一套加载逻辑更换数据集划分时不需要挪动任何图片文件。dataset_info.json 记录图像分辨率、标注格式、类别名称和划分比例方便后续复现实验条件避免自己都忘了当初是拿哪些图训出来的。文件命名里带 subject ID、左右眼标记和序号这是瞳孔虹膜数据集里很值得保留的习惯。测试集如果和训练集来自不同受试者模型学到的纹理特征泛化性会更有说服力如果来自同一批受试者则偏向验证设备一致性。两者都有工程意义但划分时要知道自己选的是哪一种。2.2 划分比例不是固定公式取决于数据来源训练集与测试集的比例在公开数据集中常见 8:2 或 7:3但这个比例背后有前提数据来自多个受试者且每个受试者的多张图片不能同时落在训练集和测试集里。瞳孔虹膜图像有一个容易忽视的点同一只眼睛的相邻帧几乎一模一样模型可能靠记忆而不是泛化。按受试者划分的代码逻辑通常长这样import json import random from pathlib import Path random.seed(42) image_root Path(images/train) mask_root Path(masks/train) subject_ids sorted({p.name.split(_)[0] for p in image_root.glob(*.png)}) random.shuffle(subject_ids) split_ratio 0.8 n_train_subjects int(len(subject_ids) * split_ratio) train_subjects set(subject_ids[:n_train_subjects]) test_subjects set(subject_ids[n_train_subjects:]) train_list, test_list [], [] for mask_path in mask_root.glob(*.png): subject mask_path.name.split(_)[0] if subject in train_subjects: train_list.append(mask_path.stem) else: test_list.append(mask_path.stem) with open(train.txt, w) as f: f.write(\n.join(train_list)) with open(test.txt, w) as f: f.write(\n.join(test_list)) print(ftrain: {len(train_list)}, test: {len(test_list)})这段代码按文件名首段提取受试者 ID先把受试者乱序再按比例切分最后生成两个 txt 文件。划分的最小单位是受试者而不是单张图片这个细节决定了测试集还能不能真实反映模型的泛化性能。靠时间顺序或摄像头编号划分同样合理关键在于要在 dataset_info.json 里写明规则否者三个月后回来复现实验时已经想不起当时为什么测试集的某张图和训练集一个受试者。2.3 标签图的格式和类别值约定瞳孔虹膜分割的标签图通常是单通道 PNG像素值只有 0 和 255对应背景和虹膜区域。少部分数据集用 0 和 1工程上建议统一转成 0 和 255 存储因为多数标注工具导出的默认就是 255而训练时在损失函数里再归一化到 0 和 1这样不容易混淆。打开标签图确认格式的步骤值得养成习惯python -c from PIL import Image import numpy as np m np.array(Image.open(masks/train/subject_001_left_001.png)) print(m.shape, m.dtype, np.unique(m)) 输出应该类似(480, 640) uint8 [0 255]如果是(480, 640, 3)或出现其他数值说明标签图带有彩色通道或者包含灰度中间值需要先转换再训练。多类别分割任务里标签是 0、1、2…但瞳孔虹膜分割只关心单通道掩码。3. 训练集加载用 UNet 跑通第一个训练循环3.1 数据集类的正确写法PyTorch 的 Dataset 类需要同时处理图像和掩码关键点在于图像转成 RGB、掩码保持单通道、两者做完全一致的随机变换。瞳孔虹膜分割里掩码的形态直接依赖图像内容例如亮度不均导致瞳孔区域过亮边界判断会跟着变因此增强操作列表要两路同步执行。基础的数据集类如下import torch from torch.utils.data import Dataset from PIL import Image import numpy as np class IrisSegDataset(Dataset): def __init__(self, data_list, image_dir, mask_dir, transformNone): with open(data_list) as f: self.names [line.strip() for line in f if line.strip()] self.image_dir image_dir self.mask_dir mask_dir self.transform transform def __len__(self): return len(self.names) def __getitem__(self, idx): name self.names[idx] image Image.open(f{self.image_dir}/{name}.png).convert(RGB) mask Image.open(f{self.mask_dir}/{name}.png).convert(L) if self.transform: augmented self.transform(imagenp.array(image), masknp.array(mask)) image augmented[image] mask augmented[mask] mask torch.from_numpy(mask).float().unsqueeze(0) / 255.0 image torch.from_numpy(np.transpose(image, (2, 0, 1))).float() / 255.0 return image, mask掩码在返回前除以 255把 0/255 归一化到 0/1后续 loss 里不需要再做额外处理。图像默认值域在 0 到 1 之间配合归一化层时要注意防止均值不为零导致灰度偏移。常见错误是把 mask 也convert(RGB)这样会得到三通道一样的标签图训练时如果直接用单通道读取就会尺寸不匹配报错信息还不容易定位到这一行。3.2 UNet 加载与训练参数的选择瞳孔虹膜分割是标准医学图像分割任务的小型变体Encoder-Decoder 结构的 UNet 是可靠的基线。可以直接用现成实现不需要自己从头搭卷积块常见的做法是导入segmentation_models_pytorchimport segmentation_models_pytorch as smp model smp.Unet( encoder_nameresnet34, encoder_weightsimagenet, in_channels3, classes1, activationNone )这里classes1配合activationNone卷积分支直接输出 logits后面用 BCEWithLogitsLoss。瞳孔边缘相对细小ResNet34 作为编码器在下采样时会丢失一部分边缘细节但配上 UNet 的跳连接后效果基本够用如果想在边缘上更强一些可以把 encoder 换成timm-efficientnet-b0代价是显存占用变高。建议的最小组训练配置如下参数推荐值理由输入尺寸256×256 或 320×320瞳孔区域占比小太小丢边界太大训练慢Batch Size8单卡 2080Ti 级别与 256 分辨率配套的保守取值学习率1e-4再高容易在分割任务上震荡LossBCEWithLogitsLoss二分类分割最稳的选择优化器AdamW配合 weight_decay 能减少训练集过拟合3.3 训练循环里的验证指标计算训练时每轮在测试集上算 IoU不只看 loss。因为 BCE 在类别不平衡时数值会很好但视觉上边界可能差很多需要靠指标强制检查。def compute_iou(pred_mask, true_mask): pred (torch.sigmoid(pred_mask) 0.5).bool() true true_mask.bool() intersection (pred true).sum().float() union (pred | true).sum().float() return (intersection 1e-6) / (union 1e-6)训练循环每轮结束遍历测试集分别计算每张图的 IoU然后取平均值。IoU 是瞳孔虹膜分割任务被引用得最频繁的评估指标比像素准确率可信因为背景像素占比高全预测为背景也能拿到很高的准确率但 IoU 就不会被这种假象骗过。4. 数据增强与训练集测试集的一致性控制4.1 瞳孔虹膜适用的增强操作瞳孔虹膜图像有独特属性眼球旋转、光照反射点、眼睑遮挡、镜头距离变化。基于这些特性增强策略需要围绕几何变换和光照变化来设计而通用的随机裁剪和缩放稍有不慎就会把虹膜移出画面中心。一个经过验证的增强组合如下import albumentations as A from albumentations.pytorch import ToTensorV2 train_transform A.Compose([ A.Resize(256, 256), A.Rotate(limit20, p0.7, border_mode0), A.RandomBrightnessContrast(brightness_limit0.15, contrast_limit0.15, p0.5), A.HorizontalFlip(p0.5), A.GaussNoise(var_limit(10.0, 30.0), p0.2), ]) test_transform A.Compose([ A.Resize(256, 256), ])参数说明Rotate(limit20)里 20 度对瞳孔不会造成方向性破坏眼睑遮挡角度也因此更多样border_mode0表示旋转出界区域填充黑色配合瞳孔图像黑色背景的自然属性不会让模型学到错误边缘GaussNoise模拟低照度相机传感器噪声对夜间或红外场景下采集的瞳孔图像有提升Resize在增强链的第一位因为后续几何变换都需要在统一尺度下做避免分辨率不一致带来额外干扰4.2 训练集增强与测试集预处理的严格区分瞳孔虹膜分割数据集的“分裂”通常出现在这里有人为了让训练集更丰富在测试集上同样用了随机亮度或噪声导致测试指标虚高或失真。测试集的作用是反映真实场景不能用随机增强来人为改变分布。工程惯例是训练集用完整的随机变换测试集只做确定性的 Resize 和归一化不包含任何随机性。上面的代码中测试集变换不包含 Rotate 和 Brightness 是有意为之。测试集做了 Resize 后输出的预测图如果要与原尺寸进行逐像素比较from torch.nn.functional import interpolate pred interpolate(pred_logit, size(orig_h, orig_w), modebilinear, align_cornersFalse)这行代码在保存结果或做像素级分析前把预测图恢复到原始尺寸。注意这里不能用nearest插值因为分割输出要经过 sigmoid 后再取阈值nearest会破坏边缘的连续性。5. 分割质量验证mIoU 计算、边界误差与易错点排查5.1 手动检查预测掩码的三种方法真正验证一个瞳孔虹膜分割模型是否可用不是看一张测试集图的效果而是要按受试者或按摄像头来源分组去看结果。把预测结果和原图重叠保存是成本最低的检查方式def save_overlay(image_path, pred_mask, save_path): image Image.open(image_path).convert(RGB) mask (pred_mask * 255).astype(np.uint8) mask Image.fromarray(mask).resize(image.size, Image.NEAREST) overlay Image.blend(image.convert(RGBA), mask.convert(RGB).convert(RGBA), 0.4) overlay.save(save_path)保存出来的图能直观定位三类问题虹膜区域偏小边缘收缩、反射点被包进虹膜区域阈值偏低、眼睑边缘被错误圈入标注口径不一致。这些问题在数值上不会显著拉低 IoU但在下游算法里会产生连锁偏差。5.2 提升分割精度的三个检查方向第一个方向是检查训练集照片里虹膜边界的标注是否统一。瞳孔和虹膜的边界在灰度图上是一条渐变带不同标注员会把边界画在渐变带的内侧或外侧这会导致模型学到两套标准。如果发现训练集 loss 降不下去建议直接抽看 20 张训练图像的 ground truth 边界过渡带而不去调模型结构。第二个方向是观察测试集图像是否包含其他设备拍摄的红外图片。红外条件下瞳孔会呈现亮瞳或暗瞳差异颜色通道的分布和可见光训练集差别很大。如果测试集混有这种图像ResNet34 编码器提取的 RGB 特征会有偏移此时在训练集里加入灰度化增强往往比更换更深的网络更有效。第三个方向是分类阈值不要默认固定。输出 logits 经过 sigmoid 后默认取 0.5 做阈值但瞳孔虹膜分割里边界像素的激活值往往在 0.3 到 0.7 之间波动。在验证集上遍历阈值 0.3 到 0.7、步长 0.05选最优阈值再报指标这个操作只需要一次best_threshold, best_miou 0.5, 0.0 for thr in np.arange(0.3, 0.75, 0.05): miou evaluate_with_threshold(model, val_loader, thr) if miou best_miou: best_miou, best_threshold miou, thr这一步往往能把 mIoU 提升 1 到 2 个百分点的量级尤其在存在大量弱边缘样本的数据集上效果更明显。thr 的搜索结果单独记在日志里并注明是在哪个测试集上算出来的避免测试集复用导致无效的过拟合验证。瞳孔虹膜分割的工程落地最终比拼的不是网络的深度而是对训练集与测试集之间分布差异的控制和检查。数据集的每张图、每个标注像素和每一条划分规则都比模型结构本身更能决定最终效果的上限。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价