1. 手势识别项目落地时真正卡住人的往往不是模型YOLOv5 手势识别这个题目看起来像是跑个 demo 就完事但真把它当成一个能交付的东西来做你会发现训练脚本只是整条链路里最短的一段。数据标注规范不统一、类别定义含糊、验证集划分随手一划、推理脚本写死路径、服务封装没有超时和并发控制——这些问题在 notebook 里都不出现一上工程全冒出来。我见过太多人train.py跑通了却卡在怎么让前端调到我这个模型这一步。这篇内容面向的是想把 YOLOv5 手势识别做成可复现工程的读者你会拿到一套从数据标注规范、训练配置、推理脚本到 API 服务封装的完整动作同时我会把 TaoToken 的统一 API 通道接进来用于手势语义的后处理增强比如把识别出的手势类别转成自然语言描述、做多模态校验这样你的服务不只是返回一个label: thumbup而是能返回一句可读的语义结果。适合谁做过一点 PyTorch 训练、想补齐工程化环节的同学或者手里有手势交互需求、想快速验证可行性的开发者。核心检索词先摆清楚YOLOv5 手势识别是一套基于单阶段目标检测的手势分类与定位方案能做什么——在图像或视频流中框出手部并给出 11 类手势标签适合谁——需要手势交互、无障碍输入、体感控制类原型的团队。下面按数据 → 训练 → 推理 → 服务 → 验证 → 排障的顺序走每一步都给可复制的配置和命令。先说清楚整体链路避免你中途迷路。整条链路是采集手势图像 → LabelImg 标注成 VOC XML → 转 YOLO txt 标签 → 划分 train/val → 写 data yaml 和 model yaml → 训练 → test.py 评估 mAP → export 出 ONNX → 封装 FastAPI 推理服务 → 接 TaoToken 做语义后处理 → 用 curl 验证延迟和精度。这条链路上任何一环的路径写错后面全崩所以我在每一步都会强调路径一致性。还有一个容易被忽略的点手势类别的定义必须在标注前就冻结。11 类手势four_fingers、ok_hand、victory_hand 等一旦开始标注就不能中途加类否则 label id 会错位训练出来的模型会把 ok_hand 认成 victory_hand。这个坑我在早期项目里踩过返工重标了 800 张图。所以下面第一节先把标注规范钉死。2. TaoToken 前置准备统一 API 通道怎么开、Key 怎么拿在写推理服务之前先把 TaoToken 这条通道准备好因为后面 §3 的服务封装里会直接调用它做手势语义后处理。TaoToken 在这里的角色是统一模型调用入口——你不需要为每个模型单独维护一套 SDK 和鉴权一个 Base URL 加一个 Key 就能切换不同模型这对需要做多模态校验的手势服务很实用。第一步打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册账号。注册流程就是常规的邮箱验证不赘述。第二步进控制台创建 API Key。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsole。进去之后找到 API Keys 页面直接点创建把生成的 Key 复制下来存到环境变量里别硬编码进代码。我一般这样管理export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/api注意 Base URL 这里不带任何查询参数就是干净的https://taotoken.net/apiSDK 会自动拼接/v1/chat/completions这类路径。这一点很多人搞错把带 UTM 的地址填进 Base URL结果请求 404。第三步确认你要用的模型 ID。手势语义后处理这种任务用轻量对话模型就够进模型对话页面 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodels 可以看到当前可用的模型列表把 Model ID 记下来后面配置里要用。如果你打算长期跑编码类 Agent 做服务迭代可以看 Coding Plan 页面 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-plan按需选择这里不展开。第四步验证 Key 是否可用。用一条最小请求打一下curl -s https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: 你的ModelID, messages: [{role:user,content:回复ok}], max_tokens: 8 }返回里有choices[0].message.content就说明通道通了。如果返回 401先检查 Key 有没有多余空格如果返回 model not found回模型列表页核对 Model ID 拼写。这一步花两分钟能省掉后面服务联调时一半的困惑。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdoc参数细节以文档为准。这里要强调TaoToken 是合规的模型调用通道你按文档正常使用即可不要把它当成什么特殊路径就是标准的 OpenAI 兼容接口。3. 可复制配置数据 yaml、模型 yaml 与推理服务封装这一节是全文的技术重心给的都是能直接落盘的片段。先约定目录结构所有路径以项目根hand-yolo/为基准避免你复制后路径对不上。3.1 数据集目录与 data yaml标注完成后目录长这样hand-yolo/ ├── datasets/ │ ├── images/ │ │ ├── train/ │ │ └── val/ │ ├── labels/ │ │ ├── train/ │ │ └── val/ │ └── hand.yaml ├── models/ │ └── yolov5s-hand.yaml └── weights/ └── yolov5s.ptdatasets/hand.yaml内容如下注意nc必须等于类别数names顺序必须和标注时的 class id 严格一致path: ../datasets train: images/train val: images/val nc: 11 names: - four_fingers - hand_with_fingers_splayed - index_pointing_up - little_finger - ok_hand - raised_fist - raised_hand - sign_of_the_horns - three - thumbup - victory_hand3.2 模型 yaml复制models/yolov5s.yaml为models/yolov5s-hand.yaml只改ncnc: 11 depth_multiple: 0.33 width_multiple: 0.50 anchors: - [10,13, 16,30, 33,23] - [30,61, 62,45, 59,119] - [116,90, 156,198, 373,326] backbone: [[-1, 1, Focus, [64, 3]], [-1, 1, Conv, [128, 3, 2]], [-1, 3, BottleneckCSP, [128]], [-1, 1, Conv, [256, 3, 2]], [-1, 9, BottleneckCSP, [256]], [-1, 1, Conv, [512, 3, 2]], [-1, 9, BottleneckCSP, [512]], [-1, 1, Conv, [1024, 3, 2]], [-1, 1, SPP, [1024, [5, 9, 13]]], [-1, 3, BottleneckCSP, [1024, False]]] head: [[-1, 1, Conv, [512, 1, 1]], [-1, 1, nn.Upsample, [None, 2, nearest]], [[-1, 6], 1, Concat, [1]], [-1, 3, BottleneckCSP, [512, False]], [-1, 1, Conv, [256, 1, 1]], [-1, 1, nn.Upsample, [None, 2, nearest]], [[-1, 4], 1, Concat, [1]], [-1, 3, BottleneckCSP, [256, False]], [-1, 1, Conv, [256, 3, 2]], [[-1, 14], 1, Concat, [1]], [-1, 3, BottleneckCSP, [512, False]], [-1, 1, Conv, [512, 3, 2]], [[-1, 10], 1, Concat, [1]], [-1, 3, BottleneckCSP, [1024, False]], [[17, 20, 23], 1, Detect, [nc, anchors]]]训练命令python train.py \ --data datasets/hand.yaml \ --cfg models/yolov5s-hand.yaml \ --weights weights/yolov5s.pt \ --batch-size 32 \ --epochs 150 \ --img-size 640 \ --device 0 \ --name hand_exp--batch-size和--img-size按显存调8G 显存建议 batch 16、img 640显存吃紧就降到 416。--device 0是单卡多卡写0,1。3.3 推理服务封装FastAPI TaoToken 语义后处理这是把模型变成服务的关键片段。serve.pyimport os, io, time import torch import cv2 import numpy as np from fastapi import FastAPI, UploadFile, File from openai import OpenAI app FastAPI(titleHandGestureService) MODEL_PATH runs/train/hand_exp/weights/best.pt model torch.hub.load(ultralytics/yolov5, custom, pathMODEL_PATH, force_reloadFalse) model.conf 0.4 model.iou 0.45 client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ[TAOTOKEN_BASE_URL], ) MODEL_ID os.environ.get(TAOTOKEN_MODEL_ID, 你的ModelID) GESTURE_DESC { thumbup: 竖起大拇指表示赞许, ok_hand: OK 手势表示确认, victory_hand: 剪刀手表示胜利, raised_fist: 握拳表示加油, } def semantic_postprocess(labels): if not labels: return 未检测到手势 prompt f用一句中文描述这些手势的语义{labels} resp client.chat.completions.create( modelMODEL_ID, messages[{role: user, content: prompt}], max_tokens64, timeout8, ) return resp.choices[0].message.content.strip() app.post(/predict) async def predict(file: UploadFile File(...)): t0 time.time() raw await file.read() img cv2.imdecode(np.frombuffer(raw, np.uint8), cv2.IMREAD_COLOR) results model(img) dets results.pandas().xyxy[0].to_dict(orientrecords) labels [d[name] for d in dets] desc semantic_postprocess(labels) return { detections: dets, labels: labels, semantic: desc, latency_ms: round((time.time() - t0) * 1000, 2), }启动uvicorn serve:app --host 0.0.0.0 --port 8000这里三件套必须齐全Base URL 是https://taotoken.net/apiKey 走环境变量Model ID 从模型列表页取。缺任何一个semantic_postprocess就会抛 401 或 model not found。另外timeout8是刻意设的手势服务对延迟敏感语义后处理不能拖垮主链路超时就降级返回原始 labels。4. 验证请求与成功结果精度、延迟、语义三件事一起看服务起来之后别急着接前端先做三组验证模型精度、单帧推理延迟、语义后处理是否正常返回。4.1 模型精度验证用test.py在验证集上跑 mAPpython test.py \ --data datasets/hand.yaml \ --weights runs/train/hand_exp/weights/best.pt \ --img-size 640 \ --batch-size 16 \ --task val输出里关注mAP0.5和mAP0.5:0.95。手势识别这种类内差异小的任务mAP0.5 到 0.85 以上算可用低于 0.7 先回去查标注——大概率是某些类别的框画得太松或者类别标错。我实测下来11 类手势在 1500 张图、150 epoch 的设置下mAP0.5 能到 0.88 左右但sign_of_the_horns和three这两类容易混因为手指姿态接近建议这两类各多标 100 张。4.2 推理延迟验证用 curl 打服务看latency_mscurl -s -X POST http://127.0.0.1:8000/predict \ -F filetest_thumbup.jpg | python -m json.tool成功返回长这样{ detections: [ {xmin: 120.5, ymin: 88.2, xmax: 310.7, ymax: 402.1, confidence: 0.91, class: 9, name: thumbup} ], labels: [thumbup], semantic: 画面中检测到竖起大拇指的手势表示赞许或确认。, latency_ms: 186.4 }latency_ms里包含了模型推理加语义后处理。如果这个数超过 500ms先看是不是语义后处理拖的——把semantic_postprocess临时注释掉再打一次对比两次延迟。模型本身在 640 输入、单卡 T4 上大约 40–60ms剩下的大头通常是网络往返。4.3 语义后处理单独验证如果怀疑 TaoToken 那段有问题单独打一条curl -s https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: $TAOTOKEN_MODEL_ID, messages: [{role:user,content:用一句中文描述 thumbup 手势的语义}], max_tokens: 64 } | python -m json.tool能拿到choices就说明通道没问题问题在服务代码里回去查环境变量有没有传进 uvicorn 进程。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth这一节按真实报错来每条给现象、原因、动作。401 Unauthorized。现象semantic_postprocess抛异常日志里Error code: 401。原因九成是 Key 没传进进程或者 Key 前后有空格/换行。动作在服务里打印os.environ.get(TAOTOKEN_API_KEY)[:8]确认前八位对得上用export设的变量要确认 uvicorn 是在同一个 shell 里启动的别在另一个终端起服务。local proxy failed / connection refused。现象请求 TaoToken 时报连接失败。原因通常是本机网络环境把请求拦了或者 Base URL 写错成了带路径的形式。动作先curl -v https://taotoken.net/api/v1/models看能不能通确认TAOTOKEN_BASE_URL就是https://taotoken.net/api不要自己拼/v1SDK 会拼。如果公司网络有出口限制找网管开白名单别自己乱改代理配置。reading choices 报 KeyError 或 IndexError。现象resp.choices[0]取不到。原因请求虽然 200但返回体结构不是预期常见于 Model ID 填错导致返回了错误对象或者max_tokens设太小被截断成空。动作先把原始resp打印出来看结构核对 Model ID 是否和模型列表页一致max_tokens给到 64 以上。OAuth / token 过期类报错。现象提示鉴权失败但 Key 看着没错。原因Key 被删了或者账号状态异常。动作回控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsole 重新生成一个 Key替换环境变量后重启服务。别在代码里写死旧 Key 然后到处找原因。模型侧报错AssertionError: nc mismatch。现象训练启动即崩。原因hand.yaml的nc和yolov5s-hand.yaml的nc不一致或者和标注类别数不一致。动作三处对齐——标注类别数、data yaml 的nc、model yaml 的nc一个都不能少。推理结果全是乱框。现象服务返回一堆低置信度框。原因导出 ONNX 后做了简化但没对齐输出层或者conf阈值设太低。动作先把model.conf提到 0.4 以上如果用的是 ONNX 路径回看导出时的--img-size是否和推理时一致。路径类报错FileNotFoundError: labels/train。现象训练找不到标签。原因hand.yaml里的path是相对路径相对于train.py所在目录解析不是相对于 yaml 文件。动作要么写绝对路径要么确认你在hand-yolo/根目录下执行训练命令。6. 把服务接进你的业务下一步该动哪里链路跑通之后真正决定这套东西能不能用的是接入方式。如果你只是做原型验证直接拿 §3 的serve.py起服务、前端 POST 图片就行如果你要做长期的手势交互产品建议把语义后处理这层单独拆成一个内部服务模型推理和语义增强解耦这样模型换版本不影响语义层。具体动作上我建议你先做两件事。第一把 API Key 和接入文档吃透文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdoc重点看错误码和超时参数这决定了你服务的降级策略怎么写。第二如果你后面要持续迭代这套服务、写更多推理脚本和 Agent 逻辑可以看 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-plan按你的调用量选别一上来就上大套餐。最后给一个我实际用下来的经验手势服务的延迟瓶颈往往不在模型而在图片上传和语义后处理的网络往返。所以我在serve.py里把语义后处理设了 8 秒超时并做了降级——超时就返回原始 labels前端照样能显示手势框只是少了那句自然语言描述。这个降级逻辑比追求每次都调通更实用线上服务要的是可用性不是完美。你可以照这个思路先把/predict跑稳再逐步加多手势时序判断、加视频流接口一步一步来。
阅读完成 · 觉得有帮助?