博客感知规控语音控制+征程6:搭建一个智能驾驶多模态交互原型

语音控制+征程6:搭建一个智能驾驶多模态交互原型

默认265282026-08-30
25
0

做智能驾驶,除了视觉感知和路径规划,人机交互也是一个重要方向。这篇记录一下我用征程6开发板 + 手机APP + 语音模块搭建的一个“语音控制小车”多模态交互原型。涉及语音识别、自然语言理解、指令分发、远程通信等多个技术点。

一、系统架构

手机APP(语音输入)

|

| CAN/UART

小车底盘(电机/舵机)

手机APP负责语音采集和ASR,把语音转成文本指令后通过WiFi发给征程6。征程6上做NLU,把文本解析成结构化控制指令,再通过CAN或UART发给底盘执行。

二、语音识别

手机端用百度语音识别API,在线+离线结合。在线精度高但依赖网络,离线精度低但不需要网络。室内WiFi环境下主要用在线识别。

```kotlin

// Android端

val speechRecognizer = SpeechRecognizer.createRecognizer(this) {

setParameter(SpeechConstant.APP_ID, "YOUR_APP_ID")

setParameter(SpeechConstant.LANGUAGE, "zh_cn")

setParameter(SpeechConstant.ACCENT, "mandarin")

}

speechRecognizer.startListening(object : RecognitionListener {

override fun onResult(results: Array?, isLast: Boolean) {

val text = results?.get(0)?.recognitionResult ?: ""

webSocketClient.send("{\"command\": \"$text\"}")

}

})

```

离线备选:PaddleSpeech轻量模型,50MB大小,识别率80%左右。

三、自然语言理解

征程6板端收到文本指令后做NLU解析。设计了规则+模板匹配的解析器:

```cpp

enum class CommandType {

MOVE_FORWARD, MOVE_BACKWARD, TURN_LEFT, TURN_RIGHT,

STOP, SET_SPEED, UNKNOWN

};

struct VehicleCommand {

CommandType type;

float speed = -1.0f;

float duration = -1.0f;

float angle = -1.0f;

};

class NLUParser {

vector> patterns_ = {

{regex(R"(前进|往前走|向前|直行)"), MOVE_FORWARD},

{regex(R"(后退|往后退|倒车)"), MOVE_BACKWARD},

{regex(R"(左转|向左转|往左)"), TURN_LEFT},

{regex(R"(右转|向右转|往右)"), TURN_RIGHT},

{regex(R"(停|停止|刹车|别动)"), STOP},

};

public:

VehicleCommand Parse(const string& text) {

VehicleCommand cmd;

for (const auto& [pattern, type] : patterns_) {

if (regex_search(text, pattern)) {

cmd.type = type;

// 提取参数(距离、速度、角度)

smatch match;

if (regex_search(text, match, regex(R"(\d+)"))) {

float value = stof(match[1].str());

if (text.find("米") != string::npos) cmd.duration = value;

else if (text.find("速度") != string::npos) cmd.speed = value / 3.6f;

else if (text.find("度") != string::npos) cmd.angle = value;

}

return cmd;

}

}

cmd.type = UNKNOWN;

return cmd;

}

};

```

四、WebSocket通信

手机APP和征程6板端之间走WebSocket,双向实时。

板端WebSocket服务器:

```cpp

#include

class VehicleWebSocketServer {

struct lws_context* context_ = nullptr;

vector clients_;

NLUParser parser_;

VehicleController controller_;

static int Callback(struct lws* wsi, enum lws_callback_reasons reason,

void* user, void* in, size_t len) {

auto* server = static_cast(

lws_context_user(lws_get_context(wsi)));

switch (reason) {

case LWS_CALLBACK_ESTABLISHED:

server->clients_.push_back(wsi);

break;

case LWS_CALLBACK_CLOSED:

// 移除断开连接的客户端

break;

case LWS_CALLBACK_RECEIVE: {

string msg(static_cast(in), len);

server->OnMessage(wsi, msg);

break;

}

}

return 0;

}

void OnMessage(struct lws* wsi, const string& msg) {

// 解析JSON

Json::Value root;

Json::Reader reader;

if (!reader.parse(msg, root)) return;

string text = root.get("command", "").asString();

auto cmd = parser_.Parse(text);

controller_.ExecuteCommand(cmd);

// 回复确认

Json::Value ack;

ack["type"] = "ack";

ack["parsed"] = CommandToString(cmd.type);

SendMessage(wsi, ack.toStyledString());

}

public:

bool Start(int port = 9002) {

struct lws_context_creation_info info;

memset(&info, 0, sizeof(info));

info.port = port;

info.protocols = protocols_;

context_ = lws_create_context(&info);

thread server_thread([this]() {

while (running_) {

lws_service(context_, 50);

}

});

server_thread.detach();

return true;

}

void BroadcastStatus(const VehicleStatus& status) {

Json::Value msg;

msg["type"] = "status";

msg["speed"] = status.speed;

msg["obstacle_detected"] = status.obstacle_detected;

for (auto wsi : clients_) {

SendMessage(wsi, msg.toStyledString());

}

}

};

```

五、CAN总线控制

```cpp

class CANController {

int socket_ = -1;

public:

bool Init(const char* interface = "can0") {

socket_ = socket(PF_CAN, SOCK_RAW, CAN_RAW);

if (socket_ < 0) return false;

struct ifreq ifr;

strcpy(ifr.ifr_name, interface);

ioctl(socket_, SIOCGIFINDEX, &ifr);

struct sockaddr_can addr;

addr.can_family = AF_CAN;

addr.can_ifindex = ifr.ifr_ifindex;

bind(socket_, (struct sockaddr*)&addr, sizeof(addr));

return true;

}

void SendMotorCommand(float left_speed, float right_speed) {

struct can_frame frame;

frame.can_id = 0x101;

frame.can_dlc = 4;

int32_t left_rpm = static_cast(left_speed * 100);

int32_t right_rpm = static_cast(right_speed * 100);

memcpy(frame.data, &left_rpm, 2);

memcpy(frame.data + 2, &right_rpm, 2);

write(socket_, &frame, sizeof(frame));

}

};

```

六、多模态融合:语音+视觉

语音控制只是交互入口,真正智能的地方在于语音指令和视觉感知的融合。比如用户说"往前走",但摄像头看到前方有行人,系统应该拒绝执行或自动绕开。

```cpp

class SafetyOverride {

public:

bool IsSafe(const VehicleCommand& cmd, const PerceptionResult& perception) {

if (cmd.type == CommandType::MOVE_FORWARD) {

for (const auto& obj : perception.obstacles) {

if (obj.distance < 2.0f && abs(obj.angle) < 30.0f) {

return false;

}

}

}

return true;

}

VehicleCommand Adjust(const VehicleCommand& cmd, const PerceptionResult& perception) {

if (cmd.type == CommandType::MOVE_FORWARD) {

float min_dist = 999.0f;

float obstacle_angle = 0.0f;

for (const auto& obj : perception.obstacles) {

if (obj.distance < min_dist && abs(obj.angle) < 45.0f) {

min_dist = obj.distance;

obstacle_angle = obj.angle;

}

}

if (min_dist < 3.0f) {

VehicleCommand adjusted;

adjusted.type = obstacle_angle < 0 ? TURN_RIGHT : TURN_LEFT;

adjusted.angle = 30.0f;

return adjusted;

}

}

return cmd;

}

};

```

七、语音反馈

板端通过WebSocket把状态推送给手机APP,APP用TTS播报:

```kotlin

val tts = TextToSpeech(context) { status ->

if (status == TextToSpeech.SUCCESS) {

tts.language = Locale.CHINESE

}

}

fun onStatusReceived(status: VehicleStatus) {

when {

status.obstacleDetected -> {

tts.speak("前方检测到障碍物,正在绕行", TextToSpeech.QUEUE_ADD, null, null)

}

status.safetyState == "SAFE" -> {

tts.speak("系统检测到异常,已停车", TextToSpeech.QUEUE_ADD, null, null)

}

}

}

```

八、注意事项总结

1. 语音识别:在线+离线结合,手机端ASR降低板端计算压力。

2. NLU设计:规则匹配为主覆盖90%场景,复杂指令再考虑模型。

3. 通信协议:WebSocket全双工,JSON协议简单清晰,状态主动推送。

4. 安全策略:语音指令必须经过视觉感知的安全校验,不能盲执行。

5. CAN优先级:安全指令永远最高优先级,防止控制冲突。

6. WiFi时效性:加timestamp和预发送机制,降低网络抖动影响。

感知规控
社区征文征程6
评论0
0/600