Сообщество Engee

Анализ и преобразование видео

Автор
avatar-yurevyurev
Notebook

Анализ и преобразование видео.

В эпоху цифровых технологий обработка видео становится ключевой задачей в различных областях — от компьютерного зрения и машинного обучения до творческого контента и научных исследований. Представляем комплексное решение на языке программирования Julia, предлагающее набор эффективных алгоритмов для анализа и обработки видеопотоков. Данный пример демонстрирует практическую реализацию системы видеообработки, включающей детекцию границ объектов (energy analysis), стилизацию под анимацию (cartoon effect), применение сепии и других визуальных фильтров. Особое внимание уделено оптимизации работы с памятью, обеспечению корректного формата данных и поддержке современных кодеков для сохранения результатов в формате MP4.

Начнём с подключения библиотек:

  • VideoIO - основная библиотека для чтения/записи видеофайлов. Открывает видео, извлекает кадры, управляет параметрами (FPS, разрешение), сохраняет результат в MP4.

  • Images - базовые операции с изображениями: загрузка, сохранение, преобразование цветовых пространств, основные фильтры.

  • ImageFiltering - продвинутая фильтрация изображений: применение ядер свертки (Гаусса, Собеля), обнаружение границ, размытие, повышение резкости.

  • FileIO - универсальный интерфейс для работы с файлами различных форматов.

  • ColorTypes - определение цветовых моделей (RGB, Gray) и работа с цветовыми компонентами (red, green, blue).

  • FixedPointNumbers - работа с фиксированной точкой (N0f8), необходимой для корректного представления пикселей.

  • ProgressMeter - индикация прогресса обработки, отображение времени выполнения.

  • FFMPEG (опционально) - низкоуровневый доступ к видео кодекам через FFmpeg для тонкой настройки параметров кодирования.

Все библиотеки работают вместе: VideoIO загружает видео, Images/ImageFiltering обрабатывают кадры, ColorTypes управляет цветом, ProgressMeter показывает прогресс, FileIO сохраняет результат.

In [ ]:
Pkg.add("FFMPEG")
Pkg.add("ProgressMeter")
Pkg.add("ColorTypes")
Pkg.add("FixedPointNumbers")

using VideoIO, Images, ImageFiltering, FileIO, ColorTypes, FixedPointNumbers
using ProgressMeter
import ColorTypes: red, green, blue

Далее объявим вспомогательные функции:

  • prepare_frame преобразует кадры из формата PermutedDimsArray в обычные матрицы для совместимости с функциями обработки изображений.

  • to_video_format гарантирует корректное преобразование цветовых форматов (RGB и Grayscale) в тип N0f8, необходимый для совместимости с VideoIO при записи видео.

In [ ]:
function prepare_frame(frame)
    if typeof(frame) <: PermutedDimsArray
        return collect(frame)
    else
        return frame
    end
end
function to_video_format(frame::AbstractArray{<:AbstractRGB})
    return RGB{N0f8}.(frame)
end
function to_video_format(frame::AbstractArray{<:Gray})
    return Gray{N0f8}.(frame)
end
Out[0]:
to_video_format (generic function with 2 methods)

Далее объявлена process_video_with_effect — основная функция-обработчик, которая последовательно считывает каждый кадр видео, применяет к нему указанный эффект, преобразует в совместимый формат и сохраняет результат в новый видеофайл с индикацией прогресса.

Эта функция открывает исходное видео, определяет его параметры (размер, FPS), затем для каждого кадра применяет указанный эффект через переданную функцию-обработчик, преобразует результат в совместимый с VideoIO формат и последовательно записывает обработанные кадры в выходной файл, отображая прогресс операции в реальном времени.

In [ ]:
function process_video_with_effect(
    input_path::String, 
    output_path::String;
    effect_function::Function,
    max_frames::Int=0,
    effect_params...
)
    video = VideoIO.openvideo(input_path)
    frame_count = counttotalframes(video)
    max_frames > 0 && (frame_count = min(frame_count, max_frames))
    first_frame = read(video)
    seek(video, 1)
    h, w = size(first_frame)
    fps = VideoIO.framerate(video)
    println("Обработка: ", input_path)
    println("Размер: ", w, "x", h, ", FPS: ", fps, ", Кадров: ", frame_count)
    first_processed = effect_function(prepare_frame(first_frame); effect_params...)
    first_processed_video = to_video_format(first_processed)
    
    open_video_out(output_path, first_processed_video, framerate=fps) do writer
        p = Progress(frame_count, 1, "Применение эффекта...")
        for i in 1:frame_count
            frame = read(video)
            processed_frame = effect_function(prepare_frame(frame); effect_params...)
            processed_frame_video = to_video_format(processed_frame)
            write(writer, processed_frame_video)
            next!(p)
        end
    end
    
    close(video)
    println("\nВидео сохранено: ", output_path)
end
Out[0]:
process_video_with_effect (generic function with 1 method)

Функция simple_cartoon_effect реализует алгоритм мультяшной стилизации изображения через комбинацию размытия и выделения границ. Сначала исходный кадр преобразуется в оттенки серого для последующего анализа границ. Затем применяется оператор Собеля по обеим осям X и Y для вычисления градиентов яркости, после чего вычисляется энергия границ как сумма квадратов градиентов. Полученная карта границ бинаризуется с заданным порогом, создавая маску черно-белых контуров. Параллельно исходное цветное изображение обрабатывается Гауссовым размытием для упрощения цветовой палитры и создания мягкого фона. Финальный результат формируется путем наложения черных контуров из бинаризованной маски на размытое цветное изображение, что создает характерный мультяшный эффект с упрощенными цветовыми областями и четкими черными границами между ними.

In [ ]:
function simple_cartoon_effect(frame::AbstractArray{<:AbstractRGB}; edge_threshold=0.1)
    frame = prepare_frame(frame)
    gray = Gray.(frame)
    edges = imfilter(gray, Kernel.sobel()[1]).^2 + imfilter(gray, Kernel.sobel()[2]).^2
    edges = edges .> edge_threshold
    blurred = imfilter(frame, Kernel.gaussian(3))
    result = copy(blurred)
    result[edges] .= RGB{N0f8}(0,0,0)
    return result
end
Out[0]:
simple_cartoon_effect (generic function with 1 method)

Функция energy_effect вычисляет карту энергии границ на изображении с помощью оператора Собеля. Сначала исходный цветной кадр преобразуется в матрицу яркости по стандартной формуле взвешенной суммы цветовых компонентов. Затем применяются свертки с ядрами Собеля по осям X и Y для вычисления горизонтальных и вертикальных градиентов яркости. Для каждого пикселя рассчитывается энергия границ как евклидова норма градиентов, что соответствует интенсивности изменения яркости в данной точке. Полученная карта энергии нормализуется на максимальное значение для приведения к диапазону от 0 до 1 и возвращается в виде grayscale-изображения, где яркость каждого пикселя соответствует силе границы в этой позиции, что визуализирует структурные особенности и контуры исходного изображения.

In [ ]:
function energy_effect(frame::AbstractArray{<:AbstractRGB}; sobel_x=nothing, sobel_y=nothing)
    frame = prepare_frame(frame)
    h, w = size(frame)
    gray_buffer = Matrix{Float32}(undef, h, w)
    energy_buffer = Matrix{Float32}(undef, h, w)
    
    if sobel_x === nothing
        sobel_x, sobel_y = Kernel.sobel()
    end
    
    for i in eachindex(frame)
        pixel = frame[i]
        gray_buffer[i] = 0.299f0 * red(pixel) + 0.587f0 * green(pixel) + 0.114f0 * blue(pixel)
    end
    
    gradient_x = imfilter(gray_buffer, sobel_x)
    gradient_y = imfilter(gray_buffer, sobel_y)
    
    for i in eachindex(energy_buffer)
        energy_buffer[i] = sqrt(gradient_x[i]^2 + gradient_y[i]^2)
    end
    
    energy_normalized = energy_buffer ./ maximum(energy_buffer)
    return Gray.(energy_normalized)
end
Out[0]:
energy_effect (generic function with 1 method)

Функция sepia_effect применяет к изображению сепийный фильтр, который имитирует старинную фотографию с характерным коричневатым оттенком. Алгоритм работает путем преобразования каждого пикселя через линейную комбинацию цветовых каналов с определенными весовыми коэффициентами, где красный канал усиливается за счет зеленого и синего, создавая теплый тон. Значения каждого цветового компонента ограничиваются сверху единицей для предотвращения переполнения, а результат сохраняется в формате с фиксированной точкой, обеспечивая совместимость с системой обработки видео. Конечным результатом является изображение с винтажным эффектом сепии, где сохраняются все детали оригинала, но цветовая гамма смещается в сторону коричневых и желтоватых тонов, характерных для исторических фотографических процессов.

In [ ]:
function sepia_effect(frame::AbstractArray{<:AbstractRGB})
    frame = prepare_frame(frame)
    result = similar(frame)
    for i in eachindex(frame)
        pixel = frame[i]
        r_val = red(pixel)
        g_val = green(pixel)
        b_val = blue(pixel)
        r = 0.393 * r_val + 0.769 * g_val + 0.189 * b_val
        g = 0.349 * r_val + 0.686 * g_val + 0.168 * b_val
        b = 0.272 * r_val + 0.534 * g_val + 0.131 * b_val
        result[i] = RGB{N0f8}(min(r,1.0), min(g,1.0), min(b,1.0))
    end
    return result
end
Out[0]:
sepia_effect (generic function with 1 method)

Функция blur_effect применяет эффект размытия к изображению с помощью гауссова фильтра, который равномерно снижает резкость и создает мягкое сглаживание деталей. Размер ядра фильтра регулируется параметром kernel_size, определяющим степень размытия - чем больше ядро, тем сильнее эффект сглаживания. Гауссово ядро вычисляет взвешенное среднее значение пикселей в окрестности, где веса убывают по гауссовой кривой от центра к краям, что обеспечивает естественное плавное размытие без артефактов. Результат преобразуется в формат с фиксированной точкой для обеспечения совместимости с системой обработки видео, сохраняя при этом плавные цветовые переходы и мягкую текстуру размытого изображения.

In [ ]:
function blur_effect(frame::AbstractArray{<:AbstractRGB}; kernel_size=5)
    frame = prepare_frame(frame)
    blurred = imfilter(frame, Kernel.gaussian(kernel_size))
    return RGB{N0f8}.(blurred)
end
Out[0]:
blur_effect (generic function with 1 method)

Функция sharpen_effect применяет эффект повышения резкости к изображению с помощью лапласианоподобного ядра свертки размером 3x3. Ядро имеет центральный коэффициент 9 и окружающие его значения -1, что усиливает высокочастотные компоненты изображения и подчеркивает контуры объектов. После применения свертки значения пикселей ограничиваются диапазоном от 0 до 1 для предотвращения выхода за допустимые границы цветового пространства, а результат преобразуется в формат с фиксированной точкой для обеспечения совместимости с системой обработки видео. Этот процесс эффективно усиливает детализацию и текстуру изображения, делая края более четкими и выраженными без значительных искажений цветового баланса.

In [ ]:
function sharpen_effect(frame::AbstractArray{<:AbstractRGB})
    frame = prepare_frame(frame)
    kernel = [-1 -1 -1; -1 9 -1; -1 -1 -1] / 1.0
    sharpened = imfilter(frame, kernel)
    sharpened = clamp.(sharpened, 0.0, 1.0)
    return RGB{N0f8}.(sharpened)
end
Out[0]:
sharpen_effect (generic function with 1 method)

Функция safe_cartoon_effect создает устойчивую версию мультяшного эффекта с гарантированной совместимостью типов данных. В отличие от базовой реализации, она сначала преобразует входное изображение в формат RGB{N0f8}, что обеспечивает корректную работу всех последующих операций. Алгоритм начинается с конвертации изображения в оттенки серого для анализа градиентов, затем применяет оператор Собеля по обеим осям для вычисления карты границ. После бинаризации границ с заданным порогом создается маска контуров, которая накладывается на размытую версию исходного изображения. Ключевое отличие от простой версии заключается в том, что все операции выполняются с уже приведенными к correct формату данными, что исключает ошибки несовместимости типов при последующей обработке видео. Результатом является изображение с характерным мультяшным стилем - мягкими цветовыми областями и четкими черными контурами, готовое для непосредственной записи в видеофайл без дополнительных преобразований.

In [ ]:
function safe_cartoon_effect(frame::AbstractArray{<:AbstractRGB}; edge_threshold=0.1)
    frame = prepare_frame(frame)
    frame_rgb = RGB{N0f8}.(frame)
    gray = Gray.(frame_rgb)
    edges = imfilter(gray, Kernel.sobel()[1]).^2 + imfilter(gray, Kernel.sobel()[2]).^2
    edges = edges .> edge_threshold
    blurred = imfilter(frame_rgb, Kernel.gaussian(3))
    result = copy(blurred)
    result[edges] .= RGB{N0f8}(0,0,0)
    return result
end
Out[0]:
safe_cartoon_effect (generic function with 1 method)

Теперь посмотрим на результаты работы всех выше описанных алгоритмов. Анализируемый код представляет собой комплексную систему обработки видео на языке Julia, реализующую разнообразные визуальные эффекты через модульный и эффективный подход. Основной алгоритм работы строится вокруг центральной функции-обработчика, которая последовательно считывает каждый кадр видео, применяет к нему выбранный эффект через специализированные функции-фильтры, обеспечивает корректное форматирование данных и сохраняет результат в выходной видеофайл с индикацией прогресса.

Ключевые преимущества данного подхода заключаются в его модульности и типобезопасности. Каждый эффект реализован как независимая функция, что позволяет легко комбинировать фильтры, добавлять новые преобразования и тестировать их изолированно. Система активно использует строгую типизацию Julia, что проявляется в функциях приведения форматов, гарантирующих совместимость данных с библиотекой VideoIO. Это исключает распространенные ошибки форматов пикселей, особенно при работе с цветовыми пространствами и фиксированной точностью.

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

In [ ]:
process_video_with_effect("input.mp4", "energy_analysis.mp4",
    effect_function=energy_effect, max_frames=50)
Обработка: input.mp4
Размер: 1280x720, FPS: 25//1, Кадров: 50
Применение эффекта...   4%|█▎                            |  ETA: 0:00:27
Применение эффекта...   8%|██▍                           |  ETA: 0:00:33
Применение эффекта...  12%|███▋                          |  ETA: 0:00:29
Применение эффекта...  16%|████▊                         |  ETA: 0:00:26
Применение эффекта...  20%|██████                        |  ETA: 0:00:24
Применение эффекта...  24%|███████▎                      |  ETA: 0:00:24
Применение эффекта...  28%|████████▍                     |  ETA: 0:00:22
Применение эффекта...  32%|█████████▋                    |  ETA: 0:00:20
Применение эффекта...  36%|██████████▊                   |  ETA: 0:00:19
Применение эффекта...  40%|████████████                  |  ETA: 0:00:18
Применение эффекта...  44%|█████████████▎                |  ETA: 0:00:17
Применение эффекта...  48%|██████████████▍               |  ETA: 0:00:16
Применение эффекта...  52%|███████████████▋              |  ETA: 0:00:15
Применение эффекта...  56%|████████████████▊             |  ETA: 0:00:13
Применение эффекта...  60%|██████████████████            |  ETA: 0:00:12
Применение эффекта...  64%|███████████████████▎          |  ETA: 0:00:11
Применение эффекта...  68%|████████████████████▍         |  ETA: 0:00:10
Применение эффекта...  72%|█████████████████████▋        |  ETA: 0:00:09
Применение эффекта...  76%|██████████████████████▊       |  ETA: 0:00:07
Применение эффекта...  80%|████████████████████████      |  ETA: 0:00:06
Применение эффекта...  84%|█████████████████████████▎    |  ETA: 0:00:05
Применение эффекта...  88%|██████████████████████████▍   |  ETA: 0:00:04
Применение эффекта...  92%|███████████████████████████▋  |  ETA: 0:00:02
Применение эффекта...  96%|████████████████████████████▊ |  ETA: 0:00:01
Применение эффекта... 100%|██████████████████████████████| Time: 0:00:30
Видео сохранено: energy_analysis.mp4
In [ ]:
process_video_with_effect("input.mp4", "cartoon_safe.mp4",
    effect_function=safe_cartoon_effect, edge_threshold=0.15, max_frames=50)
Обработка: input.mp4
Размер: 1280x720, FPS: 25//1, Кадров: 50
Применение эффекта...  14%|████▎                         |  ETA: 0:00:06
Применение эффекта...  22%|██████▋                       |  ETA: 0:00:08
Применение эффекта...  40%|████████████                  |  ETA: 0:00:06
Применение эффекта...  54%|████████████████▎             |  ETA: 0:00:05
Применение эффекта...  68%|████████████████████▍         |  ETA: 0:00:03
Применение эффекта...  78%|███████████████████████▍      |  ETA: 0:00:02
Применение эффекта...  92%|███████████████████████████▋  |  ETA: 0:00:01
Применение эффекта... 100%|██████████████████████████████| Time: 0:00:09
Видео сохранено: cartoon_safe.mp4
In [ ]:
process_video_with_effect("input.mp4", "sepia.mp4",
    effect_function=sepia_effect, max_frames=50)
Обработка: input.mp4
Размер: 1280x720, FPS: 25//1, Кадров: 50
Применение эффекта...  42%|████████████▋                 |  ETA: 0:00:01
Применение эффекта...  88%|██████████████████████████▍   |  ETA: 0:00:00
Применение эффекта... 100%|██████████████████████████████| Time: 0:00:02
Видео сохранено: sepia.mp4
In [ ]:
process_video_with_effect("input.mp4", "blur.mp4",
    effect_function=blur_effect, kernel_size=7, max_frames=50)
Обработка: input.mp4
Размер: 1280x720, FPS: 25//1, Кадров: 50
Применение эффекта...  22%|██████▋                       |  ETA: 0:00:06
Применение эффекта...  52%|███████████████▋              |  ETA: 0:00:02
Применение эффекта...  78%|███████████████████████▍      |  ETA: 0:00:01
Применение эффекта... 100%|██████████████████████████████| Time: 0:00:04
Видео сохранено: blur.mp4
In [ ]:
include("player.jl")
media_player(@__DIR__, mode="video")
blur.mp4 (1 of 5)

Вывод

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

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