首页 / 资讯中心 / 文章详情

Python语音控制Tello EDU无人机:从语音识别到UDP指令的完整实现

Python语音控制Tello EDU无人机:从语音识别到UDP指令的完整实现 ★ FEATURED ARTICLE
老实说最早看到“语音控制无人机”这种组合我心里想过直升机本来通过遥控器就能指挥多一个语音识别折腾一遍是不是自己给自己找麻烦可真让我动手写基于 Python 的语音控制 Tello EDU 项目之后我才发现这里面的技术链路远比“动嘴”更有意思麦克风采集声音、语音识别转文字、自然语言转 SDK 指令、UDP 数据包在局域网里送达无人机最后电机响应。整个过程是很完整的“语音交互嵌入式控制”小闭环。这个项目很适合三类人一是准备学 Python 网络编程的二是想接触无人机 SDK 的三是想在家里轻松给 Tello EDU 下达指令的。读完这篇文章你至少能收获一套可以直接跑的语音控制脚本框架以及我在调试过程中踩过的坑和放弃掉的方案。我尽量把每一步背后的原理和“为什么要这么写”讲清楚这样你以后换成别的型号或者别的语音引擎也能自己改。1. 为什么我会把语音控制和Tello EDU放在一起1.1 项目起点键盘控制玩腻了想换一种交互方式我之前自己写过基于 Python 的键盘控制脚本用方向键控制 Tello EDU。说实话用键盘飞无人机也算方便但每次飞行都要坐在笔记本面前视线要在屏幕、无人机和手机之间来回切换。更麻烦的是键盘控制本质上是在发“增量指令”按一下前进键就飞 20 厘米再按一下又飞 20 厘米。这种控制模式虽然安全却完全没有“人机协同”的感觉。后来我玩到一个语音助手项目突然想到既然语音识别已经能做到把“打开空调”转成一串指令那我把“起飞”、“降落”、“向前飞”这些中文命令转成 Tello EDU 能听懂的 UDP 指令不就行了这样可以解放双手同时验证一条挺完整的 AIoT 技术路线语音入口 - 语义解析 - 设备控制。1.2 语音控制真正要解决的问题语音控制无人机核心不是“把声音变成文字”而是“把意图变成可执行动作”。比如你说“前进”如果你在客厅里无人机直接盲目往前飞 5 米大概率撞墙。所以一个成熟的项目必须在语音识别之后加上距离参数、速度限制、方向限制和紧急降落指令。我给自己定的项目目标是支持中文语音指令覆盖起飞、降落、前后左右移动。能设置默认飞行距离确保每次动作幅度不会太大。识别失败时绝对不能发送误指令无人机必须保持悬停。语音识别到“急停”或“降落”时要优先执行安全动作。后来实际做下来这四个目标每一项都对应一个技术细节并不是简单把识别 API 接进来就行。1.3 Tello EDU 本身适不适合语音控制Tello EDU 是一台面向教育和编程的飞行设备几百克级别在室内小空间里悬停比较稳定螺旋桨有保护罩适合做实验。它相比消费版的优势是支持更开放的 SDK 指令而且有配套的 Python 生态。虽然它的算力不可能本地处理语音但我们可以用电脑完成语音识别再通过它自带的 Wi-Fi 热点把指令传过去相当于把“耳朵”放在了电脑上把“身体”留在无人机里。这种“外脑控制”的方式也是最安全、最容易调试的方式。哪怕某个环节崩了你还能随时切回键盘模式接管。2. 先搞明白 Tello EDU 控制命令到底怎么发2.1 SDK 指令的本质一句话 UDP 包Tello EDU 的控制协议很简单本质就是你的电脑连上无人机发出的 Wi-Fi 热点之后通过 UDP 向固定 IP 和端口发一段 ASCII 文本命令然后无人机会回复一段文本比如 “ok” 或者 “error”。这里的固定 IP 是192.168.10.1固定端口是8889。如果你连过它的 Wi-Fi就会发现自己电脑被分配了一个192.168.10.x的地址而无人机自己就是192.168.10.1。这就是一个标准的局域网 UDP 通信。需要注意Tello EDU 对每条命令都会有一个回复但这个回复不是马上来有时候需要几百毫秒甚至更久所以你的 socket 接收必须设置超时不能无限等下去。首次连接时无人机默认不是处于可接受 SDK 指令的状态必须先发送command这条“握手”指令。成功之后它会回复ok然后你才可以把takeoff、land等指令发过去。2.2 常用控制指令表下面是我在项目中用到的核心指令新手可以先背熟这一小撮。指令参数说明实际效果command无激活 SDK 模式takeoff无起飞并悬停约 1 米高度land无原地降落up xx 为 20~500垂直上升 x 厘米down xx 为 20~500垂直下降 x 厘米forward xx 为 20~500前进 x 厘米back xx 为 20~500后退 x 厘米left xx 为 20~500向左平移 x 厘米right xx 为 20~500向右平移 x 厘米cw xx 为 1~360顺时针旋转 x 度ccw xx 为 1~360逆时针旋转 x 度flip l/flip rl/r/f/b向左/右/前/后翻滚stop无立即悬停紧急停止battery?无返回当前电量百分比注意一个小细节left和right是“横向平移”而cw和ccw是“原地旋转”这两个要分清楚。刚开始语音控制时很多人会把“左转”和“左移”混为一谈导致无人机突然改变机头朝向而不是平移非常容易撞到障碍物。2.3 用 Python 的最小控制代码我先写了一个不掺任何语音识别内容的最小控制脚本目的是验证 socket 通信链路。这一步搞不定后面做语音识别也是白搭。import socket TELLO_IP 192.168.10.1 TELLO_PORT 8889 sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(5) def send_command(cmd: str) - str: 发送一条 Tello 指令并接收无人机回复。 如果没有回复返回空字符串。 try: sock.sendto(cmd.encode(utf-8), (TELLO_IP, TELLO_PORT)) data, _ sock.recvfrom(1024) return data.decode(utf-8).strip() except socket.timeout: print(f[超时] 指令 {cmd} 未收到回复) return print(send_command(command)) print(send_command(battery?))跑通这个脚本你会看到终端打印出ok和电量数字。如果卡在超时基本就是没有连上无人机的 Wi-Fi或者 IP 写错了。这一步我强烈建议先测试因为后续所有语音功能都建立在这个 socket 基础上。有个我一开始忽略的点sock.sendto发送指令后无人机确认回复的地址不一定每次都一样有些固件版本会从一个临时的端口回数据。如果接收窗口写得不对会出现“指令发出去了但收不到回复”的现象。最简单的做法是不要过早关闭 socket保持同一个 UDP socket 实例循环使用。3. 语音识别的关键选型在线接口还是本地离线模型3.1 不要一上来就选在线 API很多人做语音控制第一步就接一个在线语音识别 API因为免费的方案看起来简单两行代码就能出结果。但用在无人机控制场景在线识别有几个致命问题每一次识别都要把音频上传到云端往返时间通常是 1~3 秒。如果网络抖动识别结果会延迟很久无人机接收到指令时会“突然冲出去”。免费限额和并发限制在不同场景下表现差异很大。在飞行实验中外部网络本来就是可选资源没有必要让飞行动作依赖它。我并不是说在线 API 完全不能用而是在“实时控制无人机”这个特定需求下延迟是不可接受的。你愿意为一句“降落”多等两秒吗我不愿意我听到螺旋桨声音都觉得危险。3.2 本地离线识别是更稳的选择在离线方案里Vosk 是一个比较成熟的开源语音识别工具支持中文模型不依赖外部网络。模型文件下载后在本地运行解码速度远快于在线方案实测在普通笔记本电脑上识别一个词基本在 0.3~1 秒左右这个延迟对无人机控制来说是可以接受的。我在项目里用的就是 Vosk 的中文小模型。它体积不大准确率在“有限指令集”里完全够用。你可以先安装 Vosk 和 SpeechRecognition 相关库pip install vosk speechrecognition pyaudiopyaudio在 Linux 和 macOS 上可能需要额外安装系统依赖Windows 上建议直接安装带预编译的包。装完之后关键是配置正确的音频流参数采样率16000、单声道、位深16bit。Vosk 默认需要这个格式否则识别结果会乱掉。3.3 我不是直接把整段语音都丢给模型语音控制无人机和语音输入法不一样不需要识别长句只需要识别“起飞”“降落”“前进五十”“左转”这类短指令。所以我设计了一个关键词匹配策略先把麦克风拾取的一小段音频送到 Vosk。模型返回一个包含多个候选词的 JSON我只关心里面出现的动作词。如果动作词命中了预设指令表再执行后续转换。这种策略的好处是即使模型把“前进”识别成“前尽”或“前金”我也可以通过拼音相似度或者包含关系把它匹配到“前进”指令上。如果严格到必须一字不差语音识别里的同音字问题会让你崩溃。4. 实现语音到飞行动作映射一份可以直接跑的代码4.1 整体架构我的脚本分三个模块音频采集、语义指令匹配、UDP 飞行指令发送。用一个主循环把它们串起来大致流程是初始化 UDP socket 并发送command进入 SDK 模式。初始化语音识别器和麦克风流。每轮循环从麦克风读取约 2 秒的音频。Vosk 识别出文本。对文本做关键词映射判断是否需要执行动作。如果需要执行解析距离/角度参数调用send_command发送 UDP 包。每次指令之间加冷却时间防止连续误触发。4.2 主程序示例下面的代码是我化简后的版本不依赖任何外部云接口适合直接理解核心逻辑import json import queue import socket import sys import time import pyaudio from vosk import Model, KaldiRecognizer TELLO_IP 192.168.10.1 TELLO_PORT 8889 DEFAULT_DISTANCE 40 # 平移距离默认厘米数 DEFAULT_ANGLE 90 # 旋转角度默认度数 COOLDOWN_SECONDS 2 # 两条动作指令之间的冷却时间 # 这一份映射可以按自己需求调整 keyword_map { 起飞: takeoff, 降落: land, 急停: land, 前进: forward, 后退: back, 左转: ccw, 右转: cw, 左移: left, 右移: right, 上升: up, 下降: down, } sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(5) def send_command(cmd: str) - str: try: sock.sendto(cmd.encode(utf-8), (TELLO_IP, TELLO_PORT)) data, _ sock.recvfrom(1024) return data.decode(utf-8).strip() except socket.timeout: return def build_tello_command(keyword: str) - str: 把识别出来的关键词转成 Tello 指令距离和角度目前用默认值。 base keyword_map.get(keyword) if base is None: return None if base in (forward, back, left, right, up, down): return f{base} {DEFAULT_DISTANCE} if base in (cw, ccw): return f{base} {DEFAULT_ANGLE} return base def main(): # 连接无人机 print(正在连接 Tello EDU ...) print(send_command(command)) # 初始化 Vosk 模型 model Model(models/vosk-model-small-cn-0.3) recognizer KaldiRecognizer(model, 16000) # 打开麦克风音频流 audio_queue queue.Queue() def audio_callback(in_data, frame_count, time_info, status): audio_queue.put(in_data) return (None, pyaudio.paContinue) audio pyaudio.PyAudio() stream audio.open( formatpyaudio.paInt16, channels1, rate16000, inputTrue, frames_per_buffer4000, stream_callbackaudio_callback, ) stream.start_stream() last_command_time 0 print(语音控制已就绪等待指令……) while True: data audio_queue.get() if not data: continue if recognizer.AcceptWaveform(data): result json.loads(recognizer.Result()) text result.get(text, ).strip() if not text: continue print(f[识别] {text}) matched_keyword None for word in keyword_map: if word in text: matched_keyword word break if matched_keyword is None: continue now time.time() if now - last_command_time COOLDOWN_SECONDS: print([忽略] 指令间隔太短) continue last_command_time now cmd build_tello_command(matched_keyword) if cmd: print(f[发送] {cmd}) response send_command(cmd) print(f[回复] {response}) if __name__ __main__: main()代码本身并不复杂。要注意build_tello_command里用了固定距离和固定角度这是故意的对刚接触语音控制的用户来说固定 40 厘米比每次语音解析数字更安全。我之前试图让用户说“前进三十”然后识别数字结果识别精度和汉字数字转换带来的麻烦远超收益。先把固定参数跑通再慢慢加可变参数。4.3 如何让距离参数也支持语音解析等固定参数版本稳定之后可以做进阶升级从识别文本里提取中文数字。做法是先判断动作词命中的位置然后在文本后半段找“二十”“五十”“一百”这种数字表达。一个比较朴素的转换函数def chinese_to_number(text: str) - int: 极简中文数字解析只处理 0~999适用于常见距离。 digits { 零: 0, 一: 1, 二: 2, 两: 2, 三: 3, 四: 4, 五: 5, 六: 6, 七: 7, 八: 8, 九: 9, } units {十: 10, 百: 100} total 0 temp 0 for char in text: if char in digits: temp digits[char] elif char in units: if char 十 and temp 0: temp 1 total temp * units[char] temp 0 total temp return total这种解析器不适合做复杂的金额转换但应付“前进五十”“左转一百八”这种口语足够。然后在build_tello_command里把最初的固定距离替换成解析结果即可。不过我还是要提醒语音识别到“一百八十”时中文数字解析很容易出错实际测试时一定要先在日志里看清楚识别结果再做映射。5. 实测中的避坑记录延迟、误识别、嘈杂环境、安全兜底5.1 识别延迟比我预想的小但代码阻塞问题却很大我最初写版本时语音识别是在主循环里同步执行的。也就是说麦克风采集到音频之后要等 Vosk 返回结果再继续读下一段音频。如果模型识别稍慢麦克风缓冲区里的后续语音就会被丢弃直接后果是你说“起飞”它识别出来了但你说完“降落”它可能正好处于识别中的阻塞状态漏掉了。解决方式就是我在代码里用audio_queue异步采集。麦克风回调函数把音频流不断放入队列主循环再从队列里取数据。这样即使某次识别慢一点音频不会丢只是处理顺序稍微延后。这套生产者-消费者模型在很多实时识别场景里都能用建议记下来。5.2 关键词冲突比误识别更隐蔽我在测试中遇到一个很有意思的误触发我本来想说“起飞”但模型识别成了“集体的起飞”或者“请飞”。后来排查发现问题不在识别而在我的关键词匹配用了简单的if word in text而中文分词把“起飞”嵌在了一个长句里。对策是缩小匹配窗口。当识别文本很短时不担心但如果文本较长我会只取前 4 个字作为有效指令区域。比如“起飞谢谢”这种长尾干扰直接裁掉前两个词再判断。另一个策略是增加置信度过滤Vosk 会返回多个 token 和置信度信息我实际项目里要求命中关键词的置信度不低于 0.6。5.3 飞行环境的安全兜底无人机语音控制最怕的就是“说了不该说的词”。比如你在聊天提到“左转走”无人机恰好听到了结果突然原地转过机身。这虽然不致命但如果旁边有人真的很吓人。所以我的安全设计有三层程序只在非对话场景下运行起飞后关掉无关音源。所有平移距离参数限制在 30~60 厘米之间不让单次动作幅度过大。紧急“降落”关键词必须优先匹配而且响应时间要比其他指令更快。实际项目里我还把“急停”单独拉出来不经过完整语音长句判断直接在识别结果里查这个子串。只要出现“急停”“降落”立刻发送land然后下一次动作前强制冷却 5 秒。这种特殊待遇在异常情况下非常重要。5.4 不要开着螺旋桨测试语音这可能是最容易被忽略的一点。我第一轮测试时把无人机放在地面已经处于起飞状态但还没升起来然后自己对着麦克风反复说“旋转”“旋转”结果无人机突然原地轻微转了一下差点从桌面上扫下去。后来我把测试流程改成了“地面半托管模式”先让无人机在手里拿着或者放在开阔地面然后测试语音识别部分只打印日志不实际发送飞行指令。等打印日志稳定了再真机起飞测试。这种做法虽然慢一点但能避免很多螺旋桨事故。6. 还能怎么玩语音回传、自定义指令和后续扩展6.1 结合命令回传状态信息Tello EDU 不是只会单向接收指令它也会返回传感器数据、电池电量、姿态信息。你可以把这些信息转成语音回传比如识别到“电量”时请求battery?然后用本地语音合成说“当前电量百分之八十”。这样一个简单的语音问答系统就出来了也适合拿来做课堂演示。我建议把 UDP 状态轮询和数据解析单独封装成一个函数不要和飞行动作混在一起。比如def query_battery() - str: resp send_command(battery?) try: return f当前电量 {int(resp)}% except ValueError: return 电量查询失败6.2 自定义手势和特殊动作语音除了控制位移还可以控制翻转、相机拍照、抛飞之类的高级动作。Tello EDU 支持flip指令说“翻个跟头”让无人机向前翻演示效果非常酷。但翻转对空间有要求前后左右至少留出 1.5 米余量而且要确认电池电量足够。翻转过低电量容易掉下来。在指令映射里加一条keyword_map[翻跟头] flip f keyword_map[左翻] flip l注意翻转动作不是悬停指令执行时会有一个明显的高度变化不适合在桌面上空测试。6.3 从语音控制升级到情景自动化等语音控制稳定后更进阶的方向是情景自动化。比如利用 Tello EDU 的编程接口让无人机在识别到“开始巡检”后按照预设路径飞行同时在每个航点语音播报“到达一号位置”。这种组合非常适合教育场景学生可以在一套系统里同时接触语音识别、路径规划、UDP 通信和状态回传。我最后的体会是语音控制无人机最难的不是识别模型而是如何不让识别误差变成飞行事故。项目里真正花时间的部分其实是设计关键词冲突规避、距离限制和急停优先级。把这些东西做好之后你会发现基于 Python 的语音控制 Tello EDU 不仅酷而且可以成为一个非常不错的 AIoT 教学与实验平台。
阅读完成 · 觉得有帮助?
咨询建站