ARTICLE DETAIL

资讯详情

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

593张图训练YOLO手机端行为检测:小样本实战与避坑指南

593张图训练YOLO手机端行为检测:小样本实战与避坑指南 简介这是一份面向目标检测入门与进阶学习者的手机使用行为识别数据集聚焦“玩手机”与“打电话”两类典型场景可直接用于yolov5、yolov8、yolov9、yolov7、yolov10及yolo11等系列算法的模型训练与验证测试。资源包共1780个文件包含593张jpg图像、593个txt标签、593个xml标签以及1个yaml配置文件压缩包约20.46MB其中txt为yolo格式标注xml为voc格式标注两种格式分别存放便于按需选用yaml文件则用于快速配置训练路径与类别信息。数据集已完成训练集与验证集划分标注坐标采用归一化中心点与宽高表示类别索引从0开始省去自行清洗与转换格式的环节。目前已有191人学习下载适合希望快速复现手机行为检测、验证模型效果或开展课程实验的读者可在此基础上直接开展训练、评估与调参减少数据准备成本。1. 593 张“玩手机-打电话”图像够不够训一个能上手机的 YOLO先说结论593 张带标签图像如果标注质量过关、场景聚焦在“玩手机 / 打电话”这两类动作上完全够训出一个能在手机上跑实时检测的 YOLO 模型但前提是你得把“数据集小”这件事当成一个工程约束来对待而不是当成一个可以靠调参绕过去的玄学问题。这个标题拆开看其实包含三层信息第一层是任务定义检测目标是“玩手机”和“打电话”两类行为属于典型的人体交互动作检测不是通用目标检测第二层是数据规模593 张图像带标签量级偏小属于小样本场景第三层是部署目标标题里“手机”两个字暗示了最终要落到移动端推理这就对模型体量、输入分辨率、后处理复杂度都提出了硬约束。适合读这篇的人有三类手里已经有一批类似行为图像、想快速验证 YOLO 能不能跑通的算法工程师做课堂行为分析、驾驶行为监控、工地安全巡检这类垂直场景、需要小样本落地的开发者以及刚接触 YOLO、想拿一个真实小数据集走完“标注检查 → 训练 → 导出 → 手机端推理”全流程的新手。不适合指望靠这 593 张图直接做出通用行为识别大模型的人那是另一个量级的工程。我一般会先做一件事把这 593 张图按场景、光照、拍摄角度、人物数量四个维度各抽 20 张看一眼。如果发现超过三成图像里目标占比小于整图面积的 5%那这个数据集在训练前就得先做裁剪或重标注否则后面 mAP 上不去你会以为是模型问题其实是数据问题。这个判断动作花不了半小时但能省掉后面好几天的无效调参。2. 小样本行为检测的选型逻辑为什么是 YOLO而不是别的2.1 行为检测和通用目标检测的差别在哪“玩手机”和“打电话”这两个类别和 COCO 里的“person”“cell phone”有本质区别。通用检测器检测的是物体本身而这里检测的是“人 手机 姿态”三者组合出来的一种行为状态。同一个人拿着手机贴在耳边是打电话低头看屏幕是玩手机手机放在桌上人没碰就不算任何一类。这意味着标注框不能只框手机也不能只框人常见做法是框住“人 手机”的整体区域或者框住人的上半身并保证手机在框内。这个差别直接影响了选型。如果用两阶段检测器比如 Faster R-CNN 系列精度可能略高但推理速度在手机上基本没法看。YOLO 系列的单阶段设计天然适合这种“类别少、目标大、要求实时”的场景。593 张图、2 个类别属于典型的小样本少类别任务YOLOv8n 或 YOLOv11n 这种 nano 级别的主干就够用参数量在 300 万左右导出后模型文件通常几 MB手机端单帧推理能压到几十毫秒。另一个选型理由是 YOLO 的生态成熟度。从标注格式转换、训练脚本、验证指标到移动端导出整条链路都有现成工具不需要自己造轮子。对于 593 张这种规模的数据集工程效率比模型精度更重要因为你大部分时间会花在数据清洗和标注修正上而不是网络结构设计上。2.2 593 张图该怎么切分才不浪费小样本场景下训练集、验证集、测试集的切分不能随便按 8:1:1 拍脑袋。593 张图如果按 8:1:1 切验证集只有 59 张测试集 59 张评估结果的方差会很大今天 mAP 0.75 明天可能就 0.68你会被这个波动搞得怀疑人生。我一般会按 7:1.5:1.5 来切训练集约 415 张验证集和测试集各约 89 张。更重要的是切分时要保证场景分布一致如果原始数据里室内和室外比例是 6:4那三个子集里都得维持这个比例。具体做法是先按场景给每张图打一个 scene 标签再在每个 scene 内部随机切分最后合并。这样能避免验证集全是室内、训练集全是室外这种分布偏移。还有一个血泪经验切分前一定要做去重。593 张图里如果有连续视频帧抽出来的近似重复图训练集和验证集里各出现一张验证指标会虚高实际部署时直接翻车。用感知哈希或者简单的 SSIM 相似度跑一遍把相似度高于 0.95 的图归为一组同组只保留一张进验证集或测试集。2.3 标注格式转换从原始标签到 YOLO 格式标题里说“带标签”但没说什么格式。常见的有三种VOC 的 XML、COCO 的 JSON、以及已经转好的 YOLO txt。如果是前两种需要先转成 YOLO 格式。YOLO 格式每行是class_id x_center y_center width height全部归一化到 0 到 1 之间。下面这个脚本处理 VOC XML 到 YOLO txt 的转换同时做两件事过滤掉宽高小于 10 像素的无效框以及把类别名映射成 0 和 1。import os import xml.etree.ElementTree as ET # 类别映射顺序决定 class_id CLASS_MAP {play_phone: 0, call_phone: 1} MIN_BOX_SIZE 10 # 小于这个像素尺寸的框直接丢弃 def convert_voc_to_yolo(xml_dir, img_dir, out_dir): os.makedirs(out_dir, exist_okTrue) skipped 0 for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() # 图片宽高用于归一化 size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in CLASS_MAP: continue bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) bw xmax - xmin bh ymax - ymin if bw MIN_BOX_SIZE or bh MIN_BOX_SIZE: skipped 1 continue # 归一化并限制在 0-1 之间防止标注越界 xc max(0, min(1, (xmin xmax) / 2 / img_w)) yc max(0, min(1, (ymin ymax) / 2 / img_h)) nw max(0, min(1, bw / img_w)) nh max(0, min(1, bh / img_h)) lines.append(f{CLASS_MAP[name]} {xc:.6f} {yc:.6f} {nw:.6f} {nh:.6f}) if lines: txt_name xml_file.replace(.xml, .txt) with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines)) print(f转换完成丢弃了 {skipped} 个过小框) convert_voc_to_yolo(./annotations, ./images, ./labels)这段代码的关键参数是MIN_BOX_SIZE设成 10 是因为小于 10 像素的框在 640 输入分辨率下缩放后基本没有有效梯度留着只会引入噪声。CLASS_MAP的顺序决定了训练时类别索引一旦定下来后面不能改否则标签和模型输出会对不上。归一化时做了 clamp 操作是因为手工标注偶尔会出现框超出图像边界的情况不处理的话 YOLO 训练时会报错。转换完一定要抽查随机抽 10 张图用 OpenCV 把 YOLO 格式的框画回原图肉眼确认框的位置和类别都对。这一步花 5 分钟能拦住后面 80% 的“训练 loss 不降”问题。3. 从 593 张图到手机端模型训练、导出、推理全链路3.1 训练配置小样本必须开的几个参数593 张图训练 YOLO最容易犯的错是直接拿默认配置跑。默认配置是为 COCO 这种十几万张图设计的学习率、数据增强强度、训练轮数都不匹配。下面是我在小样本行为检测上验证过的一套配置以 YOLOv8n 为例。from ultralytics import YOLO model YOLO(yolov8n.pt) # 从预训练权重起步小样本必须用预训练 results model.train( dataphone_behavior.yaml, # 数据集配置文件 epochs150, # 小样本需要更多轮次让模型收敛 imgsz640, # 输入分辨率手机端可降到 416 batch16, # 593 张图batch 16 比较稳 lr00.001, # 初始学习率比默认 0.01 低一个量级 lrf0.01, # 最终学习率因子 warmup_epochs5, # 小样本预热要长一点 mosaic0.5, # mosaic 增强概率小样本别开太高 mixup0.0, # mixup 对小样本容易过拟合关掉 degrees10.0, # 旋转增强行为检测对角度敏感别太大 translate0.1, # 平移增强 scale0.3, # 缩放增强模拟不同距离 fliplr0.5, # 水平翻转打电话左右手都常见 patience30, # 30 轮不提升就早停 device0, # GPU 编号 projectphone_behavior, nameexp1 )几个参数值得展开说。lr00.001是因为小样本下梯度噪声大学习率太高容易在损失曲面上跳来跳去不收敛。mosaic0.5而不是默认的 1.0是因为 mosaic 把四张图拼成一张593 张图拼完实际有效样本更少开太高会让模型见过多拼接伪影真实场景反而认不准。mixup0.0是血泪教训小样本加 mixup 会让两个类别的特征混在一起玩手机和打电话的边界直接糊掉。imgsz640是训练分辨率如果手机端算力紧张可以降到 416 甚至 320但要在导出前重新用目标分辨率微调几轮否则精度掉得厉害。3.2 训练过程怎么看loss 曲线和混淆矩阵的读法训练启动后重点盯三个东西box_loss、cls_loss 和 mAP50。box_loss 管的是框的位置准不准cls_loss 管的是类别分对没有。593 张图正常情况是前 20 轮 loss 快速下降20 到 80 轮缓慢下降80 轮后基本平了。如果 50 轮了 cls_loss 还在 0.5 以上震荡大概率是标注有问题回去查标签。混淆矩阵是另一个关键。玩手机和打电话这两个类别如果混淆矩阵显示“打电话”被大量预测成“玩手机”说明两个问题之一要么标注时边界没定清楚比如有人拿着手机没贴耳朵但也没低头标注员凭感觉标了要么训练时水平翻转增强把“打电话”的左右手特征搞混了。前者要回去重新定义标注规则后者可以把 fliplr 降到 0.3。验证集 mAP50 能到 0.85 以上基本就可以进入导出环节了。如果只有 0.6 左右先别急着调模型把验证集的预测结果可视化出来看十有八九是框的位置偏了或者漏检了某些角度。3.3 导出手机端格式ONNX 和 NCNN 怎么选手机端推理常见两条路ONNX Runtime 和 NCNN。ONNX 通用性好Android 和 iOS 都能用但包体大一些NCNN 是腾讯开源的专为移动端优化速度快、包体小但主要面向 Android。如果目标平台是 Android我一般优先选 NCNN。# 导出 ONNX动态 batch 方便后续处理 model.export(formatonnx, imgsz640, dynamicTrue, simplifyTrue) # 导出 NCNN指定输入尺寸 model.export(formatncnn, imgsz640)导出后必做一步用 ONNX Runtime 或 NCNN 的 Python 接口加载模型拿一张验证集图片跑一遍和 PyTorch 原模型的输出对比。如果框的位置差超过 2 个像素说明导出过程有算子不兼容得换导出参数或者换格式。这一步是后悔药不做的话等集成到 App 里再发现就晚了。3.4 手机端推理的输入预处理和后处理手机端拿到的图像通常是 NV21 或 RGBA 格式需要先转成 RGB再缩放到模型输入尺寸。这里有个坑直接拉伸缩放会改变宽高比导致框变形。正确做法是保持宽高比缩放短边补灰边到目标尺寸推理完再把框映射回原图坐标。import cv2 import numpy as np def preprocess(img_bgr, target_size640): h, w img_bgr.shape[:2] scale min(target_size / w, target_size / h) new_w, new_h int(w * scale), int(h * scale) resized cv2.resize(img_bgr, (new_w, new_h)) # 创建灰色画布居中放置 canvas np.full((target_size, target_size, 3), 114, dtypenp.uint8) dx (target_size - new_w) // 2 dy (target_size - new_h) // 2 canvas[dy:dynew_h, dx:dxnew_w] resized # 转 RGB、归一化、HWC 转 CHW blob canvas[:, :, ::-1].astype(np.float32) / 255.0 blob np.transpose(blob, (2, 0, 1))[np.newaxis, ...] return blob, scale, dx, dy def postprocess(outputs, scale, dx, dy, conf_thres0.4, iou_thres0.45): # outputs 形状 [1, 4num_classes, 8400]需要转置 preds outputs[0].T boxes preds[:, :4] scores preds[:, 4:] class_ids np.argmax(scores, axis1) confs scores[np.arange(len(scores)), class_ids] mask confs conf_thres boxes, class_ids, confs boxes[mask], class_ids[mask], confs[mask] # xywh 转 xyxy再映射回原图 xyxy np.zeros_like(boxes) xyxy[:, 0] boxes[:, 0] - boxes[:, 2] / 2 xyxy[:, 1] boxes[:, 1] - boxes[:, 3] / 2 xyxy[:, 2] boxes[:, 0] boxes[:, 2] / 2 xyxy[:, 3] boxes[:, 1] boxes[:, 3] / 2 xyxy (xyxy - [dx, dy, dx, dy]) / scale # NMS indices cv2.dnn.NMSBoxes(xyxy.tolist(), confs.tolist(), conf_thres, iou_thres) return xyxy[indices], class_ids[indices], confs[indices]预处理里灰色填充值 114 是 YOLO 官方用的和训练时的 letterbox 保持一致不一致会导致精度下降。后处理里conf_thres0.4比训练时常用的 0.25 高是因为手机端场景误检代价高宁可漏检也别乱报。NMS 的iou_thres0.45是通用值如果发现同一个人被框了两次可以降到 0.4。4. 避坑与排查593 张图训练时最容易翻车的 5 个点4.1 现象训练 loss 正常下降但验证集 mAP 一直卡在 0.5 以下原因最常见的是训练集和验证集分布不一致。593 张图如果切分时没按场景分层很可能训练集里大部分是白天室内验证集里混进了夜间室外图模型没见过这种光照自然测不准。另一个可能是标注框普遍偏大或偏小和实际目标不匹配。解决先做分布检查把训练集和验证集的图片按亮度、尺寸、目标框面积各画一个直方图对比。如果发现明显偏移重新分层切分。如果是标注问题抽 30 张验证集图把预测框和真实框画在一起看偏大就统一收紧标注规则偏小就放宽。4.2 现象模型把“打电话”预测成“玩手机”混淆矩阵里两类互相串原因标注规则本身模糊。很多人打电话时手机没贴到耳朵只是拿在手里靠近脸标注员可能标成打电话也可能标成玩手机。另外水平翻转增强开太高会把左右手拿手机的姿态特征搞混。解决先统一标注规则写清楚“手机听筒区域与耳朵距离小于 5 厘米算打电话否则算玩手机”然后让标注员按这个规则重新过一遍边界样本。增强方面把 fliplr 从 0.5 降到 0.3同时关掉 mixup。4.3 现象导出 ONNX 后推理结果和 PyTorch 不一致框整体偏移原因导出时没开 simplify或者动态轴设置不对。YOLO 的输出层在导出时如果遇到不支持的算子会静默替换成近似实现导致数值偏差。另一个常见原因是预处理不一致PyTorch 推理时用了 letterboxONNX 推理时直接 resize。解决导出时加simplifyTrue导出后用 onnxruntime 加载拿同一张图分别跑 PyTorch 和 ONNX逐层对比输出。如果偏差出现在某一层用 Netron 打开 ONNX 看那一层被替换成了什么算子。预处理必须和训练时完全一致letterbox 的填充值、缩放比例都要对齐。4.4 现象手机端推理速度只有几帧达不到实时原因输入分辨率太高或者用了 FP32 推理。640 输入的 YOLOv8n 在中等手机上 FP32 大概 20 到 30 毫秒一帧但如果手机 CPU 较老或者同时跑了其他任务会掉到 10 帧以下。另一个可能是后处理 NMS 用了 Python 循环在手机上极慢。解决把输入降到 416 或 320精度会掉几个点但速度翻倍。导出时开 FP16 量化模型体积减半速度提升 30% 左右。后处理 NMS 尽量用框架自带的 C 实现别在 Python 层循环。如果还不行考虑把模型切成两段主干跑 NPU后处理跑 CPU。4.5 现象某些角度下漏检严重尤其是俯拍和侧拍原因593 张图里如果俯拍和侧拍样本少模型没见过这些视角泛化不出去。行为检测对视角比通用检测更敏感因为手和手机的相对位置在不同视角下变化很大。解决统计一下训练集里拍摄角度的分布如果俯拍少于 10%要么补数据要么用随机透视变换增强模拟俯拍效果。透视变换的参数别太激进warp 角度控制在 15 度以内否则框会变形到无法回归。另外可以在推理时做多尺度测试把图缩放到不同尺寸各跑一遍再融合能提升几个点召回代价是速度减半。5. 小样本行为检测的进阶技巧把 593 张用出 2000 张的效果593 张图训一个能用的模型不难难的是让它在你没见过的场景里也稳。我自己的习惯是训练完第一版后不急着上线先做一轮“困难样本挖掘”拿模型在验证集和测试集上跑一遍把置信度在 0.3 到 0.6 之间的预测框对应的图挑出来人工看一遍把漏检的补标、误检的修正然后把这些图加进训练集再训一轮。通常两轮下来mAP 能涨 5 到 8 个点比调任何超参都管用。另一个技巧是类别平衡。593 张图里“玩手机”和“打电话”的数量大概率不是 1:1如果某一类少于 30%训练时给这一类加权。YOLO 的配置文件里可以设cls_pw或者手动在 loss 里加权重让模型别忽略少数类。最后说一个验证方法别只看 mAP。行为检测在真实场景里误报的代价往往比漏报高。我会额外算一个指标叫“每小时误报次数”拿一段 10 分钟的真实场景视频跑推理统计误报了多少次除以时长。如果超过 5 次每小时说明后处理的置信度阈值还得往上提或者得加一个时序平滑连续 3 帧都检出同一位置才输出。我自己踩过最大的坑是早期太迷信 mAP验证集 0.9 就上线结果实际场景里因为光照变化误报不断。后来养成习惯任何模型上线前必须跑一段真实视频用眼睛看一遍误报和漏报这个动作比任何指标都可靠。希望帮到你。本文还有配套的精品资源点击获取
返回列表