ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

瞳孔虹膜分割数据集实战:U-Net训练全流程与避坑指南

瞳孔虹膜分割数据集实战:U-Net训练全流程与避坑指南 简介一份面向图像分割学习与研究人员的瞳孔虹膜分割数据集数据来自人眼图像训练集与测试集各自独立的原图目录与掩膜目录共包含三百九十四张训练图片及对应掩膜、一百一十二张测试图片及对应掩膜图像统一为六百四十乘六百四十分辨率。掩膜为灰度图像素值零、一、二分别代表背景、瞳孔和虹膜适合直接作为三分类语义分割训练数据。压缩包共一千零一十四个文件以JPG原图、PNG掩膜为主体另含一个Python可视化脚本可随机抽取样本并生成原图、真值图、叠加蒙板三组对比图便于快速检查标注质量与模型输出。整体包体仅二十点七二兆字节轻量易用已有二百六十六人学习使用可支撑目标分割、医学影像预处理、模型入门实验等场景是一份可直接解压投入训练的高质量小规模数据集。1. 瞳孔虹膜分割数据集标记不是贴框是让模型认准每一圈边缘做疲劳驾驶检测、视线估计或者眼科参数量测的人迟早会卡在同一个问题上单纯靠人脸关键点拿不到瞳孔和虹膜的真实边界必须回到像素级分割。这份标题里说的图像分割数据集提供的就是已经划分好训练集和测试集的瞳孔、虹膜像素标注拿到手可以直接喂给 U-Net 这类医学图像分割模型。比起目标检测里常见的矩形框这个任务要求模型把瞳孔内边界、虹膜外边界都切得干净属于医学图像分割里结构最规整、但反光干扰最典型的一类。我见过不少人把分割模型当分类模型调loss 降得快但边界一塌糊涂本文就把这类数据集从检查到训练、再到避坑的完整流程捋一遍。2. 先检查再训练用脚本核验目录、配对与 mask 标注语义2.1 数据集的目录长什么样先跑一遍目录树和基本信息瞳孔虹膜分割数据集的压缩包解压后最常见的目录组织是 images 放原始眼部图像、masks 放标注结果train 和 test 两个平级目录分别存放对应的子文件夹。拿到手的第一步不是急着写模型而是先摸清命名规则和文件格式这决定了后面的 Dataset 类怎么写。import os from PIL import Image import numpy as np data_root iris_seg_dataset for split in [train, test]: img_dir os.path.join(data_root, split, images) mask_dir os.path.join(data_root, split, masks) img_names sorted(os.listdir(img_dir)) mask_names sorted(os.listdir(mask_dir)) print(split, 图像数量:, len(img_names), 标注数量:, len(mask_names)) # 打开前 3 张图和对应 mask确认尺寸与类别取值 for name in img_names[:3]: img Image.open(os.path.join(img_dir, name)) mask Image.open(os.path.join(mask_dir, name.replace(.jpg, .png))) arr np.array(mask) print(name, img.size, mask.size, mask 类别值:, np.unique(arr))这段脚本解决三个基本信息图像和 mask 数量是否对得上、两者尺寸是否一致、mask 里到底有哪几个像素值。瞳孔虹膜分割的 mask 常见有两种保存方式一种是单通道灰度图背景为 0、前景为 255另一种是单通道标签图背景为 0、瞳孔为 1、虹膜为 2。看到np.unique的输出就能立刻判断属于哪种这一步千万别跳过因为它直接决定后面损失函数的输出通道数。如果发现图像尺寸不统一比如有的是 640x480、有的是 800x600不要慌这是瞳孔数据集的常态后面预处理统一处理即可。真正需要警惕的是 mask 尺寸和图像尺寸不一致那说明标注和原图存在缩放关系读取时就得单独做映射。另外注意图像格式大多是 jpgmask 推荐是 png因为 png 是无损压缩标注边缘不会被压缩算法污染如果发现 mask 也存成 jpg后续处理时要格外小心边缘的杂色像素。2.2 瞳孔和虹膜是两类还是一类标注语义决定损失函数和评估口径很多人在跑通第一个训练循环后才意识到一个问题mask 里的类别数和模型输出通道数对不上。这里的核心分歧在于瞳孔和虹膜到底算一个前景还是两个独立类别。如果观察np.unique得到的是 0 和 255说明是二分类标注前景把瞳孔和虹膜合并成了一个整体。这时候模型输出 1 个通道sigmoid 之后和背景做二分类评估时只算一个 Dice 或 IoU。如果得到的是 0、1、2 三个值说明是语义分割标注瞳孔一类、虹膜一类模型需要输出 2 个通道背景隐含softmax 后按类别分别计算指标。# 统计每个类别的像素占比判断类别是否均衡 for split in [train, test]: mask_dir os.path.join(data_root, split, masks) total np.zeros(3, dtypenp.int64) for name in sorted(os.listdir(mask_dir))[:50]: arr np.array(Image.open(os.path.join(mask_dir, name))) for c in range(3): total[c] (arr c).sum() print(split, 类别像素占比:, total / total.sum())这段统计脚本能快速看出瞳孔和虹膜像素占比是否悬殊。实际数据里虹膜通常比瞳孔面积大不少如果训练时用统一的 Dice Loss模型会倾向于把更多像素判成虹膜来拉高得分。解决思路有两个一个是对小类别提高权重另一个是干脆把瞳孔和虹膜合并成前景做二分类先拿到整体形状再在边界细化时分开处理。具体怎么选取决于你要落地的是瞳孔分割还是虹膜分割两者的业务诉求不一样。另一个容易踩的坑是 mask 里的像素值并不是规范的 0、1、2而是 0、128、255 之类的灰度索引。如果按 255 做归一化再送进网络类别之间会出现竞争关系模型不知道该往哪个值收敛。我一般会在加载阶段就把任意灰度值映射回 0、1、2保证标签的语义是离散类别而不是灰度强度。2.3 用脚本核验图片与 mask 的配对同尺寸、同数量、通道一致性训练集和测试集的划分本身就值得验证。有些数据集在划分时把同一个受试者的多张眼部图像同时放进了训练集和测试集导致测试分数虚高这个属于数据泄漏暂时不做深入讨论但配对的物理核验是基础。from PIL import Image import numpy as np import os def verify_pair(data_root, split): img_dir os.path.join(data_root, split, images) mask_dir os.path.join(data_root, split, masks) img_names sorted(os.listdir(img_dir)) mask_names sorted(os.listdir(mask_dir)) assert len(img_names) len(mask_names), f{split}: 图片与 mask 数量不一致 for im, ma in zip(img_names, mask_names): base_im im.rsplit(., 1)[0] base_ma ma.rsplit(., 1)[0] assert base_im base_ma, f文件名不配对: {im} vs {ma} img np.array(Image.open(os.path.join(img_dir, im))) mask np.array(Image.open(os.path.join(mask_dir, ma))) assert img.shape[:2] mask.shape[:2], f尺寸不一致: {im} assert len(mask.shape) 2, fmask 不是单通道: {ma} print(f{split}: 配对检查通过共 {len(img_names)} 对) verify_pair(data_root, train) verify_pair(data_root, test)这个脚本的断言逻辑很直白文件名前缀一致、图像和 mask 尺寸一致、mask 是单通道。如果 mask 是三通道的 RGB 图说明标注被保存成了彩色图len(mask.shape) 2这一条会直接报错这反而是好事让你在训练前就发现格式问题。如果 mask 是三通道但三个通道的像素值完全相同也可以只取第一个通道继续用但我不推荐这么做因为彩色 PNG 转灰度时可能出现标注边缘的锯齿假色最好是找到原始标注文件或者重新导出。配对检查通过后顺手把每个类的连通域数量也统计一下。瞳孔和虹膜在单张图里理应各是一个连通域如果发现动辄三五个连通域说明标注里有多余的噪点或者反光区域被错误标记成了前景这种脏数据会在训练时产生大量的假阳性样本。3. 用 U-Net 在训练集上跑通最小方案数据加载、增强与损失函数3.1 预处理等比缩放留边还是直接 resize以及眼睛 ROI 怎么选瞳孔虹膜图像来自不同采集设备分辨率差异很大训练前需要统一尺寸。直接resize到 256x256 是最省事的做法但如果原图是 640x480 这种横向图直接 resize 会让瞳孔变成椭圆破坏瞳孔近似圆形的先验信息模型学到的形状知识会被带偏。常见做法是等比缩放后补边到正方形。import cv2 import numpy as np def resize_with_pad(image, mask, target_size256): h, w image.shape[:2] scale target_size / max(h, w) new_h, new_w int(h * scale), int(w * scale) image cv2.resize(image, (new_w, new_h), interpolationcv2.INTER_LINEAR) mask cv2.resize(mask, (new_w, new_h), interpolationcv2.INTER_NEAREST) pad_h target_size - new_h pad_w target_size - new_w top, bottom pad_h // 2, pad_h - pad_h // 2 left, right pad_w // 2, pad_w - pad_w // 2 image cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT, value0) mask cv2.copyMakeBorder(mask, top, bottom, left, right, cv2.BORDER_CONSTANT, value0) return image, mask注意一个关键细节图像 resize 用线性插值没问题mask resize 必须用INTER_NEAREST最近邻插值否则类别边界会出现中间灰度值。补边时图像的 padding 区域填 0黑色对应位置的 mask 填 0背景这符合瞳孔图像的实际分布因为眼睛周围本来就是背景。另一种更激进的做法是先做人眼 ROI 裁剪再用裁剪后的区域做分割。很多瞳孔数据集在采集时就对齐到了眼睛区域但如果你拿到的图像包含眉毛、额头甚至半张脸直接全图训练会让模型把大量参数浪费在无关背景上。我一般会先跑一个人眼检测器把 ROI 扩一圈后裁剪下来再做 padding训练速度更快精度通常也更高。不过代价是推理阶段也必须走同一套检测流程工程复杂度上升小规模项目划不来。3.2 Dataset 类实现与同步数据增强PyTorch 的 Dataset 类负责把预处理后的图像和 mask 打包成张量。增强方面用 albumentations 库可以保证图像和 mask 同步变换省去手动同步的麻烦。这里给出一份能直接跑的完整实现。import glob import cv2 import numpy as np import albumentations as A import torch from torch.utils.data import Dataset class PupilIrisDataset(Dataset): def __init__(self, img_dir, mask_dir, size256, augmentFalse): self.img_paths sorted(glob.glob(img_dir /*)) self.mask_paths sorted(glob.glob(mask_dir /*)) self.size size self.augment augment self.transform A.Compose([ A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(brightness_limit0.2, contrast_limit0.2, p0.3), A.GaussNoise(var_limit(10.0, 30.0), p0.2), A.ShiftScaleRotate(shift_limit0.05, scale_limit0.1, rotate_limit15, border_modecv2.BORDER_CONSTANT, p0.5), ]) def __len__(self): return len(self.img_paths) def __getitem__(self, idx): img cv2.imread(self.img_paths[idx], cv2.IMREAD_COLOR) mask cv2.imread(self.mask_paths[idx], cv2.IMREAD_GRAYSCALE) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img, mask resize_with_pad(img, mask, self.size) if self.augment: aug self.transform(imageimg, maskmask) img, mask aug[image], aug[mask] img img.astype(np.float32) / 255.0 mask mask.astype(np.int64) mask np.clip(mask, 0, 2) img torch.from_numpy(img).permute(2, 0, 1).float() mask torch.from_numpy(mask).unsqueeze(0).long() return img, mask几个参数值得说明。RandomBrightnessContrast设到 0.2 而不是更大是因为瞳孔图像的反光点已经够讨厌了过度调亮度会让反光区域和背景完全混在一起。var_limit(10.0, 30.0)的噪声强度是经验值太强会把虹膜纹理细节洗掉太弱则起不到正则作用。ShiftScaleRotate里的border_modecv2.BORDER_CONSTANT保证旋转时填充的是黑色背景而不是像素外插这一点对瞳孔这种本身就有大片背景的数据集非常关键外插会在边缘产生不可预期的伪影。mask 在返回前被转成了int64并限制在 0 到 2 之间这是为了兼容两类标注。如果你确认数据集是二分类把np.clip改成(mask 0).astype(np.int64)即可输出通道同步改为 1。3.3 Dice Loss 与 BCE 的组合以及为什么不能只用 CrossEntropy瞳孔虹膜分割里前景占了图像的一大部分但瞳孔本身面积很小如果直接用 CrossEntropy背景类损失会盖过瞳孔的梯度模型学到最后把整张图全判成背景也能得到低损失。U-Net 训练自己的数据集时最常见的损失是 Dice Loss 和 BCE 的组合。import torch import torch.nn as nn import torch.nn.functional as F class DiceBCELoss(nn.Module): def __init__(self, dice_weight0.5, bce_weight0.5): super().__init__() self.dice_weight dice_weight self.bce_weight bce_weight self.bce nn.BCEWithLogitsLoss() def forward(self, pred, target): # pred: [B, 1, H, W], target: [B, 1, H, W] 取值 0/1float bce self.bce(pred, target) pred_sig torch.sigmoid(pred) pred_flat pred_sig.contiguous().view(pred.size(0), -1) target_flat target.contiguous().view(pred.size(0), -1) intersection (pred_flat * target_flat).sum(dim1) dice 1 - (2.0 * intersection 1.0) / (pred_flat.sum(dim1) target_flat.sum(dim1) 1.0) dice dice.mean() return self.dice_weight * dice self.bce_weight * bceDiceBCELoss加了BCEWithLogitsLoss而不是手动 sigmoid 后再算 BCE是为了利用 PyTorch 自带的数值稳定计算避免 logits 极大或极小时出现 NaN。Dice 项里的1.0是平滑系数防止在两个 mask 都完全为空时出现除零。实践里还有一个更隐蔽的问题如果目标 mask 全部是 0空图Dice 会接近 0 而 BCE 会正常惩罚所以这个组合本身就能应对空图不需要额外过滤。如果你的数据集是瞳孔和虹膜分两类就不能用这个二分类 Loss而是把输出通道改成 2用 CrossEntropy 配合 per-class Dice 权重。两种做法的精度差异并不大但二分类在工程上更简单推理时阈值处理也更直接个人建议第一版先跑二分类。4. 训练阶段的超参与调参方向学习率、Batch Size、检查点与测试集评估4.1 一套能稳定收敛的默认超参第一次跑瞳孔虹膜分割不需要太花哨的训练策略一套稳妥的默认参数能帮你快速判断数据集质量和模型结构是否合适。我常用的组合如下表。超参取值说明输入尺寸256x256兼顾精度和显存瞳距较小的大图可调到 384编码器ResNet34 预训练权重比从零训练收敛快得多泛化更好损失函数Dice BCE权重各 0.5应对前景背景不平衡优化器AdamW权重衰减设 1e-4避免过拟合初始学习率1e-4预训练 backbone 用 1e-4随机初始化头可以用 1e-3学习率调度CosineAnnealingLR余弦退火T_max 设为总 epoch 数Batch Size16256 输入下单卡 12G 显存可以放下太小则 BN 不稳定Epochs80配合早停一般在 40-60 轮达到收敛早停 Patience15以验证集 Dice 为准连续 15 轮不升就停这里最值得解释的是学习率的选择。如果你的 U-Net 编码器加载了 ImageNet 预训练权重整体学习率用 1e-4 而不是 1e-3原因在于预训练特征在大规模数据上已经足够好不需要大步伐更新大学习率反而会破坏已学到的底层边缘纹理特征。如果你的数据是小规模瞳孔数据集无预训练从头跑那学习率从 1e-3 开始反而更容易跳出局部最优。Batch Size 16 配合 AdamW 在大部分场景下都成立。如果显存有限降到 8梯度噪声变大Dice 曲线会明显抖动这时候可以把学习率同步降到 5e-5经验上能缓解抖动。4.2 训练循环、早停与检查点保存训练循环的骨架不复杂但保存检查点的策略值得说清楚。瞳孔分割的验证集 Dice 在 0.95 以上后每轮提升只有千分之几如果不按验证指标保存最后一轮模型很可能不是最优的。import torch import torch.nn as nn from torch.utils.data import DataLoader def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss 0.0 for imgs, masks in loader: imgs imgs.to(device) masks masks.to(device).float() preds model(imgs) loss criterion(preds, masks) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() return total_loss / len(loader) def evaluate(model, loader, device): model.eval() dice_total 0.0 n 0 with torch.no_grad(): for imgs, masks in loader: imgs imgs.to(device) masks masks.to(device).float() preds torch.sigmoid(model(imgs)) preds (preds 0.5).float() intersection (preds * masks).sum(dim(1, 2, 3)) union (preds masks).gt(0).sum(dim(1, 2, 3)) dice (2.0 * intersection) / (preds.sum(dim(1, 2, 3)) masks.sum(dim(1, 2, 3)) 1e-6) dice_total dice.mean().item() n 1 return dice_total / n best_dice 0.0 for epoch in range(80): train_loss train_one_epoch(model, train_loader, optimizer, criterion, device) val_dice evaluate(model, val_loader, device) scheduler.step() if val_dice best_dice: best_dice val_dice torch.save(model.state_dict(), fbest_model_dice_{val_dice:.4f}.pth) print(fepoch {epoch}: val_dice {val_dice:.4f}, 已保存检查点)代码里的gt(0)在计算 union 时用了比较技巧preds masks的每个像素如果大于 0 说明至少有一个为前景这样可以避免简单的union preds masks把 2 也算进去导致分母虚高。Dice 计算里加了 1e-6 的小平滑防止分母为零但注意这和损失函数里的平滑系数不是一回事评估指标里的平滑只是数值保护不应该影响指标本身。早停逻辑我通常放在 epoch 循环外面记录连续未提升的轮数达到 patience 就 break。这里有一个小坑验证集本身是从训练集切出来的还是单独的标题说数据集包含训练集和测试集但一般还会在训练集里再切出一小块做验证。如果直接用测试集作为早停依据等于拿最终考卷反复做题测试分数会有虚高风险。4.3 测试集评估预测阈值、Dice 与 IoU 的计算口径测试集的评估口径比训练时的验证要严格因为你只有一次机会必须在推理阈值上做足功夫。默认用 0.5 阈值展开预测没什么问题但瞳孔反光点会让概率图出现双峰分布0.5 阈值有时会把反光区域的低置信像素全丢掉。def evaluate_test(model, test_loader, device, threshold0.5): model.eval() dice_list, iou_list [], [] with torch.no_grad(): for imgs, masks in test_loader: imgs imgs.to(device) masks masks.to(device) preds torch.sigmoid(model(imgs)) preds (preds threshold).float() inter (preds * masks).sum(dim(1, 2, 3)) union (preds masks).gt(0).sum(dim(1, 2, 3)) dice (2.0 * inter) / (preds.sum(dim(1, 2, 3)) masks.sum(dim(1, 2, 3)) 1e-6) iou inter / (union 1e-6) dice_list.append(dice.cpu().numpy()) iou_list.append(iou.cpu().numpy()) dice_all np.concatenate(dice_list) iou_all np.concatenate(iou_list) print(fDice: {dice_all.mean():.4f} ± {dice_all.std():.4f}) print(fIoU: {iou_all.mean():.4f} ± {iou_all.std():.4f})把dice和iou分别按样本维度拼接后求均值和标准差比只汇报平均值更有参考价值。如果标准差超过 0.05说明模型对部分样本表现很差你要去翻这些低分样本的图片很可能就是睫毛遮挡严重或者强反光的图。测试集分数的上限基本由数据本身的标注一致性决定。瞳孔边界在普通图像上其实很模糊标注人员对边缘位置的判断差异就能造成 0.02 的 Dice 波动所以不要盲目追求 0.99要先确认标注质量再谈模型优化。5. 瞳孔虹膜分割的五个高频坑从标注语义到推理部署5.1 瞳孔内部的高光反光点被打成背景现象训练时 Dice 很高但推理结果里瞳孔中心出现黑色小洞面积跟原图里的反光点高度重合。原因角膜反光区域在灰度图上和背景一样亮模型没有足够的信息判断这里是瞳孔而不是背景。解决训练时将反光点当作一个正常类别处理不单独挖掉同时增强时保留GaussNoise增加对干扰的鲁棒性。推理后可以用形态学闭运算把小于一定面积的黑洞填补掉但更彻底的做法是增加带反光样本的权重让模型学会“边界内部全是瞳孔”。5.2 睫毛与眼皮遮挡让虹膜边缘出现锯齿和漏检现象测试集里侧面人脸或者闭眼瞬间的图像虹膜边缘分割出现明显锯齿甚至直接把遮挡区域判成背景。原因遮挡区域在标注时本来就带有主观性不同标注者对“看不见的虹膜该不该标”有分歧模型在模糊边界上无所适从。解决按训练集边缘像素的标注置信度分布做权重图对边缘附近的 loss 降权或者干脆先不做虹膜外边界细化只保瞳孔分割把遮挡问题留给上层业务判断。5.3 mask 读进来是三通道loss 维度不匹配现象train_one_epoch报维度错误或者 loss 一直是常数不下降。原因数据集里部分 mask 是 RGB 假彩色 PNG三通道完全一样代码里按IMREAD_GRAYSCALE读出来依然有形状问题。解决在 Dataset 里统一加一步cv2.cvtColor(mask, cv2.COLOR_RGB2GRAY)之后再做二值化并且在配对核验阶段就检查len(mask.shape) 2从源头拦截。5.4 训练集和测试集设备或光照分布不一致线上 Dice 接近崩溃现象训练集 Dice 0.96测试集只有 0.88差距一直缩不小。原因采集批次不同测试集里换了一种光源或摄像头瞳孔图像的整体对比度、色温都有偏移。解决训练时除了RandomBrightnessContrast再叠加HueSaturationValue或者灰度归一化把图像统一到标准范围如果测试集分布已知可以在预处理阶段做一个基于均值和标准差的归一化让两域靠近再进模型。5.5 分两类标注时按二分类默认阈值推理瞳孔与虹膜边界被吞掉现象标好 0、1、2 三类的数据集模型输出两层 softmax但你只按背景和前景取 argmax瞳孔和虹膜之间那条边界完全没体现。原因推理代码写成了二分类的阈值逻辑根本没有对不同类别做通道分离。解决确认标注语义后输出通道设置成 2推理时torch.argmax(preds, dim1)同时拿到瞳孔和虹膜掩膜后续再按需求合并或者单独取用。6. 测试集之外的验证技巧可视化叠加、概率图直方图与形态学后处理测试集指标达标并不代表模型可以直接部署还得用肉眼过一遍预测结果。最常见做法是原图、GT、预测三图并排再叠加一层半透明掩膜快速定位系统性的过分割和欠分割位置。import matplotlib.pyplot as plt import numpy as np def visualize_prediction(img, mask, pred, idx): img img.permute(1, 2, 0).cpu().numpy() mask mask.squeeze().cpu().numpy() pred pred.squeeze().cpu().numpy() overlay np.zeros_like(img) overlay[..., 1] pred # 绿色通道显示预测前景 plt.figure(figsize(12, 4)) plt.subplot(1, 3, 1); plt.imshow(img); plt.title(Input) plt.subplot(1, 3, 2); plt.imshow(mask, cmapgray); plt.title(GT) plt.subplot(1, 3, 3); plt.imshow(img); plt.imshow(overlay, alpha0.4); plt.title(Prediction) plt.savefig(fsample_{idx}.png, dpi150, bbox_inchestight)叠加图里绿色高亮区域如果明显超出 GT 的边界一圈说明模型对边缘过于激进多半是训练时边缘权重偏高如果瞳孔内部出现空洞回到反光问题。除了看图概率图直方图也是排查利器——导出预测 sigmoid 值的分布如果直方图呈 U 型且中间低洼说明模型置信度充足阈值 0.5 合适如果大量像素堆在 0.4 到 0.6 之间说明模型对边界区域犹豫这时候可以尝试把阈值调到 0.45 或者 0.55 看测试集指标变化这是最廉价的后处理手段。形态学闭运算适合作为最后一层保险。预测完成后做一次 3x3 的闭运算能闭掉瞳孔内部的反光空洞再用 3x3 开运算去掉虹膜边缘的孤立噪点。后处理参数不要贪大结构元素超过 5x5 会明显改变边界位置反而拉低 Dice。另一条好用的经验是保留 0.5 阈值的原始概率图出问题时先用阈值扫描定位是模型问题还是后处理问题不要一上来就盲调形态学参数。有一回我在测试集上跑了 0.96 的 Dice认为模型已经可靠结果抽了十张侧面图让项目经理看了一眼他发现虹膜外圈在强光下整个偏了半圈。指标不骗人但它只能反映标注口径内的表现超出分布的数据还是要靠可视化兜底。瞳孔虹膜分割这个方向入门不难把数据检查、损失函数、阈值和后处理这套基本功打扎实做出来的模型在业务上才扛得住真场景希望这篇能帮到你。本文还有配套的精品资源点击获取
返回列表