落書きを美しいイラストに変換する：OpenAIのAPIを使った方法


## はじめに

私は絵心が絶望的にありません。
常々、適当に書いた落書きをいい感じのイラストにしてくれたらいいなと思っていました。

調べてみるとそういったサービスもちらほらあって、どうやっているのかな？と気になったのでやり方を考えてみました。(当初、Variationで行けるかと思ったんですが、下手な絵が量産されるだけでした、、)

## やり方

OpenAIが公開しているAPIのうち、VisionとImage generationを使います。
ざっくりいうと、落書きをVisionで読み込んでプロンプトを作ってもらい、Image generationで画像にします。

例えば以下の落書きを入力とすると、

![IMG_9451.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649608/41991d4a-75e4-b3a3-a9b7-5ee25e0fa1f8.jpeg)


このような感じになります。
![IMG_9452.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649608/c72ee4de-75d5-adb3-b661-041297f323c0.jpeg)


プロンプトを作ってもらうときは以下のように指示をしています。プロンプトを構造化して生成する画像のスタイル(イラスト、絵画、写真、3Dなど)を指定できるようにするなど色々工夫できそうです。

```
instructions:入力された画像と説明を理解し、より詳細な画像を生成するためのプロンプトテキストを生成すること。
画像が非常に簡素なものであってもできる限りの特徴を捉え、最大限に想像力を働かせて表現してください。
例えば、描かれているものが人物が動物か無機物か、性別、年齢、数、向き、時間帯、屋外か室内か、天気、季節、雰囲気など。
attention:説明等は不要ですので、必ずプロンプトテキストのみ出力してください。
```


※有料アカウントをお持ちであればAPIキーを発行して以下から試せます。(サイドバーより画像生成→Upgrade)

https://gpt-example.streamlit.app


## コード

例によってStreamlitで実装しています。
(一部抜粋しています。全文はgithubのリポジトリを参照ください)

https://github.com/ppspps824/gpt_example


落書きを描くためのキャンバスは```streamlit-drawable-canvas```を利用しています。

https://github.com/andfanilo/streamlit-drawable-canvas


``` python
height = 1024
width = 1024
drawing_mode = st.selectbox("Drawing tool:", ("freedraw", "transform"))
stroke_width = st.slider("Stroke width: ", 1, 25, 3)

if drawing_mode == "point":
    point_display_radius = st.slider("Point display radius: ", 1, 25, 3)
    
stroke_color = st.color_picker("Stroke color hex: ")
bg_color = st.color_picker("Background color hex: ", "#eee")

canvas_result = st_canvas(
    fill_color="rgba(255, 255, 255, 1.0)",  # Fixed fill color with some opacity
    stroke_width=stroke_width,
    stroke_color=stroke_color,
    background_color=bg_color,
    update_streamlit=True,
    height=512,
    width=512,
    drawing_mode=drawing_mode,
    point_display_radius=point_display_radius
    if drawing_mode == "point"
    else 0,
    key="canvas",
)
if canvas_result:
    image = canvas_result.image_data
    image = Image.fromarray(image.astype("uint8"), mode="RGBA")

title = st.text_input("title:")
color=st.selectbox("Color",options=["モノクロ","カラー"])
style = st.selectbox("Style",options=["イラスト","写真","アイコン","絵画"])
num = st.number_input("Number of generation", step=1, min_value=1, max_value=5)

if st.button("Upgrade Image"):
    if image:
        buffered = io.BytesIO()
        image.save(buffered, format="PNG")
        image = buffered.getvalue()
        base_prompt = """
        instructions:入力された画像と説明を理解し、より詳細な画像を生成するためのプロンプトテキストを生成すること。画像が非常に簡素なものであってもできる限りの特徴を捉え、最大限に想像力を働かせて表現してください。
        例えば、描かれているものが人物が動物か無機物か、性別、年齢、数、向き、時間帯、屋外か室内か、天気、季節、雰囲気など。
        attention:説明等は不要ですので、必ずプロンプトテキストのみ出力してください。
        """
        payload = {
            "model": "gpt-4-vision-preview",
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {"type": "text", "text": base_prompt},
                        {
                            "type": "image_url",
                            "image_url": {
                                "url": f"data:image/jpeg;base64,{base64.b64encode(image).decode()}"
                            },
                        },
                    ],
                }
            ],
            "max_tokens": 300,
        }
        with st.spinner("生成中..."):
            response = requests.post(
                "https://api.openai.com/v1/chat/completions",
                headers={"Authorization": f"Bearer {openai.api_key}"},
                json=payload,
            ).json()
            response_text = response["choices"][0]["message"]["content"]
            image_prompt=f"""
            title:{title}
            details:{response_text}
            style:{style}
            color:{color}
            """
            st.write(image_prompt)
            response = openai.images.generate(
                model="dall-e-3",
                prompt=image_prompt,
                size=f"{height}x{width}",
                quality="standard",
                n=num
            )
            images = [data.url for data in response.data]

            for image_url in images:
                st.image(image_url)
```


フロント部分でごちゃごちゃしてますが、APIを利用している部分は↓だけです。

``` python
            response = requests.post(
                "https://api.openai.com/v1/chat/completions",
                headers={"Authorization": f"Bearer {openai.api_key}"},
                json=payload,
            ).json()
            response_text = response["choices"][0]["message"]["content"]
            image_prompt=f"""
            title:{title}
            details:{response_text}
            style:{style}
            color:{color}
            """
            st.write(image_prompt)
            response = openai.images.generate(
                model="dall-e-3",
                prompt=image_prompt,
                size=f"{height}x{width}",
                quality="standard",
                n=num
            )
            images = [data.url for data in response.data]

            for image_url in images:
                st.image(image_url)
```

## おわりに

APIを使うと複数の機能を自由に組み合わせられるので可能性がぐっと広がりますね。