誰もがshowwinになれるSlack Botを作った

ぼくは @showwin で、ネット上ではこのアイコン画像で活動しているのですが、みんなもこの画像になりたいと思っているだろうと思って、だれでもこのアイコンになれるSlack Botを作ったので、その話をします。
 ![](https://pbs.twimg.com/profile_images/1746238832/_______400x400.png)

こんな感じでshowwinになれます。
![](https://user-images.githubusercontent.com/1732016/46596034-f7fb6680-cb15-11e8-8670-69105dec1c20.png)

成果物は https://github.com/showwin/make-me-showwin においてあります。実際に動いているもので遊びたい場合にはぜひ [scouty](https://scouty.co.jp/) に入社してください！！

## 使った技術
* Python 3.6
* AWS Rekognition
* Pillow

Rekognition の精度がすばらしくて、自分で顔認識のモデル等用意しなくても良い時代になったのが嬉しいですね。
3,4年前にも趣味で顔認識で少し遊んでいたころがあったのですが、当時はOpenCVを使っていて、少し顔が横を向いているだけで認識してくれなかったりと結構苦労した思い出があります。

## 処理の流れ概要

1. Slackでアップロードされた画像をダウンロード
1. 画像をAWS Rekognitionに投げて、写っている顔の数だけ座標を取得
1. 抽出した顔をshowwinアイコンの顔の部分に合致するように切り取り
1. 切り取った画像をshowwinアイコンの顔部分に貼り付ける
1. 完成した顔をSlackに投稿する

です。
これらの処理が全部で140行ぐらいでサッと書けてしまうので、技術のコモディティ化が進んでいるなぁとしみじみ感じますね。

### 1. Slackでアップロードされた画像をダウンロード
Python の [lins05/slackbot](https://github.com/lins05/slackbot) を使うとこんな感じでSlackに投稿された画像をダウンロードして、ファイルに保存ができます。

```python
import shutil

from slackbot.bot import default_reply


@default_reply
def make_me_showwin(message):
    if 'files' not in message.body:  # ファイルかどうか判断
        return message.reply(DEFAULT_REPLY)
    url = message.body['files'][0]['url_private']  # これで画像のURLが取得できる
    resp = requests.get(url,
                        headers={'Authorization': 'Bearer ' + SLACK_TOKEN},
                        stream=True)
    f = open(TMP_FILE, "wb")
    shutil.copyfileobj(resp.raw, f)
    f.close()
```

### 2. 画像をAWS Rekognitionに投げて、写っている顔の数だけ座標を取得

画像ファイルをS3にアップロードして、Rekognitionに投げます。
これだけで画像に写っている顔全ての座標が取れるのすごい。。

```Python
import boto3

# S3にアップロード
s3 = boto3.resource('s3')
s3.Bucket(S3_BUCKET).upload_file(LOCAL_FILE_PATH, S3_FILE_PATH)

# これだけで顔認識が完了する…!!
client = boto3.client('rekognition')
response = client.detect_faces(Image={
    'S3Object': {
        'Bucket': S3_BUCKET,
        'Name': S3_FILE_PATH
    }}, Attributes=['ALL'])

# 見つけられた顔の数はこんな感じで取れます
face_count = len(response["FaceDetails"])

coordinate_list = []  # 顔の座標を格納したリスト
for face_info in response['FaceDetails']:
    coordinate_list.append(face_info['BoundingBox'])
```

ここで取れる顔の座標は、上が目のすぐ上あたり、下は口のすぐ下辺りを示しているのですが、今回はおでこあたりも画像として使いたいので、返ってきた座標から少しズームアウトした座標に変換しています。
この辺りで細かな調整をしています。
https://github.com/showwin/make-me-showwin/blob/master/plugins/image.py#L41-L57
コメントに `# 顔の上を元画像の 7/48 だけ広く取る` とか書いてあって微調整に微調整を重ねた結果が見て取れますねw

### 3. 抽出した顔をshowwinアイコンの顔の部分に合致するように切り取り

ここは特に面白いことはしていなくて、顔の縦横比は人によって変わるので、それをshowwinの顔のサイズに合わせて横を切り取る作業をします。
コードでいうとこの辺です。
https://github.com/showwin/make-me-showwin/blob/master/plugins/image.py#L60-L73

"showwinの顔のサイズ" と言っているものはこれで、
![](https://github.com/showwin/make-me-showwin/blob/master/data/mask.png?raw=true)

抽出した顔画像をこの白い部分で切り取ることになるので、縦横比をこの画像と一致させる必要があるのです。


### 4. 切り取った画像をshowwinアイコンの顔部分に貼り付ける

最終的な成果物を得るには3つの画像が必要です。下のコードで使っている名前で紹介すると、


`SHOWWIN_BASE` : 顔だけ切り抜いたshowwinアイコンの元画像
<img src="https://github.com/showwin/make-me-showwin/blob/master/data/showwin.png?raw=true" width=300/>

`MASK`: 上に張った顔の形をした白黒画像
`face`: 縦横比をMASKと揃えた人間の顔画像

この3点が揃えば、以下の3行で人間の顔をしたshowwinアイコン画像が作成できます。

```python
from PIL import Image

base = Image.open(SHOWWIN_BASE)
base.paste(face, (142, 273), Image.open(MASK))  # 貼り付ける場所の左上の座標
base.save(SAVING_FILE_PATH)
```


### 5.完成した顔をSlackに投稿する
この1行で先程作った画像をSlackに投稿することができます。 `slackbot` 便利すぎる！

```python
message.channel.upload_file('Slackに投稿するファイル名', SAVING_FILE_PATH)
```

## おわりに

ざっくりと一連の流れを説明しましたが、主要な部分ほどコードが短く、微調整のために少しゴニョゴニョとコードを書くという感じになっているのが伝わったと思います。
やりたいことがすぐに実現できる世の中になって便利だなーと思う一方、深くまで潜らなくても実現したいことができてしまって、「趣味で色々作っていたら気づかない間に技術力が高まっていた！」みたいなことは起きにくくなっているのかなとも感じます。
どちらの世の中が良いのか難しいところですね。ぼくは技術そのものよりも、ものを作って遊ぶほうが好きなので、中身を理解できなくても実現できてしまう世界のほうが好きですが。