ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

YOLOv5+大疆Tello TT无人机目标识别追踪与测距实战

YOLOv5+大疆Tello TT无人机目标识别追踪与测距实战 简介这是一份基于YOLOv5与大疆教育无人机Tello TT的完整项目资源面向目标识别、检测与追踪测距方向适合正在做毕业设计、课程设计或需要项目实战的深度学习CV学习者。项目已针对旗、圈两类目标完成数据集标注、模型训练与调优并配套可操作文档可直接部署到Tello TT无人机上进行识别、追踪与测距实验也可在此基础上扩展训练其他目标。压缩包共1672个文件大小约269.18MB包含758个jpg图像、694个txt标签、94个yaml配置、50个py脚本、9个pt模型权重以及训练日志、说明文档等构成从数据到训练到部署的完整链路。资源已有285人学习内容涵盖源码、数据集、预训练模型、事件日志和目录结构等便于二次开发、对照复现与结果分析适合作为本科毕设或研究生课程项目的直接参考。1. 基于 YOLOv5 与 Tello TT 目标识别检测追踪测距这份 zip 的价值核心在哪一份写着“基于yolov5大疆教育无人机Tello TT实现目标识别检测追踪测距”的交付包递到你手上真正值钱的往往不是那几行模型代码而是把地面站视觉、飞控指令和几何测距串成一条闭环的那部分工作。很多人拿到手后第一件事就是把 YOLOv5 权重往 Tello TT 里塞这是最容易翻车的第一步因为 TT 板载算力根本跑不动神经网络。这个包能解决的事很具体让无人机通过摄像头看到目标、锁定它、跟住它并且告诉你它大概离你多远。适合正在做竞赛、课程设计或入门无人机视觉开发的开发者前提是你至少跑通过一次 YOLOv5 检测但对飞控通信和测距还很陌生。2. 拆开这份源码的目录结构YOLOv5、Tello TT 与追踪模块是怎么接起来的2.1 交付包内的四个组成与各自的运行位置以这类交付的常规组织形式看压缩包里通常会按“训练、部署、控制、文档”四条线展开目录名可能有差异但骨架逃不出这几个部分weights 放训练好的模型文件和预训练权重datasets 放图片和标签src 放检测脚本、追踪脚本、工具脚本docs 放操作说明。拿到包后不要急着跑先花半小时对照文档清点一遍这四类东西分别落在哪因为后续排错时你需要精确知道模型文件在哪、数据配置文件在哪、入口脚本又该从哪个目录启动。# 解压后用 tree 确认结构避免脚本里写死相对路径导致白屏 unzip tello_tt_yolov5.zip -d ~/tello_project cd ~/tello_project tree -L 2tree 输出能帮你快速核对入口脚本和权重文件是否在同级目录很多所谓“跑不起来”的问题其实是工作目录不对。常见结构是 weights/best.pt、datasets/data.yaml、src/detect_track.py、docs/操作说明.md 这样的平铺布局。注意查看数据配置文件 data.yaml 里的路径写法YOLOv5 的 train.py 和 detect.py 对相对路径很敏感如果把数据集放在了 datasets 下data.yaml 里的 train 字段通常要写 datasets/images。2.2 Tello TT 的系统架构为什么常用“地面站推理 飞控指令”而不在板载算力上直接跑Tello TT 的重量和供电决定了它不可能背一块 Jetson 或者 RK 系列开发板摄像头采集的画面通过 Wi-Fi 实时推流到地面站地面站用 YOLOv5 做推理再把控制指令通过 UDP 发回飞行器。这套“地面站推理 飞控指令”的架构是这个标题背后最稳定的从业方案。你只需要知道三个端口级别的约定控制指令走 UDP 8889视频流走 UDP 11111地面站通过 Wi-Fi 连接 Tello TT 的热点IP 常见为 192.168.10.1。# 连接 TT 的热点后先确认链路能通再跑代码 ping 192.168.10.1 -c 4ping 通只代表网络链路正常不代表视频流已经解出来更不代表飞控指令被接受。Tello TT 的热点一旦连上电脑通常会失去外网访问能力所以训练模型、安装依赖这些事情一定要在连接无人机之前全部做完我在实际项目中吃过这个亏现场连上飞机后想补装一个包结果断网卡了半小时。正确顺序是先在普通网络下把环境和模型备好再切换到 TT 热点进行联调。2.3 最小跑通环境安装、脚本入口与第一条检测命令在跑完整追踪链路之前先用一个最小的 Python 脚本确认两件事飞控指令链路能握手、视频流能持续解码。很多交付包会直接用 djitellopy 库封装 Tello TT 的通信这是目前最省事的做法它内部帮你处理了 UDP 指令的发送和视频流的读取。以下脚本只做连接和取画面不起飞这是整个项目里最安全的验证步骤。from djitellopy import Tello import cv2 tello Tello() tello.connect() # 握手指令发出后返回电池电量说明指令链路已通 tello.streamon() # 开启视频推流TT 会开始向地面站发送 H.264 帧 frame_read tello.get_frame_read() # 连续读 30 帧确认画面稳定不花屏、不黑屏 for _ in range(30): frame frame_read.frame if frame is not None: cv2.imshow(TT_VIDEO, frame) if cv2.waitKey(1) 0xFF ord(q): break tello.streamoff()这段代码的逻辑是把“飞控指令链路”和“视频链路”两个黑匣子分别验证掉。connect() 返回电量是握手成功的标志get_frame_read().frame 如果持续返回非空且画面不闪烁说明视频流解码正常。参数角度要留意的点是 streamon() 必须在 connect() 之后调用顺序反了偶尔会出现画面推流不启动的现象。cv2.waitKey(1) 的 1 毫秒延迟是 OpenCV 显示画面的常规写法改大了会让回传画面卡顿影响后面追踪循环的帧率稳定性。3. 训练自己的数据集数据格式、yolov5 超参数与轻量选型3.1 把 VOC/COCO 标注转成 YOLO 格式转换脚本与标签细节交付包自带的数据集往往已经转成了 YOLO 格式但你要针对自己的场景补充数据时就得处理标注格式转换。YOLOv5 训练要求每个图片对应一个同名 txt 标签文件每行是“类别索引、中心点 x、中心点 y、宽、高”前四个数值都归一化到 0 到 1 之间。这个归一化步骤是新人最容易踩坑的地方直接拿像素坐标写进去训练出来的模型检测框会全部跑偏。import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_path, class_names): 把 VOC 的 XML 标注转成 YOLOv5 的 txt 标注注意归一化到 [0,1] root ET.parse(xml_path).getroot() img_w int(root.find(size).find(width).text) img_h int(root.find(size).find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: continue box obj.find(bndbox) x_min, y_min int(box.find(xmin).text), int(box.find(ymin).text) x_max, y_max int(box.find(xmax).text), int(box.find(ymax).text) x_center (x_min x_max) / 2 / img_w y_center (y_min y_max) / 2 / img_h w (x_max - x_min) / img_w h (y_max - y_min) / img_h lines.append(f{class_names.index(name)} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines)) class_names [person, car] # 顺序一旦定下训练和推理阶段必须保持一致 voc_to_yolo(a.xml, a.txt, class_names)这段代码里有三个细节会影响成败。第一class_names 的顺序必须和训练配置里的 names 字段一致类别索引从 0 开始顺序错了模型会张冠李戴第二归一化时中心点和宽高都要除以原图尺寸如果图片在标注后被裁剪过尺寸就要用裁剪后的值第三碰到不认识的类别名直接 continue这会导致部分标注被静默丢弃转完后最好统计一下各类别数量。建议转完之后随便打开一个 txt 看数值坐标值都应该在 0 到 1 之间如果出现大于 1 的数字基本就是分子分母用错。3.2 训练自己的数据集yolov5 超参数里最值得先动的三个参数数据就位后就是训练环节。YOLOv5 官方仓库提供了一套默认超参数直接跑往往也能出结果但要让模型在各种光照、飞行视角下稳定检测就得理解超参数文件里最值得先动的三个值imgsz、batch、hyp 文件里的学习率。imgsz 决定输入分辨率Tello TT 的视频流单帧清晰度有限image640 是常用的平衡点太小容易漏检小目标太大只会拖慢推理速度。batch 由显存决定训练时如果显存不够就报 CUDA out of memory这时候不是调代码是调 batch。python train.py \ --data datasets/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --hyp hyp.scratch-low.yaml \ --project runs/train \ --name tello_person训练命令看着简单但有几个参数之间的联动关系要先理清楚。data.yaml 里的 train 和 val 路径要写绝对路径或相对 train.py 的路径很多“训练开始后找不到图片”的报错都是路径写错造成的。hyp.scratch-low.yaml 是官方给低配置设备准备的超参数文件里面 lr0 初始学习率偏低适合数据量不大的场景如果数据量超过几千张可以换 hyp.scratch-high.yaml。训练开始后不要只盯着 loss 曲线第一轮结束要看验证集的 mAP 是否为正如果前几轮 mAP 一直为零问题大概率在数据标注而不是模型结构。训练中途可以在每个 epoch 结束后打开 runs/train/tello_person 目录下的 batch 图片YOLOv5 会画出来增强后的标注框这能直观确认标签是否对齐图片内容。3.3 模型选型为什么 Tello TT 的项目里常见 yolov5s/nano 而不是 l/x训练目标检测模型时大家总想用更大的网络拿更高精度但放在 Tello TT 项目里模型选型必须服从两个约束推理延迟和部署平台算力。YOLOv5l 或 YOLOv5x 在桌面 GPU 上可能只需几十毫秒但一旦你打算把推理放在电脑上跑中低端 CPU 或者核显就要面对每帧几百毫秒的推理时间整个追踪闭环根本稳不住。常见做法是选 yolov5s 或 yolov5n前者精度尚可后者速度最快配合 416 或 320 的输入分辨率能把单帧推理压到 30 毫秒以内。如果你后续想把整套视觉方案从电脑挪到边缘设备上比如在树莓派 4B 上部署 YOLOv5或者往 RK3568 这类芯片上做量化推理模型体量会更敏感。树莓派 4B 的 CPU 跑 YOLOv5s 大约只有几 FPS这时候就得考虑用 n 系列加 INT8 量化把权重从 FP32 压到 INT8速度能提上去但 mAP 会掉几个点。所以在这个项目里模型选型的顺序是先定推理平台再定模型大小最后才定超参数。不要拿一张 4K 图片的完美 mAP 数字来衡量一个跑在实时视频流里的系统那只会让你在真机调试时崩溃。4. 追踪与测距的联动实现PID 控制、单目测距与边界条件4.1 用中心点偏移驱动 Tello TT追踪循环的代码骨架与 PID 初值追踪的本质是让目标一直待在画面中心附近。检测模块输出目标框后算出目标框中心与画面中心的像素偏差再用 PID 控制器把这个偏差映射成 Tello TT 的左右飞行速度。这个闭环的难点不在 PID 公式本身而在响应链路的延迟摄像头采集一帧、YOLOv5 推理一帧、UDP 指令到达飞控、飞控调整姿态整条链路延迟可能到一两百毫秒PID 参数调不好就会来回振荡像喝醉了一样左右晃。import cv2 from djitellopy import Tello tello Tello() tello.connect() tello.streamon() frame_read tello.get_frame_read() kp 0.35 # 比例系数偏差越大速度越快太大容易冲出画面 kd 0.15 # 微分系数抑制振荡但过大会让响应变迟钝 prev_err_x 0 def get_target_box(frame): # 这里替换成你自己的 YOLOv5 推理调用 boxes detect(frame) # 返回结构为 [x, y, w, h, conf] return boxes[0] if len(boxes) 0 else None while True: frame frame_read.frame if frame is None: continue box get_target_box(frame) if box is not None: target_cx box[0] box[2] / 2 err_x target_cx - frame.shape[1] / 2 speed_x kp * err_x kd * (err_x - prev_err_x) speed_x max(-60, min(60, int(speed_x))) # 限幅防止速度突变 tello.send_rc_control(speed_x, 0, 0, 0) prev_err_x err_x else: tello.send_rc_control(0, 0, 0, 0) # 丢目标时立刻悬停别乱飞send_rc_control 的参数依次是左右速度、前后速度、上下速度、偏航角速度取值范围在 -100 到 100 之间。代码里把 speed_x 限幅到 ±60是因为 Tello TT 对突变的控制指令响应很差速度给满 100 很容易让画面剧烈晃动YOLOv5 跟着丢检测。PID 初值 I 项故意没写这里用的是 PD 控制器无人机视觉追踪场景里 I 项积出来的历史误差反而会让飞机越冲越远所以实践中普遍先把 I 项置零后续如果出现静态误差再慢慢加。还要注意循环体里没有加 sleep实际跑的时候帧率可能冲到几十 FPS建议用 time.sleep 把控制周期稳在 10 到 15 毫秒控制周期不均匀比控制参数不准更致命。4.2 单目测距公式用目标先验宽度 针孔相机模型估算距离Tello TT 没有前置激光雷达想测目标距离只能用单目几何方案。核心思路是针孔相机模型一个已知真实宽度的物体在画面里占的像素宽度与其到相机的距离成反比。公式很简单距离等于“真实宽度乘焦距除以像素宽度”。这里面最不稳定的变量是焦距焦距既可以通过标定得到也可以用相机水平视场角 FOV 和图像宽度推算两种方法各有各的使用前提。import math def estimate_distance(pixel_width, real_width_mm, focal_px): 单目测距distance 真实宽度 * 焦距 / 像素宽度 if pixel_width 5: return None # 目标太小像素量化误差会被放大 distance_mm real_width_mm * focal_px / pixel_width return distance_mm / 1000.0 # 由水平视场角估算焦距focal_px (图像宽 / 2) / tan(FOV / 2) fov_deg 60.0 # 以你的相机标定值为准 image_width 640 focal_px (image_width / 2) / math.tan(math.radians(fov_deg / 2)) dist estimate_distance(pixel_width180, real_width_mm450, focal_pxfocal_px) print(festimated distance: {dist} m)这里的参数有两个必须自己确定的量real_width_mm 是你要识别目标的真实宽度比如识别人的话可以按肩宽 400 到 500 毫米做先验识别车辆就要按车宽focal_px 建议用 OpenCV 的棋盘格标定得到经验公式得出的焦距只适合做粗略估计。pixel_width 取的是检测框的宽目标一旦转身或者侧对镜头像素宽度会突变测距结果跟着跳变。所以单目测距在工程上更适合做“近距离范围内的粗略测距”拿来辅助判断要不要减速可以拿来做精密落点控制就超出它的能力边界了。代码里对小于 5 像素的目标直接返回 None是因为这种尺寸下检测框抖一两个像素算出来的距离能差出好几倍。4.3 追踪测距的联动逻辑与三个边界条件追踪和测距不是两个独立模块在无人机控制上它们必须联动。常见做法是把飞行逻辑拆成两个阶段远距离时以追踪为主让目标先稳定在画面中心当测距结果显示进入设定范围后再切换为慢速逼近。这段联动逻辑里最怕的是检测框轻微抖动就触发速度突变所以要对测距结果做滤波。# 对连续帧的测距结果做滑窗均值抑制单帧跳变 dist_history [] def smooth_distance(distance, window5): if distance is not None: dist_history.append(distance) if len(dist_history) window: dist_history.pop(0) if len(dist_history) 3: return None return sum(dist_history) / len(dist_history)三个边界条件要在写代码时提前想好。一是目标丢失后的行为丢目标超过几十帧必须自动悬停而不是原地打转二是距离太近时的行为单目测距在 2 米以内误差急剧下降建议设定最小安全距离到了就直接停止前飞三是画面突然过曝或暗光时的行为YOLOv5 检测框会在这些时候频繁消失追踪循环要做目标重捕获而不是直接崩溃。这套联动逻辑看起来不复杂但每条边界都对应一次真机炸机风险我见过不少项目代码能跑却因为没处理丢目标后的控制指令飞机在室内直接撞墙。5. 常见问题排查追踪掉线、检测抖动的五个踩坑记录5.1 现象模型权重加载直接报错下载的 .pt 文件打不开加载权重时报出类似 KeyError 或者“attempt to load a saved model before the epoch count”的错误通常是 YOLOv5 版本不匹配造成的。YOLOv5 迭代很快不同 tag 下保存的权重结构有差异旧代码加载新权重就会报错。解决方法是先固定 YOLOv5 仓库的版本再加载训练和推理必须用同一个版本如果交付包给了特定的 requirements.txt就严格按里面的 torch 和 torchvision 版本装不要用最新的 torch 去尝试兼容老权重。这个坑看起来低级但在实际交付中占到故障率前三。5.2 现象无人机悬停时画面正常一给速度就花屏或延迟变大画面在静止时流畅一旦发送 rc 控制指令视频流就开始卡顿原因通常是 Wi-Fi 链路同时承载控制指令和视频流飞行姿态变化导致天线位置和信道质量波动。TT 的图传本身就不是为高机动场景设计的所以很多代码里会在发送速度指令时降低视频码率。解决手段是先把追踪速度限制在 ±60 以内并降低取帧分辨率到 640×480必要时可以尝试重新连接热点。不要在飞行过程中指望视频流和指令流完全互不干扰这是物理限制不是代码 bug。5.3 现象检测框稳定但追踪一直左右振荡飞机像喝醉一样检测框没有跳变说明模型推理侧没有问题问题出在 PID 控制回路的参数或者控制周期上。左右振荡最常见的两个原因比例系数 kp 过大微小的中心偏差直接给了很大的速度或者循环帧率不稳定上一帧间隔 10 毫秒、下一帧间隔 50 毫秒微分项被瞬时拉爆。解决方式是先把 kd 降到 0只留 kp从小往大调等飞机能缓慢纠正偏差后再加 kd 抑制过冲。同时在循环里加上固定 sleep把控制周期钉死在 15 毫秒左右。5.4 现象单目测距数值忽大忽小根本没法用来做决策测距结果跳变通常来自两个源头检测框宽度的像素级抖动以及目标本身姿态变化导致的宽度变化。检测框抖动可以用滑窗均值滤波压掉但如果目标从正面转成侧面真实宽度先验本身就失效了滤波也救不回来。解决方式是对测距结果做限幅相邻帧测距变化超过 30% 的直接丢弃连续 5 帧稳定后才输出。工程上还要明确告诉使用者这套测距的适用范围比如 1 到 5 米内可用超出范围只做参考。5.5 现象训练 loss 正常下降但验证集一个目标都检测不出来模型训练过程看着正常loss 也降了推理却全无输出这种翻车现象十有八九是数据配置问题。最常见的是图片和标签文件没有对齐有些图片缺少对应的 txt 文件YOLOv5 默认跳过无标签图片导致有效训练样本骤减另一个常见原因是 data.yaml 里的类别顺序和标注脚本里的 class_names 不一致模型学到的类别索引是 A推理时映射到 B。解决方式是用一个小脚本检查每个图片是否都有同名 txt再统计所有 txt 里的类别索引是否都落在合法范围内确认后再重新训练。这种问题靠看 loss 曲线永远看不出来只能靠数据检查。6. 先别急着上真机用离线视频回放验证追踪与测距链路真机调试的成本很高每飞一次都要承担摔机风险所以在把代码交给 Tello TT 之前我习惯先录一段真机拍摄的视频然后让追踪代码跑在这段视频上做“回放验证”。具体做法是把视频文件路径当作输入源把 send_rc_control 的输出打印在画面上而不是真的发给无人机这样能安全观察 PID 输出是否合理、测距是否稳定。如果回放时 PID 输出一直在剧烈变化就说明参数需要调消耗的只是几分钟视频时间而不是一块备用桨叶。回放验证通过后再到空旷场地进行低高度、慢速度的实测。验证维度观测指标验收参考检测精度目标框抖动幅度单帧像素偏移不超过 20px追踪响应PID 输出变化频率无明显高频振荡测距稳定相邻帧距离跳变跳变不超过 20%指令安全丢目标时输出落回 0 速度悬停回放验证时录视频的视角要和飞行视角一致尽量记录目标从远到近、从画面边缘到中心的过程这样测距函数和 PID 的边界都能覆盖到。验证通过后再把控制输出接到真机上先让飞机原地悬停跑通指令链路再逐步放开移动控制。我踩过最狠的一次是把没经过回放验证的代码直接上真机目标丢了以后飞机往前猛冲最后撞在墙上。那之后我的习惯是先录三段不同光照、不同距离的视频在电脑前把参数收敛好再起飞。希望这套验证流程能帮你少走同样的一段弯路让你在下一次飞行前就对追踪测距代码的边界心里有数。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进