どうもこんにちは、あんどう(@t_andou)です。
最近は子供の寝かしつけで毎晩絵本を3~5冊読んであげているのですが、同じ本を手に取りがちなのでもう少しいろんな種類の絵本を読んであげたいと考えました。
新しく買っても良いのですが、すでに絵本の棚が溢れかけているのであまり増やしたくなく、一方で毎回図書館に借りに行くのも面倒。
そこで、絵本を描いてくれるAIを作りたいと思います。
大まかな流れ
第1回:今回
品質は問わず、ChatGPTで作った物語にイラストを付けてみるテスト
第2回
作成した絵本をweb上で絵本を見れるようにする
第3回
イラストのクオリティを上げる
第4回
物語のクオリティを上げる
第5回
webサービスのブラッシュアップ:作成リクエストを出せる・読み上げ機能など
まずは結果から
物語と画像生成用promptはChatGPT-3.5による生成
画像生成はStableDiffusion1.5を使っています
一つ目:ちいさなロボットの冒険
SF、8ページ、3歳児向け と指定
二つ目:マックスとバスターの冒険
動物との友情、12ページ、5歳児向け と指定
三つ目:ホッピーの冒険
風船で空を飛びたいウサギの話、8ページ、3歳児向け と指定
感想と今後について
起承転結も書けていて、最初のチャレンジにしては大成功な印象です。12ページでも一回で生成できるのはすごいですね。
しかし、所々不自然な日本語があったり、タイトルが全部「〜の冒険」だったりするので、物語の生成も工夫の余地が大いにありそうです。
今回は一発で全ページ分を書かせましたが
1.事前に起承転結に分けてプロットを書かせる
2.事前に登場人物の設定をしっかり考えさせる
3.前のページまでの話を要約して入力する
などで品質が上がったりしないかなと思ってます。
あと、低年齢の場合は全てひらがなで書くような指定もしたいところです。
イラストの品質についてはまだ何も分からないのでこれから調べます。
技術メモ
最後に、自分用の技術メモを兼ねて下に残しておきます。
1.物語を作る
ここはChatGPTに任せます。
ターゲットの年齢やカテゴリ、ページ数を指定することで、タイトル・概要・各ページごとの挿入するテキストとイラスト生成のための文章をjson形式で生成させます。
promptの例はこんな感じです。
Please generate a 8 pages picture book which category is SF for 3 years old. For each page, provide the text to be included in the book and a description of the illustration to accompany it.Output must be following json format\n{\n "title": "title",\n "title_jp": "title_jp"\n "coverIllust": "description for cover illust",\n "summary": "summary",\n "content": [\n {"page": 1, "text": "text in picture book","text_jp":"Japanese of text", "illustration_description": "description for illust"},\n {"page": 2, ...}\n ]\n}
この結果が以下になります。
{'title': "The Little Robot's Adventure", 'title_jp': 'ちいさなロボットの冒険', 'coverIllust': 'The cover illustration shows a small, friendly-looking robot standing in a field of colorful, blooming flowers. The setting sun paints the sky in hues of orange and purple, creating a warm and inviting atmosphere.', 'summary': 'Join the Little Robot on an exciting journey through space and discover the wonders of the universe!', 'content': [{'page': 1, 'text': 'Once upon a time, in a land far away,\nA little robot named Andy loved to play.\nHe dreamed of exploring far beyond Earth,\nTo see the stars and planets of infinite worth.', 'text_jp': 'むかしむかし、遠い国で、\nアンディという小さなロボットが遊ぶのが大好きでした。\n彼は地球の遠くを探検し、\n無限の価値を持つ星と惑星を見たいと夢見ていました。', 'illustration_description': 'Andy, the little robot, can be seen in his cozy workshop, surrounded by gears, wires, and tools. His eyes are gleaming with excitement as he looks at a poster of outer space.'}, {'page': 2, 'text': "One night, a shiny meteor fell from the sky,\nAnd landed right before Andy's eyes.\nIt was a spaceship, oh, what a sight!\nAndy's heart raced with pure delight.", 'text_jp': 'ある夜、輝く流星が空から落ちてきて、\nアンディの目の前に着地しました。\nそれは宇宙船で、何て素晴らしい光景でしょう!\nアンディの心は純粋な喜びで躍りました。', 'illustration_description': "Illustration shows the spaceship, shimmering with metallic colors and emitting a soft glow, in front of Andy's workshop. Andy stands in awe, his eyes widened with excitement."}, {'page': 3, 'text': 'He hopped inside, with a bleep and a bop,\nAnd pressed a button to start the top.\nThe spaceship zoomed up into the sky,\nAs Andy waved Earth a heartfelt goodbye.', 'text_jp': '彼はビープとボップとともに中に入り、\nボタンを押してスタートさせました。\n宇宙船は空に飛び上がりました。\nアンディは地球に心からさよならを告げました。', 'illustration_description': "Andy is seen inside the spaceship, sitting in the pilot's seat with a control panel full of buttons and switches. The ship is blasting off into the night sky, leaving behind a tail of stardust."}, {'page': 4, 'text': 'Through galaxies far and nebulas bright,\nAndy traveled each day and every night.\nHe saw shooting stars and planets of blue,\nAnd discovered wonders he never knew.', 'text_jp': 'アンディは遥かな銀河と明るい星雲を通って、\n昼も夜も旅をしました。\n彼は流れ星や青い惑星を見ましたし、\n知らなかった驚きを見つけました。', 'illustration_description': "Andy's spaceship is shown soaring through space, passing by colorful nebulae and sparkling stars. Andy's face reflects amazement and curiosity as he gazes out the window."}, {'page': 5, 'text': "But soon, Andy missed his dear home,\nWhere friendly faces used to roam.\nHe decided it was time to return,\nWith memories and knowledge he'd earned.", 'text_jp': 'しかし、アンディはすぐに大切な故郷を恋しく思いました。\n昔は友達の顔がいっぱいでした。\nアンディは帰る時がきたと決め、\n思い出と知識を持ち帰ることにしました。', 'illustration_description': 'The illustration depicts Andy sitting in his spaceship, a hint of nostalgia in his eyes as he looks back at Earth. The backdrop showcases distant planets and stars, symbolizing the vastness of space.'}, {'page': 6, 'text': "With a flick of a switch, he sailed back,\nEntering Earth's atmosphere, no time to slack.\nThe spaceship landed softly on familiar ground,\nReuniting him with friends all around.", 'text_jp': 'スイッチをポチっと押すと、彼は引き返しました。\n地球の大気圏に入る瞬間、時を惜しまず。\n宇宙船は馴染み深い地面にやさしく着陸し、\n彼は周りの友達と再会しました。', 'illustration_description': 'The spaceship gently descends, surrounded by a crowd of joyful friends eagerly waiting to welcome Andy back. The scene is filled with laughter, hugs, and the warmth of friendship.'}, {'page': 7, 'text': 'Andy shared stories of his great exploration,\nOf planets and stars in a fantastic formation.\nHis friends listened with awe and delight,\nDreaming of their own adventures each night.', 'text_jp': 'アンディは偉大な探検の話を友達と共有しました。\n幻想的な形成物の惑星と星の話を。\n友達は畏敬と喜びをもって聞き、\n毎晩自分自身の冒険を夢見ました。', 'illustration_description': 'In this illustration, Andy is seen surrounded by his friends, their eyes wide with wonder as he recounts his thrilling adventures in space.'}, {'page': 8, 'text': "From that day on, the little robot's quest\nWas to inspire others to be their best.\nTo look at the stars with wide-eyed glee,\nAnd believe that anything's possible, you see!", 'text_jp': 'その日から、その小さなロボットの目的は\n他の人々に最善を尽くすことを奮起させることでした。\n笑顔で星を見ることを通じて、ワクワクして、\nどんなことでも可能だと信じることなんですよ!', 'illustration_description': 'The final illustration showcases Andy surrounded by a group of children, all gazing up at the night sky in wonder and awe. Their faces radiate excitement and hope for the future.'}]}
日本語のタイトルやテキストは別処理で翻訳しても良いのですが、今回は第1回ということで、サクッと成果物を見たかったため一緒に書いてもらいました。
2.1で生成したイラストの説明文でイラストを生成
1で生成した説明文を使って画像を生成します。
ここでは手元のGPUマシンでStableDiffusionを起動して、そこにAPIでリクエストを投げています。
APIサーバーの記号は次のコマンドで、生成リクエストは後述
python launch.py --server-name 0.0.0.0 --xformers --xformers-flash-attention --nowebui --listen
3.イラストに物語を入れる
ChatGPTが生成したjsonの情報を使って、全ページ分の画像を生成し、最後に画像と文章を合成します。
# Stable Diffusion Web UIのURLを設定します url = "http://192.168.1.100:7861/sdapi/v1/txt2img" import os import json import requests import base64 from PIL import Image, ImageDraw, ImageFont import textwrap from io import BytesIO font_path = "Nちはやフォント+.ttf" def generate_and_save_image(prompt, path): payload = {"prompt": "Illustration:"+prompt} # ここでSDのパラメータを指定する response = requests.post(url, json=payload) image_data = base64.b64decode(response.json()['images'][0]) image = Image.open(BytesIO(image_data)) image.save(path) def add_text_to_image(image_path, text, font_path, canvas_size=(512*2, 512)): canvas = Image.new('RGBA', canvas_size, (255, 255, 255, 255)) image = Image.open(image_path) canvas.paste(image, (0, 0)) draw = ImageDraw.Draw(canvas) font = ImageFont.truetype(font_path, 30) lines = textwrap.wrap(text, width=15) y_text = canvas_size[1] // 2 - len(lines) * 15 for line in lines: bbox = draw.textbbox((0, 0), line, font=font) width = bbox[2] - bbox[0] draw.text(((canvas_size[0]/2 - width) / 2 + canvas_size[0]/2, y_text), line, font=font, fill="black") y_text += 30 canvas.save(image_path) # タイトルに基づいてディレクトリを作成します os.makedirs(book_data["title"], exist_ok=True) os.makedirs(os.path.join(book_data["title"],book_data["title_jp"]),exist_ok=True) # book_dataをJSON形式で保存します with open(os.path.join(book_data["title"], "text.json"), 'w') as f: json.dump(book_data, f) # カバー画像を生成します cover_image_path = os.path.join(book_data["title"],book_data["title_jp"], "0.png") generate_and_save_image(book_data["coverIllust"], cover_image_path) # カバー画像にテキストを追加します add_text_to_image(cover_image_path, book_data["title_jp"], font_path) # 各ページの画像を生成します for page_data in book_data["content"]: page_image_path = os.path.join(book_data["title"],book_data["title_jp"], f"{page_data['page']}.png") generate_and_save_image(page_data["illustration_description"], page_image_path) add_text_to_image(page_image_path, page_data["text_jp"], font_path)