ARTICLE DETAIL

资讯详情

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

mmsegmentation自定义数据集训练全流程指南

mmsegmentation自定义数据集训练全流程指南 1. 数据集准备从原始图像到mmsegmentation能吃的格式1.1 为什么说数据集格式是mmsegmentation的第一道坎很多人拿到mmsegmentation第一件事就是clone仓库、装环境、跑demo然后卡在“我想训练自己的数据”这一步。这是正常的因为mmsegmentation不像detection系列那么好糊弄——分割任务的数据集格式严格得多一个类是文件夹也好一张图是一个png也好都需要按它的约定来组织。社区里有个很经典的段子在mmsegmentation里跑通cityscapes模型的成功率是90%但用自己数据集跑通训练流程的成功率不到50%剩下的50%基本都死在数据集格式上。这里先说清楚一个基本概念mmsegmentation底层依赖mmcv和mmengine数据加载走的是CustomDataset这个基类。它默认要求你的数据是“一图一掩膜”的结构原始图像是RGB三通道的jpg/png对应的标签是单通道的png8位或16位整数像素值就是类别ID从0开始计数0通常作为背景类。这个设计沿用了PASCAL VOC的经验但比VOC更严格——VOC的label是调色板PNGmmsegmentation直接读取像素值。所以拿到你自己的项目时第一步不是写网络结构而是回答三个问题我的图像是什么格式我的标注是什么格式我的类别ID怎么映射这三个问题没想明白前改再多config都是空转。1.2 VOC格式和MSTAR格式的取舍我推荐哪一种mmsegmentation的官方文档里提到了两种常见组织方式一种叫VOC格式一种叫MSTAR格式其实是很多遥感、医疗项目里默认的简单格式。我实际用下来强烈建议新手直接走MSTAR风格也就是纯文件夹结构data/ ├── imgs/ │ ├── train/ │ │ ├── 0001.jpg │ │ ├── 0002.jpg │ ├── val/ │ │ ├── 0001.jpg ├── masks/ │ ├── train/ │ │ ├── 0001.png │ │ ├── 0002.png │ ├── val/ │ │ ├── 0001.png它的好处是目录结构一目了然不需要写额外的splits文件只需要在config里指定img_dirimgs/train、ann_dirmasks/train就能跑。相比之下VOC格式还要维护train.txt、val.txt这样的索引文件除非你有大量历史数据已经按VOC组织好了否则没必要自找麻烦。但要注意一个细节MSTAR风格下img和mask的文件名必须一一对应也就是说imgs/train/0001.jpg的标签必须是masks/train/0001.png多一张、少一张、后缀不一致训练时都会报错或者产生标签错位。这种错位很隐蔽因为mmengine的数据管道不会校验图像和标签是不是同一个场景只会检查张量形状。我以前就吃过亏一张翻车的mask混在数据集里模型分割精度直接掉了两个点还查了半天。1.3 数据标注阶段的三个实用建议如果你是从头标注一个新数据集工具上首推LabelMe或者X-AnyLabeling。LabelMe是老牌选择导出的是JSON多边形X-AnyLabeling支持SAM辅助标注速度会快很多尤其做遥感或者医疗图像时SAM的边界质量比纯手动高不少。但不管用什么工具我有三个建议第一先标10张跑通整个训练流程再回来批量标注。很多人一次性把几百张全标完结果发现格式转换有问题、类别ID映射错了全部返工心态直接爆炸。先小批量验证格式是控制风险最好的方式。第二标注的类别定义要和控制文件一致。比如你定义了“道路、建筑、车辆”三个前景类那么像素值1、2、3分别对应这三个类背景是0不要用255做背景。mmsegmentation的ignore_index默认是255可以用于标注那些“我不想参与损失计算”的区域但不要和背景混淆。第三注意类别的边界问题。分割任务的本质是像素级分类如果在标注时两个相邻物体的边界画得模棱两可模型学到的边界就会模糊。LabelMe这类工具支持多边形吸附和顶点编辑标注时尽量贴着物体轮廓走宁可多花10秒钟把一个角标准了也别图快画个大差不差的多边形。边界质量直接决定mIoU的上限。1.4 手动转换label为单通道灰度图的操作细节不管你用什么工具标注最终都要转成单通道灰度PNG。以LabelMe导出的JSON为例转换思路是读取JSON里的形状和标签按category列表建立ID映射用PIL或OpenCV在多边形内填充对应的整数像素值。import json import numpy as np import cv2 from pathlib import Path # category顺序很重要下标就是像素值 categories [_background_, road, building, vehicle] cls2id {name: i for i, name in enumerate(categories)} def json2mask(json_path, img_shape(512, 512)): with open(json_path, r, encodingutf-8) as f: data json.load(f) mask np.zeros(img_shape, dtypenp.uint8) for shape in data[shapes]: label shape[label] if label not in cls2id: print(f警告{label} 不在类别列表中跳过) continue points np.array(shape[points], dtypenp.int32) cv2.fillPoly(mask, [points], cls2id[label]) return mask # 使用示例 mask json2mask(label.json, (512, 512)) cv2.imwrite(mask.png, mask) # 注意cv2.imwrite保存单通道png时会被自动压缩为灰度图是安全的这里有一个必须强调的坑保存mask时不要用matplotlib的imsave它会自动加上colormap存成三通道的伪彩色图也不要用cv2.imwrite保存带调色板的PNG索引图以外的任何变体。最稳妥的方式是上面看到的直接用OpenCV写uint8的单通道数组文件后缀叫png就行。训练前务必做一次像素值分布检查import numpy as np mask np.array(Image.open(mask.png)) print(np.unique(mask)) # 期望输出[0 1 2 3]如果出现[0 1 255]这种说明格式有问题2. Config文件修改不复制粘贴理解之后再动手2.1 mmsegmentation的config体系是怎样分层的mmsegmentation的config不是一个大而全的文件而是分层组合的。拿pspnet_r50为例它对应的config会继承一份自_base_目录下的四类基础配置数据集配置、模型配置、训练策略配置、运行时配置。这个设计和mmdetection一脉相承好处是公用部分不用重复写、不会被改乱坏处是新手很难一眼看出“到底哪个文件决定了我当前的训练行为”。我建议你拿到一个官方的cityscapes config之后第一步先跑一下python tools/print_config.py 你的config.py它会把继承展开之后的所有配置完整打印出来。这个命令非常重要它能让你看到所有继承关系被解析后的完整config避免你在层层继承里迷路。而且每次改过config后都值得重新打印一遍确认改动生效了。实际项目里我不建议像官方那样搞太多层继承。最多两层就够了一层是基础数据集配置一层是你当前实验的专属配置。层级太多时调一个学习率要打开三个文件心智负担太重还容易改错。2.2 data配置dataset_type、data_root、img_dir和ann_dirconfig改动里最核心的是dataset_type和data这两个字段。先说dataset_type很多博客会让你直接改成dataset_type CustomDataset这个方向是对的但有个隐含条件如果你的数据格式是“一图一掩膜”的文件夹结构并且类别数不与任何内置数据集一致那么必须用CustomDataset。同时你还需要指定一个metainfo字典把类名列表和类别颜色告诉框架否则mmsegmentation会从默认的cityscapes类名里取训练时类别名称会乱套。dataset_type CustomDataset data_root data/mydata/ metainfo { classes: (background, road, building, vehicle), palette: [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0]] } train_dataloader dict( batch_size4, num_workers4, persistent_workersTrue, samplerdict(typeInfiniteSampler, shuffleTrue), datasetdict( typedataset_type, data_rootdata_root, data_prefixdict(img_pathimgs/train, seg_map_pathmasks/train), metainfometainfo, pipelinetrain_pipeline) )注意这里的data_prefix字段官方新版用了dict(img_path..., seg_map_path...)旧版本的代码里可能叫img_dir和ann_dir。这两个写法都算合法取决于你的mmsegmentation版本2.x系列建议用data_prefix。如果遇到数据加载为空的报错第一排查项就是data_prefix里的目录名和磁盘上的实际目录名是否完全一致。还有一个高频问题classes里的元组顺序必须和标注时定义的类别ID一一对应。你标注时像素值1是roadconfig里classes元组的第一个位置就必须是road如果有错位模型训练时不会报错但预测结果的语义就全乱了评估时的mIoU也会很难看。2.3 model配置num_classes和in_channels必须改对在model字段里最容易漏改的就是decode_head和auxiliary_head里的num_classes。你用的任何官方config默认值都是cityscapes的19类或者ade20k的150类不改这个参数直接训练自己的数据框架会在构建模型时直接抛“inconsistent”之类的报错。具体操作很简单在config文件里加一行覆盖model dict( typeEncoderDecoder, data_preprocessordict( typeSegDataPreProcessor, mean[123.675, 116.28, 103.53], std[58.395, 57.12, 57.375], bgr_to_rgbTrue, pad_val0, seg_pad_val255), backbonedict( typeResNetV1c, depth50, num_stages4, out_indices(0, 1, 2, 3), dilations(1, 1, 2, 4), strides(1, 2, 1, 1), norm_cfgdict(typeSyncBN, requires_gradTrue), norm_evalFalse, stylepytorch, contract_dilationTrue), decode_headdict( typePSPHead, in_channels2048, in_index3, pool_scales(1, 2, 3, 6), channels512, dropout_ratio0.1, num_classes4, # 这里必须改 norm_cfgdict(typeSyncBN, requires_gradTrue), align_cornersFalse, loss_decodedict( typeCrossEntropyLoss, use_sigmoidFalse, loss_weight1.0)), auxiliary_headdict( typeFCNHead, in_channels1024, in_index2, channels256, num_convs1, concat_inputFalse, dropout_ratio0.1, num_classes4, # 这里也必须改 norm_cfgdict(typeSyncBN, requires_gradTrue), align_cornersFalse, loss_decodedict( typeCrossEntropyLoss, use_sigmoidFalse, loss_weight0.4)), train_cfgdict(), test_cfgdict(modewhole))同时要看一看backbone的in_channels。你的输入如果是普通RGB三通道图保持默认3不用动如果是灰度图、四通道遥感图或者带深度的图就要改backbone里第一个卷积层的输入通道数。改法也不是直接改backbone的in_channels1就完事因为ResNet的第一个卷积权重是预训练好的通道数变了权重没法直接加载。常规做法是把预训练权重的第一个卷积层做平均把3通道的权重复制到1通道或者干脆不加载预训练权重、从头训练。这里提醒一下除非数据量很大不然从零训练ResNet50做分割效果大概率不如用ImageNet预训练。所以数据如果不是特殊模态建议直接三通道输入。2.4 pipeline里的归一化参数和尺寸设置为什么不建议照搬还有一个很容易被忽视的地方data_preprocessor里的mean和std官方config里默认是ImageNet统计量即mean[123.675, 116.28, 103.53]、std[58.395, 57.12, 57.375]。很多人拿着自己的数据集尤其是在自然光条件下拍的可见光图像会纠结要不要改成自己数据集的统计量。我的建议是不要改。ImageNet的mean和std已经是一个非常好的分布近似改不改对你的最终精度影响在0.5个点以内而且改了之后如果你要用官方预训练权重流程会更麻烦。说白了这个参数只要保证训练和推理时一致就行不是追求数据分布匹配的场合。尺寸设置方面train_pipeline里的Resize、RandomFlip、PhotoMetricDistortion都可以保留但RandomCrop里的crop_size要根据你的显存来。比如你显卡只有11GBcrop_size设成(769, 769)对PSPNet来说非常吃力改成(512, 512)才是务实的选择。val和test里的Resize一般设置成(512, 512)或者原图缩放到指定比例注意保持和训练尺寸不要差太多否则精度会波动。3. 训练实战从命令行到日志解读3.1 训练前的一个关键测试单步跑通前向不要一上来就跑完整训练先用一张图、一个batch、一个step验证流程。启动训练之前我建议先跑一次完整的单步前向和反向python tools/train.py configs/pspnet/pspnet_r50_8xb2-40k_cityscapes-512x512.py --work-dir work_dirs/test_run如果正常启动你会看到模型结构、参数量、dataloader信息。这个过程也顺便验证了config里的路径都能被正确解析。我第一次跑自定义数据集时少写了一个data_prefix路径程序没报错但每个batch拿到的都是空数据训练loss一直是nan折腾了很久才定位到问题根源。还有一个更快的验证方式写一个小脚本直接加载数据管道from mmseg.datasets import build_dataset from mmengine.config import Config cfg Config.fromfile(my_config.py) dataset build_dataset(cfg.train_dataloader.dataset) for i in range(3): data dataset[i] print(data[inputs].shape, data[data_samples][0].gt_sem_seg.data.shape)只要能正常打出图像和mask的形状且mask里的类别数是符合预期的说明数据侧基本没问题接下来训练就顺畅很多。3.2 训练命令详解单卡、多卡、断点续训和定时保存正式训练的命令并不复杂复杂的是根据自己的资源和需求组合参数。单卡训练最简单的写法CUDA_VISIBLE_DEVICES0 python tools/train.py my_config.py --work-dir work_dirs/my_experiment多卡训练用官方推荐的分布式启动器bash tools/dist_train.sh my_config.py 4 --work-dir work_dirs/my_experiment后面的数字是GPU卡数。多卡时注意如果你的config里没有设置samplerdict(typeInfiniteSampler, shuffleTrue)mmengine的DistributedSampler会自动处理数据切分不需要手动管shuffle。训练过程中最常用的两个参数是--resume和--cfg-options。前者用于断点续训比如python tools/train.py my_config.py --resume work_dirs/my_experiment/iter_10000.pth后者用于临时覆盖config参数这在跑消融实验时非常高效不需要改文件python tools/train.py my_config.py --cfg-options optimizer.lr0.01 model.decode_head.num_classes4我一般会额外设置default_hooks.checkpoint.interval2000也就是每2000个迭代存一次权重。默认值是4000如果中途崩了最多可能丢4000个迭代的进度对长时间训练来说不太划算。3.3 训练过程中必须盯着哪些指标loss为什么是nanmIoU为什么不动训练日志里至少要看四个东西loss、decode.loss_ce、decode.acc_seg、data_time。前三个是模型收敛的直接体现第四个能看出dataloader是不是瓶颈。如果data_time占了每步时间的60%以上说明数据加载或者数据增强太慢训练在空转建议调高num_workers、检查磁盘IO或把persistent_workers设为True。loss变成nan是很多人第一次训练自定义数据集时最崩溃的事。原因不外乎三类标签里有超出num_classes范围的像素值最常见的用的mask不是从0开始编号、学习率过大导致梯度爆炸、或者图像里有损坏的文件。排查顺序也是这个顺序先用上一节提到的脚本统计mask的像素值分布然后把学习率降到默认值的1/10试跑一版最后排除数据管道里混入的异常图。这三步能解决90%的nan问题。如果loss正常下降但mIoU一直很低比如低于0.5多半是类别不平衡造成的。你的数据里如果背景像素占了90%以上模型会趋向于把所有像素都预测为背景mIoU自然上不去。这时候有两个调整方向一是给loss加上class_weight提高小类别权重二是在pipeline里加RandomCrop尽量让每个crop里的小目标占比更高。从我的经验看class_weight对语义分割的提升效率没有检测任务里那么明显优先考虑数据层面的均衡更有效。3.4 两份官方热词带来的启发远程开发时config文件的修改链路顺着热搜词里提到的“vscode 远程config文件”往下说我猜不少人是在远程服务器上开发用VSCode Remote SSH打开项目。这种情况下改config文件有个额外风险本地和服务器上mmsegmentation的版本可能不一致本地改了文件后不会自动同步配置跑不起来时先确认改动是否真正到了服务器上。我的习惯是项目里统一用相对路径写data_root不写绝对路径这样换机器时不用改config。还建议在config文件头部固定写好环境说明注释# 环境A100单卡CUDA 11.7mmseg 1.2.0 # 数据集自定义道路分割4类background, road, building, vehicle # 预训练权重resnet50_v1c.pth自动下载自OpenMMLab官网这个注释在半年后回看实验时太有用了。你永远会忘记当时用了哪个版本、哪个数据集、哪个预处理但注释不会。3.5 训练结束后的权重保存和转出onnx的注意事项训练结束后work_dirs里会有iter_xxxx.pth或者epoch_xx.pth的权重文件。这些权重是mmengine的格式化dict里面除了模型参数还有optimizer状态和meta信息不能直接在原生PyTorch里load。如果你需要导出到onnx做部署可以用官方脚本python tools/deploy/onnx_export.py my_config.py work_dirs/iter_10000.pth out.onnx如果只是想在本地用mmseg的推理API做效果验证更简单的做法是from mmseg.apis import init_model, inference_model model init_model(my_config.py, work_dirs/iter_10000.pth, devicecuda:0) result inference_model(model, test.jpg) # result.pred_sem_seg.data 是HW的类别ID矩阵直接用调色板映射成彩色图即可这个流程能快速让你看到分割效果方便判断模型质量是否值得继续训练。4. 常见问题与排查技巧实录4.1 高频问题速查表下面这些问题是社群和评论区里翻来覆去地出现的高频问题我整理成了一张速查表遇到报错先对着排查一遍大概率能省下半天时间。现象大概率原因处理办法训练启动时报FileNotFoundErrordata_root路径或data_prefix目录名拼写错误打开config逐个核对目录loss为nanmask像素值超出类别范围用np.unique统计mask保证最大值 num_classes - 1训练正常但mIoU极低类别不平衡或标签错位统计各类别像素占比调整crop策略标签错位时检查img和mask文件名对应关系mask尺寸与img不一致也会导致错位CUDA out of memorycrop_size过大或batch_size过大调小crop_size到512或更小batch_size降到2加载预训练权重报错backbone in_channels与权重不匹配检查in_channels是否为3二/四通道数据需要自行处理权重训练速度越来越慢验证阶段加载了太多缓存或pipeline里有耗时操作查看data_time占比调大num_workers减少验证频率有警告class_names not saved自定义数据集没有传metainfo在dataset字段里补上metainfo字典推理结果全是背景类num_classes没改模型输出维度与类别数不匹配检查decode_head和auxiliary_head的num_classes可视化保存的mask是全黑的用PIL保存时误用L模式但值范围超出显示范围可视化时手动乘回255或使用调色板映射4.2 我踩过的三个隐蔽坑第一个坑是mask的通道数。有的工具导出的PNG虽然是.png后缀但实际是4通道RGBA直接喂给mmsegmentation会在损失计算时爆维度错误。处理方法是在数据预处理前统一用OpenCV读一次检查mask.shape[2]是否存在发现是三通道或四通道就做一次cv2.cvtColor(mask, cv2.COLOR_RGB2GRAY)。类似地有些标注工具会导出16位深度图mmsegmentation也能处理但如果你中间不小心转成了8位而且丢失了类别ID那就彻底找不回来了。第二个坑是训练和验证时图像尺寸不一致。我在一个项目里训练用512x512随机裁剪验证时直接test_cfgdict(modewhole)结果遇到一张2000x3000的大图推理时显存直接不够用。后来用的是modeslide设置stride256虽然速度慢一点但显存被稳定控制在可接受范围。这个细节在部署阶段尤其重要。第三个坑是类别不平衡但靠loss_weight救场失败。实验做完后分析发现问题出在CrossEntropyLoss的默认class_weightNone上小类别梯度完全被淹没。后来先把背景区域在mask里用ignore_index255排除掉再给前景类别手动加权效果才上来。所以如果你的背景占比超过95%直接在标注阶段就把背景排除出损失比重写一个loss有效得多。4.3 训练中断了怎么办断点续训和权重转换的正确姿势训练中途因为机房断电、显存炸了、或者自己手抖CtrlC终止这是家常便饭。mmengine有一套比较完整的断点续训机制你只需要在命令行加--resume参数指向最近的权重文件即可。但要注意mmengine会自动在work_dirs下生成last_checkpoint文件来记录最新状态所以更省事的写法是python tools/train.py my_config.py --resume work_dirs/my_experiment/iter_12000.pth也可以不指定具体路径在config里设置resume True这样它会自动去找work_dirs下最新的权重。这里有一个小坑resumeTrue会同时恢复optimizer、scheduler和随机数状态如果你只是想换一个预训练权重从某个迭代重新跑而不是严格续训应该用load_from而不是resume。4.4 如何利用验证指标反推数据问题验证集的mIoU如果一直很低不要急着调模型结构先去看bad case图。建议保存一个可视化脚本每过几个epoch就把验证集里表现最差的20张图连同预测结果和ground truth拼成一张大图一眼就能看出模型错在哪里是边界糊了还是某个类别整个漏了还是小目标完全没预测到。比如你发现模型在“vehicle”这个类别上mIoU是0但训练集里明明有该类别的像素。打开bad case一看发现标注里vehicle的像素值标成了2而config里classes的第2位是building——类别错位。这种问题单纯调模型是永远调不好的只能回到数据侧修。反过来如果所有类别的mIoU都低但loss很低那很可能是过拟合了验证集分布或训练与验证的预处理不一致。检查一下val_pipeline里是否有Resize与训练不同、是否有额外的归一化差异。这类问题的排查思路本质上就是“把数据管道的每一步打开来直接对比训练和验证样本的输入张量”。5. 从跑通到精调三个让效果更好的小技巧5.1 预训练权重怎么加载load_from和init_cfg的区别很多人分不清load_from和init_cfg到底谁生效。简单说init_cfg是模型构建阶段给backbone、neck、head各组件单独加载权重的机制load_from是在整个模型初始化之后、训练开始之前按key匹配加载整个权重文件。在自定义数据集上我推荐先用init_cfgdict(typePretrained, checkpointpretrain/resnet50_v1c.pth)给backbone加载预训练权重decode_head和auxiliary_head保持随机初始化。如果你直接用load_from加载一个完整的cityscapes模型权重由于解码器的输出类别数不一致会有一堆shape mismatch的警告虽然mmengine会跳过不匹配的key但效率很低还容易让人误以为权重没加载上。5.2 学习率策略和迭代次数的经验值mmsegmentation官方config里常见schedule_40k、schedule_80k这里的40k/80k指的是迭代次数。对一个小型数据集几百张图40k迭代基本够用。如果你用默认的poly学习率策略初始学习率0.01对ResNet50 backbone来说偏大尤其当batch_size只有2或者4时建议降到0.005否则前面几百步容易震荡。我用下来的经验配置是这样的ResNet50 PSPNetbatch_size4初始lr0.005power0.9warmup_iters1000总迭代24000配合RandomCrop(512,512)。这个配置在多个自定义数据集上都能稳定收敛大家可以直接抄作业。如果你的模型更深如ResNet101或用了Transformer类的backbone初始lr要相应调低到0.0001~0.001的量级。还有一个经常被忽略的参数optim_wrapper里的clip_grad。训练分割模型时偶尔会有个别batch产生比较大的梯度导致loss突然跳高。我习惯在optimizer配置里加上梯度裁剪optim_wrapper dict( typeOptimWrapper, optimizerdict(typeSGD, lr0.005, momentum0.9, weight_decay0.0005), clip_graddict(max_norm35, norm_type2))这个max_norm35是mmseg官方几个config里出现过的值能有效防止loss spike又不影响正常收敛。5.3 可视化检查不只是看看效果还能定位数据问题训练过程中定期做可视化预测非常有用。我一般每5000个迭代跑一次可视化把原图、标注、预测结果并排输出。除了直观验证效果还能发现一些数据问题比如标注和图像没有对齐有偏移可视化里会看到预测结果在物体周围有一圈“描边”误差又比如某些类别在标注里几乎没出现过预测结果里大概率也不会出现。这时候与其说模型不好不如说是数据标注的问题。mmseg提供了一份可视化脚本也可以自己写一个简化版import matplotlib.pyplot as plt from mmseg.apis import init_model, inference_model model init_model(cfg, ckpt, devicecuda) img test.jpg result inference_model(model, img) mask result.pred_sem_seg.data[0].cpu().numpy().astype(np.uint8) # 调色板映射 palette [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0]] color_mask palette[mask] # 形状 (H, W, 3) fig, axes plt.subplots(1, 3, figsize(15, 5)) axes[0].imshow(plt.imread(img)) axes[1].imshow(mask) axes[2].imshow(color_mask) plt.savefig(vis_result.png, bbox_inchestight)这样一张图胜过千行日志能极大节省排查问题的时间。写在最后的实操体会这套流程我前前后后跑过不下十次从第一次吭哧吭哧调格式调了整整两天到现在基本半小时内能把一个全新数据集跑起来。最大的体会是mmsegmentation本身不难难的是数据侧的纪律性。把自己的标注格式、类别ID约定、目录结构固定成模板反复使用你会发现自己省下的时间远比研究模型结构的时间多。如果你后续要做到更复杂的任务比如多模态分割、半监督分割或者想把模型导出到TensorRT部署最稳妥的进阶路径还是先把这一套基础流程吃透。只要数据管道和config体系理解到位后面任何新模型加进来都只是换一个config文件的事。希望这篇教程能帮你在自定义数据集上少走几个弯路直接跑通属于你自己的第一个分割模型。
返回列表