全自動タピオカインスタ映えロボットを作った話

## はじめに
釣りタイトルっぽくてすみません。
Webからロボティクスまで、いろんな技術を使ってタピオカを茹でたりバエさせたりした話をまとめました。
まずは全体像を紹介し、細かい技術的な話はリクエストなどがあれば後々まとめようかなという感じです。

## ことの始まり
なんだなんだこの列は...

待っていたのは黒のつぶつぶが入った飲み物。
そう、タピオカティー。

「タピオカティー」は今年もっとも流行ったといっても過言ではないです。
ぐるなび総研は2019年の日本の世相を反映し象徴する「今年の一皿」としてタピオカを選んだそうです。([詳しくはこちら](https://gri.gnavi.co.jp/dishoftheyear/))

Instagramに投稿されていくタピオカティー。
Instagramで`#タピオカ`を検索すると2,161,212件(※2019/11/28 現在)もヒットしました。
[keywordtool](https://keywordtool.io/jp)で調べたところ、Googleでの`タピオカ`検索数は以下のようになっています。
![タピオカ Google検索数](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104635/0b798537-2fce-005a-e502-7192344f86d8.png)

「タピオカを茹で、ミルクティーを入れ、そしてバエさせインスタに投稿する。」

これから話すのは、ひょんなことをキッカケに大学のメンバー4人が集い、この工程を技術で実現できないかやってみた話です。
通称、インスタピオカロボットを作ってタピオカをバエさせるまでで詰まったところや思ったところなどをまとめました。

筆者は「バエさせる画像を選択する/Web View」「インスタ投稿」を主に担当し、バエさせる角度の検討や多少のモデリング、このロボットを作ったことのまとめとしてポスター作成などをしました。

# 全体構成
このロボットを作るときにまず行ったのが細分化です。
後々のバグ発見の速度をあげるために、それぞれ依存関係を無くし、独立して動くようにしました。(マイクロサービスのイメージ。)
その後、メッセージのやり取りで全体を動かしていくことを目標にしました。
その結果、ほとんど全てROS Topicで通信しています。(ROSのwikiは[こちら](http://wiki.ros.org/ja))

Web Viewでどの画像がタップされたのか、MATLABとの連携、Arduinoとの連携もROSで通信しています。

puppeteerの部分は新規カメラ画像が取得されたことを検知して実行されるようになっています。

システム全体の流れとしては、
「WebViewで参考にするバエ画像を選択」
　　　↓
「参考画像のIDからあらかじめ出力しておいた角度に変換」
　　　↓
「タピオカをゆでる」
　　　↓
「タピオカミルクティーにする」
　　　↓
「バエさせたいタピオカミルクティーのコップの角度が先の参考画像と角度と一致するようにロボットアームを動かす」
　　　↓
「写真を撮る」
　　　↓
「Instagramに投稿する」
です。

当初はこの流れでやりたかったのですが、Arduino Megaが燃えるなどの諸処のトラブルで茹でるところとミルクティーにするところはこの時期を書いたときにはまだ実装してません。それ以外のところは実装済みです。
![system.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104635/7f909ca6-820e-a825-e783-22308f4666d6.png)


## 環境
- Linux Ubuntu 16.04 LTS
- Arduino Mega
- ROS Kinetic
- Python2.7(ROSの関係で)
- Html/CSS/JavaScript
- MATLAB Release 2019a
- Headless Chrome


## バエさせる画像を選択する/Web View
限りなくInstagramのUIに近づけました。
CSS Flexbox(Flexible Box Layout Module)で作りました。
わかりにくいですが周りの余白もInstagramのUIと同じカラーになっています。

じっくりInstagramのUIを触ってみて初めてわかったのですが、興味深いことにWeb版のUIをどんどん縮小していく(スマホサイズにしていく)とスマホのアプリでよく見るスタイルになります。
そしてWeb版だと投稿するボタンがないので普通にやろうとしてもできないみたいです。(もしかしたらできるのかも？)

今回はスマホで見ることはないので実装したUIもそこまではしませんでしたが、PCではしっかり動くようなレスポンシブデザインにしました。
![Screen Shot 2019-12-10 at 2.24.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104635/e3fe8f6c-a41e-9c9f-6649-0ffb156b1ada.png)


UIも少しだけこだわりました。
よくある？画像をホバーしたときに暗くなるやつとか...


![insta1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104635/be426984-b01b-3eac-1d7c-b7897087db15.gif)

進捗情報をROS Topicで取得して更新するなどもできるようになりました。

![insta2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104635/9c1fe3db-441a-df79-e874-79757f1e9b9c.gif)



## 茹でる
CAD図は以下に置いておきます。(作った人すごい！)
タピオカを入れ、タピオカを茹でる機構を実装しました。
原点にリミットスイッチが付いているのでそこで原点出しができます。(3Dプリンタのアレ)
エンコーダ付きのブラシレスモータでX, Z軸を移動できます。
おたまの駆動とミルクティーを入れるための蛇口部分はサーボモータで制御しています。

![cad.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104635/937b26c2-771d-7c8c-9d3d-0f224e3b6feb.png)


## バエる角度検出
Sobelエッジ検出法とハフ変換でタピオカのカップの楕円を検出します。
Sobelエッジ検出法でエッジを検出し、ハフ変換で楕円の検出を行いました。
速度を求めるためにここはMATLABで実装しました。
実際にエッジの検出結果は以下のようになりました。

![cup.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104635/e44bd208-525d-0ed3-57cc-eb73de5512fa.png)

実際に撮られるようなプラスチック製のカップはまだまだ調整が必要だなと感じました。
<img width="437" alt="cup2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104635/0adff44e-e545-31ac-fd64-ef39018092a4.png">

間違って背景も検出されてしまうのでYOLOv3などを使ってカップを先に検出してからカップの楕円を検出するなどの技術的工夫は必要そうでした。

<img width="495" alt="cup3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104635/3130f82a-18cf-bd0d-f203-817ae72f6685.png">


## バエる角度にカメラを動かす
バエる角度がわかったらその位置にカメラを動かす必要があります。
これはマニピュレータを作って動かしました。(作った人すごい！)
青いブロックをタピオカとしたときに、円弧を描くように逆運動学を解いて関節の角度を計算します。
これで先ほどのモデルとなる画像と新しく撮るタピオカのバエ角度が一致した場合、写真を撮ります。

![normaldist_kde_anim.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104635/a5599ff2-7cd0-02d9-e896-46eee3ce7976.gif)


## インスタ投稿
Instagramには２つのAPI([旧API](https://www.instagram.com/developer/)と[新API](https://developers.facebook.com/docs/instagram-api/))があります．(※旧APIは2020年には完全廃止予定)

新APIに関してはビジネスアカウント向けとなっているため、そもそも個人のアカウントであれこれできないらしい。
いやいやあるだろと思って探してみたところ、画像を投稿する[Content Publishing API](https://developers.facebook.com/docs/instagram-api/guides/content-publishing)を発見。
しかし、一番上にこんなことが書いてありました。

> The Content Publishing API is in closed beta with Facebook Marketing Partners and Instagram Partners only. We are not accepting new applicants at this time.

要約すると「できない」。

APIではInstagramへのログインができないし、本来やりたかった投稿ができない...。(これができればもっと楽にできた...Twitterにはあるしここら辺どうにかしてほしい...)

人間の手を加えずにインスタに投稿する方法を一旦白紙で考え始め、次に取り掛かったのが[puppeteer](https://github.com/puppeteer/puppeteer)(できれば堂々とAPI叩きたかったがないのでしぶしぶ...泣)。
puppeteerでは、人間が直接GUIを操作することなく、ブラウザ(Chrome)を制御できます。
スクレイピングやE2Eテストでよく使われているものです。
人間がブラウザで行えるほとんどのことがpuppeteerで実現できるため、今回の場合はロボットの手と思ってもいいかもしれません(マニピュレータとか使ってスマホ制御するにもコストが以下略)。

### 気をつけたこと
スクレイピングはできれば避けたかったのですが、仕方ないので法的なギリギリラインで実装することにしました。
ここら辺に関しては初めて行うので取り扱いに十分気をつけつつ、資料や前例などを参考にしました。
場合によっては、刑法上の責任を問われる可能性があるため、事前に入念に調べました(実装よりも大変でした笑)。
もし間違っていることなどがあればご教授いただければと思います。

調べたところによると気をつける必要のある問題は大きく分けて以下の3つ。
1. 著作物
2. Webサイトへの過度なアクセス
3. 利用規約

#### 1. 著作物
そもそも情報の取得に関しては一切行なっていないです。
利用するデータは全て個人で撮影したものに限定しました。
当初はInstagramの`#タピオカ`に投稿されている画像データを使用し、それに類似した画像を撮影し投稿するということをやりたかったのですが、それは[利用規約](https://www.facebook.com/help/instagram/478745558852511)に反するため避けました。

> 不正な方法を用いて、アカウントの作成、情報へのアクセス、または情報の取得を試みることは禁止されています。
これには、弊社から明示的な許可を得ることなく、自動化された手段を用いてアカウントを作成したり、情報を取得したりする行為が含まれます。


#### 2. Webサイトへの過度なアクセス
スクレイピングでどれだけアクセスしたら違法なのか、という基準が見当たらなかったです(ある場合はとても知りたい)。
そのため前例として[岡崎市立中央図書館事件(Livrahack事件)](https://ja.wikipedia.org/wiki/%E5%B2%A1%E5%B4%8E%E5%B8%82%E7%AB%8B%E4%B8%AD%E5%A4%AE%E5%9B%B3%E6%9B%B8%E9%A4%A8%E4%BA%8B%E4%BB%B6)を参考に考えてみました。
この事件では「偽計業務妨害罪」というところが論点になったようで、その罪が成立するかどうかは、スクレイピングによって相手の「業務を妨害したかどうか」が判断基準らしいです。

そのため、ギリギリのラインとなりますが「人間が行えるスピードで投稿(スクレイピング)をする」ことでその業務妨害を避けようと思いました。
当然ですが並列処理などはせず、人間が実際にタイピングをしたり画像を選択するスピードで画像を投稿する、ということです。
連投も控えるようにしました。
これにより、サーバからすれば1ユーザが投稿することと大差がないためここもパスできたと考えます。


#### 3. 利用規約
当然のことではあるが、アカウント作成時に利用規約に同意します。
いくつかの資料を読んでいくと利用規約に「スクレイピング禁止」と書かれているケースがあるらしいです。
実際にInstagramの[利用規約](https://www.facebook.com/help/instagram/478745558852511)を読んでいったところ、一番今回のやりたいことに引っかかりそうなところとして、先にも記述しましたが以下の文章がありました。

> 不正な方法を用いて、アカウントの作成、情報へのアクセス、または情報の取得を試みることは禁止されています。
これには、弊社から明示的な許可を得ることなく、自動化された手段を用いてアカウントを作成したり、情報を取得したりする行為が含まれます。

今回、アカウント作成は人間の手で行なっています。
情報へのアクセス、または情報の取得を試みる行為に関しては一切行なっていません。
自動化された手段を用いて記述されていることも行なっていないので、これに関してもパスはできると考えました。
(そもそも、データ解析などで大量のInstagramのデータを取得して機械学習する、みたいなことをやっていたらアウトなのかもしれませんが、今回に関しては個人の投稿を人間ができるペースでするので、サーバへの負担が一番の論点と考えました)

以上のことから無理のない範囲で実装を行いました。

### 実装
<blockquote class="twitter-tweet" lang="ja"> タピオカミルクティーを自動でインスタ映えさせるやつ作った！(机汚い)<a href="https://twitter.com/wataryooou/status/1204764286226030592"></a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

無事に画像選択から投稿するところまで実装できました。
フローとしては、図に示すようにログインからファイル変更(バエた画像)を検知し、その画像を投稿します。
このファイル検知の部分で先ほどマニピュレータで撮った画像を検知します。

![実装フロー](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104635/410bfa84-0dc7-8c5f-fdb7-aaccabe5def5.png)



申し訳ないですが、実装したコードに関しては不正に利用されることを防ぐため伏せておきます。
気になる人は自分で実装してみてください。ただ利用には十分気をつけてほしいです。
puppeteerとInstagramに関していろいろ言いたいことはあるのですが、それはまた別の機会に譲ろうかと思います。
Web版のInstagramでは画像投稿ができないので、しっかりスマホサイズに落としてからpuppeteerで操作する必要があります！(もしかしてスクレイピング防止のためにWeb版は禁止しているのか？？)

### 失敗パターン
<blockquote class="twitter-tweet" lang="ja"> 見事に失敗したパターン<a href="https://twitter.com/wataryooou/status/1204768770377388037"></a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
しっかり黒幕撮って失敗しました。
エッジ検出周りの精度を上げる必要がありそうです。
あとマニピュレータの剛性がないから作り直し...。
プロトタイプ1号機なので今後とも改良は必要そうです。

## 最後に
実際に手足頭を動かして実装する、という工程でお互いの技術を理解し、実際にモノを作り上げられたのは楽しかったです。
リアルを相手にすると、重力とか光とか諸々を考慮して作っていかないといけないのでモノづくりは奥深いし楽しいなと思いました！
実装するだけでなくこのように公開するにあたって、なるべくわかりやすく説明するために過去の事例や流行り、法律周りも調べたのですが、その過程で普段気づけなかったいろんなことに触れられて非常に勉強になりました。
またひょんなことから何かしたいなと思います。

最後に、協力してくれたIくん、Kくん、Sくんに感謝！