Watsonによる画像認識でシンゴジラ風字幕を自動生成するwebサービスを作ってみた

# はじめに
簡単なwebサービスを作成してみたいなという思いから作成しました。
![スクリーンショット 2018-09-05 22.35.46.png](https://qiita-image-store.s3.amazonaws.com/0/275308/a4573e1b-7813-f021-cc94-453d2ab50356.png)
https://set-subtitle.herokuapp.com/
herokuの無料枠＋watsonの無料枠なので、途中で動かなくなっちゃう可能性があります。
内容はアップロードした画像に対して画像認識を行い、認識した画像タグをシンゴジラっぽい字幕で表示するというものです。
最初はテキストと画像をアップロードしたら字幕を入れるだけというものを考えていたのですが、すでにそのようなサービスが存在していたので若干工夫して画像認識をいれてちょっと違うサイトにしました。
シンゴジラを題材にした理由は、アマゾンプライムで見返してシンゴジラ熱が上がったためです。


# 開発環境
今回はあまり時間をかけずにサクッと作ることを目標にしたので、Flaskを採用しました。 
*  Windows10 Pro
*  Python 3.6.5
*  Flask 1.0.2

# 画像に字幕を追加
まずは画像に文字を入れていくことから始めていきました。
調べてみたところ、日本語で画像に文字を入れるためにはPillowを使う必要があるということがわかったので、下記記事を参考に文字を入れていきました。
https://www.tech-tech.xyz/archives/drawtext.html

文字をシンゴジラ風に表示するために、以下の2点を行いました。
1. フォントをシンゴジラっぽいものに変更
2. 文字の表示位置を画像下側にする

まず、シンゴジラに使われているフォントを調べてみたところHGP明朝Eというフォントが
同じもしくはかなり近いフォントみたいなのでそれを利用することにしました。
フォントサイズは最大で画面の10分の１になるようにしました。

表示位置については画像の大きさを取得して真ん中に表示されるように調整しました。


``` python
from PIL import Image, ImageDraw, ImageFont

fontPath = "./HGRME.TTC"
text = "ゴールデンレトリバー"
img_file = Image.open("sample.jpg")
img_file = img_file.point(lambda x: x*0.8)
width,height = img_file.size
draw = ImageDraw.Draw(img_file)
text_len = len(text)
size = height / 10
if text_len * size > width:
    size = width / text_len
font = ImageFont.truetype(fontPath, int(size))
draw.text((width / 2 - text_len/2 * int(size), height -int(size) - 5), text, fill=(255, 255, 255), font=font)
img_file
```
> img_file = img_file.point(lambda x: x*0.8)

この部分はフォントが白のため画像が明るすぎると文字が見づらい問題があったため、画像を少し暗くする処理を入れました。
シンゴジラっぽい感じで字幕が入った気がします
![スクリーンショット 2018-08-23 20.56.43.png](https://qiita-image-store.s3.amazonaws.com/0/275308/3f2c2ac9-e929-1bd9-e469-b6f75b022818.png)


## 画像表示webサービスを作成
字幕を入れる仕組みができたので、アップロードした画像に字幕を入れる仕組みを作っていきます。
とりあえず簡易的に動くことを目標に、ここでは画像認識をせずに決め打ちの文字を表示するようにしました。
flaskを使ったアップローダを作っている方がいらっしゃったので参考に作りました。
https://qiita.com/Gen6/items/f1636be0fe479f42b3ee

```  python:main.py
UPLOAD_FOLDER = './uploads'
app = Flask(__name__, static_url_path='', static_folder='./static')
app.config['JSON_AS_ASCII'] = False
app.secret_key = 'test'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
pic = Picture()

@app.route('/', methods=['GET'])
def start():
    return render_template('index.html')

@app.route('/send', methods=['GET', 'POST'])
def send():
    if request.method == 'POST':
        imgFile = request.files['img_file']
        if imgFile:
            filename = secure_filename(imgFile.filename)
            imgFile = writeDescription(imgFile)
            imgFile.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
            imgUrl = "/uploads/" + filename 
            return render_template('index.html',url=imgUrl)
        else:
            return render_template('index.html',fobidden = True)
    else:
        return redirect(url_for('index'))

@app.route('/uploads/<filename>')
def uploaded_file(filename):
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)

def writeDescription(img_file):
    fontPath = "./HGRME.TTC"
    text = "テキストサンプル"
    img_file = Image.open(img_file)
    img_file = img_file.point(lambda x: x*0.8)
    width,height = img_file.size
    draw = ImageDraw.Draw(img_file)
    text_len = len(text)
    size = height / 10
    if text_len * size > width:
        size = width / text_len
    font = ImageFont.truetype(fontPath, int(size))
    draw.text((width / 2 - text_len/2 * int(size), height -int(size) - 5),text, fill=(255, 255, 255), font=font)
    return img_file

if __name__ == '__main__':
    app.run()
```


# 画像から字幕を自動生成
## Watson Visual Recognitionによる画像認識
webサービスっぽい動きをするようになったので、画像を認識して字幕を生成する機能を追加してみることにしました。
最初は、pytorchを利用して画像識別を行おうとしたのですが、herokuの無料枠だとメモリーが不足してしまいました。
なので代案として、今回はWatsonのVisual Recognitionを採用しました。
採用した理由は日本語で画像のタグを取得できるため、そのまま字幕として張り出せるためです。
公式のAPIリファレンスがかなり丁寧だったので、基本的に難なく利用することができました。
ただ、日本語にするためのaccept_languageのパラメータを入れる場所を間違えて少しだけ苦労していました。。

画像をアップロードして、画像のタグを取得するということをここでは行いました。
公式リファレンス：https://www.ibm.com/watson/developercloud/visual-recognition/api/v3/python.html?python#introduction

```  python:Watson.py
import os
import json
from watson_developer_cloud import VisualRecognitionV3

class Watson:
    def __init__(self):
        self.visual_recognition = VisualRecognitionV3(
        '2018-03-19',
        iam_api_key=os.environ['WatsonAPI'])

    def predictImage(self,img):
        classes = self.visual_recognition.classify(
            img,
            accept_language='ja',
            parameters = json.dumps({
                'threshold':0.8,
                'classifier_ids': ["default"]
            }))

        return classes["images"][0]['classifiers'][0]['classes'][0]['class']
```

とりあえず、ひとつのタグを取得できればいいので一番確率の高いクラスを取得するようにしました。


# 見た目を整える
一応動くようになったのですが、見た目がちょっと寂しいのでフレームワークを利用して少し整えました。
フレームワークにはmaterializeを利用しました。
https://materializecss.com/
シンゴジラの広告が赤色と黒色を基調に作られていたので、この２つの色をベースにしていきました。
気持ちシンゴジラっぽくなったのかな…？と思っています
![スクリーンショット 2018-09-21 18.42.04.png](https://qiita-image-store.s3.amazonaws.com/0/275308/b85ea0b8-99d9-f60a-caaf-c82940402c79.png)


# 表示する字幕を少し工夫する
画面下にHGP明朝Eで字幕を入れただけでもシンゴジラっぽくなったのですが、もう少しシンゴジラっぽさを出せないかな？と考えながらシンゴジラを見返していると表示される字幕には人名と役職が表示されることが多いことに気づきました。
そこで、シンゴジラの映画内で表示された役職の中からランダムで一つ選択し、認識したタグの最後に付け足すということを行いました。
シンゴジラの字幕を書き起こしている方がいたので、そちらを参考にしました。
http://nyanyako.xyz/shin-godzilla-telop-1
スクレイピングして抜き出してくることも考えたのですが、役職だけだったら手動で抜き出してもいけるのでは？と思い手動で書き出していきました。
書き忘れがなければ、役職は71種類ありました。結構ありました。

# Herokuにdeploy
ローカルで動かすことに成功したので、最後にherokuにdeployを行いました。
herokuを選んだ理由としては、以前herokuを使った経験があったことから選択しました。
herokuにはDockerを利用してdeployしました。
https://devcenter.heroku.com/articles/container-registry-and-runtime


``` Dockerfile:Dockerfile
FROM heroku/heroku:16

RUN apt-get update && apt-get install -y \
    python3-pip

RUN pip3 install --upgrade pip
RUN pip3 install flask==1.0.2
RUN pip3 install gunicorn==19.7.1
RUN pip3 install Pillow==5.1.0
RUN pip3 install numpy==1.14.3
RUN pip3 install --upgrade "watson-developer-cloud>=1.3.4"

ADD . /opt
WORKDIR /opt

CMD gunicorn main:app --log-file=-
```
# ソースコード
https://github.com/deaju/subtitles

# 最後に
電車の画像をあげると種類を識別して無人在来線爆弾の字幕を付けるものもやりたかったのですが、電車のデータセットがすぐに見つからなかったので、今回は断念しました。
データセットを学習して、新たなモデルを作成すること自体はできそうな感じがするので今後やってみてみたいなと思っています。
あと、キルラキル風に字幕を表示するのもやってみたいです。