# 概览
AIUI多模态VAD技术:即通过摄像头,将视频流中的人脸唇形实时检测识别,结合语音、能量等VAD技术多种 维度判别主要说话人意图,辅助云端多模态识别引擎,保障在多人、嘈杂的复杂场景下精准识别,实现人机交 互的流畅性。
多模态VAD功能的获取和授权请联系我们。
# 接入指南
# 导入资源
- avvad.jet
语音+唇动模型资源
多模态vad资源放到工程中的assets目录下,在配置时以assets资源类型指明在assets下的相对路径。 具体配置可以参考下面配置文件中的资源类型和资源路径的配置。
# 配置文件
aiui.cfg中多模态vad合成配置,同时开启人脸引擎,用于定位人脸及唇部位置:
// 人脸识别引擎配置
"dsm": {
"min_face_size_w": "80", // 最小人脸宽度
"min_face_size_h": "100", // 最小人脸高度
"max_face_size_w": "260", // 最小人脸宽度
"max_face_size_h": "300", // 最小人脸高度
"is_lip_face":"1", //裁出人脸
"auto_wakeup": "1", // 内部是否自动执行唤醒和休眠操作
"fade_out_ms": 3000, // 最后未检测到人脸自动休眠的时常
"fade_in_ms": 1000, // 持续检测到人脸自动唤醒的时常
"enable": "1",
"head_angle_enable": "1", // 是否启用人脸翻转角检测
"head_angle_yaw": "20",
"head_angle_pitch": "20"
},
// 多模态vad参数
"vad":{
"vad_enable":"1",
"engine_type":"avvad",
"res_type":"assets",
"res_path":"vad/avvad.jet",
"vad_bos": "5000",
"vad_eos": "1000",
"threshold": "0.5",
"cloud_vad_eos":"1000",
"cloud_vad_gap":"500",
"avvad_is_face_mask_valid": "1",
"avvad_debug": "0"
},
// 录制参数:相机、声卡
"recorder":{
"channel_count":8,
"channel_filter":"0,1,2,3,4,5,6,7",
"sound_card_name":"USB-Audio",
//"cam_id":"/dev/video12", // 相机参数:多个摄像头时指定id
"cam_api":"2", // 相机参数:APILEVEL:1或2
"cam_aspect_ratio":"16:9", // 相机参数:宽高比
"cam_max_px":921600, // 相机参数:最大分辨率1280*720
"cam_clip_left":"0.35", // 图像采集后处理(减少运算量):左边裁剪比例
"cam_clip_right":"0.35", // 图像采集后处理(减少运算量):右边裁剪比例
"cam_clip_top":"0.25", // 图像采集后处理(减少运算量):上边裁剪比例
"cam_clip_bottom":"0.25", // 图像采集后处理(减少运算量):下边裁剪比例
"cam_rotate":"0", // 图像采集后处理:图像旋转角度
"cam_skip_frame":1, // 图像采集后处理(减少运算量):跳帧处理
"sound_device": 0,
"format": 0,
"channel": 8
},
# 调用方式
# 内部采集音视频
当使用sdk内部录制视频时,sdk会根据cfg中的配置来开启相机,并对图像做裁剪、旋转、镜像等预处理。
// 打开录音
mAIUISDKAgent.sendMessage(new AIUIMessage(AIUIConstant.CMD_START_RECORD, 0, 0, "data_type=audio",
null));
// 打开相机
mAIUISDKAgent.sendMessage(new AIUIMessage(AIUIConstant.CMD_START_RECORD, 0, 0, "data_type=image",
null));
# 外部写入图像
外部写入使用CMD_WRITE指令,图像要求格式:bgr,帧率:12-15fps(如30fps的摄像头可通过隔一帧取 一帧来实现),像素大小视设备性能来定(rk3399开发板上分辨率不高于307200,640*480)。
实例代码:
// 图像数据的参数width:宽度;height:高度;frame_index:帧序号1/2/3/4...
String params = String.format("data_type=image,width=%d,height=%d,frame_index=%d",width, height,
frameIndex);
// data为单帧图像数据,长度为width*height*3
AIUI.sendMessage(AIUIConstant.CMD_WRITE, 0, 0, params, data);
# 结果解析
人脸唤醒、sdk内部采集的相机数据通过AIUIConstant.EVENT_RESULT事件接收:人脸检测的sub值为 “dsm”,相机图像数据的sub为“img”。
private int face_w; // 检测到的人脸宽度
private int face_h; // 检测到的人脸高度
private int face_x; // 检测到的人脸坐标左上角x
private int face_y; // 检测到的人脸坐标左上角y
private AIUIListener mAIUIListener = new AIUIListener() {
@Override
public void onEvent(AIUIEvent event) {
Log.i(TAG, "on event: " + event.eventType);
switch (event.eventType) {
case AIUIConstant.EVENT_RESULT: {
try {
JSONObject bizParamJson = new JSONObject(event.info);
JSONObject data = bizParamJson.getJSONArray("data").getJSONObject(0);
JSONObject params = data.getJSONObject("params");
JSONObject content = data.getJSONArray("content").getJSONObject(0);
String sub = params.optString("sub");
if (content.has("cnt_id") && !"tts".equals(sub)) {
String cnt_id = content.getString("cnt_id");
String cntStr = new String(event.data.getByteArray(cnt_id), "utf-8");
// 获取从数据发送完到获取结果的耗时,单位:ms
// 也可以通过键名"bos_rslt"获取从开始发送数据到获取结果的耗时
long eosRsltTime = event.data.getLong("eos_rslt", -1);
// mTimeSpentText.setText(eosRsltTime + "ms");
if (TextUtils.isEmpty(cntStr)) {
return;
}
LogUtil.i(TAG, cntStr);
JSONObject cntJson = new JSONObject(cntStr);
if ("dsm".equals(sub)) {
parseDsm(event, content, cntJson);
} else if ("img".equals(sub)) {
parseImg(event, content, cnt_id);
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
break;
case AIUIConstant.EVENT_ERROR: {
showTip("错误: " + event.arg1 + "\n" + event.info);
}
break;
case AIUIConstant.EVENT_VAD: {
if (AIUIConstant.VAD_BOS == event.arg1) {
// showTip("找到音频前端点");
} else if (AIUIConstant.VAD_EOS == event.arg1) {
// showTip("找到音频后端点");
}
}
break;
case AIUIConstant.EVENT_STATE: { // 状态事件
int mAIUIState = event.arg1;
if (AIUIConstant.STATE_IDLE == mAIUIState) {
// 闲置状态,AIUI未开启
// showTip("STATE_IDLE");
} else if (AIUIConstant.STATE_READY == mAIUIState) {
// AIUI已就绪,等待唤醒
// showTip("等待唤醒");
} else if (AIUIConstant.STATE_WORKING == mAIUIState) {
// AIUI工作中,可进行交互
// showTip("已唤醒");
}
}
break;
default:
break;
}
}
};
// 解析人脸
private void parseDsm(AIUIEvent event, JSONObject content, JSONObject cntJson) throws
JSONException, UnsupportedEncodingException {
JSONObject rect = cntJson.getJSONObject("detect_rect");
face_w = rect.getInt("w");
face_h = rect.getInt("h");
face_x = rect.getInt("x");
face_y = rect.getInt("y");
}
// 解析采集图片,并绘制人脸框
private void parseImg(AIUIEvent event, JSONObject content, String cnt_id) throws
JSONException, UnsupportedEncodingException {
JSONObject cntObj = new JSONObject(new String(event.data.getByteArray(cnt_id), "utf-8"));
int imgWidth = cntObj.getInt("img_width");
int img_height = cntObj.getInt("img_height");
String img_id = content.getString("img_id");
byte[] rbgData = null;
try {
rbgData = event.data.getByteArray(img_id);
} catch (Throwable t) {
t.printStackTrace();
}
int border_width = 2;
if (face_w != 0 && face_h != 0) {
for (int i = 0; i < img_height; i++) {
int offset = i * imgWidth * 3;
if (i < face_y) {
continue;
}
if (i - face_y < border_width) {
for (int j = face_x; j < (face_x + face_w); j++) {
rbgData[offset + j * 3] = (byte) 0x98;
rbgData[offset + j * 3 + 1] = (byte) 0xFF;
rbgData[offset + j * 3 + 2] = (byte) 0x3E;
}
} else if (i - face_y - face_h > -border_width && i - face_y - face_h <= 0) {
for (int j = face_x; j < (face_x + face_w); j++) {
rbgData[offset + j * 3] = (byte) 0x98;
rbgData[offset + j * 3 + 1] = (byte) 0xFF;rbgData[offset + j * 3 + 2] = (byte) 0x3E;
}
} else if (i > face_y && i < (face_y + face_h)) {
for (int k = 0; k < border_width; k++) {
rbgData[offset + (face_x + k) * 3] = (byte) 0x98;
rbgData[offset + (face_x + k) * 3 + 1] = (byte) 0xFF;
rbgData[offset + (face_x + k) * 3 + 2] = (byte) 0x3E;
rbgData[offset + (face_x + face_w - k) * 3] = (byte) 0x98;
rbgData[offset + (face_x + face_w - k) * 3 + 1] = (byte) 0xFF;
rbgData[offset + (face_x + face_w - k) * 3 + 2] = (byte) 0x3E;
}
}
}
}
// 展示预览图
preview.updatePreviewFrame(rbgData, imgWidth, img_height);
preview.requestRender();
}