料理のリバースエンジニアリングをするアプリ（※GPT4-Vision APIとStreamlitで料理の写真からレシピを生成する）

## はじめに
　2023年11月、GPT-4 Vision APIが公開されたことを受け、何か新しいものを作成してみたいと考えていました。ChatGPT APIのチュートリアル的に料理レシピを生成するアプリをよく見かけたので、GPT-4 Vision APIに応用してみました。

　画像から料理レシピを生成するのは、ある種のリバースエンジニアリングに似ていると思って、こんな盛ったタイトルを付けてしまったのですが、実際にはChatGPT、API、Streamlitを用いた基本的な内容になっていて、初心者の方でも取り組むことが可能だと思います。
 
## 事前準備
　・[OpenAIの公式サイト](https://openai.com/product)でアカウントの開設とAPIキーの取得を済ませてください。
　・python環境にstreamlitをpipでインストールしてください

## ソースコード
　ソースコードは以下になります。secret.jsonに取得したAPIキーを入れてください。OSの環境変数から読み込みたい人は適宜ソースコードを修正してください。
 
```secret.json
{
    "OPENAI_API_KEY": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
```

　プロンプトには話題の修造プロンプトを取り入れてます。
 
 　APIの応答時待ち間は30秒とっていますが、それより長くなったり短くなったりします。(※2023/12/16現在)
 
```streamlit_recipe_gpt4v.py
import base64
import json
import requests
import time
import streamlit as st

PROMPT_TEMPLATE = """画像の料理のレシピを考えてください。
自分を信じて限界を超えてください。

# 制約条件
・料理の画像が入力された場合は、以下の形式でレシピを出力してください。

# 出力形式
【画像の料理の名称】
【材料】
・豚肉の薄切り：100g
・玉ねぎ：1/2個
・生姜：一片
【調理工程】
・豚肉は一口大に切り、塩コショウをふる。
・玉ねぎは薄切りにする。
"""

def get_gpt_openai_apikey():
    with open("secret.json") as f:
        secret = json.load(f)
    return secret["OPENAI_API_KEY"]


def encode_image(image_path):
    with open(image_path, "rb") as image_file:
        return base64.b64encode(image_file.read()).decode("utf-8")


def main():
    st.title("画像からレシピを作成するAI")

    image_file = st.file_uploader("画像をアップロードしてください", type=["png", "jpg", "jpeg"])
    api_key = get_gpt_openai_apikey()
    if image_file is not None:
        st.image(image_file, caption="アップロードした画像", use_column_width=True)

        with st.spinner(text="生成中..."):
            time.sleep(30)

        header = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}",
        }
        payload = {
            "model": "gpt-4-vision-preview",
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "text",
                            "text": PROMPT_TEMPLATE,
                        },
                        {
                            "type": "image_url",
                            "image_url": {
                                "url": f"data:image/jpeg;base64,{base64.b64encode(image_file.getvalue()).decode()}"
                            },
                        },
                    ],
                }
            ],
            "max_tokens": 1000,
        }
        response = requests.post(
            "https://api.openai.com/v1/chat/completions", headers=header, json=payload
        )

        st.write(response.json()["choices"][0]["message"]["content"])


if __name__ == "__main__":
    main()

```

## アプリの起動

　コマンドプロンプトより以下を実行

 ```
streamlit run --server.address localhost streamlit_recipe_gpt4v.py
 ```

 ### 実行画面

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/350321/a0d56b00-d57e-ebf7-db44-6c07e3ec41ce.png)

　ここにファイルをアップロードすると料理のレシピが生成されます。料金は画像のサイズや応答のトークン数によって変わってくるようですが、私が実行した感じでは512x512サイズの画像で、一回あたりでだいたい$0.01～0.03でした。
 　詳しくはこちらの[OpenAIの公式](https://openai.com/pricing)をご確認ください

## 実行例１

まずはこちらの「天ぷらそば(温玉トッピング)」の画像で実行

![test_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/350321/c03ed921-5fe4-122b-a05d-993beec4b99c.jpeg)

### 出力結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/350321/e17ae262-26ed-423b-694f-de1787927da6.png)


## 実行例２

　次にこちらの「カルボナーラ」の画像

![test2..jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/350321/25c0ce8f-309b-84ea-819b-e01dfebd9e76.jpeg)

### 出力結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/350321/08b157f6-8978-b439-3780-acad4750931e.png)

 
## 実行例３

最後はDALLE3で生成した架空の「フルーツラーメン」の画像を試してみます。

![test3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/350321/c236c290-3b5d-734b-dd76-2ac6b2e93408.jpeg)


### 出力結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/350321/5b99dcf2-ef87-cc9a-6084-de9321d52e96.png)

　ラーメンがうどんになってしまいましたが、架空の料理なのでこんなものでしょう。架空の料理等は作らせたくない場合には、プロンプトで[料理以外の画像には「料理の画像をアップロードしてください」と返してください]と入れておけば、そう返すようになります。

## おわりに
　今回、GPT4-Vision APIを使おうと思ったときはFunction callingが使えなくてちょっと焦りましたが、プロンプトのみでなんとか対応できました。GPT4-Vision APIはまだリリースされたばかりのプレビュー版なので、まだ不便な点はありますがきっと改善されていって、もっと使いやすくなっていくことでしょう。