【LINE Bot】 bluemixとFaceAPIを使った年齢性別推定Bot

LINE BOT APIが公開されたので、興味本位で作ってみました。Webプログラミングは趣味レベルですが、
とりあえず形になったので備忘録として残しておきます。

#とりあえず作ったもの
写真を送ると、顔のアップと、年齢・性別の推定結果を返してくれます。
![IMG_2113.PNG](https://qiita-image-store.s3.amazonaws.com/0/22648/1ddac320-ea42-9b4e-c961-693ec6c63b83.png)
1枚の画像から複数人同時に認識もでき（最大人数は不明）、複数人認識した場合は連続で推定結果を返してくれます。

登録用のQRコードは下記になります。
![uao3236v.png](https://qiita-image-store.s3.amazonaws.com/0/22648/f3e4957a-6993-ab84-a2b6-b3beb2e2eabc.png)

__実運用は想定していませんので下記記事で言う「多くの人がやっちゃう間違い」アーキテクチャです。__
[大量メッセージが来ても安心なLINE BOTサーバのアーキテクチャ - Qiita](http://qiita.com/yoichiro@github/items/6d4c7309210af20a5c8f)
__興味本位で作ったBOTなので、trialが外れたら本BOTのサービスを停止する可能性が高いです。__

#構成
|分類|環境|
| :----: | :--- |
| PaaS| IBM bluemix|
| 顔認識 | Microsoft Face API |
| 言語 | PHP |

タダが大前提だったんですが、herokuではなくbluemixを選んだのは、Qiitaのまとめの記事（後述）を読んだからです。顔認識もIBMにはWatsonさんがいるのですが、精度があまり良くないとの記事を読んだので、MSのFace APIにしました。PHPは適当にチョイスしました。
なお、Face APIは、無料版だと3万回/月、20回/分の呼び出しが可能とのことです。
>30,000 transactions per month, 20 per minute.

#参考
リファレンスには目を通しておいたほうがいいです。

* [API reference for BOT API - LINE Developers](https://developers.line.me/bot-api/api-reference#sending_multiple_messages)
* [Face API - V1.0 - Microsoft Cognitive Services](https://dev.projectoxford.ai/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395236)

実際の作業については、既に親切な方々がまとめてくれています。

* [LINE BOT API Trialでできる全ての事を試してみた - Qiita](http://qiita.com/betchi/items/8e5417dbf20a62f2239d)
* [LINE BOT をとりあえずタダで Heroku で動かす - Qiita](http://qiita.com/yuya_takeyama/items/0660a59d13e2cd0b2516)
* [Herokuでbotを運用する時代は終わった。これからはIBM Bluemixを使って無料で運用する - Qiita](http://qiita.com/akameco/items/39a55635ceb0ba185b7f)

Line BOTでオウム返しができるようにするには、上記記事などが大変参考になりますので、
以降にはBluemixとFaceAPIに要点を絞って記述します。

#Bluemix
BluemixではCFアプリケーションを選択しました。
gitリポジトリも用意されているのですが、何故かcloneできなかったため[Cloud Foundry](https://www.ibm.com/developerworks/jp/cloud/library/cl-bluemixfoundry/)を使用しました。
![blue02.png](https://qiita-image-store.s3.amazonaws.com/0/22648/47809984-6762-be04-b5a7-e926c8ae7e68.png)

ローカルでコーディングを行って、cfコマンドでpushします。
Bluemixでは、デプロイのたびにipが変わるので、Server IP Whitelistへの登録は毎回行いました（コンテナだと固定IP使えるそうです）。
![linebo01.png](https://qiita-image-store.s3.amazonaws.com/0/22648/029f93b0-ea7e-4c65-92aa-43192a52de50.png)

#Face API
Face APIでは、画像のURLの他にもバイナリデータを送信することができます。

![oct1.png](https://qiita-image-store.s3.amazonaws.com/0/22648/d2997c46-eb39-5fe5-915b-3200e101ce0e.png)

## LINE BOT APIから画像データを取得する
\<messageId\>を取得したのち、再度問い合せを行うことで画像データを取得することができます。
[Getting previews of message content](https://developers.line.me/bot-api/api-reference#getting_message_content_preview)


```php
// 画像データを取得する
function api_get_message_content_request($message_id) {
	$url = "https://trialbot-api.line.me/v1/bot/message/{$message_id}/content";
	$headers = array(
		"X-Line-ChannelID: {$GLOBALS['CHANNEL_ID']}",
		"X-Line-ChannelSecret: {$GLOBALS['CHANNEL_SECRET']}",
		"X-Line-Trusted-User-With-ACL: {$GLOBALS['MID']}"
	);
	$curl = curl_init($url);
	curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
	curl_setopt($curl, CURLOPT_HEADER, true);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
	$response = curl_exec($curl);
	
	$header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE); 
	$header = substr($response, 0, $header_size);

	$body = substr($response, $header_size);
	error_log("res header::".$header);
	error_log("res body::".$body);
	return array( "header"=>$header, "body"=>$body);
}
```

## 画像データを送って顔認証を行う
取得した画像データを使ってFace APIを呼び出します。

```php
// （メンバ関数）バイナリデータから顔認証を行う
public function analyze_from_binary($img_binary) {
		
		$headers = array(
			"Content-Type" => "application/octet-stream"
		);
		
		
		$request = new Http_Request2("https://api.projectoxford.ai/face/v0/detections");
		$request->setConfig(array(
			"ssl_verify_peer" => false,
		));
		$request->setMethod(HTTP_Request2::METHOD_POST);
		$request->setHeader($headers);
		
		$url = $request->getUrl();
		$url->setQueryVariables($this->query_params);
		if( empty($img_binary)){
			return false;
		}
		$request->setBody($img_binary);
		try
		{
			$response = $request->send();
			$status = $response->getStatus();
			$body_json = json_decode($response->getBody());
			
			error_log( "resp status::".$response->getStatus() );
			error_log( "resp body::".$response->getBody() );
			
			if($status != 200){
				if( $status == 400 || $status == 408 || $status == 415 ){
					return array (
						"result" => false,
						"code" => $body_json->{"code"},
						"message" => $body_json->{"message"}
					) ;
				}
				else if( $status == 401 || $status == 403 || $status == 429 ){ 
					return array (
						"result" => false,
						"code" => $body_json->{"statusCode"},
						"message" => $body_json->{"message"}
					) ;						
				}else{
					return array (
						"result" => false,
						"code" => "unknown",
						"message" => "unknown error"
					) ;
				}
			}
			
			return array (
					"result" => true,
					"faces" => $body_json
			) ;
		}
		catch (HttpException $ex)
		{
			throw $ex;
		}
	}
```
__※aplication/octet-streamにすることを忘れずに。__

あとは、取得したデータから顔をクリップして年齢と性別を送ってあげれば、完了です。

* [githubリポジトリ](https://github.com/shuheilocale/line-bot-face-analyzer)  
※ただしこれだけでは動かないので、パーツとしてご利用ください。