AI Super-Resolution для видео — апскейл видеоконтента
Апскейл видео сложнее апскейла изображений: temporal consistency — соседние кадры должны выглядеть согласованно, иначе результат мерцает. Просто применить Real-ESRGAN к каждому кадру — неправильно: шум на однородных поверхностях будет меняться от кадра к кадру.
Real-BasicVSR и BasicVSR++ — основные модели
import torch
import numpy as np
import cv2
from basicsr.archs.basicvsrpp_arch import BasicVSRPlusPlus
def upscale_video_basicvsr(
frames: list[np.ndarray], # список кадров (H, W, 3) BGR
scale: int = 4,
num_feat: int = 64,
num_propagation_blocks: int = 7,
cpu_cache_length: int = 100 # кадры в памяти GPU одновременно
) -> list[np.ndarray]:
"""
BasicVSR++ использует bidirectional propagation:
информацию из прошлых И будущих кадров.
cpu_cache_length: при длинных видео часть кадров выгружаем на CPU.
"""
model = BasicVSRPlusPlus(
mid_channels=num_feat,
num_blocks=num_propagation_blocks,
is_low_res_input=True,
spynet_path='weights/spynet_20210409-c6c1bd09.pth'
)
state_dict = torch.load(
f'weights/BasicVSR++_reds4_vimeo90k.pth'
)['params']
model.load_state_dict(state_dict, strict=True)
model.eval().cuda()
# Нормализация и конвертация BGR→RGB
tensor_frames = []
for frame in frames:
f_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
t = torch.from_numpy(f_rgb).float() / 255.0
t = t.permute(2, 0, 1).unsqueeze(0) # (1, C, H, W)
tensor_frames.append(t)
# Batch всех кадров → (1, T, C, H, W)
video_tensor = torch.stack(
[f.squeeze(0) for f in tensor_frames], dim=0
).unsqueeze(0).cuda()
with torch.no_grad(), torch.cuda.amp.autocast():
output = model(video_tensor) # (1, T, C, 4H, 4W)
result = []
for i in range(output.shape[1]):
frame_t = output[0, i].float().cpu()
frame_np = (frame_t.permute(1,2,0).numpy() * 255).clip(0,255)
result.append(
cv2.cvtColor(frame_np.astype(np.uint8), cv2.COLOR_RGB2BGR)
)
return result
Чанкованная обработка длинных видео
Целый фильм не влезет в VRAM при BasicVSR++. Обработка чанками с overlap:
def upscale_long_video(
input_path: str,
output_path: str,
chunk_frames: int = 50, # кадров в чанке
overlap_frames: int = 5, # перекрытие для бесшовного сшивания
scale: int = 4
) -> None:
cap = cv2.VideoCapture(input_path)
fps = cap.get(cv2.CAP_PROP_FPS)
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
writer = cv2.VideoWriter(
output_path,
cv2.VideoWriter_fourcc(*'mp4v'),
fps, (w * scale, h * scale)
)
all_frames = []
while True:
ret, frame = cap.read()
if not ret:
break
all_frames.append(frame)
cap.release()
n = len(all_frames)
written_up_to = 0
for start in range(0, n, chunk_frames - overlap_frames):
end = min(start + chunk_frames, n)
chunk = all_frames[start:end]
upscaled_chunk = upscale_video_basicvsr(chunk, scale=scale)
# Пишем только не-overlap часть (кроме первого чанка)
write_start = overlap_frames if start > 0 else 0
for frame in upscaled_chunk[write_start:]:
writer.write(frame)
if end == n:
break
writer.release()
Сравнение моделей
| Модель | PSNR Vid4 4x | Temporal consistency | Скорость 1080p→4K | VRAM |
|---|---|---|---|---|
| Real-ESRGAN (per-frame) | 27.4 | Низкая (мерцание) | ~8fps RTX3080 | 6GB |
| BasicVSR | 31.4 | Хорошая | ~2fps RTX3080 | 12GB |
| BasicVSR++ | 32.4 | Отличная | ~1.5fps RTX3080 | 16GB |
| RVRT | 32.8 | Отличная | ~0.8fps RTX3080 | 20GB |
| Real-BasicVSR | 31.0 | Хорошая | ~3fps RTX3080 | 10GB |
Практические кейсы и ограничения
Кейс из практики: оцифровка архивного видео 720p → 4K для OTT-платформы. 180 минут видео, BasicVSR++ с chunk_frames=30. Время обработки на двух RTX A6000: 14 часов. VMAF score вырос с 72 до 89 — платформа приняла качество без ручного ретуширования.
Ограничения: компрессионные артефакты MPEG/H.264 усиливаются SR-моделью. Блочность при высоком QP (>28) → artефакты на 4K выходе. Предобработка через FF_MPEG deblock filter обязательна.
Сроки
| Задача | Срок |
|---|---|
| Сервис апскейла видео (Real-BasicVSR) | 2–3 недели |
| Pipeline с препроцессингом + BasicVSR++ | 4–6 недель |
| Fine-tuning под специфический тип контента | 8–14 недель |







