見出し画像

Python tkinterを使ってOpenAIで画像を生成させるプログラム④

OpenAI APIの無料トライアル5ドル分を使い切り昨日突然トライアル期間が終了してしまった。このまま手を引こうとも考えたが課金してもう少し学んでいくことにしました。
課金のことを調べるとOpenAIの言語エンジンモデルには3種類あり、それぞれ特徴と使用料が違うことが分かった。このプログラムでは日英の翻訳をOpenAIのAPIで行っているのだが、その翻訳にも課金が必要になってしまう。原稿と翻訳結果は次の通りとなった。

それぞれのモデルの翻訳結果

日英翻訳なので当然英語が出力されたのだが、実際どのように翻訳されたのかを検証するためにGoogleの英日翻訳を使って日本語に戻してみました。
まずは最高峰モデルのDavinciから

Davinciが出力した翻訳結果を日本語に戻した

続いてCurieが出力した翻訳結果

Curieが出力した翻訳結果

最後にBabbage

Babbegeが出力した翻訳結果

Davinciは最高峰モデルだけあって美しい翻訳結果だが若干ポエムになっている印象。Curieは及第点でBabbegeはどう見ても落第だ。下馬評通りの結果と言ったところか。
翻訳結果はともあれプログラムを修正して日英翻訳に使う言語モデルを選択可能なものにして画像生成結果を比較することにした。ついでに翻訳はせず直接英文で画像生成するモードも作成し、DeepLツールで予め英語に翻訳した指示文を入力して結果を測定した。
それぞれの結果は次の通り

ダビンチで日英翻訳した結果で生成した画像
Curieで日英翻訳した結果で生成した画像
Babbageで日英翻訳をした結果で生成した画像
DeepL翻訳ツールであらかじめ日英翻訳した英文で生成した画像

私の願ったイメージに一番近い出力が得られたのはDeepLでの翻訳結果によるものだった。これを利用しない手はないということで調べたところなんと月間5万文字まで無料で利用できるという誠に太っ腹なAPIがあるではないか!ということでそちらも実装して動かしてみた。

DeepLの翻訳エンジンを使うモードを実装した

結果はやはり上々でDeepLすげーという感想しかない。OpenAIの言語モデルは有料なのにDeepLは月5万文字まで無料だからこちらを常用したい。

続いてOpenAIの画像生成のオプション機能で指定した画像のバリエーションを作成する機能がある。それを実装してみたところ、これも期待通りの動きとなった。次の2枚の画像で確認してください。

同じ指示文書で作成した一次生成結果
前画像の一番右端の画像のバリエーションを作成した結果

それ以外に画像の一部をマスキングしてAIに生成させた画像を重ね合わせるような機能もあるが画像のマスキング機能とかを実装するのは面倒なので今回は見送ることにした。これで基本機能は完成したと思うので次回以降はもう少し画像生成のアシストになるような機能を追加して行きたい。

今回は新しくDeepLの翻訳機能にアクセスするクラスを追加しました。

# dlcont.py

import deepl

class DeepLController:
    api_key = ""
    def __init__(self, apiKey):
        self.api_key = apiKey
   
    def Translate(self, word):
        translator = deepl.Translator(self.api_key)
        result = translator.translate_text(word, target_lang="EN-US")
        return result.text

よく出来たAPIなのでたったこれだけで良いのである。
続いてOpenAIControllerは翻訳メソッドをモデル選択式にして外部から利用できるように公開メソッドに昇格させた。前回まではGenerateImagesメソッドから呼び出されていたが、今回の変更ではその部分は削除した。

# oacont.py

import openai
import base64

class OpenAIController:
    model = {}
    def __init__(self, apiKey):
        openai.api_key = apiKey
        self.model[0] = "text-davinci-003"
        self.model[1] = "text-curie-001"
        self.model[2] = "text-babbage-001"
    
    def TranslateRequestWord(self, word, model):
        try:
            source_lang = "ja"
            target_lang = "en"
            prompt = f"Translate from {source_lang} to {target_lang}: {word}"
            completion = openai.Completion.create(
                engine=self.model[model],
                prompt=prompt,
                max_tokens=1024,
                n=1,
                stop=None,
                temperature=0
            )
            return completion.choices[0].text.strip()
        except openai.error.RateLimitError as e: raise OpenAIError(f"OpenAI API request exceeded rate limit: {e}")

    def GenerateImages(self, word, numberOfImages):
        results = {}
        try:
            response = openai.Image.create(
                prompt = word,
                n = numberOfImages,
                size = "1024x1024",
                response_format = "b64_json",
            )

            for data, n in zip(response["data"], range(numberOfImages)):
                img_data = base64.b64decode(data["b64_json"])
                results[n] = img_data
            return results
        except openai.error.RateLimitError as e: raise OpenAIError(f"OpenAI API request exceeded rate limit: {e}")
    
    def GenerateVariations(self, img_data, numberOfImages):
        results = {}
        try:
            response = openai.Image.create_variation(
                image = img_data,
                n = numberOfImages,
                size = "1024x1024",
                response_format = "b64_json",
            )
            for data, n in zip(response["data"], range(numberOfImages)):
                img_data = base64.b64decode(data["b64_json"])
                results[n] = img_data
            return results
        except openai.error.RateLimitError as e: raise OpenAIError(f"OpenAI API request exceeded rate limit: {e}")

class OpenAIError(Exception):
    pass

コードの通りGenerateVariationsメソッドを追加した。画像データをアップロードしてバリエーション作成を依頼するものです。
最後にメインのoagui.pyだがコントロールが増えたことと機能が増えたことによってコード量が増えています。私がPythonもですがtkinterを使い始めて始めて間もないので公開もためらう程の残念なコードですが次回以降にリファクタリングを進めて行きたいと思います。
※oagui_imagedialog.pyへの前回からの変更はありません

# oagui.py

import tkinter as tk
from tkinter import messagebox
from PIL import Image, ImageTk
from oacont import OpenAIController, OpenAIError
from dlcont import DeepLController
from io import BytesIO
from oagui_imagedialog import ImageDialog

OPENAI_API_KEY = "あなたのOpenAIのAPIキーどすえ"
DEEPL_API_KEY = "あなたのDeepLのAPIキーどすえ"
MODEL_DAVINCI = 0
MODEL_CURIE = 1
MODEL_BABBAGE = 2
MODEL_DEEPL = 3
MODEL_NONE = 4

class Application(tk.Frame):
    NUMBER_OF_IMAGES = 4
    CANVAS_BASE_X = 14
    CANVAS_BASE_Y = 200
    cans = {}
    variationButtons = {}
    originImages = {}

    def __init__(self,master = None):
        super().__init__(master)
        self.pack()
        self.model = tk.IntVar()
        self.model.set(MODEL_DEEPL)

        master.geometry("860x450")
        master.title("OpenAiで画像生成しちゃうよん")
        self.oac = OpenAIController(OPENAI_API_KEY)
        self.dlc = DeepLController(DEEPL_API_KEY)
        self.__create_controls()
    
    def __create_canvases(self):
        i = 0
        while i < self.NUMBER_OF_IMAGES:
            can = tk.Canvas(self.master, bg="white", width=200, height=200)
            vbtn = tk.Button(self.master, text="バリエーション")
            if i == 0: 
                can.place(x=self.CANVAS_BASE_X, y=self.CANVAS_BASE_Y)
                vbtn.place(x=self.CANVAS_BASE_X, y=self.CANVAS_BASE_Y + 204)
            elif i == 1: 
                can.place(x=self.CANVAS_BASE_X + 210, y=self.CANVAS_BASE_Y)
                vbtn.place(x=self.CANVAS_BASE_X + 210, y=self.CANVAS_BASE_Y + 204)
            elif i == 2: 
                can.place(x=self.CANVAS_BASE_X + 420, y=self.CANVAS_BASE_Y)
                vbtn.place(x=self.CANVAS_BASE_X + 420, y=self.CANVAS_BASE_Y + 204)
            else: 
                can.place(x=self.CANVAS_BASE_X + 630, y=self.CANVAS_BASE_Y)
                vbtn.place(x=self.CANVAS_BASE_X + 630, y=self.CANVAS_BASE_Y + 204)
            can.bind("<ButtonRelease-1>", self.__canvas_Click)
            vbtn.bind("<ButtonRelease-1>", self.__variation_button_Click)
            self.cans[i] = can
            self.variationButtons[vbtn] = i
            i += 1

    def __create_controls(self):
        tk.Label(text="生成呪文").place(x=20, y=20)
        tk.Label(text="日英翻訳").place(x=20, y=82)
        self.tbKeyword = tk.Text(width=92, height=4)
        self.tbKeyword.place(x=90, y=22)
        self.rbModel0 = tk.Radiobutton(self.master, value=0, variable=self.model, text="Davinci")
        self.rbModel0.place(x=90, y=80)
        self.rbModel1 = tk.Radiobutton(self.master, value=1, variable=self.model, text="Curie")
        self.rbModel1.place(x=180, y=80)
        self.rbModel2 = tk.Radiobutton(self.master, value=2, variable=self.model, text="Babbage")
        self.rbModel2.place(x=270, y=80)
        self.rbModel3 = tk.Radiobutton(self.master, value=3, variable=self.model, text="DeepL")
        self.rbModel3.place(x=360, y=80)
        self.rbModel4 = tk.Radiobutton(self.master, value=4, variable=self.model, text="翻訳なし(英文直接入力)")
        self.rbModel4.place(x=450, y=80)

        btnA = tk.Button(self.master, text="生成しちゃうぞ!",
                          command=self.__btnCreate_Click, width=12, height=5)
        btnA.place(x=750, y=18)

        btnB = tk.Button(self.master, text="結果を全クリア", command=self.__all_clear)
        btnB.place(x=16, y=176)
        self.__create_canvases()

    def __all_clear(self):
        for i in range(0, 4):
            self.cans[i].delete("photo")
        self.originImages.clear()
        self.master.update()
    
    def __canvas_Click(self, event):
        if(len(self.originImages) == 0): return
        can = event.widget
        image = self.originImages[can]
        ImageDialog.show(self, image)
    
    def __variation_button_Click(self, event):
        if(len(self.originImages) == 0): return     
        index = self.variationButtons[event.widget]
        can = self.cans[index]
        image = self.originImages[can]
        try:
            images = self.oac.GenerateVariations(image, self.NUMBER_OF_IMAGES)
        except OpenAIError as e:
            messagebox.showerror("エラーっす", e)
            return
        self.__showGeneratedImages(images)

    def __btnCreate_Click(self):
        images = {}
        wd = self.tbKeyword.get("1.0", "end")
        if (len(wd) == 1): return
        try:
            model = self.model.get() 
            if model == 3:
                wd = self.dlc.Translate(wd)
                print(wd)
            elif model != 4:
                wd = self.oac.TranslateRequestWord(wd, model)
            images = self.oac.GenerateImages(wd, self.NUMBER_OF_IMAGES)
        except OpenAIError as e:
            messagebox.showerror("エラーっす", e)
            return
        self.__showGeneratedImages(images)
    
    def __showGeneratedImages(self, images):
        self.originImages.clear()
        i = 0
        while i < self.NUMBER_OF_IMAGES:
            origin = images[i]
            self.originImages[self.cans[i]] = origin

            img = Image.open(BytesIO(origin)).resize((200, 200))
            self.cans[i].delete("photo")
            self.cans[i].photo = ImageTk.PhotoImage(img)
            self.cans[i].create_image(0, 0, image=self.cans[i].photo, anchor=tk.NW, tag="photo")
            i += 1
    
def main():
    win = tk.Tk()
    app = Application(master=win)
    app.mainloop()

if __name__ == "__main__":
    main()
バリエーションボタンが追加されたけど必ずマウスでクリックする必要がありますw


いいなと思ったら応援しよう!