ユニファ開発者ブログ

ユニファ株式会社プロダクトデベロップメント本部メンバーによるブログです。

Actions on Google と AWS Lambda で Google Home から Slack にポストする

f:id:akanuma-hiroaki:20171013013717j:plain:w300:left

 みなさまこんにちは、ユニファCTOの赤沼です。先日やっと日本でも Google Home が発売されましたね。このブログを読んでいる方の中には買われた方もそれなりに多いのではないでしょうか。ユニファでも Google Home でどのぐらいのことができるのか検証してみるために、オフィス近くのビックカメラで購入してきました。普通に使っているだけでも天気やニュースを教えてくれたりと便利ではありますが、やはりエンジニアとしてはアプリを開発したり他のものと繋げてみたくなるものです。そこで今回は Google Home から使えるオリジナルのアプリを作ってみたいと思います。公式のドキュメントで紹介されているチュートリアルでは、バックエンドとして Firebase を使っていますが、それをそのままやっても面白くないので、今回は AWS Lambda でバックエンドの処理を実装してみたいと思います。

Google Home で使うアプリの構成

 Google Home の中で実際に音声アシスタントとして動いているのは Google Assistant で、Apple の Siri や Amazon の Alexa に当たるものです。なので Google Home で使うアプリを作るということは、 Google Assistant 用のアプリを作るということになります。ということは Google Assistant が動いていれば、 Google Home だけでなく、 Android 端末や iOS 端末でも使うことができるということになります。

 Google Assistant のアプリは Actions on Google というプラットフォーム上で開発します。主に下記のような要素から構成されています。

  • Actions on Google developer project
     アプリのプロジェクトを管理するベースとなるもので、アプリの分析や、テストのためのシミュレータ等の機能を持っています。

  • Action package
     Google Assistant がアプリをどのように起動するかや、どのように Fulfillment を呼び出すかなどのメタデータを定義しています。

  • Fulfillment
     HTTP Web サービスとしてホストされる、アプリの実機能を提供するファンクションです。JSONベースの Actions Protocol で Google Assistant からのリクエストを受け付け、処理結果を返します。

アプリ開発方法の選択肢

 Actions on Google でアプリを開発する際の開発方法の選択肢としては、下記の3つがあります。

  • Templates
     あらかじめ用意されているテンプレートを使う方法で、クイズやフラッシュカードなどのアプリのテンプレートが用意されています。コードを書く必要がなく、 Google Spreadsheet でデータの用意だけすれば使えるので、アプリの種類がマッチするならこの方法が一番お手軽です。

  • Dialogflow
     以前は API.AI という名前だった、自然言語で会話できる bot を作るためのAPIを提供しているサービスで、2016年9月に Google に買収されました。そして最近 Dialogflow という名前に変わりました。(Introducing Dialogflow, the new name for API.AI)Dialogflow は Actions SDK を Web IDE でラップしたもので、Action package を簡単に作成、デプロイできるようになっています。また、NLUエンジンを含んでいるので、自前で自然言語解析をする必要がなくなります。

  • Actions SDK
     限られたユーザとシンプルなやりとりを行うだけの場合に選択される方法で、既に自前のNLUエンジンを持っていてそれを使いたい場合などは Actions SDK を用いて開発する必要があります。また、 Actions SDK はIDEを提供しないので、テキストエディタなどで Action package を作成し、CLIから Google Developer project へデプロイする必要があります。

 今回は上記の3つの中から、Dialogflowを使う方法で実装します。

Actions on Google と Dialogflow での設定

 それではアプリを作成していきます。 Actions on Google と Dialogflow での設定は、基本的にはチュートリアルで紹介されている内容を踏襲していきます。

Overview  |  Actions on Google  |  Google Developers

 まず Actions on Google のコンソールにアクセスすると、プロジェクトの作成画面になりますので、+マークと Add/Import project と書かれているところをクリックします。

f:id:akanuma-hiroaki:20171013015622p:plain:w450

 するとプロジェクトの作成ダイアログが表示されますので、プロジェクト名と国・地域を選択して、 CREATE PROJECT をクリックします。

f:id:akanuma-hiroaki:20171013015913p:plain:w450

 プロジェクトが作成され、プロジェクトのOverviewの画面が表示されます。先ほど紹介した3つの開発方法のパネルが表示されますので、今回は Dialogflow のパネルの BUILD をクリックします。

f:id:akanuma-hiroaki:20171013020107p:plain:w450

 Action を追加するためのダイアログが表示されますので、 CREATE ACTIONS ON DIALOGFLOW をクリックして、Dialogflow のコンソールへ移動します。

f:id:akanuma-hiroaki:20171013020154p:plain:w450

 Dialogflow のコンソールが表示されたら、 Description にActionの内容を入力し、 DEFAULT LANGUAGE を選択して SAVE をクリックして保存します。

f:id:akanuma-hiroaki:20171013020251p:plain:w450

 すると Intents の画面が表示されます。ここでまず Fulfillment を使うためのとりあえずの設定をするため、左メニューから Fulfillment をクリックします。

f:id:akanuma-hiroaki:20171013020318p:plain:w450

 Fulfillment の画面が表示されたら、右上のトグルが DISABLED になっていますのでクリックして ENABLED に変更し、URL にとりあえずのダミーのURLを設定して、画面下部の SAVE をクリックします。

 余談ですが、 Dialogflow に名前が変更になったのと同時にこの画面に Inline Editor が用意されたので、例えばチュートリアルで紹介されていたような、テキストエディタで Function を書いて CLI で Firebase にデプロイするというようなことは、 Inline Editor のみで完結できるようになっているようです。

 保存したら左メニューから Intents をクリックして Intents の画面に移動します。

f:id:akanuma-hiroaki:20171013020503p:plain:w450

 作成済みの Intent の一覧が表示されます。最初から Default Fallback Intent と Default Welcome Intent が用意されています。まずは Default Welcome Intent をクリックします。

f:id:akanuma-hiroaki:20171013020605p:plain:w450

 Default Welcome Intent はユーザが Action を呼び出した際の WELCOME イベントによって最初に起動されます。各項目の設定方法の詳細はチュートリアルで紹介されているので割愛しますが、今回は下記のような内容で設定しています。保存したら再度左メニューから Intents をクリックして Intent の一覧画面に戻ります。

f:id:akanuma-hiroaki:20171013020634p:plain:w450

 次にユーザが Slack にポストしたい内容を受け取るための Intent を作成するため、画面上部の CREATE INTENT をクリックします。

f:id:akanuma-hiroaki:20171013020706p:plain:w450

 新しい Intent の作成画面が表示されたら、下記のような内容で設定します。この Intent ではユーザが話したことをそのまま Slack にポストするため、パラメータとしては @sys.any で全文を使用するようにしています。リクエストを受け取ったら Slack へ投稿するための処理を Fulfillment にリクエストするため、 Fulfillment の設定は Use webhook を選択し、Slack にポストして処理は完結なので、 Google Assistant の設定で End conversation にチェックを入れています。

f:id:akanuma-hiroaki:20171013020751p:plain:w450

 ここまでで一旦 Actions on Google と Dialogflow コンソールでの設定は終了し、 AWS Lambda と API Gateway の設定を行います。

AWS Lambda Function の作成と API Gateway の設定

 今回は AWS Lambda Function と API Gateway、 Slack の Webhook の詳しい作成方法は主題ではないので割愛しますが、まず Lambda の Function は下記のような内容で作成し、postToSlackFromGoogleHome という名前で保存しました。 SLACK_POST_URL には実際の Slack の Webhook のURLを設定してください。

#-*- coding:utf-8 -*-
from urlparse import urljoin
from urllib import urlencode
import urllib2 as urlrequest
import json
import random

SLACK_POST_URL = "https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX"

def build_payload(message):
    payload = {
        "username": "Google Home",
        "text": message
    }
    return payload
    
def post(payload):
    payload_json = json.dumps(payload)
    data = urlencode({"payload": payload_json})
    req = urlrequest.Request(SLACK_POST_URL)
    response = urlrequest.build_opener(urlrequest.HTTPHandler()).open(req, data.encode('utf-8')).read()
    return response.decode('utf-8')

def post_to_slack(message):
    payload = build_payload(message)
    return post(payload)

def lambda_handler(event, context):
    return post_to_slack(event['result']['parameters']['any'])

 チュートリアルで紹介されているように node.js で Fulfillment を実装する場合には Actions on Google の SDK が利用できるので、SDKが提供するメソッドでリクエスト内容を取り出したりできますが、今回は Lambda で Python による実装なので、送信されてくる JSON から目的のパラメータを取り出しています。送信されてくる JSON で使用するパラメータに関する部分を抜粋すると下記のような内容になります。 Intent の設定でパラメータとして @sys.any を指定したので、 parameters の中で any として目的の内容が送信されてきています。今回はとりあえずユーザが話した内容だけ取れれば良いので、他の部分についてはあまり調べていません。

{
  "id": "7a27e8ff-4e18-4e80-89a0-141dbb3965a5",
  "timestamp": "2017-10-12T17:17:21.169Z",
  "lang": "ja",
  "result": {
    "source": "agent",
    "resolvedQuery": "投稿テスト",
    "action": "",
    "actionIncomplete": false,
    "parameters": {
      "any": "投稿テスト"
    }
  }
}

 作成した Lambda Function を API Gateway と紐づけ、API をデプロイしたら、そのURLを Dialogflow のコンソールの Fulfillment の画面で URL の項目に設定して保存します。

f:id:akanuma-hiroaki:20171013083215p:plain:w450

アップデートとテスト

 ここまでで一通りの設定は終了なので、テストをしてみます。まずは Dialogflow コンソールの左メニューから Integrations をクリックします。 Integration の一覧画面に移動したら、 Google Assistant のパネルをクリックします。

f:id:akanuma-hiroaki:20171013021018p:plain:w450

 初回は下記のようなダイアログが表示されるかと思いますので、そのまま DONE をクリックします。

f:id:akanuma-hiroaki:20171013021143p:plain:w450

 Google Assistant の Integration の設定ダイアログが表示されたら、下部の TEST をクリックすると、下記の画面のように Test now active と表示され、シミュレータでのテストができるようになりますので、 VIEW をクリックして Actions on Google のシミュレータに移動します。

f:id:akanuma-hiroaki:20171013021202p:plain:w450

 シミュレータに移動すると、 Input フォームにはあらかじめ「スラックポストにつないで」というテキストが入っているかと思いますので、Input フォームを選択して Enter します。

f:id:akanuma-hiroaki:20171013085449p:plain:w450

 すると Action が起動し、右側にはリクエストの JSON の内容が表示されます。続けてユーザが話した想定の文言を Input フォームから入力して Enter します。

f:id:akanuma-hiroaki:20171013085507p:plain:w450

 追加で作成した Intent の設定内容に従って処理が行われ、レスポンスが表示されます。

f:id:akanuma-hiroaki:20171013085524p:plain:w450

 実際に Slack の Webhook へのリクエストも行われ、下記のように Slack にメッセージが表示されます。

f:id:akanuma-hiroaki:20171013085544p:plain:w450

 これでとりあえず Google Home に話した内容を Slack にポストするアプリを作ることができました。

まとめ

 今回はお試しということで、ごくシンプルな内容でテスト段階までということでしたが、 Lambda 等と連携できればそこから先は自由度高く色々なことができると思います。また、一般的に使ってもらえるようなアプリを作って公開すれば、多くのユーザに使ってもらうこともできます。会話で処理を進めるというUIは画面を使うアプリと比べて用途が絞られると思いますが、もうすぐ Google Home mini も発売されますし、色々試して新しい使い方を模索してみたいと思います。

線形計画とシンプレックス法

こんにちは、システム開発部のちょうです。

最近秋の基本情報技術者試験(FE)を目標にして勉強しています。FEといえば、いろんな分野の問題が出題されます。その中に、OR・IEにおける線形計画問題は個人にとって一番難しかったです。

例えば、平成25年秋、問75

製品X及びYを生産するために2種類の原料A,Bが必要である。製品1個の生産に必要となる原料の量と調達可能量は表に示すとおりである。製品XとYの1個当たりの販売利益が,それぞれ100円,150円であるとき,最大利益は何円か。

原料 製品Xの一個当たりの必要量 製品Yの一個当たりの必要量 調達可能量
A 2 1 100
B 1 2 80

線形計画を分からなくても、問題を読んだら制約条件や目的関数が理解できると思います。

製品Xの生産量を x 、製品Yの生産量を y とする。

制約条件

2x + y <= 100
x + 2y <= 80

最大利益 max の関数

max = 100x + 150y

一見二変数の方程式ですが、不等式のゆえ、そのまま解けてはならないです。でも現実と繋がる生産の問題なので、生産量は0とそれ以上、つまり負数にならないことが分かります。

x, y >= 0

不等式と合わせて、生産量x, yの範囲は計算できそうですが、 xy お互い関連して、それといつ最大利益になるかも分からなくて、範囲を計算しても意味がないかもしれません。

ほかに方法がないでしょうか。

続きを読む

Rails 5.2で追加予定のActiveStorageを使ってみる

こんにちは、Webエンジニアの本間です。 先週、広島で RubyKaigi 2017 が開催されましたね。私は参加できなかったのですが、興味深い発表が多かったので、公開されているスライドは一通りチェックしたいなと思っています。

さて、今回のブログでは、少し乗り遅れた感がありますがRails 5.2で追加予定の ActiveStorage を使って見て、感想や気になる点などを書いていこうかなと思います。

※注意:現在開発中のalphaバージョンのRails 5.2を使用しています。記事公開後の修正によってはサンプルコードが動作しなかったり、記載された内容と動作が異なる可能性があります。

続きを読む

EC2のタグ情報で設定ファイルを切り替える

おはこんばんにちは、インフラのすずきです。

先日iPhone 6s Plusがお風呂にダイブして、新型iPhoneが出る直前で本体交換しないといけないのか!?
とドキドキしていましたが、なんとか無事で今も順調に動いてくれています。
少なくともiPhone X発売まで頑張って頂きたいところです。

本題ですが、AWSのEC2タグ情報を利用して設定ファイルを切り替える方法をご紹介します。

続きを読む

Swift3 で CoreBluetooth 実装するときにあったら良さそうな Data Extension

こんにちは、iOSエンジニアのしだです。
iOSアプリからBLE経由で Characteristics の値を読み込む場合、20バイト程度のデータをデコードする必要があります。 主に Bluetooth SIG で定められているGATTのデータフォーマットで、あったら便利な Data の Extension を共有します。

準備

  • Xcode 8.3.3 (Swift 3.1)
続きを読む

ffmpegで写真や動画を加工してみよう!

こんにちは。エンジニアの田渕です。

東京では、夜になると秋の虫の声が聞こえるようになりました。ついこの間まで、寝苦しい夜に悩んでいたのに、月日の経つのは早いものです。

日本は秋から冬にかけ比較的行事が多く、その分、写真や動画を撮影する機会も増えます。 最近ではスマートフォンの無料アプリで驚くくらいに加工ができるので、利用されている方も多いのではないでしょうか。 今回は、そんな画像や動画の加工を、コマンドでやってみよう、という記事です。

以前、こちらのブログでffmpegを利用してクロマキー合成する、という記事を書かせてもらいましたが、今回も同じくffmpegを利用して、画像の加工をしていきます。 ffmpegって何?という部分については、前回記事を参照ください。 tech.unifa-e.com

加工してみよう!

では、さっそく加工して行きましょう。

元の画像

こちらの写真は、春先に道端で撮影したものです。夜、そしてスマートフォンのカメラ、という悪条件。 特に何か加工をしたりした訳ではなく、なんだか偶然、全般的に桜の色になっております。 ただ、……ちょっと、くらいかな。。。 f:id:unifa_tech:20170904233442j:plain

コマンド紹介

ffmpegには画像加工用のフィルターという概念があるのですが、そのフィルターのオプションを利用することで、画像や動画の加工が出来ます。 今回利用するのは、eqというオプション。

公式ドキュメントは下記になります。

FFmpeg Filters Documentation

なんだか、色々とたくさんありますが、今日はこの中の、brightness、saturation、contrastを使ってみたいと思います。

コマンドは至って簡単、下記のような具合に指定して使います。

ffmpeg -i source.jpg -vf eq=brightness=0.06 -c:a copy output.jpg

赤文字の部分が、今回の主役であるeqオプションの指定部分です。サンプルは、brightnessを指定した例。 他のオプションも、同じような感じで指定します。 組み合わせて使うことも出来ますが、今回はそれぞれの効果を見るために、一つだけ指定したものを順に見ていきます。

brightness

日本語で言う「輝度」です。サンプル画像は、上で紹介したコマンドをそのまま実行した結果です。 元の画像と比較して、全体的に明るくなっているのがわかるでしょうか? f:id:unifa_tech:20170904233603j:plain

saturation

こちらは「彩度」を変更した画像です。saturation=2という値で実行しています。かなり鮮やかになりましたね。 f:id:unifa_tech:20170904235821j:plain

contrast

最後が「コントラスト」。contrast=2で実行した場合です。元の画像と比べると、くっきり物が見えるようになったような? f:id:unifa_tech:20170904233650j:plain

どうしてffmpegか?

ここまで見てきてもらったように、コマンドでオプションを指定することで、画像の加工ができることがわかりました。実はコマンドラインで画像加工出来るソフトは他にももっと色々とあるのですが、(もっと良く使われているImage Magickとか。)なぜ今回ffmpegを使ったのか?といえば、ffmpegは↑に書かれているコマンドと、全く同じコマンドで、動画の加工が出来るからです。 入力と出力のファイル名を、それぞれ動画のファイルにするだけで、暗かった動画を明るく出来たりします。

まとめ

撮影チャンスは突然にやってくるもので、必ずしも撮影に最適な状況にはなっていません。それでも、一か八か!でシャッターを切るわけですが、結果、「真っ暗で何も見えない」とか「ぼんやりしていて……。」なんてことも、ままあります。

そんな時、園の先生方がパソコンのソフトなどを使って苦労して画像を修正するのではなく、サーバー側でなんとか出来ないかなぁ……なんてことを夢見ています。 もちろん、実現には、色々とクリアすべき課題があるのですが……。

ffmpegのフィルターオプションは膨大で、なかなか面白いものも眠っているので、また折を見てご紹介できればと思います。

それでは!

顔認識でハッピー分析

こんにちは、2週連続の田中です。(でも、先週とは別の田中です。。)

前々から気になってたことを少しだけではありますが解析したので今日はそれについて共有します。

気になっていたこと

それは、、

「保護者のみなさまが購入した写真はハッピーな表情が多いのか?」

きっと、そうだろう とは思いつつも調べてなかったので今回調べてみました。

分析ステップ

  1. 保護者の方が購入した写真を抽出する
  2. 顔認識を行い、顔を検出するとともに表情の認識も行う
  3. どんな表情が検出されているか集計、可視化する

ステップ3に関して、本来は写真とその認識結果を対で示しながらというのが分かりやすくベストなのですがプライバシーの問題のため園の実際の写真を出せないのが残念です。

数字とテキストでまとめただけの結果になりますがご容赦ください。。

顔認識

顔認識は Amazon Rekognition を使いました。 Rekognition は 元々、アメリカのベンチャー企業 Orbeus社 が開発したサービスで実は弊社の子どもの顔認識機能のリリース当初、この技術を使っていました。

突然、サービス停止のアナウンスがあり、慌てて代替の顔認識サービスを探して置き換えの開発を余儀なくされた思い出深いサービスです。その時は、

ええ、マジかよ?! ╰(゚x゚​)╯

と戸惑うばかりでしたがそういう事情(Amazonによる買収)だったんですね。。

当時、顔認識をAPIで提供するサービスはいくつかありましたがRekognitionの性能が頭一つは抜けていたので今回改めて使ってみました。

Rekognitionでは顔の認識とともに表情も検出でき、検出できる表情の種類は

  • HAPPY
  • SAD
  • ANGRY
  • CONFUSED
  • DISGUSTED
  • SURPRISED
  • CALM
  • UNKNOWN

の8種類です。

http://docs.aws.amazon.com/rekognition/latest/dg/API_Emotion.html

表情にはそれぞれ0から100までのスコアがつきます。 検出されなかった表情についてはスコアはつきません。

AWSのアカウントを持っている方は Rekognitionのコンソール画面からデモでお試しできます。

Rekognitionの注意点として現時点では使えるリージョンが限定されていて東京リージョンでは使えません。そのため、今回はOregon(us-west-2)を使いました。対象写真を格納しておくS3バケットも同じリージョンになります。

参考までに使用したコードはこちらです。

require 'optparse'
require "aws-sdk"
require 'dotenv'

options = {}
OptionParser.new do |opt|
  opt.on('-b VALUE', '--bucket VALUE', 'bucket name') { |v| options[:bucket] = v }
  opt.on('-p VALUE', '--prefix VALUE', 'key prefix') { |v| options[:prefix] = v }
  opt.on('-o VALUE', '--output VALUE', 'output directory') { |v| options[:output] = v }
  opt.parse!(ARGV)
end

Dotenv.load

Aws.config = {
  access_key_id: ENV.fetch('AWS_ACCESS_KEY_ID'),
  secret_access_key: ENV.fetch('AWS_SECRET_ACCESS_KEY'),
  region: 'us-west-2'
}

bucket = options[:bucket] || 'your_default_bucket'
prefix = options[:prefix] || ''
output_directory = options[:output] || '.'

s3 = Aws::S3::Client.new(region: 'us-west-2')
keys = s3.list_objects(bucket: bucket, prefix: prefix).contents.map do |object|
  object.key
end
puts "#{keys.count} objects"

rekog = Aws::Rekognition::Client.new(region: "us-west-2")
keys.each do |key|
  puts key

  result = rekog.detect_faces({
    image: {
      s3_object: {
        bucket: bucket,
        name: key
      },
    },
    attributes: ["ALL"]
  })

  output_filename = output_directory + "/" + File.basename(key, '.jpg') + ".json"
  File.write(output_filename, result.to_h.to_json)
end

分析

とりあえず、3枚以上購入があった写真を1000枚抽出して顔認識を行い結果を見てみました。1000枚なのは私の体力的な都合によるものです。

結果

まず、検出した顔が1つの写真に絞り集計してみます。(購入者のお子様だと考えられ、結果の解釈がしやすいため)

表情 カウント スコア平均値
HAPPY 95 73.5
SURPRISED 19 52.5
CALM 3 40.4
CONFUSED 9 35.6
DISGUSTED 3 45.8
ANGRY 9 54.0
SAD 23 36.9

実際の写真も確認して、まとめると

  • 当然ながら HAPPY が多くそのスコアも高い
  • 真面目な表情(何かに真剣に取り組んでいたりetc)は SURPRISED, CALM, CONFUSED あたりでそこそこカウントされた
  • ANGRY, SAD が想定よりカウントが多い。。

ANGRY, SAD となった写真を確認してみると実際には怒ったり悲しい表情をしているというより 下向き顔が多く、機械学習をかけるとこの分類になりやすいようです。下向き顔 = うつむき顔 = 悲しい ということで分類としては妥当な気がしました。

園児がデスクワークしている姿を保育士が立った位置から撮影するとどうしても園児の顔向きとしてはうつむき加減になってしまいます。このような子どもが作業に没頭している写真も残しておきたい大事な1枚ということになります。

続いて、集計する顔の数を増やしてみましたが傾向は大きく変わりませんでした。

今後…

購入のなかった写真には HAPPYな顔は少ないのか? と言ったら恐らくそうとは言えず(自分の子ども以外は良い写真であっても基本的には買わないため)今回の分析には課題や突っ込みどころがたくさんあります。(頑張って数値解析したら有意な差が出るかも?)

ただ、自分の子どもが写っている写真がたくさんある中、購入する写真はそのごく一部であり購入された写真には親の感情により響く、言わばハッピーをより感じさせる何か画像特徴としてのエッセンスがあると考えています。

引き続き検討を行い、保護者が感じるハッピーのエッセンスをシステムとして抽出し、それにマッチする写真を優先的に届けられたらなと思います。ƪ(•◡•ƪ)"