ChatGPTを用いた画像解析LINE botを作ってみた。

ChatGPTを用いたLINE botに目(画像認識)をつけようと開発していたら、思ったより単体でChatGPTを用いた画像解析(解説)は面白かったので、単体でツールにしてみました。

作ったツールは、LINEにユーザーが画像を送ると[Cloud Vision API](https://console.cloud.google.com/apis/library/vision.googleapis.com)が画像をテキスト化して、そのテキストを元にChatGPTが画像を解説して、LINEに返すツールです。

今回は長期的に実行中にしてもしなくても良いようなツールなので、手軽に使えるGoogle colabを使用します。(コード自体は、インスタンスでも機能します。)

手順は以下です。
1. Cloud Vision APIを有効化する
2. ChatGPTを用いた画像解析LINE botを実行中にする

# 1. Cloud Vision APIを有効化する
Cloud Vision APIを有効化する手順は以下です。
1. [Google Cloud Console](https://console.cloud.google.com/) にアクセスし、プロジェクトを作成または選択します。
2. [Cloud Vision APIページ](https://console.cloud.google.com/apis/library/vision.googleapis.com)に移動し、「有効にする」をクリックして API を有効化します。
3. 必要に応じて同意画面を設定します。
4. 画面左側のメニューから「認証情報」をクリックし、「認証情報を作成」を選択して「サービスアカウント」をクリックして、必要事項を記入してサービスアカウントを作成します。
5. 作成したサービスアカウントの編集ページを開いて、キーのタブを開いて、JSONのキーを作成してダウンロードします。
6. 作成したサービスアカウントのキーをGoogle colabで使用するアカウントのGoogleドライブに入れます。

# 2. ChatGPTを用いた画像解析LINE botを実行中にする
まず、Google colabで新規ノートブックを開き、Google colabからGoogle ドライブにアクセスして、サービスアカウントのキーを読み込めるように以下の操作を行います。

ファイルを開く
写真の赤枠のマークを押す。
![ECEC3A64-9001-4920-BBB8-BB2D6042E2F0.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3287642/621e164a-9581-d55e-b1f2-700416311558.jpeg)

Googleドライブをマウントする
写真の赤枠のマークを押す。メニューが開いたら、ドライブに接続を押す。
![75EE7CC2-6328-4BFC-99DB-EE8025D72621.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3287642/0bf7e31c-6f23-1b30-b897-72924a71809b.jpeg)

次に、以下をインストールします。
```
!pip install --upgrade pip setuptools wheel openai google-cloud-vision line-bot-sdk flask pyngrok
```

最後に、先程Googleドライブに入れたサービスアカウントのキーのパス、LINE、ngrok、openai APIのキーやトークンを以下のコードに入れてから、実行します。
※LINEとngrokの設定は[こちら](https://qiita.com/katsuki_ono/items/df8830c8b07e3a59f354)を参考にしてください。
```python
# LINEのチャネルシークレット
LINE_CHANNEL_SECRET = ''
# LINEのチャネルアクセストークン
LINE_ACCESS_TOKEN = ''
# ngrokのAuthtoken
NGROK_AUTHTOKEN = ''
# openaiのキー
OPENAI_API_KEY = ""


import os

from pyngrok import ngrok
from pyngrok.conf import PyngrokConfig

from flask import Flask, request, abort

from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextMessage, ImageMessage

import openai

from google.cloud import vision


# Google APIのキーのパス
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = ""

# URLの生成
ngrok.set_auth_token(NGROK_AUTHTOKEN)
os.system('kill -9 $(pgrep ngrok)')
https_tunnel = ngrok.connect(addr='127.0.0.1:5000',bind_tls=True)
print(https_tunnel)


app = Flask(__name__)


openai.api_key = OPENAI_API_KEY
line_bot_api = LineBotApi(LINE_ACCESS_TOKEN)
handler = WebhookHandler(LINE_CHANNEL_SECRET)


@app.route("/test")
def test():
    return "TEST OK"

@app.route("/", methods=['POST'])
def callback():
    # get X-Line-Signature header value
    signature = request.headers['X-Line-Signature']

    # get request body as text
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    # handle webhook body
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        print("Invalid signature. Please check your channel access token/channel secret.")
        abort(400)

    return 'OK'


@handler.add(MessageEvent, message=ImageMessage)
def handle_image(event):
    message_content = line_bot_api.get_message_content(event.message.id)

    client = vision.ImageAnnotatorClient()
    image = vision.Image(content=message_content.content)
    response = client.annotate_image({
        'image': image,
        'features': [{'type_': vision.Feature.Type.LABEL_DETECTION},
                     {'type_': vision.Feature.Type.IMAGE_PROPERTIES},
                     {'type_': vision.Feature.Type.FACE_DETECTION},
                     {'type_': vision.Feature.Type.OBJECT_LOCALIZATION},
                     {'type_': vision.Feature.Type.LANDMARK_DETECTION}],
    })

    labels = ', '.join([label.description for label in response.label_annotations])
    colors = ', '.join([f'rgb({color.color.red}, {color.color.green}, {color.color.blue})' for color in response.image_properties_annotation.dominant_colors.colors])
    faces = 'Faces detected.' if response.face_annotations else 'No faces detected.'
    objects = ', '.join([f'{obj.name} at {obj.bounding_poly}' for obj in response.localized_object_annotations])
    landmarks = ', '.join([landmark.description for landmark in response.landmark_annotations])

    description = f'Labels: {labels}. Dominant colors: {colors}. {faces} Objects: {objects}. Landmarks: {landmarks}.'

    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "芸術評論家として、以下の一つの作品の作品情報からこの作品を日本語で評価してください。"},
            {"role": "user", "content": description},
        ]
    )
    output = response["choices"][0]["message"]["content"] 

    line_bot_api.reply_message(
        event.reply_token,
        TextMessage(text=output)
    )

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

実行したら、生成されたURLを取得して、LINE Developers のダッシュボードでwebhook URLを設定します。(詳しくは、[こちら](https://qiita.com/katsuki_ono/items/df8830c8b07e3a59f354)を参考にしてください。)

webhook URLを設定後、LINE Developersで検証を押します。
成功と表示されたら、上手く接続出来ています。(Google colab側では、上手くいっている場合は200と返されます。500の場合、プログラムに問題ありなどなので、普通のエラーと同じです。)

ここまで出来たら、LINE  botを友達登録して画像を送信してみて下さい。
以下のように送信した画像へのコメントがLINEに返ってきたら成功です！
![IMG_8323.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3287642/c2167a1c-9b48-3c07-cb58-7d8ecc478bb9.jpeg)

# 最後に
元々、Stable Diffusionが好きで、1000枚程お気に入りの絵があるので、これを気が向いた時に、ChatGPTに解説させて遊んでみようと思います。
また何か作ってみます。