ARTICLE DETAIL

资讯详情

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

yolov5+Tello TT实现目标识别追踪测距全链路解析

yolov5+Tello TT实现目标识别追踪测距全链路解析 简介面向无人机视觉开发与目标检测方向学习者的一套完整项目包基于YOLOv5算法和大疆教育无人机Tello TT覆盖目标识别、检测、追踪以及测距等核心功能。压缩包共1672个文件其中758张JPG图片构成训练与验证数据集694个TXT标注文件保存目标框信息94个YAML配置用于网络与训练参数50个Python脚本完成数据加载、模型训练和实时推理另有9个预训练模型供直接调用整体约269.35MB还包含TensorBoard事件日志便于查看训练过程。已有461人学习/下载适合作为毕业设计、期末大作业或课程设计的高分参考也适用于无人机目标检测项目入门。项目内含完整源码、数据集、训练好的模型和说明文档注释详细且结构清晰新手也可理解该方案为作者手工打造并获导师认可简单部署后即可运行。1. 基于 yolov5 Tello TT 实现目标识别追踪测距先把它当成一条链路而不是一堆代码Tello TT 到手第一天照着官方 Python 示例大家都能让它起飞、拍照、识别颜色。但需求一旦换成“识别出操场上的红色交通锥自动跟着它飞并实时测出距离”大多数人就卡住了。卡点很有共性yolov5 在电脑上能跑一上机就卡成幻灯片测距数值疯狂跳变追踪时飞机左右乱晃。这篇文章把基于 yolov5 加 Tello TT 实现目标识别检测加追踪测距的完整链路拆开讲从数据集整理、模型训练与导出到 SDK 接入、单目测距和 PID 追踪闭环每一步都给可复现的做法和参数再附上我踩过的高频坑。适合做毕设、竞赛和算法验证的开发者照步骤走一遍就能看清这套方案的所有边界。2. 先做减法yolov5 在 Tello TT 上有三条落地路线选哪条看延迟Tello TT 本质上是个教育平台飞行本体、一个开放了 SDK 2.0 的通信接口、一套基于 PX4 体系的开源飞控。想把它变成“会看会追的无人机”yolov5 只是其中一环真正决定成败的是模型跑在哪、控制指令怎么回流。很多人把源码下载下来就急着跑忽略了链路里每一级延迟的叠加项目最后死在“识别出来了但飞机反应不过来”上。所以在动手写代码前先搞清楚三条路线各自的边界。yolov5 部署本身不难难的是在 Tello TT 这套 Wi-Fi 通信和有限算力下做出能用的实时闭环。2.1 三条路线地面站识别、机载端识别、混合识别常见做法是先从地面站识别起步等算法稳定再考虑把模型压到机载端。三条路线的对比如下路线算力来源典型延迟优点适合场景地面站识别笔记本/台式机跑 yolov5视频经 Wi-Fi 回传控制指令 UDP 回传图传 100ms 左右加推理和回传实测 150~300ms能跑 yolov5m/l调试直观能看到每一帧检测结果毕设功能验证、算法演示、追踪闭环调参机载端识别TT 扩展接口上挂树莓派 Zero 2W、树莓派 5 或 Jetson机载推理省掉视频上行整体可压到 80ms 内实时性好不受地面站链路影响竞赛、室外小范围自主跟随混合识别机载粗检做初步定位关键帧回地面复核取决于策略通常 100~200ms精度与实时性折中竞赛卡点场景工程量最大我一般让新手从第一条路线起步因为它能实时看到检测框和测距数值所有问题都能可视化排查。等地面站闭环稳定后再把模型换成量化版搬到机载端。树莓派 5 上部署自己训练的 yolov5 模型思路和这里完全一致差异只在于把环境装进 ARM 版 Linux以及供电和散热要额外处理。2.2 选型前先看 yolo v5 网络结构图到底谁在吃算力yolov5 的 s/m/l 体量差别主要来自 Backbone 里 C3 模块堆叠的数量和通道数。一张 yolo v5 网络结构图里真正推高推理耗时的是 Backbone 的 C3 和 Neck 里的多次特征融合而不是最后的 Detect 头。这意味着如果你只把输入从 640 降到 320推理时间大约能降四分之一甚至更多代价是远距离小目标会漏检。Tello TT 地面站识别可以跑 yolov5s 甚至 m机载端基本只能跑 yolov5n并且要做 int8 量化。模型体量大致如下模型参数量CPU 上 ONNX int8 推理延迟320 输入是否适合 Tello TT 机载yolov5n约 1.9M40~60ms适合yolov5s约 7.2M150~250ms勉强帧率太低yolov5m约 21.2M500ms 以上不适合选型时不要只看参数量要看目标场景里目标的尺寸。TT 的摄像头视角比较广如果目标是 5 米外的交通锥320 输入下可能只有十几个像素高yolov5n 很容易漏。这种场景我会保留地面站跑 yolov5s只在近距离追踪时切机载端。2.3 项目目录怎么摆源码、数据集、模型、文档按这个结构分拿到一个所谓“完整源码包”时我习惯先重排目录。一个能长期维护的 Tello TT 识别项目应该把训练、部署、控制三件事彻底分开否则后面调 PID 和调模型的人会互相踩脚。tello_yolov5/ ├── src/ │ ├── train/ # 训练脚本、数据集配置、超参数 │ ├── deploy/ # 地面站/机载推理、onnx 加载 │ └── control/ # SDK 通信、PID、追踪测距 ├── datasets/ # 数据集images labels ├── weights/ # 训练好的模型、onnx 文件 ├── docs/ # 说明文档、标定记录 └── requirements.txt # 锁定依赖版本目录看起来简单但绝大多数翻车项目都是把训练脚本、推理脚本、控制脚本堆在一个文件里改一次模型权重就要改代码。把weights和src/deploy分开后换模型只动配置不碰逻辑。docs里我还会放一张标定记录表记录焦距、测试距离和现场光照这个习惯后面会救你一次。3. 训练一个能稳定用的 yolov5 模型环境配置、数据格式和超参数陷阱很多人拿到现成训练好的模型就直接跑发现识别效果和自己场景不匹配要识别红色锥桶模型却是在安全帽数据集上训练的。所以标题里“完整源码加数据集加训练好的模型”这套组合正确用法是把训练链路自己走一遍模型只是起点数据才是决定项目上限的东西。这一章按 yolov5 训练自己的数据集的完整流程走覆盖环境配置、标注数据整理、超参数调整和模型导出。整个过程不需要 GPU 也能完成只是慢一些。3.1 conda 配置 yolov5 环境的三板斧先说环境。yolov5 官方仓库的requirements.txt会把 torch、torchvision、opencv 等版本锁死直接用 conda 建一个干净环境是最省事的。conda create -n tello_yolo python3.8 -y conda activate tello_yolo git clone yolov5 官方仓库 yolov5 cd yolov5 pip install -r requirements.txt参数说明Python 版本固定 3.8 是我的习惯3.9、3.10 也能跑但有些旧版 onnx 转换脚本在 3.10 上会报distutils错误没必要在这个地方浪费时间。如果你的机器没有 NVIDIA GPU先把requirements.txt里带cu后缀的 torch 卸载再装 CPU 版pip install torch2.0.1cpu torchvision0.15.2cpu -f https://download.pytorch.org/whl/torch_stable.html这里的版本号要以你 clone 的 yolov5 仓库要求为准不要盲抄。装完跑python detect.py --weights yolov5s.pt --source data/images/bus.jpg能出框说明环境通了。这一步是后面所有工作的地基环境装不好后面每跑一个脚本都在还债。3.2 数据集整理与格式转换VOC 转 YOLO 的四个边界坑yolov5 训练需要的数据集结构如下路径关系必须严格一致datasets/ └── ttdetect/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── ttdetect.yamlttdetect.yaml写法和 coco.yaml 类似指定路径、类别数和类别名即可。数据标注如果用的是 LabelImg 的 VOC 格式需要转成 YOLO 的 txt 格式转换脚本的关键逻辑是把 xml 里的xmin,ymin,xmax,ymax归一化# voc2yolo.py 关键片段 import xml.etree.ElementTree as ET # 读取 VOC 格式标注 root ET.parse(xml_path).getroot() size root.find(size) w, h int(size.find(width).text), int(size.find(height).text) for obj in root.iter(object): cls obj.find(name).text box obj.find(bndbox) xmin int(box.find(xmin).text) xmax int(box.find(xmax).text) ymin int(box.find(ymin).text) ymax int(box.find(ymax).text) # 转换成 yolo 格式中心点 宽高全部归一化 cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h with open(txt_out_path, a) as f: f.write(f{class_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}\n)逻辑说明这段代码把 VOC 的绝对像素坐标转成 0~1 的归一化坐标。yolov5 训练时读取的就是这个 txt 文件每一行是“类别 id 中心点 x 中心点 y 框宽 框高”。这里有几个边界坑都是实际翻车翻出来的第一w和h必须取原图尺寸不能取标注框的尺寸否则归一化全错。第二txt 文件和对应图片必须同名图片是frame_0001.jpg标注就必须是frame_0001.txt。第三类别 id 从 0 开始和 yaml 里names的顺序一一对应。第四如果一张图里有两个相同目标txt 里就有两行行数和目标数要一致。转换完随机抽十张图用python detect.py跑一遍看看框位置比任何检查脚本都管用。3.3 yolov5 超参数到底该调什么别一上来就动学习率yolov5 的超参数写在data/hyps/hyp.scratch-low.yaml里默认值对大多数场景已经够用。新手最常见的错误是看到别人改了什么就跟着改结果模型训飞了。真正要关注的其实就几个lr0: 0.01 # 初始学习率 lrf: 0.01 # 最终学习率 lr0 * lrf momentum: 0.937 # SGD 动量 weight_decay: 0.0005 warmup_epochs: 3.0 # 前 3 个 epoch 学习率从 0 慢慢升到 lr0参数说明lr0决定模型收敛快慢Tello TT 场景数据集通常只有几百到几千张0.01 是稳妥起点不要直接加到 0.1小数据集会发散。warmup_epochs保持 3.0它让模型在头几个 epoch 不会因为大梯度震荡。如果你的数据集只有一两百张weight_decay可以适当调到 0.0003 减少正则化压力。我一般只调三个东西batch-size、imgsz、epochs。batch-size 根据显存来16G 显存跑 yolov5s 用 32 没问题显存不够就降到 8同时把学习率按比例降一点。imgsz 先用 640 训练导出部署时再降输入尺寸不要在训练阶段用小图训练大图检测效果会变差。3.4 训练、看报表和导出 onnx 部署模型训练命令如下--data指向刚才的 yaml--weights选择预训练权重python train.py \ --data datasets/ttdetect/ttdetect.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --imgsz 640 \ --device 0 \ --project runs/tt_detect训练完成后runs/tt_detect/exp/下有两个必看的文件results.png和weights/best.pt。results.png里看val/box_loss和mAP0.5两条曲线如果mAP0.5还在涨但val/box_loss已经开始反弹说明过拟合了此时应该用倒数几十个 epoch 的权重而不是最后一个 epoch 的权重。这个细节能让你的模型在陌生光照下稳定很多。部署到 Tello TT 侧我一般导出 ONNX 格式它比 PyTorch 原生权重更适合 CPU 推理也更方便做 int8 量化python export.py \ --weights runs/tt_detect/exp/weights/best.pt \ --include onnx \ --imgsz 320 \ --opset 12参数说明--imgsz 320是部署时的输入尺寸和训练时的 640 不一致会让精度略降但换来的是 CPU 推理速度翻几倍。如果你机载端跑的是树莓派 5这个尺寸基本是 yolov5n 能实时跑的甜点值。--opset 12是 ONNX 算子集版本onnxruntime 对 12 支持最稳没必要追新。4. 上机把 yolov5 接到 Tello TT 视频流上让检测框变成飞行指令训练好的模型只是眼睛要让 Tello TT 动起来还需要把视频流接进来、把检测结果转成控制指令。这一章是整套源码里最核心的部分也是“目标识别检测加追踪测距”真正落地的地方。先说清一个概念Tello TT 的 SDK 是 UDP 通信视频流走 H.264控制指令是文本命令。整套链路可以抽象为“取帧 → 推理 → 计算偏差和距离 → 发送 rc 指令 → 延时”下面按这个顺序拆。4.1 SDK 通信UDP 8889 发指令UDP 11111 收视频流Tello TT 上电开热点后电脑连接它的 Wi-FiIP 一般是192.168.10.1。SDK 指令通过 UDP 8889 端口发送视频流在streamon后从 UDP 11111 端口到达。先看一段最小通信代码# tello_sdk.py import socket import threading import time class TelloSDK: def __init__(self, ip192.168.10.1, cmd_port8889): self.ip ip self.cmd_port cmd_port self.sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock.bind((, cmd_port)) self.sock.settimeout(1.0) self.last_response def send_cmd(self, cmd): self.sock.sendto(cmd.encode(utf-8), (self.ip, self.cmd_port)) try: self.last_response, _ self.sock.recvfrom(128) except socket.timeout: self.last_response timeout return self.last_response.decode(utf-8).strip() def takeoff(self): return self.send_cmd(takeoff) def land(self): return self.send_cmd(land) def rc_control(self, lr, fb, ud, yaw): # 四个通道左右、前后、上下、偏航范围 -100~100 return self.send_cmd(frc {lr} {fb} {ud} {yaw}) def stream_on(self): return self.send_cmd(streamon)逻辑说明rc_control是追踪的核心接口四个参数分别控制左右平移、前后俯仰、上下升降和机身旋转。调用takeoff前必须先发一次command进入 SDK 模式否则飞机会忽略所有指令。每次发送后都要读一次回包Tello TT 的回包是串行处理的前一条没收到响应就发下一条容易丢命令。一个容易被忽略的点是timeout处理。Tello TT 在信号差时偶尔不回包重发机制必须加上否则控制链路卡死一次PID 积分项就会被空洞拉偏。4.2 实时识别循环PyAV 解码 H264ONNX Runtime 跑推理Tello TT 的视频流是裸 H.264OpenCV 的VideoCapture直接读 UDP 流经常花屏。我一般用 PyAV 解码它对 H.264 裸流的兼容性好很多# deploy/realtime_infer.py import av import cv2 import numpy as np import onnxruntime as ort # 加载导出的 onnx 模型 session ort.InferenceSession(weights/best.onnx, providers[CPUExecutionProvider]) input_name session.get_inputs()[0].name input_size session.get_inputs()[0].shape[-1] # 导出的输入边长 # PyAV 打开 Tello 视频流 container av.open(udp://0.0.0.0:11111) def infer_frame(img): # 预处理resize RGB 归一化 resized cv2.resize(img, (input_size, input_size)) rgb cv2.cvtColor(resized, cv2.COLOR_BGR2RGB) blob np.expand_dims(np.transpose(rgb, (2, 0, 1)), axis0).astype(np.float32) blob / 255.0 # 推理输出 [1, 25200, 5nc] 的检测结果 outputs session.run(None, {input_name: blob})[0] return postprocess(outputs, img.shape, input_size) for frame in container.decode(video0): img frame.to_ndarray(formatbgr24) detections infer_frame(img) # 画框和距离标注 for x1, y1, x2, y2, score, cls_id in detections: cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, f{score:.2f}, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow(tello_yyolov5, img) if cv2.waitKey(1) 0xFF ord(q): break container.close() cv2.destroyAllWindows()逻辑说明onnx 模型的输出是[1, 25200, 5nc]的原始预测需要做置信度过滤和 NMS 非极大值抑制这部分可以直接复用 yolov5 仓库utils/general.py里的non_max_suppression函数不要自己造轮子。postprocess里要把 320 输入下的坐标按原图比例缩放回来否则框会偏到左上角。性能优化上这是最容易掉帧的地方。我一般做三件事第一container.decode出来的frame是 PyAV 的 VideoFrame转ndarray时显式指定formatbgr24避免多余的颜色转换第二直接对 320 输入推理不要先显示 720 的图再缩放缩放放在画框前做第三把推理线程和画框线程分开用queue.Queue传递检测结果解码和推理并发执行帧率能提升 30% 以上。4.3 单目测距一个公式两个前提一组滤波Tello TT 没有激光雷达测距靠单目视觉。常见做法是基于相似三角形的单目测距公式distance (known_width * focal_length) / pixel_widthknown_width是目标的真实宽度比如红色交通锥的宽度 25cmfocal_length是焦距像素单位pixel_width是检测框在图像里的像素宽度。焦距必须先标定标定方法是把目标放在一个已知距离比如 1m测出当时的像素宽度反推焦距# 标定焦距 real_distance 1.0 # 目标离镜头 1 米 known_width 0.25 # 目标真实宽度单位米 pixel_width 180 # 检测框像素宽度从 yolov5 输出得到 focal_length (pixel_width * real_distance) / known_width print(ffocal_length {focal_length:.2f} px)这个公式有两个前提一是目标真实宽度已知且检测框宽度能代表目标实际宽度二是目标平面与镜头光轴垂直。Tello TT 追踪时目标一旦斜着对着镜头宽度就会“变短”距离就会算远。所以测距值必须滤波我用的是一阶 EMA 加中值滤波# 一阶 EMA 滤波 alpha 0.4 dist_smooth alpha * distance_raw (1 - alpha) * dist_smoothalpha越大跟随越快但噪声也越大Tello TT 悬停时会轻微晃动我一般取 0.3~0.5。如果单帧检测框突然消失我选择保持上一帧的dist_smooth值而不是输出 0否则 PID 的纵向通道会被打乱。这个“保持上一帧”的小习惯能让追踪脚本的鲁棒性提升一大截。4.4 PID 追踪闭环从检测框中心到 rc 指令追踪的本质是让检测框中心尽量靠近图像中心同时保持期望距离。偏差量有两个横向偏差offset_x和距离偏差offset_distance。PID 控制器把偏差映射成 rc 指令# control/pid.py import time class PID: def __init__(self, kp, ki, kd, output_limit100): self.kp kp self.ki ki self.kd kd self.output_limit output_limit self.integral 0.0 self.last_error 0.0 self.last_time time.time() def update(self, error): now time.time() dt now - self.last_time if dt 0: dt 1e-3 self.integral error * dt derivative (error - self.last_error) / dt output self.kp * error self.ki * self.integral self.kd * derivative self.last_error error self.last_time now # 输出限幅避免指令过大导致飞机姿态突变 return max(-self.output_limit, min(self.output_limit, output))追踪主循环如下# control/follow.py pid_yaw PID(kp0.35, ki0.01, kd0.08, output_limit40) pid_dist PID(kp0.50, ki0.02, kd0.10, output_limit30) target_distance 1.5 # 期望距离 1.5 米 frame_center_x 320 # 图像中心 x按实际分辨率设置 while True: dets get_latest_detection() # 从推理线程拿最新检测结果 if dets is None: tello.send_cmd(rc 0 0 0 0) # 没有目标悬停 time.sleep(0.05) continue x1, y1, x2, y2 dets[0][:4] # 取置信度最高的目标 box_center_x (x1 x2) / 2 box_width_px x2 - x1 error_x (box_center_x - frame_center_x) / frame_center_x # 归一化 distance_raw (known_width * focal_length) / box_width_px dist_smooth alpha * distance_raw (1 - alpha) * dist_smooth error_dist dist_smooth - target_distance yaw_out pid_yaw.update(-error_x) fwd_out pid_dist.update(error_dist) # 左右通道用 yaw前后通道用 fwd tello.rc_control(int(yaw_out), int(fwd_out), 0, 0) time.sleep(0.05) # 控制频率 20HzTT 的响应速度承受不了更高频率参数说明PID 的四个初值是我在 Tello TT 上试出来的基线yaw 通道kp0.35比较保守不会让机身转得发飘距离通道kp0.50稍高因为前后平移响应比 yaw 慢需要更大增益才能跟上目标。output_limit限幅一定要加Tello TT 的 rc 指令范围是 -100~100但实际飞行中超过 60 的输入会让姿态变化剧烈室内很容易撞墙。time.sleep(0.05)把控制频率压在 20Hz再快的话指令排队飞机会像喝醉一样左右抽搐。这里强调一个安全习惯第一次跑追踪闭环把桨拆掉手持飞机对着目标比划看电机转速是否朝正确方向变化。方向没搞对就上桨试飞基本等于送修。5. 避坑 / 常见问题排查yolov5 Tello TT 的高频翻车点下面这几条坑覆盖了从环境配置到飞行闭环的整个链路。每一条都是真实发生过的按“现象 → 原因 → 解决”的顺序写方便直接对照排查。5.1 环境、训练与模型导出的坑坑 1conda 环境重装三次import torch 还是报错。现象conda create -n tello_yolo python3.8之后安装 requirements运行时提示libtorch.so 找不到或者undefined symbol。原因最常见的是 torch、torchvision、Python 版本三者不匹配。yolov5 的 requirements.txt 只锁了最低版本没有锁 Python 版本而 PyTorch 官方预编译包对 Python 版本有严格要求3.8 上装了为 3.10 编译的 whl 就会出这种问题。解决不要手动pip install torch先把 conda 环境删掉重建然后按顺序执行pip install -r requirements.txt让 pip 自己解析匹配版本。如果 conda 源不行用 pip 加-i换国内源重装。装完用python -c import torch; print(torch.__version__)验证能出版本号再继续下一步。坑 2训练时 loss 下降得很快但 mAP 一直不动。现象results.png里train/box_loss从第一轮就垂直下降但mAP0.5几十个 epoch 都是 0 或者极低。原因八成是标签文件的问题。用自写脚本从 VOC 转 YOLO 时类别 id 和names顺序没对齐或者标签里的坐标出现负数、大于 1 的越界值。yolov5 训练时不会因为标签非法而报错它只会默默忽略这些目标结果就是模型学了个空气。解决训练前写个 3 行的小脚本读一个 label 文件打印内容肉眼确认坐标都在 0~1 之间类别 id 没超范围。再把ttdetect.yaml里的names和标注工具的类别列表对齐。这是所有人都应该做但大多数人都不做的步骤省掉它的代价是白练一个晚上。坑 3导出的 onnx 在本地测试正常部署到机载端输出全成 0。现象onnxruntime 加载模型能跑但输出的检测框全空置信度全是接近 0 的值或者形状和预期不符。原因导出的输入尺寸和部署时预处理的尺寸不一致。export.py里--imgsz 320导出的是 320 输入部署代码如果cv2.resize到 640模型推理时虽然也能出结果但特征分布完全错位。另一个原因是 onnx 的输出层没有做 NMSsession.run拿到的原始输出需要后处理而有些人直接把它当最终框用了。解决固定一个输入尺寸训练、导出、部署三处的imgsz和resize保持一致。尺寸以导出的为准部署代码里动态读取session.get_inputs()[0].shape来设置resize目标从根上杜绝不一致。5.2 视频流、测距与追踪闭环的坑坑 4视频流花屏、卡住container.decode直接卡死。现象PyAV 打开 UDP 11111 端口后前几帧正常几秒后画面变成绿屏或者for frame in container.decode()一直阻塞不返回新帧。原因Tello TT 的 UDP 视频流在弱信号下必然丢包H.264 的 I 帧一旦丢了解码器要等下一个关键帧才能恢复而默认的 GOP 间隔可能长达几秒。阻塞则是 PyAV 默认的缓冲区读满后没数据导致解码线程挂起。解决打开视频流时设置超时和丢包容忍参数比较稳的做法是用 PyAV 的container.options设置ffmpeg的timeout和reorder_queue_size。另外把解码放到独立线程主循环只从队列取最新帧即使解码器暂时卡住追踪循环也不会被拖死。流中断后自动重连的逻辑也要写检测到连续 N 秒无帧就先streamoff再streamon实测 90% 的花屏能靠这招自救。坑 5测距数值跳变距离忽远忽近。现象目标静止不动但测距输出在 0.8 米到 2.5 米之间来回跳同一个位置每次测的结果都不同。原因有三个叠加因素。第一yolov5 的检测框每帧都在轻微抖动像素宽度变化几像素距离就会跳几十厘米。第二单目测距公式基于目标宽度但 Tello TT 悬停时高度和俯仰角有小幅漂移实际距离确实在变。第三EMA 滤波系数没调好滤波太弱噪声全部透过了。解决先做标定验证把目标放在 1m、2m、3m 处各测 20 帧取均值重算焦距排除焦距不准的因素。然后给检测框宽度加中值滤波取最近 5 帧的中值代替单帧值这个操作能把测距方差缩小一半以上。最后把alpha调低到 0.3 左右让测距输出像缓慢移动的气泡而不是神经质乱跳的兔子。坑 6追踪时飞机左右甩头或者原地打转。现象目标在画面里没动飞机却一直在左右旋转像没头苍蝇一样。更严重的是yaw 通道持续输出导致飞机越过目标然后回摆形成振荡。原因PID 参数过猛尤其是kp和kd。yaw 通道的响应速度很快Tello TT 的机身本来就轻kp0.35已经会在近距离产生明显超调。另一个常见原因是控制频率和 PID 参数不匹配time.sleep(0.05)改成0.1后积分项累积速度不同原来的参数必然振荡。解决先把kp降到 0.1kd设为 0用不振荡的保守参数跑通再逐步加kp。每次只调一个参数加完飞十秒看机身反应这是 PID 调参的纪律。另外给控制加死区当error_x小于 0.05 时yaw 通道直接输出 0。死区能避免飞机在目标正前方时会做的无意义修正这个细节能让画面稳定很多。6. 让测距输出更可信标定验证、误检抑制和一本正经的卷尺测距这件事代码写得再漂亮没经过地面验证就上桨就是在赌命。我在这个项目上吃过最大的亏是拿着没标定过的焦距直接飞结果飞机在 1.5 米“期望距离”处差点贴到目标脸上。那次之后我养成了三个习惯都是低成本高回报的验证手段。第一个习惯是拿卷尺做多点标定。把目标放在 1 米、2 米、3 米三个位置每个位置记录 20 帧的像素宽度用三组数据分别反推焦距取中值作为最终焦距。如果三组数据反推的焦距偏差超过 10%说明检测框本身不稳定问题在模型而不在测距公式。标定结果写进docs/calibration.md每次换镜头、换分辨率都要重新标。第二个习惯是误检抑制。yolov5 在运动模糊和逆光场景下会偶发误检单帧的误检框一旦进了 PID飞机就会朝一个不存在的目标冲过去。我一般在检测后加一个“连续确认”逻辑目标必须连续 5 帧以上出现才认为它是真实目标并触发追踪中途消失 3 帧以内保留上一帧的追迹位置。这个逻辑在代码里只有十几行但它挡住了训练集里没有的那些“幽灵目标”。第三个习惯是手持标定验证。每次改完 PID 或测距参数我不直接起飞而是拆掉桨手持 Tello TT 对着目标做模拟追踪把飞机左右平移、前后拉近拉远观察终端打印的距离值和控制输出是否和实际操作方向一致。这个流程只需要五分钟但能发现绝大多数方向性错误和参数振荡问题。最后试飞时也先在一个 3 米见方的安全区域里低空测试绑定好各通道的输出限幅确保任何情况下都不会给出秒变最大油门这样的危险命令。追踪测距这条链路最难的部分不是让模型跑起来而是让模型输出和飞行响应之间形成一个稳定、可预期、不闯祸的闭环。每加一个功能都要回到“这个环节会不会失效、失效了飞机会怎样”这两个问题上。这套源码加数据加模型的组合真正的价值是给了你一个能改、能验证、能回溯的起点剩下的就是在一次次地面验证中把这套系统调得越来越可信。希望这些踩坑经验能帮你少走几段弯路。本文还有配套的精品资源点击获取
返回列表