本文是经过严格查阅相关权威文献和资料,形成的专业的可靠的内容。全文数据都有据可依,可回溯。特别申明:数据和资料已获得授权。本文内容,不涉及任何偏颇观点,用中立态度客观事实描述事情本身。_
我想针对以下几个指标,对三个流行的Rust视频处理库(即 ffmpeg-next
、 opencv
、 video-rs
)进行测试:易用性、易修改性以及速度。请注意,我在Rust方面还是个新手,每个实现过程中可能都存在一些“小问题”。
本次任务是提取并保存视频前20秒的视频帧。需要注意的是,这算不上一个很好的速度测试,因为大部分处理过程都是输入输出(IO)操作。不过,它可以让我们对这些库有一定的了解。
计时测试是通过构建发布版本(执行 cargo build -release
命令),
然后运行 time./target/release/binary-name
来对优化后的代码进行准确评估的。
这些测试是在VSCode中进行的,其设置说明和Dockerfile可以在这里找到。为了便于阅读,我将所有的Cargo包都添加到了一个 Cargo.toml
文件中。
[package]
name ="opencv-testing"
version ="0.1.0"
edition ="2021"
[dependencies]
ffmpeg-next="7.0.2"
image ="0.25.1"
ndarray ="0.15.6"
opencv ={ version ="0.92.0","features"=["clang-runtime"]}
video-rs ={ version ="0.8", features =["ndarray"] }
OpenCV
在对Rust版的OpenCV进行测试后,我发现它比Python版的要慢,这挺让人意外的。Python的执行时间是13 - 16秒,而Rust的执行时间是32秒!
起初,我以为可能是编译优化方面的问题,所以我从源代码编译了OpenCV。然而,这并没有很明显的节省时间,我也不确定问题出在哪里。为了内容完整,我把修改过程添加如下。
RUN cd /opt && \
git clone https://github.com/opencv/opencv.git && \
cd opencv && \
git checkout ${OPENCV_VERSION}&& \
mkdir build && \
cd build && \
cmake -D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D WITH_TBB=ON \
-D WITH_OPENMP=ON \
-D ENABLE_FAST_MATH=ON \
-D BUILD_EXAMPLES=OFF \
-D WITH_IPP=ON \
-D WITH_CUDA=OFF \
-D BUILD_opencv_python2=OFF \
-D BUILD_opencv_python3=ON \
-D WITH_FFMPEG=ON \
-D WITH_GSTREAMER=ON \
-D WITH_V4L=ON \
-D WITH_QT=OFF \
-D WITH_OPENGL=ON \
-D OPENCV_GENERATE_PKGCONFIG=ON \
-D OPENCV_ENABLE_NONFREE=ON \
..&& \
make -j$(nproc)&& \
make install && \
ldconfig
不过,如果我使用Tokio并行化输入输出文件保存操作,执行时间就会比Python实现的更快。欢迎大家对此发表见解!
以下是使用OpenCV库提取视频帧并保存的代码:
use std::fs::create_dir_all;
use std::path::Path;
use std::time::Instant;
use opencv::{imgcodecs, prelude::*, videoio,Result};
use opencv::prelude::Mat;
use tokio::task;
#[tokio::main]
asyncfnmain()->Result<()>{
letwindow="video capture";
letvideo_url="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
letmut cam= videoio::VideoCapture::from_file(video_url, videoio::CAP_ANY)?;// 0 is the default camera
letopened= videoio::VideoCapture::is_opened(&cam)?;
if!opened {
panic!("Unable to open default camera!");
}
letoutput_folder="frames_opencv";
create_dir_all(output_folder).expect("failed to create output directory");
letframe_rate= cam.get(videoio::CAP_PROP_FPS)?;// Get the frame rate of the video
letmax_duration=20.0;// Max duration in seconds
letmax_frames=(frame_rate * max_duration).ceil()asusize;
letmut frame_count=0;
letmut tasks=vec![];
while frame_count < max_frames {
letmut frame=Mat::default();
cam.read(&mut frame)?;
if frame.size()?.width >0{
letframe_path=format!("{}/frame_{:05}.png", output_folder, frame_count);
letframe_clone= frame.clone();// Clone the frame to move into the async block
// Spawn a blocking task to save the frame
lettask= task::spawn_blocking(move||{
imgcodecs::imwrite(&frame_path,&frame_clone,&opencv::types::VectorOfi32::new()).expect("failed to save frame");
});
tasks.push(task);
frame_count +=1;
}
}
// Await all tasks to finish
fortaskin tasks {
task.await.expect("task failed");
}
println!("Saved {} frames in the '{}' directory", frame_count, output_folder);
Ok(())
}
经过这样的修改后,执行时间为6秒。
Video-RS
video-rs
是一个用于Rust的通用视频库,它使用了来自FFMPEG的libav系列库。它旨在为许多常见的视频任务(如读取、写入、复用、编码和解码)提供一个稳定且符合Rust风格的接口。
为了让对比相对合理(尽管它们各有特点,就好比不同的水果一样),这里同样会使用Tokio来进行并行输入输出操作。
以下是使用 video-rs
库提取视频帧并保存的代码:
use std::fs::create_dir_all;
use std::path::Path;
use std::error::Error;
use video_rs::decode::Decoder;
use video_rs::Url;
use image::{ImageBuffer,Rgb};
use tokio::task;
#[tokio::main]
asyncfnmain()->Result<(),Box<dynError>>{
video_rs::init().unwrap();
letsource=
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
.parse::<Url>()
.unwrap();
letmut decoder=Decoder::new(source).expect("failed to create decoder");
letoutput_folder="frames_video_rs";
create_dir_all(output_folder).expect("failed to create output directory");
let(width, height)= decoder.size();
letframe_rate= decoder.frame_rate();// Assuming 30 FPS if not available
letmax_duration=20.0;// Max duration in seconds
letmax_frames=(frame_rate * max_duration).ceil()asusize;
letmut frame_count=0;
letmut elapsed_time=0.0;
letmut tasks=vec![];
forframein decoder.decode_iter(){
ifletOk((_timestamp, frame))= frame {
if elapsed_time > max_duration {
break;
}
letrgb= frame.slice(ndarray::s![..,..,0..3]).to_slice().unwrap();
letimg:ImageBuffer<Rgb<u8>,Vec<u8>>=ImageBuffer::from_raw(width, height, rgb.to_vec())
.expect("failed to create image buffer");
letframe_path=format!("{}/frame_{:05}.png", output_folder, frame_count);
lettask= task::spawn_blocking(move||{
img.save(&frame_path).expect("failed to save frame");
});
tasks.push(task);
frame_count +=1;
elapsed_time +=1.0/ frame_rate;
}else{
break;
}
}
// Await all tasks to finish
fortaskin tasks {
task.await.expect("task failed");
}
println!("Saved {} frames in the '{}' directory", frame_count, output_folder);
Ok(())
}
这段代码看起来相当直观,也就是从解码器获取帧,将帧切片为一个 ndarray
数组,然后使用图像库保存图像。执行时间为2 - 4秒!这确实是一种改进!
FFMPEG-Next
好吧,我甚至都不太确定该从哪里开始对这个库进行并行化处理。它的代码量明显比另外两个库多得多。我试着像前面两个示例那样使用 spawn_blocking
方法,但没能成功实现。
以下是使用 ffmpeg-next
库提取视频帧并保存的代码:
use ffmpeg::format::{input,Pixel};
use ffmpeg::media::Type;
use ffmpeg::software::scaling::{context::Context, flag::Flags};
use ffmpeg::util::frame::video::Video;
use std::fs::{self,File};
use std::path::Path;
fnmain()->Result<(),Box<dyn std::error::Error>>{
ffmpeg::init().unwrap();
letsource_url="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
letframes_folder="frames_ffmpeg";
// Create the frames folder if it does not exist
if!Path::new(frames_folder).exists(){
fs::create_dir(frames_folder)?;
}
// Open the input video file
letmut ictx=input(&source_url)?;
// Find the best video stream
letinput_stream= ictx
.streams()
.best(Type::Video)
.ok_or(ffmpeg::Error::StreamNotFound)?;
letframerate_q= input_stream.avg_frame_rate();
letframerate_f64= framerate_q.numerator()asf64/ framerate_q.denominator()asf64;
letmax_duration=20.0;// Max duration in seconds
letmax_frames=(framerate_f64 * max_duration).ceil()asusize;
// Get the index of the video stream
letinput_video_stream_index= input_stream.index();
// Set up the video decoder
letcontext_decoder= ffmpeg::codec::context::Context::from_parameters(input_stream.parameters())?;
letmut video_decoder= context_decoder.decoder().video()?;
// Set up the scaler to convert frames to RGB24
letmut scaler=Context::get(
video_decoder.format(),
video_decoder.width(),
video_decoder.height(),
Pixel::RGB24,
video_decoder.width(),
video_decoder.height(),
Flags::BILINEAR,
)?;
letmut frame_index=0;
// Function to receive and process decoded frames
letmut receive_and_process_decoded_frames=|decoder:&mut ffmpeg::decoder::Video, frame_index:&mutusize|->Result<(), ffmpeg::Error>{
letmut decoded=Video::empty();
while decoder.receive_frame(&mut decoded).is_ok(){
letmut rgb_frame=Video::empty();
scaler.run(&decoded,&mut rgb_frame)?;
save_file(&rgb_frame,*frame_index, frames_folder).unwrap();
*frame_index +=1;
}
Ok(())
};
// Process packets from the input video stream
for(stream, packet)in ictx.packets(){
if frame_index > max_frames {
break;
}
if stream.index()== input_video_stream_index {
video_decoder.send_packet(&packet)?;
receive_and_process_decoded_frames(&mut video_decoder,&mut frame_index)?;
}
}
// Finalize decoding
video_decoder.send_eof()?;
receive_and_process_decoded_frames(&mut video_decoder,&mut frame_index)?;
Ok(())
}
// Function to save a frame as a PNG file
fnsave_file(frame:&Video, index:usize, folder:&str)-> image::ImageResult<()>{
letfilename=format!("{}/frame{}.png", folder, index);
letpath=Path::new(&filename);
letmut _file=File::create(path)?;
let(width, height)=(frame.width(), frame.height());
letbuffer= frame.data(0);
// Create an ImageBuffer from the raw frame data
letimg_buffer= image::ImageBuffer::from_raw(width, height, buffer.to_vec()).unwrap();
letimg= image::DynamicImage::ImageRgb8(img_buffer);
img.save(&filename)?;
Ok(())
}
那我试着尽力弄清楚这里面的情况。这里有需要循环处理的数据包,还有一个用于解码帧并保存它的闭包。我可能需要也可能不需要这个缩放器。在未进行并行化处理时,执行时间是10秒。这里可能还有很大的优化空间。
结论
总体而言,我认为对于视频处理来说, video-rs
是我的首选。它最容易上手运行,语法看起来最简洁,而且在本次任务中速度也是最快的。遗憾的是,我觉得 FFMPEG-Next
不是一个好的选择,除非你想深入研究后端视频处理的细节内容。OpenCV虽然由于某些原因速度有点慢,但对于复杂任务来说可能是个更好的选项。由于上面的示例大多是输入输出操作,所以在某些任务(比如计算机视觉相关任务)中,Rust版的OpenCV仍有可能比Python版的OpenCV快得多。
以上就是我的分享。这些分析皆源自我的个人经验,希望上面分享的这些东西对大家有帮助,感谢大家!