AI連携FW『シンプルエーアイ』を使ってみた(5)：「魔法の鏡」まとめ＋α（※目次は随時更新）

#はじめに

AI連携フレームワーク『シンプルエーアイ』を使って遊ぶ、第5回目のまとめ編＋αです。

今回の記事は、目次がわりのページを作るのが一番の目的ではあるのですが…
せっかく新たに記事を公開するのですし、新しいお知らせや、今までご説明できなかった機能のご紹介をしたいと思います。


#目次（随時更新）

・[(1)準備編](https://qiita.com/denaripam/items/0c128c50788d9553f88e)
　『シンプルエーアイ』を使用するための手順をまとめました。
　フレームワークのダウンロードと、Microsoft の API キーの取得について記載しています。

・[(2)：AIっぽい事をさせてみたい(その1)](https://qiita.com/denaripam/items/1aede5526136729ea0c5)
　フレームワークを利用したサンプルコード掲載、1回目です。
　『シンプルエーアイ』によるカメラの制御と、Face API を利用した人物判断について記載しています。

・[(2)：AIっぽい事をさせてみたい(その2)](https://qiita.com/denaripam/items/c53c01ea8856ab18f0f0)
　フレームワークを利用したサンプルコード掲載、２回目です。
　『シンプルエーアイ』によるマイクの制御と、Bing Speech API を利用してAIにお喋りさせるやり方について記載しています。

・[(3)：「魔法の鏡」を作るぞ](https://qiita.com/denaripam/items/80e0e8b3e68585c2e921)
　前回までのコードを元にした、『シンプルエーアイ』を利用するサンプルアプリ作成の１回目です。
　作成するアプリの概要説明と、基本機能を搭載したサンプルコードを記載しています。

・[(4)：より「魔法の鏡」らしく](https://qiita.com/denaripam/items/2367a872c9d7eaff4a1f)
　サンプルアプリ作成の２回目です。
　『シンプルエーアイ』を利用してAIのお喋りを変化させる、またYoutubeプレイヤーを起動する方法について記載しています。

・[(5)：「魔法の鏡」まとめ＋α(本記事)](https://qiita.com/denaripam/items/2367a872c9d7eaff4a1f)
　目次とまとめを兼ねた本記事では、Face API を利用した感情分析と、Computer Vision API を利用した情報追加表示について記載しています。

**公式サイト(Japanese)はこちら：[https://smartaifw.com/simpleai-trial/](https://smartaifw.com/simpleai-trial/)**
**公式サイト（Engrish）はこちら：[https://smartaifw.com/en/simpleai-trial](https://smartaifw.com/en/simpleai-trial)**


#短期連載はしんどかったです

『シンプルエーアイ』の使いやすさを実感するために始めた、この連続記事。
お陰様で、沢山の方に見ていただけているようで、非常に嬉しく思っております。
正直なところ、1日～2日に1本、サンプルコード作成と記事作成をセットで行い公開レベルまで持っていくというのは、自分にとっては相当な作業量でして…。
専業のライターさんでもないのに何故こんな頻度で…と、日々半泣きになりながら仕上げていたので、アクセスがある事は記事作成の大きな励みとなりました。
改めて感謝申し上げます。


#公式サイトの英語版が公開されました

目次の最後にもURLを記載していますが、『シンプルエーアイ』の公式サイトの英語版が公開されました。
英語版のAIは、日本語版とはまた違った声なので、かなり新鮮です。よろしければ、一度お試しください。

**公式サイト（英語版）：[https://smartaifw.com/en/simpleai-trial](https://smartaifw.com/en/simpleai-trial)**

###余談：改めて言うけど、Google翻訳さんが凄い

この翻訳も中の人が行ったのですが、現在はGoogle翻訳さんが非常に優秀でして、大変助かりました。
昔の翻訳ソフトの「お堅い」翻訳のイメージしかなかったのですが、とんでもない。
技術用語もばっちり認識してくれますし、ちょっと砕けた言い回しでも、句読点や主語述語がきちんとしていれば、かなり自然な感じに翻訳してくれます。
しかも、HTMLタグ付きの文章をそのまま放り込んでも、ちゃんとタグを認識し、その上で文章を組み立ててくれるのです。凄い。

その他には、[DMM英会話なんてuKnow? - これって英語で何ていうの？を解決するQ&Aサービス](http://eikaiwa.dmm.com/uknow/)というサイトにも、大変お世話になりました。
かなり砕けた言い回しや日本語独特の言葉も、割合よく使うような表現であれば結構回答があります。皆さん、同じような事で悩むものなんですね。
翻訳を生業とされている方からの回答も沢山あり、生きた英語の言い回しがわかるので、お勧めです。

###余談：英語版と日本語版では、AIの音声認識が結構違う

今回の英語版サイトを作成するにあたり、AI側も勿論英語版を指定し、動作チェックを行いました。
そこで気づいたのですが、AIの反応がだいぶ違うように感じます。
中の人はネイティブ発声なんて出来ないローカル人間ですので、テストする際には、スマートフォンに入っている翻訳ソフトに英語版の音声キーを入力して喋らせる…という方法を取りました。
すると、日本語対応した無音期間等の設定では、うまく音声認識してくれませんでした。日本語版よりも、かなり短めにする必要があるようです。
また、このテスト方法だと話す速さを変えづらいので長さが足りず、結構な頻度で「音声が短すぎる」と判定されてしまいました。ある程度の長さの文章でないと、うまくいきません。
日本語でしたら「人物判断実行」とゆっくりめに言えばよいところを、翻訳アプリに喋らせる際には「"Simple AI",how are you?Please start face image judgment.」くらいは入力しなければなりませんでした。

#『シンプルエーアイ』で出来ること、まだまだあります：やりたい事

いったん一区切りとしました連載記事ですが、『シンプルエーアイ』は、まだまだ色々な事ができます。
そこで今回の記事では、今までの記事で作成した「魔法の鏡」アプリに、以下のような機能を追加したいと思います。

* メイクチェックした時に笑顔の度合いを読み取り、鏡が声をかける
* メイクチェックした時の情報表示に、室内・室外の情報を追加する


#メイクチェックした時に笑顔の度合いを読み取り、鏡が声をかける

それでは、本記事で想定しているサンプルプログラムの動作を説明します。
現在の「魔法の鏡」は、鏡に「メイクチェック」等と呼びかけると、メイク情報を画面左上に表示します。
今回は、この「メイクチェック」の際に、笑顔の度合いによって鏡が言葉を返す機能を追加したいと思います。

笑顔の度合いを読み取る為には、Face API が返す情報のうち、FaceAttributes の「emotion」を取得する必要があります。
サンプルコードでは、APIキーの設定部分のところで、何を取得するかをパラメータとして指定しているので、ここに「emotion」を追加しましょう。
追加部分のソースは以下の通りです。

```js
MicrosoftAzureFaceAPIparams:{
	"returnFaceAttributes": "gender,age,glasses,facialHair,makeup,emotion",	//取得したい情報をここで指定する	//2018.06
```


FaceAttributes の「emotion」には、"anger"、"neutral"、"happiness"など、幾つかのパラメータがあり、これらの値の組み合わせで様々な判断を行うことができます。
怒っている時は"anger"の値が高くなり、笑顔の場合は"happiness"の値が高くなる…という具合です。
このサンプルコードを使って「メイクチェック」する時は、通常は真顔だったので、"neutral"の値が概ね0.9以上（最大値は1）でした。

今回は、身支度の相棒にしてもらうという使い方を想定した「魔法の鏡」なので、笑顔の度合いだけを判断するようにします。
満面の笑顔で写っている場合は「とっても素敵な笑顔ですね」、微笑んでいる場合は「気持ちの良い笑顔ですね」、その他の場合は「今日も笑顔を忘れずに」と、AIがお喋りしてくれるようにしましょう。

この判断は、"happiness"の数値で分岐するようにしました。
といっても、正直なところ「微笑み具合」の判断が難しいので、数値は少々曖昧な指定になっています。

これら一連の処理は、メイク情報の編集処理の後に行うようにしました。

追加部分のソースは以下の通りです。

```js
//感情部分の判定を行い、AIが声をかけるようにする
var aimsg = "今日も笑顔を忘れずに";
var emotionScore = 0;
if(responsedata[0].faceAttributes.emotion){
	if(responsedata[0].faceAttributes.emotion.happiness){
		emotionScore = responsedata[0].faceAttributes.emotion.happiness;
		//笑顔の数値によってメッセージを変更する
		switch(true){
		case emotionScore >= 0.9:
			aimsg = "とっても素敵な笑顔ですね";
		 	break;
		case 0.5 <= emotionScore && emotionScore < 0.9:
			aimsg = "気持ちの良い笑顔ですね";
		 	break;
		default:
			aimsg = "今日も笑顔を忘れずに";
		 	break;
		}
	}
}
```


#メイクチェックした時の情報表示に、室内／室外の情報を追加する

連載記事の第一回目、APIキーの準備を行った際に、三種類のキーを取得しました。
しかし2～4回目の記事では、結局二種類のAPIキーしか利用しておらず、ずっと気になっていました。
そこで今回は、三種類目のキーである Computer Vision API を利用し、室内／室外の情報を追加で表示するようにしたいと思います。

それでは、本記事で想定しているサンプルプログラムの動作を説明します。
鏡に向かって「メイクチェック」等と呼びかけると、メイク情報を画面左上に表示しますが、この情報に「場所」を追加したいと思います。
下記のスクリーンショットは、今回の機能追加を行ったバージョンでの動作結果です。

![5-1.png](https://qiita-image-store.s3.amazonaws.com/0/255412/3c76088d-de49-20f7-1bed-4a484618384a.png)

 

Computer Vision API は、画像の情報を分析し、画像内に含まれた情報のキーワード群と、画像に含まれた情報を元にAIが独自編集した表題を返してくれるAPIです。
このキーワード群の中から「室内／室外」の情報を取得することにします。
（とはいえ、お化粧は通常室内で行われるので、殆ど「室内」と表示されてしまうでしょうが…
　「魔法の鏡」らしい使い方を他に思いつかなかったので、どうぞご容赦下さい。）

APIが取得した画像の情報はJSON形式で Processingtext に格納されていますので、この中から"indoor"というキーワードを探し出します。
大変大まかですが、今回はこのキーワードが含まれていない場合は室外と判断してしまいましょう。

追加部分のソースは以下の通りです。

```js
//画像分析を行い、室外/室内を追加で表示する
sender.Imageanalysis(
	{
		done:function(data) {
			var output2=document.getElementById("denaripamSimpleAI_responseTextArea");
			if(output2) {
				var responsedata2 = JSON.parse(output2.value);
				//判断結果を画面表示用に編集する
				if(responsedata2.Processingtext) {
					var regstr = new RegExp("indoor",'i');;
					if(regstr.test(responsedata2.Processingtext)){
						msg = msg + "場所：室内" + '<br>';
					} else {
						msg = msg + "場所：室外" + '<br>';
					}
					document.getElementById("dispMsg").innerHTML = msg;
				}
			}
		},
	},
);

```

これで、画面左上に「場所」の情報も表示されるようになります。


#サンプルコード

最後に、今回作成したサンプルプログラムの全文を記載しておきます。

<details><summary>サンプルコード</summary><div>

```js
 <!DOCTYPE html>
<html>
<head>
	<title>魔法の鏡</title>
	<script src="https://code.jquery.com/jquery-3.0.0.js"></script>
	<script src="simpleai-0.1.0.min.enc.js"></script>
	<script type="text/javascript" src="https://www.youtube.com/iframe_api"></script>
<script>
//------------------------------------------------------------------------------------------------------------------------
//起動時処理
window.onload = function () {
	var SimpleAI = new denaripamSimpleAI(
		this,
		{},
		{
			initCamera:true,
			initMicrophone:true,
			VoiceRecognition:{listen:true},

//-----uriBase、APIキーを設定してください START-----
			MicrosoftAzureFaceAPI:true,
			MicrosoftAzureFaceAPIuriBase:"",					//FaceAPIのエンドポイントを入力
			MicrosoftAzureFaceAPIsubscriptionKey:"",									//FaceAPIのキー②を入力
			MicrosoftAzureFaceAPIparams:{
				"returnFaceAttributes": "gender,age,glasses,facialHair,makeup,emotion",									//取得したい情報をここで指定する	//2018.06
//				"returnFaceAttributes": "gender,age,glasses,facialHair,makeup",											//取得したい情報をここで指定する
			},

			MicrosoftBingSpeechAPIREST:true,
			MicrosoftBingSpeechAPIRESTuriBase:"https://speech.platform.bing.com/speech/recognition/<RECOGNITION_MODE>/cognitiveservices/v1?language=<LANGUAGE_TAG>&format=<OUTPUT_FORMAT>",		//※※※※変更不要※※※※
			MicrosoftBingSpeechAPIsubscriptionKey:"",									//BingSpeechAPIのキー②を入力

			MicrosoftAzureComputerVisionAPI:true,
			MicrosoftAzureComputerVisionAPIuriBase:"",		//ComputerVisionAPIのエンドポイントを入力

			MicrosoftAzureComputerVisionAPIsubscriptionKey:"",							//ComputerVisionAPIのキー①を入力

//-----uriBase、APIキーを設定してください END-----
		},
	);
//-----------------------------------------------
	//情報表示用
	var msg="";
	//現在日時のタイマー起動
	setInterval('showClock()',1000);

	if(SimpleAI) {
		//スマートエージェント開始
		SimpleAI.SmartAgent();

		//AIの応答の言葉を変更する
		SimpleAI.param.SmartAgent.Prompt = "かしこまりました、";
		//AIの語尾の言葉を変更する
		SimpleAI.param.SmartAgent.PromptEnd = "、ご主人様";

		//人物判断（スマートエージェントコマンド追加サンプル）
		var cmd;
		cmd=SimpleAI.getSmartAgentCommand();
		cmd.MainCommand.push(
			{
				//人物判断の音声キーを追加する
				label: "PersonalJudgment",
				labelname: "人物判断",				//ラベル名称、現在のモード読み上げ等に使用
				word:[/メイク|make|化粧|けしょう|仕上げ/,/check|確認|判断|判定|見て/],
				message:"しばらくお待ちください",
//				word:[/人物/,/判断/],
//				message:"人物判断を行います",
				messageafter:function(sender,event,param) {
					SimpleAI.Personinformation(		//人物判断処理
						{
							done:function(data) {
								msg = "";
								var output=document.getElementById("denaripamSimpleAI_responseTextArea");
								if(output) {
									var responsedata = JSON.parse(output.value);
									//判断結果を画面表示用に編集する
									if(responsedata[0]) {
										if(responsedata[0].faceAttributes.gender){
											if(responsedata[0].faceAttributes.gender=='female'){
												msg = msg + "性別：女性" + '<br>';
											} else {
												msg = msg + "性別：男性" + '<br>';
											}
										}
										if(responsedata[0].faceAttributes.age){
											msg = msg + "年齢：" + responsedata[0].faceAttributes.age + '<br>';
										}
										if(responsedata[0].faceAttributes.glasses){
											if(responsedata[0].faceAttributes.glasses=='NoGlasses'){
												msg = msg + "眼鏡：なし" + '<br>';
											} else {
												msg = msg + "眼鏡：あり" + '<br>';
											}
										}
										if(responsedata[0].faceAttributes.makeup.eyeMakeup){
											msg = msg + "アイメイク：あり" + '<br>';
										} else {
											msg = msg + "アイメイク：なし" + '<br>';
										}
										if(responsedata[0].faceAttributes.makeup.lipMakeup){
											msg = msg + "リップメイク：あり" + '<br>';
										} else {
											msg = msg + "リップメイク：なし" + '<br>';
										}
//2018.06 START====================>
										//感情部分の判定を行い、AIが声をかけるようにする
										var aimsg = "今日も笑顔を忘れずに";
										var emotionScore = 0;
										if(responsedata[0].faceAttributes.emotion){
											if(responsedata[0].faceAttributes.emotion.happiness){
												emotionScore = responsedata[0].faceAttributes.emotion.happiness;
												//笑顔の数値によってメッセージを変更する
												switch(true){
												case emotionScore >= 0.9:
													aimsg = "とっても素敵な笑顔ですね";
												 	break;
												case 0.5 <= emotionScore && emotionScore < 0.9:
													aimsg = "気持ちの良い笑顔ですね";
												 	break;
												default:
													aimsg = "今日も笑顔を忘れずに";
												 	break;
												}
											}
										}
										//画像分析を行い、室外/室内を追加で表示する
										sender.Imageanalysis(
											{
												done:function(data) {
													var output2=document.getElementById("denaripamSimpleAI_responseTextArea");
													if(output2) {
														var responsedata2 = JSON.parse(output2.value);
														//判断結果を画面表示用に編集する
														if(responsedata2.Processingtext) {
															var regstr = new RegExp("indoor",'i');;
															if(regstr.test(responsedata2.Processingtext)){
																msg = msg + "場所：室内" + '<br>';
															} else {
																msg = msg + "場所：室外" + '<br>';
															}
															document.getElementById("dispMsg").innerHTML = msg;
														}
													}
												},
											},
										);
										if(aimsg) SimpleAI.SpeechSynthesis(aimsg);
//2018.06 END====================>
									} else {
										msg = "認識できませんでした。明るい所で鏡に映るようにして下さい。";
									}
								} else {
									msg = "認識できませんでした。明るい所で鏡に映るようにして下さい。";
								}
								document.getElementById("dispMsg").innerHTML = msg;
							},
							fail:function(errstring) {
								msg = "APIサービスが無効です。";
								document.getElementById("dispMsg").innerHTML = msg;
								//alert("NG!!" + errstring);
							},
						},
					);
				},
			},
			//音楽をかける音声キーを追加する
			{
				label: "PlayMusics",
				labelname: "音楽をかける",				//ラベル名称、現在のモード読み上げ等に使用
				word:[/音楽|mugic|曲/,/かける|かけて|流す|流して|きく|きかせて｜ききたい/],
				message:"こちらの曲をどうぞ",
				messageafter:function(sender,event,param) {
					document.getElementById("dispYTPlayer").innerHTML = html_temp_modal;
					YouTubeplayer_start("やる気の出る クラシック音楽 ピアノBGM");
				},
			},
			//音楽を止める音声キーを追加する
			{
				label: "StopMusics",
				labelname: "音楽を止める",				//ラベル名称、現在のモード読み上げ等に使用
				word:[/音楽|mugic|曲/,/止める|止めて|やめる|やめて|終わり|終了/],
				messageafter:function(sender,event,param) {
					YouTubeplayer_stop();
				},
			},
			//朝のニュースを呼び出す音声キーを追加する
			{
				label: "ShowNews",
				labelname: "ニュース表示",				//ラベル名称、現在のモード読み上げ等に使用
				word:[/ニュース/,/見る|見せて|見たい|流す|流して/],
				message:"どうぞ",
				messageafter:function(sender,event,param) {
					document.getElementById("dispYTPlayer").innerHTML = html_temp_modal;
					var date = new Date();
					YouTubeplayer_start("nhk 朝のニュース");
					//前ゼロなし日付だと予想と異なるものが表示される。前ゼロ付きスラッシュ日付だと、うまく再生できなくなる。
					//YouTubeplayer_start("nhk ニュース " + formatYMD(date));
				},
			},
			//朝のニュースを止める音声キーを追加する
			{
				label: "StopNews",
				labelname: "ニュース停止",				//ラベル名称、現在のモード読み上げ等に使用
				word:[/ニュース/,/止める|止めて|やめる|やめて|終わり|終了/],
				messageafter:function(sender,event,param) {
					YouTubeplayer_stop();
				},
			},
			//天気予報を呼び出す音声キーを追加する
			{
				label: "ShowWeatherForecast",
				labelname: "天気予報表示",				//ラベル名称、現在のモード読み上げ等に使用
				word:[/天気/,/教えて|見る|見せて|見たい/],
				message:"どうぞ",
				messageafter:function(sender,event,param) {
					document.getElementById("dispYTPlayer").innerHTML = html_temp_modal;
					var date = new Date();
					YouTubeplayer_start("全国の天気予報 朝");
				},
			},
			//天気予報を止める音声キーを追加する
			{
				label: "StopWeatherForecast",
				labelname: "天気予報停止",				//ラベル名称、現在のモード読み上げ等に使用
				word:[/天気/,/止める|止めて|やめる|やめて|終わり|終了/],
				messageafter:function(sender,event,param) {
					YouTubeplayer_stop();
				},
			},
		);
	}
//--------------------------------------------
};
//--------------------------------------------
//現在日時表示
function showClock(){
	var now = new Date();
	document.getElementById("dispClock").innerHTML = now.toLocaleString();
}
//--------------------------------------------
//youtube
var YouTubeplayer;
var YouTubeIframeAPI=false;
 function onYouTubeIframeAPIReady() {
	YouTubeIframeAPI=true;
}
//youtube表示ウィンドウ
var html_temp_modal = 
	 '<!-- YoutubePlayer -->'
	+'<div class="embed-responsive embed-responsive-16by9">'
	+'	<div class="embed-responsive-item" id="player"></div>'
	+'</div>';
//youtube起動
var YouTubeplayer_start=function(keyword,volume) {
	if(YouTubeIframeAPI) {
		YouTubeplayer_stop();
		YouTubeplayer = new YT.Player('player', {
			height: '150',
			width: '200',
			controls: 2,		//コントロールの表示
			iv_load_policy: 3,	//アノテーション非表示
			showinfo: 0,		//インフォメーション非表示
			events: {
				onReady: function(event){
					if(keyword) YouTubeplayer.loadPlaylist({listType:"search",list:keyword,});
					if(volume)  YouTubeplayer.setVolume(100);
				},
			},
		});
	}
};
//youtube停止
var YouTubeplayer_stop=function() {
	if(YouTubeplayer)YouTubeplayer.destroy();
};
//--------------------------------------------
//前ゼロ付き日付変換
function formatYMD(date){
	if (date){
		var yyyy = date.getFullYear();
		var mm = ("0"+ (date.getMonth()+1) ).slice(-2);	// 前ゼロ付き2桁
		var dd = ("0"+date.getDate()).slice(-2);		// 前ゼロ付き2桁
		return yyyy + '年' + mm + '月' + dd + "日";
	}
};
//------------------------------------------------------------------------------------------------------------------------
</script>
<style>/** CSS：Area */
/* 鏡フレーム */
.img-over-top {
	position: absolute;
	z-index: 1;
}
/* 鏡本体 */
.video-wrap {
  position: relative;

}
/* 時計表示部 */
.clock-over-top {
	background: #cbb994;
	color: #eaf4fc;
	font-family: serif;
	font-size: 100%;
	position: absolute;
	left: 2%;
	top: 2%;
	z-index: 1;
	padding-left: 5px; padding-right: 5px;
}
/* 情報表示部 */
.msg-over-top {
	background: #cbb994;
	color: #eaf4fc;
	font-family: serif;
	font-size: 80%;
	position: absolute;
	left: 2%;
	top: 7%;
	z-index: 1;
	padding-left: 5px; padding-right: 5px;
}
/* YoutubePlayer表示部 */
.youtube-over-top {
	opacity: 0.5;	/* 対象の透過度(0:完全透明 1:不透明) */
	position: absolute;
	left: 580px;
	top: 2%;
	z-index: 1;
	padding-left: 5px; padding-right: 5px;
}
</style>
</head>
<body>
<img class="img-over-top" src="fl_001.png" width="800px" height="600px">
<p class="clock-over-top" id="dispClock"><script>showClock();</script></p>
<p class="msg-over-top" id="dispMsg"></p>
<p class="youtube-over-top" id="dispYTPlayer"></p>
<div class="video-wrap">
  <video id="denaripamControlSensor_video" width="800px" height="600px" controls autoplay>
  </video>
</div>
<div hidden id="denaripamSimpleAI_responseRect"></div><br>
<textarea hidden id="denaripamSimpleAI_responseTextArea" class="UIInput" style="width:580px; height:400px;"></textarea><br>
</body>
</html>
```

</div></details>

#最後に

今回はここまでです。お疲れ様でした。

今後も、新しい機能のご紹介等を不定期で行っていくやもしれません。
その際は、この記事の「目次」部分にリンクを随時追加していきますので、ご興味お持ち下さった方はチェックをお願いいたします。
ご覧下さり、ありがとうございました。