Upload 8 files
Browse files- app.py +79 -0
- app_enhance.py +221 -0
- app_upscale.py +148 -0
- croper.py +135 -0
- enhance_utils.py +95 -0
- requirements.txt +12 -0
- segment_utils.py +123 -0
- themes.py +54 -0
app.py
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Image Enhancer Main App
|
3 |
+
-----------------------
|
4 |
+
Aplikasi utama Gradio untuk menjalankan UI image enhancer berbasis tab.
|
5 |
+
Untuk versi ini, hanya ada 1 tab "Enhance" yang memanggil demo dari modul app_enhance.
|
6 |
+
|
7 |
+
Created by _drat | 2025
|
8 |
+
"""
|
9 |
+
|
10 |
+
import gradio as gr
|
11 |
+
|
12 |
+
from app_enhance import create_demo as create_demo_enhance # Import fungsi untuk membangun demo UI 'Enhance'
|
13 |
+
# from app_upscale import create_demo as create_demo_upscale # Import fungsi untuk membangun demo UI 'Upscale'
|
14 |
+
from themes import IndonesiaTheme # Impor tema custom
|
15 |
+
|
16 |
+
import warnings
|
17 |
+
warnings.filterwarnings("ignore")
|
18 |
+
|
19 |
+
# CSS untuk styling antarmuka
|
20 |
+
css = """
|
21 |
+
#col-left, #col-mid {
|
22 |
+
margin: 0 auto;
|
23 |
+
max-width: 400px;
|
24 |
+
padding: 10px;
|
25 |
+
border-radius: 15px;
|
26 |
+
background-color: #f9f9f9;
|
27 |
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
28 |
+
}
|
29 |
+
#col-right {
|
30 |
+
margin: 0 auto;
|
31 |
+
max-width: 400px;
|
32 |
+
padding: 10px;
|
33 |
+
border-radius: 15px;
|
34 |
+
background: linear-gradient(180deg, #B6BBC4, #EEEEEE);
|
35 |
+
color: white;
|
36 |
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
37 |
+
}
|
38 |
+
#col-bott {
|
39 |
+
margin: 0 auto;
|
40 |
+
padding: 10px;
|
41 |
+
border-radius: 15px;
|
42 |
+
background-color: #f9f9f9;
|
43 |
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
44 |
+
}
|
45 |
+
#banner {
|
46 |
+
width: 100%;
|
47 |
+
text-align: center;
|
48 |
+
margin-bottom: 20px;
|
49 |
+
}
|
50 |
+
#run-button {
|
51 |
+
background-color: #ff4b5c;
|
52 |
+
color: white;
|
53 |
+
font-weight: bold;
|
54 |
+
padding: 30px;
|
55 |
+
border-radius: 10px;
|
56 |
+
cursor: pointer;
|
57 |
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
58 |
+
}
|
59 |
+
#footer {
|
60 |
+
text-align: center;
|
61 |
+
margin-top: 20px;
|
62 |
+
color: silver;
|
63 |
+
}
|
64 |
+
#markdown-silver {
|
65 |
+
color: silver; /* Mengatur warna font Markdown menjadi silver */
|
66 |
+
}
|
67 |
+
"""
|
68 |
+
|
69 |
+
|
70 |
+
# Inisialisasi Gradio Blocks dengan custom CSS (style.css)
|
71 |
+
with gr.Blocks(css=css, theme=IndonesiaTheme()) as demo:
|
72 |
+
with gr.Tabs():
|
73 |
+
with gr.Tab(label="✨ Enhance"):
|
74 |
+
create_demo_enhance() # Panggil UI Enhance dari app_enhance.py (atau sesuai modul Anda)
|
75 |
+
# with gr.Tab(label="🚀 Upscale"):
|
76 |
+
# create_demo_upscale() # Panggil UI Upscale dari app_upscale.py (atau sesuai modul Anda)
|
77 |
+
|
78 |
+
# Jalankan aplikasi Gradio (local/web)
|
79 |
+
demo.queue(api_open=False).launch(show_api=False)
|
app_enhance.py
ADDED
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Image Enhancer App
|
3 |
+
------------------
|
4 |
+
Aplikasi berbasis Gradio untuk meningkatkan kualitas gambar dengan berbagai teknik pemrosesan citra
|
5 |
+
seperti perbaikan resolusi, penajaman, dan penghilangan noise.
|
6 |
+
Dapat digunakan secara lokal maupun sebagai web-app AI image enhancement.
|
7 |
+
|
8 |
+
Created by _drat | 2025
|
9 |
+
"""
|
10 |
+
|
11 |
+
# Import library standar dan eksternal yang dibutuhkan
|
12 |
+
import os
|
13 |
+
import subprocess
|
14 |
+
import spaces # Library dari HuggingFace untuk GPU management & dekorator
|
15 |
+
import torch # Library untuk deep learning, cek GPU
|
16 |
+
import cv2 # OpenCV untuk pengolahan gambar
|
17 |
+
import uuid # Untuk generate nama file unik
|
18 |
+
import gradio as gr # Untuk membuat UI web berbasis Python
|
19 |
+
import numpy as np # Operasi numerik, termasuk array
|
20 |
+
|
21 |
+
from PIL import Image # Untuk memproses dan menyimpan gambar
|
22 |
+
# Import arsitektur model dan utilitas untuk enhancement & upscaling
|
23 |
+
from basicsr.archs.srvgg_arch import SRVGGNetCompact
|
24 |
+
from gfpgan.utils import GFPGANer
|
25 |
+
from realesrgan.utils import RealESRGANer
|
26 |
+
|
27 |
+
import warnings
|
28 |
+
warnings.filterwarnings("ignore")
|
29 |
+
|
30 |
+
# Fungsi untuk menjalankan command shell (misal: wget file model)
|
31 |
+
def runcmd(cmd, verbose = False):
|
32 |
+
process = subprocess.Popen(
|
33 |
+
cmd,
|
34 |
+
stdout = subprocess.PIPE,
|
35 |
+
stderr = subprocess.PIPE,
|
36 |
+
text = True,
|
37 |
+
shell = True
|
38 |
+
)
|
39 |
+
std_out, std_err = process.communicate()
|
40 |
+
if verbose:
|
41 |
+
print(std_out.strip(), std_err)
|
42 |
+
pass
|
43 |
+
|
44 |
+
# Pastikan model AI (GFPGAN dan RealESRGAN) sudah terdownload. Jika belum, otomatis download.
|
45 |
+
if not os.path.exists('GFPGANv1.4.pth'):
|
46 |
+
runcmd("wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth -P .")
|
47 |
+
if not os.path.exists('realesr-general-x4v3.pth'):
|
48 |
+
runcmd("wget https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-general-x4v3.pth -P .")
|
49 |
+
|
50 |
+
# Inisialisasi model Real-ESRGAN untuk upscaling gambar
|
51 |
+
model = SRVGGNetCompact(num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=32, upscale=4, act_type='prelu')
|
52 |
+
model_path = 'realesr-general-x4v3.pth'
|
53 |
+
half = True if torch.cuda.is_available() else False # Pakai mode half-precision jika ada GPU (lebih cepat)
|
54 |
+
upsampler = RealESRGANer(
|
55 |
+
scale=4, model_path=model_path, model=model,
|
56 |
+
tile=0, tile_pad=10, pre_pad=0, half=half
|
57 |
+
)
|
58 |
+
|
59 |
+
# Fungsi utama untuk enhance gambar (dipanggil saat tombol di UI ditekan)
|
60 |
+
@spaces.GPU(duration=15)
|
61 |
+
def enhance_image(
|
62 |
+
input_image: Image, # Gambar input dari user (PIL Image)
|
63 |
+
scale: int, # Skala upscaling (misal: 2x, 4x)
|
64 |
+
enhance_mode: str, # Mode enhance: face saja, image saja, atau keduanya
|
65 |
+
):
|
66 |
+
only_face = enhance_mode == "Only Face Enhance"
|
67 |
+
|
68 |
+
# Pilih mode enhancer: hanya wajah, hanya gambar, atau kombinasi
|
69 |
+
if enhance_mode == "Only Face Enhance":
|
70 |
+
face_enhancer = GFPGANer(
|
71 |
+
model_path='GFPGANv1.4.pth', upscale=scale,
|
72 |
+
arch='clean', channel_multiplier=2
|
73 |
+
)
|
74 |
+
elif enhance_mode == "Only Image Enhance":
|
75 |
+
face_enhancer = None # Tidak enhance wajah, hanya upscaling gambar saja
|
76 |
+
else:
|
77 |
+
face_enhancer = GFPGANer(
|
78 |
+
model_path='GFPGANv1.4.pth', upscale=scale,
|
79 |
+
arch='clean', channel_multiplier=2, bg_upsampler=upsampler
|
80 |
+
)
|
81 |
+
|
82 |
+
# Konversi gambar input ke format BGR (OpenCV)
|
83 |
+
img = cv2.cvtColor(np.array(input_image), cv2.COLOR_RGB2BGR)
|
84 |
+
h, w = img.shape[0:2]
|
85 |
+
|
86 |
+
# Optional: perbesar gambar jika terlalu kecil (kurang dari 300px)
|
87 |
+
if h < 300:
|
88 |
+
img = cv2.resize(img, (w * 2, h * 2), interpolation=cv2.INTER_LANCZOS4)
|
89 |
+
|
90 |
+
# Proses enhance gambar (tergantung mode)
|
91 |
+
if face_enhancer is not None:
|
92 |
+
# Enhance wajah (GFPGAN), output gambar hasil enhancement
|
93 |
+
_, _, output = face_enhancer.enhance(
|
94 |
+
img, has_aligned=False, only_center_face=only_face, paste_back=True
|
95 |
+
)
|
96 |
+
else:
|
97 |
+
# Hanya upscaling image (tanpa enhance wajah)
|
98 |
+
output, _ = upsampler.enhance(img, outscale=scale)
|
99 |
+
|
100 |
+
# --- Optional resize (tidak digunakan)
|
101 |
+
# if scale != 2:
|
102 |
+
# interpolation = cv2.INTER_AREA if scale < 2 else cv2.INTER_LANCZOS4
|
103 |
+
# h, w = img.shape[0:2]
|
104 |
+
# output = cv2.resize(output, (int(w * scale / 2), int(h * scale / 2)), interpolation=interpolation)
|
105 |
+
|
106 |
+
# Batasi hasil enhance agar tidak lebih dari 3480px (biar aman dan efisien)
|
107 |
+
h, w = output.shape[0:2]
|
108 |
+
max_size = 3480
|
109 |
+
if h > max_size:
|
110 |
+
w = int(w * max_size / h)
|
111 |
+
h = max_size
|
112 |
+
if w > max_size:
|
113 |
+
h = int(h * max_size / w)
|
114 |
+
w = max_size
|
115 |
+
|
116 |
+
# Resize hasil akhir
|
117 |
+
output = cv2.resize(output, (w, h), interpolation=cv2.INTER_LANCZOS4)
|
118 |
+
|
119 |
+
# Konversi kembali ke RGB & PIL Image (untuk output Gradio)
|
120 |
+
enhanced_image = Image.fromarray(cv2.cvtColor(output, cv2.COLOR_BGR2RGB))
|
121 |
+
|
122 |
+
# Simpan gambar hasil enhance ke folder output dengan nama unik
|
123 |
+
tmpPrefix = "/tmp/gradio/"
|
124 |
+
extension = 'png'
|
125 |
+
# targetDir = f"{tmpPrefix}output/"
|
126 |
+
targetDir = "output/"
|
127 |
+
if not os.path.exists(targetDir):
|
128 |
+
os.makedirs(targetDir)
|
129 |
+
|
130 |
+
enhanced_path = f"{targetDir}{uuid.uuid4()}.{extension}"
|
131 |
+
enhanced_image.save(enhanced_path, quality=100)
|
132 |
+
|
133 |
+
return enhanced_image, enhanced_path # Return: image untuk preview dan path file untuk download
|
134 |
+
|
135 |
+
|
136 |
+
#
|
137 |
+
# DEMO DI SINI
|
138 |
+
#
|
139 |
+
|
140 |
+
def create_demo() -> gr.Blocks:
|
141 |
+
with gr.Blocks() as demo:
|
142 |
+
# Feature Image dengan border subtle & shadow
|
143 |
+
gr.HTML(
|
144 |
+
"""
|
145 |
+
<div style='display:flex; justify-content:center;'>
|
146 |
+
<img src='https://i.ibb.co/jkkT7MZQ/feature-image-enhancer.jpg'
|
147 |
+
alt='Feature Image'
|
148 |
+
style='height:200px; width:auto; border-radius:24px; box-shadow:0 4px 18px #ffa07430; border:2.5px solid #ffe0b3;'
|
149 |
+
id='feature-image'/>
|
150 |
+
</div>
|
151 |
+
"""
|
152 |
+
)
|
153 |
+
# Judul & Tagline dengan emoji & gradient text
|
154 |
+
gr.Markdown("""
|
155 |
+
<div style="text-align:center; margin-top: -10px; margin-bottom: 14px;">
|
156 |
+
<span style="font-size:2.1rem; font-family:'Quicksand',sans-serif; font-weight:800; background: linear-gradient(90deg,#d84040,#ff914d 60%); -webkit-background-clip: text; color:transparent; display:inline-block;">
|
157 |
+
<span style="vertical-align:middle;">📸</span> Image Enhancer <b>Pro</b> <span style="font-size:1.1em;vertical-align:middle;">✨</span>
|
158 |
+
</span><br>
|
159 |
+
<span style="font-size:1.12rem; color:#d84040; font-family:'Inter',sans-serif;">
|
160 |
+
<span style="vertical-align:middle;">🤖</span> Foto blur? Jadikan jernih & tajam pakai AI!
|
161 |
+
</span>
|
162 |
+
</div>
|
163 |
+
""")
|
164 |
+
|
165 |
+
# Input dan Output dalam card, dengan icon pada title
|
166 |
+
with gr.Row():
|
167 |
+
with gr.Column(scale=1):
|
168 |
+
with gr.Group(elem_id="input-card"):
|
169 |
+
gr.Markdown("<div class='card-title'><span style='font-size:1.3em;vertical-align:middle;'>🖼️</span> <b>Gambar Asli</b></div>")
|
170 |
+
input_image = gr.Image(label="", type="pil", elem_id="input-image", show_label=False)
|
171 |
+
gr.Markdown("<div class='hint'><span style='font-size:1.1em;'>⬆️</span> Upload foto format <b>JPG/PNG</b>, max 5MB</div>")
|
172 |
+
with gr.Column(scale=1):
|
173 |
+
with gr.Group(elem_id="output-card"):
|
174 |
+
gr.Markdown("<div class='card-title'><span style='font-size:1.3em;vertical-align:middle;'>🌈</span> <b>Hasil AI Enhance</b></div>")
|
175 |
+
output_image = gr.Image(label="", type="pil", interactive=False, elem_id="output-image", show_label=False)
|
176 |
+
enhance_image_path = gr.File(label="⬇️ Download (.png)", interactive=False, elem_id="download-btn")
|
177 |
+
gr.Markdown("""
|
178 |
+
<div class='hint' style="color:#ff914d;"><span style='font-size:1.2em;'>💡</span> Klik untuk download hasilnya!</div>
|
179 |
+
""")
|
180 |
+
|
181 |
+
# Kontrol dengan ikon pada label
|
182 |
+
with gr.Group(elem_id="control-card"):
|
183 |
+
with gr.Row():
|
184 |
+
scale = gr.Slider(
|
185 |
+
minimum=1,
|
186 |
+
maximum=4,
|
187 |
+
value=2,
|
188 |
+
step=1,
|
189 |
+
label="🔎 Upscale x",
|
190 |
+
elem_id="scale-slider"
|
191 |
+
)
|
192 |
+
enhance_mode = gr.Dropdown(
|
193 |
+
label="🛠️ Mode Enhance",
|
194 |
+
choices=[
|
195 |
+
"Only Face Enhance",
|
196 |
+
"Only Image Enhance",
|
197 |
+
"Face Enhance + Image Enhance",
|
198 |
+
],
|
199 |
+
value="Face Enhance + Image Enhance",
|
200 |
+
elem_id="mode-dropdown"
|
201 |
+
)
|
202 |
+
g_btn = gr.Button("🚀 Enhance Sekarang!", elem_id="enhance-btn", size="lg")
|
203 |
+
|
204 |
+
g_btn.click(
|
205 |
+
fn=enhance_image,
|
206 |
+
inputs=[input_image, scale, enhance_mode],
|
207 |
+
outputs=[output_image, enhance_image_path],
|
208 |
+
)
|
209 |
+
# Footer dengan ikon & sentuhan branding
|
210 |
+
# Tambahkan footer di bagian bawah
|
211 |
+
gr.HTML("""
|
212 |
+
<footer id="footer">
|
213 |
+
Transfer Energi Semesta Digital © 2024 | 🇮🇩 Untuk Indonesia Jaya!
|
214 |
+
</footer>
|
215 |
+
""")
|
216 |
+
|
217 |
+
return demo
|
218 |
+
|
219 |
+
|
220 |
+
|
221 |
+
|
app_upscale.py
ADDED
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Image Upscaler App
|
3 |
+
------------------
|
4 |
+
Aplikasi AI berbasis Gradio yang memanfaatkan Stable Diffusion Upscaler untuk meningkatkan resolusi gambar.
|
5 |
+
Tersedia juga fitur segmentasi & restorasi area tertentu pada gambar (misal: wajah).
|
6 |
+
Aplikasi mendukung input prompt teks untuk conditioning hasil upscaling.
|
7 |
+
|
8 |
+
Created by _drat | 2025
|
9 |
+
"""
|
10 |
+
|
11 |
+
# Import library eksternal yang diperlukan
|
12 |
+
import requests
|
13 |
+
from PIL import Image
|
14 |
+
from io import BytesIO
|
15 |
+
from diffusers import StableDiffusionUpscalePipeline # Pipeline Stable Diffusion untuk upscaling gambar
|
16 |
+
import torch
|
17 |
+
import gradio as gr
|
18 |
+
import time
|
19 |
+
import spaces
|
20 |
+
|
21 |
+
# Import fungsi segmentasi dan restorasi (definisi di segment_utils.py)
|
22 |
+
from segment_utils import(
|
23 |
+
segment_image, # Untuk segmentasi area penting pada gambar (misal: wajah)
|
24 |
+
restore_result, # Untuk menggabungkan hasil upscaling dengan gambar asli
|
25 |
+
)
|
26 |
+
|
27 |
+
# Setup device: gunakan CUDA (GPU) jika tersedia, jika tidak fallback ke CPU
|
28 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
29 |
+
print(f'{device} is available') # Debug: print device yang digunakan
|
30 |
+
|
31 |
+
# Load model Stable Diffusion Upscaler dari HuggingFace
|
32 |
+
model_id = "stabilityai/stable-diffusion-x4-upscaler"
|
33 |
+
upscale_pipe = StableDiffusionUpscalePipeline.from_pretrained(model_id, torch_dtype=torch.float16)
|
34 |
+
upscale_pipe = upscale_pipe.to(device)
|
35 |
+
|
36 |
+
# Default prompt dan kategori (untuk input Gradio)
|
37 |
+
DEFAULT_SRC_PROMPT = "a person with pefect face"
|
38 |
+
DEFAULT_CATEGORY = "face"
|
39 |
+
|
40 |
+
# Fungsi utama untuk membuat UI aplikasi Gradio
|
41 |
+
def create_demo() -> gr.Blocks:
|
42 |
+
|
43 |
+
# --- [ Function Definitions Tetap Seperti Asli Anda ] ---
|
44 |
+
@spaces.GPU(duration=30)
|
45 |
+
def upscale_image(
|
46 |
+
input_image: Image,
|
47 |
+
prompt: str,
|
48 |
+
num_inference_steps: int = 10,
|
49 |
+
):
|
50 |
+
time_cost_str = ''
|
51 |
+
run_task_time = 0
|
52 |
+
run_task_time, time_cost_str = get_time_cost(run_task_time, time_cost_str)
|
53 |
+
upscaled_image = upscale_pipe(
|
54 |
+
prompt=prompt,
|
55 |
+
image=input_image,
|
56 |
+
num_inference_steps=num_inference_steps,
|
57 |
+
).images[0]
|
58 |
+
run_task_time, time_cost_str = get_time_cost(run_task_time, time_cost_str)
|
59 |
+
return upscaled_image, time_cost_str
|
60 |
+
|
61 |
+
def get_time_cost(run_task_time, time_cost_str):
|
62 |
+
now_time = int(time.time()*1000)
|
63 |
+
if run_task_time == 0:
|
64 |
+
time_cost_str = 'start'
|
65 |
+
else:
|
66 |
+
if time_cost_str != '':
|
67 |
+
time_cost_str += f'-->'
|
68 |
+
time_cost_str += f'{now_time - run_task_time}'
|
69 |
+
run_task_time = now_time
|
70 |
+
return run_task_time, time_cost_str
|
71 |
+
|
72 |
+
# --- [ UI Section ] ---
|
73 |
+
with gr.Blocks(css="creative_enhance.css") as demo:
|
74 |
+
gr.HTML("""
|
75 |
+
<div style='display:flex; justify-content:center;'>
|
76 |
+
<img src='https://i.ibb.co/pvWSfsMJ/feature-image-upscaler.jpg' alt='Feature Image' style='height:200px; width:auto; border-radius:24px; box-shadow:0 4px 18px #ffa07430; border:2.5px solid #ffe0b3;' id='feature-image'/>
|
77 |
+
</div>
|
78 |
+
<div style='text-align:center;margin-top:6px;'>
|
79 |
+
<span style='font-size:2.0rem;font-weight:800;color:#d84040;letter-spacing:0.04em;font-family:Quicksand,sans-serif;'>
|
80 |
+
🔬 <b>AI Image Upscaler</b> <span style='font-size:1.2em;vertical-align:middle;'>🚀</span>
|
81 |
+
</span>
|
82 |
+
<br>
|
83 |
+
<span style='font-size:1.1rem;color:#ff914d;font-family:Inter,sans-serif;'>Perbesar gambar <b>HD</b> otomatis, detail makin nyata!</span>
|
84 |
+
</div>
|
85 |
+
""")
|
86 |
+
|
87 |
+
# Input prompt & parameter di satu card
|
88 |
+
with gr.Row():
|
89 |
+
with gr.Group(elem_id="control-card"):
|
90 |
+
with gr.Row():
|
91 |
+
input_image_prompt = gr.Textbox(
|
92 |
+
lines=1, label="🎯 Prompt AI (opsional)",
|
93 |
+
value=DEFAULT_SRC_PROMPT, elem_id="input-image-prompt"
|
94 |
+
)
|
95 |
+
num_inference_steps = gr.Number(
|
96 |
+
label="⚙️ Steps (Quality)", value=5, elem_id="num-inference"
|
97 |
+
)
|
98 |
+
generate_size = gr.Number(
|
99 |
+
label="📐 Size (px)", value=512, elem_id="generate-size"
|
100 |
+
)
|
101 |
+
g_btn = gr.Button("🪄 Upscale Sekarang", elem_id="upscale-btn")
|
102 |
+
|
103 |
+
# Input & Output Gambar
|
104 |
+
with gr.Row():
|
105 |
+
with gr.Column(scale=1):
|
106 |
+
with gr.Group(elem_id="input-card"):
|
107 |
+
gr.Markdown("<div class='card-title'><span style='font-size:1.2em;'>🖼️</span> Gambar Asli</div>")
|
108 |
+
input_image = gr.Image(label="", type="pil", elem_id="input-image", show_label=False)
|
109 |
+
gr.Markdown("<div class='hint'><span style='font-size:1.1em;'>⬆️</span> JPG/PNG max 5MB</div>")
|
110 |
+
with gr.Column(scale=1):
|
111 |
+
with gr.Group(elem_id="output-card"):
|
112 |
+
gr.Markdown("<div class='card-title'><span style='font-size:1.2em;'>💡</span> Upscale Preview</div>")
|
113 |
+
restored_image = gr.Image(label="Hasil Akhir", format="png", type="pil", interactive=False)
|
114 |
+
origin_area_image = gr.Image(label="", format="png", type="pil", interactive=False, visible=False)
|
115 |
+
upscaled_image = gr.Image(label="Upscaled", format="png", type="pil", interactive=False)
|
116 |
+
download_path = gr.File(label="⬇️ Download Image", interactive=False, elem_id="download-btn")
|
117 |
+
gr.Markdown("<div class='hint'><span style='font-size:1.1em;'>💾</span> Download hasil upscale PNG</div>")
|
118 |
+
generated_cost = gr.Textbox(label="⏱️ Time (ms)", visible=True, interactive=False)
|
119 |
+
|
120 |
+
# Hidden inputs untuk workflow
|
121 |
+
category = gr.Textbox(label="Category", value=DEFAULT_CATEGORY, visible=False)
|
122 |
+
mask_expansion = gr.Number(label="Mask Expansion", value=20, visible=False)
|
123 |
+
mask_dilation = gr.Slider(minimum=0, maximum=10, value=2, step=1, label="Mask Dilation", visible=False)
|
124 |
+
croper = gr.State()
|
125 |
+
|
126 |
+
# Workflow chaining
|
127 |
+
g_btn.click(
|
128 |
+
fn=segment_image,
|
129 |
+
inputs=[input_image, category, generate_size, mask_expansion, mask_dilation],
|
130 |
+
outputs=[origin_area_image, croper],
|
131 |
+
).success(
|
132 |
+
fn=upscale_image,
|
133 |
+
inputs=[origin_area_image, input_image_prompt, num_inference_steps],
|
134 |
+
outputs=[upscaled_image, generated_cost],
|
135 |
+
).success(
|
136 |
+
fn=restore_result,
|
137 |
+
inputs=[croper, category, upscaled_image],
|
138 |
+
outputs=[restored_image, download_path],
|
139 |
+
)
|
140 |
+
|
141 |
+
gr.Markdown("""
|
142 |
+
<div style='text-align:center;color:#aaa;font-size:0.98rem;margin-top:14px;'>
|
143 |
+
© 2025 <b>AI Image Upscaler</b> • Powered by <b>_drat</b> 🚀
|
144 |
+
</div>
|
145 |
+
""")
|
146 |
+
|
147 |
+
return demo
|
148 |
+
|
croper.py
ADDED
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Croper Utility
|
3 |
+
--------------
|
4 |
+
Kelas utilitas untuk melakukan crop area tertentu (berbasis mask) pada gambar, lalu menyiapkan square crop image dan square mask.
|
5 |
+
Juga menyediakan fungsi untuk mengembalikan (restore) hasil modifikasi ke gambar asli.
|
6 |
+
|
7 |
+
Created by _drat | 2025
|
8 |
+
"""
|
9 |
+
|
10 |
+
import PIL
|
11 |
+
import numpy as np
|
12 |
+
from PIL import Image
|
13 |
+
|
14 |
+
class Croper:
|
15 |
+
def __init__(
|
16 |
+
self,
|
17 |
+
input_image: PIL.Image, # Gambar input (format PIL Image)
|
18 |
+
target_mask: np.ndarray, # Mask biner area target (array numpy)
|
19 |
+
mask_size: int = 256, # Ukuran akhir mask & image yang dihasilkan (default 256x256)
|
20 |
+
mask_expansion: int = 20, # Ekspansi area mask (agar crop lebih lebar)
|
21 |
+
):
|
22 |
+
self.input_image = input_image
|
23 |
+
self.target_mask = target_mask
|
24 |
+
self.mask_size = mask_size
|
25 |
+
self.mask_expansion = mask_expansion
|
26 |
+
|
27 |
+
# Fungsi utama untuk crop area sesuai mask dan membentuk square crop & mask
|
28 |
+
def corp_mask_image(self):
|
29 |
+
target_mask = self.target_mask
|
30 |
+
input_image = self.input_image
|
31 |
+
mask_expansion = self.mask_expansion
|
32 |
+
original_width, original_height = input_image.size
|
33 |
+
|
34 |
+
# Cari koordinat bounding box area mask (area bertanda True/1)
|
35 |
+
mask_indices = np.where(target_mask)
|
36 |
+
start_y = np.min(mask_indices[0])
|
37 |
+
end_y = np.max(mask_indices[0])
|
38 |
+
start_x = np.min(mask_indices[1])
|
39 |
+
end_x = np.max(mask_indices[1])
|
40 |
+
|
41 |
+
mask_height = end_y - start_y
|
42 |
+
mask_width = end_x - start_x
|
43 |
+
|
44 |
+
# Ambil sisi terpanjang untuk square crop
|
45 |
+
max_side_length = max(mask_height, mask_width)
|
46 |
+
|
47 |
+
# Ekspansi area mask agar crop tidak terlalu sempit
|
48 |
+
height_diff = (max_side_length - mask_height) // 2
|
49 |
+
width_diff = (max_side_length - mask_width) // 2
|
50 |
+
start_y = start_y - mask_expansion - height_diff
|
51 |
+
if start_y < 0:
|
52 |
+
start_y = 0
|
53 |
+
end_y = end_y + mask_expansion + height_diff
|
54 |
+
if end_y > original_height:
|
55 |
+
end_y = original_height
|
56 |
+
start_x = start_x - mask_expansion - width_diff
|
57 |
+
if start_x < 0:
|
58 |
+
start_x = 0
|
59 |
+
end_x = end_x + mask_expansion + width_diff
|
60 |
+
if end_x > original_width:
|
61 |
+
end_x = original_width
|
62 |
+
|
63 |
+
expanded_height = end_y - start_y
|
64 |
+
expanded_width = end_x - start_x
|
65 |
+
expanded_max_side_length = max(expanded_height, expanded_width)
|
66 |
+
|
67 |
+
# Crop mask dari area yang sudah diekspansi
|
68 |
+
crop_mask = target_mask[start_y:end_y, start_x:end_x]
|
69 |
+
|
70 |
+
# Hitung posisi crop mask pada square mask
|
71 |
+
crop_mask_start_y = (expanded_max_side_length - expanded_height) // 2
|
72 |
+
crop_mask_end_y = crop_mask_start_y + expanded_height
|
73 |
+
crop_mask_start_x = (expanded_max_side_length - expanded_width) // 2
|
74 |
+
crop_mask_end_x = crop_mask_start_x + expanded_width
|
75 |
+
|
76 |
+
# Buat square mask dengan area crop mask di tengah
|
77 |
+
square_mask = np.zeros((expanded_max_side_length, expanded_max_side_length), dtype=target_mask.dtype)
|
78 |
+
square_mask[crop_mask_start_y:crop_mask_end_y, crop_mask_start_x:crop_mask_end_x] = crop_mask
|
79 |
+
square_mask_image = Image.fromarray((square_mask * 255).astype(np.uint8))
|
80 |
+
|
81 |
+
# Crop image asli sesuai area ekspansi
|
82 |
+
crop_image = input_image.crop((start_x, start_y, end_x, end_y))
|
83 |
+
# Buat square image, lalu paste crop image di tengah
|
84 |
+
square_image = Image.new("RGB", (expanded_max_side_length, expanded_max_side_length))
|
85 |
+
square_image.paste(crop_image, (crop_mask_start_x, crop_mask_start_y))
|
86 |
+
|
87 |
+
# Simpan koordinat/ukuran untuk keperluan restore nanti
|
88 |
+
self.origin_start_x = start_x
|
89 |
+
self.origin_start_y = start_y
|
90 |
+
self.origin_end_x = end_x
|
91 |
+
self.origin_end_y = end_y
|
92 |
+
|
93 |
+
self.square_start_x = crop_mask_start_x
|
94 |
+
self.square_start_y = crop_mask_start_y
|
95 |
+
self.square_end_x = crop_mask_end_x
|
96 |
+
self.square_end_y = crop_mask_end_y
|
97 |
+
|
98 |
+
self.square_length = expanded_max_side_length
|
99 |
+
self.square_mask_image = square_mask_image
|
100 |
+
self.square_image = square_image
|
101 |
+
self.corp_mask = crop_mask
|
102 |
+
|
103 |
+
# Resize hasil square mask & image ke mask_size (misal: 256x256) untuk kebutuhan pipeline AI
|
104 |
+
mask_size = self.mask_size
|
105 |
+
self.resized_square_mask_image = square_mask_image.resize((mask_size, mask_size))
|
106 |
+
self.resized_square_image = square_image.resize((mask_size, mask_size))
|
107 |
+
|
108 |
+
return self.resized_square_mask_image # Kembalikan mask hasil resize
|
109 |
+
|
110 |
+
# Fungsi untuk restore hasil generate ke gambar asli dengan bantuan mask transparan
|
111 |
+
def restore_result(self, generated_image):
|
112 |
+
square_length = self.square_length
|
113 |
+
# Resize hasil generate ke ukuran square original
|
114 |
+
generated_image = generated_image.resize((square_length, square_length))
|
115 |
+
square_mask_image = self.square_mask_image
|
116 |
+
# Crop area yang sudah di-generate sesuai posisi awal
|
117 |
+
cropped_generated_image = generated_image.crop((self.square_start_x, self.square_start_y, self.square_end_x, self.square_end_y))
|
118 |
+
cropped_square_mask_image = square_mask_image.crop((self.square_start_x, self.square_start_y, self.square_end_x, self.square_end_y))
|
119 |
+
|
120 |
+
# Tempel hasil generate ke gambar asli dengan mask transparan
|
121 |
+
restored_image = self.input_image.copy()
|
122 |
+
restored_image.paste(cropped_generated_image, (self.origin_start_x, self.origin_start_y), cropped_square_mask_image)
|
123 |
+
|
124 |
+
return restored_image
|
125 |
+
|
126 |
+
# Versi restore tanpa mask (area hasil tempelkan langsung tanpa transparansi)
|
127 |
+
def restore_result_v2(self, generated_image):
|
128 |
+
square_length = self.square_length
|
129 |
+
generated_image = generated_image.resize((square_length, square_length))
|
130 |
+
cropped_generated_image = generated_image.crop((self.square_start_x, self.square_start_y, self.square_end_x, self.square_end_y))
|
131 |
+
|
132 |
+
restored_image = self.input_image.copy()
|
133 |
+
restored_image.paste(cropped_generated_image, (self.origin_start_x, self.origin_start_y))
|
134 |
+
|
135 |
+
return restored_image
|
enhance_utils.py
ADDED
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Enhance Utils
|
3 |
+
-------------
|
4 |
+
Utility functions untuk meningkatkan kualitas gambar dengan menggunakan model AI:
|
5 |
+
- Real-ESRGAN untuk upscaling gambar umum
|
6 |
+
- GFPGAN untuk enhancement wajah (face restoration)
|
7 |
+
|
8 |
+
Created by _drat | 2025
|
9 |
+
"""
|
10 |
+
|
11 |
+
import os
|
12 |
+
import torch
|
13 |
+
import cv2
|
14 |
+
import numpy as np
|
15 |
+
import subprocess
|
16 |
+
|
17 |
+
from PIL import Image
|
18 |
+
from gfpgan.utils import GFPGANer
|
19 |
+
from basicsr.archs.srvgg_arch import SRVGGNetCompact
|
20 |
+
from realesrgan.utils import RealESRGANer
|
21 |
+
|
22 |
+
# Fungsi untuk menjalankan perintah shell (misal: pip, wget)
|
23 |
+
def runcmd(cmd, verbose = False, *args, **kwargs):
|
24 |
+
process = subprocess.Popen(
|
25 |
+
cmd,
|
26 |
+
stdout = subprocess.PIPE,
|
27 |
+
stderr = subprocess.PIPE,
|
28 |
+
text = True,
|
29 |
+
shell = True
|
30 |
+
)
|
31 |
+
std_out, std_err = process.communicate()
|
32 |
+
if verbose:
|
33 |
+
print(std_out.strip(), std_err)
|
34 |
+
pass
|
35 |
+
|
36 |
+
# Debug: Menampilkan daftar library yang terinstall di environment
|
37 |
+
runcmd("pip freeze")
|
38 |
+
|
39 |
+
# Download model GFPGAN jika belum ada di direktori saat ini
|
40 |
+
if not os.path.exists('GFPGANv1.4.pth'):
|
41 |
+
runcmd("wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth -P .")
|
42 |
+
|
43 |
+
# Download model RealESRGAN jika belum ada di direktori saat ini
|
44 |
+
if not os.path.exists('realesr-general-x4v3.pth'):
|
45 |
+
runcmd("wget https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-general-x4v3.pth -P .")
|
46 |
+
|
47 |
+
# Inisialisasi model Real-ESRGAN (untuk upscaling gambar umum)
|
48 |
+
model = SRVGGNetCompact(num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=32, upscale=4, act_type='prelu')
|
49 |
+
model_path = 'realesr-general-x4v3.pth'
|
50 |
+
half = True if torch.cuda.is_available() else False # Pakai half precision jika tersedia GPU
|
51 |
+
upsampler = RealESRGANer(
|
52 |
+
scale=4,
|
53 |
+
model_path=model_path,
|
54 |
+
model=model,
|
55 |
+
tile=0,
|
56 |
+
tile_pad=10,
|
57 |
+
pre_pad=0,
|
58 |
+
half=half
|
59 |
+
)
|
60 |
+
|
61 |
+
# Inisialisasi enhancer wajah (GFPGAN)
|
62 |
+
face_enhancer = GFPGANer(
|
63 |
+
model_path='GFPGANv1.4.pth',
|
64 |
+
upscale=1, # Default tidak melakukan upscaling (hanya enhance wajah)
|
65 |
+
arch='clean',
|
66 |
+
channel_multiplier=2
|
67 |
+
)
|
68 |
+
|
69 |
+
# Fungsi utama enhancement gambar
|
70 |
+
def enhance_image(
|
71 |
+
pil_image: Image, # Input gambar dalam format PIL
|
72 |
+
enhance_face: bool = False, # Apakah perlu enhance wajah?
|
73 |
+
):
|
74 |
+
# Konversi PIL image ke array BGR (OpenCV)
|
75 |
+
img = cv2.cvtColor(np.array(pil_image), cv2.COLOR_RGB2BGR)
|
76 |
+
|
77 |
+
h, w = img.shape[0:2]
|
78 |
+
# Optional: jika gambar kecil, perbesar dulu agar hasil enhancement lebih maksimal
|
79 |
+
if h < 300:
|
80 |
+
img = cv2.resize(img, (w * 2, h * 2), interpolation=cv2.INTER_LANCZOS4)
|
81 |
+
|
82 |
+
# Enhance menggunakan GFPGAN (wajah) atau Real-ESRGAN (umum)
|
83 |
+
if enhance_face:
|
84 |
+
# Hanya enhance bagian wajah utama di tengah gambar
|
85 |
+
_, _, output = face_enhancer.enhance(
|
86 |
+
img, has_aligned=False, only_center_face=True, paste_back=True
|
87 |
+
)
|
88 |
+
else:
|
89 |
+
# Upscale seluruh gambar dengan Real-ESRGAN (outscale=2x)
|
90 |
+
output, _ = upsampler.enhance(img, outscale=2)
|
91 |
+
|
92 |
+
# Konversi hasil output ke format PIL RGB untuk siap digunakan di aplikasi
|
93 |
+
pil_output = Image.fromarray(cv2.cvtColor(output, cv2.COLOR_BGR2RGB))
|
94 |
+
|
95 |
+
return pil_output
|
requirements.txt
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
torch
|
2 |
+
torchvision
|
3 |
+
diffusers
|
4 |
+
transformers
|
5 |
+
accelerate
|
6 |
+
mediapipe
|
7 |
+
gradio
|
8 |
+
spaces
|
9 |
+
gfpgan
|
10 |
+
git+https://github.com/XPixelGroup/BasicSR@master
|
11 |
+
facexlib
|
12 |
+
realesrgan
|
segment_utils.py
ADDED
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Segment Utils
|
3 |
+
-------------
|
4 |
+
Utilitas segmentasi gambar berbasis MediaPipe dan mask biner untuk aplikasi image enhancer/upscaler:
|
5 |
+
- Segmentasi area: wajah, rambut, pakaian, dll.
|
6 |
+
- Membuat dan mengelola mask hasil segmentasi serta integrasi dengan class Croper
|
7 |
+
- Mendukung proses restore hasil generate ke gambar asli
|
8 |
+
|
9 |
+
Created by _drat | 2025
|
10 |
+
"""
|
11 |
+
|
12 |
+
import numpy as np
|
13 |
+
import mediapipe as mp
|
14 |
+
import uuid
|
15 |
+
import os
|
16 |
+
|
17 |
+
from PIL import Image
|
18 |
+
from mediapipe.tasks import python
|
19 |
+
from mediapipe.tasks.python import vision
|
20 |
+
from scipy.ndimage import binary_dilation
|
21 |
+
from croper import Croper
|
22 |
+
|
23 |
+
# Inisialisasi model segmentasi MediaPipe (load tflite model untuk multiclass selfie segmentation)
|
24 |
+
segment_model = "checkpoints/selfie_multiclass_256x256.tflite"
|
25 |
+
base_options = python.BaseOptions(model_asset_path=segment_model)
|
26 |
+
options = vision.ImageSegmenterOptions(base_options=base_options, output_category_mask=True)
|
27 |
+
segmenter = vision.ImageSegmenter.create_from_options(options)
|
28 |
+
|
29 |
+
# Fungsi untuk mengembalikan hasil generate ke gambar asli, dengan mask transparan hasil segmentasi
|
30 |
+
def restore_result(croper, category, generated_image):
|
31 |
+
square_length = croper.square_length
|
32 |
+
generated_image = generated_image.resize((square_length, square_length))
|
33 |
+
|
34 |
+
# Crop area hasil generate, lalu buat mask hasil restore
|
35 |
+
cropped_generated_image = generated_image.crop((croper.square_start_x, croper.square_start_y, croper.square_end_x, croper.square_end_y))
|
36 |
+
cropped_square_mask_image = get_restore_mask_image(croper, category, cropped_generated_image)
|
37 |
+
|
38 |
+
# Tempel hasil generate ke gambar asli dengan mask transparan
|
39 |
+
restored_image = croper.input_image.copy()
|
40 |
+
restored_image.paste(cropped_generated_image, (croper.origin_start_x, croper.origin_start_y), cropped_square_mask_image)
|
41 |
+
|
42 |
+
extension = 'png'
|
43 |
+
tmpPrefix = "/tmp/gradio/"
|
44 |
+
# targetDir = f"{tmpPrefix}output/"
|
45 |
+
targetDir = "output/"
|
46 |
+
if not os.path.exists(targetDir):
|
47 |
+
os.makedirs(targetDir)
|
48 |
+
|
49 |
+
path = f"{targetDir}{uuid.uuid4()}.{extension}"
|
50 |
+
restored_image.save(path, quality=100)
|
51 |
+
|
52 |
+
return restored_image, path
|
53 |
+
|
54 |
+
# Fungsi utama segmentasi gambar sesuai kategori
|
55 |
+
def segment_image(input_image, category, input_size, mask_expansion, mask_dilation):
|
56 |
+
mask_size = int(input_size)
|
57 |
+
mask_expansion = int(mask_expansion)
|
58 |
+
|
59 |
+
# Konversi input image ke format yang sesuai untuk MediaPipe
|
60 |
+
image = mp.Image(image_format=mp.ImageFormat.SRGB, data=np.asarray(input_image))
|
61 |
+
segmentation_result = segmenter.segment(image)
|
62 |
+
category_mask = segmentation_result.category_mask
|
63 |
+
category_mask_np = category_mask.numpy_view()
|
64 |
+
|
65 |
+
# Pilih fungsi pembentukan mask sesuai kategori segmentasi
|
66 |
+
if category == "hair":
|
67 |
+
target_mask = get_hair_mask(category_mask_np, mask_dilation)
|
68 |
+
elif category == "clothes":
|
69 |
+
target_mask = get_clothes_mask(category_mask_np, mask_dilation)
|
70 |
+
elif category == "face":
|
71 |
+
target_mask = get_face_mask(category_mask_np, mask_dilation)
|
72 |
+
else:
|
73 |
+
target_mask = get_face_mask(category_mask_np, mask_dilation)
|
74 |
+
|
75 |
+
# Buat objek Croper dengan mask hasil segmentasi, untuk crop & resize
|
76 |
+
croper = Croper(input_image, target_mask, mask_size, mask_expansion)
|
77 |
+
croper.corp_mask_image()
|
78 |
+
origin_area_image = croper.resized_square_image
|
79 |
+
|
80 |
+
return origin_area_image, croper # Kembalikan area hasil crop + objek croper
|
81 |
+
|
82 |
+
# Fungsi pembuat mask khusus area wajah (+opsi dilasi mask)
|
83 |
+
def get_face_mask(category_mask_np, dilation=1):
|
84 |
+
face_skin_mask = category_mask_np == 3
|
85 |
+
if dilation > 0:
|
86 |
+
face_skin_mask = binary_dilation(face_skin_mask, iterations=dilation)
|
87 |
+
return face_skin_mask
|
88 |
+
|
89 |
+
# Fungsi pembuat mask khusus area baju (dan kulit tubuh)
|
90 |
+
def get_clothes_mask(category_mask_np, dilation=1):
|
91 |
+
body_skin_mask = category_mask_np == 2
|
92 |
+
clothes_mask = category_mask_np == 4
|
93 |
+
combined_mask = np.logical_or(body_skin_mask, clothes_mask)
|
94 |
+
combined_mask = binary_dilation(combined_mask, iterations=4)
|
95 |
+
if dilation > 0:
|
96 |
+
combined_mask = binary_dilation(combined_mask, iterations=dilation)
|
97 |
+
return combined_mask
|
98 |
+
|
99 |
+
# Fungsi pembuat mask khusus area rambut
|
100 |
+
def get_hair_mask(category_mask_np, dilation=1):
|
101 |
+
hair_mask = category_mask_np == 1
|
102 |
+
if dilation > 0:
|
103 |
+
hair_mask = binary_dilation(hair_mask, iterations=dilation)
|
104 |
+
return hair_mask
|
105 |
+
|
106 |
+
# Fungsi untuk membuat mask kombinasi hasil generate dan area awal (untuk proses restore)
|
107 |
+
def get_restore_mask_image(croper, category, generated_image):
|
108 |
+
image = mp.Image(image_format=mp.ImageFormat.SRGB, data=np.asarray(generated_image))
|
109 |
+
segmentation_result = segmenter.segment(image)
|
110 |
+
category_mask = segmentation_result.category_mask
|
111 |
+
category_mask_np = category_mask.numpy_view()
|
112 |
+
|
113 |
+
if category == "hair":
|
114 |
+
target_mask = get_hair_mask(category_mask_np, 0)
|
115 |
+
elif category == "clothes":
|
116 |
+
target_mask = get_clothes_mask(category_mask_np, 0)
|
117 |
+
elif category == "face":
|
118 |
+
target_mask = get_face_mask(category_mask_np, 0)
|
119 |
+
|
120 |
+
# Gabungkan mask hasil segmentasi dan mask area awal croper
|
121 |
+
combined_mask = np.logical_or(target_mask, croper.corp_mask)
|
122 |
+
mask_image = Image.fromarray((combined_mask * 255).astype(np.uint8))
|
123 |
+
return mask_image
|
themes.py
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from __future__ import annotations
|
2 |
+
from typing import Iterable
|
3 |
+
from gradio.themes.base import Base
|
4 |
+
from gradio.themes.utils import colors, fonts, sizes
|
5 |
+
|
6 |
+
class IndonesiaTheme(Base):
|
7 |
+
def __init__(
|
8 |
+
self,
|
9 |
+
*,
|
10 |
+
primary_hue: colors.Color | str = colors.red,
|
11 |
+
secondary_hue: colors.Color | str = colors.orange,
|
12 |
+
neutral_hue: colors.Color | str = colors.gray,
|
13 |
+
spacing_size: sizes.Size | str = sizes.spacing_md,
|
14 |
+
radius_size: sizes.Size | str = sizes.radius_md,
|
15 |
+
text_size: sizes.Size | str = sizes.text_lg,
|
16 |
+
font: fonts.Font
|
17 |
+
| str
|
18 |
+
| Iterable[fonts.Font | str] = (
|
19 |
+
fonts.GoogleFont("Quicksand"),
|
20 |
+
"ui-sans-serif",
|
21 |
+
"sans-serif",
|
22 |
+
),
|
23 |
+
font_mono: fonts.Font
|
24 |
+
| str
|
25 |
+
| Iterable[fonts.Font | str] = (
|
26 |
+
fonts.GoogleFont("IBM Plex Mono"),
|
27 |
+
"ui-monospace",
|
28 |
+
"monospace",
|
29 |
+
),
|
30 |
+
):
|
31 |
+
super().__init__(
|
32 |
+
primary_hue=primary_hue,
|
33 |
+
secondary_hue=secondary_hue,
|
34 |
+
neutral_hue=neutral_hue,
|
35 |
+
spacing_size=spacing_size,
|
36 |
+
radius_size=radius_size,
|
37 |
+
text_size=text_size,
|
38 |
+
font=font,
|
39 |
+
font_mono=font_mono,
|
40 |
+
)
|
41 |
+
super().set(
|
42 |
+
# Ganti background dengan gradasi putih ke coklat muda (contoh: #fff ke #fbeee0)
|
43 |
+
body_background_fill="linear-gradient(to bottom, #fff 65%, #fbeee0 100%)",
|
44 |
+
body_background_fill_dark="linear-gradient(to bottom, #c8b7a6 70%, #7c6957 100%)", # gradasi coklat untuk dark mode
|
45 |
+
button_primary_background_fill="linear-gradient(90deg, #d84a4a, #b33030)",
|
46 |
+
button_primary_background_fill_hover="linear-gradient(90deg, #e85b5b, #cc4b4b)",
|
47 |
+
button_primary_text_color="white",
|
48 |
+
button_primary_background_fill_dark="linear-gradient(90deg, #b33030, #8f1f1f)",
|
49 |
+
slider_color="*secondary_300",
|
50 |
+
slider_color_dark="*secondary_600",
|
51 |
+
block_title_text_weight="600",
|
52 |
+
block_border_width="2px",
|
53 |
+
block_shadow="*shadow_drop_lg",
|
54 |
+
)
|