Engee 文档
Notebook

视频处理-对象边界分析

本文考虑了一种用于构建视频序列的能量图的方法。

能量图作为亮度梯度的欧几里德范数计算,是缝雕和轮廓高亮等计算机视觉算法的关键组成部分。

我们已经提出了Julia语言的实现,其中包括在单个静态帧上的调试阶段以及随后对视频数据流的优化处理。

为了确保性能,使用了内存预分配技术和操作矢量化。 这项工作的结果是一个脚本,以GIF格式生成视觉动画,它演示了能量图随时间的动态变化。 首先,让我们来看看原始视频。

In [ ]:
include("player.jl")
media_player("input.mp4", mode="video")
input.mp4 (1 of 1)

现在让我们继续实现一个简单的处理示例。 此脚本的目的是从视频文件中读取第一帧。 input.mp4,将其转换为黑白图像,计算能量图,可视化结果并将其保存到文件中,以及输出基本统计数据,然后逐点分析脚本。

该脚本是研究和调试,它旨在在更复杂和资源密集型任务中使用它之前验证单个静态帧上算法的正确性。

1. 导入必要的库

  • Images.jl:用于处理图像(加载,保存,基本像素操作)的主软件包。
  • ImageFiltering.jl:提供图像过滤功能,例如 imfilter,其用于应用卷积核(在本例中为Sobel算子)。
  • VideoIO.jl:用于读取和写入视频文件的软件包。 它在这里用于打开视频和提取帧。
In [ ]:
Pkg.add(["Images", "ImageFiltering", "VideoIO"])
In [ ]:
using Images, ImageFiltering, VideoIO

2. RGB到灰度转换函数,该函数使用人眼的标准亮度感知系数,该函数的目的是将彩色图像的一个像素(RGB)转换成其亮度值(灰度)。

  • **pixel::AbstractRGB:**函数参数。 类型注释 ::AbstractRGB 指定函数期望表示RGB像素的对象作为输入。
  • **::Float64:**返回类型的注解表示函数将返回一个双精度浮点数。
    ***公式(0.299 * R + 0.587 * G + 0.114 * B):**这些是标准系数(ITU-R BT。601)用于将彩色图像转换为灰度。 它们考虑到人眼对不同颜色的不同敏感度(绿色被认为是最亮的,蓝色是最暗的)。
  • **red(pixel), green(pixel), blue(pixel):**功能从包 Images.jl,其中从一个像素中提取相应的颜色通道。 返回的值通常归一化为 [0, 1].
In [ ]:
function calculate_brightness(pixel::AbstractRGB)::Float64
    return 0.299 * red(pixel) + 0.587 * green(pixel) + 0.114 * blue(pixel)
end
Out[0]:
calculate_brightness (generic function with 1 method)

3. 能量图计算函数获取彩色图像并返回其能量图。

  • **image::AbstractMatrix{<:AbstractRGB}**输入图像表示为二维数组(矩阵),其中每个元素都是RGB像素。
    ***关键论点 sobel_kernel:**允许您传输自定义卷积内核。 默认情况下,使用Sobel运算符内核。 (Kernel.sobel()[1], Kernel.sobel()[2]),由函数返回 Kernel.sobel()ImageFiltering.jl.
  • **::Matrix{Float64}:**函数返回一个数字矩阵 Float64,这是能量图。
    *公式: energy = sqrt(Gx² + Gy²). 这计算每个点处的梯度向量的量值(模量)。
    *高能量值表示亮度(物体边缘,纹理)的突然变化。
    *低能量值表示平滑过渡或均匀区域(天空,模糊背景)。
In [ ]:
function calculate_energy(image::AbstractMatrix{<:AbstractRGB};
    sobel_kernel::Tuple=(Kernel.sobel()[1], Kernel.sobel()[2]))::Matrix{Float64}
# Конвертируем в grayscale
gray_image = calculate_brightness.(image)
# Вычисляем градиенты по осям X и Y
gradient_x = imfilter(gray_image, sobel_kernel[1])
gradient_y = imfilter(gray_image, sobel_kernel[2])
# Вычисляем энергию как евклидову норму градиентов
energy_map = sqrt.(gradient_x.^2 + gradient_y.^2)
return energy_map
end
Out[0]:
calculate_energy (generic function with 1 method)

4. 脚本的主要部分:视频处理

  • **VideoIO.openvideo("input.mp4"):**打开视频文件进行阅读。
  • **read(video):**从视频中读取下一帧。 由于视频刚刚打开,这将是第一帧。
  • **close(video)**重要的是在读取必要的数据后立即关闭视频文件以释放资源。
  • **size(frame):**输出帧矩阵的维数(例如, (720, 1280) -以像素为单位的高度和宽度)。
  • **eltype(frame):**输出矩阵元素的类型(例如, RGB{N0f8} -RGB像素,其中每个通道由8位无符号归一化数表示 [0, 1]).
In [ ]:
println("Чтение видео...")
println()
video = VideoIO.openvideo("input.mp4")
frame = read(video)
close(video)
println("Размер кадра: ", size(frame))
println("Тип данных: ", eltype(frame))
Чтение видео...

Размер кадра: (720, 1280)
Тип данных: RGB{N0f8}

5. 能量图的计算、归一化和守恒。
**@time:**度量表达式的执行时间和分配内存量的宏 calculate_energy(frame). 用于分析。

In [ ]:
println("Вычисление карты энергии...")
@time energy_result = calculate_energy(frame)
Вычисление карты энергии...
  3.525561 seconds (3.27 M allocations: 231.483 MiB, 2.55% gc time, 211.54% compilation time: 8% of which was recompilation)
Out[0]:
720×1280 Matrix{Float64}:
 0.00207973   0.00346621  0.00249952  0.00285831  …  0.00196078  0.00196078
 0.00346621   0.00346621  0.00353485  0.00421683     0.00196078  0.00196078
 0.00249952   0.00353485  0.00346621  0.00438445     0.00196078  0.00196078
 0.00285831   0.00421683  0.00438445  0.00485269     0.00196078  0.00196078
 0.00373322   0.00415945  0.00421683  0.00490196     0.00196078  0.00196078
 0.00346621   0.00346621  0.00421683  0.00485269  …  0.00196078  0.00196078
 0.00249952   0.00353485  0.00421683  0.00438445     0.00196078  0.00196078
 0.00168833   0.00239457  0.00324651  0.00415548     0.00265611  0.0024771
 0.00168833   0.00307968  0.00445745  0.0051481      0.00385631  0.00248562
 0.00651902   0.00809465  0.00874961  0.00809465     0.00421683  0.00249952
 0.00997693   0.0105391   0.00990548  0.00747741  …  0.00415945  0.00499904
 0.00749628   0.00756774  0.00500523  0.00253093     0.00554594  0.00438445
 0.00690355   0.00562718  0.00345571  0.00304781     0.00485269  0.00285831
 ⋮                                                ⋱              
 0.00639137   0.00970538  0.00970538  0.008504       0.00155014  0.000693242
 0.00438445   0.00825837  0.00837142  0.00853233     0.00155014  0.00196078
 0.00310027   0.00649016  0.00618004  0.00804052  …  0.00207973  0.00196078
 0.00179171   0.00457896  0.00437281  0.00641239     0.00196078  1.4259e-16
 0.0030243    0.0032631   0.00334111  0.00496933     0.00219222  0.00155014
 0.00588235   0.00499904  0.00285831  0.00412068     0.00285831  0.00353485
 0.00588235   0.00499904  0.00310027  0.00367745     0.00373322  0.00353485
 0.00541439   0.00504688  0.00438445  0.00353485  …  0.00438445  0.00219222
 0.0055891    0.0055891   0.00421683  0.00249952     0.00404226  0.000693242
 0.00438445   0.00554594  0.00485269  0.00249952     0.00392157  0.00138648
 0.00285831   0.00490196  0.00504688  0.00207973     0.00404226  0.00285831
 0.000693242  0.00404226  0.00541439  0.00196078     0.00404226  0.00249952

**归一化:**源能量图 energy_result 它具有任意范围的值。 要将其正确保存为图像(其中像素值应在范围内 [0, 1]Float[0, 255] 对于整数),我们将每个元素除以矩阵中的最大值。 这将所有值缩放到一个区间 [0.0, 1.0].

In [ ]:
energy_normalized = energy_result ./ maximum(energy_result)
Out[0]:
720×1280 Matrix{Float64}:
 0.00790444  0.0131741   0.00949995  …  7.786e-16   0.00745238  0.00745238
 0.0131741   0.0131741   0.013435       7.786e-16   0.00745238  0.00745238
 0.00949995  0.013435    0.0131741      7.786e-16   0.00745238  0.00745238
 0.0108636   0.0160269   0.016664       7.786e-16   0.00745238  0.00745238
 0.0141889   0.0158089   0.0160269      7.786e-16   0.00745238  0.00745238
 0.0131741   0.0131741   0.0160269   …  7.786e-16   0.00745238  0.00745238
 0.00949995  0.013435    0.0160269      7.786e-16   0.00745238  0.00745238
 0.00641684  0.00910106  0.012339       0.00187029  0.0100951   0.00941476
 0.00641684  0.011705    0.0169415      0.00941476  0.0146567   0.00944714
 0.0247769   0.0307654   0.0332547      0.0186309   0.0160269   0.00949995
 0.0379194   0.0400562   0.0376479   …  0.0191817   0.0158089   0.0189999
 0.0284912   0.0287628   0.0190234      0.0210785   0.0210785   0.016664
 0.0262384   0.0213873   0.0131342      0.0210785   0.0184437   0.0108636
 ⋮                                   ⋱                          
 0.0242918   0.0368874   0.0368874      0.013435    0.00589162  0.00263481
 0.016664    0.0313877   0.0318174      0.0117832   0.00589162  0.00745238
 0.0117832   0.0246672   0.0234886   …  0.0131741   0.00790444  0.00745238
 0.00680979  0.0174033   0.0166198      0.0131741   0.00745238  5.41943e-16
 0.0114945   0.0124021   0.0126986      0.0108636   0.00833201  0.00589162
 0.0223571   0.0189999   0.0108636      0.00833201  0.0108636   0.013435
 0.0223571   0.0189999   0.0117832      0.00949995  0.0141889   0.013435
 0.0205785   0.0191817   0.016664    …  0.0141889   0.016664    0.00833201
 0.0212425   0.0212425   0.0160269      0.0153635   0.0153635   0.00263481
 0.016664    0.0210785   0.0184437      0.0117832   0.0149048   0.00526963
 0.0108636   0.0186309   0.0191817      0.00790444  0.0153635   0.0108636
 0.00263481  0.0153635   0.0205785      0.00589162  0.0153635   0.00949995

**save:**功能从包 Images.jl 以保存图像。 默认情况下,它理解浮点数的矩阵在范围内 [0,1] 它必须保存为灰度图像。

In [ ]:
save("first_frame_energy_improved.png", energy_normalized)

**load...display**下载新保存的图像并直接在运行时环境中显示以查看结果。

In [ ]:
display(load("first_frame_energy_improved.png"))
No description has been provided for this image

6. 统计输出
**maximum/minimum**显示标准化前能量图上的实际值范围。 它有助于了解数据的动态范围。 接近于零的最小值(8.13e-17),对于同质区域是预期的,并且最大(~0.26)表示最对比的边界的强度。

In [ ]:
println("Максимальное значение энергии: ", maximum(energy_result))
println("Минимальное значение энергии: ", minimum(energy_result))
Максимальное значение энергии: 0.26310858855863645
Минимальное значение энергии: 8.139066063954426e-17

接下来,让我们来看看修改后的算法,它的代码是批量视频处理的优化版本,与之前的脚本完全不同,后者只分析了一帧。 关键区别在于通过创建动画可视化从单个计算过渡到多个帧的流式处理。

操作原理是基于为缓冲区预分配内存gray_buffer, energy_buffer, frame_matrix),这消除了为每帧重新创建数组并显着加快计算速度。 功能 calculate_energy_fast! 用途矢量化操作(@.)和单精度类型(Float32)以优化性能,并且还与外部传输的缓冲区一起工作,避免了昂贵的内存分配。

主循环顺序读取帧,为每个帧计算能量图,创建具有预设可视化设置的热图,并将帧添加到动画中。 结果,形成了GIF动画,该动画清楚地演示了视频的前40帧的能量图随时间变化的动态,与单个帧的静态图像相比,它提供了定性新的分析水平。

In [ ]:
using Images, ImageFiltering, VideoIO

function calculate_energy_fast!(output::Matrix{Float32}, image::AbstractMatrix{<:AbstractRGB}, 
                               gray_buffer::Matrix{Float32}, sobel_x, sobel_y)
    @. gray_buffer = 0.299f0 * red.(image) + 0.587f0 * green.(image) + 0.114f0 * blue.(image)
    gradient_x = imfilter(gray_buffer, sobel_x)
    gradient_y = imfilter(gray_buffer, sobel_y)
    @. output = sqrt(gradient_x^2 + gradient_y^2)
    return output
end

function process_video_energy()
    video = VideoIO.openvideo("input.mp4")
    frame_count = counttotalframes(video)
    anim = Animation()
    sobel_x, sobel_y = Kernel.sobel()
    first_frame = read(video)
    seek(video, 1)
    frame_matrix = collect(first_frame)
    h, w = size(frame_matrix)
    gray_buffer = Matrix{Float32}(undef, h, w)
    energy_buffer = Matrix{Float32}(undef, h, w)
    heatmap_settings = (color = :viridis, aspect_ratio = :equal, axis = false, colorbar = false, size = (600, 400), dpi = 80)
    total_frames = min(40, frame_count)
    println("Создаю GIF...")
    gif_output = "energy_animation.gif"
    for i in 1:total_frames
        frame = read(video)
        copyto!(frame_matrix, frame)
        calculate_energy_fast!(energy_buffer, frame_matrix, gray_buffer, sobel_x, sobel_y)
        plt = heatmap(reverse(energy_buffer); heatmap_settings...)
        Plots.frame(anim)
        if i % 20 == 0
            println("Обработано $i из $total_frames кадров")
        end
    end
    close(video)
    display(gif(anim, gif_output, fps=10))
    println("Анимация сохранена как '$gif_output'")
end
@time process_video_energy()
Создаю GIF...
Обработано 20 из 40 кадров
Обработано 40 из 40 кадров
[ Info: Saved animation to /user/my_projects/Demo/Work/track_contours/energy_animation.gif
No description has been provided for this image
Анимация сохранена как 'energy_animation.gif'
132.895229 seconds (134.93 M allocations: 9.237 GiB, 4.35% gc time, 18.41% compilation time: 12% of which was recompilation)

结论

这些示例是关于现代图像和视频处理的全面教程,揭示了科学环境中编程的几个关键方面。
此示例将检查以下问题:

  1. **调试方法:**首先在缩放之前验证小数据上的算法
  2. **优化计算:**使用预分配,正确的数据类型(Float32),矢量化
  3. **使用内存:**避免循环中不必要的内存分配
  4. **创建可重复的结果:**在处理的所有阶段导出和可视化数据

这些例子展示了科学编程的整个周期-从研究到优化代码,这使它们成为掌握现代多媒体数据处理方法的宝贵材料。