こんにちは、CCCMKホールディングス TECH LABの三浦です。
10月になってだいぶ気温が下がってきました。秋は自分にとっては夏が終わった後に訪れる、過ごしやすくて食べ物が美味しい季節、という印象ですが、秋から学校の新学年が始まる国もあり、その国の人たちにとっては秋って特別な季節なんだな、と最近知る機会がありました。自分が春になると感じるような気持ちを、その国の人たちは秋になると感じるのかな、と想像したりしました。
みなさんは絵を描くこと、お好きでしょうか?私は好きなのですが、あまり上手ではありません・・・。動画配信などでさらさら~っと上手に絵を描いている人たちの姿を見ていると、とてもうらやましくなります。
絵を上手に描けるようになるためにはコツコツ練習が必要だと思いますが、せっかく練習をするのなら、楽しく練習できるといいですよね。もし自分が描いた絵を誰かにいつでもすごくほめてもらえるとしたら、きっと楽しく絵を描く練習が出来るのではないでしょうか?
そこで今回は生成AIの力を借りて、自分が描いた絵をものすごくほめてもらえるアプリを作ってみました。このアプリを活用することで、きっと絵を描くことが楽しくなるのではないでしょうか。
どんなアプリ?
アプリを起動すると、絵を描くことが出来るキャンバスが表示されます。
絵を描いて、"ほめて"ボタンをクリックすると
めちゃくちゃほめてもらえます。
「ここまでほめてもらえるの!?」っていうくらい、ほめてもらえます。
アプリの仕組み
このアプリの仕組みをイラストにすると、以下の様になります。
手書きの絵を画像データとして読み込み、最初に画像を説明するテキストを生成する"Image Captioning"の処理をHugging Faceのモデルで行います。生成された説明文をPromptに埋め込み、Azure OpenAI ServiceのChatGPT("gpt-35-turbo")に入力して絵をほめる文章を生成させています。
使用したライブラリやHugging Faceのモデル
このアプリを作るにあたり、Streamlitで手書きの絵を描くことが出来るキャンパスのコンポーネントを追加するライブラリとしてstreamlit-drawable-canvas
を使用しました。
Image Captioningを行うためのモデルはHugging Faceに公開されているBLIP(Bootstrapping Language-Image Pre-training for Unified Vision-Language Understanding and Generation)というモデルを使用しました。
Promptを受け取り、ほめる文章を生成するLLMsはAzure OpenAI Serviceの"gpt-35-turbo"を使用しています。
コードについて
以下、作成したコードからいくつかピックアップしてご紹介します。
Promptテンプレートなど
LLMsに入力するPromptのテンプレートは以下の様にしました。Image Captioningモデルでは英語の説明文を生成するようにしているため、日本語でレスポンスを返すことを"日本語でほめて"という言葉を含めることで対応しました。{english_caption}
の部分にImage Captioningモデルが生成した説明文が埋め込まれます。
それからレスポンスが"ユーザーさんが描いた絵は・・・"のように、ユーザーのことを"ユーザーさん"と呼ぶ傾向があり、個人的にちょっと違和感を感じたので"ユーザーのことは"あなた"と呼んでください。"という言葉もPromptに含めるようにしました。
prompt_template = """ ユーザーが素敵な絵を描きました!考えられる最大限の誉め言葉でこの絵をほめてあげて下さい! ほめる時は日本語でほめて下さい!ところどころ強調箇所を入れると最高です。 強調する箇所は**強調する言葉**のように左右をアスタリスク2つで囲ってください。 ユーザーが描いた絵について、英語で説明すると以下の様になります。 {english_caption} それでは思いっきりユーザーの描いた絵をほめてあげて下さい!ユーザーのことは"あなた"と呼んでください。 """
Image Captioningモデルには生成する説明文の最初の文章を指定することが出来ます。手書き絵が入力されることを想定しているので、以下のようにしました。
text = "a drawing of
そして説明文を受け取り、Promptに埋め込んでLLMsに文章を生成させる処理を関数にしました。LangChainのAzureChatOpenAI
を使ってLLMsにアクセスしています。
def compliment_drawing(img_caption): """ 手書き絵に対する説明文を受け取り、LLMsにほめる文章を生成させる """ prompt = prompt_template.format(english_caption=img_caption) return llm.predict(prompt)
Image Captioningモデルのロード
Image Captioningモデルをロードします。かなり大きなファイルサイズになるため、ダウンロードしたら一度ローカルディスクに保存し、以降はそこから読むようにしました。また、読み込んだモデルはStreamlitのst.session_state
に格納するようにしています。
if "model" not in st.session_state: # モデルのダウンロード処理は一度だけ実行する。また、 if len(os.listdir(model_save_path)) == 0: st.session_state["processor"] = BlipProcessor.from_pretrained("Salesforce/blip-image-captioning-base") st.session_state["model"] = BlipForConditionalGeneration.from_pretrained("Salesforce/blip-image-captioning-base") st.session_state["model"].save_pretrained(model_save_path) st.session_state["processor"].save_pretrained(model_save_path) else: print("load from desc") st.session_state["processor"] = BlipProcessor.from_pretrained(model_save_path) st.session_state["model"] = BlipForConditionalGeneration.from_pretrained(model_save_path)
画面の構築
あとはStreamlitでの開発方法に従って画面に部品を配置していきます。キャンバス周りの実装は、streamlit-drawable-canvas
のREADMEに記載されているExample Usageの実装にならっています。
st.title("🤖描いた絵をすごくほめてもらえるアプリ") # 以下はstreamlit-drawable-canvasのExample Usageを参照 drawing_mode = st.sidebar.selectbox( "Drwing tool:",("point","freedraw","line","rect","circle","transform") ) stroke_width = st.sidebar.slider("Stroke width: ", 1, 25, 3) if drawing_mode == "point": point_display_radius = st.sidebar.slider("Point display radius: ",1,25,3) stroke_color = st.sidebar.color_picker("Stroke color hex: ") bg_color = st.sidebar.color_picker("Background color hex: ","#eee") bg_image = st.sidebar.file_uploader("Background image:",type=["png","jpg"]) realtime_update = st.sidebar.checkbox("Update in realtime", True) canvas_result = st_canvas( fill_color="rgba(255, 165, 0, 0.3)", stroke_width=stroke_width, stroke_color=stroke_color, background_color=bg_color, background_image=Image.open(bg_image) if bg_image else None, update_streamlit=realtime_update, height=450, width=450, drawing_mode=drawing_mode, point_display_radius=point_display_radius if drawing_mode=="point" else 0, key="canvas" ) # ほめてボタン homete = st.button("ほめて")
ほめる
最後は"ほめて"ボタンを押された時の処理です。streamlit-drawable-canvas
のキャンバスからはRGBAの形式のnumpy.array
で画像データを取得できるのですが、この状態ではImage Captioningモデルに入力出来ないため、画像処理ライブラリPillowを使ってRGB形式に変換する処理を行っています。
if homete: if canvas_result.image_data is not None: img_data = canvas_result.image_data # numpy to pillow img_data = Image.fromarray(img_data) # rgba to rgb rgb_img = Image.new("RGB", img_data.size, (255, 255, 255)) rgb_img.paste(img_data, mask=img_data.split()[3]) inputs = st.session_state["processor"](rgb_img, text, return_tensors="pt") out = st.session_state["model"].generate(**inputs) caption = st.session_state["processor"].decode(out[0],skip_special_tokens=True) print(caption) # Debug用 # 説明文をつかってほめてもらう compliment = compliment_drawing(caption) with st.chat_message("assistant"): st.write(compliment)
以上が今回作成したコードになります。
まとめ
いかがでしたでしょうか。個人的にはなかなか楽しいアプリを作ることが出来、とてもうれしかったです。最近は画像とテキストを両方入力できるマルチモーダルな仕組みが増えてきたように感じています。以前Image Captioning周りは調べたことがあったのですが、あれから少し時間も経ったので、最新の情報を一度調べておきたいなと思いました。