一个带人脸识别的智能照相机应用

语音识别,语义理解一站式解决之智能照相机(人脸识别,olami)

转载请注明CSDN博文地址:http://blog.csdn.net/ls0609/article/details/76546716

olami sdk实现了把录音或者文字转化为用户可以理解的json字符串从而实现语义理解,用户可以定义自己的

语义,通过这种方式可以实现用户需要的语义理解。前面写了两篇语音识别,语义理解的博文,分别是语音

在线听书和语音记帐软件,本篇是语音智能照相机。

1.智能照相机的功能

手机后摄像头像素比较高,如果用后设想头对准自己自拍,那么看不到屏幕的情况下怎么知道

自己在不在镜头中呢?而本篇做的智能照相机就可以为您解决这个问题。

想要做的是这样一个照相机app,可以语音切换摄像头,人脸识别并语音播报识别的人脸是否在屏幕中央,

是偏向哪里,当人脸居中的时候,提示用户可以拍照了,用户说“拍照”,“茄子”就会自动抓拍并保存图

片在手机中,还可以说“切换摄像头”来切换前后摄像头。

抓了两张应用运行时的图片:




2.eclipse中的lib目录结构如下


assets下面的事tts播报的资源文件

libs目录下,

libtts.so tts播报所需的库文件

libspeex.so语音识别所需的库文件

libolamsc.so语音识别所需的库文件

tts.jar tts播报所需的库文件

voicesdk_android.jar语音识别所需的库文件

3.AndroidManifest.xml


需要录音,网络,读写sd卡,拍照等权限。

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,因为这个版本暂时不支持语音唤醒功能(后续添加后再更新),添加一个button用于用户想随时说拍照的时候点击触发用。

5.MainActivity.java和FaceView.java

-1.MainActivity.Java

@Override

protectedvoidonCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.layout_camera);

initHandler();//用于处理录音状态回调的消息

initView();//初始化界面

initViaVoiceRecognizerListener();//初始化olami语音回调监听

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);//设置身份标识,可以填null

//设置识别结果回调listener

mOlamiVoiceRecognizer.setListener(mOlamiVoiceRecognizerListener);

//设置支持的语音类型,优先选择中文简体

mOlamiVoiceRecognizer.setLocalization(

OlamiVoiceRecognizer.LANGUAGE_SIMPLIFIED_CHINESE);

mOlamiVoiceRecognizer.setAuthorization(

"51a4bb56ba954655a4fc834bfdc46af1",

"asr",

"68bff251789b426896e70e888f919a6d",

"nli");

//注册Appkey,在olami官网注册应用后生成的appkey

//注册api,请直接填写“asr”,标识语音识别类型

//注册secret,在olami官网注册应用后生成的secret

//注册seq,请填写“nli”

//录音时尾音结束时间,建议填//2000ms

mOlamiVoiceRecognizer.setVADTailTimeout(2000);

//设置经纬度信息,不愿上传位置信息,可以填0

mOlamiVoiceRecognizer.setLatitudeAndLongitude(

31.155364678184498,121.34882432933009);

}

定义OlamiVoiceRecognizerListener,此处代码就不贴了。

onError(int errCode)//出错回调,可以对比官方文档错误码看是什么错误

onEndOfSpeech()//录音结束

onBeginningOfSpeech()//录音开始

onResult(String result, int type)//result是识别结果JSON字符串

onCancel()//取消识别,不会再返回识别结果

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 ="位置已经居中,可以拍照了";

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消息中,通过解析服务器返回的json字符串,可以找到modifier这个字段的值,如果是take_photo表示拍照,如果是switch_camera表示切换摄像头。

当用户说拍照或者茄子的时候,服务器返回如下json字符串:

[

{

"desc_obj": {

"status":0

},

"semantic": [

{

"app":"camera",

"input":"拍照",

"slots": [

],

"modifier": [

"take_photo"

],

"customer":"58df512384ae11f0bb7b487e"

}

],

"type":"camera"

}

]

这个拍照,茄子等语法都是自己定义的,详细请看:

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;

}

//准备矩形框

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秒,发送一次识别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中,由于旋转了270度,所以需要face矩形框上下值进行交换,不然人脸识别总是左右或者上下不能追踪。每隔4秒发送一次矩形框的值,在MainActivity.java的handler中收到这个消息并进行是否居中的判断。

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 ="位置已经居中,可以拍照了";

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;

可以获得屏幕的中心点和人脸识别的矩形框的中心点,对比横向和纵向的中心点大小和绝对值差,当横向的值差100像素以上就认为横向不居中,并且根据大小分居左和居右,纵向大小差值在200像素以上认为纵向不居中,并且根据大小分偏上和偏下,这个100,200像素值用户可以自己调节到合适的值。

调用TtsPlayer.playText提示,当播报结束后回调到onPlayEnd(),如果居中那么已经提示用户可以拍照了,此时启动录音程序,用户不用点击button也不用唤醒,只许说拍照或者茄子就可以拍照了。

6.源码下载链接

https://pan.baidu.com/s/1qXITWs8

7.相关链接

语音在线听书: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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容