Апскейл видео из SD (DVD) в FullHD-4K современными нейросетями. апскейл.. апскейл. Будущее здесь.. апскейл. Будущее здесь. видео.. апскейл. Будущее здесь. видео. ИИ.. апскейл. Будущее здесь. видео. ИИ. искусственный интеллект.. апскейл. Будущее здесь. видео. ИИ. искусственный интеллект. кино.. апскейл. Будущее здесь. видео. ИИ. искусственный интеллект. кино. нейросети.. апскейл. Будущее здесь. видео. ИИ. искусственный интеллект. кино. нейросети. Работа с видео.

Меня давно интересовала тема апскейла изображений, отдельно – апскейла старых видео. Одно из первых решений, которое попалось в руки несколько лет назад – waifu2x (https://github.com/nagadomi/waifu2x). Но эта нейронка больше подходила для апскейла аниме (насколько я помню на них она и тренировалась). То есть, waifu2x подходила для довольно простых изображений без избытка деталей и сложности текстур.

Затем я поизучал ESRGAN (https://github.com/xinntao/ESRGAN) и Real-ESRGAN (https://github.com/xinntao/Real-ESRGAN). Довольно неплохие модельки, вполне годятся для апскейла изображений, но очень часто заметна синтетичность, особенно в сложных сценах, например когда на изображении есть деревья. Я даже попробовал дотренировать Real-ESRGAN, к слову это делать не сложно, на их гитхабе есть скрипты и инструкции (https://github.com/xinntao/Real-ESRGAN/blob/master/docs/Training.md), но пока дособирал свой датасет для тренировки на глаза попалась другая модель – SwinIR (https://github.com/JingyunLiang/SwinIR), потестировав которую понял – она покрывает мои текущие потребности, если не полностью, то по меньшей мере процентов на 80%. А потребности были – заапскейлить несколько старых фильмов, и чтобы после апскейла фильм смотрелся как фильм, а не как пластилиновый театр. В целом все получилось. Именно об этом это статья.

Апскейлить будем фильм “Пираты Силиконовой долины” (1999г, США). Он повествует о появлении домашнего ПК и становлении компаний Apple и Microsoft. Довольно интересный фильм с бунтарским духом той эпохи. Главные герои – молодые Джобс, Возняк, Гейтс и другие участники “революции домашних ПК”. Кстати, апскейлить фильм будем конечно же на домашнем ПК.

Характеристики диска:
– DVD5, MPEG-2, разрешение 720×480 (NTSC)

Моя конфигурация для этой задачи:
– Gigabyte A520M, AMD Ryzen 5 PRO 3600, 32Gb DDR4 3200 MT/s (16+16)
– Gigabyte GeForce RTX 3060 12GB, CUDA Version: 12.5
– Ubuntu 22.04

Нам понадобятся:
– ПК и видеокарта с поддержкой CUDA
– Python
– Библиотека Spandrel для Python
– Одна из моделей SwinIR
– ffmpeg и ffprobe
– Порядка 80Гб дискового пространства
– Несколько дней работы ПК (если непрерывно, то ~5 дней для апскейла в 2x на конфигурации вроде моей)

Кратко суть алгоритма:
– С помощью ffmpeg распаковываем фильм по кадрам в формате PNG
– Апскейлим каждый кадр
– С помощью ffmpeg кодируем полученные изображения в HD-версию фильма с присоединением аудио-дорожек из исходного материала

Приступим.

Актер Ноа Уайли в роли Стива Джобса, уже улучшенный
Актер Ноа Уайли в роли Стива Джобса, уже улучшенный

Установка ПО

Установка ffmpeg

Под Ubuntu:

apt update
apt install ffmpeg

(при установке ffmpeg одновременно подтягивается ffprobe)

Под Windows:
https://www.ffmpeg.org/download.html

Качаем один из последних build, распаковываем архив, например в c:ffmpeg. Из архива нам нужны две утилиты: ffmpeg и ffprobe.
Можно прописать путь к папке ffmpeg в PATH, чтобы вызывать ffmpeg из командной строки в любой директории.

Установка Spandrel

pip install spandrel

Скачивание модели

Список доступных моделей SwinIR:
https://github.com/JingyunLiang/SwinIR/releases

Я тестировал все модели из списка, наиболее понравились эти 2:
Для апскейла 2x: 003_realSR_BSRGAN_DFO_s64w8_SwinIR-M_x2_GAN.pth
Для апскейла 4x: 003_realSR_BSRGAN_DFOWMFC_s64w8_SwinIR-L_x4_GAN.pth

Качаем модель, запоминаем путь к модели.

Я делал апскейл до 2x, соответственно модель: 003_realSR_BSRGAN_DFO_s64w8_SwinIR-M_x2_GAN.pth

О требуемом времени и ресурсах будет ниже, замечу лишь, что апскейл в 4x будет существенно дольше, а качество в сравнении с 2x скорее незначительное, а в чем-то хуже.

Этап1: распаковка видео

Склейка VOB’ов

В моем DVD экземпляре 4 основных vob’а:
VTS_01_1.VOB
VTS_01_2.VOB
VTS_01_3.VOB
VTS_01_4.VOB

Склеим их.
Команда для Linux:
cat VTS_01_1.VOB VTS_01_2.VOB VTS_01_3.VOB VTS_01_4.VOB > video.vob

Команда для Windows:
copy /b VTS_01_1.VOB+VTS_01_2.VOB+VTS_01_3.VOB+VTS_01_4.VOB video.vob

Дальше будем работать с объединенным video.vob.

Сразу можно выгрузить аудио-дорожки. Посмотрим какие вообще есть:
ffprobe -i video.vob

Видим 2 аудиодорожки:

Stream #0:2[0x80]: Audio: ac3, 48000 Hz, stereo, fltp, 192 kb/s, Start-Time 0.281s
Stream #0:3[0x81]: Audio: ac3, 48000 Hz, stereo, fltp, 192 kb/s, Start-Time 0.281s

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

ffmpeg -i video.vob -map 0:a:0 -c copy audio_track_eng.ac3 -map 0:a:1 -c copy audio_track_rus.ac3

Параметр “-c copy” указывает выгружать аудио как есть, без пережатия.

Теперь можно приступить к распаковке фреймов.

Раскадровка

Ремарка:
Здесь и далее все команды приводятся под Linux. Под Windows они обычно идентичные, если не задано другого варианта явно, за исключением указания путей к директориям. В Linux путь к директории идет через прямой слеш “/”, в Windows через обратный “”.

В идеальном сценарии мы могли бы выполнить простую команду:
ffmpeg -i video.vob "video_in_png/file_%06d.png"

После чего в папке “video_in_png” появились бы все фреймы в исходном количестве и в исходном разрешении. Но… Из двух DVD, которые я успел обработать, в обоих случаях были “особенности”. Данный диск оказался самым сложным из них.

Во-первых, реальная частота кадров. Я смотрел данные по ней несколькими утилитами и разными способами, вот краткий список FPS, которые определялись для фильма:
60000/1001 = 59,94…
29970/1000 = 29,97
119/4 = 29,75
24000/1001 = 23,98…

Мне даже пришлось смотреть VOB файлы в hex-редакторе и по определенным сигнатурам расшифровывать значения байтов (большое спасибо ChatGPT).
Там я узнал “точно”, что:

  • фреймрейт 29.97 fps – не правда (спойлер: либо диск скомпонован криво, либо я просто не обладаю магистерской степенью по структуре DVD)

  • разрешение 720×480 – не правда, фактическое разрешение оказалось 640×480

Кстати, утилита ffprobe тоже выводит эти данные:
Stream #0:1[0x1e0]: Video: mpeg2video (Main), yuv420p(tv, smpte170m, progressive), 720x480 [SAR 32:27 DAR 16:9], 29.75 fps

Еще и уверяет, что видео формата 16:9, тогда как оно 4:3.

В общем методом перебора я пришел к тому, что реальная частота кадров:
24000/1001, то есть, ~23,98 fps. Не хочу сильно задерживаться на этом, это история размером с отдельную статью, и надеюсь данный случай был редким исключением, поэтому нет смысла его описывать.

Отдельно – история с разрешением. Все утилиты, а также биты в исходных VOB’ах, говорят, что разрешение видео стандартное – 720×480, но если выгружать фреймы в этом разрешении, картинка становится растянута по горизонтали. Как уже писал выше, фактическое разрешение фреймов оказалось 640×480. Это я вывел эмпирически, и позже подтвердил расчетами (на всякий случай). Здесь тоже не хотелось бы задерживаться, надеюсь этот диск просто исключение, видеоряд формата 640×480 записали как стандартное для DVD разрешение 720×480, и вероятно просто не корректно внесли разметку в структуру данных.

Если бы вместо:
720×480 [SAR 32:27 DAR 16:9]
было указано:
720×480 [SAR 8:9 DAR 4:3]
Тогда бы все сходилось. SAR (Sample Aspect Ratios) если простыми словами, указывает, как растянуть или сузить видео. На всякий случай формула расчета:
Ширина фактическая = Ширина исходная (в файле) x (SAR_width / SAR_height) = 720 x (8/9) = 640, высота не меняется, итого 640×480.

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

Итоговая команда для выгрузки фреймов:
ffmpeg -i video.vob -r 24000/1001 -vf "scale=640:480:flags=lanczos" "/home/user/frames_orig/file_%06d.png"

Здесь:
“-i video.vob” – исходный объединенный файл
“-r 24000/1001” – частота кадров равная ~23,98 кадров/сек
“-vf “scale=640:480:flags=lanczos”” – фильтр выходного потока: меняем разрешение на 640×480 и добавляем метод интерполяции Ланцоша, последний хорошо сглаживает неровности на изображении после любой его деформации, но сохраняет четкость изображения
“/home/user/frames_orig” – директория куда будут выгружаться фреймы; нужно предварительно создать
“file_%06d.png” – формат PNG, маска файлов %06d – 6-значный счетчик начиная с 000000, файлы будут вида “file_000000.png”, “file_000001.png” и тд.

Вариант этой же команды, но с использованием GPU (CUDA), работает обычно быстрее:
ffmpeg -hwaccel cuda -i video.vob -r 24000/1001 -vf "scale=640:480:flags=lanczos" "/home/user/frames_orig/file_%06d.png"

Еще раз напомню, если бы не было проблем с конкретным диском, команда была бы проще и очевидней:
ffmpeg -i video.vob -vf "scale=640:480:flags=lanczos" "/home/user/frames_orig/file_%06d.png"

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

Если бы мы выгружали фреймы этой командой, тогда каждый 3-5 фрейм был бы дублем предыдущего. Всего бы выгрузилось 174000 фреймов, тогда как фактически их 139202, или 139235 по другой оценке, но не будем про это, и так достаточно :)

Кстати, насчет другого DVD который я апскейлил.
Параметры видеоряда там были следующие:
Stream #0:0: Video: mpeg2video (Main), yuv420p(tv, top first), 720x576 [SAR 64:45 DAR 16:9], 25 fps, 25 tbr, 1k tbn

Они соответствовали действительности. Исходя из исходного разрешения 720×576 и SAR 64:45, выходило, что кадр будет выведен на экран в разрешении 1024×576 16:9 (720 x (64/45) = 1024). Я хотел увеличить разрешение видео 2 раза, до FullHD, соответственно разрешение исходного кадра нужно было 960×540 (умножаем на 2 получается 1920×1080). Здесь главное, что пропорции сохранены, 1024×576 и 960×540 оба 16:9. Соответственно выгружал фреймы командой:
ffmpeg -i video_in.mkv -vf scale=960:540:flags=lanczos "/home/user/frames_orig/file_%06d.png"

Актер Майкл Энтони Холл, в роли Билла Гейтса, кажется всем доволен

Актер Майкл Энтони Холл, в роли Билла Гейтса, кажется всем доволен

Этап2: Апскейл фреймов

Теперь можно приступить к апскейлу фреймов. Это процесс длительный, в зависимости от количества фреймов и их разрешения занимает до нескольких суток. Я обычно делаю итерациями, пачками по 10000 фреймов. Для этого можно, например, разбить общую папку с фреймами на несколько по 10тыс файлов в каждой, либо доработать скрипт ниже, чтобы он работал по диапазону файлов (главное не запутаться в их порядке, тогда можно словить рассинхронизацию).

Пример команды в Linux для разбивки общей папки на несколько по 10тыс файлов:
i=1; for file in all_frames/*; do mkdir -p "frames_((i/10000+1))file" "frames_$((i/10000+1))"; ((i++)); done

Команда для Windows (PowerShell):
i=1; Get-ChildItem all_frames | ForEach-Object { $d=([math]::Floor(($i-1)/10000)+1)"; if (!(Test-Path $d)) {New-Item -ItemType Directory -Path $d | Out-Null}; Move-Item $_.FullName $d; $i++ }

Не буду останавливаться здесь на долго, как именно разбить процесс на части – дело вкуса.

Основной скрипт:

Код:
import os
import torch
from PIL import Image
import torchvision.transforms as transforms
from spandrel import ImageModelDescriptor, ModelLoader


# ПАРАМЕТРЫ
images_path = "/home/user/frames_in"
output_dir = "/home/user/frames_upscaled"
os.makedirs(output_dir, exist_ok=True) # Создаем папку если не найдена

model_path = "/home/user/models/003_realSR_BSRGAN_DFO_s64w8_SwinIR-M_x2_GAN.pth"

batch_size = 2  # Размер батча
batch_images = []  # Очередь изображений в батче

output_format = "JPG"  # PNG, JPG

# Список изображений
all_files = sorted([f for f in os.listdir(images_path) if os.path.isfile(os.path.join(images_path, f))])
all_files_count = len(all_files)

# Очищать torch.cuda.empty_cache(), True или False, иногда помогает вместить изображения в батч
clean_cache = 0

# Загрузка модели
model = ModelLoader().load_from_file(model_path)
assert isinstance(model, ImageModelDescriptor)
model.cuda().eval()


def save_image(image_name, output_tensor):
    ''' Преобразование изображения из тензора и сохранение в зависимости от заданного формата '''
    
    output_image = transforms.ToPILImage()(output_tensor.cpu().clamp(0, 1))
    output_path = os.path.join(output_dir, f"{image_name}.{output_format.lower()}")
    
    fmt = output_format.upper()
    
    if fmt == "PNG":
        output_image.save(output_path, format="PNG")
    elif fmt == "JPG":
        output_image.save(output_path, format="JPEG", quality=100)
    else:
        raise ValueError(f"Ошибка формата: {output_format!r}")
        

# СТАРТ ОБРАБОТКИ    
for idx, image_in in enumerate(all_files):
    image_path = os.path.join(images_path, image_in)
    image_name = os.path.splitext(image_in)[0]
    
    image = Image.open(image_path).convert("RGB")
    input_tensor = transforms.ToTensor()(image).unsqueeze(0).cuda()

    batch_images.append({'image_name': image_name, 'input_tensor': input_tensor})
    
    # Если накопился полный батч или это последнее изображение - обрабатываем батч
    if len(batch_images) == batch_size or idx == all_files_count - 1:
        try:
            # Очищаем память Cuda если clean_cache = True (иногда помогает вместить изображения в батч)
            if clean_cache: torch.cuda.empty_cache()
            
            # Проверяем доступную память перед объединением изображений в батч
            required_memory = sum(item['input_tensor'].element_size() * item['input_tensor'].nelement() for item in batch_images)
            free_memory = torch.cuda.memory_reserved(0) - torch.cuda.memory_allocated(0)
            if free_memory < required_memory:
                raise RuntimeError("CUDA out of memory")
    
            batch_tensor = torch.cat([item['input_tensor'] for item in batch_images], dim=0)
            
            # Отправляем модели батч с изображениями
            with torch.no_grad():
                output_tensor = model(batch_tensor)

            # Сохраняем обработанные изображения
            for i, item in enumerate(batch_images):
                image_name = item['image_name']
                save_image(image_name, output_tensor[i])
                
        except RuntimeError as e:
            if "CUDA out of memory" in str(e):
                print("Ошибка наличия свободной памяти, обрабатываем изображения по одному...")
            
                for item in batch_images:
                    image_name = item['image_name']
                    single_tensor = item['input_tensor']
                
                    with torch.no_grad():
                        output_tensor = model(single_tensor)
                    save_image(image_name, output_tensor[0])
                    
            else:
                raise
                
        except Exception as e:  # Другая ошибка
            print(f"Ошибка: {e}")
        
        finally:  # Очищаем батч
            batch_images.clear()


print("ГОТОВО.")


# Выгружаем модель
del model
torch.cuda.empty_cache()

Пояснение по некоторым параметрам.
batch_size = 2
Размер батча который мы передаем модели, сколько изображений обрабатывать одновременно. Я обычно использую 2, т.к., во-первых – это существенно ускоряет обработку в сравнении с 1 изображением, во-вторых – оптимальное расходование памяти, в третьих – дальнейшее увеличение батча, в моем случае, почти не дает прироста скорости, зато потребление памяти возрастает существенно.

К слову, для фреймов в разрешении 640×480 на моей RTX 3600 12Gb batch_size максимум 3, больше не хватит памяти; для 960×540 максимум 2 фрейма можно отправить видеокарте единовременно.

А вот замер обработки фреймов в разрешении 320×240 с разными батчами, апскейл 2x:

Открыть

1 batch:
Время выполнения: 1 минута 40 секунд
Максимум памяти использовалось: 849.29 MB
Максимум памяти включая резерв: 982.00 MB

2 batches:
Время выполнения: 1 минута 14 секунд
Максимум памяти использовалось: 1612.66 MB
Максимум памяти включая резерв: 1840.00 MB

3 batches:
Время выполнения: 1 минута 13 секунд
Максимум памяти использовалось: 2365.52 MB
Максимум памяти включая резерв: 2712.00 MB

4 batches:
Время выполнения: 1 минута 12 секунд
Максимум памяти использовалось: 3122.71 MB
Максимум памяти включая резерв: 3570.00 MB

6 batches:
Время выполнения: 1 минута 12 секунд
Максимум памяти использовалось: 4640.16 MB
Максимум памяти включая резерв: 5328.00 MB

8 batches:
Время выполнения: 1 минута 12 секунд
Максимум памяти использовалось: 6153.86 MB
Максимум памяти включая резерв: 7046.00 MB

10 batches:
Время выполнения: 1 минута 12 секунд
Максимум памяти использовалось: 7674.44 MB
Максимум памяти включая резерв: 8778.00 MB

12 batches:
Время выполнения: 1 минута 12 секунд
Максимум памяти использовалось: 9183.08 MB
Максимум памяти включая резерв: 10538.00 MB

Как видно прирост в скорости заметен лишь в сравнении 2 batches и 1 batch. Возможно на других конфигурациях и видеокартах увеличение батча скажется существеннее.

output_format = “JPG” # PNG, JPG
В каком формате сохранить увеличенное изображение. Итоговые файлы я обычно сохраняю в JPG, т.к. увеличенные фреймы в PNG будут занимать слишком много места, а вот исходные фреймы выгружаю через ffmpeg в PNG.

clean_cache = 1
Честно признаться – это костыль, чтобы впихнуть невпихуемое вместить фреймы в батчи в отдельных случаях. Только с помощью этого костыля мне удалось вместить 2 фрейма в батч в случае обработки другого диска, где разрешение входящих фреймов было 960×540.

По другим параметрам должно быть все очевидно.

Запускаем скрипт и уходим на несколько дней ждать обработки.

Примеры изображений ДО и ПОСЛЕ

Слева оригинал, справа апскейл 2x

Слева оригинал, справа апскейл 2x
Слева оригинал, справа апскейл 2x

Слева оригинал, справа апскейл 2x
Слева оригинал, справа апскейл 2x

Слева оригинал, справа апскейл 2x
Слева оригинал, справа апскейл 2x

Слева оригинал, справа апскейл 2x
Слева оригинал, справа апскейл 2x

Слева оригинал, справа апскейл 2x
Слева оригинал, справа апскейл 2x

Слева оригинал, справа апскейл 2x

Или к примеру анимированные гифки в большем разрешении. Рекомендуется смотреть в увеличенном виде:

Сравнение в гифах:
До и после

До и после
До и после

До и после
До и после

До и после
До и после

До и после
До и после

До и после
До и после

До и после
До и после

До и после

Этап3: Финал, кодирование видео

После того как мы запскейлили все фреймы, остается закодировать итоговое видео.
Команда:
ffmpeg -r 24000/1001 -i "/home/user/frames_upscaled/file_%06d.jpg" -i "/home/user/audio_track_rus.ac3" -i "/home/user/audio_track_eng.ac3" -c:v hevc_nvenc -b:v 10M -minrate 5M -maxrate 15M -bufsize 30M -preset p7 -map 0:v -map 1:a -map 2:a -metadata:s:a:0 title="Russian" -metadata:s:a:0 language=rus -metadata:s:a:1 title="English" -metadata:s:a:1 language=eng -c:a copy -pix_fmt yuv420p video_hd.mkv

Здесь:
“-r 24000/1001” – частота кадров равная ~23,98 кадров/сек
“-i /home/user/frames_upscaled/file_%06d.jpg” – директория с увеличенными фреймами
“-i “/home/user/audio_track_rus.ac3” -i “/home/user/audio_track_eng.ac3″” – подключаем аудиодорожки
“-c:v hevc_nvenc” – кодек
“-b:v 10M -minrate 5M -maxrate 15M” – переменный битрейт, среднее значение 10Мбит/сек, минимальное 5Мбит/сек, максимальное 15Мбит/сек
“-bufsize 30M” – размер буфера для переменного битрейта, рекомендуется использовать 2x от maxrate (2x15M=30M), либо можно не указывать, оставить на усмотрение ffmpeg
“-preset p7” – пресет 7 для кодека hevc_nvenc, высокое качество
“-map 0:v -map 1:a -map 2:a” – порядок потоков которые подключаем в итоговый файл: видео из фреймов, аудио1, аудио2 – которые указали до этого
“-metadata:s:a:0 title=”Russian” -metadata:s:a:0 language=rus -metadata:s:a:1 title=”English” -metadata:s:a:1 language=eng” – прописываем мета-информацию о языках аудиодорожек
“-c:a copy” – копировать аудио без пережатия
“-pix_fmt yuv420p” – цветовой формат пикселей, для обычных видео обычно используется yuv420p
“video_hd.mkv” – имя выходного видео в контейнере MKV

Ждем компиляции и все готово.

Заключение

Мы заапскейлили фильм с помощью модели SwinIR. В принципе можно попробовать любую другую модель, библиотека Spandrel позволяет работать со многими моделями. Есть сайт https://openmodeldb.info/ где сотни моделей на разных архитектурах, в основном там тюны базовых моделей.

Ремарка:
Важно! Если у вас PyTorch ниже версии 2.6, тогда настоятельно рекомендуется запускать модели .pth/.bin от неизвестных авторов с флагом weights_only=True. Это связано с тем, что в бинарные файлы моделей может быть встроен вредоносный код, который может произвольно выполниться при десериализации модели в память (проще говоря при загрузке). Если мы задаем флаг weights_only=True, тогда PyTorch загружает только веса модели.
Начиная с PyTorch 2.6 флаг по умолчанию стал weights_only=True, если аргумент не передается явно.

Итого, из исходных 640×480 мы получили видео 1280×960 (4:3). Это не стандартное разрешение формата HD (1280×720 16:9) или FullHD (1920×1080 16:9), но и исходный материал оказался не стандартным.

Ниже будут ссылки на примеры кадров ДО/ПОСЛЕ, а также фрагмент видео ДО/ПОСЛЕ. В целом качество вполне приличное, как-будто действительно полноценное HD.

Какие замечены недостатки? Не очень реалистично получаются трава и деревья, часто заметно, что нейронка их перерисовала включив фантазию. Также иногда можно заметить синтетичность в отдельных деталях или объектах, но не часто и если всматриваться. Вообще чем более размытое исходное изображение или отдельные объекты на нем, тем хуже они заапскейлятся, будет заметна синтетичность.

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

Дополнительные материалы

Примеры кадров ДО и ПОСЛЕ можно посмотреть и скачать на моем гугл-диске. Там же есть примеры изображений увеличенных в 4 раза. Некоторые примеры опубликую у себя в Тг-канале.

Фрагмент видео: оригинал и улучшенный.

После того как из DVD видео сделали HD, его можно переделать в 3D. Об этом я писал в другой статье:
Как сделать 3D версию любого фильма на примере StarWars4 (DepthAnythingV2 + Parallax)

Спасибо за внимание.

Автор: peterplv

Источник

Rambler's Top100