音控制Tello EDU無(wú)人機(jī):從語(yǔ)音識(shí)別到UDP指令的完整實(shí)現(xiàn))
老實(shí)說(shuō)最早看到“語(yǔ)音控制無(wú)人機(jī)”這種組合我心里想過(guò)直升機(jī)本來(lái)通過(guò)遙控器就能指揮多一個(gè)語(yǔ)音識(shí)別折騰一遍是不是自己給自己找麻煩可真讓我動(dòng)手寫(xiě)基于 Python 的語(yǔ)音控制 Tello EDU 項(xiàng)目之后我才發(fā)現(xiàn)這里面的技術(shù)鏈路遠(yuǎn)比“動(dòng)嘴”更有意思麥克風(fēng)采集聲音、語(yǔ)音識(shí)別轉(zhuǎn)文字、自然語(yǔ)言轉(zhuǎn) SDK 指令、UDP 數(shù)據(jù)包在局域網(wǎng)里送達(dá)無(wú)人機(jī)最后電機(jī)響應(yīng)。整個(gè)過(guò)程是很完整的“語(yǔ)音交互嵌入式控制”小閉環(huán)。這個(gè)項(xiàng)目很適合三類(lèi)人一是準(zhǔn)備學(xué) Python 網(wǎng)絡(luò)編程的二是想接觸無(wú)人機(jī) SDK 的三是想在家里輕松給 Tello EDU 下達(dá)指令的。讀完這篇文章你至少能收獲一套可以直接跑的語(yǔ)音控制腳本框架以及我在調(diào)試過(guò)程中踩過(guò)的坑和放棄掉的方案。我盡量把每一步背后的原理和“為什么要這么寫(xiě)”講清楚這樣你以后換成別的型號(hào)或者別的語(yǔ)音引擎也能自己改。1. 為什么我會(huì)把語(yǔ)音控制和Tello EDU放在一起1.1 項(xiàng)目起點(diǎn)鍵盤(pán)控制玩膩了想換一種交互方式我之前自己寫(xiě)過(guò)基于 Python 的鍵盤(pán)控制腳本用方向鍵控制 Tello EDU。說(shuō)實(shí)話用鍵盤(pán)飛無(wú)人機(jī)也算方便但每次飛行都要坐在筆記本面前視線要在屏幕、無(wú)人機(jī)和手機(jī)之間來(lái)回切換。更麻煩的是鍵盤(pán)控制本質(zhì)上是在發(fā)“增量指令”按一下前進(jìn)鍵就飛 20 厘米再按一下又飛 20 厘米。這種控制模式雖然安全卻完全沒(méi)有“人機(jī)協(xié)同”的感覺(jué)。后來(lái)我玩到一個(gè)語(yǔ)音助手項(xiàng)目突然想到既然語(yǔ)音識(shí)別已經(jīng)能做到把“打開(kāi)空調(diào)”轉(zhuǎn)成一串指令那我把“起飛”、“降落”、“向前飛”這些中文命令轉(zhuǎn)成 Tello EDU 能聽(tīng)懂的 UDP 指令不就行了這樣可以解放雙手同時(shí)驗(yàn)證一條挺完整的 AIoT 技術(shù)路線語(yǔ)音入口 - 語(yǔ)義解析 - 設(shè)備控制。1.2 語(yǔ)音控制真正要解決的問(wèn)題語(yǔ)音控制無(wú)人機(jī)核心不是“把聲音變成文字”而是“把意圖變成可執(zhí)行動(dòng)作”。比如你說(shuō)“前進(jìn)”如果你在客廳里無(wú)人機(jī)直接盲目往前飛 5 米大概率撞墻。所以一個(gè)成熟的項(xiàng)目必須在語(yǔ)音識(shí)別之后加上距離參數(shù)、速度限制、方向限制和緊急降落指令。我給自己定的項(xiàng)目目標(biāo)是支持中文語(yǔ)音指令覆蓋起飛、降落、前后左右移動(dòng)。能設(shè)置默認(rèn)飛行距離確保每次動(dòng)作幅度不會(huì)太大。識(shí)別失敗時(shí)絕對(duì)不能發(fā)送誤指令無(wú)人機(jī)必須保持懸停。語(yǔ)音識(shí)別到“急?!被颉敖德洹睍r(shí)要優(yōu)先執(zhí)行安全動(dòng)作。后來(lái)實(shí)際做下來(lái)這四個(gè)目標(biāo)每一項(xiàng)都對(duì)應(yīng)一個(gè)技術(shù)細(xì)節(jié)并不是簡(jiǎn)單把識(shí)別 API 接進(jìn)來(lái)就行。1.3 Tello EDU 本身適不適合語(yǔ)音控制Tello EDU 是一臺(tái)面向教育和編程的飛行設(shè)備幾百克級(jí)別在室內(nèi)小空間里懸停比較穩(wěn)定螺旋槳有保護(hù)罩適合做實(shí)驗(yàn)。它相比消費(fèi)版的優(yōu)勢(shì)是支持更開(kāi)放的 SDK 指令而且有配套的 Python 生態(tài)。雖然它的算力不可能本地處理語(yǔ)音但我們可以用電腦完成語(yǔ)音識(shí)別再通過(guò)它自帶的 Wi-Fi 熱點(diǎn)把指令傳過(guò)去相當(dāng)于把“耳朵”放在了電腦上把“身體”留在無(wú)人機(jī)里。這種“外腦控制”的方式也是最安全、最容易調(diào)試的方式。哪怕某個(gè)環(huán)節(jié)崩了你還能隨時(shí)切回鍵盤(pán)模式接管。2. 先搞明白 Tello EDU 控制命令到底怎么發(fā)2.1 SDK 指令的本質(zhì)一句話 UDP 包Tello EDU 的控制協(xié)議很簡(jiǎn)單本質(zhì)就是你的電腦連上無(wú)人機(jī)發(fā)出的 Wi-Fi 熱點(diǎn)之后通過(guò) UDP 向固定 IP 和端口發(fā)一段 ASCII 文本命令然后無(wú)人機(jī)會(huì)回復(fù)一段文本比如 “ok” 或者 “error”。這里的固定 IP 是192.168.10.1固定端口是8889。如果你連過(guò)它的 Wi-Fi就會(huì)發(fā)現(xiàn)自己電腦被分配了一個(gè)192.168.10.x的地址而無(wú)人機(jī)自己就是192.168.10.1。這就是一個(gè)標(biāo)準(zhǔn)的局域網(wǎng) UDP 通信。需要注意Tello EDU 對(duì)每條命令都會(huì)有一個(gè)回復(fù)但這個(gè)回復(fù)不是馬上來(lái)有時(shí)候需要幾百毫秒甚至更久所以你的 socket 接收必須設(shè)置超時(shí)不能無(wú)限等下去。首次連接時(shí)無(wú)人機(jī)默認(rèn)不是處于可接受 SDK 指令的狀態(tài)必須先發(fā)送command這條“握手”指令。成功之后它會(huì)回復(fù)ok然后你才可以把takeoff、land等指令發(fā)過(guò)去。2.2 常用控制指令表下面是我在項(xiàng)目中用到的核心指令新手可以先背熟這一小撮。指令參數(shù)說(shuō)明實(shí)際效果command無(wú)激活 SDK 模式takeoff無(wú)起飛并懸停約 1 米高度land無(wú)原地降落up xx 為 20~500垂直上升 x 厘米down xx 為 20~500垂直下降 x 厘米forward xx 為 20~500前進(jìn) x 厘米back xx 為 20~500后退 x 厘米left xx 為 20~500向左平移 x 厘米right xx 為 20~500向右平移 x 厘米cw xx 為 1~360順時(shí)針旋轉(zhuǎn) x 度ccw xx 為 1~360逆時(shí)針旋轉(zhuǎn) x 度f(wàn)lip l/flip rl/r/f/b向左/右/前/后翻滾stop無(wú)立即懸停緊急停止battery?無(wú)返回當(dāng)前電量百分比注意一個(gè)小細(xì)節(jié)left和right是“橫向平移”而cw和ccw是“原地旋轉(zhuǎn)”這兩個(gè)要分清楚。剛開(kāi)始語(yǔ)音控制時(shí)很多人會(huì)把“左轉(zhuǎn)”和“左移”混為一談導(dǎo)致無(wú)人機(jī)突然改變機(jī)頭朝向而不是平移非常容易撞到障礙物。2.3 用 Python 的最小控制代碼我先寫(xiě)了一個(gè)不摻任何語(yǔ)音識(shí)別內(nèi)容的最小控制腳本目的是驗(yàn)證 socket 通信鏈路。這一步搞不定后面做語(yǔ)音識(shí)別也是白搭。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: 發(fā)送一條 Tello 指令并接收無(wú)人機(jī)回復(fù)。 如果沒(méi)有回復(fù)返回空字符串。 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[超時(shí)] 指令 {cmd} 未收到回復(fù)) return print(send_command(command)) print(send_command(battery?))跑通這個(gè)腳本你會(huì)看到終端打印出ok和電量數(shù)字。如果卡在超時(shí)基本就是沒(méi)有連上無(wú)人機(jī)的 Wi-Fi或者 IP 寫(xiě)錯(cuò)了。這一步我強(qiáng)烈建議先測(cè)試因?yàn)楹罄m(xù)所有語(yǔ)音功能都建立在這個(gè) socket 基礎(chǔ)上。有個(gè)我一開(kāi)始忽略的點(diǎn)sock.sendto發(fā)送指令后無(wú)人機(jī)確認(rèn)回復(fù)的地址不一定每次都一樣有些固件版本會(huì)從一個(gè)臨時(shí)的端口回?cái)?shù)據(jù)。如果接收窗口寫(xiě)得不對(duì)會(huì)出現(xiàn)“指令發(fā)出去了但收不到回復(fù)”的現(xiàn)象。最簡(jiǎn)單的做法是不要過(guò)早關(guān)閉 socket保持同一個(gè) UDP socket 實(shí)例循環(huán)使用。3. 語(yǔ)音識(shí)別的關(guān)鍵選型在線接口還是本地離線模型3.1 不要一上來(lái)就選在線 API很多人做語(yǔ)音控制第一步就接一個(gè)在線語(yǔ)音識(shí)別 API因?yàn)槊赓M(fèi)的方案看起來(lái)簡(jiǎn)單兩行代碼就能出結(jié)果。但用在無(wú)人機(jī)控制場(chǎng)景在線識(shí)別有幾個(gè)致命問(wèn)題每一次識(shí)別都要把音頻上傳到云端往返時(shí)間通常是 1~3 秒。如果網(wǎng)絡(luò)抖動(dòng)識(shí)別結(jié)果會(huì)延遲很久無(wú)人機(jī)接收到指令時(shí)會(huì)“突然沖出去”。免費(fèi)限額和并發(fā)限制在不同場(chǎng)景下表現(xiàn)差異很大。在飛行實(shí)驗(yàn)中外部網(wǎng)絡(luò)本來(lái)就是可選資源沒(méi)有必要讓飛行動(dòng)作依賴它。我并不是說(shuō)在線 API 完全不能用而是在“實(shí)時(shí)控制無(wú)人機(jī)”這個(gè)特定需求下延遲是不可接受的。你愿意為一句“降落”多等兩秒嗎我不愿意我聽(tīng)到螺旋槳聲音都覺(jué)得危險(xiǎn)。3.2 本地離線識(shí)別是更穩(wěn)的選擇在離線方案里Vosk 是一個(gè)比較成熟的開(kāi)源語(yǔ)音識(shí)別工具支持中文模型不依賴外部網(wǎng)絡(luò)。模型文件下載后在本地運(yùn)行解碼速度遠(yuǎn)快于在線方案實(shí)測(cè)在普通筆記本電腦上識(shí)別一個(gè)詞基本在 0.3~1 秒左右這個(gè)延遲對(duì)無(wú)人機(jī)控制來(lái)說(shuō)是可以接受的。我在項(xiàng)目里用的就是 Vosk 的中文小模型。它體積不大準(zhǔn)確率在“有限指令集”里完全夠用。你可以先安裝 Vosk 和 SpeechRecognition 相關(guān)庫(kù)pip install vosk speechrecognition pyaudiopyaudio在 Linux 和 macOS 上可能需要額外安裝系統(tǒng)依賴Windows 上建議直接安裝帶預(yù)編譯的包。裝完之后關(guān)鍵是配置正確的音頻流參數(shù)采樣率16000、單聲道、位深16bit。Vosk 默認(rèn)需要這個(gè)格式否則識(shí)別結(jié)果會(huì)亂掉。3.3 我不是直接把整段語(yǔ)音都丟給模型語(yǔ)音控制無(wú)人機(jī)和語(yǔ)音輸入法不一樣不需要識(shí)別長(zhǎng)句只需要識(shí)別“起飛”“降落”“前進(jìn)五十”“左轉(zhuǎn)”這類(lèi)短指令。所以我設(shè)計(jì)了一個(gè)關(guān)鍵詞匹配策略先把麥克風(fēng)拾取的一小段音頻送到 Vosk。模型返回一個(gè)包含多個(gè)候選詞的 JSON我只關(guān)心里面出現(xiàn)的動(dòng)作詞。如果動(dòng)作詞命中了預(yù)設(shè)指令表再執(zhí)行后續(xù)轉(zhuǎn)換。這種策略的好處是即使模型把“前進(jìn)”識(shí)別成“前盡”或“前金”我也可以通過(guò)拼音相似度或者包含關(guān)系把它匹配到“前進(jìn)”指令上。如果嚴(yán)格到必須一字不差語(yǔ)音識(shí)別里的同音字問(wèn)題會(huì)讓你崩潰。4. 實(shí)現(xiàn)語(yǔ)音到飛行動(dòng)作映射一份可以直接跑的代碼4.1 整體架構(gòu)我的腳本分三個(gè)模塊音頻采集、語(yǔ)義指令匹配、UDP 飛行指令發(fā)送。用一個(gè)主循環(huán)把它們串起來(lái)大致流程是初始化 UDP socket 并發(fā)送command進(jìn)入 SDK 模式。初始化語(yǔ)音識(shí)別器和麥克風(fēng)流。每輪循環(huán)從麥克風(fēng)讀取約 2 秒的音頻。Vosk 識(shí)別出文本。對(duì)文本做關(guān)鍵詞映射判斷是否需要執(zhí)行動(dòng)作。如果需要執(zhí)行解析距離/角度參數(shù)調(diào)用send_command發(fā)送 UDP 包。每次指令之間加冷卻時(shí)間防止連續(xù)誤觸發(fā)。4.2 主程序示例下面的代碼是我化簡(jiǎn)后的版本不依賴任何外部云接口適合直接理解核心邏輯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 # 平移距離默認(rèn)厘米數(shù) DEFAULT_ANGLE 90 # 旋轉(zhuǎn)角度默認(rèn)度數(shù) COOLDOWN_SECONDS 2 # 兩條動(dòng)作指令之間的冷卻時(shí)間 # 這一份映射可以按自己需求調(diào)整 keyword_map { 起飛: takeoff, 降落: land, 急停: land, 前進(jìn): forward, 后退: back, 左轉(zhuǎn): ccw, 右轉(zhuǎn): 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: 把識(shí)別出來(lái)的關(guān)鍵詞轉(zhuǎn)成 Tello 指令距離和角度目前用默認(rèn)值。 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(): # 連接無(wú)人機(jī) print(正在連接 Tello EDU ...) print(send_command(command)) # 初始化 Vosk 模型 model Model(models/vosk-model-small-cn-0.3) recognizer KaldiRecognizer(model, 16000) # 打開(kāi)麥克風(fēng)音頻流 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(語(yǔ)音控制已就緒等待指令……) 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[識(shí)別] {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[發(fā)送] {cmd}) response send_command(cmd) print(f[回復(fù)] {response}) if __name__ __main__: main()代碼本身并不復(fù)雜。要注意build_tello_command里用了固定距離和固定角度這是故意的對(duì)剛接觸語(yǔ)音控制的用戶來(lái)說(shuō)固定 40 厘米比每次語(yǔ)音解析數(shù)字更安全。我之前試圖讓用戶說(shuō)“前進(jìn)三十”然后識(shí)別數(shù)字結(jié)果識(shí)別精度和漢字?jǐn)?shù)字轉(zhuǎn)換帶來(lái)的麻煩遠(yuǎn)超收益。先把固定參數(shù)跑通再慢慢加可變參數(shù)。4.3 如何讓距離參數(shù)也支持語(yǔ)音解析等固定參數(shù)版本穩(wěn)定之后可以做進(jìn)階升級(jí)從識(shí)別文本里提取中文數(shù)字。做法是先判斷動(dòng)作詞命中的位置然后在文本后半段找“二十”“五十”“一百”這種數(shù)字表達(dá)。一個(gè)比較樸素的轉(zhuǎn)換函數(shù)def chinese_to_number(text: str) - int: 極簡(jiǎn)中文數(shù)字解析只處理 0~999適用于常見(jiàn)距離。 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這種解析器不適合做復(fù)雜的金額轉(zhuǎn)換但應(yīng)付“前進(jìn)五十”“左轉(zhuǎn)一百八”這種口語(yǔ)足夠。然后在build_tello_command里把最初的固定距離替換成解析結(jié)果即可。不過(guò)我還是要提醒語(yǔ)音識(shí)別到“一百八十”時(shí)中文數(shù)字解析很容易出錯(cuò)實(shí)際測(cè)試時(shí)一定要先在日志里看清楚識(shí)別結(jié)果再做映射。5. 實(shí)測(cè)中的避坑記錄延遲、誤識(shí)別、嘈雜環(huán)境、安全兜底5.1 識(shí)別延遲比我預(yù)想的小但代碼阻塞問(wèn)題卻很大我最初寫(xiě)版本時(shí)語(yǔ)音識(shí)別是在主循環(huán)里同步執(zhí)行的。也就是說(shuō)麥克風(fēng)采集到音頻之后要等 Vosk 返回結(jié)果再繼續(xù)讀下一段音頻。如果模型識(shí)別稍慢麥克風(fēng)緩沖區(qū)里的后續(xù)語(yǔ)音就會(huì)被丟棄直接后果是你說(shuō)“起飛”它識(shí)別出來(lái)了但你說(shuō)完“降落”它可能正好處于識(shí)別中的阻塞狀態(tài)漏掉了。解決方式就是我在代碼里用audio_queue異步采集。麥克風(fēng)回調(diào)函數(shù)把音頻流不斷放入隊(duì)列主循環(huán)再?gòu)年?duì)列里取數(shù)據(jù)。這樣即使某次識(shí)別慢一點(diǎn)音頻不會(huì)丟只是處理順序稍微延后。這套生產(chǎn)者-消費(fèi)者模型在很多實(shí)時(shí)識(shí)別場(chǎng)景里都能用建議記下來(lái)。5.2 關(guān)鍵詞沖突比誤識(shí)別更隱蔽我在測(cè)試中遇到一個(gè)很有意思的誤觸發(fā)我本來(lái)想說(shuō)“起飛”但模型識(shí)別成了“集體的起飛”或者“請(qǐng)飛”。后來(lái)排查發(fā)現(xiàn)問(wèn)題不在識(shí)別而在我的關(guān)鍵詞匹配用了簡(jiǎn)單的if word in text而中文分詞把“起飛”嵌在了一個(gè)長(zhǎng)句里。對(duì)策是縮小匹配窗口。當(dāng)識(shí)別文本很短時(shí)不擔(dān)心但如果文本較長(zhǎng)我會(huì)只取前 4 個(gè)字作為有效指令區(qū)域。比如“起飛謝謝”這種長(zhǎng)尾干擾直接裁掉前兩個(gè)詞再判斷。另一個(gè)策略是增加置信度過(guò)濾Vosk 會(huì)返回多個(gè) token 和置信度信息我實(shí)際項(xiàng)目里要求命中關(guān)鍵詞的置信度不低于 0.6。5.3 飛行環(huán)境的安全兜底無(wú)人機(jī)語(yǔ)音控制最怕的就是“說(shuō)了不該說(shuō)的詞”。比如你在聊天提到“左轉(zhuǎn)走”無(wú)人機(jī)恰好聽(tīng)到了結(jié)果突然原地轉(zhuǎn)過(guò)機(jī)身。這雖然不致命但如果旁邊有人真的很?chē)樔恕K晕业陌踩O(shè)計(jì)有三層程序只在非對(duì)話場(chǎng)景下運(yùn)行起飛后關(guān)掉無(wú)關(guān)音源。所有平移距離參數(shù)限制在 30~60 厘米之間不讓單次動(dòng)作幅度過(guò)大。緊急“降落”關(guān)鍵詞必須優(yōu)先匹配而且響應(yīng)時(shí)間要比其他指令更快。實(shí)際項(xiàng)目里我還把“急停”單獨(dú)拉出來(lái)不經(jīng)過(guò)完整語(yǔ)音長(zhǎng)句判斷直接在識(shí)別結(jié)果里查這個(gè)子串。只要出現(xiàn)“急?!薄敖德洹绷⒖贪l(fā)送land然后下一次動(dòng)作前強(qiáng)制冷卻 5 秒。這種特殊待遇在異常情況下非常重要。5.4 不要開(kāi)著螺旋槳測(cè)試語(yǔ)音這可能是最容易被忽略的一點(diǎn)。我第一輪測(cè)試時(shí)把無(wú)人機(jī)放在地面已經(jīng)處于起飛狀態(tài)但還沒(méi)升起來(lái)然后自己對(duì)著麥克風(fēng)反復(fù)說(shuō)“旋轉(zhuǎn)”“旋轉(zhuǎn)”結(jié)果無(wú)人機(jī)突然原地輕微轉(zhuǎn)了一下差點(diǎn)從桌面上掃下去。后來(lái)我把測(cè)試流程改成了“地面半托管模式”先讓無(wú)人機(jī)在手里拿著或者放在開(kāi)闊地面然后測(cè)試語(yǔ)音識(shí)別部分只打印日志不實(shí)際發(fā)送飛行指令。等打印日志穩(wěn)定了再真機(jī)起飛測(cè)試。這種做法雖然慢一點(diǎn)但能避免很多螺旋槳事故。6. 還能怎么玩語(yǔ)音回傳、自定義指令和后續(xù)擴(kuò)展6.1 結(jié)合命令回傳狀態(tài)信息Tello EDU 不是只會(huì)單向接收指令它也會(huì)返回傳感器數(shù)據(jù)、電池電量、姿態(tài)信息。你可以把這些信息轉(zhuǎn)成語(yǔ)音回傳比如識(shí)別到“電量”時(shí)請(qǐng)求battery?然后用本地語(yǔ)音合成說(shuō)“當(dāng)前電量百分之八十”。這樣一個(gè)簡(jiǎn)單的語(yǔ)音問(wèn)答系統(tǒng)就出來(lái)了也適合拿來(lái)做課堂演示。我建議把 UDP 狀態(tài)輪詢和數(shù)據(jù)解析單獨(dú)封裝成一個(gè)函數(shù)不要和飛行動(dòng)作混在一起。比如def query_battery() - str: resp send_command(battery?) try: return f當(dāng)前電量 {int(resp)}% except ValueError: return 電量查詢失敗6.2 自定義手勢(shì)和特殊動(dòng)作語(yǔ)音除了控制位移還可以控制翻轉(zhuǎn)、相機(jī)拍照、拋飛之類(lèi)的高級(jí)動(dòng)作。Tello EDU 支持flip指令說(shuō)“翻個(gè)跟頭”讓無(wú)人機(jī)向前翻演示效果非???。但翻轉(zhuǎn)對(duì)空間有要求前后左右至少留出 1.5 米余量而且要確認(rèn)電池電量足夠。翻轉(zhuǎn)過(guò)低電量容易掉下來(lái)。在指令映射里加一條keyword_map[翻跟頭] flip f keyword_map[左翻] flip l注意翻轉(zhuǎn)動(dòng)作不是懸停指令執(zhí)行時(shí)會(huì)有一個(gè)明顯的高度變化不適合在桌面上空測(cè)試。6.3 從語(yǔ)音控制升級(jí)到情景自動(dòng)化等語(yǔ)音控制穩(wěn)定后更進(jìn)階的方向是情景自動(dòng)化。比如利用 Tello EDU 的編程接口讓無(wú)人機(jī)在識(shí)別到“開(kāi)始巡檢”后按照預(yù)設(shè)路徑飛行同時(shí)在每個(gè)航點(diǎn)語(yǔ)音播報(bào)“到達(dá)一號(hào)位置”。這種組合非常適合教育場(chǎng)景學(xué)生可以在一套系統(tǒng)里同時(shí)接觸語(yǔ)音識(shí)別、路徑規(guī)劃、UDP 通信和狀態(tài)回傳。我最后的體會(huì)是語(yǔ)音控制無(wú)人機(jī)最難的不是識(shí)別模型而是如何不讓識(shí)別誤差變成飛行事故。項(xiàng)目里真正花時(shí)間的部分其實(shí)是設(shè)計(jì)關(guān)鍵詞沖突規(guī)避、距離限制和急停優(yōu)先級(jí)。把這些東西做好之后你會(huì)發(fā)現(xiàn)基于 Python 的語(yǔ)音控制 Tello EDU 不僅酷而且可以成為一個(gè)非常不錯(cuò)的 AIoT 教學(xué)與實(shí)驗(yàn)平臺(tái)。