上一节:4500元,手搓一台会走路的人形机器人(下)
第一部分:缘起与蓝图

第 2 章 成果展示:这台4500元的机器人到底能做什么(一)极低成本 · 实物上手 · 可迁移至高端人形平台

2.1 先看看它能做什么

上一章说了"为什么",这一章直接看"是什么"。4500元到底能造出什么?下面六个功能,我一个一个说明下。
语音对话: 你可以跟它自然聊天,不是预设对话。所有AI推理(ASR+LLM+TTS)在手机端离线运行,不需要联网,不需要API key,不需要付费。
舞蹈与动作编辑器: 内置舞蹈系统,可跟着音乐跳预设动作。Unity 3D动作编辑器可视化编排21个关节,支持BVH动作捕捉导入、时间线关键帧编辑、音乐同步,所见即所得。这不是核心功能,但做完以后,你会觉得"它活了"。
自主平衡站立: 推它一下,它不会倒。100Hz实时控制循环:IMU每10ms报告倾斜角度,ZMP控制器计算补偿力矩,21个舵机毫秒级响应。你看着它微微晃动但始终不倒——那种感觉很难用语言形容。
跨步恢复: 推力太大踝关节扛不住时,它会试图迈一步尝试。三级响应:小扰动踝关节PD调整,中等扰动髋关节侧移,大扰动迈步恢复。
调试和运行支撑工具: PC端监控系统实时展示IMU姿态曲线、21个舵机角度和温度、足底压力ZMP分布、ROS2通信状态。不是黑盒,是透明的可观测系统。
开发工具链: 一键备份恢复、双系统切换、关节标定工具、自动调参框架——每个工具都是踩坑后总结出来的,设计第二款机器人时能省掉80%的重复劳动。
全景看完了,下面逐个拆开。每个功能我都会解释它“怎么做到的”,而不是只告诉你“它能做什么”。你读完这一章,应该能对每个模块的实现思路有一个清晰的框架。

2.2 语音对话:离线AI,开箱即用

乐宝的语音对话系统全部运行在手机端,离线运行,不需要云端服务。核心思路是:把整个AI推理链路搬到手机上。为什么一定要离线?因为现在的AI成本太高了,如果开发了一个机器人,有一个语音对话功能,如果你的用户一直持续聊天,那么产生的Token使用费甚至可能高于机器的售价本身。而现在的程序,可以无延迟的在我的红米K60上面1秒内识别和生成语音回复我。
在这里插入图片描述

AI Service Hub:手机就是AI大脑
AI Service Hub是一个运行在Android手机上的Python应用(Kivy框架),集成三个核心模块——ASR(语音识别)、LLM(大语言模型)和TTS(语音合成)。这三个模块各司其职,通过进程间通信协作,形成一条完整的“听→想→说”流水线。
ASR(语音识别): 基于Whisper-Base多语言版INT8量化模型,通过sherpa-onnx框架在手机端离线运行。模型约155MB,参数:language=“zh”, task=“transcribe”, num_threads=4, sampling_rate=16000。这些参数是我从"en"改成"zh"的——一开始用的是英文版Whisper(50256个token,不含中文字符),发现怎么都识别不了中文,折腾了半天才意识到是模型选错了。换上多语言版(50257个token,含1378个中文token)后,中文识别率从0%直接跳到90%以上。这个坑告诉我们:模型选型时一定要确认token覆盖范围,不是所有Whisper都支持中文。
ASR模型: Whisper-Base 多语言 INT8 量化
模型文件: encoder 29MB + decoder 125MB + tokens.txt 0.8MB
推理框架: sherpa-onnx
语音编码: 50257 tokens (含1378个中文token)
关键参数: language=“zh”, task=“transcribe”, num_threads=4
采样率: 16000Hz (单声道)
LLM(大语言模型): 基于llama.cpp框架,通过ctypes调用libllama.so实现原生推理,支持任何GGUF格式模型。采用混合策略:规则引擎优先处理常见问题(问候、天气、数学计算等),LLM处理开放式对话。这个设计是我踩过坑后总结的——小模型(450M参数)对开放式问题回复质量不稳定,但常见问题用规则引擎又快又准。LLM的ChatML格式提示词里我还加了"你必须用中文回复"的强制指令,因为小模型有时候会莫名其妙切到英文——你问它"今天天气怎么样",它回你"The weather is nice",这显然不行。另外,TEMPERATURE=0.9这个参数我调了很久——太高(>1.2)模型会胡言乱语,太低(<0.5)回复像机器人,0.9是个不错的平衡点。
LLM参数: MAX_TOKENS=256, TEMPERATURE=0.9, TOP_K=50, TOP_P=0.95
推理框架: llama.cpp (libllama.so)
模型格式: GGUF
混合策略: 规则引擎 + LLM 双引擎
提示词: ChatML格式, 含"你必须用中文回复"强制指令
REPETITION_PENALTY: 1.05 (防止重复输出)
TTS(语音合成): 基于VITS-Tiny中文模型(model.onnx约115.5MB),通过sherpa-onnx框架加载。合成22050Hz 16-bit PCM音频,通过Android AudioTrack播放。关键配置:dict_dir指向jieba分词词典——不设置的话中文TTS会逐字发音而非按词朗读,听起来像"你-好-我-是-乐-宝",非常不自然。还有一个容易被忽略的参数:silence_scale=0.2。这个控制句子间的停顿长度——太小了听起来赶,太大了像在等对方插话,0.2秒刚好是一个自然的呼吸停顿。
TTS模型: VITS-Tiny 中文 (vits-zh-hf-fanchen-wnj)
模型文件: model.onnx ~115.5MB
参数: noise_scale=0.667, noise_scale_w=0.8, length_scale=1.0
分词: jieba (dict_dir 必须配置, 否则逐字发音)
silence_scale: 0.2 (句子间停顿, 模拟自然呼吸)
输出: 22050Hz 16-bit PCM
机器人端有一个命令执行服务(robot_command_executor),监听9002端口,接收来自手机端的HTTP请求。当AI Service Hub识别出用户意图(比如"跳舞"),它会把对应的动作指令通过HTTP POST发给这个服务:

# robot_command_executor.py - HTTP API 处理 (端口9002)
class CommandExecutorHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        if self.path == "/api/executor/execute":
            body = self._read_body()
            data = json.loads(body.decode("utf-8"))
            action = data.get("action", "unknown")
            arguments = data.get("arguments", {})
            result = execute_command(action, arguments)
            self._send_json(result, 200)
        if self.path == "/api/executor/batch":
            # 批量执行多个命令 (用于舞蹈动作序列)
            data = json.loads(body.decode("utf-8"))
            commands = data.get("commands", [])
            results = []
            for cmd in commands:
                results.append(execute_command(
                    cmd.get("action"), cmd.get("arguments", {})))
            self._send_json({"ok": True, "results": results})

这个设计很简洁——手机端不需要知道机器人内部怎么执行命令,只需要发HTTP请求就行。机器人端收到请求后,调用对应的动作模块(舞蹈播放器、舵机控制、语音播报等),完成动作后返回结果。这种松耦合的设计让你可以随时替换手机端的AI模型,而不影响机器人端的执行逻辑。而且batch接口支持一次发送多个命令——比如"先鞠躬,然后说’你好’,最后跳一段舞",三个动作可以打包成一个请求,减少网络往返。
完整流程:你说一句话 → 手机麦克风收音 → Whisper转文字 → LLM生成回复 → TTS合成语音 → 手机扬声器播放。全部在手机上完成,离线运行,零延迟感知,零费用,零隐私泄露。整体从收到发来的语音文件到TTS吐出第一个声音,在1秒内,对于对话来说,这个延迟在可接受范围内——你说话停顿1秒很正常,机器人回复等1秒也不算慢。
在这里插入图片描述

关键更新:APK瘦身与Windows版本
AI Service Hub经历了30多个版本的迭代,v35解决了两个关键问题:
APK瘦身(2.6GB→400MB): 早期版本因buildozer.spec未排除临时文件、日志和测试用例,导致APK膨胀到2.6GB,解压超时启动崩溃。排查发现tmp_apk临时文件(308MB)全被打包进去了。修复后排除tmp_apk等目录和build_log、test_.py等文件,APK降到约400MB,启动正常。这个坑告诉我们:打包工具默认不帮你做清理,你得自己告诉它哪些文件不该带。具体来说,source.exclude_dirs里加了tmp_apk、tmp_orig、tmp_repack、tmp_work、.buildozer、.git,source.exclude_patterns里加了build_log.txt、test_.py、.wav等。这些配置字面上看没什么,但少了任何一条,你的APK就会多出几百MB的垃圾文件。
Windows版本支持: 新增main_windows.py入口、engines_win.py引擎(使用sherpa-onnx Python包+sounddevice替代Android原生实现),以及run_windows.bat一键启动脚本。LLM在Windows上使用规则引擎兜底(libllama.so不可用),ASR/TTS使用更好的模型。这个版本的意义是:你可以更换效果更好的大语言模型和带声音复刻和感情的TTS。
语音对话的完整链路
手机通过WiFi连接到机器人Ubuntu主控板。当你说了"跳舞",AI Service Hub识别意图后通过HTTP请求发给机器人端的命令执行服务(端口9002),命令执行服务解析意图并调用对应动作模块。
机器人端音频桥接服务负责音频输入输出。WM8960音频转接板做双声道录音和立体声播放,音频数据通过WebSocket实时传输到手机做ASR,TTS合成语音再通过WebSocket回传机器人播放。全程延迟在500ms以内,你几乎感觉不到。
在这里插入图片描述

如图:在主控板上直接通过GPIO叠加插上WM8960声音模组。
机器人端的音频桥接服务(robot_audio_bridge,端口9001)是整个语音交互链路的"耳朵和嘴巴"。看看它的核心配置:

# robot_audio_bridge.py - 音频桥接服务配置 (端口9001)
DEFAULT_CONFIG = {
    "host": "0.0.0.0",
    "port": 9001,
    "windows_server_url": "http://192.168.3.51:8090",
    "sampling_rate": 48000,
    "channels": 1,
    "vad_enabled": True,          # 语音活动检测
    "vad_threshold": 0.002,       # VAD阈值
    "silence_limit": 1.0,         # 静音超时(秒)
    "min_speech_seconds": 0.8,    # 最短有效语音
    "max_speech_seconds": 30.0,   # 最长录音时间
    "auto_listen": True,          # 自动开始监听
    "tts_playback_enabled": True, # TTS回放
    "input_device": "plughw:0,0", # WM8960录音设备
}

注意几个关键参数:sampling_rate=48000,因为WM8960声卡固定采样率就是48kHz;vad_enabled和auto_listen配合,让机器人能自动判断你什么时候在说话、什么时候说完了;silence_limit=0.5秒,你停顿超过0.5秒,系统就认为你说完了,开始做ASR识别。这个阈值调了好几次——太短会打断你说话(比如你说"嗯……“的时候就被截断了),太长会等很久才回复(你说完了它还在等,气氛尴尬)。0.5秒是基于大量测试找到的平衡点。
说实话,这套语音交互系统做出来以后,我经常跟它聊天。有时候是测试功能——“今天天气怎么样”“你是谁”“你有什么功能”——有时候纯粹是好玩,问它"你觉得人类能造出真正有意识的机器人吗”。它给了一个很长的回答,我坐在那里听完,觉得这个对话本身,比4500元这个数字有说服力得多。
2.3 舞蹈与动作编辑器:不只是个铁架子
舞蹈和动作编排不是核心功能,但它是让机器人"活过来"的那一点感觉。一个金属骨架站在你面前,和一段它跟着音乐起舞,这是两种完全不同的体验。前者你会说"哦,一个机器人",后者你会说"哇,它活了"。

舞蹈系统

舞蹈播放器是一个独立的Python模块,读取JSON格式的关键帧序列(21个舵机的目标角度和过渡时间),然后逐帧发送舵机指令。音乐通过ffplay同步播放。关键技术在关键帧之间做三次样条插值让速度曲线平滑——为什么是三次样条而不是线性插值?因为线性插值在关键帧边界处会产生速度突变,机器人的动作会看起来"一顿一顿"的。三次样条保证了一阶导数(速度)和二阶导数(加速度)的连续性,让动作流畅自然。同时监控舵机实际位置回读,动态调整关键帧发送时机,避免动作卡顿——如果舵机还没转到目标角度,下一帧指令就来了,舵机会直接跳过去,造成动作不连贯。
舞蹈播放器的底层是舵机串口通信。下面这段代码展示了如何按照Lobot协议格式构造并发送舵机指令——这是整个机器人运动的"原子操作",所有平衡、行走、舞蹈最终都变成这样的字节流发到舵机控制板:

# robot_dance_player.py - 舵机串口通信 (Lobot协议)
class ServoSerial:
    def send_positions(self, positions, move_time=50):
        """Lobot协议: 每个舵机发送独立帧"""
        for sid, pos in positions.items():
            pos = max(0, min(1000, int(pos)))   # 限幅0~1000
            t = max(0, min(30000, move_time))
            buf = bytearray(10)
            buf[0] = 0x55     # 帧头1
            buf[1] = 0x55     # 帧头2
            buf[2] = sid      # 舵机ID
            buf[3] = 7        # 数据长度
            buf[4] = 1        # 命令: CMD_MOVE_TIME_WRITE
            buf[5] = pos & 0xFF        # 位置低字节
            buf[6] = (pos >> 8) & 0xFF # 位置高字节
            buf[7] = t & 0xFF          # 时间低字节
            buf[8] = (t >> 8) & 0xFF   # 时间高字节
            buf[9] = self._checksum(buf) # 校验和
            self.ser.write(bytes(buf))
            self.ser.flush()

你可能注意到了,每个舵机是独立发送一帧的,不是把所有舵机打包在一起。这是BusLinker控制板的要求——每个舵机单独一帧,控制板逐个转发。move_time参数控制舵机从当前位置转到目标位置的时间,单位是毫秒。比如你让舵机从500转到600,move_time=50,那舵机会在50ms内完成这个动作。这个参数很关键——设太小舵机会抖动(因为舵机速度跟不上),设太大动作会拖沓(看起来像慢动作)。50ms是经过反复测试的最优值。
在这里插入图片描述
如图:用动作编辑器做好的Hitpop舞蹈动作

Unity 3D动作编辑器:为机器人编排动作

舞蹈动作不是凭空写在代码里的——你需要在PC端可视化地编排每一个关节的角度,然后导出成动作文件,机器人端再读取播放。这个编排工具就是Unity 3D动作编辑器。
关节控制面板: 21个关节滑块(-90°~+90°),拖动时3D预览实时同步,支持旋转视角、缩放。面板上实时计算重心(COM)球体——拖关节时重心球跟着移动,让你直观看到"这个姿势会不会倒"。省掉了大量"部署到真机看效果"的试错时间。以前我调完一个动作要编译、部署、上电、看效果,发现问题再改,一个动作来回折腾半小时。现在编辑器里拖拖滑块,重心球告诉你稳不稳,几分钟就能调好一个动作,当然,这个是只是一个大概的算法,并不能完全的实现稳定。
时间线编辑器: 21个关节轨道独立管理关键帧,支持线性插值和贝塞尔曲线插值。音乐同步播放,你可以一边听音乐一边调整关键帧,直到动作和节拍吻合。导出格式是.robotanimationclip(JSON),舞蹈播放器直接读取。时间线编辑器还有一个很实用的功能:框选多个关键帧批量移动。比如你觉得"这个wave动作整体太快了",框选所有关键帧,拖一下时间线,全部同步缩放,不用逐个调整。
在这里插入图片描述

BVH动作捕捉导入: 内置BVH解析器,自动把标准BVH骨骼映射到乐宝的21个关节。导入后需要手动微调(人体骨骼和乐宝关节布局不完全一样,比如人体有锁骨、肩胛骨,乐宝没有),但已省掉从零开始摆关键帧的80%工作量。你从网上下载一段跳舞的BVH文件,导入编辑器,21个关节的轨道自动生成关键帧,然后你只需要调整那些"看起来不对劲"的地方——比如手臂角度、头部朝向。后来发现BVH文件也不是那么好用,数据太多了,但是做成FBX文件又时间不够了,暂时先这样吧,期待牛人来实现。
撤销系统: Ctrl+Z撤销,Ctrl+Y重做,最多20步历史记录。调动作的时候经常"改完发现还不如原来",这个功能救了我很多次。20步的意思是,你可以大胆尝试各种角度、各种时序,因为你知道随时可以回退到20步之前的任意状态。这个功能在技术实现上很简单(一个栈存历史状态),但用户体验上却是编辑器里最重要的功能之一。
编辑器预置了60多个动作文件——从HipHop、Funk、EDM等舞蹈风格,到蝙蝠侠、关羽、孙悟空、奥特曼等角色招牌动作,再到挥手、鞠躬、点头等基础动作。这些可以直接用,也可以作为模板修改。比如你想让机器人调整"奥特曼发射光线"的动作,打开预设的奥特曼动作文件,把发射光线的那个关键帧的手臂角度调高一点,保存,搞定。不用从零开始摆十几个关键帧。
设计思路:把动作编排从"写代码"变成"拖滑块"。你在编辑器里看到的样子,就是机器人做出来的样子。所见即所得。你不用在代码里写死角度值,不用手动计算关键帧的时序,不用对着日志排查"哪个关节的角度不对"。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐