Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -26,7 +26,7 @@ MAX_API_SIZE = 1024
|
|
| 26 |
|
| 27 |
def pil_to_base64(img: Image.Image) -> str:
|
| 28 |
"""PIL画像をBase64エンコードされたPNGデータに変換します。"""
|
| 29 |
-
buffered = io.
|
| 30 |
# アルファチャンネル(PNG)を維持
|
| 31 |
if img.mode != 'RGBA':
|
| 32 |
img = img.convert('RGBA')
|
|
@@ -131,27 +131,25 @@ async def nano_banana_completion_api(base_image: Image.Image, prompt: str) -> Im
|
|
| 131 |
|
| 132 |
# --- メイン処理ロジック (Main Processing Logic) ---
|
| 133 |
|
| 134 |
-
async def segment_and_inpaint(
|
| 135 |
"""
|
| 136 |
アップロードされた一枚絵とブラシで描かれたマスクを受け取り、
|
| 137 |
マスクされた領域を分割し、欠損部分をAI補完する関数。
|
| 138 |
"""
|
| 139 |
-
if
|
| 140 |
-
return None, None, None, "エラー:
|
|
|
|
|
|
|
| 141 |
|
| 142 |
-
# Gradioのtype="sketch"は (画像データ, マスクデータ) のタプルを返す
|
| 143 |
-
# NOTE: type="sketch"の出力構造は (元の画像 (np.array), マスク画像 (np.array)) です。
|
| 144 |
-
original_rgba_np, sketch_mask_np = image_data_tuple
|
| 145 |
-
|
| 146 |
# PIL Image (元の画像) を取得
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
#
|
|
|
|
| 151 |
|
| 152 |
-
#
|
| 153 |
-
|
| 154 |
-
sketch_mask_alpha = sketch_mask_np[:, :, 3]
|
| 155 |
|
| 156 |
# ユーザーが何も描画しなかった場合のチェック
|
| 157 |
if np.all(sketch_mask_alpha == 0):
|
|
@@ -173,7 +171,7 @@ async def segment_and_inpaint(image_data_tuple: tuple, inpaint_prompt: str):
|
|
| 173 |
# 反転マスクを体パーツのアルファチャンネルとして設定
|
| 174 |
body_with_hole.putalpha(Image.fromarray(inverted_mask_np, mode='L'))
|
| 175 |
|
| 176 |
-
print("--- 1. 手動パーツ分割 (Gradio
|
| 177 |
|
| 178 |
# 2. 欠損領域のAI補完 (AI Inpainting)
|
| 179 |
completed_body_part = await nano_banana_completion_api(body_with_hole, inpaint_prompt or "マスクされた領域のキャラクターの顔と身体の肌、及び下に着ている服を、元のイラストのテイストに合わせて自然に補完してください。")
|
|
@@ -244,30 +242,46 @@ with gr.Blocks(theme=theme, title="Live2D素材自動分割・補完アプリ")
|
|
| 244 |
<div style='text-align: center; margin-bottom: 20px; padding: 10px; background: #E0F7FA; border-radius: 12px;'>
|
| 245 |
<h1 style='color: #00796B; font-size: 2.5em; font-weight: 700;'>🎨 Live2D 素材 自動分割・補完アプリ 🤖</h1>
|
| 246 |
<p style='color: #004D40; font-size: 1.1em;'>一枚絵をアップロードし、**ブラシでパーツを指定**することで、AIによる欠損部分の自動補完(Nano Banana API利用)を行います。</p>
|
| 247 |
-
<p style='color: #004D40; font-size: 1.0em;'>💡 **操作方法:**
|
| 248 |
</div>
|
| 249 |
"""
|
| 250 |
)
|
| 251 |
|
| 252 |
with gr.Row():
|
| 253 |
with gr.Column(scale=1):
|
| 254 |
-
# ★★★
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
height=400,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
)
|
|
|
|
| 260 |
inpaint_prompt = gr.Textbox(
|
| 261 |
-
label="
|
| 262 |
value="マスクされた領域のキャラクターの顔と身体の肌、及び下に着ている服を、元のイラストのテイストに合わせて自然に補完してください。",
|
| 263 |
placeholder="例: マスクされた領域を元の絵柄で自然に描き足す"
|
| 264 |
)
|
| 265 |
-
process_button = gr.Button("
|
| 266 |
|
| 267 |
gr.Markdown(
|
| 268 |
"""
|
| 269 |
### 📌 開発のポイント
|
| 270 |
-
*
|
| 271 |
"""
|
| 272 |
)
|
| 273 |
|
|
@@ -295,7 +309,7 @@ with gr.Blocks(theme=theme, title="Live2D素材自動分割・補完アプリ")
|
|
| 295 |
# --- イベントリスナー ---
|
| 296 |
process_button.click(
|
| 297 |
fn=segment_and_inpaint,
|
| 298 |
-
inputs=[
|
| 299 |
outputs=[
|
| 300 |
completed_body_output,
|
| 301 |
hair_part_output,
|
|
|
|
| 26 |
|
| 27 |
def pil_to_base64(img: Image.Image) -> str:
|
| 28 |
"""PIL画像をBase64エンコードされたPNGデータに変換します。"""
|
| 29 |
+
buffered = io.BytesIo()
|
| 30 |
# アルファチャンネル(PNG)を維持
|
| 31 |
if img.mode != 'RGBA':
|
| 32 |
img = img.convert('RGBA')
|
|
|
|
| 131 |
|
| 132 |
# --- メイン処理ロジック (Main Processing Logic) ---
|
| 133 |
|
| 134 |
+
async def segment_and_inpaint(original_image_np: np.ndarray, sketchpad_image_np: np.ndarray, inpaint_prompt: str):
|
| 135 |
"""
|
| 136 |
アップロードされた一枚絵とブラシで描かれたマスクを受け取り、
|
| 137 |
マスクされた領域を分割し、欠損部分をAI補完する関数。
|
| 138 |
"""
|
| 139 |
+
if original_image_np is None:
|
| 140 |
+
return None, None, None, "エラー: 元の画像がアップロードされていません。", None
|
| 141 |
+
if sketchpad_image_np is None:
|
| 142 |
+
return None, None, None, "エラー: 分割したいパーツをブラシで描画してください。", None
|
| 143 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
# PIL Image (元の画像) を取得
|
| 145 |
+
# NOTE: Gradioのデフォルト出力はNumPy配列
|
| 146 |
+
original_image = Image.fromarray(original_image_np).convert("RGBA")
|
| 147 |
+
|
| 148 |
+
# Sketchpadからの入力は、元の画像の上にブラシで描画されたRGBA画像 (描画部分は不透明)
|
| 149 |
+
# そのため、アルファチャンネルがマスクとして機能する
|
| 150 |
|
| 151 |
+
# Sketchpadの画像からアルファチャンネルを抽出(これがユーザーの描いたマスク)
|
| 152 |
+
sketch_mask_alpha = sketchpad_image_np[:, :, 3]
|
|
|
|
| 153 |
|
| 154 |
# ユーザーが何も描画しなかった場合のチェック
|
| 155 |
if np.all(sketch_mask_alpha == 0):
|
|
|
|
| 171 |
# 反転マスクを体パーツのアルファチャンネルとして設定
|
| 172 |
body_with_hole.putalpha(Image.fromarray(inverted_mask_np, mode='L'))
|
| 173 |
|
| 174 |
+
print("--- 1. 手動パーツ分割 (Gradio Sketchpad利用) 完了: 分割パーツと補完が必要な欠損体を作成 ---")
|
| 175 |
|
| 176 |
# 2. 欠損領域のAI補完 (AI Inpainting)
|
| 177 |
completed_body_part = await nano_banana_completion_api(body_with_hole, inpaint_prompt or "マスクされた領域のキャラクターの顔と身体の肌、及び下に着ている服を、元のイラストのテイストに合わせて自然に補完してください。")
|
|
|
|
| 242 |
<div style='text-align: center; margin-bottom: 20px; padding: 10px; background: #E0F7FA; border-radius: 12px;'>
|
| 243 |
<h1 style='color: #00796B; font-size: 2.5em; font-weight: 700;'>🎨 Live2D 素材 自動分割・補完アプリ 🤖</h1>
|
| 244 |
<p style='color: #004D40; font-size: 1.1em;'>一枚絵をアップロードし、**ブラシでパーツを指定**することで、AIによる欠損部分の自動補完(Nano Banana API利用)を行います。</p>
|
| 245 |
+
<p style='color: #004D40; font-size: 1.0em;'>💡 **操作方法:** ①に元画像をアップロードし、②のキャンバスにその画像が表示された後、**ブラシ**を使って、前髪など**分割したいパーツ**を塗りつぶしてください。塗った部分がパーツとして分離され、AIがその下の欠損を補完します。</p>
|
| 246 |
</div>
|
| 247 |
"""
|
| 248 |
)
|
| 249 |
|
| 250 |
with gr.Row():
|
| 251 |
with gr.Column(scale=1):
|
| 252 |
+
# ★★★ 修正箇所1: 元画像をアップロードするためのコンポーネント (描画機能なし) ★★★
|
| 253 |
+
original_image_upload = gr.Image(
|
| 254 |
+
label="① 元画像のアップロード",
|
| 255 |
+
type="numpy", # NumPy配列として受け取る
|
| 256 |
+
height=200,
|
| 257 |
+
)
|
| 258 |
+
# ★★★ 修正箇所2: 描画専用のSketchpadコンポーネント ★★★
|
| 259 |
+
input_sketchpad = gr.Sketchpad(
|
| 260 |
+
label="② 分割したいパーツをブラシで塗る (例: 前髪)",
|
| 261 |
height=400,
|
| 262 |
+
# Sketchpadにアップロードされた元画像を表示させるためのプレースホルダー
|
| 263 |
+
brush={"color": "#FF0000", "size": 20}, # ブラシ設定
|
| 264 |
+
)
|
| 265 |
+
|
| 266 |
+
# 描画キャンバスの背景をアップロード画像に設定するための処理
|
| 267 |
+
original_image_upload.change(
|
| 268 |
+
fn=lambda x: gr.update(image=x),
|
| 269 |
+
inputs=[original_image_upload],
|
| 270 |
+
outputs=[input_sketchpad],
|
| 271 |
+
queue=False
|
| 272 |
)
|
| 273 |
+
|
| 274 |
inpaint_prompt = gr.Textbox(
|
| 275 |
+
label="③ AI補完プロンプト(オプション)",
|
| 276 |
value="マスクされた領域のキャラクターの顔と身体の肌、及び下に着ている服を、元のイラストのテイストに合わせて自然に補完してください。",
|
| 277 |
placeholder="例: マスクされた領域を元の絵柄で自然に描き足す"
|
| 278 |
)
|
| 279 |
+
process_button = gr.Button("④ 手動分割・AI補完を実行", variant="primary", scale=0)
|
| 280 |
|
| 281 |
gr.Markdown(
|
| 282 |
"""
|
| 283 |
### 📌 開発のポイント
|
| 284 |
+
* **Gradio互換性対応:** 環境のGradioバージョンが低すぎる問題を回避するため、描画機能に特化した`gr.Sketchpad`を使用しました。
|
| 285 |
"""
|
| 286 |
)
|
| 287 |
|
|
|
|
| 309 |
# --- イベントリスナー ---
|
| 310 |
process_button.click(
|
| 311 |
fn=segment_and_inpaint,
|
| 312 |
+
inputs=[original_image_upload, input_sketchpad, inpaint_prompt], # 入力コンポーネントの順序を修正
|
| 313 |
outputs=[
|
| 314 |
completed_body_output,
|
| 315 |
hair_part_output,
|