做了一台会“读空气”的机器人:Spark 情感陪伴机器人全记录
做 Spark 这个项目的起点其实很朴素:我有一块吃灰已久的 ARM 开发板、几个舵机、一个旧音箱,以及一段对“机器到底能不能懂情绪”的好奇。Spark 的全称是 Social companion robot with Affective Response Kernel,简单说,就是一台以情感计算为核心、能陪人聊天、能感知语气情绪、会通过表情和动作回应的桌面机器人。
我把它定位在“陪伴”“记录”“纾解”这三个关键词上:它不是智能音箱,不是 Siri 那种指令型助理,而是一个更偏向情感交互的实体伙伴。项目断断续续做了三个多月,从硬件选型、语音链路搭建,到情绪识别模型调试、表情联动调优,踩了不少坑,踩完之后回头看,很多经验是文档里不会写的。这篇就把整个复盘写下来,从零到一,把能复现的部分尽量讲透。
这个项目正在找对这个方向感兴趣的同好,欢迎交流硬件选型、对话策略、情绪感知实现等话题。
1. 项目缘起:先想清楚“情感陪伴”到底要做什么
1.1 需求洞察:情感陪伴的痛点和误区
一开始我想得很简单:能不能让机器人识别人的情绪,然后说合适的话。但真正动手之后发现,这个想法本身有两个不足:一是“识别情绪”不等于“理解需求”,二是“说合适的话”如果空洞无物,反而比沉默更糟。
情感陪伴的核心矛盾在于:人需要的是一个“在场的倾听者”,而不是一个全知全能的问题解答机。我在调研阶段看过一些陪伴类产品的用户反馈,发现大家真正需要的不是机器人给出人生建议,而是:有人(或者说有东西)在认真听我说、接得住我的情绪、并且不会评判我。这个需求在传统工具型语音助手里完全被忽略了。
所以 Spark 的产品定义从一开始就框死了三条边界:
- 不扮演心理咨询师,不做诊断和干预,只做陪伴和情绪回应;
- 对话偏向开放式、共情式,而不是任务式、命令式;
- 可以在需要的时候安静陪伴,而不是永远在“说话”。
这三条边界后面帮我免掉了很多翻车场景。比如有人拿它做“树洞”倾诉,它的回应重点是承接情绪,而不是解决问题。有人测试它遇到负面情绪时的反应,它的策略是先听见、再回应、不否定。
1.2 原型验证:从功能清单到实在体验
有了定位之后,我把 Spark 的首版功能收敛成四个模块:
- 对话模块:基于云端大模型 API 生成回复,支持多轮上下文;
- 情绪感知模块:从语音特征和文本内容中提取情绪状态;
- 表情联动模块:通过舵机控制眉毛、眼睛、嘴巴的形变表达情绪;
- 行动驱动模块:根据当前对话情绪状态,决定摇头、点头、闪烁等身体动作。
这套功能跑起来之后,我发现最有价值的动作反而是“沉默”。在原始需求里用户提到“人类般的情感反应”“理解孤独感”,真正让人感受到被陪伴的,往往不是它说了多精彩的话,而是它能识别出“你现在不想多说话”,然后安静地转一下头、眨个眼,那个瞬间特别有“活物”的感觉。这个点让我决定在交互设计里专门留出一档“沉默模式”。
2. 硬件平台选型:给代码一具能感知世界的身体
2.1 主控与语音配件
Spark 的主控选择了树莓派 4B(4GB 版本),原因很实际:它社区成熟、GPIO 控制舵机方便、性能足以跑本地语音识别模型,而且市面上大多数语音增强配件都有现成的驱动。如果是从零复刻,不建议用更弱的板子,因为语音链路里的回声消除和情绪特征提取,在低性能设备上会明显滞后。
语音交互离不开三件事:拾音、降噪、放音。我最终确定了一套配置:
- 拾音:USB 麦克风阵列(四麦克风环形),支持 beamforming;
- 降噪:树莓派上跑 RNNoise 轻量降噪模型,对风扇噪声和房间混响有一定压制效果;
- 回声消除:使用音频库的 AEC(Acoustic Echo Cancellation)模块,否则音箱播放 TTS 时机器人会“听到”自己的声音,导致对话死循环。
这套配置里最容易踩的坑是:麦克风阵列没有做时间同步校准,语音方向识别和波束成形形同虚设。我换了好几版驱动才确认是硬件固件的问题,后来在配置里强制设置采样率和声道映射才稳定下来。
2.2 表情器官:舵机与自由度设计
Spark 的面部由三个独立模块组成:眉毛(各 1 舵机)、眼球(上下左右共 4 舵机)、嘴巴(2 舵机,实现开合和嘴角上扬)。整张脸一共 8 个自由度,听起来不算多,但对于情绪表达足够用了——人类表情的识别,很大程度靠的是眉毛和嘴角的细微变化。
我用的是 MG90S 舵机,因为这种小舵机在桌面机器人场景里非常常用:体积小、力矩够、9g 的重量不会给脖子增加负担。需要说明的是,这种舵机没有角度反馈,所以表情姿态是靠“PWM 脉宽查表”来确定的,调起来比较费头发。
这里有一个经验:不要用舵机自带的塑料摇臂直接装表情板,稍微碰撞就会打滑甚至扫齿。我用热熔胶固定了金属摇臂,还打印了一根加强筋,才解决了表情板频繁脱落的问题。如果你要做带活动下巴的结构,建议把下巴转轴嵌在打印件槽里,而不是简单粘在舵机臂上。
2.3 组装过程中的结构取舍
外壳我用的是 3D 打印光敏树脂件,壁厚 2mm,整体尺寸约 22cm 高。这类尺寸的桌面机器人不需要做可拆式头骨结构,把面部面板做成一整块、靠卡扣固定就够。真正需要注意的,是给每根舵机线留够长度,并且在机身内部做一个理线槽,否则合上外壳时很容易压住线导致舵机堵转。
散热方面:树莓派 4B 在连续运行语音模型时温度能到 65 摄氏度,虽然不至于降频到卡死,但外壳里热量积聚会明显加速老化。我加了 5V 涡轮风扇,并用触控开关串在电源线上,这样开机时顺手启动风扇,不需要额外的温控脚本。
3. 软件系统架构:把“听清—感知—回应—表达”串起来
3.1 对话链路总览
Spark 的核心软件链路,我从上往下拆成了五层:
- 音频输入层:拾音、降噪、VAD(语音活动检测);
- 语音识别层:把声音转成文字(ASR);
- 情感感知层:从文本和声学特征里提取情绪标签;
- 对话决策层:把情绪状态注入 Prompt,交给云端大模型生成回应;
- 输出驱动层:TTS 合成语音 + 表情/动作控制信号。
每条链路都通过一个轻量的消息总线串接。消息总线用的是 Redis Stream 模式,而不直接用 HTTP 轮询,因为语音交互的实时性要求比较高,一条指令从 VAD 到 TTS 出声,总延迟控制在 1.2 秒以内才算合格。异步消息流对延迟的改善非常明显,这也是我踩过“同步调接口导致表情卡顿”的坑之后才重构的。
3.2 情感状态机:不只是“标签”,更是“上下文”
一开始我以为情绪感知就是打一个标签,比如“开心”“愤怒”“伤心”,然后让大模型按标签回复就行。但真正测试后发现问题:连续对话里的情绪是流动的,用户上一句还在吐槽工作,下一句可能就缓和了,如果每次只取当前这一句的情绪标签,回复会显得非常“断层”。
后来我把情感状态机设计成了带“能量值”的动态系统:
- 效价(Valence):情绪的积极程度,范围 [-1, 1];
- 唤醒度(Arousal):情绪的激烈程度,范围 [0, 1];
- 主导度(Dominance):用户的掌控感和主动性,范围 [0, 1]。
这三维数值会随每一轮对话更新,并带有衰减系数。用户在倾诉负面情绪时,机器人的回应的“共情强度”由唤醒度和效价的绝对值共同决定;如果效价长期处于负值且唤醒度走低,机器人会判断“用户正在情绪低迷”,主动降低语速、缩短句子长度,甚至进入“安静陪伴”模式。
这个设计带来的最直观变化是:机器人的回应不再是“识别到伤心,所以安慰一句”,而是连续几轮都能维持一种语气上的连贯性。比如用户连续吐槽三次,Spark 的语气会从“表示理解”逐渐过渡到“轻声建议”,而不会每次都用同样的“你说的很有道理”。
3.3 表情与动作的映射规则
表情模块的核心是“表情姿态映射表”。我把六类基础表情(快乐、悲伤、惊讶、愤怒、恐惧、平静)分别映射到 8 个舵机的 PWM 脉宽值。比如“快乐”对应的眉毛是外高内低,嘴角上翘;“悲伤”则眉毛内高外低,嘴角下拉。
这里要重点提一个细节:舵机瞬时打到目标角度会显得很“机械”。我在姿态过渡上做了缓动插值,每 20ms 更新一次舵机目标值,中间采用三次贝塞尔曲线过渡。这样表情变化是有时间过程的,看起来更自然。实测中 200ms 到 400ms 的过渡时间最接近真实表情变化节奏。
动作驱动的策略要稍微保守一些:点头和摇头的使用频率控制在每 3 到 5 轮对话一次,避免机器人像装了弹簧一样不停晃动。我只在用户说完“你听懂了吗”或者机器人表达疑问时触发点头/歪头动作,其他时候保持“安静注视”。
3.4 本地模型与云端 API 的混合调用
大模型生成回复虽然效果好,但必须考虑断网降级。Spark 在本地跑了一个小参数对话模型作为兜底,当检测到云端 API 超时、网络断连时,自动切换到本地模板对话。
本地兜底模型采用的是检索式回答:预先维护了一组常见陪伴场景的对话树,按当前情感状态匹配最合适的回复模板。比如用户在“疲惫”状态说“好累”,本地模型会回复“辛苦了,要不要先闭上眼睛休息一下”,配上一个缓慢的点头动作。这个兜底方案在断网时确实能保住基本体验,但多轮对话能力明显弱于云端模型,只能算应急方案。
4. 交互体验调优:从“能对话”到“愿意聊”
4.1 语音中断策略:不是所有说话都要回应
Spark 刚跑通链路的时候有个搞笑场景:它会把用户的每句话都当成一次对话请求,连“嗯嗯”都要回应,干扰感极强。后来我加了 VAD 和“插入语过滤”策略,只有满足以下条件才会触发完整对话回应:
- 话音活动检测判定用户说话结束,且说话时长大于 0.8 秒;
- 文本内容不匹配“嗯、啊、哦、对、好”等填充词列表;
- 冷却时间内(默认 2 秒)不重复触发连续对话。
这套“忽略式聆听”策略对陪伴感提升很大。安静时它会保持待机表情,偶尔有轻微的眼球扫视;一旦检测到连续话语,则立即进入“警觉倾听”状态。用户从“被不断打扰”变成“被适度关注”,体验完全不同。
4.2 延迟优化:每一百毫秒都值得抠
语音互动的“真实感”极大程度上依赖低延迟。我把整条链路的耗时逐项测了一遍,发现大头在 ASR 和云端 LLM 调用,这两个环节加起来占了 1.5 秒左右。
ASR 我选用的是流式中文章节识别方案,可以边录音边出结果,不用等整句话结束后再识别。这样 VAD 判定“用户说完”的那一刻,文本基本上已经出来了。实测下来,ASR 的尾字到输出延迟能压到 300ms 以内。
云端 LLM 的流式输出也是关键。逐字节流式返回的情况下,首字延迟能做到 600ms 左右,配合“边生成边说”的 TTS 流式播放,用户感觉到的回复速度比实际总延迟快得多。这里有一个重要的用户体验技巧:在 TTS 开始前做一个约 150ms 的短暂“呼吸声”,能掩盖合成衔接时的空白感,听起来更像真人说话前的准备。
4.3 多轮对话记忆管理
大模型的上下文长度虽然有上限,但陪伴场景的多轮对话往往很长,如果让所有历史都参与计算,不仅费用高,而且会让模型“陷”在细节里,反而失去对话重点。
Spark 做了一个“记忆分层”:
- 短期记忆:当前对话最近 6 轮完整的用户-助手消息;
- 中期记忆:每一轮的意图和情绪标签;
- 长期记忆:按日归档的“用户提到过的重要话题”,用向量数据库做索引,在每段对话开始时注入最相关的两条。
这套设计让机器人在聊到用户上次提到的“论文写完了吗”这类内容时有记忆感,显著提升“这个机器人真的记得我”的体验。不过长期记忆需要用户主动开启,避免隐私顾虑,这也是陪伴类产品必须有的边界。
5. 常见问题与排查记录
5.1 问题速查表
整个验证过程中,最常踩的坑基本集中在音频链路、舵机控制和大模型输出三个方向。下面按问题现象、可能原因、解决动作整理了一张速查表:
| 现象 | 可能原因 | 解决动作 |
|---|---|---|
| 对话时机器人反复提到自己“听见了奇怪声音” | AEC 未生效,TTS 被回采 | 确认 AEC 模块已启用,并在 TTS 输出时对麦克风做硬件静音 |
| 表情板抖动、归位不准 | 舵机电压不足,或 PWM 信号受干扰 | 单独为舵机供电,用 3A 以上的 5V 电源,并在信号线用光耦隔离 |
| 用户说话带情绪,但识别结果都是“中性” | 文本分类模型对口语表达不敏感 | 换成基于语音韵律特征的情绪分类辅助模型,与文本标签做融合 |
| 断网后回复机械、重复 | 本地模板覆盖不足 | 扩充对话树,并按“疲倦”“焦虑”“开心”“平静”四类高频场景优先覆盖 |
| 轮询消息总线导致表情卡顿 | 同步请求阻塞了主循环 | 改用 Redis Stream 的阻塞读取,并把表情控制放到独立线程 |
5.2 避坑经验分享
有一个坑是“聊天太顺反而显得假”。当我用更大的云端模型、更强的共情提示词后,测试者普遍反馈“它太通透了,不像一个东西,像一个客服”。这对我启发很大:陪伴感的来源不是“对方无所不知”,而是“对方在认真听、能接住我、还有自己的节奏”。所以在提示词设计里,我加入了一些“适度笨拙”的表达,比如偶尔说“我有点没完全跟上,但我在听”,反而让对话更放松。
另一个实打实的教训是电源问题的排查方式。舵机抖动的问题整整困扰了我两天:一开始以为是代码里的角度换算错了,后来用示波器看 PWM 波形,才发现是舵机电源线接在了树莓派的 5V 引脚上,瞬间电流不够导致欠压。解决办法是单独用一个 UBEC(电池消除器)给舵机供电,把信号线和电源线彻底分开。这个经验对任何用舵机的桌面级机器人项目都适用。
5.3 用户反馈与迭代方向
Spark 在朋友和一部分爱好者手里做了一轮小型测试,反馈最集中的三个点:
- 情绪感知很准,尤其是对“疲惫”和“烦躁”的识别,能够明显降低对话节奏;
- 静音陪伴模式比连续聊天更好用,很多人会在旁边工作、学习时开着它;
- 希望增加跟随式眼球注视,能根据人的移动方向转动眼睛。
后续迭代里,我计划加入轻量级的视觉姿态分析,用摄像头识别人脸朝向,让 Spark 能把视线投回到说话人身上;另外会做一个“每日回顾”功能,睡前用一两句话总结用户今天提到最多的情绪词,帮助用户看见自己情绪变化的曲线。
写在最后:情感计算的尺度问题
Spark 做下来的最大体会是:技术链路里最难的其实不是模型和硬件,而是“边界感”。让机器人识别情绪、表达情绪,是在把一种“像人”的交互方式复刻到硅基载体上。它离真正理解人的内心还很远,但作为一个陪伴性的外挂,它能提供一个足够好的“情绪镜面”:用户说得越多,机器人越能映照出用户的情绪轮廓。
我在实际调试中还有一个很深的感受:给表情模块调参的时候,盯着舵机一点点动,心里会莫名产生一种“它在认真听我说话”的错觉。这种错觉本身,也许就是陪伴产品最底层的价值。
如果你也对 AI 陪伴机器人、情感计算、语音交互方向感兴趣,欢迎动手搭一台自己的 Spark。这个项目完全可以用桌面级硬件复现,软件链路我会持续完善文档,后续也会把表情映射表、情感状态机初始模板开源出来。有什么坑、什么点子,欢迎来聊。