supli6669 commited on
Commit ·
46aa171
1
Parent(s): 7bc1a4f
feat: add multi-category dataset crawler + Real-ESRGAN training pipeline
Browse files- crawl_datasets.py: crawl face/landscape/anime images from HuggingFace
(FFHQ, mertcobanov/nature-dataset, amirali900/Anime-Face-Dataset-10k)
- train_realesrgan.py: setup + train Real-ESRGAN for general image enhancement
(landscape, anime, scenery) alongside CodeFormer face model
- train_sharpening_model.py: sharpening model training script
- scan_c_drive.py: disk scan utility
- CodeFormer_stage3_custom.yml: bump total_iter 350->600 to continue training
- crawl_datasets.py +193 -0
- models/CodeFormer/options/CodeFormer_stage3_custom.yml +72 -61
- scan_c_drive.py +74 -0
- train_realesrgan.py +335 -0
- train_sharpening_model.py +90 -0
crawl_datasets.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import io
|
| 4 |
+
import pandas as pd
|
| 5 |
+
from PIL import Image
|
| 6 |
+
import requests
|
| 7 |
+
import time
|
| 8 |
+
|
| 9 |
+
# ─────────────────────────────────────────────
|
| 10 |
+
# HuggingFace dataset sources
|
| 11 |
+
# ─────────────────────────────────────────────
|
| 12 |
+
SOURCES = {
|
| 13 |
+
"face": {
|
| 14 |
+
"parquet_url": "https://huggingface.co/datasets/Ryan-sjtu/ffhq512-caption/resolve/main/data/train-00000-of-00054-9b5f7c3e6bc03b3b.parquet",
|
| 15 |
+
"num_images": 300,
|
| 16 |
+
"subdir": "faces",
|
| 17 |
+
"prefix": "face",
|
| 18 |
+
"description": "FFHQ 512x512 real face photos",
|
| 19 |
+
},
|
| 20 |
+
"landscape": {
|
| 21 |
+
"parquet_url": "https://huggingface.co/datasets/mertcobanov/nature-dataset/resolve/refs%2Fconvert%2Fparquet/default/train/0000.parquet",
|
| 22 |
+
"num_images": 300,
|
| 23 |
+
"subdir": "landscapes",
|
| 24 |
+
"prefix": "landscape",
|
| 25 |
+
"description": "Nature & landscape scenery (50k images)",
|
| 26 |
+
},
|
| 27 |
+
"anime": {
|
| 28 |
+
"parquet_url": "https://huggingface.co/datasets/amirali900/Anime-Face-Dataset-10k/resolve/refs%2Fconvert%2Fparquet/default/train/0000.parquet",
|
| 29 |
+
"num_images": 300,
|
| 30 |
+
"subdir": "anime",
|
| 31 |
+
"prefix": "anime",
|
| 32 |
+
"description": "Anime Face Dataset 10k illustrations",
|
| 33 |
+
},
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
# Fallback: direct image download lists for anime / landscape
|
| 37 |
+
ANIME_FALLBACK_URLS = [
|
| 38 |
+
"https://huggingface.co/datasets/huggan/anime-faces/resolve/main/data/train-00000-of-00001.parquet",
|
| 39 |
+
]
|
| 40 |
+
|
| 41 |
+
LANDSCAPE_FALLBACK_URLS = [
|
| 42 |
+
"https://huggingface.co/datasets/jlbaker361/flickr_humans/resolve/main/data/train-00000-of-00001.parquet",
|
| 43 |
+
]
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def load_parquet_safe(url: str) -> pd.DataFrame | None:
|
| 47 |
+
"""Try to load a parquet file from URL, return None on failure."""
|
| 48 |
+
print(f" Loading: {url}")
|
| 49 |
+
try:
|
| 50 |
+
df = pd.read_parquet(url)
|
| 51 |
+
print(f" [OK] Loaded {len(df)} rows.")
|
| 52 |
+
return df
|
| 53 |
+
except Exception as e:
|
| 54 |
+
print(f" [FAIL] Failed: {e}")
|
| 55 |
+
return None
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def find_image_column(df: pd.DataFrame) -> str | None:
|
| 59 |
+
"""Detect which column holds image data (bytes dict or PIL-compatible)."""
|
| 60 |
+
for col in df.columns:
|
| 61 |
+
sample = df[col].iloc[0]
|
| 62 |
+
if isinstance(sample, dict) and "bytes" in sample:
|
| 63 |
+
return col
|
| 64 |
+
if isinstance(sample, bytes):
|
| 65 |
+
return col
|
| 66 |
+
return None
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def extract_image_bytes(cell) -> bytes | None:
|
| 70 |
+
"""Extract raw image bytes from a dataframe cell regardless of format."""
|
| 71 |
+
if isinstance(cell, dict):
|
| 72 |
+
return cell.get("bytes")
|
| 73 |
+
if isinstance(cell, bytes):
|
| 74 |
+
return cell
|
| 75 |
+
return None
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def crawl_source(name: str, cfg: dict, base_dataset_dir: str):
|
| 79 |
+
"""Download images for a single source category."""
|
| 80 |
+
save_dir = os.path.join(base_dataset_dir, cfg["subdir"])
|
| 81 |
+
os.makedirs(save_dir, exist_ok=True)
|
| 82 |
+
|
| 83 |
+
print(f"\n{'='*55}")
|
| 84 |
+
print(f" [{name.upper()}] {cfg['description']}")
|
| 85 |
+
print(f" Save dir : {save_dir}")
|
| 86 |
+
print(f" Target : {cfg['num_images']} images")
|
| 87 |
+
print(f"{'='*55}")
|
| 88 |
+
|
| 89 |
+
df = load_parquet_safe(cfg["parquet_url"])
|
| 90 |
+
|
| 91 |
+
# Try fallbacks if primary failed
|
| 92 |
+
if df is None:
|
| 93 |
+
fallbacks = []
|
| 94 |
+
if name == "anime":
|
| 95 |
+
fallbacks = ANIME_FALLBACK_URLS
|
| 96 |
+
elif name == "landscape":
|
| 97 |
+
fallbacks = LANDSCAPE_FALLBACK_URLS
|
| 98 |
+
for fb_url in fallbacks:
|
| 99 |
+
print(f" Trying fallback: {fb_url}")
|
| 100 |
+
df = load_parquet_safe(fb_url)
|
| 101 |
+
if df is not None:
|
| 102 |
+
break
|
| 103 |
+
|
| 104 |
+
if df is None:
|
| 105 |
+
print(f" [FAIL] All sources failed for '{name}'. Skipping.")
|
| 106 |
+
return 0
|
| 107 |
+
|
| 108 |
+
img_col = find_image_column(df)
|
| 109 |
+
if img_col is None:
|
| 110 |
+
print(f" [FAIL] No image column detected in dataset. Columns: {list(df.columns)}")
|
| 111 |
+
return 0
|
| 112 |
+
|
| 113 |
+
print(f" Image column: '{img_col}'")
|
| 114 |
+
|
| 115 |
+
# Find existing highest index in this subdir to avoid overwrites
|
| 116 |
+
existing = [
|
| 117 |
+
f for f in os.listdir(save_dir)
|
| 118 |
+
if f.endswith(".png") and f.startswith(cfg["prefix"])
|
| 119 |
+
]
|
| 120 |
+
highest_idx = -1
|
| 121 |
+
for f in existing:
|
| 122 |
+
try:
|
| 123 |
+
idx = int(f.replace(cfg["prefix"] + "_", "").replace(".png", ""))
|
| 124 |
+
highest_idx = max(highest_idx, idx)
|
| 125 |
+
except ValueError:
|
| 126 |
+
continue
|
| 127 |
+
|
| 128 |
+
start_idx = highest_idx + 1
|
| 129 |
+
saved = 0
|
| 130 |
+
target = cfg["num_images"]
|
| 131 |
+
|
| 132 |
+
for i in range(len(df)):
|
| 133 |
+
if saved >= target:
|
| 134 |
+
break
|
| 135 |
+
try:
|
| 136 |
+
cell = df[img_col].iloc[i]
|
| 137 |
+
img_bytes = extract_image_bytes(cell)
|
| 138 |
+
if not img_bytes:
|
| 139 |
+
continue
|
| 140 |
+
|
| 141 |
+
img = Image.open(io.BytesIO(img_bytes)).convert("RGB")
|
| 142 |
+
img = img.resize((512, 512), Image.Resampling.LANCZOS)
|
| 143 |
+
|
| 144 |
+
filename = f"{cfg['prefix']}_{start_idx + saved:05d}.png"
|
| 145 |
+
filepath = os.path.join(save_dir, filename)
|
| 146 |
+
img.save(filepath, "PNG")
|
| 147 |
+
saved += 1
|
| 148 |
+
|
| 149 |
+
if saved % 20 == 0 or saved == target:
|
| 150 |
+
print(f" [{name}] {saved}/{target} images saved...")
|
| 151 |
+
|
| 152 |
+
except Exception as e:
|
| 153 |
+
print(f" [warn] Row {i} error: {e}")
|
| 154 |
+
continue
|
| 155 |
+
|
| 156 |
+
print(f"\n [OK] [{name.upper()}] Done: {saved} images saved to {save_dir}")
|
| 157 |
+
return saved
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def main():
|
| 161 |
+
project_dir = os.path.dirname(os.path.abspath(__file__))
|
| 162 |
+
codeformer_dir = os.path.join(project_dir, "models", "CodeFormer")
|
| 163 |
+
base_dataset_dir = os.path.join(codeformer_dir, "datasets", "ffhq", "ffhq_512")
|
| 164 |
+
os.makedirs(base_dataset_dir, exist_ok=True)
|
| 165 |
+
|
| 166 |
+
print("=" * 55)
|
| 167 |
+
print(" MULTI-CATEGORY DATASET CRAWLER")
|
| 168 |
+
print(" Categories: Face | Landscape | Anime Girl")
|
| 169 |
+
print("=" * 55)
|
| 170 |
+
|
| 171 |
+
total_saved = 0
|
| 172 |
+
for name, cfg in SOURCES.items():
|
| 173 |
+
saved = crawl_source(name, cfg, base_dataset_dir)
|
| 174 |
+
total_saved += saved
|
| 175 |
+
time.sleep(0.5)
|
| 176 |
+
|
| 177 |
+
# Count all images recursively
|
| 178 |
+
all_images = []
|
| 179 |
+
for root, _, files in os.walk(base_dataset_dir):
|
| 180 |
+
for f in files:
|
| 181 |
+
if f.lower().endswith(".png"):
|
| 182 |
+
all_images.append(f)
|
| 183 |
+
|
| 184 |
+
print("\n" + "=" * 55)
|
| 185 |
+
print(f" CRAWL COMPLETE")
|
| 186 |
+
print(f" New images this run : {total_saved}")
|
| 187 |
+
print(f" Total dataset size : {len(all_images)} images")
|
| 188 |
+
print(f" Dataset location : {base_dataset_dir}")
|
| 189 |
+
print("=" * 55)
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
if __name__ == "__main__":
|
| 193 |
+
main()
|
models/CodeFormer/options/CodeFormer_stage3_custom.yml
CHANGED
|
@@ -1,10 +1,7 @@
|
|
| 1 |
-
# general settings
|
| 2 |
name: CodeFormer_stage3_custom
|
| 3 |
model_type: CodeFormerJointModel
|
| 4 |
num_gpu: 0
|
| 5 |
manual_seed: 0
|
| 6 |
-
|
| 7 |
-
# dataset and data loader settings
|
| 8 |
datasets:
|
| 9 |
train:
|
| 10 |
name: CustomDataset
|
|
@@ -13,142 +10,156 @@ datasets:
|
|
| 13 |
filename_tmpl: '{}'
|
| 14 |
io_backend:
|
| 15 |
type: disk
|
| 16 |
-
|
| 17 |
in_size: 512
|
| 18 |
gt_size: 512
|
| 19 |
-
mean:
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
use_hflip: true
|
| 22 |
use_corrupt: true
|
| 23 |
-
|
| 24 |
-
# Comprehensive degradation parameters (Motion blur, Gaussian blur, Downsampling, Noise, JPEG)
|
| 25 |
blur_kernel_size: 41
|
| 26 |
use_motion_kernel: true
|
| 27 |
motion_kernel_prob: 0.05
|
| 28 |
-
kernel_list:
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
latent_gt_path: null
|
| 44 |
-
|
| 45 |
-
# Data loader configurations
|
| 46 |
num_worker_per_gpu: 0
|
| 47 |
batch_size_per_gpu: 1
|
| 48 |
dataset_enlarge_ratio: 1
|
| 49 |
prefetch_mode: cpu
|
| 50 |
-
|
| 51 |
-
# network structures
|
| 52 |
network_g:
|
| 53 |
type: CodeFormer
|
| 54 |
dim_embd: 512
|
| 55 |
n_head: 8
|
| 56 |
n_layers: 9
|
| 57 |
codebook_size: 1024
|
| 58 |
-
connect_list:
|
| 59 |
-
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
network_vqgan:
|
| 62 |
type: VQAutoEncoder
|
| 63 |
img_size: 512
|
| 64 |
nf: 64
|
| 65 |
-
ch_mult:
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
codebook_size: 1024
|
| 68 |
-
|
| 69 |
network_d:
|
| 70 |
type: VQGANDiscriminator
|
| 71 |
nc: 3
|
| 72 |
ndf: 64
|
| 73 |
n_layers: 4
|
| 74 |
-
|
| 75 |
-
# path settings
|
| 76 |
path:
|
| 77 |
-
pretrain_network_g:
|
| 78 |
param_key_g: params_ema
|
| 79 |
strict_load_g: false
|
| 80 |
pretrain_network_d: null
|
| 81 |
-
resume_state:
|
| 82 |
-
|
| 83 |
-
# training loss and optimizer settings
|
| 84 |
train:
|
| 85 |
use_hq_feat_loss: true
|
| 86 |
feat_loss_weight: 1.0
|
| 87 |
cross_entropy_loss: true
|
| 88 |
entropy_loss_weight: 0.5
|
| 89 |
scale_adaptive_gan_weight: 0.1
|
| 90 |
-
|
| 91 |
optim_g:
|
| 92 |
type: Adam
|
| 93 |
-
lr:
|
| 94 |
weight_decay: 0
|
| 95 |
-
betas:
|
|
|
|
|
|
|
| 96 |
optim_d:
|
| 97 |
type: Adam
|
| 98 |
-
lr:
|
| 99 |
weight_decay: 0
|
| 100 |
-
betas:
|
| 101 |
-
|
|
|
|
| 102 |
scheduler:
|
| 103 |
type: CosineAnnealingRestartLR
|
| 104 |
-
periods:
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
|
|
|
| 109 |
warmup_iter: -1
|
| 110 |
ema_decay: 0.997
|
| 111 |
-
|
| 112 |
pixel_opt:
|
| 113 |
type: L1Loss
|
| 114 |
loss_weight: 1.0
|
| 115 |
reduction: mean
|
| 116 |
-
|
| 117 |
perceptual_opt:
|
| 118 |
type: LPIPSLoss
|
| 119 |
loss_weight: 1.0
|
| 120 |
use_input_norm: true
|
| 121 |
range_norm: true
|
| 122 |
-
|
| 123 |
gan_opt:
|
| 124 |
type: GANLoss
|
| 125 |
gan_type: hinge
|
| 126 |
-
loss_weight:
|
| 127 |
-
|
| 128 |
use_adaptive_weight: true
|
| 129 |
net_g_start_iter: 0
|
| 130 |
net_d_iters: 1
|
| 131 |
net_d_start_iter: 5001
|
| 132 |
manual_seed: 0
|
| 133 |
-
|
| 134 |
-
# validation settings
|
| 135 |
val:
|
| 136 |
-
val_freq:
|
| 137 |
save_img: true
|
| 138 |
metrics:
|
| 139 |
psnr:
|
| 140 |
type: calculate_psnr
|
| 141 |
crop_border: 4
|
| 142 |
test_y_channel: false
|
| 143 |
-
|
| 144 |
-
# logging settings
|
| 145 |
logger:
|
| 146 |
print_freq: 1
|
| 147 |
save_checkpoint_freq: 5
|
| 148 |
use_tb_logger: false
|
| 149 |
wandb:
|
| 150 |
project: null
|
| 151 |
-
|
| 152 |
find_unused_parameters: true
|
| 153 |
dist_params: null
|
| 154 |
dist: false
|
|
|
|
|
|
|
| 1 |
name: CodeFormer_stage3_custom
|
| 2 |
model_type: CodeFormerJointModel
|
| 3 |
num_gpu: 0
|
| 4 |
manual_seed: 0
|
|
|
|
|
|
|
| 5 |
datasets:
|
| 6 |
train:
|
| 7 |
name: CustomDataset
|
|
|
|
| 10 |
filename_tmpl: '{}'
|
| 11 |
io_backend:
|
| 12 |
type: disk
|
|
|
|
| 13 |
in_size: 512
|
| 14 |
gt_size: 512
|
| 15 |
+
mean:
|
| 16 |
+
- 0.5
|
| 17 |
+
- 0.5
|
| 18 |
+
- 0.5
|
| 19 |
+
std:
|
| 20 |
+
- 0.5
|
| 21 |
+
- 0.5
|
| 22 |
+
- 0.5
|
| 23 |
use_hflip: true
|
| 24 |
use_corrupt: true
|
|
|
|
|
|
|
| 25 |
blur_kernel_size: 41
|
| 26 |
use_motion_kernel: true
|
| 27 |
motion_kernel_prob: 0.05
|
| 28 |
+
kernel_list:
|
| 29 |
+
- iso
|
| 30 |
+
- aniso
|
| 31 |
+
kernel_prob:
|
| 32 |
+
- 0.5
|
| 33 |
+
- 0.5
|
| 34 |
+
blur_sigma:
|
| 35 |
+
- 0.1
|
| 36 |
+
- 10.0
|
| 37 |
+
downsample_range:
|
| 38 |
+
- 1.0
|
| 39 |
+
- 12.0
|
| 40 |
+
noise_range:
|
| 41 |
+
- 0.0
|
| 42 |
+
- 20.0
|
| 43 |
+
jpeg_range:
|
| 44 |
+
- 50
|
| 45 |
+
- 100
|
| 46 |
+
blur_sigma_large:
|
| 47 |
+
- 1.0
|
| 48 |
+
- 15.0
|
| 49 |
+
downsample_range_large:
|
| 50 |
+
- 4.0
|
| 51 |
+
- 30.0
|
| 52 |
+
noise_range_large:
|
| 53 |
+
- 0.0
|
| 54 |
+
- 30.0
|
| 55 |
+
jpeg_range_large:
|
| 56 |
+
- 30
|
| 57 |
+
- 80
|
| 58 |
latent_gt_path: null
|
|
|
|
|
|
|
| 59 |
num_worker_per_gpu: 0
|
| 60 |
batch_size_per_gpu: 1
|
| 61 |
dataset_enlarge_ratio: 1
|
| 62 |
prefetch_mode: cpu
|
|
|
|
|
|
|
| 63 |
network_g:
|
| 64 |
type: CodeFormer
|
| 65 |
dim_embd: 512
|
| 66 |
n_head: 8
|
| 67 |
n_layers: 9
|
| 68 |
codebook_size: 1024
|
| 69 |
+
connect_list:
|
| 70 |
+
- '32'
|
| 71 |
+
- '64'
|
| 72 |
+
- '128'
|
| 73 |
+
- '256'
|
| 74 |
+
fix_modules:
|
| 75 |
+
- quantize
|
| 76 |
+
- generator
|
| 77 |
network_vqgan:
|
| 78 |
type: VQAutoEncoder
|
| 79 |
img_size: 512
|
| 80 |
nf: 64
|
| 81 |
+
ch_mult:
|
| 82 |
+
- 1
|
| 83 |
+
- 2
|
| 84 |
+
- 2
|
| 85 |
+
- 4
|
| 86 |
+
- 4
|
| 87 |
+
- 8
|
| 88 |
+
quantizer: nearest
|
| 89 |
codebook_size: 1024
|
|
|
|
| 90 |
network_d:
|
| 91 |
type: VQGANDiscriminator
|
| 92 |
nc: 3
|
| 93 |
ndf: 64
|
| 94 |
n_layers: 4
|
|
|
|
|
|
|
| 95 |
path:
|
| 96 |
+
pretrain_network_g: null
|
| 97 |
param_key_g: params_ema
|
| 98 |
strict_load_g: false
|
| 99 |
pretrain_network_d: null
|
| 100 |
+
resume_state: D:\.gemini-scratch\custom-ai-enhancer\models\CodeFormer\experiments\20260708_201102_CodeFormer_stage3_custom\training_states\350.state
|
|
|
|
|
|
|
| 101 |
train:
|
| 102 |
use_hq_feat_loss: true
|
| 103 |
feat_loss_weight: 1.0
|
| 104 |
cross_entropy_loss: true
|
| 105 |
entropy_loss_weight: 0.5
|
| 106 |
scale_adaptive_gan_weight: 0.1
|
|
|
|
| 107 |
optim_g:
|
| 108 |
type: Adam
|
| 109 |
+
lr: 5.0e-05
|
| 110 |
weight_decay: 0
|
| 111 |
+
betas:
|
| 112 |
+
- 0.9
|
| 113 |
+
- 0.99
|
| 114 |
optim_d:
|
| 115 |
type: Adam
|
| 116 |
+
lr: 5.0e-05
|
| 117 |
weight_decay: 0
|
| 118 |
+
betas:
|
| 119 |
+
- 0.9
|
| 120 |
+
- 0.99
|
| 121 |
scheduler:
|
| 122 |
type: CosineAnnealingRestartLR
|
| 123 |
+
periods:
|
| 124 |
+
- 150000
|
| 125 |
+
restart_weights:
|
| 126 |
+
- 1
|
| 127 |
+
eta_min: 2.0e-05
|
| 128 |
+
total_iter: 600
|
| 129 |
warmup_iter: -1
|
| 130 |
ema_decay: 0.997
|
|
|
|
| 131 |
pixel_opt:
|
| 132 |
type: L1Loss
|
| 133 |
loss_weight: 1.0
|
| 134 |
reduction: mean
|
|
|
|
| 135 |
perceptual_opt:
|
| 136 |
type: LPIPSLoss
|
| 137 |
loss_weight: 1.0
|
| 138 |
use_input_norm: true
|
| 139 |
range_norm: true
|
|
|
|
| 140 |
gan_opt:
|
| 141 |
type: GANLoss
|
| 142 |
gan_type: hinge
|
| 143 |
+
loss_weight: 1.0
|
|
|
|
| 144 |
use_adaptive_weight: true
|
| 145 |
net_g_start_iter: 0
|
| 146 |
net_d_iters: 1
|
| 147 |
net_d_start_iter: 5001
|
| 148 |
manual_seed: 0
|
|
|
|
|
|
|
| 149 |
val:
|
| 150 |
+
val_freq: 50000000000.0
|
| 151 |
save_img: true
|
| 152 |
metrics:
|
| 153 |
psnr:
|
| 154 |
type: calculate_psnr
|
| 155 |
crop_border: 4
|
| 156 |
test_y_channel: false
|
|
|
|
|
|
|
| 157 |
logger:
|
| 158 |
print_freq: 1
|
| 159 |
save_checkpoint_freq: 5
|
| 160 |
use_tb_logger: false
|
| 161 |
wandb:
|
| 162 |
project: null
|
|
|
|
| 163 |
find_unused_parameters: true
|
| 164 |
dist_params: null
|
| 165 |
dist: false
|
scan_c_drive.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
|
| 4 |
+
def get_dir_size(path, max_depth=4, current_depth=0):
|
| 5 |
+
total = 0
|
| 6 |
+
if current_depth > max_depth:
|
| 7 |
+
return 0
|
| 8 |
+
try:
|
| 9 |
+
for entry in os.scandir(path):
|
| 10 |
+
try:
|
| 11 |
+
if entry.is_file(follow_symlinks=False):
|
| 12 |
+
total += entry.stat().st_size
|
| 13 |
+
elif entry.is_dir(follow_symlinks=False):
|
| 14 |
+
total += get_dir_size(entry.path, max_depth, current_depth + 1)
|
| 15 |
+
except Exception:
|
| 16 |
+
continue
|
| 17 |
+
except Exception:
|
| 18 |
+
pass
|
| 19 |
+
return total
|
| 20 |
+
|
| 21 |
+
def main():
|
| 22 |
+
user_home = os.path.expanduser("~")
|
| 23 |
+
print(f"Scanning large directories in: {user_home}...")
|
| 24 |
+
|
| 25 |
+
large_dirs = []
|
| 26 |
+
|
| 27 |
+
# List top level directories in user home
|
| 28 |
+
try:
|
| 29 |
+
entries = list(os.scandir(user_home))
|
| 30 |
+
except Exception as e:
|
| 31 |
+
print(f"Error reading home directory: {e}")
|
| 32 |
+
return
|
| 33 |
+
|
| 34 |
+
for entry in entries:
|
| 35 |
+
if entry.is_dir(follow_symlinks=False):
|
| 36 |
+
# Skip some standard tiny folders or system symbolic links to speed up
|
| 37 |
+
if entry.name.startswith('.') and entry.name not in ['.cache', '.conda', '.docker', '.npm', '.nuget', '.vscode', '.gradle']:
|
| 38 |
+
continue
|
| 39 |
+
|
| 40 |
+
print(f"Checking size of {entry.name}...")
|
| 41 |
+
size = get_dir_size(entry.path, max_depth=5)
|
| 42 |
+
size_gb = size / (1024 * 1024 * 1024)
|
| 43 |
+
if size_gb > 0.5: # larger than 500 MB
|
| 44 |
+
large_dirs.append((entry.path, size_gb))
|
| 45 |
+
print(f"-> Found large dir: {entry.name} ({size_gb:.2f} GB)")
|
| 46 |
+
|
| 47 |
+
print("\n=== Scan Results (Directories > 0.5 GB) ===")
|
| 48 |
+
large_dirs.sort(key=lambda x: x[1], reverse=True)
|
| 49 |
+
for path, size_gb in large_dirs:
|
| 50 |
+
print(f"- {path}: {size_gb:.2f} GB")
|
| 51 |
+
|
| 52 |
+
# If it is AppData, scan one level deeper to pinpoint the culprit
|
| 53 |
+
if "AppData" in path:
|
| 54 |
+
print(" Pinpointing AppData contents...")
|
| 55 |
+
for sub_name in ["Local", "Roaming", "LocalLow"]:
|
| 56 |
+
sub_path = os.path.join(path, sub_name)
|
| 57 |
+
if os.path.exists(sub_path):
|
| 58 |
+
sub_size = get_dir_size(sub_path, max_depth=4)
|
| 59 |
+
sub_size_gb = sub_size / (1024 * 1024 * 1024)
|
| 60 |
+
if sub_size_gb > 0.5:
|
| 61 |
+
print(f" - {sub_path}: {sub_size_gb:.2f} GB")
|
| 62 |
+
# Pinpoint Local/Roaming further
|
| 63 |
+
try:
|
| 64 |
+
for local_entry in os.scandir(sub_path):
|
| 65 |
+
if local_entry.is_dir(follow_symlinks=False):
|
| 66 |
+
l_size = get_dir_size(local_entry.path, max_depth=3)
|
| 67 |
+
l_size_gb = l_size / (1024 * 1024 * 1024)
|
| 68 |
+
if l_size_gb > 0.5:
|
| 69 |
+
print(f" - {local_entry.path}: {l_size_gb:.2f} GB")
|
| 70 |
+
except Exception:
|
| 71 |
+
pass
|
| 72 |
+
|
| 73 |
+
if __name__ == "__main__":
|
| 74 |
+
main()
|
train_realesrgan.py
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Train Real-ESRGAN for general image enhancement (landscape, anime, scenery).
|
| 3 |
+
This script:
|
| 4 |
+
1. Clones Real-ESRGAN repo if not present
|
| 5 |
+
2. Prepares a training config pointing to our crawled dataset
|
| 6 |
+
3. Launches the training process
|
| 7 |
+
"""
|
| 8 |
+
import os
|
| 9 |
+
import sys
|
| 10 |
+
import subprocess
|
| 11 |
+
import shutil
|
| 12 |
+
import yaml
|
| 13 |
+
import glob
|
| 14 |
+
|
| 15 |
+
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 16 |
+
REALESRGAN_DIR = os.path.join(PROJECT_DIR, "models", "Real-ESRGAN")
|
| 17 |
+
|
| 18 |
+
# Dataset paths (populated by crawl_datasets.py)
|
| 19 |
+
DATASET_BASE = os.path.join(
|
| 20 |
+
PROJECT_DIR, "models", "CodeFormer", "datasets", "ffhq", "ffhq_512"
|
| 21 |
+
)
|
| 22 |
+
LANDSCAPE_DIR = os.path.join(DATASET_BASE, "landscapes")
|
| 23 |
+
ANIME_DIR = os.path.join(DATASET_BASE, "anime")
|
| 24 |
+
FACE_DIR = os.path.join(DATASET_BASE, "faces")
|
| 25 |
+
|
| 26 |
+
# Combined GT (high-quality) folder for Real-ESRGAN training
|
| 27 |
+
REALESRGAN_GT_DIR = os.path.join(PROJECT_DIR, "datasets", "realesrgan_gt")
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def clone_realesrgan():
|
| 31 |
+
"""Clone Real-ESRGAN repository if not already present."""
|
| 32 |
+
if os.path.exists(REALESRGAN_DIR):
|
| 33 |
+
print("[OK] Real-ESRGAN repo already present.")
|
| 34 |
+
return True
|
| 35 |
+
print("Cloning Real-ESRGAN repository...")
|
| 36 |
+
result = subprocess.run(
|
| 37 |
+
["git", "clone", "https://github.com/xinntao/Real-ESRGAN.git", REALESRGAN_DIR],
|
| 38 |
+
capture_output=False,
|
| 39 |
+
)
|
| 40 |
+
if result.returncode != 0:
|
| 41 |
+
print("[FAIL] Failed to clone Real-ESRGAN.")
|
| 42 |
+
return False
|
| 43 |
+
print("[OK] Real-ESRGAN cloned successfully.")
|
| 44 |
+
return True
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def install_realesrgan_deps():
|
| 48 |
+
"""Install Real-ESRGAN dependencies."""
|
| 49 |
+
print("Installing Real-ESRGAN dependencies...")
|
| 50 |
+
requirements_file = os.path.join(REALESRGAN_DIR, "requirements.txt")
|
| 51 |
+
if os.path.exists(requirements_file):
|
| 52 |
+
subprocess.run(
|
| 53 |
+
[sys.executable, "-m", "pip", "install", "-r", requirements_file],
|
| 54 |
+
check=True,
|
| 55 |
+
)
|
| 56 |
+
# Also install basicsr for Real-ESRGAN
|
| 57 |
+
subprocess.run(
|
| 58 |
+
[sys.executable, "-m", "pip", "install", "basicsr", "facexlib", "gfpgan"],
|
| 59 |
+
check=True,
|
| 60 |
+
)
|
| 61 |
+
print("[OK] Dependencies installed.")
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def prepare_gt_dataset():
|
| 65 |
+
"""Combine landscape + anime + face images into a single GT folder."""
|
| 66 |
+
os.makedirs(REALESRGAN_GT_DIR, exist_ok=True)
|
| 67 |
+
|
| 68 |
+
total_copied = 0
|
| 69 |
+
sources = {
|
| 70 |
+
"landscape": LANDSCAPE_DIR,
|
| 71 |
+
"anime": ANIME_DIR,
|
| 72 |
+
"face": FACE_DIR,
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
for category, src_dir in sources.items():
|
| 76 |
+
if not os.path.exists(src_dir):
|
| 77 |
+
print(f" [skip] {category} dir not found: {src_dir}")
|
| 78 |
+
continue
|
| 79 |
+
images = glob.glob(os.path.join(src_dir, "*.png"))
|
| 80 |
+
print(f" Copying {len(images)} {category} images...")
|
| 81 |
+
for img_path in images:
|
| 82 |
+
dest = os.path.join(REALESRGAN_GT_DIR, os.path.basename(img_path))
|
| 83 |
+
if not os.path.exists(dest):
|
| 84 |
+
shutil.copy2(img_path, dest)
|
| 85 |
+
total_copied += 1
|
| 86 |
+
|
| 87 |
+
# Also copy root-level face images (original FFHQ)
|
| 88 |
+
root_images = glob.glob(os.path.join(DATASET_BASE, "*.png"))
|
| 89 |
+
print(f" Copying {len(root_images)} root FFHQ face images...")
|
| 90 |
+
for img_path in root_images:
|
| 91 |
+
dest = os.path.join(REALESRGAN_GT_DIR, os.path.basename(img_path))
|
| 92 |
+
if not os.path.exists(dest):
|
| 93 |
+
shutil.copy2(img_path, dest)
|
| 94 |
+
total_copied += 1
|
| 95 |
+
|
| 96 |
+
total = len([f for f in os.listdir(REALESRGAN_GT_DIR) if f.endswith(".png")])
|
| 97 |
+
print(f"[OK] GT dataset ready: {total} images (copied {total_copied} new)")
|
| 98 |
+
return total
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def create_training_config(num_images: int) -> str:
|
| 102 |
+
"""Create a Real-ESRGAN training YAML config."""
|
| 103 |
+
config_dir = os.path.join(REALESRGAN_DIR, "options")
|
| 104 |
+
os.makedirs(config_dir, exist_ok=True)
|
| 105 |
+
config_path = os.path.join(config_dir, "train_realesrgan_custom.yml")
|
| 106 |
+
|
| 107 |
+
# Relative path from Real-ESRGAN dir to GT dataset
|
| 108 |
+
gt_path_rel = os.path.relpath(REALESRGAN_GT_DIR, REALESRGAN_DIR).replace("\\", "/")
|
| 109 |
+
|
| 110 |
+
config = {
|
| 111 |
+
"name": "train_RealESRGAN_custom",
|
| 112 |
+
"model_type": "RealESRGANModel",
|
| 113 |
+
"scale": 4,
|
| 114 |
+
"num_gpu": 0,
|
| 115 |
+
"manual_seed": 0,
|
| 116 |
+
"datasets": {
|
| 117 |
+
"train": {
|
| 118 |
+
"name": "CustomMixedDataset",
|
| 119 |
+
"type": "RealESRGANDataset",
|
| 120 |
+
"dataroot_gt": gt_path_rel,
|
| 121 |
+
"meta_info": None,
|
| 122 |
+
"io_backend": {"type": "disk"},
|
| 123 |
+
"gt_size": 256,
|
| 124 |
+
"use_hflip": True,
|
| 125 |
+
"use_rot": False,
|
| 126 |
+
"blur_kernel_size": 21,
|
| 127 |
+
"kernel_list": ["iso", "aniso", "generalized_iso", "generalized_aniso",
|
| 128 |
+
"plateau_iso", "plateau_aniso"],
|
| 129 |
+
"kernel_prob": [0.45, 0.25, 0.12, 0.03, 0.12, 0.03],
|
| 130 |
+
"sinc_prob": 0.1,
|
| 131 |
+
"blur_sigma": [0.2, 3.0],
|
| 132 |
+
"betag_range": [0.5, 4.0],
|
| 133 |
+
"betap_range": [1, 2.0],
|
| 134 |
+
"blur_kernel_size2": 21,
|
| 135 |
+
"kernel_list2": ["iso", "aniso", "generalized_iso", "generalized_aniso",
|
| 136 |
+
"plateau_iso", "plateau_aniso"],
|
| 137 |
+
"kernel_prob2": [0.45, 0.25, 0.12, 0.03, 0.12, 0.03],
|
| 138 |
+
"sinc_prob2": 0.1,
|
| 139 |
+
"blur_sigma2": [0.2, 1.5],
|
| 140 |
+
"betag_range2": [0.5, 4.0],
|
| 141 |
+
"betap_range2": [1, 2.0],
|
| 142 |
+
"final_sinc_prob": 0.8,
|
| 143 |
+
"resize_prob": [0.2, 0.7, 0.1],
|
| 144 |
+
"resize_range": [0.15, 1.5],
|
| 145 |
+
"gaussian_noise_prob": 0.5,
|
| 146 |
+
"noise_range": [1, 30],
|
| 147 |
+
"poisson_scale_range": [0.05, 3.0],
|
| 148 |
+
"gray_noise_prob": 0.4,
|
| 149 |
+
"jpeg_range": [30, 95],
|
| 150 |
+
"resize_prob2": [0.3, 0.4, 0.3],
|
| 151 |
+
"resize_range2": [0.3, 1.2],
|
| 152 |
+
"gaussian_noise_prob2": 0.5,
|
| 153 |
+
"noise_range2": [1, 25],
|
| 154 |
+
"poisson_scale_range2": [0.05, 2.5],
|
| 155 |
+
"gray_noise_prob2": 0.4,
|
| 156 |
+
"jpeg_range2": [30, 95],
|
| 157 |
+
"gt_size_h": 256,
|
| 158 |
+
"gt_size_w": 256,
|
| 159 |
+
"batch_size_per_gpu": 4,
|
| 160 |
+
"num_worker_per_gpu": 0,
|
| 161 |
+
"dataset_enlarge_ratio": 1,
|
| 162 |
+
"prefetch_mode": "cpu",
|
| 163 |
+
"num_prefetch_queue": 1,
|
| 164 |
+
}
|
| 165 |
+
},
|
| 166 |
+
"network_g": {
|
| 167 |
+
"type": "RRDBNet",
|
| 168 |
+
"num_in_ch": 3,
|
| 169 |
+
"num_out_ch": 3,
|
| 170 |
+
"num_feat": 64,
|
| 171 |
+
"num_block": 23,
|
| 172 |
+
"num_grow_ch": 32,
|
| 173 |
+
"scale": 4,
|
| 174 |
+
},
|
| 175 |
+
"network_d": {
|
| 176 |
+
"type": "UNetDiscriminatorSN",
|
| 177 |
+
"num_in_ch": 3,
|
| 178 |
+
"num_feat": 64,
|
| 179 |
+
"skip_connection": True,
|
| 180 |
+
},
|
| 181 |
+
"path": {
|
| 182 |
+
"pretrain_network_g": None,
|
| 183 |
+
"param_key_g": "params_ema",
|
| 184 |
+
"strict_load_g": False,
|
| 185 |
+
"resume_state": None,
|
| 186 |
+
},
|
| 187 |
+
"train": {
|
| 188 |
+
"ema_decay": 0.999,
|
| 189 |
+
"optim_g": {
|
| 190 |
+
"type": "Adam",
|
| 191 |
+
"lr": 1.0e-4,
|
| 192 |
+
"weight_decay": 0,
|
| 193 |
+
"betas": [0.9, 0.99],
|
| 194 |
+
},
|
| 195 |
+
"optim_d": {
|
| 196 |
+
"type": "Adam",
|
| 197 |
+
"lr": 1.0e-4,
|
| 198 |
+
"weight_decay": 0,
|
| 199 |
+
"betas": [0.9, 0.99],
|
| 200 |
+
},
|
| 201 |
+
"scheduler": {
|
| 202 |
+
"type": "MultiStepLR",
|
| 203 |
+
"milestones": [400000],
|
| 204 |
+
"gamma": 0.5,
|
| 205 |
+
},
|
| 206 |
+
"total_iter": 500,
|
| 207 |
+
"warmup_iter": -1,
|
| 208 |
+
"pixel_opt": {
|
| 209 |
+
"type": "L1Loss",
|
| 210 |
+
"loss_weight": 1.0,
|
| 211 |
+
"reduction": "mean",
|
| 212 |
+
},
|
| 213 |
+
"perceptual_opt": {
|
| 214 |
+
"type": "PerceptualLoss",
|
| 215 |
+
"layer_weights": {"conv1_2": 0.1, "conv2_2": 0.1, "conv3_4": 1.0,
|
| 216 |
+
"conv4_4": 1.0, "conv5_4": 1.0},
|
| 217 |
+
"vgg_type": "vgg19",
|
| 218 |
+
"use_input_norm": True,
|
| 219 |
+
"range_norm": False,
|
| 220 |
+
"perceptual_weight": 1.0,
|
| 221 |
+
"style_weight": 0.0,
|
| 222 |
+
"criterion": "l1",
|
| 223 |
+
},
|
| 224 |
+
"gan_opt": {
|
| 225 |
+
"type": "GANLoss",
|
| 226 |
+
"gan_type": "vanilla",
|
| 227 |
+
"real_label_val": 1.0,
|
| 228 |
+
"fake_label_val": 0.0,
|
| 229 |
+
"loss_weight": 1.0e-1,
|
| 230 |
+
},
|
| 231 |
+
"net_d_iters": 1,
|
| 232 |
+
"net_d_start_iter": 0,
|
| 233 |
+
},
|
| 234 |
+
"val": {
|
| 235 |
+
"val_freq": 500,
|
| 236 |
+
"save_img": True,
|
| 237 |
+
},
|
| 238 |
+
"logger": {
|
| 239 |
+
"print_freq": 1,
|
| 240 |
+
"save_checkpoint_freq": 50,
|
| 241 |
+
"use_tb_logger": False,
|
| 242 |
+
"wandb": {"project": None},
|
| 243 |
+
},
|
| 244 |
+
"dist_params": None,
|
| 245 |
+
"dist": False,
|
| 246 |
+
"find_unused_parameters": False,
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
with open(config_path, "w", encoding="utf-8") as f:
|
| 250 |
+
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
|
| 251 |
+
|
| 252 |
+
print(f"[OK] Config written: {config_path}")
|
| 253 |
+
return config_path
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
def find_latest_state() -> str | None:
|
| 257 |
+
"""Find the latest training state checkpoint."""
|
| 258 |
+
pattern = os.path.join(
|
| 259 |
+
REALESRGAN_DIR, "experiments", "*train_RealESRGAN_custom*",
|
| 260 |
+
"training_states", "*.state"
|
| 261 |
+
)
|
| 262 |
+
states = glob.glob(pattern)
|
| 263 |
+
if not states:
|
| 264 |
+
return None
|
| 265 |
+
latest = max(states, key=lambda p: int(os.path.basename(p).replace(".state", "")))
|
| 266 |
+
return latest
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
def main():
|
| 270 |
+
print("=" * 55)
|
| 271 |
+
print(" Real-ESRGAN Custom Training Runner")
|
| 272 |
+
print(" Target: Landscape + Anime + Face enhancement")
|
| 273 |
+
print("=" * 55)
|
| 274 |
+
|
| 275 |
+
# 1. Clone repo
|
| 276 |
+
if not clone_realesrgan():
|
| 277 |
+
sys.exit(1)
|
| 278 |
+
|
| 279 |
+
# 2. Install deps
|
| 280 |
+
install_realesrgan_deps()
|
| 281 |
+
|
| 282 |
+
# 3. Prepare combined GT dataset
|
| 283 |
+
print("\n[Step 3] Preparing GT dataset...")
|
| 284 |
+
num_images = prepare_gt_dataset()
|
| 285 |
+
if num_images == 0:
|
| 286 |
+
print("[FAIL] No images found in dataset directories. Run crawl_datasets.py first.")
|
| 287 |
+
sys.exit(1)
|
| 288 |
+
|
| 289 |
+
# 4. Create training config
|
| 290 |
+
print("\n[Step 4] Creating training config...")
|
| 291 |
+
config_path = create_training_config(num_images)
|
| 292 |
+
|
| 293 |
+
# 5. Check for resume state
|
| 294 |
+
latest_state = find_latest_state()
|
| 295 |
+
if latest_state:
|
| 296 |
+
iter_num = int(os.path.basename(latest_state).replace(".state", ""))
|
| 297 |
+
print(f"\n>>> RESUME MODE: Found checkpoint at iteration {iter_num}")
|
| 298 |
+
# Patch config to resume
|
| 299 |
+
with open(config_path, "r", encoding="utf-8") as f:
|
| 300 |
+
cfg = yaml.safe_load(f)
|
| 301 |
+
cfg["path"]["resume_state"] = latest_state
|
| 302 |
+
cfg["path"]["pretrain_network_g"] = None
|
| 303 |
+
with open(config_path, "w", encoding="utf-8") as f:
|
| 304 |
+
yaml.dump(cfg, f, default_flow_style=False, sort_keys=False)
|
| 305 |
+
else:
|
| 306 |
+
print("\n>>> FRESH START: No previous checkpoint. Starting from scratch.")
|
| 307 |
+
|
| 308 |
+
# 6. Run training
|
| 309 |
+
train_script = os.path.join("basicsr", "train.py")
|
| 310 |
+
cmd = [
|
| 311 |
+
sys.executable,
|
| 312 |
+
train_script,
|
| 313 |
+
"-opt", os.path.join("options", "train_realesrgan_custom.yml"),
|
| 314 |
+
"--launcher", "none",
|
| 315 |
+
]
|
| 316 |
+
|
| 317 |
+
env = os.environ.copy()
|
| 318 |
+
env["PYTHONPATH"] = os.path.pathsep.join([REALESRGAN_DIR, env.get("PYTHONPATH", "")])
|
| 319 |
+
|
| 320 |
+
print(f"\nStarting Real-ESRGAN training...")
|
| 321 |
+
print(f"Working directory: {REALESRGAN_DIR}")
|
| 322 |
+
print(f"Command: {' '.join(cmd)}")
|
| 323 |
+
print("-" * 55)
|
| 324 |
+
|
| 325 |
+
try:
|
| 326 |
+
subprocess.run(cmd, cwd=REALESRGAN_DIR, env=env, check=True)
|
| 327 |
+
print("-" * 55)
|
| 328 |
+
print("[OK] Real-ESRGAN training completed!")
|
| 329 |
+
except subprocess.CalledProcessError as e:
|
| 330 |
+
print(f"[FAIL] Training failed with exit code: {e.returncode}")
|
| 331 |
+
sys.exit(e.returncode)
|
| 332 |
+
|
| 333 |
+
|
| 334 |
+
if __name__ == "__main__":
|
| 335 |
+
main()
|
train_sharpening_model.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import argparse
|
| 3 |
+
import glob
|
| 4 |
+
import time
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn as nn
|
| 8 |
+
import torch.optim as optim
|
| 9 |
+
from torch.utils.data import DataLoader, Dataset
|
| 10 |
+
from torchvision import transforms
|
| 11 |
+
from PIL import Image
|
| 12 |
+
|
| 13 |
+
class ImageDataset(Dataset):
|
| 14 |
+
def __init__(self, root_dir, transform=None):
|
| 15 |
+
self.paths = sorted(glob.glob(os.path.join(root_dir, "*.png")))
|
| 16 |
+
self.transform = transform
|
| 17 |
+
|
| 18 |
+
def __len__(self):
|
| 19 |
+
return len(self.paths)
|
| 20 |
+
|
| 21 |
+
def __getitem__(self, idx):
|
| 22 |
+
img = Image.open(self.paths[idx]).convert("RGB")
|
| 23 |
+
if self.transform:
|
| 24 |
+
img = self.transform(img)
|
| 25 |
+
return img
|
| 26 |
+
|
| 27 |
+
class SimpleUNet(nn.Module):
|
| 28 |
+
"""Lightweight UNet‑style model for image sharpening (placeholder)."""
|
| 29 |
+
def __init__(self):
|
| 30 |
+
super().__init__()
|
| 31 |
+
self.encoder = nn.Sequential(
|
| 32 |
+
nn.Conv2d(3, 16, kernel_size=3, padding=1), nn.ReLU(inplace=True),
|
| 33 |
+
nn.Conv2d(16, 32, kernel_size=3, padding=1), nn.ReLU(inplace=True)
|
| 34 |
+
)
|
| 35 |
+
self.decoder = nn.Sequential(
|
| 36 |
+
nn.Conv2d(32, 16, kernel_size=3, padding=1), nn.ReLU(inplace=True),
|
| 37 |
+
nn.Conv2d(16, 3, kernel_size=3, padding=1)
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
def forward(self, x):
|
| 41 |
+
x = self.encoder(x)
|
| 42 |
+
x = self.decoder(x)
|
| 43 |
+
return x
|
| 44 |
+
|
| 45 |
+
def train(dataset_dir, checkpoint_dir, epochs, batch_size, lr, use_gpu):
|
| 46 |
+
device = torch.device("cuda" if torch.cuda.is_available() and use_gpu else "cpu")
|
| 47 |
+
transform = transforms.Compose([
|
| 48 |
+
transforms.Resize((512, 512)),
|
| 49 |
+
transforms.ToTensor()
|
| 50 |
+
])
|
| 51 |
+
dataset = ImageDataset(dataset_dir, transform=transform)
|
| 52 |
+
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True, num_workers=2)
|
| 53 |
+
|
| 54 |
+
model = SimpleUNet().to(device)
|
| 55 |
+
criterion = nn.L1Loss()
|
| 56 |
+
optimizer = optim.Adam(model.parameters(), lr=lr)
|
| 57 |
+
|
| 58 |
+
print(f"Start training on {len(dataset)} images (device: {device})")
|
| 59 |
+
for epoch in range(1, epochs + 1):
|
| 60 |
+
epoch_loss = 0.0
|
| 61 |
+
for batch in dataloader:
|
| 62 |
+
batch = batch.to(device)
|
| 63 |
+
optimizer.zero_grad()
|
| 64 |
+
outputs = model(batch)
|
| 65 |
+
loss = criterion(outputs, batch)
|
| 66 |
+
loss.backward()
|
| 67 |
+
optimizer.step()
|
| 68 |
+
epoch_loss += loss.item() * batch.size(0)
|
| 69 |
+
epoch_loss /= len(dataset)
|
| 70 |
+
print(f"Epoch {epoch}/{epochs} - Loss: {epoch_loss:.4f}")
|
| 71 |
+
ckpt_path = os.path.join(checkpoint_dir, f"epoch_{epoch}.pth")
|
| 72 |
+
torch.save(model.state_dict(), ckpt_path)
|
| 73 |
+
final_path = os.path.join(checkpoint_dir, "sharpening_model_final.pth")
|
| 74 |
+
torch.save(model.state_dict(), final_path)
|
| 75 |
+
print("Training completed. Model saved to", final_path)
|
| 76 |
+
|
| 77 |
+
def main():
|
| 78 |
+
parser = argparse.ArgumentParser(description="Train sharpening model on 512x512 PNG images.")
|
| 79 |
+
parser.add_argument("--dataset_dir", type=str, required=True, help="Folder containing PNG images.")
|
| 80 |
+
parser.add_argument("--checkpoint_dir", type=str, default="checkpoints", help="Directory to store checkpoints.")
|
| 81 |
+
parser.add_argument("--epochs", type=int, default=10, help="Number of epochs.")
|
| 82 |
+
parser.add_argument("--batch_size", type=int, default=8, help="Batch size.")
|
| 83 |
+
parser.add_argument("--lr", type=float, default=1e-4, help="Learning rate.")
|
| 84 |
+
parser.add_argument("--gpu", action="store_true", help="Use GPU if available.")
|
| 85 |
+
args = parser.parse_args()
|
| 86 |
+
os.makedirs(args.checkpoint_dir, exist_ok=True)
|
| 87 |
+
train(args.dataset_dir, args.checkpoint_dir, args.epochs, args.batch_size, args.lr, args.gpu)
|
| 88 |
+
|
| 89 |
+
if __name__ == "__main__":
|
| 90 |
+
main()
|