13浏览
查看: 13|回复: 0

[M10项目] 行空板M10 + 硅基流动:打造语音触发的多模态交互小助手

[复制链接]
本帖最后由 云天 于 2026-2-12 17:09 编辑

【项目背景】
       作为一名创客,我一直希望制作一个能“听懂”并“看见”世界的智能助手。借助行空板M10的硬件扩展能力和硅基流动(SiliconFlow)提供的免费AI模型调用额度,我实现了一个按键录音、语音识别、智能分析(图像/对话)、带情感/方言的语音合成的完整闭环系统。
       这个项目不仅展示了行空板在AI应用中的潜力,也验证了硅基流动API对创客项目的友好性——使用OpenAI SDK统一调用多种模态模型,门槛极低,体验流畅。
【项目亮点】
       1.行空板M10——它拥有完整的Linux系统、Python3.12环境、丰富的USB接口、板载GPIO,并且即插即用各种摄像头、麦克风、音箱。
       2.硅基流动提供的免费多模态API(语音理解、视觉分析、对话生成、情感方言TTS)完美兼容OpenAI SDK,让创客无需编写复杂代码,就能用上工业级AI模型。
       用行空板M10作为“身体”,用硅基流动AI作为“大脑”,打造一个看得见、听得懂、会方言、有情绪的智能助手。
行空板M10 + 硅基流动:打造语音触发的多模态交互小助手图4
【硬件准备】
  • 行空板:作为项目的主控板,负责运行程序和处理数据。集成麦克风用于语音输入,集成显示屏用于显示提示信息和视频内容。
  • 行空板M10扩展板组合:包括电机IO扩展板、金手指扩展板和800mAh电池扩展板。在这个项目中,主要利用800mAh电池扩展板为行空板提供稳定的移动电源,确保项目可以在没有外部电源的情况下运行。
  • 带麦克风的USB免驱摄像头,插入行空板M10的USB口,可被Python直接调用
  • 蓝牙音箱
    所有硬件即插即用,行空板M10的Linux系统完美支持OpenCV、PyAudio、SoundDevice等库,无需驱动折腾。
行空板M10 + 硅基流动:打造语音触发的多模态交互小助手图3

【软件准备】
  • Mind+:用于图形化编程和上传代码到行空板。
  • Python环境:行空板支持Python编程(暂时使用1.8版本),Mind+软件的Python模式下,连接行空板并安装相关库。
  • 硅基流动API:用于模型调用。
    通过第三方的硅基流动注册获取API,如方便注册,使用我的邀请码注册:https://cloud.siliconflow.cn/i/KwyEBX3e,邀请码:KwyEBX3e。共同获取免费额度。如果不方便注册,可使用我的API:sk-kxwsrzianqfxsebnihblrgyyytrrtgvvdjvdiujcuvwymrfp。
行空板M10 + 硅基流动:打造语音触发的多模态交互小助手图1

【软件架构】
      项目完全使用Python编写,核心库如下:
  • 录音:sounddevice + numpy + wave
  • 摄像头:opencv-python
  • AI服务:openai(硅基流动API,兼容OpenAI SDK)
  • 语音合成播放:subprocess + aplay(行空板自带)
  • 按键检测:pinpong(行空板GPIO库)

行空板M10 + 硅基流动:打造语音触发的多模态交互小助手图2

核心功能实现解析
       1. 一键录音,自动编码
利用sounddevice采集5秒16kHz单声道音频,实时转换为16-bit PCM,再封装成WAV格式并Base64编码,生成可直接用于硅基流动API的data:audio/wav;base64,xxxx字符串。
  1. def record_to_base64(duration=5, samplerate=16000):
  2.     recording = sd.rec(int(duration * samplerate), samplerate=samplerate,
  3.     channels=1, dtype='float32')
  4.     sd.wait()
  5.     recording_int16 = (recording * 32767).clip(-32768, 32767).astype(np.int16)
  6.     wav_buffer = io.BytesIO()
  7.     with wave.open(wav_buffer, 'wb') as wf:
  8.         wf.setnchannels(1)
复制代码
        2. 语音内容理解——Qwen3-Omni 多模态模型
将Base64音频直接作为audio_url发送给Qwen3-Omni-30B-A3B-Instruct模型,同时附加提示词:
“如用户说‘使用生气的口气说:我放假了’,返回‘使用生气的口气说<|endofprompt|>我放假了’;如用户说‘模拟四川方言说:今天天气不错’,返回‘模拟四川方言说<|endofprompt|>今天天气不错’……如不是以上形式,直接输出音频内容。”
       这个设计是项目的灵魂:
  • 如果用户要求方言/情感/语速,模型会自动将指令与文本内容用<|endofprompt|>分隔。
  • 如果用户说到“照片”,模型输出会包含“照片”关键词,触发后续拍照流程。
  • 普通对话则直接输出原始语音文本,交给对话模型处理。

       3. 视觉理解——Qwen2.5-VL 72B
       当检测到内容含“照片”时,立即调用cv2.VideoCapture拍照,编码为JPEG Base64,通过image_url传给Qwen2.5-VL-72B-Instruct,并将刚才的识别文本作为问题发送。例如用户说“这张桌子上的杯子是什么颜色?”,模型会输出“红色的马克杯”。
       4. 普通对话——Qwen2.5-Coder 32B
       对于不含“照片”且不含<|endofprompt|>的内容,将其作为纯文本输入给对话模型,获得流畅自然的回复。
       5. 情感/方言语音合成——FunAudioLLM/CosyVoice2-0.5B
       这是项目的另一大亮点。硅基流动提供的CosyVoice2模型支持跨语言、方言、情感、细粒度韵律控制。我们直接将从AI获得的文本(可能已包含<|endofprompt|>指令)传给TTS接口,例如:
  1. "使用生气的口气说<|endofprompt|>我真的很生气!"
复制代码
       模型会自动识别“生气的口气”指令,合成出饱含情绪的语音,并保留方言特征(粤语、四川话等)。
       播放:行空板预装aplay,直接将合成好的WAV文件通过命令行播放,稳定无依赖。
  1. def tts_and_play(text):
  2.     speech_file = Path(__file__).parent / "speech.wav"
  3.     tts_response = client.audio.speech.create(
  4.         model="FunAudioLLM/CosyVoice2-0.5B",
复制代码
【完整代码】

# ---------- 导入库 ----------
import sounddevice as sd
import numpy as np
import wave
import io
import base64
import subprocess
import cv2
from pathlib import Path
from openai import OpenAI
from pinpong.board import Board, Pin
from pinpong.extension.unihiker import *
import time
import traceback
import sys

Board().begin()
btn = Pin(Pin.P21, Pin.IN)  # 行空板板载按键

# ---------- 硅基流动客户端 ----------
client = OpenAI(
    api_key="sk-你的新密钥",   # 替换为你自己的
    base_url="https://api.siliconflow.cn/v1"
)

# 摄像头初始化
cap = cv2.VideoCapture(0)
if not cap.isOpened():
    print("无法打开摄像头")
    sys.exit(1)

# ---------- 函数定义 ----------
def image_to_base64(image):
    if image is None:
        return None
    success, encoded = cv2.imencode('.jpg', image)
    if not success:
        return None
    return base64.b64encode(encoded).decode('utf-8')

def tts_and_play(text):
    speech_file = Path(__file__).parent / "speech.wav"
    tts_response = client.audio.speech.create(
        model="FunAudioLLM/CosyVoice2-0.5B",
        voice="FunAudioLLM/CosyVoice2-0.5B:alex",
        input=text,
        response_format="wav"
    )
    tts_response.stream_to_file(speech_file)
    subprocess.run(["aplay", str(speech_file)], check=True)

def record_to_base64(duration=5):
    print(f"录音{duration}秒...")
    recording = sd.rec(int(duration * 16000), samplerate=16000, channels=1, dtype='float32')
    sd.wait()
    recording_int16 = (recording * 32767).clip(-32768, 32767).astype(np.int16)
    wav_buffer = io.BytesIO()
    with wave.open(wav_buffer, 'wb') as wf:
        wf.setnchannels(1)
        wf.setsampwidth(2)
        wf.setframerate(16000)
        wf.writeframes(recording_int16.tobytes())
    wav_buffer.seek(0)
    b64_str = base64.b64encode(wav_buffer.read()).decode('utf-8')
    return f"data:audio/wav;base64,{b64_str}"

# ---------- 主循环 ----------
def main():
    while True:
        if btn.read_digital():
            try:
                # 1. 录音+音频理解
                audio_url = record_to_base64()
                print("分析音频中...")
                resp = client.chat.completions.create(
                    model="Qwen/Qwen3-Omni-30B-A3B-Instruct",
                    messages=[{
                        "role": "user",
                        "content": [
                            {"type": "audio_url", "audio_url": {"url": audio_url}},
                            {"type": "text", "text": "如果用户说“使用生气的口气说:我放假了”,返回“使用生气的口气说<|endofprompt|>我放假了”;如果用户说“模拟四川方言说:今天天气不错”,返回“模拟四川方言说<|endofprompt|>今天天气不错”……如不是以上形式,直接输出音频内容。"}
                        ]
                    }]
                )
                content = resp.choices[0].message.content
                print("音频理解:", content)

                # 2. 判断分支
                if "照片" in content:
                    print("触发拍照分析")
                    ret, frame = cap.read()
                    if not ret:
                        continue
                    img_b64 = image_to_base64(frame)
                    if img_b64:
                        data_uri = f"data:image/jpeg;base64,{img_b64}"
                        resp = client.chat.completions.create(
                            model="Qwen/Qwen2.5-VL-72B-Instruct",
                            messages=[{
                                "role": "user",
                                "content": [
                                    {"type": "image_url", "image_url": {"url": data_uri}},
                                    {"type": "text", "text": content}
                                ]
                            }]
                        )
                        content = resp.choices[0].message.content
                        print("视觉分析:", content)

                elif "<|endofprompt|>" not in content:
                    # 普通对话
                    resp = client.chat.completions.create(
                        model="Qwen/Qwen2.5-Coder-32B-Instruct",
                        messages=[{"role": "user", "content": content}]
                    )
                    content = resp.choices[0].message.content
                    print("对话回复:", content)

                # 3. 语音播报
                tts_and_play(content)

            except Exception as e:
                traceback.print_exc()
            finally:
                print("--- 等待下次按键 ---")
                time.sleep(0.5)
        time.sleep(0.1)

if __name__ == "__main__":
    main()
行空板M10 + 硅基流动:打造语音触发的多模态交互小助手图5

【演示视频】


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2026 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail