i0switch commited on
Commit
b7d7077
·
verified ·
1 Parent(s): 478bf4d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +97 -215
app.py CHANGED
@@ -1,294 +1,176 @@
1
- # app.py — InstantID × Beautiful Realistic Asians v7(ZeroGPU / ControlNetMediaPipeFace)
2
- # 2025-06-22
3
 
4
  ##############################################################################
5
- # 0. API 新 API 互換パッチ(必ず diffusers import の前に置く)
6
  ##############################################################################
7
  from huggingface_hub import hf_hub_download
8
- import huggingface_hub as _hf_hub
9
-
10
- # diffusers-0.27 は cached_download() を呼び出すため、HF-Hub ≥0.28 でも使えるように注入
11
- if not hasattr(_hf_hub, "cached_download"):
12
- _hf_hub.cached_download = hf_hub_download # :contentReference[oaicite:1]{index=1}
13
 
14
  ##############################################################################
15
- # 1. 標準 & 外部ライブラリ
16
  ##############################################################################
17
  import os, io, base64, subprocess, traceback
18
  from pathlib import Path
19
  from typing import Optional
20
-
21
  import numpy as np
22
- import torch
23
- import gradio as gr
24
- import spaces
25
  from fastapi import FastAPI, UploadFile, File, Form, HTTPException
26
  from PIL import Image
27
-
28
  from diffusers import (
29
  StableDiffusionControlNetPipeline,
30
  ControlNetModel,
31
  DPMSolverMultistepScheduler,
32
- AutoencoderKL,
33
  )
34
  from diffusers.loaders import AttnProcsLayers
35
  from insightface.app import FaceAnalysis
36
- from basicsr.utils.download_util import load_file_from_url
37
  from realesrgan import RealESRGANer
38
 
39
  ##############################################################################
40
- # 2. キャッシュ & 永続パス
41
  ##############################################################################
42
- PERSIST_BASE = Path("/data")
43
- CACHE_ROOT = (
44
- PERSIST_BASE / "instantid_cache"
45
- if PERSIST_BASE.exists() and os.access(PERSIST_BASE, os.W_OK)
46
- else Path.home() / ".cache" / "instantid_cache"
47
- )
48
- MODELS_DIR = CACHE_ROOT / "models"
49
- LORA_DIR = CACHE_ROOT / "lora"
50
- UPSCALE_DIR = CACHE_ROOT / "realesrgan"
51
- for p in (MODELS_DIR, LORA_DIR, UPSCALE_DIR):
52
- p.mkdir(parents=True, exist_ok=True)
53
 
54
  ##############################################################################
55
- # 3. モデル識別子 & ファイル名
56
  ##############################################################################
57
- # すべて HF Hub 側にバイナリがあるため、curl ではなく hf_hub_download() を推奨
58
- BRA_REPO = "i0switch-assets/Beautiful_Realistic_Asians_v7"
59
- BRA_FILE = "beautiful_realistic_asians_v7_fp16.safetensors"
60
-
61
- IP_REPO = "h94/IP-Adapter"
62
- IP_FILE_BIN = "ip-adapter-plus-face_sd15.bin" # Git LFS バイナリ :contentReference[oaicite:2]{index=2}
63
 
64
- IP_LORA_REPO = "h94/IP-Adapter-FaceID"
65
- IP_FILE_LORA = "ip-adapter-faceid-plusv2_sd15_lora.safetensors" # Git LFS バイナリ
 
66
 
67
- CN_REPO = "CrucibleAI/ControlNetMediaPipeFace" # 公開・無認証で DL :contentReference[oaicite:3]{index=3}
68
- CN_FOLDER = "diffusion_sd15" # SD-1.5 用フォルダ :contentReference[oaicite:4]{index=4}
69
 
70
- REALESRGAN_REPO = "aimagelab/realesrgan"
71
- REALESRGAN_FILE = "RealESRGAN_x4plus.pth"
72
 
73
  ##############################################################################
74
- # 4. ダウンローダ(HF Hub 優先)
75
  ##############################################################################
76
- def dl_hf(repo: str, filename: str, subfolder: Optional[str] = None) -> Path:
77
- """HF Hub から大容量バイナリを安全に取得(Git LFS ポインタ問題を回避)"""
78
- return Path(
79
- hf_hub_download(
80
- repo_id=repo,
81
- filename=filename,
82
- subfolder=subfolder,
83
- cache_dir=str(MODELS_DIR),
84
- )
85
- )
86
-
87
- def dl_http(url: str, dst: Path):
88
- """小さなファイルのみ curl で取得(retry 付)"""
89
- if dst.exists():
90
- return dst
91
- for _ in range(2):
92
- try:
93
- subprocess.check_call(["curl", "-L", "-o", str(dst), url])
94
- return dst
95
- except subprocess.CalledProcessError:
96
- pass
97
- load_file_from_url(url=url, model_dir=str(dst.parent), file_name=dst.name)
98
- return dst
99
 
100
  ##############################################################################
101
- # 5. グローバル変数(lazy-load)
102
  ##############################################################################
103
  pipe: Optional[StableDiffusionControlNetPipeline] = None
104
  face_analyser: Optional[FaceAnalysis] = None
105
  upsampler: Optional[RealESRGANer] = None
106
 
107
  ##############################################################################
108
- # 6. パイプライン初期化
109
  ##############################################################################
110
- def initialize_pipelines():
111
  global pipe, face_analyser, upsampler
112
  if pipe is not None:
113
  return
 
114
 
115
- print("[INIT] Downloading model assets …")
 
116
 
117
- # 6-1 主要モデル
118
- bra_ckpt = dl_hf(BRA_REPO, BRA_FILE)
119
- ip_bin = dl_hf(IP_REPO, IP_FILE_BIN)
120
- ip_lora = dl_hf(IP_LORA_REPO, IP_FILE_LORA)
121
- cn_model = ControlNetModel.from_pretrained(
122
- CN_REPO, subfolder=CN_FOLDER, torch_dtype=torch.float16, cache_dir=str(MODELS_DIR)
123
  )
124
 
125
- # 6-2 Diffusers パイプライン
126
- pipe_tmp = StableDiffusionControlNetPipeline.from_pretrained(
127
- "runwayml/stable-diffusion-v1-5",
128
- controlnet=cn_model,
129
- vae=AutoencoderKL.from_pretrained(
130
- "stabilityai/sd-vae-ft-mse", torch_dtype=torch.float16
131
- ),
132
- torch_dtype=torch.float16,
133
- cache_dir=str(MODELS_DIR),
134
- safety_checker=None,
135
- )
136
- pipe_tmp.scheduler = DPMSolverMultistepScheduler.from_pretrained(
137
- "runwayml/stable-diffusion-v1-5",
138
- subfolder="scheduler",
139
- cache_dir=str(MODELS_DIR),
140
  )
 
141
 
142
- # 6-3 IP-Adapter ロード(必須 3 引数) :contentReference[oaicite:5]{index=5}
143
- pipe_tmp.load_ip_adapter(
144
- str(ip_bin.parent), # repo_or_path
145
- "", # subfolder(直下なので空文字)
146
- ip_bin.name # weight_name
147
- )
148
- AttnProcsLayers(pipe_tmp.unet.attn_processors).load_lora_weights(
149
  ip_lora, adapter_name="ip_faceid", safe_load=True
150
  )
151
- pipe_tmp.set_adapters(["ip_faceid"], adapter_weights=[0.6])
152
- pipe_tmp.to("cuda")
153
 
154
- pipe = pipe_tmp
155
-
156
- # 6-4 InsightFace
157
  face_analyser = FaceAnalysis(
158
- name="buffalo_l", root=str(MODELS_DIR), providers=["CUDAExecutionProvider"]
159
- )
160
- face_analyser.prepare(ctx_id=0, det_size=(640, 640))
161
-
162
- # 6-5 Real-ESRGAN
163
- re_ckpt = dl_hf(REALESRGAN_REPO, REALESRGAN_FILE)
164
- upsampler = RealESRGANer(
165
- scale=4,
166
- model_path=str(re_ckpt),
167
- half=True,
168
- tile=512, tile_pad=10, pre_pad=0, gpu_id=0
169
- )
170
 
171
- print("[INIT] Pipelines ready.")
 
 
 
172
 
173
  ##############################################################################
174
- # 7. プロンプトテンプレ
175
  ##############################################################################
176
- BASE_PROMPT = (
177
- "(masterpiece:1.2), best quality, ultra-realistic, RAW photo, 8k, "
178
- "cinematic lighting, textured skin, "
179
- )
180
- NEG_PROMPT = (
181
- "verybadimagenegative_v1.3, ng_deepnegative_v1_75t, "
182
- "(worst quality:2), (low quality:2), lowres, blurry, bad anatomy, "
183
- "bad hands, extra digits, watermark, signature"
184
- )
185
 
186
  ##############################################################################
187
- # 8. 生成コア(GPU アタッチ)
188
  ##############################################################################
189
- @spaces.GPU(duration=60) # ZeroGPU で 60 s まで実行可 :contentReference[oaicite:6]{index=6}
190
- def generate_core(
191
- face_img: Image.Image,
192
- subject: str,
193
- add_prompt: str = "",
194
- add_neg: str = "",
195
- cfg: float = 7.5,
196
- ip_scale: float = 0.6,
197
- steps: int = 30,
198
- w: int = 768,
199
- h: int = 768,
200
- upscale: bool = False,
201
- up_factor: int = 4,
202
- progress: gr.Progress = gr.Progress(track_tqdm=True),
203
- ):
204
- try:
205
- if pipe is None:
206
- initialize_pipelines()
207
-
208
- if len(face_analyser.get(np.array(face_img))) == 0:
209
- raise ValueError("顔が検出できません。別の画像でお試しください。")
210
-
211
- pipe.set_adapters(["ip_faceid"], adapter_weights=[ip_scale])
212
-
213
- prompt = BASE_PROMPT + subject + ", " + add_prompt
214
- negative = NEG_PROMPT + ", " + add_neg
215
-
216
- result = pipe(
217
- prompt=prompt,
218
- negative_prompt=negative,
219
- num_inference_steps=int(steps),
220
- guidance_scale=float(cfg),
221
- image=face_img,
222
- control_image=None,
223
- width=int(w), height=int(h),
224
- ).images[0]
225
-
226
- if upscale:
227
- upsampler.scale = 4 if up_factor == 4 else 8
228
- result, _ = upsampler.enhance(np.array(result))
229
- result = Image.fromarray(result)
230
-
231
- return result
232
-
233
- except Exception as e:
234
- traceback.print_exc()
235
- raise e
236
 
237
  ##############################################################################
238
  # 9. Gradio UI
239
  ##############################################################################
240
- with gr.Blocks(title="InstantID × BRA v7 (ZeroGPU)") as demo:
241
- gr.Markdown("## InstantID × Beautiful Realistic Asians v7")
242
  with gr.Row():
243
- face_img = gr.Image(type="pil", label="Face ID", sources=["upload"])
244
- subject = gr.Textbox(label="被写体説明(例: 30代日本人女性、黒髪セミロング)", interactive=True)
245
- add_prompt = gr.Textbox(label="追加プロンプト", interactive=True)
246
- add_neg = gr.Textbox(label="追加ネガティブ", interactive=True)
247
  with gr.Row():
248
- cfg = gr.Slider(1, 20, value=7.5, step=0.5, label="CFG Scale")
249
- ip_scale = gr.Slider(0.1, 1.0, value=0.6, step=0.05, label="IP-Adapter Weight")
250
  with gr.Row():
251
- steps = gr.Slider(10, 50, value=30, step=1, label="Steps")
252
- w = gr.Slider(512, 1024, value=768, step=64, label="Width")
253
- h = gr.Slider(512, 1024, value=768, step=64, label="Height")
254
  with gr.Row():
255
- upscale = gr.Checkbox(label="Real-ESRGAN Upscale", value=False)
256
- up_factor = gr.Radio([4, 8], value=4, label="Upscale Factor")
257
- run_btn = gr.Button("Generate")
258
- output_im = gr.Image(type="pil", label="Result")
259
-
260
- run_btn.click(
261
- fn=generate_core,
262
- inputs=[face_img, subject, add_prompt, add_neg,
263
- cfg, ip_scale, steps, w, h, upscale, up_factor],
264
- outputs=output_im, show_progress=True
265
- )
266
 
267
  ##############################################################################
268
- # 10. FastAPI REST
269
  ##############################################################################
270
  app = FastAPI()
271
 
272
  @app.post("/api/generate")
273
- async def api_generate(
274
- subject: str = Form(...),
275
- cfg: float = Form(7.5),
276
- steps: int = Form(30),
277
- ip_scale: float = Form(0.6),
278
- w: int = Form(768),
279
- h: int = Form(768),
280
- file: UploadFile = File(...),
281
- ):
282
- try:
283
- img = Image.open(io.BytesIO(await file.read())).convert("RGB") # noqa
284
- res = generate_core(img, subject, "", "", cfg, ip_scale, steps, w, h, False, 4)
285
- buf = io.BytesIO(); res.save(buf, format="PNG")
286
- return {"image": "data:image/png;base64," + base64.b64encode(buf.getvalue()).decode()}
287
- except Exception as e:
288
- traceback.print_exc()
289
- raise HTTPException(status_code=500, detail=str(e))
290
 
291
  ##############################################################################
292
- # 11. Launch(Gradio が自動で Uvicorn を起動)
293
  ##############################################################################
294
- demo.queue(default_concurrency_limit=2).launch(share=False) # :contentReference[oaicite:7]{index=7}
 
1
+ # app.py — BRA v7 (AIGaming repo) × InstantID × ZeroGPU
2
+ # 2025-06-22
3
 
4
  ##############################################################################
5
+ # 0. diffusers-0.27 互換: cached_download() パッチ
6
  ##############################################################################
7
  from huggingface_hub import hf_hub_download
8
+ import huggingface_hub as _hf
9
+ if not hasattr(_hf, "cached_download"):
10
+ _hf.cached_download = hf_hub_download
 
 
11
 
12
  ##############################################################################
13
+ # 1. ライブラリ
14
  ##############################################################################
15
  import os, io, base64, subprocess, traceback
16
  from pathlib import Path
17
  from typing import Optional
 
18
  import numpy as np
19
+ import torch, gradio as gr, spaces
 
 
20
  from fastapi import FastAPI, UploadFile, File, Form, HTTPException
21
  from PIL import Image
 
22
  from diffusers import (
23
  StableDiffusionControlNetPipeline,
24
  ControlNetModel,
25
  DPMSolverMultistepScheduler,
 
26
  )
27
  from diffusers.loaders import AttnProcsLayers
28
  from insightface.app import FaceAnalysis
 
29
  from realesrgan import RealESRGANer
30
 
31
  ##############################################################################
32
+ # 2. キャッシュパス
33
  ##############################################################################
34
+ ROOT = Path("/data") if Path("/data").exists() else Path.home() / ".cache/instantid"
35
+ MODELS = ROOT / "models"; LORA = ROOT / "lora"; UPSCALE = ROOT / "realesrgan"
36
+ for p in (MODELS, LORA, UPSCALE): p.mkdir(parents=True, exist_ok=True)
 
 
 
 
 
 
 
 
37
 
38
  ##############################################################################
39
+ # 3. モデル ID / ファイル
40
  ##############################################################################
41
+ # --- BRA v7 (公開) ---
42
+ BRA_REPO = "AIGaming/beautiful_realistic_asians" # :contentReference[oaicite:1]{index=1}
43
+ BRA_FILE = "beautifulRealistic_v7.safetensors"
44
+ BRA_REV = "801a9b1999dd7018e58a1e2b432fdccd3d1d723d" # 固定 revision
 
 
45
 
46
+ # --- IP-Adapter 本体 & LoRA ---
47
+ IP_REPO, IP_BIN = "h94/IP-Adapter", "ip-adapter-plus-face_sd15.bin"
48
+ LORA_REPO,IP_LORA = "h94/IP-Adapter-FaceID", "ip-adapter-faceid-plusv2_sd15_lora.safetensors"
49
 
50
+ # --- ControlNet (MediaPipe Face) ---
51
+ CN_REPO, CN_SUBF = "CrucibleAI/ControlNetMediaPipeFace", "diffusion_sd15"
52
 
53
+ # --- Real-ESRGAN ---
54
+ ESRGAN_REPO, ESRGAN_FILE = "aimagelab/realesrgan", "RealESRGAN_x4plus.pth"
55
 
56
  ##############################################################################
57
+ # 4. HF Hub ダウンロード
58
  ##############################################################################
59
+ def dl(repo: str, file: str, sub: str | None = None, rev: str | None = None) -> Path:
60
+ return Path(hf_hub_download(repo, file, subfolder=sub,
61
+ revision=rev, cache_dir=str(MODELS)))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
  ##############################################################################
64
+ # 5. グローバル
65
  ##############################################################################
66
  pipe: Optional[StableDiffusionControlNetPipeline] = None
67
  face_analyser: Optional[FaceAnalysis] = None
68
  upsampler: Optional[RealESRGANer] = None
69
 
70
  ##############################################################################
71
+ # 6. 初期化
72
  ##############################################################################
73
+ def init():
74
  global pipe, face_analyser, upsampler
75
  if pipe is not None:
76
  return
77
+ print("[INIT] downloading models…")
78
 
79
+ # 6-1 BRA v7
80
+ bra_ckpt = dl(BRA_REPO, BRA_FILE, rev=BRA_REV)
81
 
82
+ # 6-2 ControlNet
83
+ cn = ControlNetModel.from_pretrained(
84
+ CN_REPO, subfolder=CN_SUBF, torch_dtype=torch.float16,
85
+ cache_dir=str(MODELS)
 
 
86
  )
87
 
88
+ # 6-3 Pipeline from .safetensors + ControlNet
89
+ pipe_ = StableDiffusionControlNetPipeline.from_single_file(
90
+ bra_ckpt, controlnet=cn, torch_dtype=torch.float16,
91
+ safety_checker=None
 
 
 
 
 
 
 
 
 
 
 
92
  )
93
+ pipe_.scheduler = DPMSolverMultistepScheduler.from_config(pipe_.scheduler.config)
94
 
95
+ # 6-4 IP-Adapter
96
+ ip_bin = dl(IP_REPO, IP_BIN)
97
+ ip_lora = dl(LORA_REPO, IP_LORA)
98
+ pipe_.load_ip_adapter(str(ip_bin.parent), "", ip_bin.name)
99
+ AttnProcsLayers(pipe_.unet.attn_processors).load_lora_weights(
 
 
100
  ip_lora, adapter_name="ip_faceid", safe_load=True
101
  )
102
+ pipe_.set_adapters(["ip_faceid"], adapter_weights=[0.6])
103
+ pipe_.to("cuda"); pipe_ = pipe_
104
 
105
+ pipe = pipe_
 
 
106
  face_analyser = FaceAnalysis(
107
+ name="buffalo_l", root=str(MODELS), providers=["CUDAExecutionProvider"]
108
+ ); face_analyser.prepare(ctx_id=0, det_size=(640,640))
 
 
 
 
 
 
 
 
 
 
109
 
110
+ esr = dl(ESRGAN_REPO, ESRGAN_FILE)
111
+ upsampler = RealESRGANer(scale=4, model_path=str(esr), half=True,
112
+ tile=512, tile_pad=10, pre_pad=0, gpu_id=0)
113
+ print("[INIT] ready.")
114
 
115
  ##############################################################################
116
+ # 7. プロンプト
117
  ##############################################################################
118
+ BASE = "(masterpiece:1.2), best quality, ultra-realistic, RAW photo, 8k, cinematic lighting, textured skin, "
119
+ NEG = "verybadimagenegative_v1.3, ng_deepnegative_v1_75t, (worst quality:2), (low quality:2), lowres, blurry, bad anatomy, bad hands, extra digits, watermark, signature"
 
 
 
 
 
 
 
120
 
121
  ##############################################################################
122
+ # 8. 生成コア
123
  ##############################################################################
124
+ @spaces.GPU(duration=60)
125
+ def generate(face: Image.Image, subj: str, add: str, neg: str,
126
+ cfg: float, ipw: float, steps: int, w: int, h: int,
127
+ up: bool, upf: int, progress=gr.Progress(track_tqdm=True)):
128
+ if pipe is None:
129
+ init()
130
+ if len(face_analyser.get(np.array(face))) == 0:
131
+ raise ValueError("顔が検出できません。他の画像でお試しください。")
132
+ pipe.set_adapters(["ip_faceid"], adapter_weights=[ipw])
133
+ img = pipe(prompt=BASE+subj+", "+add,
134
+ negative_prompt=NEG+", "+neg,
135
+ num_inference_steps=steps, guidance_scale=cfg,
136
+ image=face, width=w, height=h).images[0]
137
+ if up:
138
+ upsampler.scale = 4 if upf==4 else 8
139
+ img, _ = upsampler.enhance(np.array(img)); img = Image.fromarray(img)
140
+ return img
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
  ##############################################################################
143
  # 9. Gradio UI
144
  ##############################################################################
145
+ with gr.Blocks(title="BRA v7 × InstantID (ZeroGPU)") as demo:
146
+ gr.Markdown("## BRA v7 × InstantID")
147
  with gr.Row():
148
+ f = gr.Image(type="pil", label="Face ID"); s = gr.Textbox(label="被写体説明")
149
+ ap = gr.Textbox(label="追加プロンプト"); ng = gr.Textbox(label="追加ネガ")
 
 
150
  with gr.Row():
151
+ cf = gr.Slider(1,20,7.5,0.5,"CFG"); ip = gr.Slider(0.1,1.0,0.6,0.05,"IP-Adapter Weight")
 
152
  with gr.Row():
153
+ st = gr.Slider(10,50,30,1,"Steps"); W = gr.Slider(512,1024,768,64,"W"); H = gr.Slider(512,1024,768,64,"H")
 
 
154
  with gr.Row():
155
+ up = gr.Checkbox(label="Real-ESRGAN"); upf = gr.Radio([4,8], value=4, label="アップスケール")
156
+ btn = gr.Button("Generate"); out = gr.Image(type="pil", label="Result")
157
+ btn.click(generate, [f,s,ap,ng,cf,ip,st,W,H,up,upf], out, show_progress=True)
 
 
 
 
 
 
 
 
158
 
159
  ##############################################################################
160
+ # 10. FastAPI
161
  ##############################################################################
162
  app = FastAPI()
163
 
164
  @app.post("/api/generate")
165
+ async def api_gen(subj: str=Form(...), cfg: float=Form(7.5), stp: int=Form(30),
166
+ ipw: float=Form(0.6), W: int=Form(768), H: int=Form(768),
167
+ file: UploadFile=File(...)):
168
+ img = Image.open(io.BytesIO(await file.read())).convert("RGB")
169
+ res = generate(img, subj, "", "", cfg, ipw, stp, W, H, False, 4)
170
+ buf = io.BytesIO(); res.save(buf,"PNG")
171
+ return {"image":"data:image/png;base64,"+base64.b64encode(buf.getvalue()).decode()}
 
 
 
 
 
 
 
 
 
 
172
 
173
  ##############################################################################
174
+ # 11. Launch
175
  ##############################################################################
176
+ demo.queue(default_concurrency_limit=2).launch(share=False)