
简介一套面向中医舌诊研究的高质量图像数据集以Labelme标注工具生成的JSON标签文件为核心对应512×512高分辨率舌苔原图数据涵盖黑色舌苔、地图舌、白厚腻苔、红舌黄腻苔、紫色舌苔等多种常见类型。压缩包共2000个文件全部为JSON标注文件整体大小约206MB文件命名带有清晰的前缀标识方便按类别筛选与批次加载。该数据集适合Python开发者、深度学习者及医疗影像研究人员使用可直接用于图像分类、特征分析、语义分割等实验。标签字段详细记录了舌苔的颜色、分布、质地、厚薄等信息能为有监督模型提供可靠标注。结合PyTorch或TensorFlow等框架可快速构建卷积神经网络开展数据增强、归一化、交叉验证和超参数调优等工作。已有2453人学习下载这套数据在中医舌诊数字化、自动化分析以及教学科研场景中具有较高的参考价值是训练多分类模型、验证算法性能的理想基础资源。1. 舌诊AI落地的第一道坎先说清楚这份数据到底解决了什么问题最近看到不少做医学影像、智能诊断方向的朋友在找舌诊相关的数据集多数人在公开渠道翻来翻去要么找到的是几十张样本的demo级数据要么是图片带了个简单的分类csv根本喂不动模型。所以当听说有一份“两千多张图片、512x512分辨率、原图labelme标签齐全”的舌苔数据集时我的第一反应是这玩意儿确实是刚需。舌诊是中医里特别有代表性的“望闻问切”环节舌头表面的颜色、舌苔厚度、湿润度、裂纹、齿痕、舌下络脉这些特征是判断身体状况的重要依据。但要把这套经验转成计算机能理解的信号第一步就是要有大量、标准化、标注可靠的图像数据。深度学习模型在医学图像上能不能work很大程度取决于训练数据“干不干净”——这里说的干净不光是图像本身清晰还包括标注边界是否精确、类别是否统一、标签文件是否和原图严格对应。如果这些基础没打好后面无论用yolov8、nnunet还是mmdetection训练出来的模型都像是在流沙上盖楼跑通容易落地就崩。这份数据集的定位很明确它不是什么几十万张的海量数据而是按“能直接开训”的标准来准备的。两千多张512x512分辨率原图和labelme标注的JSON标签一一对应文件结构清晰不附带多余的花活。对于做舌诊目标检测、舌体分割、舌苔区域提取这类任务的朋友来说这个规模恰好处于“够用又不至于让人无从下手”的区间。做迁移学习、跑基线对比、验证算法思路或者写论文做消融实验这个量级的数据往往比动辄几万张的大数据集更省心因为你可以把每一张的标注情况都检查一遍心里有底。项目正文是空的但标题已经把这套数据最关键的三条命门点出来了数据量(两千多张)、分辨率(512x512)、标注形式(原图labelme标签)。接下来我根据自己的使用经验把这份数据集从目录结构、标签格式、实际训练适配到踩坑反射全部拆开讲透。2. 这两千多张图的底细目录形态、图像规格与样本构成推断第一步先别急着谈算法拿到任何数据集先做“查户口”。所谓查户口就是把数据的物理形态摸清楚文件放在哪里、图片是什么格式、尺寸是否统一、标注文件叫什么、命名有没有规律。这套流程看起来基础但至少能避免后面三种典型的尴尬读到一半发现图片损坏、标签和图片对不上号、跑训练时被乱七八糟的命名搞乱队列。2.1 一张图配一个JSON目录结构基本盘按照labelme这类工具的默认输出习惯这份数据集应该遵循“一图一JSON”的配对规则。原图放在images目录或直接散落在根目录同名的.json文件放在labels目录图片叫什么名字标签就叫什么名字。比如一条样本就是tongue_001.jpg tongue_001.json tongue_002.jpg tongue_002.json ...这种命名规则的直接好处是在写数据加载代码时根本不用维护任何映射文件只要遍历某个目录把扩展名从.jpg替换成.json就能找到对应标签。如果你打算把这份数据转成COCO或YOLO格式这个规则也能帮你省掉大量对齐时间。2.2 512x512的真实含义是硬性裁剪还是统一缩放标题里明确写了512x512这个信息很关键。舌图像不像自然场景里的物体拍摄时舌头在画面中的占比、角度、光照都会有变化原图很可能来自不同设备、不同距离。统一成512x512有两种常见做法直接resize拉伸。这种做法速度快、代码简单但会把舌头的长宽比例搞变形尤其是舌形偏长的人硬拉成1:1会让舌体轮廓失真标注框和分割掩码也会跟着偏移。等比例缩放后居中填充padding。这种做法保留了舌形比例但会引入黑边或白边训练时如果没做预处理这些填充区域可能被模型当成背景特征学到影响分割精度。如果你打算用这份数据训yolov8做目标检测512x512是不需要额外处理的yolov8自己会在训练时做letterbox自适应缩放。但如果要做像素级分割比如nnunet建议先随机抽几张图手动比对JSON里的多边形坐标和原图边缘确认整个数据集是不是真的拍平到了512x512还是说只是标注文件被resize了原图尺寸其实没变。这个坑我踩过一次原图是768x1024标签是512的比例模型训完画出来mask全是偏移的排查了半天才发现是图像和标签的尺寸基准不一致白白浪费了两天。2.3 样本构成推测类别分布与常见舌象偏斜两千多张看似不少但具体任务不同实际有效样本量差别很大。如果是做整舌分割那每一张图都是有效样本如果做的是“舌质颜色分类舌苔厚度评估”那就要看类别分布是否均衡。中医舌诊的常见观察维度包括舌色淡白、淡红、红、绛、紫、苔色白、黄、灰黑、苔质薄、厚、腻、燥、润、舌形胖大、瘦薄、裂纹、齿痕、舌下络脉等。以我的经验这类数据集经常出现类别偏斜——比如正常淡红舌占了50%而绛舌、青紫舌这类病理性舌象可能只有几十张。因为采集场景大多是体检中心或门诊亚健康人群的舌象本身就偏向“有点问题但不严重”的区间极端舌象本来就少。拿到数据后我建议先别急着训模型写个脚本把labelme JSON里每个shape的label字段统计一遍看看每个类别的样本量。如果某些类别少于50张后续训练时要么考虑类别加权要么做数据增强颜色扰动、翻转、小角度旋转要么就老老实实focal loss。当前官方没有给出详细注释所以我只能说这是大概率存在的偏斜风险但无论如何先跑一下统计脚本总没坏处。3. labelme标签拆透JSON结构、区域类别与几个隐藏细节labelme是计算机视觉领域最常用的图像标注工具之一它输出的JSON文件和LabelImg输出XML不太一样。labelme主打多边形标注适合做分割数据labelme也可以画矩形框用来做检测数据。这套数据集既然叫“labelme打好的标签”那大概率包含的是多边形polygon标注——也就是用于语义分割或实例分割任务的标签。3.1 labelme JSON的标准字段长什么样一个典型的labelme JSON文件打开之后大概是这样{ version: 5.2.0.post4, flags: {}, shapes: [ { label: tongue_coating, points: [[123, 45], [156, 78], ...], group_id: null, shape_type: polygon, flags: {} }, { label: tongue_body, points: [[20, 30], [50, 90], ...], group_id: null, shape_type: polygon, flags: {} } ], imagePath: tongue_001.jpg, imageData: null, imageHeight: 512, imageWidth: 512 }我把每个字段的用途和坑点都列一下字段含义注意事项versionlabelme版本号不同版本间JSON结构微调不影响读取flags图像级标签多半是空的shapes标注对象列表核心每个元素是一个区域label区域类别名必须检查拼写是否统一points多边形顶点坐标注意坐标系是(x, y)还是(row, col)shape_type标注类型polygon、rectangle、circle等imagePath原始图片相对路径移动目录后要检查imageData嵌入的base64图片有些版本会写图片进去文件巨大imageHeight/imageWidth图片原始高度宽度用于校验3.2 舌象标注的类别设计该有哪几层基于舌诊业务的实际需求好的标注一般不会只标一个“舌头”就完事。更合理的做法是分区域、分属性整舌区域tongue用于全舌分割做舌体定位和粗分割。舌质区域tongue_body对应舌体本身一般排除了舌苔覆盖比较厚的区域。舌苔区域tongue_coating苔色和白苔覆盖的部分与舌质区域有重叠。舌尖/舌中/舌根/舌边如果想做分区分析比如舌尖红、舌边齿痕这个层级就很有用。舌下络脉sublingual_vein如果把舌头卷起来拍的图可以标这一步。由于官方只给了标题没给完整的类别清单我无法确认这套数据里具体包含哪些类别。但按labelme这种工具的逻辑不管标几类每个对象的label字段都是纯文本字符串所以你拿到数据后第一件事就是跑一遍统计脚本import json, glob label_counter {} for json_path in glob.glob(labels/*.json): with open(json_path, r) as f: data json.load(f) for shape in data[shapes]: label shape[label] label_counter[label] label_counter.get(label, 0) 1 print(label_counter)这个脚本不仅能在30秒内告诉你数据集的类别分布还能帮助你发现“tongue_body”和“tongue body”这种带空格和不带空格同时存在的情况——如果标注时有人手滑这种问题很常见。如果两类实际上是同一类建议统一替换否则训练时模型会为了这几个错误样本白白消耗参数。3.3 imageData为null还是非null很多从labelme导出的JSON文件里会有一个imageData字段里面存的是整张图片的base64编码。如果这个字段被填满了一个JSON文件会变得很大2MB的图片可能变成2.7MB的base64字符串。有人不擅长处理这种大文件直接读了JSON字符串再去解码导致训练数据加载很慢。我的建议是拿到数据后先检查JSON大小。如果每个JSON超过3MB大概率是嵌入了图片数据。写数据加载器时直接在JSON里找imagePath字段去读原图即可把imageData标为null或者忽略掉没必要在内存里多存一份冗余图片。反过来如果imageData确实是null就正常通过路径读图不要傻乎乎去解码一个不存在的字段。4. 数据质检流程在训练之前先把烂数据揪出来数据集的“干净”程度直接决定你的模型上界。哪怕只花一下午做质检也好过训练完才发现问题再来回折腾强。labelme标注的JSON再规范也不能保证图片与标签的内容完全匹配、边界不扭曲更别说可能混入标注时手滑画错、类别标反、坐标越界等实际问题。我的习惯是每次拿到新数据集先跑一套质检四板斧图像完整性、标签可解析性、坐标合法性、语义合理性。4.1 图像完整性检查有图无签、有签无图按“一图一JSON”的命名规则先做一轮名字集合比对。写一段简单的脚本找出两侧文件名不一致的样本。最常见的三种情况是某张jpg没有对应json这种样本要么是采集后忘了标要么是标注后又删了。某份json没有对应jpg说明图片被误删或移动了。同名但JSON里imagePath指向的路径已经不存在了。处理策略很简单优先把不完整的样本移到一个suspect/目录训练集里只保留严格配对的样本。至于那些“可能有标注但图片丢失”的样本建议先放着不要为了凑数强行保留。4.2 多边形坐标与图像尺寸的绝对关系labelme的points坐标有一个特性坐标原点在图像左上角x轴向右y轴向下。如果图片尺寸是512x512那所有坐标理论上应该在[0, 512)之间。但现实中有人会把标注完的JSON从大图迁移到小图或反向操作导致坐标越界。检测方法简单粗暴import json with open(labels/tongue_001.json) as f: data json.load(f) w, h data[imageWidth], data[imageHeight] for shape in data[shapes]: for x, y in shape[points]: if x 0 or y 0 or x w or y h: print(越界坐标, shape[label], x, y)如果发现越界注意不是直接把坐标裁到边界就完事——那会让多边形变形、面积失真。更合理的做法是回到原图重新标注或者直接删掉这条样本。如果整批数据都越界且越界量一致可能是某次resize操作没同步坐标基准可能需要检测一下是否有统一缩放因子再批量修正。但这类修正要非常小心我并不建议初学者直接凭代码修坐标因为一旦方向错了可能把本来没问题的样本也搞坏。4.3 可视化抽检用图像叠加多边形看标注合理性数值检查只能发现“格式问题”发现不了“语义问题”。漏标、错标、边界画得歪七扭八这些只有人眼能判断。如果你有好用的工具可以直接用labelme打开原图和JSON来检查但一次打开2000多张太费劲。更高效的办法是用OpenCV批量生成带标注叠加的可视化图像把5%的图打印出来翻一遍import json, cv2, numpy as np def draw_labelme_annotation(image_path, json_path, save_path): img cv2.imread(image_path) with open(json_path) as f: data json.load(f) for shape in data[shapes]: pts np.array(shape[points], dtypenp.int32) label shape[label] cv2.polylines(img, [pts], isClosedTrue, color(0, 255, 0), thickness2) cv2.putText(img, label, tuple(pts[0]), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) cv2.imwrite(save_path, img)抽样个一两百张重点看三件事第一是否每张图上该标的结构都标全了第二有没有把舌苔和舌质的边界画串了第三有没有把舌头轮廓画得超出实际语义边界、把嘴唇或者牙齿也圈进去了。这些都是多标注人员协作时最容易出现的低级错误。4.4 labelme自带“验证”功能不检查JSON的flags字段很多人在labelme里标注时会顺手勾选一些图像级标签比如difficult、occluded这些信息会落在JSON根级的flags字段里。如果这份数据集里flags字段不为空那这些信息可能对训练过滤有帮助。举个例子如果有几十张图被标记为blur那这类图像在训练时可能需要单独处理或直接剔除。但目前基于标题信息我预计大多数flags是空的毕竟标题没有提到这部分那就当它不存在不用过度解读。5. 从标注到训练yolov8和nnunet两条路线的适配方法如果你拿到这份数据集只是为了写报告、做demo那看完上面几节就已经够了。但真正要用它训出自己的模型还得再走一步把labelme格式转成目标框架需要的格式。yolov8和nnunet是目前最主流的两个方向正好也和热点词里的内容对得上。5.1 yolov8目标检测路线从多边形到矩形框yolov8默认使用的标注格式是class_id center_x center_y width height全部归一化到0~1范围。labelme的JSON里存的是多边形需要先把多边形转成外接矩形框bounding box。这一步很容易出现两种坏结果多边形本身是斜的、成角度的外接矩形会把很多无关背景包进去影响检测精度。如果标注区域是舌苔、舌质这种内部有镂空或不规则形状直接取外接矩形会导致大量背景噪声。所以在转换前先想清楚你的目标是“检测整个舌头”还是“检测细节区域”。如果只是检测舌头在整个图像中的位置那整舌多边形的最小外接矩形基本可用。如果要检测舌苔这类精细结构且矩形框带来的背景干扰太大建议改成分割路线不要硬套检测模型。yolo所需的标注目录组织方式dataset_yolo/ ├── images/ │ ├── train/ │ │ ├── tongue_001.jpg │ │ └── ... │ └── val/ │ ├── tongue_101.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── tongue_001.txt │ │ └── ... │ └── val/ │ ├── tongue_101.txt │ └── ... └── data.yamldata.yaml示例train: dataset_yolo/images/train val: dataset_yolo/images/val nc: 3 names: [tongue_body, tongue_coating, tongue]如果把每个JSON的多边形按类别转成矩形一行txt就是一个类别一个框。转换脚本需要用到Python的shapely库把多边形转成最小外接矩形minimum rotated rectangle或轴对齐矩形。但最少边界矩形和平行于坐标轴的矩形不一样yolo只支持轴对齐矩形。所以遇到倾斜舌头时要权衡一下用大矩形包住整个区域还是切成上下几段分别检测。讲实话这类问题需要根据实际业务目标来做判断没有什么放之四海而皆准的标准答案。5.2 nnunet分割路线保留多边形原样如果要做语义分割把多边形转成掩码mask是核心。nnunet不直接吃labelme的JSON它需要的是以图片名命名的mask PNG每个像素的灰度值等于类别ID。例如0是背景1是舌体2是舌苔。转换基本流程读原图创建一张全0的单通道mask。读JSON对每个shape用cv2.fillPoly()把该多边形内部填成对应的类别ID。如果有重叠区域后画的会覆盖先画的所以要设定好优先级比如舌苔区域在舌质区域之上。保存为PNG不要用JPG存maskJPG有损压缩会改变像素值。nnunet对数据格式的要求非常严格尤其是类别标签的连续性和数值边界如果掩码里出现了一个不在预设列表里的灰值nnunet会在预处理阶段报错或者训练时无限崩溃。所以转换完mask后先跑一个像素值统计import cv2 mask cv2.imread(mask.png, cv2.IMREAD_GRAYSCALE) unique_vals set(mask.flatten().tolist()) print(unique_vals)确保里面只出现0、1、2……这类连续ID而不出现255或3这样的杂值。5.3 数据集划分别把同一个病人的舌头同时放进训练集和验证集医学数据有一个特别容易犯的错一张舌头正面照和一张舌头侧卷照可能来自同一个人但采集时没记录身份ID于是随机划分训练集和验证集时这两张可能被分到了两边。模型在验证时“看到了”和训练集极端相似但不完全一样的舌头纹理指标会虚高真正部署到新病人身上时性能立刻缩水。理想情况是按受试者划分但这份数据集标题里没提到身份信息所以我推测大概率是按图像文件随机划分。如果无法按照人来划分至少要保证划分时按“采集时间”或“图片编号前缀”做分组避免同一批次连续拍摄的照片被拆开。假如你只知道文件名而完全没其他元信息那就老老实实按文件名排序后每隔9张取1张做val模拟一定的分布外验证。5.4 多任务多头是否可以同时出检测和分割舌诊分析很多时候不只需要一个功能。你可能既要画舌头轮廓分割又要知道舌苔面积占比分割精度还想判断是否存在齿痕检测。如果这份数据集的标注足够细致你也可以试试多任务架构比如一个模型同时输出分割掩码和检测框。这种做法比各自训练强一些因为共享了底层特征提取器但需要标签和任务高度统一且代码复杂度高出不少。如果你之前没玩过多任务模型架构第一次尝试容易调参调到怀疑人生。老老实实先训一个纯分割或纯检测模型是更稳的选择。6. 医学图像标注的边界问题我在处理类似数据时踩过的坑文章最后这节我想多聊几句相对软性但很关键的内容。舌苔数据集不是普通的自然图像数据集它背后有严格的医学场景约束。我在这里不讨论诊断效果也不给任何临床建议只从数据处理和模型训练的角度把需要注意的事说清楚。6.1 隐私和合规舌头照片也算个人信息拍照时舌头往往连带脸的下半部和嘴唇一起入镜这在很多国家都属于个人敏感信息不能随意散布。如果你是从公开渠道获取的这份数据集作者大概率已经做了匿名化处理但这不等于你拿到数据后就没有责任。我的习惯是本地训练时不开公网传输模型发布时把测试集里可能露脸的图像裁掉脸部区域再展示做demo时也给舌头区域打个马赛克。这个习惯应该适用于所有医学图像不仅仅是舌诊。6.2 样本标签的“金标准”问题舌诊的中医辨证结果受标注者的经验影响很大。同一个舌头可能两位老中医给出的苔质判断就不一样。所以在用这份数据集训练模型时如果模型的某些预测和常识不符别急着怀疑代码也可能是标注本身有主观性。建议在训练完成后挑出那些多次预测不一致的样本找有经验的同事或专业人士复核一遍发现是标注错就改发现模型错就调这种“人机协同”的循环迭代能反过来帮你提升数据集质量。6.3 从原型到产品模型输出不能替代专业诊断最后这点我必须反复强调这个数据集训练的模型其输出应该被定位为辅助分析、教学演示、学术研究工具而不是直接的诊断结论。不要把模型输出的“异常舌象概率”直接告诉用户然后就完了。做产品的话好的思路是让模型输出多个结构化的观察项比如舌色、苔色、苔厚、齿痕有无再由医学专业人员进行解释。这个定位既符合技术现状也符合医疗责任的基本要求。我在实际项目中也是坚持把模型的输出限定在“描述性特征”层面把“是否生病”“需不需要吃药”这类判断留给人。最后再顺手分享一个数据处理习惯无论你用这份数据做什么强烈建议建一个dataset_stats/目录把每次统计出来的类别分布、样本量、图像尺寸分布、标注点数分布、坐标越界情况都存成CSV或JSON按日期归档。这样过两三周再回头看数据时能直接查出当时用的是哪个版本的数据、做了哪些清洗而不是凭记忆猜来猜去。数据集的坑很多不是当下炸出来的而是等你换了模型、换了任务之后才冒出来——到那时候历史统计记录就是你的救命稻草。本文还有配套的精品资源点击获取