|
|
|
|
|
|
|
|
|
|
|
|
|
import gradio as gr |
|
import torch |
|
import numpy as np |
|
from PIL import Image, ImageEnhance |
|
import cv2 |
|
import os |
|
import sys |
|
import subprocess |
|
import time |
|
from huggingface_hub import hf_hub_download |
|
|
|
|
|
CACHE_DIR = os.path.join(os.path.expanduser("~"), ".cache", "image_enhancer") |
|
os.makedirs(CACHE_DIR, exist_ok=True) |
|
|
|
|
|
import logging |
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') |
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
def install_dependencies(): |
|
logger.info("Checking and installing dependencies...") |
|
|
|
packages_to_install = [ |
|
"opencv-python", |
|
"opencv-contrib-python", |
|
"numpy", |
|
"pillow", |
|
"torch torchvision torchaudio", |
|
"facexlib", |
|
"basicsr", |
|
"gfpgan", |
|
"realesrgan", |
|
"huggingface_hub" |
|
] |
|
|
|
|
|
|
|
|
|
|
|
|
|
for package in packages_to_install: |
|
try: |
|
logger.info(f"Installing {package}") |
|
subprocess.check_call([sys.executable, "-m", "pip", "install", package]) |
|
except Exception as e: |
|
logger.warning(f"Error installing {package}: {str(e)}") |
|
|
|
logger.info("Dependencies installation complete") |
|
|
|
|
|
try: |
|
install_dependencies() |
|
|
|
import cv2 |
|
import torch |
|
import numpy as np |
|
from PIL import Image, ImageEnhance |
|
from huggingface_hub import hf_hub_download |
|
try: |
|
from realesrgan import RealESRGAN |
|
except ImportError: |
|
logger.warning("RealESRGAN import failed after installation attempt.") |
|
RealESRGAN = None |
|
try: |
|
from gfpgan import GFPGANer |
|
except ImportError: |
|
logger.warning("GFPGANer import failed after installation attempt.") |
|
GFPGANer = None |
|
|
|
time.sleep(2) |
|
except Exception as e: |
|
logger.error(f"Failed to install dependencies or import libraries: {str(e)}") |
|
|
|
|
|
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
|
logger.info(f"Using device: {device}") |
|
|
|
|
|
MODEL_OPTIONS = { |
|
"OpenCV Super Resolution": { |
|
"type": "upscale", |
|
"method": "opencv", |
|
"scale": 4 |
|
}, |
|
"Real-ESRGAN-x4": { |
|
"repo_id": "xinntao/Real-ESRGAN", |
|
"filename": "RealESRGAN_x4plus.pth", |
|
"type": "upscale", |
|
"method": "realesrgan", |
|
"scale": 4 |
|
}, |
|
"GFPGAN (Face Enhancement)": { |
|
"repo_id": "TencentARC/GFPGAN", |
|
"filename": "GFPGANv1.4.pth", |
|
"type": "face", |
|
"method": "gfpgan", |
|
"scale": 1 |
|
}, |
|
"HDR Enhancement": { |
|
"type": "hdr", |
|
"method": "custom", |
|
"scale": 1 |
|
} |
|
} |
|
|
|
|
|
model_cache = {} |
|
|
|
|
|
def load_model(model_name): |
|
global model_cache |
|
|
|
|
|
if model_name in model_cache: |
|
logger.info(f"Using cached model: {model_name}") |
|
return model_cache[model_name] |
|
|
|
logger.info(f"Loading model: {model_name}") |
|
config = MODEL_OPTIONS.get(model_name) |
|
if not config: |
|
return None, f"Model {model_name} not found in configuration" |
|
|
|
model_type = config["type"] |
|
|
|
try: |
|
|
|
if config["method"] == "opencv": |
|
logger.info("Loading OpenCV Super Resolution model") |
|
try: |
|
sr = cv2.dnn_superres.DnnSuperResImpl_create() |
|
|
|
|
|
model_path = hf_hub_download( |
|
repo_id="eugenesiow/edsr", |
|
filename="EDSR_x4.pb", |
|
cache_dir=CACHE_DIR |
|
) |
|
|
|
sr.readModel(model_path) |
|
sr.setModel("edsr", 4) |
|
|
|
|
|
if torch.cuda.is_available(): |
|
sr.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA) |
|
sr.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA) |
|
|
|
model_cache[model_name] = (sr, model_type) |
|
return sr, model_type |
|
except Exception as e: |
|
logger.error(f"Error loading OpenCV SR model: {str(e)}") |
|
|
|
return None, f"Failed to load OpenCV SR model: {str(e)}" |
|
|
|
|
|
|
|
elif config["method"] == "realesrgan": |
|
if RealESRGAN is None: |
|
logger.warning("RealESRGAN class not found, falling back to OpenCV SR.") |
|
return load_model("OpenCV Super Resolution") |
|
|
|
try: |
|
logger.info("Loading Real-ESRGAN model") |
|
|
|
model_path = hf_hub_download( |
|
repo_id=config["repo_id"], |
|
filename=config["filename"], |
|
cache_dir=CACHE_DIR |
|
) |
|
|
|
|
|
model = RealESRGAN(device, scale=config["scale"]) |
|
model.load_weights(model_path) |
|
|
|
model_cache[model_name] = (model, model_type) |
|
return model, model_type |
|
except Exception as e: |
|
logger.error(f"Error loading Real-ESRGAN model: {str(e)}") |
|
logger.warning("Falling back to OpenCV Super Resolution") |
|
return load_model("OpenCV Super Resolution") |
|
|
|
|
|
elif config["method"] == "gfpgan": |
|
if GFPGANer is None: |
|
logger.warning("GFPGANer class not found, falling back to OpenCV SR.") |
|
return load_model("OpenCV Super Resolution") |
|
|
|
try: |
|
logger.info("Loading GFPGAN model") |
|
|
|
model_path = hf_hub_download( |
|
repo_id=config["repo_id"], |
|
filename=config["filename"], |
|
cache_dir=CACHE_DIR |
|
) |
|
|
|
|
|
|
|
|
|
|
|
face_enhancer = GFPGANer( |
|
model_path=model_path, |
|
upscale=config["scale"], |
|
arch='clean', |
|
channel_multiplier=2, |
|
bg_upsampler=None |
|
) |
|
|
|
model_cache[model_name] = (face_enhancer, model_type) |
|
return face_enhancer, model_type |
|
except Exception as e: |
|
logger.error(f"Error loading GFPGAN model: {str(e)}") |
|
logger.warning("Falling back to OpenCV Super Resolution") |
|
return load_model("OpenCV Super Resolution") |
|
|
|
|
|
elif config["method"] == "custom": |
|
|
|
model_cache[model_name] = (None, model_type) |
|
return None, model_type |
|
|
|
else: |
|
return None, f"Unknown model method: {config['method']}" |
|
|
|
except Exception as e: |
|
logger.error(f"Unexpected error during model loading for {model_name}: {str(e)}") |
|
import traceback |
|
traceback.print_exc() |
|
|
|
|
|
if model_name != "OpenCV Super Resolution": |
|
logger.info("Critical error loading model, falling back to OpenCV Super Resolution") |
|
return load_model("OpenCV Super Resolution") |
|
else: |
|
|
|
return None, f"Failed to load any model, including fallback: {str(e)}" |
|
|
|
|
|
|
|
def preprocess_image(image): |
|
"""Convert PIL image to numpy array for processing""" |
|
if image is None: |
|
return None |
|
|
|
if isinstance(image, Image.Image): |
|
|
|
img = np.array(image) |
|
else: |
|
|
|
img = image |
|
|
|
|
|
if len(img.shape) == 2: |
|
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) |
|
|
|
|
|
if img.shape[2] == 4: |
|
img = img[:, :, :3] |
|
|
|
|
|
img_bgr = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) |
|
|
|
return img_bgr |
|
|
|
|
|
def postprocess_image(img_bgr): |
|
"""Convert processed BGR image back to RGB PIL image""" |
|
if img_bgr is None: |
|
return None |
|
|
|
|
|
if img_bgr.dtype != np.uint8: |
|
|
|
img_bgr = np.clip(img_bgr, 0, 255) |
|
img_bgr = img_bgr.astype(np.uint8) |
|
|
|
|
|
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) |
|
|
|
return Image.fromarray(img_rgb) |
|
|
|
|
|
def enhance_hdr(img_bgr, strength=1.0): |
|
"""Custom HDR enhancement using OpenCV""" |
|
|
|
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) |
|
|
|
|
|
img_float = img_rgb.astype(np.float32) / 255.0 |
|
|
|
|
|
try: |
|
|
|
merge_mertens = cv2.createMergeMertens(contrast_weight=1.0, saturation_weight=1.0, exposure_weight=0.0) |
|
|
|
|
|
|
|
|
|
|
|
img_lab = cv2.cvtColor(img_float, cv2.COLOR_RGB2LAB) |
|
l, a, b = cv2.split(img_lab) |
|
|
|
|
|
|
|
clahe_l = cv2.createCLAHE(clipLimit=max(1.0, 5.0 * strength), tileGridSize=(8, 8)) |
|
|
|
l_uint8 = np.clip(l * 255.0, 0, 255).astype(np.uint8) |
|
l_enhanced_uint8 = clahe_l.apply(l_uint8) |
|
l_enhanced = l_enhanced_uint8.astype(np.float32) / 255.0 |
|
|
|
|
|
l_final = l * (1 - strength) + l_enhanced * strength |
|
|
|
|
|
img_lab_enhanced = cv2.merge([l_final, a, b]) |
|
img_rgb_enhanced = cv2.cvtColor(img_lab_enhanced, cv2.COLOR_LAB2RGB) |
|
|
|
|
|
|
|
img_hsv = cv2.cvtColor(img_rgb_enhanced, cv2.COLOR_RGB2HSV) |
|
h, s, v = cv2.split(img_hsv) |
|
|
|
|
|
saturation_factor = 0.4 * strength |
|
s_enhanced = np.clip(s + (s * saturation_factor * (1 - s)), 0, 1) |
|
|
|
|
|
brightness_factor = 0.1 * strength |
|
v_enhanced = np.clip(v + (v * brightness_factor), 0, 1) |
|
|
|
|
|
|
|
img_rgb_enhanced_hsv = cv2.cvtColor(cv2.merge([h, s_enhanced, v_enhanced]), cv2.COLOR_HSV2RGB) |
|
|
|
|
|
|
|
img_uint8_detail = (np.clip(img_rgb_enhanced_hsv, 0, 1) * 255).astype(np.uint8) |
|
blur = cv2.GaussianBlur(img_uint8_detail, (0, 0), 5) |
|
|
|
blur_float = blur.astype(np.float32) / 255.0 |
|
|
|
detail = img_rgb_enhanced_hsv - blur_float |
|
|
|
img_final_float = np.clip(img_rgb_enhanced_hsv + detail * (0.8 * strength), 0, 1) |
|
|
|
|
|
img_bgr_enhanced = (img_final_float * 255).astype(np.uint8) |
|
img_bgr_enhanced = cv2.cvtColor(img_bgr_enhanced, cv2.COLOR_RGB2BGR) |
|
|
|
return img_bgr_enhanced |
|
|
|
except Exception as e: |
|
logger.error(f"Error during HDR enhancement: {str(e)}") |
|
|
|
return img_bgr |
|
|
|
|
|
|
|
def enhance_image(image, model_name, strength=1.0, denoise=0.0, sharpen=0.0): |
|
"""Enhance image using selected model with additional processing options""" |
|
if image is None: |
|
return "Please upload an image.", None |
|
|
|
try: |
|
|
|
model, model_info = load_model(model_name) |
|
if isinstance(model_info, str) and model_info.startswith("Failed"): |
|
|
|
return model_info, None |
|
|
|
model_type = model_info |
|
|
|
|
|
img_bgr = preprocess_image(image) |
|
if img_bgr is None: |
|
return "Failed to process image", None |
|
|
|
|
|
if denoise > 0: |
|
logger.info(f"Applying denoising with strength {denoise}") |
|
|
|
|
|
h_val = int(denoise * 20 + 10) |
|
img_bgr = cv2.fastNlMeansDenoisingColored( |
|
img_bgr, None, |
|
h=h_val, |
|
hColor=h_val, |
|
templateWindowSize=7, |
|
searchWindowSize=21 |
|
) |
|
|
|
output_bgr = img_bgr |
|
|
|
|
|
if model_type == "upscale": |
|
if model is None: |
|
return f"Upscaling model '{model_name}' is not loaded or available.", None |
|
logger.info(f"Upscaling image with {model_name}") |
|
|
|
if model_name == "OpenCV Super Resolution": |
|
|
|
output_bgr = model.upsample(img_bgr) |
|
|
|
elif model_name == "Real-ESRGAN-x4": |
|
|
|
|
|
output_bgr = model.predict(img_bgr) |
|
|
|
|
|
|
|
elif model_type == "face": |
|
if model is None: |
|
return f"Face enhancement model '{model_name}' is not loaded or available.", None |
|
logger.info(f"Enhancing face with {model_name}") |
|
|
|
if model_name == "GFPGAN (Face Enhancement)": |
|
|
|
try: |
|
|
|
|
|
_, _, output_bgr = model.enhance( |
|
img_bgr, |
|
has_aligned=False, |
|
only_center_face=False, |
|
paste_back=True |
|
) |
|
except Exception as e: |
|
logger.error(f"Error enhancing face with GFPGAN: {str(e)}") |
|
|
|
|
|
output_bgr = img_bgr |
|
return f"Error applying GFPGAN: {str(e)}. Returning base image.", postprocess_image(output_bgr) |
|
|
|
elif model_type == "hdr": |
|
|
|
logger.info(f"Applying HDR enhancement with strength {strength}") |
|
output_bgr = enhance_hdr(img_bgr, strength=strength) |
|
|
|
else: |
|
|
|
return f"Unknown model type for processing: {model_type}", None |
|
|
|
|
|
|
|
if sharpen > 0: |
|
logger.info(f"Applying sharpening with strength {sharpen}") |
|
|
|
kernel = np.array([ |
|
[0, -1, 0], |
|
[-1, 5, -1], |
|
[0, -1, 0] |
|
], np.float32) |
|
|
|
|
|
sharpened_img = cv2.filter2D(output_bgr, -1, kernel) |
|
|
|
output_bgr = cv2.addWeighted(output_bgr, 1.0 - sharpen, sharpened_img, sharpen, 0) |
|
|
|
|
|
|
|
enhanced_image = postprocess_image(output_bgr) |
|
|
|
return "Image enhanced successfully!", enhanced_image |
|
|
|
except Exception as e: |
|
logger.error(f"An error occurred during image processing: {str(e)}") |
|
import traceback |
|
traceback.print_exc() |
|
|
|
if image is not None: |
|
try: |
|
original_img_pil = Image.fromarray(cv2.cvtColor(preprocess_image(image), cv2.COLOR_BGR2RGB)) |
|
return f"Processing failed: {str(e)}. Returning original image.", original_img_pil |
|
except Exception as post_e: |
|
logger.error(f"Failed to return original image after error: {str(post_e)}") |
|
return f"Processing failed: {str(e)}. Could not return image.", None |
|
else: |
|
return f"Processing failed: {str(e)}. No image provided.", None |
|
|
|
|
|
|
|
with gr.Blocks(title="Image Upscale & Enhancement - By FebryEnsz") as demo: |
|
gr.Markdown( |
|
""" |
|
# 🖼️ Image Upscale & Enhancement |
|
### By FebryEnsz |
|
|
|
Upload an image and enhance it with AI-powered upscaling and enhancement. |
|
|
|
**Features:** |
|
- Super-resolution upscaling (4x) using Real-ESRGAN or OpenCV |
|
- Face enhancement for portraits using GFPGAN |
|
- HDR enhancement for better contrast and details |
|
- Additional Denoise and Sharpen options |
|
""" |
|
) |
|
|
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
image_input = gr.Image(label="Upload Image", type="pil", image_mode="RGB") |
|
|
|
|
|
with gr.Group(): |
|
gr.Markdown("### Enhancement Options") |
|
model_choice = gr.Dropdown( |
|
choices=list(MODEL_OPTIONS.keys()), |
|
label="Model Selection", |
|
value="OpenCV Super Resolution", |
|
allow_flagging="never" |
|
) |
|
|
|
with gr.Accordion("Advanced Settings", open=False): |
|
|
|
strength_slider = gr.Slider( |
|
minimum=0.1, |
|
maximum=1.0, |
|
step=0.05, |
|
label="Enhancement Strength", |
|
value=0.8, |
|
visible=True |
|
) |
|
|
|
denoise_slider = gr.Slider( |
|
minimum=0.0, |
|
maximum=1.0, |
|
step=0.05, |
|
label="Noise Reduction Strength", |
|
value=0.0, |
|
) |
|
|
|
sharpen_slider = gr.Slider( |
|
minimum=0.0, |
|
maximum=1.0, |
|
step=0.05, |
|
label="Sharpening Strength", |
|
value=0.0, |
|
) |
|
|
|
enhance_button = gr.Button("✨ Enhance Image", variant="primary") |
|
|
|
with gr.Column(scale=1): |
|
output_text = gr.Textbox(label="Status") |
|
output_image = gr.Image(label="Enhanced Image", type="pil") |
|
|
|
|
|
|
|
def on_model_change(model_name): |
|
model_config = MODEL_OPTIONS.get(model_name, {}) |
|
model_type = model_config.get("type", "") |
|
|
|
if model_type == "hdr": |
|
return gr.update(label="HDR Intensity") |
|
elif model_type == "face": |
|
return gr.update(label="Face Enhancement Strength") |
|
elif model_type == "upscale": |
|
return gr.update(label="Enhancement Strength") |
|
else: |
|
return gr.update(label="Enhancement Strength") |
|
|
|
model_choice.change(on_model_change, inputs=[model_choice], outputs=[strength_slider]) |
|
|
|
|
|
enhance_button.click( |
|
fn=enhance_image, |
|
inputs=[image_input, model_choice, strength_slider, denoise_slider, sharpen_slider], |
|
outputs=[output_text, output_image], |
|
api_name="enhance" |
|
) |
|
|
|
|
|
gr.Markdown( |
|
""" |
|
### Tips |
|
- For best results with face enhancement, ensure faces are clearly visible. |
|
- HDR enhancement works best with images that have both bright and dark areas. |
|
- For noisy images, try increasing the noise reduction slider. |
|
- Sharpening can add detail but may also increase noise if applied too strongly. |
|
|
|
--- |
|
Version 2.1 | Running on: """ + (f"GPU 🚀 ({torch.cuda.get_device_name(0)})" if torch.cuda.is_available() else "CPU ⚙️") + """ |
|
""" |
|
) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
demo.launch(enable_queue=True) |