ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

家庭场景19类家具全景分割数据集:从txt标签到训练实战

家庭场景19类家具全景分割数据集:从txt标签到训练实战 简介面向家庭场景中家具识别的图像分割任务该全景分割数据集提供640×640分辨率的家庭室内图像覆盖床、椅子、橱柜、门、灯、地毯、桌子、窗户等19类常见家具可用于细粒度分割、实例分割或全景分割模型的训练与精度验证也适合作为室内场景理解的入门与进阶数据集。资源包共727个文件主体为362张JPG原始图像与363张PNG掩膜图图像与掩膜一一对应同时包含1个txt类别标签文件和1个Python可视化脚本压缩包整体仅14.74MB下载与解压速度快。数据已按训练集与验证集划分训练集包含309张原图及对应掩膜验证集包含53张原图及对应掩膜目录结构清晰可直接代入现有深度学习框架进行训练。运行附带的show脚本可在原图上叠加显示掩膜结果便于检查标注质量与模型输出txt标签文本中列出了所有类别的具体名称与编号可结合可视化代码实现样本筛选、类别统计与调试分析。已有147人学习下载适合从事目标检测、语义分割或室内智能化的开发者与研究者在实际项目中参考与复用。1. 家庭场景里的19类家具全景分割这套数据集能做什么、不能做什么做室内机器人导航、AR 家装或者智能家居监控的人迟早会撞上一个尴尬语义分割把客厅里两张沙发合成了同一坨红色机器人数沙发永远数错。换成全景分割之后每个像素除了要有类别还得带上“这是第几张沙发”的实例号——这正是家庭场景下 19 类家具全景分割图像数据集存在的理由。它把床、沙发、桌椅这些家具按 19 类逐像素标注并附可视化代码和 txt 标签文本适合做算法选型验证、模型训练和标注规范对齐。它解决的是“识别什么 区分第几个”两个问题。但它不是开箱即用的工业级数据包你得先读懂标签编码否则后面每一步都在翻车。2. 全景分割为什么比“抠图”难语义类别与实例编号的约定2.1 语义分割、实例分割与全景分割的边界先说概念上的差别。语义分割做逐像素分类整张图里所有沙发都归为“沙发”一类两张沙发没有任何区分实例分割只处理有名字的物体墙、地板这类背景不在讨论范围全景分割等于把两者合起来每个像素输出两个信息它属于哪一类以及它在那一类里是第几个个体。所以“全景”并不是“看得更远”而是“没有遗漏”背景区域也必须有类别前景区域还必须有实例编号。家庭场景是这个差异最明显的测试场。客厅里有沙发、茶几、电视柜也有地板、墙、地毯。墙和地板属于 stuff不需要区分个体沙发、椅子、餐桌都是 things每件东西都要有自己的 instance id。于是很多全景分割数据集里会用一种合并编码像素值 class_id * 1000 instance_id。比如 class_id3 是沙发instance_id1 的那件沙发在标签图里像素值就是 3001。这样拿到 txt 标签文本之后一步解码就能同时得到类别和实例不用维护两套文件。19 这个数量本身也体现出“家庭场景”的粒度。少于 15 类椅子、凳子、边几这些高频物体会被并到一起机器人执行“搬椅子”指令时无从区分多于 25 类标注成本翻倍小样本类别反而带崩整体指标。19 类是语义复杂度和标注代价之间的常见折中。真要落地你还会发现任意两张家庭图的家具摆放几乎不相同类别分布天然长尾。2.2 txt 标签文本的常见形态先判断再写解析器拿到了“txt标签文本”很多人第一时间写正则解析然后花一下午调 bug。我的习惯是先看三行原始数据再决定解析器怎么写。常见的格式有两种。第一种是每行一个物体的多边形标注class_id x1 y1 x2 y2 ...坐标通常是相对原图宽高的归一化值小数形式。一张图有多少个家具就有多少行墙这类 stuff 有时会单独一行用整张图的四个角表示。第二种是每行一个类别的 RLE 压缩串class_id 0:123 456:78这样的“起始游程:长度”对解码后填充出一个和原图等大的掩码图。实例号可能出现在另一个字段里也可能写在文件名里比如00012_3.txt表示第 3 个实例。判断方法很简单打印第一行。如果一行超过四个数字多半是多边形如果出现连续冒号和数字对多半是 RLE如果只有一列那多半是像素级语义标签之外的辅助信息。这一步决定了后面可视化脚本的解析分支做得越早越省钱。2.3 类别 id 从 0 还是 1 开始直接影响颜色表这是最容易翻车的一处。有的标注规范让 class_id 从 0 开始0 是背景有的让 0 保留给 void/ignored真正的家具从 1 开始还有的 19 类里混入了“屋顶”“墙面”这类 stuff导致类别编号和视觉印象对不上。正确做法是先找 classes.txt 或 label.txt一行行读成 list用行号作为 id 基准找不到就把标签文本里出现过的 class_id 去重后拉一个集合再人工对照原图确认一两张。不要急着在代码里写死一个 19 元字符串数组更不要在生成颜色表时让 0 号参与随机色——0 号最好是黑色这样与原图叠加时背景不会盖住画面。我一般会在这一步顺手生成一个类别像素占比表统计每个 class_id 的像素总数既验证解析器没有串列又给后面的样本均衡提供依据。床和沙发占掉一半像素而脚凳、花架可能只有 0.1%这个落差在训练时一定会回来惩罚你。2.4 模型输出端的两种表示决定你从哪里入手模型输出端也有两种常见表示理解它们才能看懂“标注好的数据集”到底喂给谁。第一种是“每类一个 mask”的 one-hot 语义输出配合一个额外的实例中心或嵌入分支做聚类Panoptic FPN 就是这路子第二种是“像素值直接作为标签”的整型索引图U-Net 加 softmax 就能跑简单很多。这套 19 类家具数据集如果给你的是合并编码后的整型标签图你可以直接用第二种方式起步如果给的是 txt 里的独立多边形那么先回答“实例 id 要不要进 loss”。我的建议是第一阶段只把 class_id 作为监督信号实例部分通过连通域后处理实现先看语义底子牢不牢。实例预测的收益在类别准确率低于 70% 时根本体现不出来还白白增加计算量。如果标签里给了独立的 instance 字段还有一个容易忽略的点训练和评估要对实例编码保持同一套映射否则 PQ 计算时会错乱。我的做法是把这个映射写成一个独立函数放在constants.py里训练、评估、可视化三个入口都 import 它不要在不同脚本里各写一份。类似这样的小约定能省掉很多“训练时好用、可视化时错位”的夜班。3. 把 txt 标签文本变成能看的图可视化代码与目录结构落地3.1 先摸清楚目录里到底有什么拿到“包含标注好的数据集、可视化代码、txt标签文本”的压缩包别急着拖进训练脚本。先做一次存档级检查确认目录完整。常见结构是这样的dataset/ ├── images/ # 原图jpg 或 png ├── labels_txt/ # 每张图对应的 txt 标签文本 ├── classes.txt # 类别名字列表一行一个 ├── visualize.py # 可视化代码 ├── train.txt # 训练集划分每行一个文件名 └── val.txt # 验证集划分这个结构不是死的。有的包会把划分写在 config 里有的会多一个 json 目录。我一般用一条 find 命令核对数量和命名对应关系find images -name *.jpg | wc -l再对比 labels_txt 里的文件数两个数不一致就直接找说明文档别指望代码能猜出来。命名对不上是最隐蔽的坑。image 叫scene_001.jpg标签叫1.txt中间隔着三个零。这种问题只有按文件主名做映射才躲得掉写代码时优先用Path.stem做字典 key不要用glob返回顺序。3.2 最小可视化脚本把多边形刷到原图上可视化代码通常是最先要动手改的。下面这份脚本按“多边形型 txt”来写兼容空格和逗号分隔够应付大多数情况。import numpy as np import cv2 from pathlib import Path def parse_txt_label(txt_path, img_w, img_h): 解析单张图的 txt 标签返回 [(class_id, polygon_ndarray)] results [] for line in txt_path.read_text().strip().splitlines(): parts line.replace(,, ).split() if len(parts) 3: continue class_id int(parts[0]) # 顶点坐标是归一化值两两成对读取并转成像素坐标 pts np.array(parts[1:], dtypefloat).reshape(-1, 2) pts[:, 0] * img_w pts[:, 1] * img_h results.append((class_id, pts.astype(np.int32))) return results # 类别数量从 classes.txt 读取避免写死 19 class_names Path(classes.txt).read_text().splitlines() num_classes len(class_names) # 颜色表固定随机种子保证多次运行结果一致 rng np.random.default_rng(42) color_map rng.randint(0, 255, (num_classes, 3), dtypenp.uint8) color_map[0] [0, 0, 0] # 0 号固定为背景/黑色 img_path Path(images/scene_001.jpg) img cv2.imread(str(img_path)) img_h, img_w img.shape[:2] mask np.zeros((img_h, img_w), dtypenp.int32) for class_id, pts in parse_txt_label(Path(labels_txt/scene_001.txt), img_w, img_h): cv2.fillPoly(mask, [pts], colorclass_id) # 区域填充 mask_vis color_map[mask] # 索引查色表 overlay cv2.addWeighted(img, 0.6, mask_vis, 0.4, 0) cv2.imwrite(vis_scene_001.jpg, np.hstack([img, overlay]))逻辑说明parse 函数先做归一化到像素坐标的转换可视化用查表代替逐类 else if省心且速度快图像叠加权重原图 0.6、mask 0.4能同时看清边缘和内部纹理。参数说明reshape(-1, 2)假设顶点按 x,y 顺序交替如果你的 txt 是 y,x 顺序把这两列换一下即可cv2.fillPoly接受整数坐标数组转 int32 是必要的color_map的随机种子固定为 42否则每次跑出来的颜色都不一样对比前后效果时会误判。3.3 可视化之后要做三件事第一件抽查类别编号和真实物体是否对应。沙发是不是真标成了沙发茶几有没有被标成餐桌这类错标在 txt 里根本看不出来只有叠图可见。第二件看边界质量。家具之间互相遮挡时标注边缘是干净切分还是锯齿状。全景分割对边界更敏感边缘不干净会让后续连通域分析崩掉。第三件统计像素占比直方图。把 mask 里每个 class_id 的像素数打印出来可能你会看到地毯、窗帘这类大面积 stuff 占了一半像素而边几只有几百像素。这个长尾分布直接决定你要不要做类别重采样也决定模型最后哪几类先崩。到这里可视化代码就不是“送的一个脚本”而是你检查数据质量的主工具。后面模型无论怎么调只要把预测结果也叠到同一套可视化代码上就能做最直观的 A/B 对比这一招在排障时比看 loss 曲线有用得多。3.4 如果标签是 RLE 压缩串可视化怎么写RLE 型 txt 的解析思路完全不同每行先拿到 class_id再解码“start:length”对填充一个连续行段。代码短但容易踩坑因为有些工具输出的游程基于行优先的展平坐标有些基于列优先。判断办法是“解码后矩阵的长宽是否与原图一致”不一致就先转置或 reshape 再验证。def decode_rle_line(line, h, w): parts line.split() class_id int(parts[0]) mask_flat np.zeros(h * w, dtypenp.uint8) for run in parts[1:]: start, length map(int, run.split(:)) mask_flat[start:start length] 1 return class_id, mask_flat.reshape(h, w)这段代码对“行优先展平”有效。用reshape(h, w)时如果画面是横竖颠倒的把 h、w 互换再验证一次基本能确定标注工具是按哪种顺序导出的。这类解析器写完之后一定要用一张带明显边框的测试图跑一遍不要直接上全量数据。4. 从 txt 到训练 pipeline格式转换、U-Net 与参数设置4.1 把多边形标注转成 COCO 风格 JSON如果你计划用 Mask R-CNN、实例分割或者“检测 裁剪分类”这类成熟框架第一步是把 txt 转成 COCO 2017 风格的 JSON。转换脚本的核心是把多边形坐标复制为闭合顶点并计算 bbox 和 area。import json from pathlib import Path def poly_to_coco(polygon_xy): # COCO 要求多边形闭合首尾点相同 closed polygon_xy.copy() closed.append(polygon_xy[0]) return [coord for xy in closed for coord in xy] def polygon_area(polygon_xy): # 鞋带公式计算面积 x [p[0] for p in polygon_xy] y [p[1] for p in polygon_xy] return 0.5 * abs(sum(x[i] * y[(i 1) % len(x)] - y[i] * x[(i 1) % len(x)] for i in range(len(x))))转换时要留意两点第一COCO 的图片 id 和标注 id 都要从 1 开始0 是背景类别所以 txt 里的 class_id 如果从 0 开始需要整体加 1第二area 不要用h * w代替鞋带公式算出来的才是真实像素面积很多采样策略依赖这个值。如果你之前跟着“yolov8 训练自己的数据集”的教程转过检测框那套 JSON 结构完全兼容只是把segmentation字段换成多边形顶点列表bbox用包围盒算出即可。这一节做对了后面套任何 COCO 生态的模型都不必再写新转换器。4.2 不进 COCO 也能训直接生成整型全景标签图自己做实验我更推荐一条更省事的路径不转 JSON直接把每张图的 txt 渲染成和原图同尺寸的整型索引图作为训练标签。import numpy as np import cv2 def render_panoptic_label(txt_path, img_w, img_h, instance_idNone): label np.zeros((img_h, img_w), dtypenp.int32) for line in txt_path.read_text().strip().splitlines(): parts line.split() class_id int(parts[0]) pts np.array(parts[1:], dtypefloat).reshape(-1, 2) pts[:, 0] * img_w pts[:, 1] * img_h # 把类别和实例合并编码进一个整型像素值 instance int(parts[0]) if instance_id is None else instance_id cv2.fillPoly(label, [pts.astype(np.int32)], colorclass_id * 1000 instance) return label这里把实例号暂时等于 class_id避免合并编码为 0 而和背景冲突。真正的实例 id 可以从文件名或标注附加字段读入训练时确定一个“实例编码基数”让 class_id * 1000 的方案保持恒定。参数说明这个 1000 不是固定值只要大于单类别最大实例数量即可如果你的场景里同一种家具可能超过 1000 件把基数调成 10000更稳妥。用 U-Net 这类编码器-解码器模型做图像分割时输入是三通道原图标签就是这张 int32 矩阵。注意 int32 矩阵不能直接进交叉熵需在 Dataset 里手工转成 int64 张量cross-entropy loss 的 ignore_index 设为 0把背景排除在统计之外。4.3 训练参数怎么给一组可复现的起点值我用表格给出实际训练时常用的一组起点参数后续按数据分布微调。参数推荐值说明num_classes19类别数不含独立背景类ignore_index0背景/void 不参与 lossinput_size512x512 或 640x480按原图宽高比选择优先短边 512batch_size8单卡 1080Ti 附近的可接受值learning_rate1e-4Adam/AdamW配 cosine 衰减lossCE Dice缓解长尾类别见不到梯度参数说明input_size 不要直接缩到 256很多小家具缩到 256 时只剩几个像素边界标注直接失真batch_size 受限于显存如果放不下 8优先降输入尺寸而不是降 batch学习率 1e-3 会让前 500 步震荡明显家具小目标多收敛慢一点更稳。epoch 数建议 60 到 100每隔 5 个 epoch 保存一次权重。训练中断时可以直接加载上一次权重继续跑。家庭场景的标注噪声比自动驾驶数据集小但长尾更重早期 mIoU 波动也更大跑完整个周期再看 PQ 比较公平。在类别严重失衡时可以给每个样本按 class_id 的像素占比计算权重做法是在 Dataset 的__getitem__里按权重抽样或者用 WeightedRandomSampler。计算权重时以“每个类别像素数”为准不要以“每个类别出现次数”为准因为一次出现可能占半张图后者会严重放大高频大物体。4.4 评估指标PQ 才是全景分割的主场语义分割看 mIoU全景分割看 PQPanoptic Quality。PQ 由两部分相乘分割质量 SQ 是对匹配实例的 IoU 均值识别质量 RQ 是 F1 式的匹配比率。最终公式大致是“匹配实例的 IoU 求和除以 0.5 (预测实例数 真值实例数)”未匹配的实例直接算错。这个指标对 19 类家庭场景很残酷。床和沙发这类大物体容易拿分边几、花架这类小物体一旦没检测出来RQ 立刻掉一截。所以训练时盯着 PQ 不等于盯着 mIoU后者对小类别几乎无感。我习惯同时打印每一类的 PQ低于阈值比如 5%的类别单独跑可视化看是漏检还是边界错。5. 家具全景分割避坑实录5 个踩过的坑与排查方法5.1 可视化一片黑或满屏乱色class_id 从 0 还是 1 开始现象跑完可视化脚本输出图上要么全黑要么所有物体都同一颜色根本分不出家具。原因最常见是 txt 里第一列不是 class_id而是 instance_id其次是 class_id 以 0 开头0 被预留成 void而你的颜色表恰好把 0 设为黑色于是所有像素套进 0 号颜色。还有一种可能是 class_id 从 1 开始但可视化颜色表只有 19 行索引越界后被 OpenCV 静默截断。解决打印前三条 txt 行人工对照 classes.txt 确认列含义在解析函数里加一行断言assert max_class_id len(color_map)越界立即报错而不是画出烂图。再不行就找样本里最明显的那张床数一数它对应的 class_id 和真实类别做一次反向映射。5.2 mask 画在图上位置怪坐标基准不是原图尺寸现象多边形能画出来但位置和原图对不上明显往左或往下偏移有的甚至镜像。原因标注工具生成的 txt 里坐标归一化的基准可能不是原图尺寸而是“去除黑边后的有效区域”或者标注时用了正方形画布导出时又按宽高比裁切导致 x、y 的缩放系数不一致。解决先用一张只有单独沙发的简单图做“探针”把 txt 里的原始坐标打印出来人工验算一遍缩放公式。若所有点的 y 都偏大检查是不是把 h 当成了 1.0 的基准若整体镜像检查索引排列是行优先还是列优先。这类问题修一次就够了但如果不做探针测试会误判成模型问题白白调两周参数。5.3 小家具类别在 loss 里消失长尾分布现象训练到后期整体 PQ 长得还行但边几、花架、床头柜的类别 IoU 一直在个位数徘徊甚至不涨。原因床、沙发、地毯这类大面积类别占据了 70% 以上的像素交叉熵对它们过分友好。模型发现“把所有像素预测成大类别”就能拿到很低的 loss小类别直接学不到特征梯度被大物体覆盖。解决给每个类别算一份像素占比表做类别加权 softmax或者对样本做按类别频率的过采样让包含小家具的图片在训练中出现更频繁。更直接的做法是把 loss 换成Dice Loss CrossEntropy的组合Dice 对类别不平衡天然不敏感对小目标边界也更宽容。加权重之后记得回看验证集确认大物体没有明显掉点。5.4 同一件家具被拆成两个实例连通域断裂现象可视化验证时看到明明是一张餐桌却被分成了左右两半各有一个独立 instance id反过来两把挨在一起的椅子共用一个 id。原因标注工具在画多边形时如果跨了遮挡边界或者一次交互没有点完整导出脚本按“单个多边形等于一个实例”直接生成 id不做连通域合并。家庭场景里家具互相遮挡非常多这个问题几乎每个数据集都有。解决写一个预处理脚本把同一 class_id 的掩码做一次连通域分析。使用 OpenCV 的connectedComponentsWithStats把面积小于阈值的碎片合并到距离最近的同类别连通域里再重排 instance id。阈值一般取整张图像素数的 0.01%具体值按原图分辨率调整。这个步骤要在训练之前完成不要指望模型自己学出来。5.5 图像和标签对不上划分列表里的命名差异现象训练时 loss 正常但验证时频繁报FileNotFoundError仔细看是 txt 文件名和图片名差了前缀或后缀。原因划分列表 train.txt/val.txt 通常由标注平台导出里面的图片名可能是scene_001.jpg而 labels_txt 目录里存的是scene_001_gt.txt中间加了个_gt后缀反过来也常见。解决以图片文件主名为基准建映射表标签路径用image_stem 可能的后缀拼接。具体做法是在加载函数里先检查直连路径再尝试带后缀和带前缀的候选路径命中后锁定。不要在划分文件里直接写标签路径那相当于把格式问题埋进了数据加载器。6. 进阶用可视化回归测试检验每一次模型改动模型训练刚开始可以直接盯着 PQ 数字但跑过三轮之后就会发现数字涨了未必是好事指标跌了也未必是模型的问题。我自己最习惯的一件事是固定 10 到 20 张验证图训练前先跑一遍预测并把标签存成 npy作为回归基准之后每次改动数据处理或模型结构都重新跑一遍这批图对比新预测和基准之间的差异图。差异图不是简单的像素相减。我会把结果分成三类着色漏检真值是家具但预测成背景、误检预测成家具但真值没有和边界误差类别一致但边缘偏移超过 2 像素。这三类用不同颜色叠加在原图上一眼就能看出这次改动到底伤了哪一类。比只看 PQ 加权值可靠得多。另一个对我很有效的技巧是把实例掩码按面积排序后重新分配颜色面积最大的沙发用最亮的颜色角落里的花架用冷色。这样渲染出的全景图会自然形成“主次分明”的视觉层级小实例一旦消失人眼能立刻发现——而等你的模型产出来你也能第一时间发现问题。有一次我改了 resize 策略把短边从 512 提到 640全图 PQ 涨了 0.3但差异图里所有小桌子的边缘都在塌。后来查明是上采样时插值方式变了小物体边界被抹平。如果只盯着 PQ这个问题可能一直藏到部署才发现。所以我的教训是每次改动后先跑回归图再谈指标。希望帮到你。本文还有配套的精品资源点击获取
返回列表