Spaces:
Running on Zero
Running on Zero
Upload 6 files
Browse files- .gitattributes +4 -0
- app.py +122 -133
- esempi/8bit.png +3 -0
- esempi/architetto.png +3 -0
- esempi/popart.png +3 -0
- esempi/rinascimentale.png +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,7 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
esempi/8bit.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
esempi/architetto.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
esempi/popart.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
esempi/rinascimentale.png filter=lfs diff=lfs merge=lfs -text
|
app.py
CHANGED
|
@@ -1,189 +1,177 @@
|
|
| 1 |
-
import
|
| 2 |
-
import cv2
|
| 3 |
-
import math
|
| 4 |
import torch
|
|
|
|
| 5 |
import numpy as np
|
| 6 |
-
import
|
|
|
|
|
|
|
| 7 |
import spaces
|
| 8 |
-
from PIL import Image
|
| 9 |
-
|
| 10 |
-
from huggingface_hub import hf_hub_download, snapshot_download
|
| 11 |
from insightface.app import FaceAnalysis
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
from pipeline_stable_diffusion_xl_instantid import StableDiffusionXLInstantIDPipeline
|
| 15 |
|
| 16 |
# ==============================================================================
|
| 17 |
-
# 1. SETUP E
|
| 18 |
# ==============================================================================
|
| 19 |
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
def download_antelopev2():
|
| 24 |
-
"""Scarica automaticamente i modelli di InsightFace se non sono presenti."""
|
| 25 |
-
model_dir = "./models/antelopev2"
|
| 26 |
-
if not os.path.exists(model_dir) or len(os.listdir(model_dir)) < 5:
|
| 27 |
-
print("Scaricamento dei modelli antelopev2 in corso...")
|
| 28 |
-
os.makedirs(model_dir, exist_ok=True)
|
| 29 |
-
snapshot_download(repo_id="DIAMONIK7777/antelopev2", local_dir=model_dir)
|
| 30 |
-
print("Scaricamento completato.")
|
| 31 |
-
return "./"
|
| 32 |
-
|
| 33 |
-
model_root = download_antelopev2()
|
| 34 |
-
|
| 35 |
-
face_app = FaceAnalysis(name='antelopev2', root=model_root, providers=['CPUExecutionProvider'])
|
| 36 |
face_app.prepare(ctx_id=0, det_size=(640, 640))
|
| 37 |
|
| 38 |
-
#
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
"
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
)
|
|
|
|
|
|
|
| 49 |
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
pipe = StableDiffusionXLInstantIDPipeline.from_pretrained(
|
| 53 |
-
"stabilityai/stable-diffusion-xl-base-1.0",
|
| 54 |
-
controlnet=controlnet,
|
| 55 |
torch_dtype=dtype,
|
|
|
|
| 56 |
use_safetensors=True
|
| 57 |
)
|
| 58 |
|
| 59 |
-
#
|
| 60 |
-
|
| 61 |
-
pipe.load_ip_adapter_instantid(face_adapter)
|
| 62 |
-
pipe.to(device)
|
| 63 |
-
|
| 64 |
-
print("Pipeline pronta!")
|
| 65 |
-
|
| 66 |
|
| 67 |
# ==============================================================================
|
| 68 |
-
# 2. CONFIGURAZIONE STILI
|
| 69 |
# ==============================================================================
|
| 70 |
-
|
| 71 |
STYLES = {
|
| 72 |
"Stile 1: Bit-Builder (8-Bit/Pixel Art)": {
|
|
|
|
| 73 |
"repo_id": "nerijs/pixel-art-xl",
|
| 74 |
"weight_name": "pixel-art-xl.safetensors",
|
| 75 |
-
"prompt": "pixelart style,
|
| 76 |
-
"negative_prompt": "ugly, deformed, lowres,
|
| 77 |
},
|
| 78 |
"Stile 2: Il Costruttore Rinascimentale": {
|
|
|
|
| 79 |
"repo_id": "davidmoref/sdxl-lora-adapter-renaissance",
|
| 80 |
"weight_name": "pytorch_lora_weights.safetensors",
|
| 81 |
-
"prompt": "renaissance oil painting,
|
| 82 |
-
"negative_prompt": "ugly, deformed, modern, photography, bad anatomy, worst quality, low quality,
|
| 83 |
},
|
| 84 |
-
"Stile 3:
|
| 85 |
-
"
|
| 86 |
-
"weight_name": "flat_illustration_sdxl.safetensors",
|
| 87 |
-
"prompt": "flat vector art, Modern flat vector illustration of a tech architect holding a smartphone. He wears a minimalist grey suit jacket featuring a small geometric green, white, and red enamel badge. Clean geometric lines, industrial design background, minimalistic.",
|
| 88 |
-
"negative_prompt": "ugly, deformed, photorealistic, 3d render, bad anatomy, worst quality, low quality, watermark, text"
|
| 89 |
-
},
|
| 90 |
-
"Stile 4: Genio Creativo (Pop-Art)": {
|
| 91 |
"repo_id": None,
|
| 92 |
"weight_name": None,
|
| 93 |
-
"prompt": "vintage pop art, Comic book style pop-art
|
| 94 |
-
"negative_prompt": "ugly, deformed, photorealistic,
|
| 95 |
}
|
| 96 |
}
|
| 97 |
|
| 98 |
# ==============================================================================
|
| 99 |
-
# 3.
|
| 100 |
# ==============================================================================
|
| 101 |
|
| 102 |
-
def
|
| 103 |
-
"""
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
@spaces.GPU(duration=120)
|
| 129 |
def generate_avatar(user_image, selected_style):
|
| 130 |
if user_image is None:
|
| 131 |
raise gr.Error("Per favore, carica un'immagine del volto.")
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
if len(faces_resized) > 0:
|
| 147 |
-
face_kps = sorted(faces_resized, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1].kps
|
| 148 |
-
|
| 149 |
-
kps_image = draw_kps(user_image_resized, face_kps)
|
| 150 |
-
|
| 151 |
-
# 2. Gestione LoRA
|
| 152 |
style_config = STYLES[selected_style]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
repo_id = style_config["repo_id"]
|
| 154 |
-
|
|
|
|
| 155 |
if repo_id:
|
| 156 |
try:
|
| 157 |
print(f"Scaricamento/Caricamento LoRA {repo_id}...")
|
| 158 |
pipe.load_lora_weights(repo_id, weight_name=style_config["weight_name"])
|
| 159 |
except Exception as e:
|
| 160 |
print(f"Errore caricamento LoRA da HF: {e}")
|
| 161 |
-
|
| 162 |
-
#
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
controlnet_conditioning_scale=0.8,
|
| 174 |
num_inference_steps=30,
|
| 175 |
-
guidance_scale=5.0,
|
| 176 |
).images[0]
|
| 177 |
|
|
|
|
| 178 |
if repo_id:
|
| 179 |
try:
|
| 180 |
pipe.unload_lora_weights()
|
| 181 |
except:
|
| 182 |
pass
|
| 183 |
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
return image
|
| 187 |
|
| 188 |
# ==============================================================================
|
| 189 |
# 4. INTERFACCIA GRADIO (FRONTEND)
|
|
@@ -205,11 +193,12 @@ theme = gr.themes.Soft(primary_hue="blue", secondary_hue="emerald").set(
|
|
| 205 |
with gr.Blocks(css=custom_css, theme=theme) as demo:
|
| 206 |
with gr.Column(elem_classes="container"):
|
| 207 |
gr.HTML("<h1 class='header-title'>🇮🇹 Italian Builders Avatar Generator 🇮🇹</h1>")
|
| 208 |
-
gr.HTML("<p class='header-subtitle'>Trasforma il tuo selfie
|
| 209 |
|
| 210 |
with gr.Row():
|
| 211 |
with gr.Column(scale=1):
|
| 212 |
input_image = gr.Image(type="pil", label="1. Carica il tuo Selfie")
|
|
|
|
| 213 |
with gr.Column(scale=1):
|
| 214 |
style_selector = gr.Radio(
|
| 215 |
choices=list(STYLES.keys()),
|
|
@@ -220,7 +209,7 @@ with gr.Blocks(css=custom_css, theme=theme) as demo:
|
|
| 220 |
generate_button = gr.Button("Genera il tuo Avatar 🚀", elem_classes="generate-btn", variant="primary")
|
| 221 |
|
| 222 |
with gr.Column(scale=1):
|
| 223 |
-
output_image = gr.Image(label="Il tuo Avatar
|
| 224 |
|
| 225 |
generate_button.click(
|
| 226 |
fn=generate_avatar,
|
|
|
|
| 1 |
+
import gradio as gr
|
|
|
|
|
|
|
| 2 |
import torch
|
| 3 |
+
import cv2
|
| 4 |
import numpy as np
|
| 5 |
+
import os
|
| 6 |
+
from PIL import Image, ImageDraw, ImageFilter
|
| 7 |
+
from diffusers import StableDiffusionXLInpaintPipeline
|
| 8 |
import spaces
|
|
|
|
|
|
|
|
|
|
| 9 |
from insightface.app import FaceAnalysis
|
| 10 |
+
import insightface
|
| 11 |
+
from huggingface_hub import hf_hub_download
|
|
|
|
| 12 |
|
| 13 |
# ==============================================================================
|
| 14 |
+
# 1. SETUP DELL'AMBIENTE E DOWNLOAD MODELLI
|
| 15 |
# ==============================================================================
|
| 16 |
|
| 17 |
+
# Inizializza l'analizzatore di volti (InsightFace)
|
| 18 |
+
print("Inizializzazione FaceAnalysis...")
|
| 19 |
+
face_app = FaceAnalysis(name='antelopev2', root='./', providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
face_app.prepare(ctx_id=0, det_size=(640, 640))
|
| 21 |
|
| 22 |
+
# Download e caricamento del modello Inswapper
|
| 23 |
+
print("Download e caricamento Inswapper...")
|
| 24 |
+
try:
|
| 25 |
+
inswapper_path = hf_hub_download(repo_id="ezioruan/inswapper_128.onnx", filename="inswapper_128.onnx")
|
| 26 |
+
face_swapper = insightface.model_zoo.get_model(inswapper_path, providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
|
| 27 |
+
except Exception as e:
|
| 28 |
+
print(f"Errore caricamento Inswapper: {e}")
|
| 29 |
+
face_swapper = None
|
| 30 |
+
|
| 31 |
+
# Caricamento Pipeline Inpainting SDXL
|
| 32 |
+
print("Caricamento pipeline SDXL Inpaint...")
|
| 33 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 34 |
+
dtype = torch.float16 if torch.cuda.is_available() else "float32"
|
| 35 |
|
| 36 |
+
pipe = StableDiffusionXLInpaintPipeline.from_pretrained(
|
| 37 |
+
"diffusers/stable-diffusion-xl-1.0-inpainting-0.1",
|
|
|
|
|
|
|
|
|
|
| 38 |
torch_dtype=dtype,
|
| 39 |
+
variant="fp16" if torch.cuda.is_available() else None,
|
| 40 |
use_safetensors=True
|
| 41 |
)
|
| 42 |
|
| 43 |
+
# Rimuovi il limitatore di sicurezza se crea problemi (opzionale)
|
| 44 |
+
pipe.watermark = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
# ==============================================================================
|
| 47 |
+
# 2. CONFIGURAZIONE STILI E TEMPLATE
|
| 48 |
# ==============================================================================
|
| 49 |
+
# I template sono le immagini pre-generate fornite dall'utente.
|
| 50 |
STYLES = {
|
| 51 |
"Stile 1: Bit-Builder (8-Bit/Pixel Art)": {
|
| 52 |
+
"template": "esempi/8bit.png",
|
| 53 |
"repo_id": "nerijs/pixel-art-xl",
|
| 54 |
"weight_name": "pixel-art-xl.safetensors",
|
| 55 |
+
"prompt": "pixelart style, 8-bit game style face, highly detailed, perfect pixel art shading, 8-bit retro aesthetic, matching lighting",
|
| 56 |
+
"negative_prompt": "ugly, deformed, lowres, realistic, photographic, 3d render, photo, photorealistic"
|
| 57 |
},
|
| 58 |
"Stile 2: Il Costruttore Rinascimentale": {
|
| 59 |
+
"template": "esempi/rinascimentale.png",
|
| 60 |
"repo_id": "davidmoref/sdxl-lora-adapter-renaissance",
|
| 61 |
"weight_name": "pytorch_lora_weights.safetensors",
|
| 62 |
+
"prompt": "renaissance oil painting, oil painting face, elegant brush strokes, chiaroscuro lighting, classic masterpiece, perfectly integrated face",
|
| 63 |
+
"negative_prompt": "ugly, deformed, modern, photography, bad anatomy, worst quality, low quality, pixel art, cartoon"
|
| 64 |
},
|
| 65 |
+
"Stile 3: Genio Creativo (Pop-Art)": {
|
| 66 |
+
"template": "esempi/popart.png",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
"repo_id": None,
|
| 68 |
"weight_name": None,
|
| 69 |
+
"prompt": "vintage pop art, Comic book style pop-art face, vibrant colors, halftone dots, graphic illustration, bold lines",
|
| 70 |
+
"negative_prompt": "ugly, deformed, photorealistic, realistic photography, oil painting, 3d render"
|
| 71 |
}
|
| 72 |
}
|
| 73 |
|
| 74 |
# ==============================================================================
|
| 75 |
+
# 3. FUNZIONI CORE DELL'APPLICAZIONE
|
| 76 |
# ==============================================================================
|
| 77 |
|
| 78 |
+
def create_face_mask(image, bbox, expansion=0.2, blur_radius=15):
|
| 79 |
+
"""
|
| 80 |
+
Crea una maschera sfumata attorno al volto per l'inpainting.
|
| 81 |
+
"""
|
| 82 |
+
mask = Image.new("L", image.size, 0)
|
| 83 |
+
draw = ImageDraw.Draw(mask)
|
| 84 |
+
|
| 85 |
+
x1, y1, x2, y2 = bbox
|
| 86 |
+
w = x2 - x1
|
| 87 |
+
h = y2 - y1
|
| 88 |
+
|
| 89 |
+
# Espandi leggermente la bounding box
|
| 90 |
+
nx1 = max(0, x1 - w * expansion)
|
| 91 |
+
ny1 = max(0, y1 - h * expansion)
|
| 92 |
+
nx2 = min(image.width, x2 + w * expansion)
|
| 93 |
+
ny2 = min(image.height, y2 + h * (expansion * 1.5)) # Espandi un po' di più verso il basso per il mento
|
| 94 |
+
|
| 95 |
+
# Disegna un ovale
|
| 96 |
+
draw.ellipse([nx1, ny1, nx2, ny2], fill=255)
|
| 97 |
+
|
| 98 |
+
# Sfuma i bordi per fondere meglio
|
| 99 |
+
mask = mask.filter(ImageFilter.GaussianBlur(blur_radius))
|
| 100 |
+
return mask
|
| 101 |
+
|
| 102 |
+
@spaces.GPU(duration=60)
|
|
|
|
|
|
|
| 103 |
def generate_avatar(user_image, selected_style):
|
| 104 |
if user_image is None:
|
| 105 |
raise gr.Error("Per favore, carica un'immagine del volto.")
|
| 106 |
+
|
| 107 |
+
if face_swapper is None:
|
| 108 |
+
raise gr.Error("Modello Inswapper non caricato correttamente.")
|
| 109 |
+
|
| 110 |
+
# 1. Analisi del volto utente
|
| 111 |
+
cv_user_image = cv2.cvtColor(np.array(user_image), cv2.COLOR_RGB2BGR)
|
| 112 |
+
user_faces = face_app.get(cv_user_image)
|
| 113 |
+
if len(user_faces) == 0:
|
| 114 |
+
raise gr.Error("Nessun volto rilevato nella tua foto. Riprova con un selfie più chiaro.")
|
| 115 |
+
|
| 116 |
+
# Prendi il volto più grande
|
| 117 |
+
user_face = sorted(user_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
|
| 118 |
+
|
| 119 |
+
# 2. Caricamento e analisi del Template
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
style_config = STYLES[selected_style]
|
| 121 |
+
template_path = style_config["template"]
|
| 122 |
+
|
| 123 |
+
if not os.path.exists(template_path):
|
| 124 |
+
raise gr.Error(f"Errore: il file di template {template_path} non esiste.")
|
| 125 |
+
|
| 126 |
+
template_pil = Image.open(template_path).convert("RGB")
|
| 127 |
+
cv_template_image = cv2.cvtColor(np.array(template_pil), cv2.COLOR_RGB2BGR)
|
| 128 |
+
|
| 129 |
+
template_faces = face_app.get(cv_template_image)
|
| 130 |
+
if len(template_faces) == 0:
|
| 131 |
+
raise gr.Error("Impossibile trovare un volto nel template di base.")
|
| 132 |
+
template_face = sorted(template_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
|
| 133 |
+
|
| 134 |
+
# 3. Swap del Volto (Fotorealistico)
|
| 135 |
+
print("Eseguendo Face Swap...")
|
| 136 |
+
swapped_cv = face_swapper.get(cv_template_image, template_face, user_face, paste_back=True)
|
| 137 |
+
swapped_pil = Image.fromarray(cv2.cvtColor(swapped_cv, cv2.COLOR_BGR2RGB))
|
| 138 |
+
|
| 139 |
+
# 4. Creazione Maschera per l'Inpainting
|
| 140 |
+
mask_pil = create_face_mask(template_pil, template_face.bbox)
|
| 141 |
+
|
| 142 |
+
# 5. Stylization tramite Inpainting
|
| 143 |
repo_id = style_config["repo_id"]
|
| 144 |
+
|
| 145 |
+
# Carica il LoRA dello stile se necessario
|
| 146 |
if repo_id:
|
| 147 |
try:
|
| 148 |
print(f"Scaricamento/Caricamento LoRA {repo_id}...")
|
| 149 |
pipe.load_lora_weights(repo_id, weight_name=style_config["weight_name"])
|
| 150 |
except Exception as e:
|
| 151 |
print(f"Errore caricamento LoRA da HF: {e}")
|
| 152 |
+
|
| 153 |
+
# Sposta la pipeline su GPU per l'inferenza
|
| 154 |
+
pipe.to(device)
|
| 155 |
+
|
| 156 |
+
print("Inizio Inpainting per fondere lo stile...")
|
| 157 |
+
stylized_image = pipe(
|
| 158 |
+
prompt=style_config["prompt"],
|
| 159 |
+
negative_prompt=style_config["negative_prompt"],
|
| 160 |
+
image=swapped_pil,
|
| 161 |
+
mask_image=mask_pil,
|
| 162 |
+
strength=0.35, # Bassa strength: mantiene forte l'identità, applica solo lo stile superficiale
|
| 163 |
+
guidance_scale=7.5,
|
|
|
|
| 164 |
num_inference_steps=30,
|
|
|
|
| 165 |
).images[0]
|
| 166 |
|
| 167 |
+
# Scarica il LoRA per evitare conflitti al prossimo giro
|
| 168 |
if repo_id:
|
| 169 |
try:
|
| 170 |
pipe.unload_lora_weights()
|
| 171 |
except:
|
| 172 |
pass
|
| 173 |
|
| 174 |
+
return stylized_image
|
|
|
|
|
|
|
| 175 |
|
| 176 |
# ==============================================================================
|
| 177 |
# 4. INTERFACCIA GRADIO (FRONTEND)
|
|
|
|
| 193 |
with gr.Blocks(css=custom_css, theme=theme) as demo:
|
| 194 |
with gr.Column(elem_classes="container"):
|
| 195 |
gr.HTML("<h1 class='header-title'>🇮🇹 Italian Builders Avatar Generator 🇮🇹</h1>")
|
| 196 |
+
gr.HTML("<p class='header-subtitle'>Trasforma il tuo selfie e unisciti alla community.</p>")
|
| 197 |
|
| 198 |
with gr.Row():
|
| 199 |
with gr.Column(scale=1):
|
| 200 |
input_image = gr.Image(type="pil", label="1. Carica il tuo Selfie")
|
| 201 |
+
|
| 202 |
with gr.Column(scale=1):
|
| 203 |
style_selector = gr.Radio(
|
| 204 |
choices=list(STYLES.keys()),
|
|
|
|
| 209 |
generate_button = gr.Button("Genera il tuo Avatar 🚀", elem_classes="generate-btn", variant="primary")
|
| 210 |
|
| 211 |
with gr.Column(scale=1):
|
| 212 |
+
output_image = gr.Image(label="Il tuo Avatar", interactive=False)
|
| 213 |
|
| 214 |
generate_button.click(
|
| 215 |
fn=generate_avatar,
|
esempi/8bit.png
ADDED
|
|
Git LFS Details
|
esempi/architetto.png
ADDED
|
|
Git LFS Details
|
esempi/popart.png
ADDED
|
|
Git LFS Details
|
esempi/rinascimentale.png
ADDED
|
|
Git LFS Details
|