ちょっと未来

ちょっと未来を感じさせるガジェットやアプリ、そして作ったものについて書きます

お正月に向けて、かるた読み上げアプリを作ってみた

かるたの読み手をアプリにやってもらうようにしました。

今回もReact Native + Expoです。
Speech API便利〜!

きっかけ

息子がそろそろ平仮名を読めるようになってきたので、今年の正月はかるたをやってみよう! ということでおもちゃ屋さんに行ってきました。

どれにしようかね〜、と見ていたところ、読み上げアプリ配信中という記載のあるかるたが目に飛び込んできました。

なるほど、たしかにアプリが読み上げてくれれば二人で遊べるし便利だ、、
と思ってそのかるたを手に取ろうとしたところ、、、

息子が「これがいい!!」と別のディズニーのかるたを手に取りました。
残念ながらそのディズニーのかるたには読み上げアプリが付属していません

f:id:wasan:20191231072624j:plain
ディズニーかるた 読み上げアプリはない

一度こっちがいいと言い出すと言うことを聞きません、、

そこでこのディズニーかるた用の読み上げアプリを作ることにしました

先日ExpoのSpeech APIを試して、かなりお手軽だったので(^^)

作ってみた

こんな感じになりました。 

f:id:wasan:20191231072431j:plain:w300
こんな感じでシンプルに

  • つぎへ、で次のカードを読む
  • 10秒ごとにリピートして読み上げる
  • 完了したカードを一覧表示

ソースコードはこちら。

https://github.com/takahi5/karuta-reader

なんだか変なイントネーションの場合もあるけど、十分実用レベルでしょう。

お正月はこれでかるたするぞ〜。

React Native+Expoでこどもちゃれんじの付録を模したアプリを秒で作る

小さいお子様をお持ちの方ならわかると思うのですが、子供ができるとどこから情報を手に入れたのか、ベネッセからダイレクトメールが届くようになります。

ダイレクトメールの中には教材の紙製サンプルが入ってることもあります。 紙で試したところ気に入ったので入会、ということもあるのでしょう。

きっかけ

ところで先日、こどもチャレンジEnglishの紙製サンプルが届いたのですが、これを息子が非常に気に入っていました。

f:id:wasan:20191222142550j:plain:w300
こういうサンプルが付属してきます

たぶん本物はボタンを押すと英語が鳴るんだと思われます。

紙を押して「ぴっつぅあ〜」と話しているのを見て、なにか不憫に感じました、、、 これ入会したら英語の勉強するんかな、、とも思いました。

Expoで作ってみる

が、そこで一歩立ち止まって、ExpoのAPIを検索したところSpeechというのを発見した結果、開発モチベーションが爆上がりして、紙製サンプルを模したアプリを作りました。

f:id:wasan:20191222142913p:plain:w300
紙製サンプルを模したアプリ

さて、Expoを触ったことがある方なら想像付くかと思いますが、この程度のアプリだと一瞬で作れてしまいます。

ソースコードこちら

息子も気に入ったようで、二人でお店屋さんごっこをしました。

Speechについて

Speech部分の例は以下のようになります。

import * as Speech from "expo-speech";

const onPressButton = (thingToSay) => {
    Speech.speak(thingToSay, { language: "en-US" });
}

これだけのコードで、ボタンタップ時に指定した単語を発音させられるのでExpo最高って思います。

強いて言うと { language: "en-US" } の指定を忘れないようにしましょう。
これを指定しないと、実機(Android)で動かした際に、ガチガチのジャパニーズイングリッシュで「はんばーがー」と発音されてしまい焦りました。

Expoが自分専用アプリに向いている

今回は息子専用アプリを作ったわけですが、Expoはこういう用途に向いているなと思いました。

Expo Clientアプリのおかげで開発したものを実機に反映するのが非常に楽です。

また改修を反映するのも楽で、このメリットが大きいです。

今回も「ポップコーンを入れてほしい!」「数字を増やしてほしい!」とかのリクエストをオンラインですぐに反映することが出来ました。

firestoreを本気で使ってみて知った勘違い3つほど

いまReact Native ✕ firebaseなプロダクトを開発しています。

仕様的にそこまで複雑でなかったので、バックエンドは完全firebaseで作っています。
なのでDBはfirestoreを使っています。

realtime databaseを使ったことはあったけど、firestoreを使うのは初めてです。
というかNo SQL的なのを使うのが初めて。

RDBといろいろ違うってことは分かってたつもりだけど、いざ初めてみると色々な勘違いがありました。

ちなみにfirestoreの勉強は Firestore Mastery - shiodaifuku - BOOTH がめちゃくちゃ為になりました。
チームではバイブル本となっています。

では、勘違いしてたことを3つほど!

何でも非正規化すればよいわけではない

firestoreはRDBとは違って、正規化せずにデータ重複を恐れず非正規化すべき!
という印象が強く、基本的に非正規化するものかと思っていたけど、そういう訳ではないようです。

firestoreの結合は思ったより早く、ケース・バイ・ケースで非正規化するかどうかは判断する必要があります。 そして、バイブル本(上記)によると大半の場合では非正規化をする必要がないとのこと。なんと!

例えば、食べログのようなレビューサイトで、reviewのドキュメントにuser情報を展開するか否かですが、、

  • 画面にレビューを1件だけ表示
    非正規化は不要。1つのuserを結合すればよいだけなので、非正規化せずに都度結合する。

f:id:wasan:20191018215455p:plain

  • レビュー一覧の画面
    非正規化したほうがよさげ。レビュー数だけのuser結合が発生するので、userの名前、アバター画像は非正規化して展開しておくのがよさそう。  

f:id:wasan:20191018215529p:plain

しかし、、、

  • プロフの更新頻度が高い場合
    例えばプロフにScoreという情報があり、1日に10回くらいScoreが更新されるとします。 ある人がレビューを1000件書いてるとすると、毎日1000件x10回のreviewドキュメントの更新が必要になります。 なので、非正規化したデータの更新頻度がめちゃめちゃ高い場合は、非正規化のデメリットも大きくなります。

f:id:wasan:20191018220206p:plain

そのへんのバランスも考慮する必要があり、サービス毎にケース・バイ・ケースで判断が必要になりそうですね。

検索はかなり弱い

firestoreはあまり検索が強くなく、例えば"全文検索"したい場合はalgoliaなどの別の検索サービスを併用するのがよいですよ、っていのは聞いていました。

が、実際使ってみると全文検索の以前に、ORIN での検索もできないんですね。

MySQLの間隔だと当たり前の検索も一筋縄ではいかないようです。

結局ORの絞り込み検索のためにalgoliaを使うことにしました。

他のコレクションへの参照はidでなくreferenceで

RDB的な感覚だと、ほかテーブルのidで参照したりします。 が、firestoreではidではなくreferenceを使いましょう、とのこと。

たぶん基本的なことなんだけど、知らんかった! 先に例のバイブル本を読んでおいてよかったです。

collection groupというもの

これは勘違いというか、こういうものがあると知ったのですが、 collectin groupというものを使うと複数のサブコレクションを横断して検索できます。

具体的に言うと、reviewがrestaurantのサブコレクションとしてに紐付いているとして、、
あるユーザーの投稿したreviewだけを抽出したい場合(マイページとかでありそう)、 restaurant配下を横断して検索できます。

f:id:wasan:20191018222208p:plain

ちなみに、collection groupの登場以前は、あるユーザーの投稿したreviewだけを抽出したい、という要件がある場合は、 reviewはrestaurant配下でなく、まったく別に分けて保持していました。

collection groupの有る無しで、データの設計方法がガラッと変わるので、これは先に知っておいてよかったです。

ちなみにこのcollection groupは今年(2019年)5月に登場したばかりらしいです。
ベストプラクティスもどんどん進化するので、常にキャッチアップが必要ですね〜。

Amazon Rekognitionで保育園の膨大な写真リストから我が子が写った写真を抽出する

息子が通っている保育園では、運動会や遠足の写真をWebサイトから選んで注文できるようになっています。

ただその写真の数が膨大(2000枚とか!)で、その中から我が子を探すのはかなり苦行です。
まして全員同じ制服を着てるので、かなり注意深く探さないと見落とします。

こんな苦行は人間のやるものでないので、画像認識で自動で探すようにしてみました。

きっかけ

そもそも、このような画像認識で我が子を抽出する仕組みは、今どきのイケてるサービスには搭載されています。
https://www.jagat.or.jp/cm2017_ai_8122

しかし我が保育園のサービスは90年代か?ってレベルの作りなので、到底そのような便利機能はありません。

そこで、なければ作ればいいやん!精神で自分で作ることにしました。

仕組み

  1. まずwebサイトをクローリングして写真をローカルにダウンロードします。古き良き90年代風な作りなので、スクレイピングするのは簡単でした。
  2. 我が子のソース画像を1枚用意します。
  3. AWS Rekognition APIのface を使って類似度を調べます。類似度がしきい値以上の顔が含まれていたら、その写真は別フォルダにコピーします。

RubyでRekognitionを実装してみる

f:id:wasan:20190806143719j:plain

画像認識にはAWSのRekognitionを使いました。

類似度比較のコードは以下のようになります。

require "aws-sdk"
require "dotenv"

Dotenv.load

Aws.config.update({
  region: ENV["AWS_REGION"],
  credentials: Aws::Credentials.new(ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_ACCESS_KEY"]),
})

rekognition = Aws::Rekognition::Client.new(region: Aws.config[:region], credentials: Aws.config[:credentials])

response_compare_faces = rekognition.compare_faces({
  source_image: { bytes: File.read("./source.jpg") },
  target_image: { bytes: File.read("./target.jpg") },
  similarity_threshold: 90,
})
  

↓で、類似度比較をしています。

similarity_threshold以上の類似度のレスポンスのみが返されます。

response_compare_faces = rekognition.compare_faces({
  source_image: { bytes: File.read("./source.jpg") },
  target_image: { bytes: File.read("./target.jpg") },
  similarity_threshold: 90,
})

↓のように実行結果がJSONで返ってきます。

face_matches配列に90%以上のマッチ度の顔情報が返ってきます。 similarityが類似度になります。保育園の写真で試したところ、96%くらいで切るのがよさげでした。

検出した顔の座標もわかるので、顔の位置に印を描いたりもできますね。

{  
   "face_matches":[  
      {  
         "similarity":96.90641784667969,
         "face":{  
            "bounding_box":{  
               "width":0.045815691351890564,
               "height":0.07679319381713867,
               "left":0.9444459676742554,
               "top":0.2314029484987259
            },
            "confidence":99.99993133544922,
            "landmarks":[  
               {  
                  "type":"eyeLeft",
                  "x":0.9546165466308594,
                  "y":0.26450955867767334
               },
               {  
                  "type":"eyeRight",
                  "x":0.9719675779342651,
                  "y":0.26381704211235046
               },
               {  
                  "type":"mouthLeft",
                  "x":0.9573304057121277,
                  "y":0.2928299605846405
               },
               {  
                  "type":"mouthRight",
                  "x":0.9717002511024475,
                  "y":0.29241642355918884
               },
               {  
                  "type":"nose",
                  "x":0.9607734084129333,
                  "y":0.2815253734588623
               }
            ],
            "pose":{  
               "roll":-1.0979448556900024,
               "yaw":-14.801901817321777,
               "pitch":-16.4953670501709
            },
            "quality":{  
               "brightness":76.67810821533203,
               "sharpness":12.848764419555664
            }
         }
      }
   ],
   (略)  
}

精度は?

十分に実用レベルでした。
むしろ人力で探すより精度が高い気がします。
(あ、こんなとこに写ってた!みたいなのが何枚かありました)

類似度のしきい値次第などところもありますが、今回は少し緩めの96%以上を対象にしました。
中には他人を検出してるのもありますが、そこは最後に目視で除けばよいので。

例えばフリー素材で試した感じだと、、、

f:id:wasan:20190808212058j:plain:w200
ソース画像

↑のソース画像に対して↓をしっかり抽出してくれます。

f:id:wasan:20190808212303j:plainf:id:wasan:20190808212321j:plainf:id:wasan:20190808212334j:plain
複数人でも / メガネでも / こんな表情でも

気になるお値段は?

AWS の無料利用枠の一環として、Amazon Rekognition Image を無料で開始していただけます。サインアップすると、Amazon Rekognition を初めて使用するお客様は、最初の 12 か月間は、1 か月あたり 5,000 枚の画像分析と、毎月最大 1,000 個の顔メタデータの保存が可能です。

処理画像 1,000 枚あたりの料金
処理された 1 か月あたりの画像 100 万枚まで* 1.3USD

無料枠もあるし、1000枚処理して1.3USDなのでそんなに高くなさそうですね!

サンプル

こちらにソースコードをアップしました。

github.com

Webから簡単に使えるようにすれば、うちの保育園のパパママに感謝されるかな!?

Maker Faire Tokyo 2019 今年も親子で行ってきました

今年もMaker Faire Tokyoに行ってきました。

息子が生まれてから毎年参加しており、年々できることが増えていくので、成長のバロメーターになっています。

成長記録

-1歳
出展側で参加。VRメーヴェを展示。息子はお腹の中。

0歳
炎天下のなか外出するのにヒヤヒヤ

1歳
ベビーカーで観覧。ロボットにビビってた。

2歳
お絵かきコーナーへ足を踏み入れる。
クレヨンで線を描けた。

3歳
ミニカー作りのワークショップに参加。
親のサポートもあるが、自分でデザインしたミニカーを気に入っていた。

そして4歳の今年

展示を楽しむことができた

これが一番の変化。
今までは展示を見ても「よくわからん」「怖い」って感じだったけど、今年は「次あれ!」って興味を持って展示を巡ることができました。

展示自体も体験系、かつ子供向けが多くなってる気がしました。
プラレールとかレゴが動くやつには惹かれがち。
あと運転できる系。間違いないく少年心をくすぐる。

f:id:wasan:20190805200350j:plain
待ちなしで体験できたクレープ作り

f:id:wasan:20190805200842j:plain
本物のタクシー運転席でラジコンを操縦 by Japan Taxi

お目当てのミニカー作りはチケット売り切れ

去年好評だったミニカー作り「ナーディー・ダービー」。今年もこれを楽しみに足を運びました。

が、展示を一巡りしチケットを買おうとしたところギリギリで売り切れで参加できず😨
来年は先にチケットを買おう。

たまたま昨年制作したミニカーを持ってきていたので、それをコッソリ出して走らせました。

f:id:wasan:20190805201050j:plain

代わりに参加したストロー工作が面白かった

そこで代わりにストロー工作のワークショップに参加しました。

これは難易度が低いので4歳の息子もほぼ自分の手で作ることができました。
そのせいか完成した「傘」がたいそう気に入っていました。

f:id:wasan:20190805201116j:plain

来年は?

Make Tokyo Meetingと呼ばれ、東工大で開催されていた頃から足を運んでいますが、
かつてのちょっとマニアックな雰囲気から、 いまは子供が楽しめるイベントとして完成してますね。

子供ができてからというもの、二人で楽しむべく毎年足を運んでいます。
去年くらいまでは、父が一人楽しんでた感があるものの、
今年はだいぶ二人で楽しめるようになった感。

イベントの継続には険しい空気も流れているようですが、なんとか続いてほしいです。
もう少し息子が大きくなったら、一緒に出展してみたいものです。

Google ToDoで昨日消化したタスクをメールする - Google App Script

Google ToDoで昨日消化したタスクをメールする、っていうスクリプトGoogle App Scriptで作りました。
昨日こんだけこなしたわ〜、じぶんお疲れ!ってやるためです。
はい、自己満のためです。

f:id:wasan:20190317223532p:plain

今更ながらToDoリストがよいって話

↓の本を読んでから、ToDoリストを付けるのをはじめました。
なぜ、あなたの仕事は終わらないのか スピードは最強の武器である

今更ながらToDoリストっていいですねw

毎朝、その日やることを書き出し、また差し込みタスクがあったときは、まずToDoリストにメモする、ってことを習慣づけました。

その結果 、いろいろと恩恵がありました。
- さて次なにやるんだっけ? が無くなった
- 依頼されたタスクを忘れちゃうことがなくなった減った
- 優先度を意識して仕事をこなせるようになった

あと、やったことが可視化されるので働いた感が出るのもよいですw

ツールとしては、上記の本が薦める通りに、アナログのメモ帳を使いました。
手書きでタスクを書き出し、完了したら赤ペンでズサッと斜線を引きます。

デジタルのToDoリストにしてみたが...

そして、11月頃から使い始めたメモ帳がいっぱいになったのを機に、デジタルツールに移行してみることにしました。

Google のToDoリストを使うことにしました。
GmailGoogleカレンダーからのアクセスがよく、スマホアプリもあるので使い勝手がよさそうです。

しかし、しばらく使ってみると何だか物足りなさがありました...

あと、やったことが可視化されるので働いた感が出るのもよいですw

ここが欠けています。

デジタルのToDoリストだと完了したタスクが非表示になっていくので、
「あー今日はこんだけこなしたわー」
がイマイチわかりません。

てことで、Google App Scriptで、昨日こなしたタスクを通知するツールを作りました。
...前置き長い!!!

Google App Scriptでタスクを取得する

コードはこちらになります。

本体のJSに加え、
メールで通知する形にしたので、HTMLメール用のテンプレもあります。

本体のJS gist.github.com

HTMLテンプレ

gist.github.com

あまり特殊なところはないのですが、しいて言えばTaskを取ってくる際のオプションの指定です。

この辺のオプションにより、完了したタスクを取得できます。

      showCompleted: true,
      showDeleted: true,
      showHidden: true,

また、このオプションにより、直近24時間に完了したタスクに絞り込んでます。

      completedMax: now.toISOString(),
      completedMin: yesterday.toISOString()

さいごに

このスクリプトを、毎朝6時に実行するようにトリガー設定しました。
(cronみたいな定期実行もできるしGAS便利!)

これでデジタルの不満もちょっと解消されたので、もうしばらくデジタルToDoリストを運用してみようと思います。

【読書メモ】ファンベース: 支持され、愛され、長く売れ続けるために

webサービスからリアルな商品まで「コミュニティマネジメント」っていう言葉が浸透しつつありますが、そういった施策全般に通じるお話でした。

コアファン

ファン、って考えたときにありがちなのが 「全員をファンにしよう!」はありがちな間違え、とのこと。

多くのサービスで売り上げは一部のコアファンによって支えられてる。

例えばカゴメのトマトジュースは上位2.5%のファンが売り上げの30-40%を支えているとか!

オセロニアの運営に聞いた話だと、、
ファンイベントでは基本的に上位ユーザーをもてなす。
上位ユーザーの熱量が上がれば、自然と他のファンも付いてくるから。
らしいです。

「コアファン」を徹底的に大事にしよう、ってことですね。

キャンペーンは瞬間風速じゃ意味がない

これありがち。

キャンペーンで何人ユーザー獲得したか!?
みたいな数字をKPIに置いちゃうと、こうなりがちです。

単純にユーザー獲得を増やすのであれば、インセンティブを豪華にすればいい。

豪華な景品を用意した結果、景品目当ての雑多なユーザーが集まっちゃう。

キャンペーンが終わったらユーザーは去り元の景色に‥.

「キャンペーンやってるけど何人ユーザー獲得したん??」
みたいな上司がいるとなりがちですね。

大事なのはキャンペーン後にどれだけユーザーが定着しているか

キャンペーンで入会したユーザーのキャンペーン後の継続率やアクティビティをKPIに置くとべきですね。

「この前キャンペーンやってたけど、その後どう?」ですね。上司が聞くべきなのは。

東京は別の国

東京にいると、みんなスマホ使ってるし、なんでもネットで調べてる、って気がしますが、そうでもないらしいです。

地方では割とテレビなどのマスメディアを使ったキャンペーンが有効だったりするらしいです。

なるほど。
そういえばメルカリが地方で新聞広告だしてましたね。

東京に住んでるとついついそれがスタンダードに思っちゃうけど、「東京は別の国」って心の中に刻んでおくとよさそうですね。

選択肢が多すぎると人は買うのをやめてしまう

ジャムの実験、というのがあるらしいです。

  • 24種類のジャムを置いた売り場
  • 6種類のジャムを置いた売り場

結果、後者の方がたくさん売れた、とのこと。

似たような経験をソシャゲの設計でしたことがあります。

アイテム売り上げを増やすために、今までは剣アイテムだけだったけど、新しく盾アイテムを追加しました。
剣だけでなく盾も買ったほうが強くなるので、両方購入されて、売り上げが増える見込みでした。

しかし結果は、今まで売れていた剣も売れなくなり、全体売り上げも下がってしまいました。

剣と盾、どっちを買うのがよいか?

という迷いが生じた結果、どちらも買わない、ということになったのだと思います。

じっくり考えて買うような高級品なら、ラインナップが多くてもいい気はしますが、
瞬間で意思決定しなければならない、スマホアプリのような場合は、パッと決定させるためにも、選択肢は少ないほうがいいのだと思います。

まとめ

本書の事例はマーケ系の話題が多かったですが、
Webサービスやアプリの運用やってる人にも為になりそうでした。

特にto Cとかコミュニティ系のサービスだと学びが多そうです。

ファンベース (ちくま新書)

ファンベース (ちくま新書)