語音識別,語義理解一站式解決之智能照相機(人臉識別,olami)
轉(zhuǎn)載請注明CSDN博文地址:http://blog.csdn.net/ls0609/article/details/76546716
olami sdk實現(xiàn)了把錄音或者文字轉(zhuǎn)化為用戶可以理解的json字符串從而實現(xiàn)語義理解疗杉,用戶可以定義自己的
語義吨掌,通過這種方式可以實現(xiàn)用戶需要的語義理解。前面寫了兩篇語音識別劳淆,語義理解的博文,分別是語音
在線聽書和語音記帳軟件,本篇是語音智能照相機端仰。
1.智能照相機的功能
手機后攝像頭像素比較高,如果用后設(shè)想頭對準(zhǔn)自己自拍残家,那么看不到屏幕的情況下怎么知道
自己在不在鏡頭中呢榆俺?而本篇做的智能照相機就可以為您解決這個問題。
想要做的是這樣一個照相機app坞淮,可以語音切換攝像頭茴晋,人臉識別并語音播報識別的人臉是否在屏幕中央,
是偏向哪里回窘,當(dāng)人臉居中的時候诺擅,提示用戶可以拍照了,用戶說“拍照”啡直,“茄子”就會自動抓拍并保存圖
片在手機中烁涌,還可以說“切換攝像頭”來切換前后攝像頭。
抓了兩張應(yīng)用運行時的圖片:
2.eclipse中的lib目錄結(jié)構(gòu)如下
assets下面的事tts播報的資源文件
libs目錄下酒觅,
libtts.so tts播報所需的庫文件
libspeex.so語音識別所需的庫文件
libolamsc.so語音識別所需的庫文件
tts.jar tts播報所需的庫文件
voicesdk_android.jar語音識別所需的庫文件
3.AndroidManifest.xml
需要錄音撮执,網(wǎng)絡(luò),讀寫sd卡舷丹,拍照等權(quán)限抒钱。
4.layout布局
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
android:id="@+id/faceView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:id="@+id/btn_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="開始"/>
在surfaceview中自定義了一個FaceView,faceview用來顯示抓拍的人臉。
屏幕最下方有個button谋币,因為這個版本暫時不支持語音喚醒功能(后續(xù)添加后再更新)仗扬,添加一個button用于用戶想隨時說拍照的時候點擊觸發(fā)用。
5.MainActivity.java和FaceView.java
-1.MainActivity.Java
@Override
protectedvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_camera);
initHandler();//用于處理錄音狀態(tài)回調(diào)的消息
initView();//初始化界面
initViaVoiceRecognizerListener();//初始化olami語音回調(diào)監(jiān)聽
init();//初始化olami語音識別sdk
initTts();//初始化tts語音播報
DisplayMetrics dm =newDisplayMetrics();//定義DisplayMetrics對象
getWindowManager().getDefaultDisplay().getMetrics(dm);//取得窗口屬性
mScreenCenterx = dm.widthPixels/2;//窗口的寬度
mScreenCentery = dm.heightPixels/2;//窗口的高度
}
以下是olamisdk的初始化
publicvoidinit()
{
mOlamiVoiceRecognizer =newOlamiVoiceRecognizer(MainActivity.this);
TelephonyManager telephonyManager=
(TelephonyManager)this.getSystemService(
this.getBaseContext().TELEPHONY_SERVICE);
Stringimei=telephonyManager.getDeviceId();
mOlamiVoiceRecognizer.init(imei);//設(shè)置身份標(biāo)識蕾额,可以填null
//設(shè)置識別結(jié)果回調(diào)listener
mOlamiVoiceRecognizer.setListener(mOlamiVoiceRecognizerListener);
//設(shè)置支持的語音類型早芭,優(yōu)先選擇中文簡體
mOlamiVoiceRecognizer.setLocalization(
OlamiVoiceRecognizer.LANGUAGE_SIMPLIFIED_CHINESE);
mOlamiVoiceRecognizer.setAuthorization(
"51a4bb56ba954655a4fc834bfdc46af1",
"asr",
"68bff251789b426896e70e888f919a6d",
"nli");
//注冊Appkey,在olami官網(wǎng)注冊應(yīng)用后生成的appkey
//注冊api诅蝶,請直接填寫“asr”退个,標(biāo)識語音識別類型
//注冊secret,在olami官網(wǎng)注冊應(yīng)用后生成的secret
//注冊seq秤涩,請?zhí)顚憽皀li”
//錄音時尾音結(jié)束時間帜乞,建議填//2000ms
mOlamiVoiceRecognizer.setVADTailTimeout(2000);
//設(shè)置經(jīng)緯度信息,不愿上傳位置信息筐眷,可以填0
mOlamiVoiceRecognizer.setLatitudeAndLongitude(
31.155364678184498,121.34882432933009);
}
定義OlamiVoiceRecognizerListener黎烈,此處代碼就不貼了。
onError(int errCode)//出錯回調(diào)匀谣,可以對比官方文檔錯誤碼看是什么錯誤
onEndOfSpeech()//錄音結(jié)束
onBeginningOfSpeech()//錄音開始
onResult(String result, int type)//result是識別結(jié)果JSON字符串
onCancel()//取消識別照棋,不會再返回識別結(jié)果
onUpdateVolume(int volume)//錄音時的音量,1-12個級別大小音量
以下是handler消息處理武翎,包含語義解析
privatevoidinitHandler()
{
mHandler =newHandler(){
@Override
publicvoidhandleMessage(Message msg)
{
switch(msg.what){
caseMessageConst.CLIENT_ACTION_START_RECORED:
mBtnStart.setText("錄音中");
break;
caseMessageConst.CLIENT_ACTION_STOP_RECORED:
mBtnStart.setText("識別中");
break;
caseMessageConst.CLIENT_ACTION_CANCEL_RECORED:
mBtnStart.setText("開始");
break;
caseMessageConst.CLIENT_ACTION_ON_ERROR:
mBtnStart.setText("開始");
break;
caseMessageConst.CLIENT_ACTION_UPDATA_VOLUME:
//mTextViewVolume.setText("音量: "+msg.arg1);
break;
caseMessageConst.SERVER_ACTION_RETURN_RESULT:
mBtnStart.setText("開始");
try{
String message =(String) msg.obj;
String input =null;
JSONObject jsonObject =newJSONObject(message);
JSONArray jArrayNli =
jsonObject.optJSONObject("data").optJSONArray("nli");
JSONObject jObj =jArrayNli.optJSONObject(0);
JSONArrayjArraySemantic =null;
if(message.contains("semantic"))
{
jArraySemantic =jObj.getJSONArray("semantic");
String modifier =
jArraySemantic.optJSONObject(0).optJSONArray(
"modifier").optString(0);
if("take_photo".equals(modifier))
capture();
elseif("switch_camera".equals(modifier))
switchCamera();
}
else{
Log.i("ppp","result
error");
}
}
catch(Exception e)
{
e.printStackTrace();
}
break;
caseMessageConst.CLIENT_ACTION_UPDATA_FACEDECTION_DATA:
if(mIsRecording)
break;
RectF rect = (RectF) msg.obj;
mLeft = rect.left;
mRight = rect.right;
mTop = rect.top;
mBottom = rect.bottom;
floatcenterx = mLeft +(mRight - mLeft)/2;
floatcentery = mTop + (mBottom-mTop)/2;
String promptString ="";
if(centerx
Math.abs(mScreenCenterx-centerx) >100)
promptString ="位置偏左,";
elseif((centerx > mScreenCenterx)&&
(Math.abs(centerx -mScreenCenterx)>100))
promptString ="位置偏右,";
if((centery
Math.abs(mScreenCentery-centery) >200))
{
if("".equals(promptString))
promptString ="位置偏上";
else
promptString +="并且偏上";
}
elseif((centery > mScreenCentery)&&
(Math.abs(centery-mScreenCenterx)>200))
{
if("".equals(promptString))
promptString ="位置偏下";
else
promptString +="并且偏下";
}
if("".equals(promptString))
{
promptString ="位置已經(jīng)居中,可以拍照了";
mIsCenter =true;
}
else
{
mIsCenter =false;
}
ITtsListener ttsListener =newITtsListener()
{
@Override
publicvoidonPlayEnd() {
if(mIsCenter)
{
if(mOlamiVoiceRecognizer
!=null)
mOlamiVoiceRecognizer.start();
}
}
@Override
publicvoidonPlayFlagEnd(String arg0) {
}
@Override
publicvoidonTTSPower(longarg0) {
}
};
TtsPlayer.playText(MainActivity.this,
promptString, ttsListener,Tts.TTS_SYSTEM_PRIORITY);
break;
}
}
};
}
在MessageConst.SERVER_ACTION_RETURN_RESULT消息中烈炭,通過解析服務(wù)器返回的json字符串,可以找到modifier這個字段的值宝恶,如果是take_photo表示拍照符隙,如果是switch_camera表示切換攝像頭。
當(dāng)用戶說拍照或者茄子的時候垫毙,服務(wù)器返回如下json字符串:
[
{
"desc_obj": {
"status":0
},
"semantic": [
{
"app":"camera",
"input":"拍照",
"slots": [
],
"modifier": [
"take_photo"
],
"customer":"58df512384ae11f0bb7b487e"
}
],
"type":"camera"
}
]
這個拍照霹疫,茄子等語法都是自己定義的,詳細(xì)請看:
olami開放平臺語法編寫簡介:http://blog.csdn.net/ls0609/article/details/71624340
olami開放平臺語法官方介紹:https://cn.olami.ai/wiki/?mp=nli&content=nli2.html
2.人臉識別FaceView.java
publicclassFaceViewextendsView{
privateCamera.Face[] mFaces;
privatePaint mPaint;
privateMatrix matrix =newMatrix();
privateRectF mRectF =newRectF();
privateHandler mHandler;
privatelongmCurrentTime;
publicvoidsetFaces(Camera.Face[] faces) {
mFaces = faces;
invalidate();
}
publicFaceView(Context context) {
super(context);
init(context);
}
publicFaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
publicFaceView(Context context, AttributeSet attrs,intdefStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
publicvoidinit(Context context) {
mPaint =newPaint();
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(5f);
mPaint.setStyle(Paint.Style.STROKE);
}
publicvoidsetHandler(Handler handler)
{
mHandler = handler;
}
@Override
protectedvoidonDraw(Canvas canvas) {
super.onDraw(canvas);
if(mFaces ==null|| mFaces.length <0) {
return;
}
//準(zhǔn)備矩形框
MainActivity.prepareMatrix(matrix,false,270, getWidth(),getHeight());
canvas.save();
matrix.postRotate(0);
canvas.rotate(-0);
RectF tempRectF =newRectF();
longtempTime = System.currentTimeMillis();
for(inti =0; i < mFaces.length; i++) {
mRectF.set(mFaces[i].rect);//獲取face矩形框值
floattemp = mRectF.top;
mRectF.top = -mRectF.bottom;
mRectF.bottom = - temp;//上下交換
matrix.mapRect(mRectF);
canvas.drawRect(mRectF, mPaint);//繪制矩形框
tempRectF.set(mRectF);
if((mCurrentTime ==0)
||((tempTime-mCurrentTime)/1000) >=4)
{//超過4秒综芥,發(fā)送一次識別face矩形框值
mHandler.sendMessage(mHandler.obtainMessage(
MessageConst.CLIENT_ACTION_UPDATA_FACEDECTION_DATA, tempRectF));
mCurrentTime = tempTime;
}
Log.i("ppp","mRectF.left = "+mRectF.left+"mRectF.right = "+mRectF.right);
}
canvas.restore();
}
}
自定義FaceView中丽蝎,由于旋轉(zhuǎn)了270度,所以需要face矩形框上下值進(jìn)行交換膀藐,不然人臉識別總是左右或者上下不能追蹤屠阻。每隔4秒發(fā)送一次矩形框的值,在MainActivity.java的handler中收到這個消息并進(jìn)行是否居中的判斷额各。
caseMessageConst.CLIENT_ACTION_UPDATA_FACEDECTION_DATA:
if(mIsRecording)
break;
RectF rect = (RectF) msg.obj;
mLeft = rect.left;
mRight = rect.right;
mTop = rect.top;
mBottom = rect.bottom;//保存上下左右的矩形框值
floatcenterx = mLeft +(mRight - mLeft)/2;//獲取矩形框橫向中心點位置
floatcentery = mTop
+ (mBottom-mTop)/2;//獲取矩形框縱向中心點位置
String promptString ="";
if(centerx
>100)
promptString ="位置偏左,";
elseif((centerx> mScreenCenterx)&&
(Math.abs(centerx-mScreenCenterx)>100))
promptString ="位置偏右,";
if((centery < mScreenCentery)&&(
Math.abs(mScreenCentery-centery) >200))
{
if("".equals(promptString))
promptString ="位置偏上";
else
promptString +="并且偏上";
}
elseif((centery> mScreenCentery)&&
(Math.abs(centery -mScreenCenterx)>200))
{
if("".equals(promptString))
promptString ="位置偏下";
else
promptString +="并且偏下";
}
if("".equals(promptString))
{
promptString ="位置已經(jīng)居中,可以拍照了";
mIsCenter =true;
}
else
{
mIsCenter =false;
}
ITtsListener ttsListener =newITtsListener()
{
@Override
publicvoidonPlayEnd() {
if(mIsCenter)
{
if(mOlamiVoiceRecognizer !=null)
mOlamiVoiceRecognizer.start();
}
}
@Override
publicvoidonPlayFlagEnd(String arg0) {
}
@Override
publicvoidonTTSPower(longarg0) {
}
};
TtsPlayer.playText(MainActivity.this,
promptString,ttsListener,Tts.TTS_SYSTEM_PRIORITY);
break;
可以獲得屏幕的中心點和人臉識別的矩形框的中心點国觉,對比橫向和縱向的中心點大小和絕對值差,當(dāng)橫向的值差100像素以上就認(rèn)為橫向不居中虾啦,并且根據(jù)大小分居左和居右蛉加,縱向大小差值在200像素以上認(rèn)為縱向不居中蚜枢,并且根據(jù)大小分偏上和偏下缸逃,這個100针饥,200像素值用戶可以自己調(diào)節(jié)到合適的值。
調(diào)用TtsPlayer.playText提示需频,當(dāng)播報結(jié)束后回調(diào)到onPlayEnd()丁眼,如果居中那么已經(jīng)提示用戶可以拍照了,此時啟動錄音程序昭殉,用戶不用點擊button也不用喚醒苞七,只許說拍照或者茄子就可以拍照了。
6.源碼下載鏈接
https://pan.baidu.com/s/1qXITWs8
語音在線聽書:http://blog.csdn.net/ls0609/article/details/71519203
語音記賬demo:http://blog.csdn.net/ls0609/article/details/72765789
olami開放平臺語法編寫簡介:http://blog.csdn.net/ls0609/article/details/71624340
olami開放平臺語法官方介紹:https://cn.olami.ai/wiki/?mp=nli&content=nli2.html