In diesem Beitrag soll es darum gehen, wie man die Fooocus API nutzen kann, auch wenn ich davon abrate. Jedoch vorweg erst Mal, was ist Fooocus eigentlich? Fooocus ist eine „KI“-Software zur Bilderzeugung, welche aus einem Text- und/oder Bild-Prompt Bilder erzeugen kann. Selber gibt der Entwickler an, dass es ein „neu-denken“ von Stable Diffusion und Midjourney ist. Dabei nutzt es selber Stable Diffusion bzw. setzt darauf auf. Die Benutzung erinnert jedoch eher an die Einfachheit von Midjourney, da viele Dinge im Hintergrund passieren, die dafür sorgen, dass man schnell und unkompliziert hervorragende Ergebnisse erhält. So werden an jeden Prompt weitere Schlüsselwörter angehangen und durch die sogenannten „Styles“ erweitert, welche wiederum Erweiterungen für den Prompt sind durch weitere Schlüsselwörter. Auch sind die ganzen Abläufe und Schritte dahin vordefiniert, so dass man sich darum nicht kümmern muss. Man kann jedoch an einigen Stellschrauben noch drehen, was aber nichts im Vergleich zu der Komplexität von z.B. Automatic1111 oder ComfyUI ist.
Warum man die Fooocus API nicht nutzen sollte
So liegt der Gedanke auch Nahe, dass man ebenfalls die Fooocus API nutzen möchte. Besonders da ein Link „Use via API“ einen regelrecht dazu einlädt. Aber lass dich nicht davon täuschen und verwerfe diese Idee am besten direkt wieder! Die „Dokumentation“, welche sich hinter dem Link versteckt, ist mehr als dürftig. Und einige Herausforderungen auf dem Weg Bilder per API zu erzeugen lassen einen schnell die Lust verlieren. Für all jene, die dennoch ihr Glück versuchen wollen, ist dieser Blog-Beitrag. Allen Anderen rate ich, guckt Euch ComfyUI an, da gibt es sogar Skript-Beispiele, wie man die API nutzen kann.
Die Installation
Die Installation von Fooocus selber ist erstaunlich einfach: Man lädt es sich runter, entpackt es und startet es. Das war es. Zumindest beim ersten Ausführen lädt er noch einige Dinge runter, was je nach Internet-Anbindung einige Zeit dauern kann, da es mehrere Gigabytes sind. Irgendwann wird dann eine Web UI im Browser geöffnet und man kann loslegen. Die Download-Links findet man im offiziellen Foocus-GitHub-Repo.
Code-Beispiel
Nun aber ans Eingemachte. Als Beispiel soll ein kurzes Skript dienen, welches per API alle bekannten Styles (derzeit 276) abfragt und für jedes mit dem Prompt „Husky puppy“ ein Bild erzeugt und das Bild in einem Unterverzeichnis speichert:
# Fooocus 2.4.3 from gradio_client import Client from pathlib import Path import random import shutil from slugify import slugify # Constants for the placeholder PNG image data MIN_PNG = ('data:image/png;base64,' 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=') def get_all_styles(client): """Fetches all available styles from the client and returns them sorted.""" result = client.predict(fn_index=18) return sorted(style[0] for style in result['choices']) def predict(client, prompt='', negative_prompt='', style=None, performance='Speed', aspect_ratio='1024×1024', num_images=1, output_format='png', seed=0, image_sharpness=2, guidance_scale=3, base_model='juggernautXL_v8Rundiffusion.safetensors', refiner='None', refiner_switch_at=0.8, lora1='sd_xl_offset_example-lora_1.0.safetensors', lora1_weight=0.25, lora2='None', lora2_weight=1, lora3='None', lora3_weight=1, lora4='None', lora4_weight=1, lora5='None', lora5_weight=1): """Generates images based on the given parameters and returns the result.""" # set parameters client.predict( False, # bool in 'Generate Image Grid for Each Batch' Checkbox component prompt, # str in 'parameter_11' Textbox component negative_prompt, # str in 'Negative Prompt' Textbox component style or [], # List[str] in 'Selected Styles' Checkboxgroup component performance, # str in 'Performance' Radio component aspect_ratio, # str in 'Aspect Ratios' Radio component num_images, # int | float (numeric value between 1 and 32) in 'Image Number' Slider component output_format, # str in 'Output Format' Radio component str(seed), # str in 'Seed' Textbox component True, # bool in 'Read wildcards in order' Checkbox component image_sharpness, # int | float (numeric value between 0.0 and 30.0) in 'Image Sharpness' Slider component guidance_scale, # int | float (numeric value between 1.0 and 30.0) in 'Guidance Scale' Slider component base_model, # str (Option from: ['animaPencilXL_v100.safetensors', # 'juggernautXL_v8Rundiffusion.safetensors', 'realisticStockPhoto_v20.safetensors', # 'sd_xl_base_1.0_0.9vae.safetensors', 'sd_xl_refiner_1.0_0.9vae.safetensors']) # in 'Base Model (SDXL only)' Dropdown component refiner, # str (Option from: ['None', 'animaPencilXL_v100.safetensors', # 'juggernautXL_v8Rundiffusion.safetensors', 'realisticStockPhoto_v20.safetensors', # 'sd_xl_base_1.0_0.9vae.safetensors', 'sd_xl_refiner_1.0_0.9vae.safetensors']) # in 'Refiner (SDXL or SD 1.5)' Dropdown component refiner_switch_at, # int | float (numeric value between 0.1 and 1.0) in 'Refiner Switch At' Slider component lora1 != 'None', # bool in 'Enable' Checkbox component lora1, # str (Option from: ['None', 'sd_xl_offset_example-lora_1.0.safetensors', # 'SDXL_FILM_PHOTOGRAPHY_STYLE_BetaV0.4.safetensors', 'sdxl_lcm_lora.safetensors']) # in 'LoRA 1' Dropdown component lora1_weight, # int | float (numeric value between -2 and 2) in 'Weight' Slider component lora2 != 'None', # bool in 'Enable' Checkbox component lora2, # str (Option from: ['None', 'sd_xl_offset_example-lora_1.0.safetensors', # 'SDXL_FILM_PHOTOGRAPHY_STYLE_BetaV0.4.safetensors', 'sdxl_lcm_lora.safetensors']) # in 'LoRA 2' Dropdown component lora2_weight, # int | float (numeric value between -2 and 2) in 'Weight' Slider component lora3 != 'None', # bool in 'Enable' Checkbox component lora3, # str (Option from: ['None', 'sd_xl_offset_example-lora_1.0.safetensors', # 'SDXL_FILM_PHOTOGRAPHY_STYLE_BetaV0.4.safetensors', 'sdxl_lcm_lora.safetensors']) # in 'LoRA 3' Dropdown component lora3_weight, # int | float (numeric value between -2 and 2) in 'Weight' Slider component lora4 != 'None', # bool in 'Enable' Checkbox component lora4, # str (Option from: ['None', 'sd_xl_offset_example-lora_1.0.safetensors', # 'SDXL_FILM_PHOTOGRAPHY_STYLE_BetaV0.4.safetensors', 'sdxl_lcm_lora.safetensors']) # in 'LoRA 4' Dropdown component lora4_weight, # int | float (numeric value between -2 and 2) in 'Weight' Slider component lora5 != 'None', # bool in 'Enable' Checkbox component lora5, # str (Option from: ['None', 'sd_xl_offset_example-lora_1.0.safetensors', # 'SDXL_FILM_PHOTOGRAPHY_STYLE_BetaV0.4.safetensors', 'sdxl_lcm_lora.safetensors']) # in 'LoRA 5' Dropdown component lora5_weight, # int | float (numeric value between -2 and 2) in 'Weight' Slider component False, # bool in 'Input Image' Checkbox component '', # str in 'parameter_91' Textbox component 'Disabled', # str in 'Upscale or Variation:' Radio component MIN_PNG, # str (filepath or URL to image) in 'Drag above image to here' Image component [], # List[str] in 'Outpaint Direction' Checkboxgroup component MIN_PNG, # str (filepath or URL to image) # in 'Drag inpaint or outpaint image to here' Image component '', # str in 'Inpaint Additional Prompt' Textbox component MIN_PNG, # str (filepath or URL to image) in 'Mask Upload' Image component True, # bool in 'Disable Preview' Checkbox component True, # bool in 'Disable Intermediate Results' Checkbox component False, # bool in 'Disable seed increment' Checkbox component False, # bool in 'Black Out NSFW' Checkbox component 1.5, # int | float (numeric value between 0.1 and 3.0) # in 'Positive ADM Guidance Scaler' Slider component 0.8, # int | float (numeric value between 0.1 and 3.0) # in 'Negative ADM Guidance Scaler' Slider component 0.3, # int | float (numeric value between 0.0 and 1.0) # in 'ADM Guidance End At Step' Slider component 7, # int | float (numeric value between 1.0 and 30.0) # in 'CFG Mimicking from TSNR' Slider component 1, # int | CLIP Skip Slider (numeric value between 1 and 12) 'dpmpp_2m_sde_gpu', # str (Option from: ['euler', 'euler_ancestral', 'heun', 'heunpp2', 'dpm_2', # 'dpm_2_ancestral', 'lms', 'dpm_fast', 'dpm_adaptive', 'dpmpp_2s_ancestral', 'dpmpp_sde', # 'dpmpp_sde_gpu', 'dpmpp_2m', 'dpmpp_2m_sde', 'dpmpp_2m_sde_gpu', 'dpmpp_3m_sde', # 'dpmpp_3m_sde_gpu', 'ddpm', 'lcm', 'ddim', 'uni_pc', 'uni_pc_bh2']) # in 'Sampler' Dropdown component 'karras', # str (Option from: ['normal', 'karras', 'exponential', 'sgm_uniform', 'simple', # 'ddim_uniform', 'lcm', 'turbo']) in 'Scheduler' Dropdown component 'Default (model)', # str | (Option from: ['Default (model)']) in 'VAE' Dropdown component -1, # int | float (numeric value between -1 and 200) # in 'Forced Overwrite of Sampling Step' Slider component -1, # int | float (numeric value between -1 and 200) # in 'Forced Overwrite of Refiner Switch Step' Slider component -1, # int | float (numeric value between -1 and 200) # in 'Forced Overwrite of Generating Width' Slider component -1, # int | float (numeric value between -1 and 200) # in 'Forced Overwrite of Generating Height' Slider component -1, # int | float (numeric value between -1 and 200) # in 'Forced Overwrite of Denoising Strength of "Vary"' Slider component -1, # int | float (numeric value between -1 and 200) # in 'Forced Overwrite of Denoising Strength of "Upscale"' Slider component False, # bool in 'Mixing Image Prompt and Vary/Upscale' Checkbox component False, # bool in 'Mixing Image Prompt and Inpaint' Checkbox component False, # bool in 'Debug Preprocessors' Checkbox component False, # bool in 'Skip Preprocessors' Checkbox component 64, # int | float (numeric value between 1 and 255) in 'Canny Low Threshold' Slider component 128, # int | float (numeric value between 1 and 255) in 'Canny High Threshold' Slider component 'joint', # str (Option from: ['joint', 'separate', 'vae']) # in 'Refiner swap method' Dropdown component 0.25, # int | float (numeric value between 0.0 and 1.0) # in 'Softness of ControlNet' Slider component False, # bool in 'Enabled' Checkbox component 1.01, # int | float (numeric value between 0 and 2) in 'B1' Slider component 1.02, # int | float (numeric value between 0 and 2) in 'B2' Slider component 0.99, # int | float (numeric value between 0 and 4) in 'S1' Slider component 0.95, # int | float (numeric value between 0 and 4) in 'S2' Slider component False, # bool in 'Debug Inpaint Preprocessing' Checkbox component False, # bool in 'Disable initial latent in inpaint' Checkbox component 'None', # str (Option from: ['None', 'v1', 'v2.5', 'v2.6']) in 'Inpaint Engine' Dropdown component 1, # int | float (numeric value between 0.0 and 1.0) # in 'Inpaint Denoising Strength' Slider component 0.618, # int | float (numeric value between 0.0 and 1.0) # in 'Inpaint Respective Field' Slider component False, # bool in 'Enable Mask Upload' Checkbox component False, # bool in 'Invert Mask' Checkbox component 0, # int | float (numeric value between -64 and 64) in 'Mask Erode or Dilate' Slider component True, # bool in 'Save Metadata to Images' Checkbox component 'fooocus', # str in 'Metadata Scheme' Radio component MIN_PNG, # str (filepath or URL to image) in 'Image' Image component 0, # int | float (numeric value between 0.0 and 1.0) in 'Stop At' Slider component 0, # int | float (numeric value between 0.0 and 2.0) in 'Weight' Slider component 'ImagePrompt', # str in 'Type' Radio component MIN_PNG, # str (filepath or URL to image) in 'Image' Image component 0, # int | float (numeric value between 0.0 and 1.0) in 'Stop At' Slider component 0, # int | float (numeric value between 0.0 and 2.0) in 'Weight' Slider component 'ImagePrompt', # str in 'Type' Radio component MIN_PNG, # str (filepath or URL to image) in 'Image' Image component 0, # int | float (numeric value between 0.0 and 1.0) in 'Stop At' Slider component 0, # int | float (numeric value between 0.0 and 2.0) in 'Weight' Slider component 'ImagePrompt', # str in 'Type' Radio component MIN_PNG, # str (filepath or URL to image) in 'Image' Image component 0, # int | float (numeric value between 0.0 and 1.0) in 'Stop At' Slider component 0, # int | float (numeric value between 0.0 and 2.0) in 'Weight' Slider component 'ImagePrompt', # str in 'Type' Radio component fn_index=46) # generate image(s) result = client.predict(fn_index=47) return result def main(): """Main function to generate and save images for all available styles.""" prompt = 'Husky puppy' negative_prompt = '' performance = 'Speed' # Speed / Quality aspect_ratio = '896×1152' # 7:9 num_images = 1 seed = random.randint(0, 2 ** 32 - 1) image_sharpness = 2 guidance_scale = 4 base_model = 'realisticStockPhoto_v20.safetensors' lora1 = 'SDXL_FILM_PHOTOGRAPHY_STYLE_BetaV0.4.safetensors' lora1_weight = 0.25 cwd = Path.cwd() base_path = cwd / 'images' / 'all_styles' base_path.mkdir(exist_ok=True) client = Client('http://localhost:7865', serialize=False) for style in get_all_styles(client): file_path = base_path / f'{slugify(style)}_{seed}.png' print(f'Generating image for style: {style}') result = predict(client, prompt=prompt, negative_prompt=negative_prompt, style=[style], performance=performance, aspect_ratio=aspect_ratio, num_images=num_images, seed=seed, image_sharpness=image_sharpness, guidance_scale=guidance_scale, base_model=base_model, lora1=lora1, lora1_weight=lora1_weight) src_file_path = result[3]['value'][0]['name'] shutil.copy(src_file_path, file_path) if __name__ == '__main__': main()
Dieses Skript auszuführen stellt einen jedoch bereits vor die ersten Herausforderungen: Die aktuelle Version des gradio-client funktioniert nicht mit Fooocus. Derzeit (Fooocus 2.4.§) muss man die Version 0.5.3 installieren, da es sonst zu Fehlern in der Kommunikation kommt. Ebenfalls zu Fehlern kommt es nach dem Erzeugen des ersten Bildes, wenn man den Client nicht mit serialize=False
aufruft. Dies führt jedoch dazu, dass man an jeder Stelle, an welcher man ein Bild übergeben muss, nun eine serialisierte Version eines Bildes (in dem Fall ein transparentes PNG) übergeben muss, wodurch man (vermutlich) einige der Features nicht mehr anständig per API nutzen kann. Zumindest habe ich es so keinen FaceSwap mehr hinbekommen und hatte schnell die Motivation verloren da weiter zu experimentieren.
Hinweis: Mit Version 2.2.0 von Fooocus hat sich die Verwendung der API zum Erstellen von Bildern verändert, so muss man nun erst Mal alle Parameter setzen und dann mit der Folge-Funktion das Generieren starten. Dies passierte vorher über ein und die selbe Funktion. Diese gibt nun jedoch lediglich ein leeres Tuple zurück.
Fazit
Sieht man nun davon ab, dass man ihm per API nicht oder nur eingeschränkt Bilder übergeben kann, kann man jedoch den Rest recht gut beeinflussen und so automatisiert einige Einstellungen und Stellschrauben testen. Für die Vergleichbarkeit sollte man immer den gleichen Seed benutzen, da bei gleicher Eingabe (Prompt, Styles, Parameter, Model usw. usf.) und gleichem Seed das Ergebnis das Gleiche ist.
Durch minimale Anpassungen sollte es dir so möglich sein, verschiedene Stellschrauben einfach und schnell zu vergleichen. Wobei schnell natürlich auch davon abhängt, wie gut Deine Grafikkarte ist. In meinem Fall dauert es knapp 10 Sekunden ein Bild mit dem obigen Skript zu erstellen.
Solltest du noch mehr über die API rausfinden oder findest du, dass ein Aspekt nicht ausreichend oder gar falsch beleuchtet wurde, lass es mich gerne in den Kommentaren wissen.