Зачем нам свой улучшатор?

Мы живем в эпоху доступности генеративных нейросетей, однако большинство качественных инструментов улучшения качества фото и видео остаются либо платными SaaS‑сервисами, либо сложными академическими проектами. Когда речь заходит об обработке больших объемов личных данных — будь то оцифровка семейных видеокассет или архива институтских пленок — возникает закономерный вопрос доверия и стоимости.

Мы поставили перед собой цель: автоматизировать процесс реставрации старых материалов, сохранив их аутентичность. Готовые решения зачастую применяют избыточную резкость или некорректно обрабатывают специфические дефекты пленки (например, царапины или цветовой сдвиг). Кроме того, изучение механики обучения и инференса подобных моделей требует полного доступа к весам и архитектуре, чего закрытые API предоставить не могут.

Формальная постановка задачи

В рамках данной статьи поставим задачу supervised image‑to‑image translation (обучение с учителем для преобразования изображения в изображение) для восстановления ретрофотографий и изображений очень низкого качества.

Формально задача выглядит так:

Вход: изображение низкого разрешения XLR ∈RH×W×3 с наложенным синтетическим шумом (гауссов шум, «соль и перец», квантование цвета и др.), имитирующим дефекты старой плёнки или цифрового сенсора.

Выход: изображение высокого разрешения YHR ∈R2H×2W×3, максимально близкое к эталону.

Функция потерь: комбинированная — L1 (MAE) для попиксельной точности + perceptual loss (на признаках предобученной VGG) для сохранения визуального качества.

Критерий качества: PSNR (пиковое отношение сигнала к шуму) и SSIM (индекс структурного сходства) на отложенной валидационной выборке.

В этом материале мы подробно разберем путь создания локального сервераулучшатора. Это история о том, как превратить теоретическое желание разобраться в устройстве GAN‑сетей и диффузионных моделей в работающий прототип. Мы пройдем по всем граблям: от подбора комплектующих и настройки драйверов до схваток с ошибкой CUDA out of memory и поиска оптимальной конфигурации тайлов для обработки 4K‑видео на одной видеокарте.

Железо

Для нашего пути у нас имеется ноутбук MSI Katana GF66 11UD, его характеристики по официальной спецификации.

Параметр

Значение

Комментарий для ML‑задач

Видеокарта

NVIDIA GeForce

RTX 3050 Ti

Laptop GPU (4

ГБ GDDR6)

Главный ограничивающий фактор. Памяти критически мало для обучения на большом объеме. Обучение будет длиться дольше, чем на более мощных видеокартах.

Графический чип

GA106 / Ampere

Архитектура современная, поддерживает Tensor Cores для FP16/BF16, что спасает от OOM ценой небольшой потери точности.

Потребляемая мощность GPU

(TGP)

60 Вт (Base), до

75 Вт с учетом

Dynamic Boost 2.0

Ограниченный тепловой пакет. В закрытом корпусе под длительной нагрузкой карта быстро упрется в лимит мощности и сбросит частоты.

Процессор

Intel Core i7-

11800H (8 ядер /

16 потоков)

Отличный CPU для декодирования видео FFmpeg и препроцессинга кадров. Не станет бутылочным горлышком.

Оперативная память

32 ГБ DDR43200 (заняты оба слота)

Идеальный объем для локального инференса. Позволяет кэшировать батчи кадров перед подачей на GPU.

Но железо — железом, а дьявол кроется в деталях, и это софт. С ним пришлось разобраться подробнее, так как на софт, а соответственно и на эффективность обучения, повлияло то, что на нашем ноутбуке установлена операционная система Windows и нам очень повезло, что видеокарта — от NVIDIA.

Для видеокарт AMD имеется свой аналог CUDA под названием ROCM, но установка софта и библиотек схожа.

Установка необходимого софта и настройка проекта

На Windows «из коробки» ничего работать не будет. Нам нужно подружить Python, драйверы видеокарты и системные библиотеки Microsoft.

1. Драйвер NVIDIA

Установите свежий Game Ready или Studio драйвер с официального сайта NVIDIA. Почему это важно первым шагом: драйвер даёт системе доступ к GPU и базовые библиотеки CUDA Runtime. Если ставить CUDA Toolkit до драйвера или на старую версию драйвера, возможны ошибки инициализации контекста CUDA, особенно при запуске PyTorch.

Важно: начиная с CUDA 13.0 драйвер больше не входит в установщик CUDA Toolkit на Windows. Его нужно ставить отдельно с сайта NVIDIA до установки Toolkit.

2. Выбор версии CUDA Toolkit

Где брать

Скачивайте CUDA Toolkit с официального сайта NVIDIA. Важно не использовать сторонние зеркала: они могут содержать устаревшие или модифицированные билды, которые ломают совместимость с cuDNN и PyTorch.

Как выбрать версию CUDA

Для нейросетей на Windows ориентируются не на «самую новую», а на версию, под которую собраны готовые бинарные сборки PyTorch. Чтобы определить подходящую версию для вашей видеокарты:

1. Проверьте архитектуру GPU. Узнайте Compute Capability вашей карты на официальной таблице NVIDIA. RTX 3050 Ti (Ampere) имеет Compute Capability 8.6.

2. Сверьтесь с матрицей совместимости PyTorch. На странице установки PyTorch выберите вашу ОС, пакетный менеджер и версию CUDA — сайт покажет, какие сборки доступны.

3. Выбирайте максимальную версию CUDA, для которой есть готовые wheelпакеты PyTorch. На момент написания это CUDA 13.0.

Если вы поставите CUDA 12.x или 11.x, вам придётся либо:

собирать PyTorch из исходников (что на Windows — отдельная боль с зависимостями и компилятором),

или использовать старые версии PyTorch, где нет поддержки новых функций и оптимизаций.

Почему именно CUDA 13.0:

Официальная поддержка PyTorch. В репозитории download.pytorch.org есть готовые CUDA‑билды именно под эту версию. Это избавляет от необходимости компилировать PyTorch вручную.

Совместимость с RTX 3050 Ti. Архитектура Ampere (RTX 30xx) полностью поддерживается в CUDA 13, включая актуальные оптимизации для матричных операций (Tensor Cores).

Баланс стабильности и новых возможностей. CUDA 13 содержит улучшения для асинхронных операций, управления памятью и профилирования — это полезно при обучении.

Поддержка cuDNN. Версии cuDNN, идущие в комплекте с CUDA 13 или доступные отдельно, совместимы с текущими версиями PyTorch и TorchVision.

Нюанс: не обязательно ставить все компоненты CUDA. Для запуска PyTorch достаточно базовых библиотек. Если вы не планируете писать и компилировать свои CUDA‑ядра, можно снять галочки с «CUDA Samples», «CUDA Documentation» и «Visual Studio Integration» — это сэкономит место и ускорит установку.

Пошаговый процесс установки CUDA Toolkit на Windows

1. Запустите установщик и выберите режим «Custom» (не «Express»), чтобы контролировать, что именно ставится.

2. В списке компонентов оставьте включёнными:

CUDA Runtime (обязательно);

Developer Tools (если планируете писать CUDA‑ядра); cuDNN (если он идёт в составе установщика — иначе скачайте отдельно).

Примечание: начиная с CUDA 13.0 пункт «Driver Components» в установщике отсутствует — драйвер ставится отдельно (см. шаг 1).

3. Путь установки лучше оставить по умолчанию (C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.0). Изменение пути усложняет настройку переменных окружения и поиск библиотек.

4. После установки проверьте, что переменные окружения настроены:

Если nvcc не найден, добавьте в переменные окружения PATH:

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.0\bin

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.0\libnvvp (опционально, для профилировщика).

Важно:

Не ставите несколько версий CUDA параллельно без чёткого понимания, как управлять переменными окружения. Конфликты путей — одна из частых причин ошибок «CUDA not found» или «Mismatch between runtime and driver».

Скачивайте CUDA Toolkit с официального сайта NVIDIA. Важно не использовать сторонние зеркала: они могут содержать устаревшие или модифицированные билды, которые ломают совместимость с cuDNN и PyTorch.

Не смешивайте CUDA Toolkit, cuDNN и версии PyTorch «на глаз». Всегда сверяйтесь с официальной матрицей совместимости на сайте PyTorch. Именно поэтому мы фиксируем CUDA 13.0, чтобы гарантировать, что будут скачаны именно те wheel‑пакеты, которые протестированы вместе.

3. Microsoft Visual C++ Redistributable

Самая частая причина ошибок вида ImportError: DLL load failed — отсутствие актуальных библиотек VC++.

Скачайте и установите актуальную версию x64 с официального сайта Microsoft. Без этого OpenCV, Torch и многие другие пакеты просто откажутся запускаться: им нужны системные DLL от Microsoft CRT.

Развёртывание окружения через uv

Классический pip install ‑r requirements.txt на Windows превращается в пытку из‑за долгого поиска совместимых wheel‑файлов и частых ошибок компиляции зависимостей. Мы используем uv — менеджер пакетов на Rust: он работает в разы быстрее и гарантирует детерминизм версий.

Наш файл конфигурации (pyproject.toml) решает две критические задачи пайплайна:

1. Блокировка NumPy (<2.0). Библиотеки вроде scikit‑image и старые сборки opencvpython ещё не адаптировались под ломающие изменения NumPy 2.0. В pyproject.toml это фиксируется как зависимость numpy<2.0.

2. Кастомный индекс PyTorch. Официальный репозиторий PyPI содержит CPU‑only билды. Чтобы получить поддержку RTX 3050 Ti, мы принудительно указываем сервер PyTorch с поддержкой CUDA 13.0:

Установка uv и синхронизация проекта

Если uv ещё не установлен:

Затем синхронизируем проект:

Эта команда:

создаст изолированную виртуальную среду .venv;

скачает правильные бинарные сборки PyTorch именно под вашу ОС и CUDA 13.0; ускорит процесс благодаря агрессивному кэшированию бинарников; зафиксирует версии зависимостей, чтобы окружение было воспроизводимым.

Подготовка датасета: пары LR/HR и аугментация шумаСтруктура данных и разделение на train / valid / test

Датасет построен на классе SRDataset (app/models/dataset.py) — кастомной реализации torch.utils.data.Dataset. На вход подаются два каталога:

lr_dir — изображения низкого разрешения (low‑res, LR); hr_dir — эталонные изображения высокого разрешения (high‑res, HR).

Класс загружает файлы из обоих каталогов, сортирует их и отдаёт пары (lr_image, hr_image) по индексу. Параметр cnt_im_start ограничивает число загружаемых изображений (берутся первые cnt_im_start файлов), а необязательный cnt_im_end позволяет дополнительно взять последние cnt_im_end файлов — это удобно для совмещения обучающей и валидационной частей в одном датасете.

Разделение данных в проекте устроено следующим образом:

Набор

Каталоги

Объём

Назначение

Train (обучающий)

lr_dir / hr_dir

12 000 объектов

Основное обучение модели. Зашумление включено.

Valid

(валидационный)

lv_dir / hv_dir

200 объектов

Оценка качества после каждой эпохи. Зашумление начинается с пустого списка и расширяется по мере роста качества модели.

Test (тестовый)

test_lr / test_hr

отдельный каталог

Финальная оценка на данных, не использованных ни в обучении, ни в валидации.

Общее количество собранных изображений в папке hr_dir — порядка 24 000. Параметры cnt_im_start и cnt_im_end мы добавили, чтобы регулировать объём данных из собранного материала.

class SRDataset(Dataset):
def __init__(self, lr_dir, hr_dir, cnt_im_start, cnt_im_end=None,
transform=None, noise_augmenter=None):
llr_dir = sorted(os.listdir(lr_dir))
lhr_dir = sorted(os.listdir(hr_dir))
if cnt_im_end is None:
self.lr_paths = llr_dir[:cnt_im_start]
self.hr_paths = lhr_dir[:cnt_im_start]
else:
self.lr_paths = llr_dir[:cnt_im_start] + llr_dir[-cnt_im_end:]
self.hr_paths = lhr_dir[:cnt_im_start] + lhr_dir[-cnt_im_end:]
self.transform = transform
self.noise_augmenter = noise_augmenter

Upscaler учится на синтетическом шуме

LR‑изображения проходят через NoiseAugmenter (app/noise/_init_.py), который накладывает один или несколько типов шума:

1. Гауссов шум (Gaussian Noise): Это самый распространенный тип цифрового шума. Он равномерно распределяется по всему изображению и имитирует помехи сенсора камеры или ошибки квантования при сжатии JPEG с низким качеством.

Изображение до искажения
Изображение до искажения
Изображение после искажения
Изображение после искажения
Изображение до искажения
Изображение до искажения
Изображение после искажения
Изображение после искажения

2. Соль и перец (Salt & Pepper): Этот шум проявляется в виде случайных белых и черных пикселей. В контексте реставрации он идеально моделирует повреждения пленки: царапины, пыль, битые пиксели матрицы старой цифровой камеры.

Изображение до искажения
Изображение до искажения
Изображение после искажения
Изображение после искажения
Изображение до искажения
Изображение до искажения
Изображение после искажения
Изображение после искажения

3. Квантование цвета (Color Quantization): Эффект постеризации. Модель учится восстанавливать плавные градиенты там, где они были потеряны из‑за низкого количества цветов. Этот шум особенно важен для восстановления неба и однотонных поверхностей.

Изображение до искажения
Изображение до искажения
Изображение после искажения
Изображение после искажения
Изображение до искажения
Изображение до искажения
Изображение после искажения
Изображение после искажения

4. Цветной соль и перец (Color Salt & Pepper): Отличие от обычного Salt & Pepper заключается в том, что белые точки заменяются не только черными, но и цветными артефактами. Это позволяет моделировать специфические дефекты сканирования пленок, когда свет проходит через пылинки или загрязнения эмульсии.

Изображение до искажения
Изображение до искажения
Изображение после искажения
Изображение после искажения
Изображение до искажения
Изображение до искажения
Изображение после искажения
Изображение после искажения

Объединив все вышеперечисленные шумы и искажения, получились следующие объекты для дальнейшего обучения:

Изображение до искажения
Изображение до искажения
Изображение после искажения
Изображение после искажения
Изображение до искажения
Изображение до искажения
 Изображение после искажения
Изображение после искажения

Почему это важно: Стандартный датасет DIV2K обучает модель исправлять синтетический JPEG‑артефакт. Наш кастомный датасет с этими шумами позволил нейросети научиться работать с реальными дефектами.

Все функции реализованы в app/noise/noises.py и работают с numpy.ndarray. Параметр prob управляет интенсивностью наложения шума: например, для salt_paper это плотность зашумления (доля пикселей, подверженных искажению), для gaus — размер ядра размытия, для pixelated — коэффициент масштабирования (значение 12 означает уменьшение в 12 раз с последующим увеличением обратно, что создаёт эффект пикселизации). По умолчанию мы выставили prob = 12.

Путь боли

Падения cuDNN при свертках высокого разрешения

Одна из самых коварных ошибок в глубоком обучении.

CUDNN_STATUS_EXECUTION_FAILED при вызове loss.backward() означает, что драйвер CUDA не смог корректно выполнить ядро свертки или другого математического оператора на видеокарте.

В отличие от ошибки нехватки памяти (out of memory), здесь видеопамять обычно свободна, но сам вычислительный блок (Streaming Multiprocessor) выдает ошибку выполнения ядра. Это происходит по нескольким причинам:

Недетерминированный выбор алгоритма: PyTorch использует флаг torch.backends.cudnn.benchmark = True, чтобы автоматически искать самый быстрый алгоритм свертки для ваших данных. Этот процесс недетерминирован и может выбрать нестабильный алгоритм для конкретной комбинации размера тензоров и архитектуры GPU.

Несоответствие версий драйвера и библиотеки cuDNN: Если версия драйвера NVIDIA не совпадает с версией cuDNN, которая была использована при сборке PyTorch, могут возникать аппаратные конфликты.

Перегрев Memory Junction: Часто забывают мониторить температуру именно чипов видеопамяти (Memory Junction). При нагреве до 95°C–100°C контроллер памяти начинает пропускать такты шины данных, что приводит к ошибке вычислений на уровне железа.

Численная нестабильность: Значение loss уходит в NaN или Inf, и попытка применить градиент к некорректному числу вызывает сбой ядра.

Аппаратная неисправность: Битый кэш L2/L3 на GPU или дефектные чипы памяти.

План лечения, который мы применили:

1. Отключаем бенчмаркинг и включаем детерминизм: Это самое первое действие. Оно лечит ~70% проблем, связанных с падениями по ошибкам cuDNN. По умолчанию PyTorch пытается найти самый быстрый способ посчитать свертку для вашего конкретного входного тензора. Флаг deterministic=True запрещает ему рисковать и заставляет использовать проверенные реализации, которые гарантированно работают стабильно.

2. Полностью очищаем кэш компиляции ядер PyTorch: Очищаем папку

~/.cache/torch/

3. Переустанавливаем драйверы: Делали переустановку утилиты CUDA Toolkit и стабильного драйвера Game Ready, а не Studio.

4. Мониторинг температуры: Ошибка часто возникает из‑за перегрева чипов видеопамяти (Memory Junction), а не всего GPU. Мы для анализа температуры смотрели на показатели из диспетчера задач, но лучше бы использовали стороннюю утилиту для более точного анализа температур.

Важный нюанс: Температура Memory Junction критична! Даже если температура ядра GPU нормальная (~80°C), память может быть уже за 95°C. В этом случае сброс частот неизбежен, и ядро просто не успевает завершить работу.

Классика жанра (torch.AcceleratorError: CUDA error: out of memory) и Memory Leak в цикле обработки изображений

Одна из самых распространенных проблем в глубоком обучении. Она означает ровно то, что написано: видеопамять (VRAM) видеокарты полностью исчерпана.

В отличие от CUDNN_STATUS_EXECUTION_FAILED, здесь проблема всегда связана с нехваткой ресурсов. Но часто причина не так очевидна, как кажется на первый взгляд.

Итак, по какой причине эта память может расходоваться:

1. Параметры модели: Веса нейросети и их градиенты (parameters, gradients). Для больших моделей это десятки гигабайт.

2. Активационные признаки (Activations): Промежуточные результаты вычислений каждого слоя. Это самый «прожорливый» компонент. Чем больше разрешение входного изображения или размер батча, тем выше потребление памяти.

3. Состояние оптимизатора: Библиотека Adam хранит два дополнительных тензора для каждого параметра (v и m). SGD без момента потребляет ноль дополнительной памяти.

4. Фрагментация VRAM: Память может быть физически свободна, но драйвер не сможет выделить один непрерывный блок нужного размера из‑за разбросанных мелких объектов.

5. Кэш PyTorch: Драйвер держит часть освобожденной памяти про запас. Вызов torch.cuda.empty_cache() помогает вернуть её системе, но не гарантирует немедленное освобождение.

Мы использовали следующие пути для решения:

1. Уменьшаем аппетит сети: Для уменьшения аппетитов нужно оптимально подобрать batch_size, в нашем случае мы выставляли batch_size = 6.

2. Чистим тензоры внутри DataLoader: Самая частая причина утечек — это то, что мы не контролируем жизненный цикл данных, поступающих из DataLoader. То есть при цикличном прогоне данных типа for lr_imgs, hr_imgs in DataLoader, PyTorch создает новые тензоры для каждого батча. Если просто проходить по ним без явного удаления ссылок (del), эти тензоры остаются висеть в Python‑пространстве имён до следующего цикла сборки мусора. Для очистки мы использовали вызов в коде команды del lr_imgs, hr_imgs, loss, что заметно снизило потребление памяти.

Важно: одного del недостаточно для немедленного освобождения видеопамяти. Python‑сборщик мусора не всегда запускается мгновенно. Мы рекомендуем дополнительно вызывать gc.collect() после del — это форсирует сборку мусора и освобождает ссылки на тензоры, после чего torch.cuda.empty_cache() сработает эффективнее.

3. Очистка кэша CUDA‑драйвера: После каждой итерации выполняем torch.cuda.empty_cache(), чтобы очистить кэш CUDA и тем самым освободить место VRAM, однако стоит понимать, что:

Миф о волшебной палочке: Многие думают, что этот метод мгновенно освобождает VRAM. На самом деле он лишь просит драйвер вернуть неиспользуемые блоки памяти операционной системе. Он не очищает граф вычислений или временные тензоры.

Когда это работает: У нас GPU с ограниченным объемом видеопамяти (4 ГБ). Вызов .empty_cache() помогает бороться с фрагментацией памяти между эпохами. Особенно полезно перед вызовом функции для валидации расчетов, так как там имеется другая конфигурация батчей.

4. Смешанная точность (AMP) и градиенты: Мы использовали torch.amp.autocast('cuda') в связке с GradScaler('cuda') — это компоненты Automatic Mixed Precision (AMP). autocast выполняет прямой проход в fp16, а GradScaler динамически масштабирует градиенты при обратном проходе, чтобы избежать потери точности. Вместе они ускоряют вычисления на Tensor Cores и снижают потребление видеопамяти — это хороший ход для стабильности на архитектурах Ampere.

Общая структура обучения AI модели

Сердцем системы для обучения и валидации обученной модели служит файл training.py. Здесь собраны все инженерные решения, позволяющие запустить обучение на скромном железе.

Выбранный механизм глубокого обучения

В основе модели лежат Residual Blocks (остаточные блоки) — это ключевой механизм, который позволяет обучать глубокие сети и при этом не терять мелкие детали изображения. Для задачи реставрации ретрофотографий это критично: важно не «размыть» текстуры, царапины и тонкие линии, а аккуратно их восстановить.

Определение Residual Block — это базовый элемент глубоких нейросетей, который решает проблему затухающих градиентов за счёт skip‑connections («коротких путей»). Вместо того чтобы каждый слой полностью перезаписывал признаки, сеть учится вносить «поправки» к исходному сигналу.

Почему мы выбрали этот метод:

Сохранение деталей. Skip‑connection напрямую передаёт исходный сигнал дальше по сети. Даже если последовательные слои исказят данные, оригинал останется «в цепи» и поможет восстановить структуру.

Устойчивое обучение. Градиент (сигнал ошибки) беспрепятственно проходит к ранним слоям, устраняя проблему затухания. Это позволяет строить более глубокие архитектуры и лучше обобщать.

Аккуратные правки. Модель фокусируется на «поправках» (удаление шума, царапин, коррекция цвета), а не на повторном воссоздании всей сцены — это снижает риск искажения содержания фото.

Чтение и подготовка кастомного датасета

В разделе про подготовку данных и создание шумов мы сформировали до начала обучения апскейлера объекты для обучения, находящиеся в app/models/dataset.

Обучающий и валидационный наборы разделены по каталогам:

Обучение: lr_dir / hr_dir — SRDataset, количество объектов cnt_im_start=12000, зашумление включено (noise_augmenter с типами ['gaus', 'salt_paper', 'quantize', 'color_salt_paper']).

Валидация: lv_dir / hv_dir — SRDataset, количество объектов cnt_im_start=200, зашумление начинается с пустого списка и расширяется по мере роста качества модели.

Для полноценной оценки модели зарезервирован отдельный каталог (test_lr / test_hr), не используемый ни в обучении, ни в валидации.

Процесс обучения и валидацияОбщий цикл

Обучение управляется классом TrainModel (app/training.py). Класс инкапсулирует модель, функцию потерь, оптимизатор и состояние тренировочного процесса: номер эпохи, лучший PSNR, версию модели, список активных шумов и текущие метрики.

Цикл устроен так: после каждой эпохи обучения автоматически вызывается валидация.

Если обучение прерывается вручную (Ctrl+C), отрабатывает блок except

KeyboardInterrupt — модель сохраняется и проходит финальную валидацию.

Mixed Precision: ускорение на GPU

Обучение идёт в смешанной точности (mixed precision) через torch.amp — это позволяет ускорить вычисления на видеокартах NVIDIA и снизить потребление видеопамяти. AMP состоит из двух компонентов: autocast (прямой проход в fp16) и GradScaler (масштабирование градиентов для обратного прохода). GradScaler динамически масштабирует градиенты, чтобы избежать потери точности при обратном проходе:

Валидация

Валидация проходит в режиме model.eval() — без обновления весов и без накопления градиентов (torch.no_grad()). Используется отдельный датасет из каталогов lv_dir / hv_dir с теми же трансформациями (нормализация в диапазон ([-1, 1])), но с размерностью batch_size=2 и без перемешивания.

В качестве оценки мы использовали следующие метрики:

PSNR (Peak Signal‑to‑Noise Ratio) — пиковое отношение сигнала к шуму. Измеряется в децибелах (дБ) и показывает, насколько восстановленное изображение близко к эталону.

Значение

Качество восстановления

< 10 дБ

Очень низкое качество. Изображение практически неузнаваемо.

~14–18 дБ

Низкое или среднее качество. Видны явные артефакты, но основные контуры различимы. Для задач реставрации старых фото с сильными дефектами это часто является целевым диапазоном, так как более высокие значения могут привести к «перерисовке» лица моделью.

20–30 дБ

Хорошее качество. Артефакты минимальны, изображение воспринимается как близкое к оригиналу. Это типичный диапазон для стандартных задач суперразрешения на синтетических данных.

> 30 дБ

Отличное качество. Практически полное совпадение с эталоном по яркости и контрасту. Однако для реальных фотографий такое значение может означать потерю фактуры кожи или перенасыщенность цвета.

SSIM (Structural Similarity Index Measure) — индекс структурного сходства. Метрика, оценивающая схожесть изображений не по попиксельной разности, а по трём компонентам: яркости, контрасту и структуре. Значение лежит в диапазоне от

(-1) до (1), где (1) означает полное совпадение. В отличие от PSNR, SSIM учитывает пространственные отношения между пикселями — то есть реагирует на размытие, сдвиги и структурные искажения, которые попиксельные метрики пропускают. Поэтому SSIM лучше коррелирует с тем, как человек воспринимает качество.

В коде проекта SSIM реализован через scikit‑image:

Результаты и метрики

Обучение нашей модели было довольно долгим, и на каждую эпоху затрачивалось приблизительно 60 часов. Мы уверены, что если взять видеокарту помощнее и побольше оперативной памяти, то можно было бы увеличить параметр batch_size, тем самым не снижая количество объектов для обучения.

На своём ноутбуке мы использовали для обучения максимум 12 000 объектов с размерностью batch_size 6.

Результатом получилось следующее:

Эталон
Эталон
Эпоха 1. PSNR: 14.3417 дБ
Эпоха 1. PSNR: 14.3417 дБ
Эпоха 2. PSNR: 14.9004 дБ
Эпоха 2. PSNR: 14.9004 дБ
Эпоха 3. PSNR: 16.2098 дБ
Эпоха 3. PSNR: 16.2098 дБ
Эпоха 4. PSNR: 16.6049 дБ
Эпоха 4. PSNR: 16.6049 дБ

Выводы и итог

В этой статье мы прошли полный путь: от выбора железа и настройки CUDA‑окружения на Windows до построения кастомного датасета с синтетическим шумом, обучения модели на базе Residual Blocks и борьбы с ошибками нехватки видеопамяти на 4 ГБ VRAM.

Ключевые результаты:

Окружение: настроен воспроизводимый пайплайн на базе uv с зафиксированными версиями PyTorch (CUDA 13.0) и NumPy (<2.0), что исключает конфликты зависимостей.

Датасет: создан кастомный SRDataset с четырьмя типами синтетического шума, имитирующего реальные дефекты плёнки и старых цифровых сенсоров. Данные разделены на train (12 000), valid (200) и test‑наборы.

Обучение: модель обучена в смешанной точности (AMP) с batch_size=6 на RTX 3050 Ti Laptop GPU. Одна эпоха занимала ~60 часов; за 4 эпохи PSNR вырос с 14.3 до 16.6 дБ.

Инженерные решения: отключение cuDNN benchmark, детерминизм, ручная очистка тензоров с gc.collect() и torch.cuda.empty_cache(), а также мониторинг температуры Memory Junction позволили избежать падений и утечек памяти.

Итоговый PSNR ≈ 14 дБ является низким показателем в академическом смысле, но приемлемым для задачи реставрации изображений с сильными дефектами, где важнее сохранить аутентичность, чем достичь попиксельного совпадения. На слабой видеокарте ноутбука (RTX 3050 Ti Laptop GPU) удалось создать рабочий код для получения собственной небольшой модели для улучшения качества изображений.

Комментарии (0)