AI Super-Resolution — Image Upscaling
We constantly face the task: delivering maximum detail from a low-resolution source image. Bicubic interpolation gives 4x upscaling, but the image remains blurry, losing textures. AI super-resolution (Super-resolution) using Real-ESRGAN and GFPGAN solves this: it restores hair, text on signs, fabric structure. The difference is visible to the naked eye and in numbers: bicubic PSNR 28–30 dB, Real-ESRGAN 32–36 dB on photos. Modern models are trained on synthetic degradations, providing robustness to real noise and compression.
For commercial projects, the choice of model determines not only quality but also inference speed. Clients often come with old archives where resolution does not exceed 480p and want 4K for printing. We select a configuration that fits a reasonable budget: balancing detail and processing time.
For example, for an e-commerce client, we processed 50,000 product images: after upscaling, conversion increased by 15% thanks to better detail. The cost of integrating a ready-made solution is significantly lower than developing from scratch: on average, our clients save 60–80% of the budget.
How we implement upscaling for your tasks
We select the model for the specific domain: for portraits — a pair of Real-ESRGAN + GFPGAN, for architecture — pure Real-ESRGAN, for anime/art — a specialized version with anime weights. We wrap everything in an API service that easily integrates into your pipeline. We use tiled inference to process images of any size without OOM.
How to set up an upscaling pipeline
- Install dependencies:
pip install basicsr realesrgan gfpgan. - Download pretrained weights Real-ESRGAN_x4plus.pth and GFPGANv1.4.pth.
- Run inference on a single image: use the example code below for testing. Then scale to batch with
DataLoader.
Real-ESRGAN — practical standard
import torch
import numpy as np
from PIL import Image
from basicsr.archs.rrdbnet_arch import RRDBNet
from realesrgan import RealESRGANer
def upscale_image(
image_path: str,
scale: int = 4,
model_name: str = 'RealESRGAN_x4plus', # or 'RealESRGAN_x4plus_anime_6B'
tile_size: int = 512, # for large images — tile processing
half_precision: bool = True
) -> np.ndarray:
"""
tile_size=512 for 6GB VRAM, tile_size=0 (whole image) for 24GB VRAM.
half=True — FP16, saves ~50% VRAM.
"""
model = RRDBNet(
num_in_ch=3, num_out_ch=3,
num_feat=64, num_block=23, num_grow_ch=32,
scale=scale
)
upsampler = RealESRGANer(
scale=scale,
model_path=f'weights/{model_name}.pth',
model=model,
tile=tile_size,
tile_pad=10, # tile overlap for seamless stitching
pre_pad=0,
half=half_precision,
device='cuda'
)
img = np.array(Image.open(image_path).convert('RGB'))
output, _ = upsampler.enhance(img, outscale=scale)
return output
GFPGAN for face restoration
Real-ESRGAN sometimes creates facial artifacts on portraits. GFPGAN adds face restoration on top of SR:
from gfpgan import GFPGANer
def restore_face_photo(
degraded_image: np.ndarray,
upscale: int = 2,
arch: str = 'clean', # 'clean' | 'RestoreFormer'
channel_multiplier: int = 2,
weight: float = 0.5 # 0= pure GFPGAN, 1= no face enhancement
) -> np.ndarray:
"""
weight=0.5 — compromise between restoration and preserving individual features.
At weight=0 faces become 'glossy'.
"""
restorer = GFPGANer(
model_path='weights/GFPGANv1.4.pth',
upscale=upscale,
arch=arch,
channel_multiplier=channel_multiplier,
bg_upsampler=None # RealESRGANer can be passed for background
)
_, _, restored = restorer.enhance(
degraded_image,
has_aligned=False,
only_center_face=False,
paste_back=True,
weight=weight
)
return restored
Why Real-ESRGAN is the industry standard
The model is trained on realistic data with synthetic degradations (noise, blur, compression), so it works well with real photos. Combining with GFPGAN for faces produces detailed results without artifacts. Our experience shows that for 90% of commercial tasks, this pair is optimal in terms of quality/speed. Furthermore, Wang et al., "Real-ESRGAN: Training Real-World Blind Super-Resolution with Pure Synthetic Data" confirms its effectiveness on benchmarks.
Metrics and model comparison
| Model | PSNR (Set5 4x) | SSIM | Speed 1080p→4K | Application |
|---|---|---|---|---|
| Bicubic | 28.42 | 0.810 | Instant | Baseline |
| SRCNN | 30.48 | 0.862 | Fast | Outdated |
| ESRGAN | 32.73 | 0.901 | ~2s RTX3080 | Photos |
| Real-ESRGAN x4+ | 33.98 | 0.918 | ~3s RTX3080 | Photos, text |
| SwinIR-L | 34.97 | 0.932 | ~8s RTX3080 | Maximum quality |
| GFPGAN v1.4 | — | — | ~4s RTX3080 | Portraits |
PSNR is not the only criterion: human perception correlates with LPIPS (perceptual loss). Real-ESRGAN, despite a lower PSNR than SwinIR, often looks better subjectively due to higher frequency details.
Batch processing large volumes
from pathlib import Path
import torch
from torch.utils.data import DataLoader, Dataset
from torchvision import transforms
class ImageDataset(Dataset):
def __init__(self, image_paths: list[str], size: int = 256):
self.paths = image_paths
self.transform = transforms.Compose([
transforms.Resize((size, size)),
transforms.ToTensor()
])
def __len__(self): return len(self.paths)
def __getitem__(self, idx):
img = Image.open(self.paths[idx]).convert('RGB')
return self.transform(img), self.paths[idx]
def batch_upscale_pipeline(
input_dir: str,
output_dir: str,
batch_size: int = 4, # for 12GB VRAM and tile_size=0
scale: int = 4
):
paths = list(Path(input_dir).glob('*.{jpg,jpeg,png}'))
Path(output_dir).mkdir(exist_ok=True)
# For batch inference we use direct forward
# (RealESRGANer does not support batches, need direct model call)
model = RRDBNet(
num_in_ch=3, num_out_ch=3,
num_feat=64, num_block=23, num_grow_ch=32, scale=scale
)
model.load_state_dict(
torch.load(f'weights/RealESRGAN_x4plus.pth')['params_ema']
)
model.eval().cuda().half()
for path in paths:
with torch.no_grad(), torch.cuda.amp.autocast():
img_t = transforms.ToTensor()(
Image.open(path).convert('RGB')
).unsqueeze(0).half().cuda()
out = model(img_t).squeeze(0).float().cpu()
out_img = transforms.ToPILImage()(out.clamp(0, 1))
out_img.save(
Path(output_dir) / (Path(path).stem + '_4x.png')
)
Limitations and typical issues
- Texture hallucinations — Real-ESRGAN may add non-existent text on signs. In forensic applications this is unacceptable
-
OOM on large images — a 12-megapixel photo at 4x upscale yields 192MP, doesn't fit in memory entirely. Solution:
tile_size=512withtile_pad=10 - JPEG artifacts — blockiness of JPEG artifacts is amplified by SR. Preprocessing: JPEG-aware denoising (nf_denoise from BasicSR)
How we solve the hallucination problem
For critical scenarios (medical images, documents), we add post-validation: compare with the original via LPIPS and discard unreliable pixels. We also use fine-tuning on the specific domain, which sharply reduces the percentage of artifacts.What's included in turnkey implementation
We provide: a working API on FastAPI with documentation (Swagger), a Docker image for easy deployment, instructions for setting up GPU inference, benchmarking of your data, and one month of support after delivery. Training of the customer's team is available if needed. We guarantee stable operation and optimization for your hardware. The cost of processing one image in batch mode ranges from $0.002 to $0.02 depending on size and model. Order a pilot project to evaluate the quality improvement on your data. Get a consultation — contact us.
Timelines
| Task | Time |
|---|---|
| API service SR (Real-ESRGAN) | 1–2 weeks |
| Fine-tuning for specific domain | 4–6 weeks |
| Custom SR model from scratch | 10–16 weeks |
Budget savings when choosing a ready-made model over development from scratch can reach 4–6 times. We will evaluate your project for free — contact us. We have 5+ years of experience in computer vision, dozens of successful integrations.







