简介这是一份面向计算机视觉与遥感应用场景的水域实例分割数据集覆盖河流、湖泊、海洋、湿地、池塘及其他水域六大类别可用于训练YOLO系列模型服务于水域监测、水体提取、洪水预警等任务。压缩包共2000个文件主要包含1673个txt格式的分割标注文件提供多边形掩码与类别标签、325张高分辨率水域影像、1个yaml配置说明以及1份数据集介绍docx总体积约103.31MB结构简洁便于直接接入训练流程。图片采集自卫星、航拍与地面拍摄等多种场景能帮助模型适应不同视角与光照条件。数据划分明确训练集1340张、验证集333张适合作为实例分割任务的训练与评估基准。目前已有179人学习下载适合需要高质量水域标注数据进行算法验证或项目开发的开发者与研究人员。1. 水域实例分割数据集为什么要把“一片水”数成“一处处水”如果你手里正握着这个“水域实例分割数据集.zip”大概率不是来泛泛了解什么叫深度学习而是有明确任务从无人机正射影像里把河道、鱼塘、蓄洪区一块块提取出来或者在一张含半个湖泊的地图上统计互不连通的水面数量。水域实例分割数据集要解决的就是这类问题——它不像语义分割那样把整张图里所有水体像素一次性涂成一个颜色而是把每一处独立水域当成一个对象输出各自的轮廓和类别同一张图里的“水”可以被编号成 instance_1、instance_2。这份 zip 对于做防汛应急、水利巡检、养殖水面确权或者城市内涝评估的人尤其有用。接下来的内容我会按我拿到这类数据集后的真实顺序来写先判断 zip 里到底是什么标注格式再解开可能存在的伪加密和压缩陷阱然后把标注转成 YOLO 实例分割能直接训练的格式最后给出训练参数、踩坑记录和验证方法。跟着走一遍你大概率能在一个晚上把这份数据集跑进自己训练管线里。2. 先看格式再动手水域实例分割数据集的三种标注形态2.1 为什么这份数据集偏要用实例分割而不是语义分割经常有人把实例分割和语义分割在“水”这上面搞混。语义分割的任务是逐像素分类图里所有水都标成同一个 water 类别而实例分割要求区分“哪一片水是哪一片水”。YOLO26 出来后很多人误以为分割只是在原有检测模型上换了个 head其实实例分割和语义分割的区别在于输出粒度实例分割输出的是“对象掩码列表”每个掩码带独立编号语义分割输出的是“类别概率图”同名类别只对比不分组。落到水域上区别很大。一片断桥两侧的水面语义分割会把它们连成一个连通区而实例分割要把它们记为两个实例因为实际管理时它们分属不同鱼塘、不同闸口或者不同蓄滞洪单元。再比如洪水影像里水体被道路、堤坝切断实例分割能告诉你“这张图里有 7 处渍水点”语义分割只能告诉你有 7% 的像素是水。这个区别直接决定下游统计口径。所以我在用 YOLO 系模型做水域任务时只要目标是数个数、算单块面积、按块追踪变化一律选 segment 分支不用仅做语义分割。2.2 先解压再说密码、伪加密和 CRC 校验水域实例分割数据集.zip 最常见的第一个坑是解压报错。很多数据包用百度网盘或者自建站分发打包前套了一层密码更麻烦的是有些包被第三方转存时做了“伪加密”——它在压缩文件头里把加密标志位置为 1实际数据段根本没有加密你输什么密码都报错或者某些解压工具直接让你填密码。先别急着骂数据集用这个命令看一眼zipinfo -v water_seg.zip | head -n 80输出里会列出每个文件的压缩方式、通用位标志和加密方式。真正的加密会显示Encryption: AES-256或Encryption: ZipCrypto如果你看到Encryption: none但解压时仍然要求输密码那就是伪加密标志在作怪。也可以用 Python 直接看通用位字段import zipfile z zipfile.ZipFile(water_seg.zip) for info in z.infolist()[:5]: flags info.flag_bits is_encrypted bool(flags 0x1) print(f{info.filename}: flag_bits{hex(flags)} 加密标志{is_encrypted})flag_bits 的 bit0 表示加密标志位bit3 表示是否有数据描述符bit11 表示文件名是否用了 UTF-8。如果这里 bit0 为 1但用 010 Editor 或 HxD 打开 zip 后对应本地文件头里找不到合法的密码头部基本可以断定是伪加密。伪加密在 zip 格式里并不罕见常见做法是数据商为了避免爬虫批量下载手工改了标志位而不是真正加密。我一般遇到这种包不会去研究数据段有没有真密码而是直接把两个位置的加密位清掉再重新打包def clear_encrypt_flag(src_path, dst_path): data bytearray(open(src_path, rb).read()) count 0 i 0 # PK\x03\x04 是本地文件头PK\x01\x02 是中央目录头 while i len(data) - 4: if data[i:i4] bPK\x03\x04 or data[i:i4] bPK\x01\x02: # 通用位字段在头部偏移 6 处bit0 置 0 data[i6] 0xFE count 1 i 4 else: i 1 open(dst_path, wb).write(data) print(f清除了 {count} 个头的加密标志位) clear_encrypt_flag(water_seg.zip, water_seg_fixed.zip)这个脚本可靠的前提是原始 zip 确实没有真密码且数据段没有加密如果真加密这样改完会发现解压出来的文件 CRC 校验失败因为压缩数据段本身就带了密码头的处理。另一点要注意的是伪加密清完还需要看 CRC 是否通过流式解压工具会忽略 CRC 直接产出损坏文件所以解压后一定要校验文件大小和内容。我做这一步的一部分原因是为了让人能读自己合法获得的数据集而不是用来绕过别人的付费机制边界自己把握。2.3 解压后的目录结构images、masks、annotations 三类文件要点清除这些障碍后数据集解压出来通常是三类内容原始图像目录、掩码目录、标注文件。我拆过不少这样的包最常见的组织方式一个images目录放着 RGB 影像按train、val划分一个annotations目录放着 COCO 格式的 JSON掩码则要么是独立的 PNG 单通道文件要么直接内嵌在 JSON 的 segmentation 字段里。如果这份数据集源头是遥感还可能带一个geoinfo目录记录每张影像对应的地理坐标和投影参数训练用不上但做成果导出时能找回真实位置。先搞清楚标注藏在哪儿再决定怎么处理。我拿到任何实例分割数据集的第一件事是打开一个 JSON 看它的结构。这里引用最通用的 COCO2017 数据集结构作为参照JSON 顶层必须有images、annotations、categories三组images里记录id、file_name、width、heightannotations里每个实例带image_id、category_id、segmentation、area、bbox、iscrowd。水域数据最大的特点是category_id多半只有一个 water 类别但也有混入“堤岸”“桥梁”“漂浮物”的多类变体多类变体比单类难处理得多因为掩码边缘一个像素的错位就会导致类别标签抖动。掩码的表现形式也决定后续工作量。COCO 格式里segmentation字段可以是一个多边形点阵的列表也可以是一个 RLE 编码的字典。多边形适合人工标注的水域边界干净且文件小RLE 适合遥感软件自动生成的掩码边界细碎但完整。下载下来的数据如果只有一张张 PNG 掩码而不是 JSON那我建议先把它当语义分割数据验一下再看每个掩码里连通域数量因为同一个水体在影像里被道路切断会形成多个连通区这是否需要拆成多个实例取决于原数据作者的标准。这个判断直接影响后面转换出来的 YOLO 标签质量后面避坑章节会专门讲。2.4 用 pycocotools 先做个体检实例数量、面积分布和坏标注在动手转换格式前先用 pycocotools 把这份水域实例分割数据集的统计信息拉出来这一步能避免后面反复返工。很简单的四五行代码就能得到每个类别的实例数、面积中位数和最大最小范围from pycocotools.coco import COCO import numpy as np coco COCO(annotations/instances_train.json) cat_ids coco.getCatIds() for cid in cat_ids: anns coco.loadAnns(coco.getAnnIds(catIdscid)) areas np.array([a[area] for a in anns if a[iscrowd] 0]) if len(areas) 0: print(f类别 {cid}: 无有效实例) continue print(f类别 {cid}: {len(areas)} 个实例, f面积中位 {np.median(areas):.0f} px, f最大 {areas.max():.0f} px, 最小 {areas.min():.0f} px)注意iscrowd字段。COCO 里iscrowd1表示该标注是一整片密集对象不参与实例分割指标计算很多水域数据商把无法逐块标注的连片水面全标成iscrowd1如果你没过滤就直接转 YOLO 标签会发现这些大块水体全部变成训练负样本最后模型学出来只认小水塘不认湖泊。面积分布也要看中位数而不是平均值水域实例里极少数超大水面会把平均值抬高让你误以为所有目标都很大从而把imgsz调得过大。如果这一步发现 JSON 中所有掩码面积都是 0 或者 bbox 越界说明文件在传输或伪加密修改过程中损坏了。这种情况下不用急着删数据重下先检查images里有没有和 JSON 对不上的文件名常见原因是某些软件导出 JSON 时把.tif写成了.jpg。把验证集和训练集都跑一遍同样的体检能过滤掉后面训练时 90% 的“莫名奇妙不收敛”问题。3. 把 COCO 标注转成 YOLO 分割格式坐标系、顶点上限和贴合边界3.1 先说清 YOLO 分割标注的存放约定一个实例一行文本YOLO 实例分割的训练标签不是 JSON而是每张图对应一个同名.txt文件放在labels/train和labels/val目录下。文本每行表示一个实例格式为“类别id 顶点x1 顶点y1 顶点x2 顶点y2 ...”坐标必须归一化到 01也就是实际像素坐标除以图片的宽度或高度。水域数据通常只有一个 water 类那每行开头都是 0如果包含建筑、桥梁、船等多类就按它们在classes.yaml里的索引编号。目录结构我推荐这样组织和 ultralytics 的习惯一致water_seg/ images/train/*.jpg images/val/*.jpg labels/train/*.txt labels/val/*.txt classes.yaml标签文件必须与图像文件同名后缀不同。比如img_00012.jpg对应img_00012.txt。这个约定新手经常记反把标签文件名写成img_00012.jpg.txt训练时 YOLO 会静默跳过这些样本表现为训练集 loss 始终很小但验证集精度上不去因为有效训练样本少了。3.2 转换脚本多边形和 RLE 掩码统一处理COCO JSON 里segmentation是列表时直接取出多边形坐标如果是 RLE 字典需要先解码成二值掩码再提取轮廓。下面这段转换代码足以应付大部分水域实例分割数据集import json import numpy as np import cv2 from pathlib import Path from pycocotools import mask as mask_utils def mask_to_polygon(segmentation, img_w, img_h, epsilon2.0): 把 COCO segmentation 转成归一化多边形点列表 if isinstance(segmentation, list): # 多边形格式直接拿第一组点 coords np.array(segmentation[0], dtypenp.float32).reshape(-1, 2) else: # RLE 格式解码成掩码再提取最大外轮廓 m mask_utils.decode(segmentation) if m.ndim 3: m m[:, :, 0] m (m 0).astype(np.uint8) contours, _ cv2.findContours(m, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return None coords max(contours, keycv2.contourArea).reshape(-1, 2).astype(np.float32) # Douglas-Peucker 简化减少顶点数量 approx cv2.approxPolyDP(coords.astype(np.float32), epsilon, True) if len(approx) 3: return None approx[:, 0] / img_w approx[:, 1] / img_h return approx.reshape(-1).tolist() def convert_coco_to_yolo(json_path, labels_out_dir): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_map {img[id]: img for img in data[images]} Path(labels_out_dir).mkdir(parentsTrue, exist_okTrue) for ann in data[annotations]: if ann[iscrowd] 1: continue img img_map[ann[image_id]] poly mask_to_polygon(ann[segmentation], img[width], img[height]) if poly is None: continue txt_name Path(img[file_name]).stem .txt line f{ann[category_id]} .join(f{v:.6f} for v in poly) with open(Path(labels_out_dir) / txt_name, a, encodingutf-8) as f: f.write(line \n) convert_coco_to_yolo(annotations/instances_train.json, labels/train)逻辑说明先把 RLE 解码成二值图再用cv2.findContours提取外轮廓RETR_EXTERNAL只取最外层轮廓避免水体中间有岛时把洞的轮廓也当新实例。epsilon2.0是轮廓简化阈值单位是像素阈值越大顶点越少、边界越粗糙。水域岸线细节很重要我的经验是 epsilon 不要超过 3否则细长的河汊会被磨平。多边形简化后过滤掉顶点数少于 3 的残渣防止后面 YOLO 解析时崩溃。3.3 归一化、越界裁剪和最小面积过滤三个必调参数转换时最容易翻车的不是格式本身而是边界条件。第一坐标归一化一定要把 x 除以原图宽度、y 除以原图高度不能图省事统一除以最大边长。水域影像常见长宽比失衡的航测图比如 8000×6000 的大图裁出来的瓦片除以统一值会让 x 和 y 的还原比例不一致掩码在训练时被拉伸变形。第二有些标注多边形的顶点正好压在图像边缘四舍五入后可能落进 1.0 之外YOLO 训练时会警告坐标越界。对这种情况我通常直接对坐标做np.clip(poly, 0.0, 1.0)并把裁剪后重复的相邻顶点删掉。还有一个必须做的过滤最小面积。COCO 转换时全图 50% 都是水的超大实例要保留但只有几个像素的碎块要丢掉。因为 YOLO 的 mask 分支在imgsz640下会把小目标缩成一个点那些面积不足 20 像素的实例不仅学不到岸线特征还严重拖慢训练时的正样本匹配。我在实践里设了两个规则原始面积小于 36 像素的实例直接跳过简化后坐标范围最大跨度小于 1.5 个像素的也跳过。这些阈值写在转换脚本里以后换一份数据集也不用改代码只调常量。大图遥感数据还有另一个特有问题就是分块裁剪。文件夹里放着的可能不是完整大图而是按网格切的瓦片每块瓦片存成独立图片。如果一份瓦片恰好把一个大湖裁成四块湖本体在这四张图上重复出现而这四张图又被同时划进训练集和验证集就会造成数据集泄漏。我做训练集划分时会检查所有图片文件名是否带行列号前缀如果有就按网格而不是按文件随机划分。网格划分这个思路和有人拿 mmrotate 训练 DOTA 数据集时遇到的问题一模一样遥感切片数据最怕同源瓦片既在训练又在验证评估结果虚高得离谱。4. 用 YOLO 实例分割训练水域数据集最小命令和关键参数4.1 最小示范装好 ultralytics第一条训练命令现在就跑如果你的环境是干净的直接安装依赖并启动训练。普通单卡 8GB 显存也能跑通只是慢一点pip install ultralytics yolo segment train \ datawater_seg/classes.yaml \ modelyolov8n-seg.pt \ epochs200 \ imgsz640 \ batch16 \ device0集成在 ultralytics 里的 YOLO 实例分割训练命令就是yolo segment train注意不是yolo train也不是yolo detect train检测和分割的模型、标签格式、损失函数、预测输出都不同。data必须指向 YAML里面写好训练集和验证集绝对路径或项目路径。classes.yaml 我这样写类别从 0 开始顺序编号path: /path/to/water_seg train: images/train val: images/val names: 0: water如果你的标注里还有“岸线”“桥墩”等类别name 逐行往下加即可但必须和转换脚本写入的category_id一致。modelyolov8n-seg.pt表示从预训练分割权重开始微调换成yolov8s-seg.pt或更大的版本能提升边界精度但显存占用和训练时长线性增加。我一般在验证数据量只有几百张时先用 nano 跑通管道确认能收敛后再上 s 或 m 模型节省时间。4.2 水域任务要重点调的六项训练参数从方案到调参参考训练命令跑通只是第一步水域实例分割的难点在于背景复杂、目标边界弱、同一幅图里水体面积跨度大。下面这组参数是我在多个水域数据集上试出来的调节方向适合大多数单波段 RGB 影像不是唯一解参数默认值水域任务参考原因imgsz640640960河道和岸线细长过小分辨率会磨平边界batch16按显存调 816mask 分支计算量比检测大batch 高不代表收敛快hsv_h0.0150.005水域颜色单调过强色相增强会让水体改色hsv_s0.70.3防止把黄色泥沙水增强成红色水体fliplr0.50.00.3左右翻转会颠倒水流方向和倒影特征mosaic1.00.5大水体被四图拼接切碎反而破坏整体结构参数说明imgsz影响最大。水面是典型的大而空目标太大显存扛不住太小岸线纹理丢失。我常用瓦片尺寸是 1024 但训练时缩到 768既保留细长水体又避免 OOM。mosaic增强是 YOLO 训练精度的关键但水域图像特殊四张图随机拼贴后一块完整大湖会被切断同一个实例的掩码碎在拼图边缘模型被迫学“破碎的水面”。把它从默认 1.0 降到 0.5可以让模型更多看到完整实例。还有一点YOLO26 这类新版本默认会在最后 10 个 epoch 关闭 mosaic这个行为是刻意的不要手动改成全程开启否则验证阶段会出现大量碎块预测。4.3 训练中看什么loss 曲线和类别损失的阅读姿势训练启动后在终端输入yolo segment train ...同目录下打开runs/segment/train/用 tensorboard 或者直接看终端输出。分割训练一共输出四类 lossbox_loss是检测框损失seg_loss是掩码损失cls_loss是类别损失dfl_loss是边界框分布损失。对水域这个单一类别任务最值得盯的是seg_loss它是直接决定掩码质量的部分。如果前 50 个 epoch 里seg_loss从 1 掉到 0.1 左右训练基本正常。但有一个常见假象box_loss下降很快而seg_loss缓慢说明模型先找到了“水体可能在哪”却还没学会贴着岸线细抠边界这是水体边界与背景像素可分性差导致的正常加 epoch 或换更大模型都能解决。另一个极端seg_loss快速掉到 0.01 以下、验证集 mAP 0.5 却很低通常是过拟合需要减少 epoch 或者添加 dropout、提高 lr 衰减。水域任务不比检测漏掉一个小河汊不影响 loss 但严重影响下游面积统计所以不必追求 train loss 无限低优先看验证集掩码贴得是否准。4.4 半监督标注用现成权重预标注快速扩充自己的水域数据如果你手上的水域实例分割数据集.zip 只覆盖了河流湖泊而你要识别的是鱼塘或排水渠直接微调可能不够。这时候常见的做法是先用自己的少量标注训练一个粗糙模型再让它去预标注未标注影像人工只修正错漏。我在水域项目里会用训练好的yolov8s-seg.pt批量预测新影像然后写一个脚本把高置信度的预测结果写回 COCO 格式再合并进原数据集。这个链路里最有用的命令是预测加导出yolo segment predict \ modelruns/segment/train/weights/best.pt \ sourceraw_unlabeled/ \ save_txtTrue \ save_confTrue \ conf0.6save_txtTrue会把预测掩码写成 YOLO 格式的标签save_confTrue在每个标签行末尾追加置信度方便你后续按 0.9、0.7 分档筛选。半自动标注只做两步高置信度样本直接进训练集低置信度样本排队人工重画。实际经验告诉我水域掩码的难点集中在“水陆过渡带”——有泥沙、倒影、水草的区域AI 预标注在这些地方会频繁出错所以人工修正时不需要全图检查只看岸线附近的预测掩码是否越界。5. 水域实例分割数据集的避坑记录解压、转换、训练三条路上的翻车点5.1 解压时报“密码错误”密码移除后仍然报错——伪加密在捣鬼现象用解压工具打开水域实例分割数据集.zip要求输入密码从说明文档里找到密码填进去依然提示文件头损坏或密码错误。更奇怪的是某些文件能解开、某些文件解到一半中断。原因这是很典型的 zip 伪加密。数据商把文件头的通用位加密标志位改成了 1但数据本身没有用任何密钥加密。解压工具看到加密标志就进入解密流程去读一个不存在的密码头部自然全盘报错。解决先用 2.2 节里的 Python 脚本读flag_bits如果 bit0 为 1用十六进制工具查看对应本地文件头的加密头部是否存在确认是伪加密后执行清除加密位的脚本再重新解压。要注意脚本执行后必须逐文件对比 CRC 和文件大小。真加密的数据段带密码头部伪加密没有如果改完标志位后解压出来一堆文件损坏那就不是伪加密而是数据本身损坏或传输中丢包。5.2 掩码只有黑白两色连通域处理成一片——语义分割标注重了实例分割的牌现象数据集里提供的是 PNG 掩码打开黑底白图原本在影像里被桥梁分割开的两个相邻水塘在掩码里是同一个白色连通区。拿它做实例分割训练模型永远学不会“桥左桥右是两个实例”。原因这份数据集可能是从语义分割项目转卖或派生出来的作者用连通域分析把语义掩码自动拆成实例却忽略了中间有 1 像素的连接缝隙或桥下阴影连通。自动连通域拆出的实例和人工判定不一致桥下阴影会黏连两片水。解决如果必须基于这份掩码训练先做形态学开运算切断细连接再用cv2.connectedComponents重新编号每个独立实例最后核对拆出的边界是否合理。如果目标是防洪调度级别的高精度成果这一步不要全自动抽 20% 样本人工校验两张图原始影像和实例编号图叠着看。顺便说一句这也能解释为什么“实例分割数据集”和“语义分割数据集”不能互相混用标注口径在源头就不同。5.3 生成的 YOLO 标签坐标出现大于 1 的数值训练报警告现象转换完标签后训练日志里出现WARNING: normalize coord out of range部分标签行里坐标值等于 1.02、1.30 之类。原因标注多边形有顶点贴图边缘归一化时四舍五入或者轮廓提取时cv2.approxPolyDP生成了超出原图边界的点。航测瓦片数据里尤其常见因为岸边会有少量像素被裁掉一半。解决转换脚本里对所有坐标做clip到 01然后把裁剪后相邻重复点删掉。还有一种隐蔽情况某个实例的掩码面积很小简化轮廓时因为 ε 太大产生了自交多边形坐标虽然都在 01 但轮廓线来回折叠。对这种对象直接过滤掉不要试图修正它的顶点顺序。5.4 训练完效果挺好的推理时水面却碎成几块——桥墩和阴影拆散了实例现象训练集里明明有完整的大湖面验证集结果却把湖预测成几十个碎块每一块面积很小、互相之间隔着桥墩、船影或堤坝。原因训练标注本身把每个可见切片当成了独立实例比如同一片湖被桥墩分成三块标注者标了 3 个实例。模型学到了“凡是桥墩遮挡处水体就断开”推理时遇到阴影也会触发同样的断连逻辑。这不是训练 bug是标注口径与你的业务口径不一致。解决先想清楚你的下游任务是“按自然连通域计数”还是“按行政管理单元统计”。如果是前者训练前把同一管理单元内因障碍物断裂的水体合并成一个实例如果是后者保持现状即可。我的经验是在标注说明里明确写一行“桥下水面视为同一实例船舶阴影处不要断开”并配 3 张示例标注图能让不同标注员的认知统一起来。这块的坑在于“同一片水”的判断依赖语义理解没法靠后处理完全修复。5.5 训练曲线正常但推理掩码粗糙如锯齿——边界细节的退化现象seg_loss一直下降验证集 IoU 也不差但放大推理结果岸线边缘呈明显锯齿有的地方还多出一圈 23 像素的假阳性水边。原因常见于三处叠加转换脚本里eps2.0甚至更大的轮廓简化过度原图标注就存在多边形抽稀训练时imgsz640下岸线的细节被下采样丢失。解决把转换脚本eps降到 1.0 重新生成标签训练用imgsz960或 1024推理时不直接输出 mask 而是做一次 3×3 形态学闭运算。前两个是治本最后一个是救急。水域不像车辆行人那样有刚硬边界水陆交界是连续过渡的标注权威性很难有一个像素级的 ground truth所以细节退化的容忍阈值要看下游如果用于面积统计锯齿带来的误差通常小于 1%可接受如果做红线执法那必须回到人工精修标注集。6. 效果真不真用水域掩码的 IoU、岸线误差和可视化做最后一道验证训练完成后不要只看验证集 mAP水域任务的分数漂亮不代表岸线位置准。我习惯了自家流程里的三道验证第一道是标准 COCO 评估第二道是单独算岸线边界误差第三道是把预测掩码叠到原图上肉眼抽查。第一道可以用 ultralytics 自带的验证命令跑yolo segment val \ modelruns/segment/train/weights/best.pt \ datawater_seg/classes.yaml \ iou0.5默认输出 mAP50 和 mAP50-95水域任务里我更看重 mAP50-95因为它对掩码重叠度更敏感能暴露“位置对了但边界差几像素”的问题。第二道岸线误差我单独写一个小脚本思路是分别提取预测掩码和真实掩码的边界像素计算边界点之间的平均最近距离import cv2 import numpy as np from scipy.ndimage import binary_erosion def boundary_distance(gt_mask, pred_mask): gt_edge gt_mask ~binary_erosion(gt_mask) pred_edge pred_mask ~binary_erosion(pred_mask) gt_pts np.argwhere(gt_edge) pred_pts np.argwhere(pred_edge) if len(gt_pts) 0 or len(pred_pts) 0: return None # 对预测边界每个点找最近的真实边界点距离 from scipy.spatial import cKDTree tree cKDTree(gt_pts) dist, _ tree.query(pred_pts) return dist.mean(), dist.max() mean_d, max_d boundary_distance(gt, pred) print(f岸线平均偏移 {mean_d:.2f} px最大偏移 {max_d:.2f} px)这段代码里binary_erosion让掩码向内收缩一圈原掩码减掉收缩结果就是边界像素然后用 KDTree 算预测边界到真实边界的最短距离。平均偏移小于 3 像素可以放心用于大量统计超过 10 像素说明岸线整体偏移通常不是训练问题而是训练标签本身岸线标粗了我遇到过一次是把掩码从 RLE 转多边形再转回 RLE 时连续两次重采样造成的系统性漂移重转标签后才恢复。第三道永远不能省把验证集里最好和最差的各挑十张图预测掩码半透明叠加到原图上盯着桥下阴影、干湿交界、泥沙地三处看。有一次我发现模型把岸上的灰色防渗膜全部识别成水mAP 却还很高因为验证集里防渗膜图太少统计数据被淹没了。从那次以后我每次训练完都会先干这一件事再决定是否要调节训练集或 loss 权重而不是直接买单 mAP 数字。希望这份水域实例分割数据集的拆解和训练流程能帮你在自己的水域任务里少走几步弯路下次再拿到一份 .zip先别急着解压按格式、真伪加密、标注口径、转换、参数、验证这六步走结果一般都不会太差。本文还有配套的精品资源点击获取