ARTICLE DETAIL

资讯详情

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

室内家具与人目标检测数据集实战:从格式转换到训练调参的完整避坑指南

室内家具与人目标检测数据集实战:从格式转换到训练调参的完整避坑指南 简介这份室内家具与人目标检测数据集面向计算机视觉开发者、机器人导航研究者及高校师生用于训练和评估室内场景下的目标检测模型。数据覆盖长椅、椅子、沙发、餐桌、笔记本电脑、人共6个类别全部采用YOLO格式精确标注边界框与类别标签可直接接入主流检测框架。压缩包共1700个文件含849张JPEG图像、849个对应txt标注、1个yaml配置文件与1份docx说明文档整体约56.17MB训练集594张、验证集170张、测试集85张划分合理支持从训练到性能评估的完整流程。目前已有34人学习。读者可获得开箱即用的标注数据与配置说明用于智能家居、安防监控、机器人交互及零售行为分析等场景的算法开发与教学实践也可作为目标检测与实例分割任务的基准数据快速验证模型效果并复现实验。1. 拿到「室内家具与人目标检测数据集」先别急着训练三个反直觉的真相你下载完室内家具与人目标检测数据集_20251116_143557.zip解压看到一堆图片和标注文件第一反应大概率是直接丢进 YOLO 跑一版 baseline。我劝你先停十分钟。这个数据集的名字里藏着两个关键信息「室内家具」和「人」意味着它是一个室内场景下的多类别共存检测任务家具之间遮挡严重、人和家具尺度差异巨大、光照条件极不稳定。我见过太多人拿这类数据集直接开训结果 mAP 卡在 0.3 上不去回头查半天发现是标注格式没对齐、类别映射写错了、或者验证集里混进了训练集的近邻帧。这篇文章要解决的问题很具体给你一份室内家具人混合标注的目标检测数据集怎么从零把它跑通、调好、验证到位。适合两类人——刚拿到数据集想做第一个可用模型的新手以及已经跑过几个检测项目、但在这类室内复杂场景上反复翻车的熟手。我会把数据检查、格式转换、训练参数、评估口径、以及那些只有踩过才知道的坑一条条拆开讲。你不需要提前知道这个 zip 里具体有多少张图、什么格式因为下面给的是一套拿到任何同类数据集都能套用的排查和落地流程。2. 先搞清楚标注长什么样室内家具与人数据集的格式排查与统一2.1 三种常见标注格式的识别方法室内检测数据集最常见的标注格式有三种Pascal VOC 的 XML、COCO 的 JSON、以及 YOLO 的 TXT。你解压后第一件事不是看图片是看标注目录的结构。如果看到Annotations/和JPEGImages/两个文件夹基本是 VOC如果只有一个annotations.json或instances_train.json那是 COCO如果图片旁边跟着同名.txt每行是class x_center y_center w h那是 YOLO。用下面这段脚本一次性把目录结构和标注格式摸清楚import os, json, glob from collections import Counter root 室内家具与人目标检测数据集 # 改成你解压后的实际目录 # 1. 看目录树前两层 for dirpath, dirnames, filenames in os.walk(root): depth dirpath.replace(root, ).count(os.sep) if depth 1: continue print(f[DIR] {dirpath} 子目录:{dirnames[:5]} 文件数:{len(filenames)}) # 2. 探测标注格式 xml_files glob.glob(os.path.join(root, **, *.xml), recursiveTrue) json_files glob.glob(os.path.join(root, **, *.json), recursiveTrue) txt_files glob.glob(os.path.join(root, **, *.txt), recursiveTrue) print(f\nXML:{len(xml_files)} JSON:{len(json_files)} TXT:{len(txt_files)}) # 3. 如果是 COCO JSON打印类别名和图片数 if json_files: with open(json_files[0], r, encodingutf-8) as f: data json.load(f) if categories in data: print(\n类别列表:) for c in data[categories]: print(f id{c[id]} name{c[name]}) print(f图片总数: {len(data.get(images, []))}) print(f标注总数: {len(data.get(annotations, []))})这段脚本的逻辑很直白先看目录层级判断数据集组织方式再统计三种标注文件的数量最后如果是 COCO 格式就直接把类别名和数量打出来。关键参数说明depth 1控制目录树打印深度避免输出爆炸recursiveTrue保证子目录也能扫到。跑完你就能确定这个数据集到底是哪种格式以及类别名有没有拼写不一致的问题——我遇到过sofa和Sofa同时存在的数据集直接导致类别数翻倍。2.2 类别分布统计为什么「人」这个类总是被家具淹没室内场景里椅子和沙发的标注框数量往往是「人」的好几倍。这不是数据集做错了是场景本身决定的——一间客厅可能只有一个人但有六把椅子、一张沙发、两张茶几。如果你直接训练模型会偏向预测家具类人的召回率上不去。先统计每个类的实例数做到心里有数import json from collections import Counter with open(annotations.json, r, encodingutf-8) as f: data json.load(f) cat_map {c[id]: c[name] for c in data[categories]} counter Counter(ann[category_id] for ann in data[annotations]) print(类别实例数分布:) for cid, name in sorted(cat_map.items(), keylambda x: -counter[x[0]]): print(f {name:15s} {counter[cid]:6d}) # 计算不平衡比 counts [counter[cid] for cid in cat_map] print(f\n最多类/最少类 {max(counts)/max(min(counts),1):.1f})如果最多类和最少类差 10 倍以上你就需要在训练时考虑类别权重或者对少数类做过采样。参数说明counter统计的是标注框数量而非图片数量这更准确反映训练时的梯度贡献。常见做法是给少数类设置cls_weight或者在数据加载时对包含少数类的图片提高采样概率。2.3 把 VOC 或 COCO 统一转成 YOLO 格式的完整脚本不管你最终用哪个框架YOLO 格式的 TXT 是最通用的中间格式。下面这个转换脚本同时支持 VOC XML 和 COCO JSON 两种输入import os, json, glob import xml.etree.ElementTree as ET from PIL import Image def voc_to_yolo(xml_path, img_dir, out_dir, class_list): tree ET.parse(xml_path) root tree.getroot() img_name root.find(filename).text img_path os.path.join(img_dir, img_name) w, h Image.open(img_path).size lines [] for obj in root.findall(object): cls_name obj.find(name).text.strip() if cls_name not in class_list: class_list.append(cls_name) cls_id class_list.index(cls_name) bbox obj.find(bndbox) x1 float(bbox.find(xmin).text) y1 float(bbox.find(ymin).text) x2 float(bbox.find(xmax).text) y2 float(bbox.find(ymax).text) # 转成归一化中心点宽高 cx (x1 x2) / 2 / w cy (y1 y2) / 2 / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) out_path os.path.join(out_dir, img_name.replace(.jpg, .txt)) with open(out_path, w) as f: f.write(\n.join(lines)) def coco_to_yolo(json_path, img_dir, out_dir): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_info {img[id]: img for img in data[images]} cat_list [c[name] for c in sorted(data[categories], keylambda x: x[id])] cat_id2idx {c[id]: i for i, c in enumerate(sorted(data[categories], keylambda x: x[id]))} grouped {} for ann in data[annotations]: grouped.setdefault(ann[image_id], []).append(ann) for img_id, anns in grouped.items(): info img_info[img_id] w, h info[width], info[height] lines [] for ann in anns: x, y, bw, bh ann[bbox] # COCO 是左上角宽高 cx (x bw / 2) / w cy (y bh / 2) / h lines.append(f{cat_id2idx[ann[category_id]]} {cx:.6f} {cy:.6f} {bw/w:.6f} {bh/h:.6f}) out_path os.path.join(out_dir, info[file_name].replace(.jpg, .txt)) with open(out_path, w) as f: f.write(\n.join(lines)) return cat_list # 使用示例 os.makedirs(labels, exist_okTrue) classes coco_to_yolo(annotations.json, images, labels) print(类别顺序:, classes)逻辑说明VOC 的坐标是绝对像素值COCO 的 bbox 是[x, y, w, h]左上角格式YOLO 要求的是归一化后的[cx, cy, w, h]中心点格式。三个格式之间的转换核心就是这两步先算中心点再除以图片宽高归一化。参数说明class_list的顺序决定了训练时类别 ID 的映射必须和后续训练配置文件里的names列表完全一致否则模型学出来的类别全是错的。转换完成后务必随机抽 5 张图用可视化脚本画框验证一遍别信自己的代码一次就对。3. 训练前的数据清洗室内场景特有的四类脏数据3.1 遮挡框和截断框的判定标准室内家具检测里被遮挡的物体标注是最容易出问题的。一张沙发被茶几挡住一半标注员可能只标了可见部分也可能标了完整轮廓。这两种标法混在一起模型学出来的框会忽大忽小。判定标准我一般用这条如果物体可见面积小于 30%直接丢弃该标注如果可见面积在 30% 到 70% 之间保留但打上occluded标记大于 70% 正常保留。用代码批量筛一遍import json with open(annotations.json, r, encodingutf-8) as f: data json.load(f) # 假设有 occluded 字段或通过面积比判断 clean_anns [] dropped 0 for ann in data[annotations]: area ann[bbox][2] * ann[bbox][3] img_area next(img[width] * img[height] for img in data[images] if img[id] ann[image_id]) ratio area / img_area if ratio 0.005: # 极小框通常是误标 dropped 1 continue clean_anns.append(ann) print(f原始标注 {len(data[annotations])} 条清洗后 {len(clean_anns)} 条丢弃 {dropped} 条) data[annotations] clean_anns with open(annotations_clean.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse)参数说明ratio 0.005这个阈值不是固定的取决于你的图片分辨率。1080P 图片下 0.005 大约是 50x50 像素的框再小基本没有检测意义。如果你的数据集是 4K 图这个阈值要相应调大。3.2 重复图片和近邻帧的去重如果数据集是从视频抽帧来的相邻帧几乎一模一样。训练集里混入近邻帧验证集又恰好抽到了同一段视频的另一帧你的验证 mAP 会虚高得离谱。用感知哈希做去重import imagehash from PIL import Image import os, glob hashes {} duplicates [] for img_path in glob.glob(images/*.jpg): h imagehash.phash(Image.open(img_path)) for existing_h, existing_path in hashes.items(): if h - existing_h 5: # 汉明距离小于等于5视为重复 duplicates.append((img_path, existing_path)) break else: hashes[h] img_path print(f发现 {len(duplicates)} 张重复/近邻图片) for dup, orig in duplicates[:10]: print(f {dup} ≈ {orig})参数说明phash是感知哈希对缩放和轻微压缩不敏感汉明距离阈值 5 是经验值调小到 3 更严格调到 8 会误杀相似但不同的场景。去重后建议按 8:1:1 重新划分训练/验证/测试集且同一段视频的帧必须分到同一个集合否则数据泄漏会让你的评估结果变成自欺欺人。3.3 类别名不一致的批量修复前面提过sofa和Sofa的问题还有dining table和diningtable这种空格差异。写个映射表统一import json alias_map { Sofa: sofa, couch: sofa, diningtable: dining table, dining_table: dining table, person: person, Person: person, chair : chair, # 尾部空格 } with open(annotations.json, r, encodingutf-8) as f: data json.load(f) for cat in data[categories]: raw cat[name] cat[name] alias_map.get(raw, raw.strip().lower()) # 去重合并同名类别只保留一个 id seen {} new_cats [] for cat in data[categories]: if cat[name] not in seen: seen[cat[name]] cat[id] new_cats.append(cat) else: # 把旧 id 的标注映射到保留的 id old_id cat[id] new_id seen[cat[name]] for ann in data[annotations]: if ann[category_id] old_id: ann[category_id] new_id data[categories] new_cats print(修复后类别:, [c[name] for c in new_cats])这段代码做了两件事先把别名统一成标准名再把重复类别合并、同步更新所有标注的category_id。注意合并后一定要重新统计每个类的实例数确认没有因为 id 映射错误导致某些类的标注全部丢失。4. 训练参数怎么设室内家具与人检测的调参实战4.1 输入分辨率与多尺度增强的取舍室内场景的物体尺度跨度极大——远处一个小板凳可能只有 20 像素宽近处一张沙发占满半个画面。输入分辨率设 640 时小物体基本废了设 1280 显存又吃不消。我的做法是训练用 640 配合多尺度增强320~960 随机缩放推理时用 960 或 1280。这样训练时模型见过各种尺度推理时放大输入能显著提升小家具的召回。YOLO 系列的多尺度增强默认是关闭的需要在配置里手动开# data.yaml train: ./images/train val: ./images/val nc: 8 names: [person, chair, sofa, table, bed, cabinet, lamp, tv] # 训练命令中的关键参数 # --img 640 训练输入尺寸 # --multi-scale 开启多尺度增强每次batch随机缩放±50% # --rect 矩形推理减少padding提升小物体检测参数说明multi-scale会让每个 batch 的输入尺寸在 320 到 960 之间随机变化显存占用会有波动建议把 batch size 设小一点留余量。rect在验证时开启可以避免大量 padding 导致的小物体特征被稀释。4.2 正负样本分配与遮挡场景的 anchor 调整室内家具的宽高比分布和通用数据集差别很大椅子又高又窄沙发又矮又宽床接近正方形。用 COCO 预训练的 anchor 直接训召回率会吃亏。先跑一遍 k-means 聚类自己的 anchorimport numpy as np def kmeans_anchors(bboxes, k9, iters100): # bboxes: Nx2 的宽高数组归一化后 np.random.seed(42) centers bboxes[np.random.choice(len(bboxes), k, replaceFalse)] for _ in range(iters): dists np.array([np.abs(bboxes - c).sum(axis1) for c in centers]) labels dists.argmin(axis0) for i in range(k): if (labels i).any(): centers[i] bboxes[labels i].mean(axis0) return centers[np.argsort(centers[:, 0] * centers[:, 1])] # 从标注中提取所有宽高 wh np.array([[ann[bbox][2], ann[bbox][3]] for ann in data[annotations]]) wh_norm wh / wh.max(axis0) # 简单归一化 anchors kmeans_anchors(wh_norm, k9) print(推荐 anchor归一化宽高:) for a in anchors: print(f w{a[0]:.3f} h{a[1]:.3f})参数说明k9对应 YOLO 的三个检测头各三个 anchor。聚类结果里如果出现特别扁或特别窄的 anchor说明数据集中有极端宽高比的家具比如落地灯、长条电视柜这些 anchor 要保留否则这类物体永远检不出来。4.3 损失函数中分类与回归的权重平衡室内数据集的类别不平衡问题会直接反映在损失上家具类的分类损失很快降下去人这个类的分类损失居高不下。YOLOv8 默认的分类损失权重是 0.5我一般会调到 0.7 到 1.0 之间同时给少数类加cls_pw分类正样本权重# 在训练脚本中调整损失权重 model.train( datadata.yaml, epochs200, imgsz640, batch16, cls0.8, # 分类损失增益默认0.5不平衡数据调到0.8~1.0 box7.5, # 回归损失增益默认7.5小物体多可以降到5.0 dfl1.5, # 分布焦点损失增益 multi_scaleTrue, rectFalse, cos_lrTrue, # 余弦学习率衰减后期收敛更稳 label_smoothing0.1, # 标签平滑缓解过拟合 )参数说明cls调高会让模型更关注分类正确性但太高会导致框回归变差0.8 是我在室内数据集上比较稳的值。box降到 5.0 是因为室内小物体多过高的回归权重会让模型对大框过拟合。label_smoothing0.1对标注噪声大的数据集特别有用室内数据集的遮挡标注往往有主观偏差平滑一下能提升泛化。5. 避坑与排查室内检测训练中最容易翻车的五个点5.1 验证集 mAP 很高但实际推理一塌糊涂现象训练日志里验证 mAP0.5 到了 0.85但拿手机拍一张客厅照片推理框全是乱的。原因验证集和训练集来自同一批图片的随机划分近邻帧泄漏导致验证集「见过」训练样本。另外验证时的预处理如 letterbox padding和实际推理时的预处理不一致也会造成这种落差。解决按视频来源或场景 ID 划分数据集确保同一场景不出现在训练和验证中。推理时严格复用验证时的预处理代码包括归一化参数和 padding 方式。用--rect验证可以减少 padding 差异。5.2 训练 loss 震荡不收敛NaN 频繁出现现象前 20 个 epoch loss 正常下降之后突然跳到 NaN或者 loss 在某个值附近剧烈震荡。原因室内数据集里存在宽高为 0 或负数的脏标注框计算 IoU 时除零。另外学习率设太大比如 0.01 起步在 batch size 较小时也会导致梯度爆炸。解决训练前用脚本过滤掉w 0 or h 0的标注。学习率用 warmup 从 0.0001 逐步升到 0.001配合cos_lr衰减。如果已经出现 NaN检查数据加载器里有没有图片路径失效导致返回全黑图。5.3 人和家具的框互相「吞并」现象一个人坐在沙发上模型只输出了一个巨大的「沙发」框把人也包进去了或者人站在椅子后面模型把人和椅子检成了同一个框。原因NMS 的 IoU 阈值设得太高比如 0.7导致重叠框被错误合并。室内场景人和家具天然重叠这个阈值必须调低。解决推理时把 NMS IoU 阈值降到 0.45 到 0.5 之间同时开启agnostic_nmsFalse确保不同类之间不做合并。如果还是吞并考虑用 Soft-NMS 替代标准 NMS。5.4 小家具台灯、花瓶召回率极低现象大件家具检测没问题但台灯、花瓶、遥控器这类小物体几乎检不出来mAP 接近 0。原因输入分辨率不够小物体在特征图上只剩几个像素。另外 anchor 尺寸没有覆盖小物体正样本分配阶段这些小框根本匹配不到 anchor。解决训练分辨率提到 960 或 1280同时用前面 k-means 聚类出的最小 anchor。如果显存不够用梯度累积模拟大 batch。还可以在数据增强里加copy_paste把小物体复制粘贴到更多位置增加正样本。5.5 换一个房间场景模型就失效现象在卧室场景训练的模型拿到客厅或办公室推理准确率断崖式下跌。原因模型过拟合到了训练场景的背景纹理和光照条件没有学到家具本身的通用特征。室内数据集的场景多样性往往不够。解决训练时加强颜色抖动HSV 增强的 hsv_h、hsv_s、hsv_v 都调到 0.5 以上、随机裁剪、马赛克增强。如果条件允许混入其他室内场景的公开数据做联合训练。评估时一定要留出一个完全没见过的场景作为测试集这个数字才是真实泛化能力。6. 用 TTA 和分块推理把室内小目标召回再拉一截训练跑通之后推理阶段的优化往往能再挤出 3 到 5 个点的 mAP尤其是小家具的召回。我常用的两个技巧是测试时增强TTA和分块推理。TTA 的做法是对同一张图做多种变换水平翻转、多尺度缩放分别推理后把结果融合。YOLO 系列可以直接在推理命令里开from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) # 开启 TTA 和多尺度推理 results model.predict( sourcetest_images/, imgsz960, augmentTrue, # 开启TTA水平翻转多尺度 conf0.25, iou0.45, # NMS阈值室内重叠场景调低 agnostic_nmsFalse, max_det300, # 室内场景物体多提高最大检测数 rectTrue, )参数说明augmentTrue会同时跑原图、翻转图、以及不同尺度推理时间大约是单次的 3 倍但 mAP 通常能涨 2 到 3 个点。max_det300是因为室内一张图可能有几十个家具默认的 300 够用但如果你的场景特别密集可以再调高。分块推理适合高分辨率图片比如 4K 的室内全景图。把图片切成有重叠的子块分别推理后再把框映射回原图坐标做 NMSimport cv2 import numpy as np from ultralytics import YOLO def tiled_inference(model, img_path, tile_size640, overlap128, conf0.25): img cv2.imread(img_path) h, w img.shape[:2] all_boxes [] step tile_size - overlap for y in range(0, h, step): for x in range(0, w, step): tile img[y:ytile_size, x:xtile_size] if tile.shape[0] 32 or tile.shape[1] 32: continue results model.predict(tile, confconf, verboseFalse) for r in results: for box in r.boxes: xyxy box.xyxy[0].cpu().numpy() xyxy[0] x xyxy[1] y xyxy[2] x xyxy[3] y all_boxes.append((*xyxy, float(box.conf[0]), int(box.cls[0]))) # 对所有框做全局NMS if not all_boxes: return [] boxes np.array([b[:4] for b in all_boxes]) scores np.array([b[4] for b in all_boxes]) classes np.array([b[5] for b in all_boxes]) indices cv2.dnn.NMSBoxes( boxes.tolist(), scores.tolist(), conf, 0.45 ) return [(boxes[i], scores[i], classes[i]) for i in indices.flatten()]逻辑说明分块推理的核心是重叠切块 全局 NMS。overlap128保证切块边界处的物体至少在一个块里是完整的。全局 NMS 用cv2.dnn.NMSBoxes把所有块的框合并去重。参数说明tile_size根据你的 GPU 显存调640 是安全值overlap一般设 tile_size 的 20% 左右太小会漏掉边界物体太大推理时间翻倍。最后说一个我自己的习惯每次训完模型我一定会挑 20 张训练集里没有的场景做人工目检把漏检和误检的案例截图存下来按「小物体漏检」「遮挡误判」「类别混淆」分类。这个习惯比看 mAP 数字有用得多——数字告诉你「好不好」目检告诉你「哪里不好、为什么不好」。室内家具与人检测这个方向数据质量比模型结构重要场景覆盖比参数量重要。希望帮到你。本文还有配套的精品资源点击获取
返回列表