# 概览

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();
}