feat: SOTA AI photo restoration with multi-step pipeline (#58) (#62)

Add comprehensive photo restoration tool that chains multiple AI models:
- Scratch/tear/spot detection via morphological analysis (top-hat/black-hat transforms)
- Damage inpainting via LaMa ONNX model (reuses existing infrastructure)
- Face enhancement via CodeFormer ONNX (~377MB, from facefusion/models-3.0.0)
- Noise reduction via OpenCV NLMeans in LAB color space
- Optional B&W auto-colorization via DDColor (reuses existing model)

Settings: 3 restoration modes (Light/Auto/Heavy), individual feature toggles
for scratch removal, face enhancement (with fidelity slider), denoising
(with strength slider), and auto-colorize. Before/after comparison view.

Handles HEIC, HEIF, and all standard formats. Batch processing supported.
No new Python dependencies - reuses onnxruntime, cv2, mediapipe, PIL.

Co-authored-by: stirling-image <stirling-image@users.noreply.github.com>
This commit is contained in:
stirling-image
2026-04-13 21:57:51 +08:00
committed by GitHub
co-authored by stirling-image
parent 8071fe61c5
commit 6a43cc1b77
11 changed files with 1224 additions and 0 deletions
+43
View File
@@ -53,6 +53,10 @@ SCUNET_MODEL_URL = (
SCUNET_MODEL_PATH = os.path.join(SCUNET_MODEL_DIR, "scunet_color_real_psnr.pth")
SCUNET_MIN_SIZE = 3_000_000 # ~4 MB
CODEFORMER_MODEL_DIR = "/opt/models/codeformer"
CODEFORMER_ONNX_PATH = os.path.join(CODEFORMER_MODEL_DIR, "codeformer.onnx")
CODEFORMER_MIN_SIZE = 100_000_000 # ~377 MB
NAFNET_MODEL_DIR = "/opt/models/nafnet"
NAFNET_MODEL_URL = (
"https://huggingface.co/mikestealth/nafnet-models/resolve/main/"
@@ -219,6 +223,35 @@ def download_ddcolor_model():
def download_codeformer_model():
"""Download CodeFormer ONNX model for AI face restoration.
Uses the pre-converted ONNX model from HuggingFace (facefusion repo)
for direct inference via onnxruntime without needing PyTorch.
"""
print("=== Downloading CodeFormer ONNX model ===")
os.makedirs(CODEFORMER_MODEL_DIR, exist_ok=True)
from huggingface_hub import hf_hub_download
print(" Downloading CodeFormer ONNX from HuggingFace...")
downloaded_path = hf_hub_download(
repo_id="facefusion/models-3.0.0",
filename="codeformer.onnx",
local_dir=CODEFORMER_MODEL_DIR,
)
actual_path = os.path.join(CODEFORMER_MODEL_DIR, "codeformer.onnx")
if not os.path.exists(actual_path) and os.path.exists(downloaded_path):
os.rename(downloaded_path, actual_path)
size = os.path.getsize(actual_path)
assert size > CODEFORMER_MIN_SIZE, (
f"CodeFormer model too small: {size} bytes (expected > {CODEFORMER_MIN_SIZE})"
)
print(f" CodeFormer ONNX model ready ({size / 1_000_000:.1f} MB)\n")
def download_paddleocr_models():
"""Pre-download PaddleOCR PP-OCRv5 model weights from HuggingFace.
@@ -358,6 +391,15 @@ def smoke_test():
)
print(" DDColor ONNX model file verified")
# CodeFormer ONNX model must exist
assert os.path.exists(CODEFORMER_ONNX_PATH), (
f"CodeFormer model missing: {CODEFORMER_ONNX_PATH}"
)
assert os.path.getsize(CODEFORMER_ONNX_PATH) > CODEFORMER_MIN_SIZE, (
"CodeFormer model file is too small"
)
print(" CodeFormer ONNX model file verified")
# SCUNet model file must exist
assert os.path.exists(SCUNET_MODEL_PATH), f"SCUNet model not found: {SCUNET_MODEL_PATH}"
assert os.path.getsize(SCUNET_MODEL_PATH) > SCUNET_MIN_SIZE
@@ -392,6 +434,7 @@ def main():
download_gfpgan_model()
download_codeformer_model()
download_ddcolor_model()
download_codeformer_model()
download_paddleocr_models()
download_paddleocr_vl_model()
download_scunet_model()