ChatGPTを使ってLINE Botを映画監督に！？

皆さん、「映画を作ってみたい！」とか「映画の主人公になってみたい！」なんて思ったことありませんか？
僕は小さいころ自分を主人公にした映画を想像して作ったりしてました:joy_cat:
でも物語を考えるのって、ストーリーのアイデアがなかなか出てこないし、センスのいいタイトルをつけるのだって難しいですよね。
しかし！最近のAI技術を使えば、それっぽいことができるんじゃね？っていうのがこの記事の内容です。
今回つくったものをとりあえずお見せすると。。。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633178/a367963e-68bb-cddc-8ef7-2c8de9dee67c.png)
こんなかんじで、画像を送るとその画像をジャケットとする映画の「タイトル」と「あらすじ」を作ってくれるというものです。
技術力は皆無ですが、そこそこ面白いものができたと思ってるので温かい目で見ていただけると幸いです！

# 1. システムの流れ
最初に、どのような流れで画像からタイトルとあらすじを生成しているかを簡単に説明します。
システムを構成する重要な要素は「CLIP-interrogator」と「ChatGPT」の2つです。
## 1.1 CLIP-interrogator
CLIP-interrogatorというのは、画像を入力すると、その画像を表す文字列（正確に言うとプロンプト）を出力するというものになります。
Image-to-Text（画像からテキストへ）の深層学習モデルということになります。
このページで簡単に試すことができます。

https://huggingface.co/spaces/pharma/CLIP-Interrogator

例えば、この画像↓を入力することとします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633178/78492548-9518-c108-3f02-9f17876a4a83.png)

このとき、CLIP-interrogatorの出力はこんな感じ↓

```
an aerial view of a city at night, ehime, the photo shows a large, srgb, android format, standing on mountain, future miramar, inspired by Ras Akyem, high definition photo, website
（夜の街の空撮、愛媛、写真には大きな、srgb、アンドロイドフォーマット、山に立つ、未来のミラマー、ラスアキエムをイメージ、高精細写真、ウェブサイト）
```
写真に何が写っているか、どんな雰囲気かなどをテキストで表現してくれます。

## 1.2 ChatGPT
ChatGPTは、説明しなくてもわかる人が多いかもしれません。
簡単にいうとめちゃめちゃ頭のいいText-to-Text（文字から文字へ）モデルですね。
正確に言うとgpt-3.5-turboというモデルのことを使うんですが、本記事ではわかりやすくChatGPTと呼ぶこととします。
超頭がいいので、文字列を渡していろいろとお願いすれば、大抵のことをやってくれます。
なので、大体何でもできるChatGPTにお願いして、映画のタイトルやあらすじを作ってもらいます。

## 1.3 全体図
察しのいい人はもうほとんどお分かりかと思いますが、CLIP-interrogatorの出力をChatGPTに渡して、何とかしてもらおう！というのが今回の肝です。
図にするとこんな感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633178/e194875c-175b-31fe-d0fa-665d69ea63e2.png)

まず画像をCLIP-interrogatorに入力して、画像を表現するテキストに変換。
そしてChatGPTにそのテキストを渡して、タイトルとあらすじを作るようにお願いするといった感じです。


# 2. Botの動作の記述
LineBotを作成するためには、PythonでBotの動作を記述する前にDeveloperのサイトでBotを作ったり、トークンを取得したりといろいろやることがあります。
しかし、
・僕はそのあたりを分かりやすく説明する自信がない
・調べれば先人たちがめちゃめちゃわかりやすい記事をたくさん残してくれている
・なんならChatGPTに大体教えてもらえる
という理由から今回はBotの動作を記述したコードのみ載せたいと思います。
今回の肝となるBotの動作を記述した関数は以下のようになります。

```python
# CLIP-interrogatorのインスタンスを生成
ci = Interrogator(Config(clip_model_name="ViT-L-14/openai"))

# 画像を受け取って映画のタイトルとあらすじを出力する関数
@handler.add(MessageEvent, message=ImageMessage)
def handle_image(event):
    #画像ファイルを保存
    message_id = event.message.id
    message_content = line_bot_api.get_message_content(message_id)
    with open(Path(f"input.jpg").absolute(), "wb") as f:
        for chunk in message_content.iter_content():
            f.write(chunk)
    
    line_bot_api.push_message(event.source.user_id, TextSendMessage(text="現在、映画を製作中です..."))
    # 画像の読み込み
    image = Image.open('input.jpg')
    # CLIP-interrogatorを使って画像を表現するテキストを取得
    text = ci.interrogate(image)
    # ChatGPTのインスタンスを作成し、タイトルとあらすじを作るようにお願いする
    movietitlemaker = ChatGPT(system_setting="あなたは映画作成者です。受け取った英文はある画像の内容を表したプロンプトです。
    この画像をパッケージとする映画を作るときにワクワクするようなタイトルと100文字程度のあらすじを日本語で生成して出力してください。")
    # ChatGPTのインスタンスにCLIP-interrogatorが出力したテキストを入力
    movietitlemaker.input_message(text)
    # ChatGPTの返答（タイトルとあらすじ）を取得
    movie = movietitlemaker.input_list[-1]["content"]
    # タイトルとあらすじをメッセージでユーザに送信
    line_bot_api.push_message(event.source.user_id, TextSendMessage(text=movie))
```

CLIP-interrogatorは以下を参考にするとライブラリとして簡単に使うことができます。（便利すぎ！）

https://github.com/pharmapsychotic/clip-interrogator

ChatGPTはOpenAIのAPIで利用しています。以前作成した記事で使い方を軽く触れているので分からない方は参考にどうぞ。

https://qiita.com/yodystudying/items/61ef9fa108b4a06cd132

<details><summary>コードの全容はここでみれます</summary><div>

```python:app.py
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextMessage, ImageMessage, TextSendMessage, ImageSendMessage
from flask import Flask, request, abort
from dotenv import load_dotenv
import os
from pathlib import Path
from clip_interrogator import Config, Interrogator
from PIL import Image
import openai

app = Flask(__name__)

# 環境変数を .env ファイルから読み込む
load_dotenv()

# LINE Developers のチャネルシークレットとアクセストークンを環境変数から取得
YOUR_CHANNEL_ACCESS_TOKEN = os.getenv('YOUR_CHANNEL_ACCESS_TOKEN')
YOUR_CHANNEL_SECRET = os.getenv('YOUR_CHANNEL_SECRET')

# OpenAI API の API キーを環境変数から取得
API_KEY = os.getenv('API_KEY')

# LINE Messaging API のインスタンスを生成
line_bot_api = LineBotApi(YOUR_CHANNEL_ACCESS_TOKEN)

# LINE Messaging API からのWebhookの署名検証に使うインスタンスを生成
handler = WebhookHandler(YOUR_CHANNEL_SECRET)

# OpenAI API の API キーを設定
openai.api_key = API_KEY


class ChatGPT:
    def __init__(self, system_setting):
        # システム設定として与えられたメッセージを用意する
        self.system = {"role": "system", "content": system_setting}

        # これまでに入力されたメッセージのリスト
        self.input_list = [self.system]

        # OpenAI API で返されたログのリスト
        self.logs = []

    def input_message(self, input_text):
        # ユーザーが入力したテキストをリストに追加する
        self.input_list.append({"role": "user", "content": input_text})

        # OpenAI API による応答を取得する
        result = openai.ChatCompletion.create(
            model="gpt-3.5-turbo", messages=self.input_list
        )

        # OpenAI API から返されたログをリストに追加する
        self.logs.append(result)

        # 応答をリストに追加する
        self.input_list.append(
            {"role": "assistant", "content": result.choices[0].message.content}
        )


# CLIP-interrogatorのインスタンスを生成
ci = Interrogator(Config(clip_model_name="ViT-L-14/openai"))

# LINE Messaging API からの Webhook を受け取る関数
@app.route("/callback", methods=['POST'])
def callback():
    # LINE Messaging API から送られた HTTP リクエストの署名を取得
    signature = request.headers['X-Line-Signature']

    # HTTP リクエストのボディを取得
    body = request.get_data(as_text=True)

    try:
        # LINE Messaging API から送られたリクエストが正当であるか署名を検証
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)
    return 'OK'

# 画像を受け取って映画のタイトルとあらすじを出力する関数
@handler.add(MessageEvent, message=ImageMessage)
def handle_image(event):
    #画像ファイルを保存
    message_id = event.message.id
    message_content = line_bot_api.get_message_content(message_id)
    with open(Path(f"input.jpg").absolute(), "wb") as f:
        for chunk in message_content.iter_content():
            f.write(chunk)
    
    line_bot_api.push_message(event.source.user_id, TextSendMessage(text="現在、映画を製作中です..."))
    # 画像の読み込み
    image = Image.open('input.jpg')
    # CLIP-interrogatorを使って画像を表現するテキストを取得
    text = ci.interrogate(image)
    # ChatGPTのインスタンスを作成し、タイトルとあらすじを作るようにお願いする
    movietitlemaker = ChatGPT(system_setting="あなたは映画作成者です。受け取った英文はある画像の内容を表したプロンプトです。
    この画像をパッケージとする映画を作るときにワクワクするようなタイトルと100文字程度のあらすじを日本語で生成して出力してください。")
    # ChatGPTのインスタンスにCLIP-interrogatorが出力したテキストを入力
    movietitlemaker.input_message(text)
    # ChatGPTの返答（タイトルとあらすじ）を取得
    movie = movietitlemaker.input_list[-1]["content"]
    # タイトルとあらすじをメッセージでユーザに送信
    line_bot_api.push_message(event.source.user_id, TextSendMessage(text=movie))
    
if __name__ == "__main__":
    app.run()
```
</div></details>

# 3. 実際に使ってみた！
それでは夜景以外にも何枚か画像を送ってみましょう！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633178/6483dff7-f358-dc6c-e18b-66185d7acbda.png)
そんなストーリーあるか？って感じですが、なんとなくそれっぽくはなってますね:sweat_smile:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633178/a4d82bce-d088-dc37-667a-cd656e24534d.png)
これはなんか、迫力満点なスキー映画になりそう？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633178/c49fdf58-ae27-7f24-f202-9695ed9bb325.png)
なんか難しそうだけど面白そう:sunglasses:


このように見てみると、それっぽいタイトルとあらすじができていますね。
あらすじは結構意味不明なところもありますが、タイトルはめちゃめちゃかっこいいですね。

# 4. おまけ
せっかくなのでChatGPTに映画の評論家になってもらう機能も追加しました。
仕組みとしては、ChatGPTで生成したタイトルとあらすじをさらにChatGPTに入力して、評論をするようにお願いするといったものです。

変更後の記述は以下の通りです
```python
# 画像を受け取って映画のタイトルとあらすじを出力する関数
@handler.add(MessageEvent, message=ImageMessage)
def handle_image(event):
    #画像ファイルを保存
    message_id = event.message.id
    message_content = line_bot_api.get_message_content(message_id)
    with open(Path(f"input.jpg").absolute(), "wb") as f:
        for chunk in message_content.iter_content():
            f.write(chunk)
    
    line_bot_api.push_message(event.source.user_id, TextSendMessage(text="現在、映画を製作中です..."))
    # 画像の読み込み
    image = Image.open('input.jpg')
    # CLIP-interrogatorを使って画像を表現するテキストを取得
    text = ci.interrogate(image)
    # ChatGPTのインスタンスを作成し、タイトルとあらすじを作るようにお願いする
    movietitlemaker = ChatGPT(system_setting="あなたは映画作成者です。受け取った英文はある画像の内容を表したプロンプトです。
    この画像をパッケージとする映画を作るときにワクワクするようなタイトルと100文字程度のあらすじを日本語で生成して出力してください。")
    # ChatGPTのインスタンスにCLIP-interrogatorが出力したテキストを入力
    movietitlemaker.input_message(text)
    # ChatGPTの返答（タイトルとあらすじ）を取得
    movie = movietitlemaker.input_list[-1]["content"]
    #ChatGPTのインスタンスを作成し、タイトルとあらすじから映画を評価するようにお願いする
    critic = ChatGPT(system_setting="あなたは映画評論家です。受け取った映画のタイトルとあらすじから映画を評価し、70文字程度で評価を述べてください。
    また、100点満点で点数をつけてください。出力は【点数】の表記から始めて、評価の文章は【評価】で始めてください。")
    #ChatGPTのインスタンスに、タイトルとあらすじを入力
    critic.input_message(movie)
    #評価コメントを取得
    criticism = critic.input_list[-1]["content"]
    # タイトルとあらすじをメッセージでユーザに送信
    line_bot_api.push_message(event.source.user_id, TextSendMessage(text=movie))
    #評論コメントをメッセージでユーザに送信
    line_bot_api.push_message(event.source.user_id, TextSendMessage(text=criticism))
```

図にするとこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633178/14a44d76-45fb-3e28-d4ab-9548fd98feed.png)
さっきのシステムの最後に付け加えただけですね。

では早速実際に使ってみましょう！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633178/61dde1c3-06c5-21e9-ae89-90ef79e961b0.png)

結構それっぽいことを言っているし、なかなか辛辣なコメントなのがおもしろいですね:joy_cat:
点数もつけてくれるので、高得点を目指してたくさん試してみたくなりました:relieved:


# 5. 最後に
今回は画像から映画のタイトルとあらすじを作るLineBotを作りました！
個人的には割と面白いものを作れて満足しています。
ただあらすじは結構な頻度で意味の分からない場合があるため、改良の余地ありです。
考えられる改善策は、
・ChatGPTのsystem_settingで与えるプロンプトを工夫
・CLIP-interrogator(プロンプトを出力)以外のキャプションを生成するモデルを使用
といったところです。
2つ目のキャプションを生成するモデルに置き換えるのはかなり有効な可能性があるので、今度やってみたいですね。