
简介这份资源面向K12阶段学生与AI入门教师提供基于YOLOv5与大疆教育无人机Tello TT的目标识别、检测、追踪与测距完整方案将深度学习理论落地到真实飞行场景解决从模型训练到无人机部署的全流程问题。压缩包共1667个文件约269.13MB其中758张jpg图像与694个txt标注构成“旗”“圈”两类目标数据集46个py脚本负责训练与推理94个yaml与9个pt文件对应网络配置和训练好的权重另有说明文档、Dockerfile及若干sh脚本辅助环境搭建与运行。目前已有679人学习下载。读者可直接复用预训练模型与标注数据省去从零采集与训练的成本同时借助操作说明理解环境配置、模型加载、视频流处理与无人机控制等环节适合作为STEM课程实践、竞赛项目或课外拓展的参考素材。1. 从 Tello TT 到 YOLOv5一套能飞起来的目标识别与追踪测距方案长什么样手里有一台大疆教育无人机 Tello TT又想把 YOLOv5 跑成能识别、能追、还能测距的完整链路这件事的难点从来不在模型本身而在「机载算力、图传延迟、坐标换算」这三件事怎么串起来。Tello TT 自带 SDK能拿到视频流和飞行指令接口但它没有机载 GPU所有推理都得放到地面端或边缘设备上跑再把控制指令回传。所以这套方案的本质是地面端用 YOLOv5 做目标检测把检测框中心点转成无人机机体坐标系下的偏航、俯仰、距离三个量再通过 Tello SDK 发 rc 控制指令实现追踪同时用单目测距或已知目标尺寸反推距离。它适合做毕设、做无人机视觉入门、做教育场景演示的从业者也适合想把 YOLOv5 从「只会跑图片」推进到「闭环控制真实设备」的人。下面按环境配置、数据准备、训练调参、机载联调、避坑、进阶技巧的顺序把这条链路拆开讲清楚。2. 环境配置与 Tello TT 通信链路搭建从 conda 到第一帧图传2.1 为什么用 conda 隔离 YOLOv5 环境而不是直接 pipYOLOv5 对 PyTorch、torchvision、numpy 的版本耦合比较紧尤其是做后处理和导出 ONNX 的时候版本错一位就可能出现 NMS 报错或者输出维度对不上。我一般用 conda 建一个独立环境把 Python 锁在 3.8 到 3.9 之间PyTorch 按 CUDA 版本选。如果你机器上没有 NVIDIA 显卡用 CPU 推理也能跑但帧率会掉到个位数追踪基本没法闭环所以建议至少有一块 6G 显存的卡。conda create -n tello_yolo python3.9 -y conda activate tello_yolo # 按你的 CUDA 版本选下面以 cu118 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt这段命令的逻辑是先建环境避免污染全局再装和 CUDA 匹配的 PyTorch最后拉 YOLOv5 仓库并装依赖。参数上要注意--index-url后面的 cu118 要和你nvidia-smi显示的 CUDA 版本对应装完用python -c import torch; print(torch.cuda.is_available())验证返回 True 才算通。requirements.txt 里会装 opencv、pandas、matplotlib 这些如果卡在某个包上多半是网络问题换源重试即可。2.2 Tello TT 的 SDK 通信与视频流接入Tello TT 的通信分两条链路一条是命令链路走 UDP 8889 端口用来发 takeoff、land、rc 这类指令另一条是视频流走 UDP 11111 端口拿到的是 H.264 裸流。常见做法是用 djitellopy 这个库封装命令链路视频流用 OpenCV 的 VideoCapture 接udp://0.0.0.0:11111。from djitellopy import Tello import cv2 tello Tello() tello.connect() print(电量:, tello.get_battery()) tello.streamon() # 开启视频流 frame_read tello.get_frame_read() while True: frame frame_read.frame # BGR 格式 if frame is None: continue frame cv2.resize(frame, (960, 720)) cv2.imshow(Tello, frame) if cv2.waitKey(1) 0xFF ord(q): break tello.streamoff() tello.end()逻辑说明connect()会握手并返回电量streamon()让飞机开始推流get_frame_read()起一个后台线程持续解码主循环里直接取最新帧。参数上Tello TT 图传默认 960x720延迟在 100 到 200 毫秒之间这个延迟对慢速追踪够用对快速机动就不够所以后面控制指令要加死区和限幅。注意frame_read.frame拿到的是 BGR直接喂给 YOLOv5 前要确认通道顺序YOLOv5 内部会做 BGR 到 RGB 的转换不用手动转。2.3 把 YOLOv5 推理接进图传循环的最小闭环先不急着做追踪先把「取帧 → 推理 → 画框 → 显示」跑通确认帧率和延迟能接受再往上加控制逻辑。这一步是后面所有事情的地基。import torch import cv2 from djitellopy import Tello model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue) model.conf 0.4 # 置信度阈值 model.iou 0.45 # NMS 的 IoU 阈值 tello Tello() tello.connect() tello.streamon() frame_read tello.get_frame_read() while True: frame frame_read.frame if frame is None: continue results model(frame) annotated results.render()[0] # 画好框的图 cv2.imshow(YOLOv5 on Tello, annotated) if cv2.waitKey(1) 0xFF ord(q): break tello.streamoff() tello.end()逻辑说明torch.hub.load会下载 yolov5s 预训练权重第一次跑需要联网。model.conf和model.iou是两个必调参数conf 调低会出更多框但误检增加调高会漏检iou 控制重叠框合并目标密集时调低一点。results.render()直接返回带框的 numpy 图省去手动画框。跑通后看左上角帧率如果低于 10 帧要么换更小的模型yolov5n要么把输入尺寸从 640 降到 416。3. 数据集准备与 YOLOv5 训练自己的数据集从标注到 mAP 曲线3.1 采集与标注用 Tello 自己拍还是用公开数据集做目标追踪数据集最好和实际飞行场景一致。我一般先用 Tello 悬停在不同高度、不同光照下录几段视频再用脚本抽帧每 10 帧抽一张避免相邻帧太像导致过拟合。抽完用 labelImg 或 Roboflow 标注类别就设你要追的那个目标比如「person」「car」「ball」。如果目标有公开数据集比如 COCO 里已有的类别可以直接拿 COCO 的子集先训一版再用自己的数据微调这样收敛快很多。标注格式选 YOLO 格式每张图对应一个 txt每行是class_id x_center y_center width height全部归一化到 0 到 1。常见坑是标注时框超出图像边界训练时会报错或者学歪所以标注完要写个脚本检查一遍。import os import cv2 def check_labels(img_dir, lbl_dir): for name in os.listdir(lbl_dir): path os.path.join(lbl_dir, name) with open(path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: print(格式错误:, path) continue _, x, y, w, h map(float, parts) if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): print(越界:, path, line.strip()) check_labels(images/train, labels/train)逻辑说明这段脚本遍历标签文件检查每行是不是 5 个字段以及归一化坐标是否在合法范围。参数上x_center和y_center必须在 0 到 1 之间width和height必须大于 0 且不超过 1。发现越界就回去重新标别指望训练时自动修正。3.2 目录结构与 data.yaml 的写法YOLOv5 对目录结构有约定常见做法是建datasets/mytello目录下面分images/train、images/val、labels/train、labels/val。然后写一个data.yaml指向这些路径并列出类别名。path: ./datasets/mytello train: images/train val: images/val nc: 1 names: [target]逻辑说明path是数据集根目录train和val是相对路径nc是类别数names是类别名列表顺序要和标注里的 class_id 对应。常见错误是nc写成 0 或者和 names 长度不一致训练时会直接报 assert 失败。改完 yaml 用python train.py --data data.yaml --img 640 --batch 16 --epochs 100 --weights yolov5s.pt启动训练。3.3 训练参数怎么设batch、img、epochs 与超参数训练参数没有万能值但有几个经验区间。--img默认 640如果你的目标在画面里很小可以提到 960 或 1280但显存占用会翻倍。--batch在 6G 显存上跑 640 一般能到 16跑 960 就只能到 8 或 4。--epochs看数据量几千张图跑 100 到 300 轮看 mAP 曲线不再上升就可以停。YOLOv5 的超参数在data/hyp.scratch.yaml里学习率lr0默认 0.01微调时可以降到 0.001。数据增强里mosaic默认 1.0小目标多的时候可以保留但如果你追的目标经常被遮挡mosaic太强反而会让模型学不到完整目标可以降到 0.5。训练过程中看runs/train/exp下的results.png重点看mAP0.5和box_loss如果 loss 震荡不降多半是学习率太大或者标注有问题。python train.py \ --data data.yaml \ --img 640 \ --batch 16 \ --epochs 150 \ --weights yolov5s.pt \ --hyp data/hyp.scratch.yaml \ --project runs/train \ --name tello_exp逻辑说明--weights指定预训练权重从 yolov5s.pt 开始比从零训快很多。--project和--name控制输出目录方便多次实验对比。训练完最好的权重在runs/train/tello_exp/weights/best.pt后面部署就用这个。4. 目标追踪与单目测距把检测框变成飞行指令4.1 从检测框到偏航、俯仰、距离的换算检测框给出的是像素坐标要变成无人机能执行的控制量需要做三步先算框中心相对画面中心的偏移再映射到偏航和俯仰的角速度最后用框的宽度或高度反推距离。假设画面宽 W、高 H框中心为 (cx, cy)则水平偏移dx (cx - W/2) / (W/2)范围在 -1 到 1 之间垂直偏移dy (cy - H/2) / (H/2)。偏航角速度yaw_rate -dx * Kp_yaw俯仰用up_down dy * Kp_pitch这里的 Kp 是比例系数一般从 30 到 60 试起。距离估算用相似三角形如果你知道目标的真实宽度real_w镜头焦距f像素单位框的像素宽度pixel_w则距离D real_w * f / pixel_w。焦距可以用已知距离下拍一张标定图反推。这个测距是单目的精度受目标姿态影响目标侧对镜头时误差会很大所以实际用的时候我会加一个卡尔曼滤波平滑距离或者干脆只用距离做前后速度控制不做精确定位。def box_to_cmd(cx, cy, w, h, frame_w, frame_h, real_w, focal): dx (cx - frame_w / 2) / (frame_w / 2) dy (cy - frame_h / 2) / (frame_h / 2) yaw_rate int(-dx * 50) # 左右 up_down int(dy * 40) # 上下 distance real_w * focal / max(w, 1) # 距离目标 1.5 米远了前进近了后退 forward int((distance - 1.5) * 30) forward max(min(forward, 40), -40) # 限幅 return yaw_rate, up_down, forward, distance逻辑说明dx和dy归一化到 -1 到 1乘上比例系数得到速度指令。forward用距离误差乘系数并限幅在 -40 到 40 之间防止飞机猛冲。参数上real_w和focal要提前标定focal可以用focal pixel_w * D / real_w在已知距离 D 下反推。限幅值根据你的飞行安全范围调室内建议不超过 30。4.2 用 PID 平滑控制指令并下发 rc直接比例控制会有抖动飞机一抖图传就糊检测框跟着跳形成正反馈。常见做法是加一个简单的 PIDP 管响应I 消稳态误差D 抑制震荡。Tello 的 rc 指令格式是rc left_right forward_backward up_down yaw每个值 -100 到 100。class PID: def __init__(self, kp, ki, kd): self.kp, self.ki, self.kd kp, ki, kd self.prev_err 0 self.integral 0 def update(self, err, dt0.05): self.integral err * dt derivative (err - self.prev_err) / dt self.prev_err err return self.kp * err self.ki * self.integral self.kd * derivative pid_yaw PID(0.6, 0.0, 0.1) pid_fwd PID(0.5, 0.0, 0.08) # 在主循环里 err_yaw -dx yaw_cmd int(pid_yaw.update(err_yaw)) err_dist distance - 1.5 fwd_cmd int(pid_fwd.update(err_dist)) tello.send_rc_control(0, fwd_cmd, 0, yaw_cmd)逻辑说明PID 的update接收误差返回控制量。dt是循环周期按实际帧率填。参数上先调 P 到飞机能朝目标转但不震荡再加 D 压震荡I 一般室内不用加加了容易积分饱和。send_rc_control四个参数分别是左右、前后、上下、偏航这里只用了前后和偏航左右和上下留 0。注意每次发指令前要判断检测是否有效丢帧时发 0 让飞机悬停别让上一次指令一直生效。4.3 追踪丢失与重捕获的处理目标丢失是常态遮挡、快速移动、光照突变都会导致丢框。我的做法是维护一个丢失计数器连续 N 帧没检测到就进入搜索模式原地缓慢偏航一圈同时降低 conf 阈值重新找。如果超过一定时间还找不到就悬停等待或者降落。这个逻辑不复杂但能避免飞机追着空气跑。lost_frames 0 MAX_LOST 15 if len(results.xyxy[0]) 0: lost_frames 0 # 取置信度最高的框 box results.xyxy[0][results.xyxy[0][:, 4].argmax()] # ... 计算控制量 else: lost_frames 1 if lost_frames MAX_LOST: tello.send_rc_control(0, 0, 0, 0) # 悬停 else: tello.send_rc_control(0, 0, 0, 20) # 缓慢偏航搜索逻辑说明results.xyxy[0]是检测结果每行是 x1,y1,x2,y2,conf,cls。取 conf 最大的框作为追踪目标避免多个目标来回跳。MAX_LOST设 15 帧按 20 帧率算大约 0.75 秒太短会频繁进搜索太长会跟丢。搜索时的偏航速度给 20别给太大否则图传糊了更找不到。5. 避坑与排查Tello TT YOLOv5 联调时最容易翻车的 5 个点5.1 图传延迟导致控制震荡现象飞机一追目标就左右摆越摆越大。原因图传延迟 100 到 200 毫秒加上推理时间从取帧到发指令可能过了 300 毫秒PID 的 D 项按当前误差算实际控制的是 300 毫秒前的状态相位滞后导致震荡。解决降低 P 和 D 的增益把控制周期和推理周期解耦推理跑在一个线程控制跑在另一个线程用最新结果或者直接降帧率到 10 帧让延迟占比小一点。5.2 检测框抖动导致距离估算跳变现象距离读数在 1 米到 3 米之间乱跳飞机前后抽搐。原因单目测距对框宽非常敏感框宽抖几个像素距离就跳几十厘米。解决对框宽做滑动平均或者对距离做一阶低通滤波d_smooth 0.8 * d_prev 0.2 * d_new牺牲一点响应换稳定。另外 conf 阈值别设太低低 conf 的框通常不准。5.3 类别不匹配导致追踪错对象现象明明要追人结果追着背景里的车跑。原因预训练模型有 80 类你的场景里可能同时出现多个类别代码里取了 conf 最大的框但没过滤类别。解决在取框前先按类别过滤results.xyxy[0][results.xyxy[0][:, 5] target_cls]只保留你要追的类。如果自己训的模型只有一类这个问题自然没有所以做专用场景建议自己训。5.4 电量低时图传和指令延迟变大现象飞了几分钟后追踪变迟钝指令响应慢。原因Tello TT 电量低于 30% 后图传码率和电机响应都会降这是保护机制。解决别等低电量才测每次测试前充满单次飞行控制在 5 分钟内。另外get_battery()可以实时读电量低于 40% 就准备降落别硬飞。5.5 室内光流定位漂移导致追踪基准偏移现象飞机悬停时自己慢慢飘检测框跟着飘追踪目标越来越偏。原因Tello TT 室内靠光流和下视摄像头定位地面纹理少或者光线暗时定位会漂。解决在地面铺一块有纹理的垫子或者开灯保证光照充足。如果还是漂可以在控制里加一个基于画面中心的偏置补偿但治标不治本室内飞行尽量选纹理丰富的地面。6. 进阶技巧用 TensorRT 加速推理并把测距误差压到 10% 以内前面跑通之后如果帧率还是不够或者测距精度不满意可以往两个方向优化。第一个方向是推理加速。YOLOv5 支持导出 TensorRT 引擎在 NVIDIA 显卡上能比 PyTorch 快 2 到 3 倍。导出命令是python export.py --weights best.pt --include engine --device 0 --img 640导出后在代码里用torch.hub.load加载 engine 文件或者用trt运行时直接跑。注意 TensorRT 引擎和显卡架构绑定换机器要重新导出。第二个方向是测距标定。单目测距的误差主要来自焦距标定不准和目标姿态变化。我的做法是在 1 米、2 米、3 米三个距离各拍 20 张目标正面图用focal pixel_w * D / real_w算出 60 个焦距值取中位数作为标定焦距这样比单次标定稳。然后在实际测距时如果目标框的宽高比和标定时差异超过 30%说明目标侧对镜头这时候距离读数不可信直接丢弃或者只用来做前后速度的粗略控制。import numpy as np def calibrate_focal(samples): # samples: [(pixel_w, real_distance, real_w), ...] focals [pw * d / rw for pw, d, rw in samples] return np.median(focals) samples [(120, 1.0, 0.3), (60, 2.0, 0.3), (40, 3.0, 0.3)] focal calibrate_focal(samples) print(标定焦距:, focal)逻辑说明samples里每个元组是像素宽、真实距离、真实宽度。calibrate_focal算出每个样本的焦距取中位数避免个别异常值带偏。参数上real_w要量准目标宽度量错 1 厘米距离就错 3% 以上。标定完把focal写进配置后面测距直接用。最后一个习惯每次改完控制参数先在室内低高度0.5 米悬停测试确认飞机不抽风再放高。我踩过最大的坑就是参数没调好直接飞 2 米结果飞机追着目标撞墙。这套方案值不值得做取决于你是不是需要一个能闭环的真实设备验证环境如果只是跑通 YOLOv5 推理那不用无人机也能做但如果你要验证「感知到控制」的完整链路Tello TT 是成本最低的选择之一。希望帮到你。本文还有配套的精品资源点击获取