作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
对于开发者来说,视频及其所有移动部件都是一件很棘手的事情. 一个专业的开发人员 对数据结构有深刻的理解, 编码技术, 而图像和信号处理在所谓简单的结果中起着重要作用, 日常视频处理任务如 压缩或编辑.
有效地处理视频内容, 您必须了解其主要文件格式(如.g., .mp4, .mov, .wmv, .Avi)及其版本特定的编解码器(例如.g., H.264, H.265, vp8, vp9). 有效视频处理所需的工具很少被整齐地打包成综合库, 让开发人员在广阔的空间中摸索, 复杂的开源工具生态系统,提供引人入胜的计算机视觉应用程序.
计算机视觉应用是基于一系列技术的实现-从简单的启发式到 复杂神经网络-通过这种方式,我们将图像或视频作为输入输入计算机并产生有意义的输出,例如:
前面的例子完全不同, 每个都展示了一个完全不同的功能, 但有一个简单的共同点: 图像是它们的主要输入. 每个应用程序都将非结构化(有时是混乱的)图像或帧转换为可理解的有序数据,从而为最终用户提供好处.
观看视频的最终用户可能会将其视为单个实体. 但是开发人员必须将其作为独立的、连续的框架的集合来处理. 例如, 在工程师编写程序来检测移动车辆视频中的实时交通模式之前, 他们必须首先从视频中提取单个帧, 然后应用一种算法来检测路上的车辆.
在原始状态下, 视频文件的大小非常大, 使其太大而无法存储在计算机内存中, 对于开发人员来说难以处理, 难以分享, 而且储存成本很高. 一分钟60帧/秒(fps)的原始画面, 未压缩的视频需要超过22gb的存储空间, 例如:
60 秒* 1080 px(高度) * 1920 Px(宽度)* 3 每像素字节数* 60 fps = 22.39 GB
因此,视频在被处理之前被压缩,这是理所当然的. 但是不能保证单个压缩视频帧将完整地显示图像. 这是因为在压缩时应用的参数定义了视频的单个帧将保留的质量和细节. 而压缩视频, 作为一个整体, 可能打得足够好,提供一个极好的观看体验, 这并不等同于构成它的单个帧可以被解释为完整的图像.
在本教程中, 我们将使用流行的开源计算机视觉工具来解决视频处理的一些基本挑战. 这种经验将使您能够为您的确切用例定制计算机视觉管道. (为了简单起见,我们不会在本文中描述视频的音频组件.)
为了提供计算机视觉应用程序 工程团队 开发并实现了一个高效、强大的计算机视觉管道,其体系结构包括, 至少:
步骤1:图像采集 | 图像或视频可以从广泛的来源获得, 包括摄像头或传感器, 存储在磁盘上的数字视频, 或者在互联网上播放视频. |
第二步:图像预处理 | 开发人员选择预处理操作, 比如去噪, 调整, 或者转换成更易于访问的格式. 这样做的目的是使图像更容易处理或分析. |
步骤3:特征提取 | 在表示或提取步骤中, 捕获预处理图像或帧中的信息. 例如,这些信息可以由边、角或形状组成. |
步骤4:解释、分析或输出 | 在最后一步,我们完成手头的任务. |
让我们想象一下,你被雇佣来构建一个计算视频各个帧亮度的工具. 我们将调整项目的管道架构以匹配上面共享的简单计算机视觉模型.
我们将在本教程中生成的程序已作为一个 以hypertrigger为例, 一个我开发的开源Rust库. hypertrigger包含了在互联网流媒体视频上运行计算机视觉管道所需的一切: TensorFlow 图像识别的绑定; 超正方体 用于光学字符识别, 支持使用gpu加速视频解码,速度提升10倍. 要安装,请克隆 Hypetrigger回购 然后运行命令 货物添加超级触发器
.
最大限度地学习和获得经验, 在本教程中,我们将从头开始构建计算机视觉管道, 而不是实现用户友好的hypertrigger.
对于我们的项目,我们将使用:
Tool | 描述 |
---|---|
它被吹捧为处理视频的最佳工具之一, FFmpeg视频中的瑞士军刀是一个用C语言编写的开源库,用于编码, 解码, 转换, 和流媒体. 它被用于企业软件,如 谷歌Chrome, VLC媒体播放器, and 开放广播软件(OBS)除其他外. FFmpeg可以作为可执行命令行工具或源代码库下载, 并且可以与任何可以产生子进程的语言一起使用. | |
Rust的一个主要优点是它检测内存错误的能力.g.(如空指针、段错误、悬空引用). Rust在保证内存安全的同时提供高性能, 而且性能也很好, 使其成为视频处理的良好选择. |
在此场景中,先前获得的 动画示例视频 准备好要处理了吗.
在这个项目中,图像预处理包括将视频从H.264编码格式为raw RGB,这是一种更容易使用的格式.
让我们使用FFmpeg的便携式工具来解压缩视频, 可执行的命令行工具,从一个Rust程序. Rust程序将打开并将我们的示例视频转换为RGB. 为了获得最佳结果,我们将追加适当的 FFmpeg语法 to the ffmpeg
命令:
观点* | 描述 | 用例 |
---|---|---|
-i | 源视频的文件名或URL. |
示例视频的URL 或者其他你选择的 |
-f | 设置输出格式. | The rawvideo 格式获取原始视频帧 |
-pix_fmt | 设置像素格式. | rgb24 生成每个通道8位的RGB颜色通道 |
-r | 设置输出帧率. | 1 每秒生成一帧 |
| Tells FFmpeg where to send output; it is a required final argument. | 标识符 pipe:1 输出 标准输出 而不是去归档 |
*输入参数的完整屏幕列表 ffmpeg -
.
这些参数在命令行或终端结合在一起给我们 Ffmpeg -i input_video.Mp4 -f rawvideo -pix_fmt rgb24 pipe
并作为我们处理视频帧的起点:
使用std:: {
io:: {BufReader,阅读},
过程::{命令,Stdio},
};
Fn main() {
//测试视频由http://gist提供.github.com/jsturgis/3b19447b304616f18657.
让test_video =
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
// Video is in RGB format; 3 bytes per pixel (1 red, 1 blue, 1 green).
让bytes_per_pixel = 3;
让video_width = 1280;
让video_height = 720;
//使用指定的参数创建一个FFmpeg命令.
let mut ffmpeg = Command::new("ffmpeg")
.arg(“我”)
.arg(test_video) //指定输入视频
.arg("-f") //指定输出格式(原始RGB像素)
.arg(“rawvideo”)
.arg(“-pix_fmt”)
.arg("rgb24") //指定像素格式(RGB,每通道8位)
.arg (- r)
.arg("1") //请求速率为每秒1帧
.arg("pipe:1") //发送输出到标准输出管道
.stderr (Stdio: null ())
.stdout (Stdio::管道())
.spawn() //生成命令进程
.打开(); // Unwrap the result (i.e.,如果出现错误,请panic并退出)
}
我们的程序将每次接收一个视频帧,每个帧都解码为原始RGB. 为了避免积累大量的数据, 让我们分配一个帧大小的缓冲区,它将在完成处理每个帧时释放内存. 让我们添加一个循环,用来自FFmpeg标准输出通道的数据填充缓冲区:
Fn main() {
// …
//将视频输出读入缓冲区.
让stdout = ffmpeg.stdout.带().打开();
让buf_size = video_width * video_height * bytes_per_pixel;
let mut reader = buffreader::new(stdout);
让mut buffer = vec![0u8; buf_size];
让mut frame_num = 0;
while让Ok(()) = reader.read_exact(缓冲.as_mut_slice ()) {
//检索每个视频帧作为原始RGB像素的矢量.
设raw_rgb = buffer.克隆();
}
}
注意 while
的引用 raw_rgb
,一个包含完整RGB图像的变量.
计算第2步预处理的每帧的平均亮度, 让我们将以下函数添加到程序中(在 main
方法):
///计算图像的平均亮度
///返回0到1之间的浮点数.
fn average_brightness(raw_rgb: Vec) -> f64 {
让mut sum = 0.0;
For (i, _) in raw_rgb.iter ().enumerate ().step_by (3) {
Let r = raw_rgb[i] as f64;
设g = raw_rgb[i + 1] as f64;
设b = raw_rgb[i + 2] as f64;
设pixel_亮度= (r / 255).0 + g / 255.0 + b / 255.0) / 3.0;
Sum += pixel_亮度;
}
Sum / (raw_rgb.Len () = f64 / 3.0)
}
然后,在最后 while
循环,我们可以计算帧的亮度并打印到控制台:
Fn main() {
// …
while让Ok(()) = reader.read_exact(缓冲.as_mut_slice ()) {
//检索每个视频帧作为原始RGB像素的矢量.
设raw_rgb = buffer.克隆();
//计算帧的平均亮度.
设亮度= average_亮度(raw_rgb);
println!("frame {frame_num}具有亮度{brightness}");
Frame_num += 1;
}
}
此时,代码将与此匹配 示例文件.
现在我们在示例视频上运行该程序,产生以下输出:
帧0的亮度为0.055048076377046
帧1的亮度为0.467577447011064
帧2的亮度为0.878193112575386
帧3的亮度为0.859071674156269
帧4的亮度为0.820603467400872
帧5的亮度为0.766673757205845
帧6的亮度为0.717223347005918
帧7的亮度为0.674823835783496
帧8的亮度为0.656084418402863
帧9的亮度为0.656437488652946
[省略了500多帧]
以下是这些数字的图表:
在前面的图表中,请注意表示视频亮度的标线. 它的尖峰和低谷代表了连续帧之间亮度的戏剧性转变. 第0帧的亮度,如图最左边所示,测量值为5% (i).e.,相当暗),并在87% (i.e.(非常明亮),仅仅两帧之后. 同样显著的转变发生在视频的5点、8点和9点40分左右. 在这种情况下, 如此强烈的亮度变化代表了正常的电影场景过渡, 如视频所示.
在现实世界中, 我们可能会继续分析探测到的亮度, 有条件地, 触发一个动作. 在真正的后期处理, 导演, 电视录像制作人, 或者视频编辑器会分析这些数据,并保留所有亮度值在项目商定范围内的帧. 另外, 专业人员可能会拉出并检查亮度值不确定的框架, 最终可能会批准, 重新呈现, 或者从视频的最终输出中排除单个帧.
分析帧亮度的另一个有趣用例可以通过考虑一个涉及办公楼安全摄像机镜头的场景来说明. 通过将框架的亮度水平与建筑物的进出日志进行比较, 我们可以确定最后一个离开的人是否真的像他们应该的那样关灯. 如果我们的分析表明,在所有人下班后,灯还开着, 我们可以发送提醒,鼓励人们在离开时关灯,以节约能源.
本教程详细介绍了一些基本的计算机视觉处理,并为更高级的技术奠定了基础, 如绘制输入视频的多个特征来关联使用更多 先进的统计措施. 这样的分析标志着从视频世界到统计推断和机器学习领域的跨越,这是计算机视觉的本质.
通过遵循本教程中列出的步骤并利用所提供的工具, 您可以将我们通常与解压缩视频和解释RGB像素相关联的障碍(大文件大小或复杂的视频格式)最小化. 当你简化了视频和计算机视觉的工作, 您可以更好地专注于重要的事情:交付智能和健壮的产品 视频功能 在您的应用程序中.
Toptal工程博客的编辑团队向 马丁·戈德堡 查看本文中提供的代码示例和其他技术内容.
计算机视觉的工作原理是模仿人类大脑识别视觉信息的能力.
计算机视觉将图像或视频作为输入,并从输入的某些特征或方面提取有意义的解释.
计算机视觉在当今的应用领域非常广泛, 包括自动驾驶汽车, 手机支票网上银行存款, 以及用于在社交媒体上添加标签的面部识别.
框架的最佳选择在很大程度上取决于您试图从视觉输入中提取什么信息. 有一些通用的库(例如.g.(Python OpenCV),涵盖了广泛的用例.
NLP(自然语言处理)关注的是从文本输入中提取信息. 另一方面,计算机视觉从图像或视频等视觉输入中提取信息. 您可以通过扫描图像来识别要与NLP框架共享的文本字符的形状,从而将NLP和计算机视觉结合起来.
尽管它的基础是复杂的,涉及到大量的数学, 在实践中使用计算机视觉并不需要比典型的软件工程更多的数学知识或使用. 开源工具的大量生态系统意味着您可以利用计算机视觉技术,而无需理解底层数学.
世界级的文章,每周发一次.
世界级的文章,每周发一次.