OpenCV视频处理全解:从读取到处理再到写入
1. 视频读取基础
在OpenCV中,读取视频有多种方式。可以使用重载的读取操作符
capture >> frame;
,也能调用基本方法
capture.grab();
和
capture.retrieve(frame);
。
为了让视频按合适的速度显示,我们使用
cv::waitKey
函数引入延迟。延迟时间通常根据视频帧率来设置,如果帧率为
fps
,那么两帧之间的延迟(毫秒)就是
1/fps
。你可以调整这个值来改变视频播放速度。若要确保窗口有足够时间刷新,插入延迟是很重要的,因为窗口刷新是低优先级操作,CPU繁忙时可能无法刷新。
cv::waitKey
还能通过按键中断读取过程,按下任意键后,函数会返回该键的ASCII码。若指定延迟为0,它会无限期等待用户按键,这在逐帧检查处理结果时很有用。
最后,使用
release
方法可以关闭视频文件,但由于
cv::VideoCapture
析构函数也会调用该方法,所以这一步并非必需。
需要注意的是,要打开指定的视频文件,计算机必须安装相应的编解码器,否则
cv::VideoCapture
无法解码输入文件。通常,如果能在机器上用视频播放器(如Windows Media Player)打开视频文件,OpenCV 也应该能读取该文件。
除了本地视频文件,还可以读取连接到计算机的摄像头(如USB摄像头)产生的视频流。此时,只需在
open
函数中指定一个ID号(整数),指定ID为0将打开默认安装的摄像头。由于摄像头的视频流会无限读取,
cv::waitKey
函数在停止处理方面的作用就变得至关重要。
此外,还能从网络加载视频,例如:
cv::VideoCapture capture("http://www.laganiere.name/bike.avi");
下面是视频读取的操作步骤总结:
1. 选择读取方式:可以使用重载操作符或基本方法。
2. 设置延迟:根据视频帧率设置
cv::waitKey
的延迟时间。
3. 考虑编解码器:确保计算机安装了相应编解码器。
4. 选择输入源:可以是本地文件、摄像头或网络视频。
2. 视频帧处理
我们的目标是对视频序列的每一帧应用处理函数。为此,将OpenCV视频捕获框架封装到自定义的
VideoProcessor
类中。
首先,需要定义一个处理函数(回调函数),该函数接收一个
cv::Mat
实例并输出处理后的帧。有效的回调函数签名如下:
void processFrame(cv::Mat& img, cv::Mat& out);
以计算输入图像的Canny边缘为例,简单的处理函数如下:
void canny(cv::Mat& img, cv::Mat& out) {
// Convert to gray
if (img.channels()==3)
cv::cvtColor(img,out, cv::COLOR_BGR2GRAY);
// Compute Canny edges
cv::Canny(out,out,100,200);
// Invert the image
cv::threshold(out,out,128,255,cv::THRESH_BINARY_INV);
}
使用
VideoProcessor
类处理视频的步骤如下:
// Create instance
VideoProcessor processor;
// Open video file
processor.setInput("bike.avi");
// Declare a window to display the video
processor.displayInput("Current Frame");
processor.displayOutput("Output Frame");
// Play the video at the original frame rate
processor.setDelay(1000./processor.getFrameRate());
// Set the frame processor callback function
processor.setFrameProcessor(canny);
// Start the process
processor.run();
VideoProcessor
类包含多个成员变量来控制视频帧处理的各个方面:
class VideoProcessor {
private:
// the OpenCV video capture object
cv::VideoCapture capture;
// the callback function to be called
// for the processing of each frame
void (*process)(cv::Mat&, cv::Mat&);
// a bool to determine if the
// process callback will be called
bool callIt;
// Input display window name
std::string windowNameInput;
// Output display window name
std::string windowNameOutput;
// delay between each frame processing
int delay;
// number of processed frames
long fnumber;
// stop at this frame number
long frameToStop;
// to stop the processing
bool stop;
};
以下是该类的一些重要方法:
-
setFrameProcessor
:设置回调函数。
void setFrameProcessor(void (*frameProcessingCallback)(cv::Mat&, cv::Mat&)) {
process= frameProcessingCallback;
}
-
setInput:打开视频文件。
bool setInput(std::string filename) {
fnumber= 0;
capture.release();
return capture.open(filename);
}
-
displayInput和displayOutput:创建显示窗口。
void displayInput(std::string wn) {
windowNameInput= wn;
cv::namedWindow(windowNameInput);
}
void displayOutput(std::string wn) {
windowNameOutput= wn;
cv::namedWindow(windowNameOutput);
}
-
run:包含帧提取循环。
void run() {
cv::Mat frame;
cv::Mat output;
if (!isOpened())
return;
stop= false;
while (!isStopped()) {
if (!readNextFrame(frame))
break;
if (windowNameInput.length()!=0)
cv::imshow(windowNameInput,frame);
if (callIt) {
process(frame, output);
fnumber++;
}
else {
output= frame;
}
if (windowNameOutput.length()!=0)
cv::imshow(windowNameOutput,output);
if (delay>=0 && cv::waitKey(delay)>=0)
stopIt();
if (frameToStop>=0 && getFrameNumber()==frameToStop)
stopIt();
}
}
还可以通过
callProcess
和
dontCallProcess
方法指定是否调用回调函数,以及使用
stopAtFrameNo
方法指定在某一帧停止处理。
3. 扩展功能
3.1 处理图像序列
有时输入序列是由一系列单独存储在不同文件中的图像组成。可以对
VideoProcessor
类进行修改以适应这种输入。添加成员变量来保存图像文件名向量及其迭代器:
std::vector<std::string> images;
std::vector<std::string>::const_iterator itImg;
新增
setInput
方法指定要读取的文件名:
bool setInput(const std::vector<std::string>& imgs) {
fnumber= 0;
capture.release();
images= imgs;
itImg= images.begin();
return true;
}
修改
isOpened
方法:
bool isOpened() {
return capture.isOpened() || !images.empty();
}
修改
readNextFrame
方法,根据输入源读取帧:
bool readNextFrame(cv::Mat& frame) {
if (images.size()==0)
return capture.read(frame);
else {
if (itImg != images.end()) {
frame= cv::imread(*itImg);
itImg++;
return frame.data != 0;
} else
return false;
}
}
3.2 使用帧处理器类
在面向对象的环境中,使用帧处理类可能更有意义。可以定义一个接口,任何希望在
VideoProcessor
中使用的类都需要实现该接口:
class FrameProcessor {
public:
virtual void process(cv:: Mat &input, cv:: Mat &output)= 0;
};
添加一个
setFrameProcessor
方法来输入
FrameProcessor
实例:
void setFrameProcessor(FrameProcessor* frameProcessorPtr) {
process= 0;
frameProcessor= frameProcessorPtr;
callProcess();
}
修改
run
方法的
while
循环以考虑这种变化:
while (!isStopped()) {
if (!readNextFrame(frame))
break;
if (windowNameInput.length()!=0)
cv::imshow(windowNameInput,frame);
if (callIt) {
if (process)
process(frame, output);
else if (frameProcessor)
frameProcessor->process(frame,output);
fnumber++;
}
else {
output= frame;
}
if (windowNameOutput.length()!=0)
cv::imshow(windowNameOutput,output);
if (delay>=0 && cv::waitKey(delay)>=0)
stopIt();
if (frameToStop>=0 && getFrameNumber()==frameToStop)
stopIt();
}
4. 视频写入
在OpenCV中,使用
cv::VideoWriter
类来写入视频文件。创建实例时,需要指定文件名、视频帧率、帧大小以及是否为彩色视频:
writer.open(outputFile, // filename
codec, // codec to be used
framerate, // frame rate of the video
frameSize, // frame size
isColor); // color video?
打开视频文件后,通过重复调用
write
方法添加帧:
writer.write(frame);
可以扩展
VideoProcessor
类以具备写入视频文件的能力。示例代码如下:
VideoProcessor processor;
processor.setInput("bike.avi");
processor.setFrameProcessor(canny);
processor.setOutput("bikeOut.avi");
processor.run();
还可以将输出帧保存为单独的图像文件,采用前缀名加数字的命名约定:
processor.setOutput("bikeOut", //prefix
".jpg", // extension
3, // number of digits
0); // starting index
为了实现这些功能,需要对
VideoProcessor
类进行修改。添加成员变量:
class VideoProcessor {
private:
cv::VideoWriter writer;
std::string outputFile;
int currentIndex;
int digits;
std::string extension;
};
添加
setOutput
方法来指定输出视频文件:
bool setOutput(const std::string &filename, int codec=0,
double framerate=0.0, bool isColor=true) {
outputFile= filename;
extension.clear();
if (framerate==0.0)
framerate= getFrameRate();
char c[4];
if (codec==0) {
codec= getCodec(c);
}
return writer.open(outputFile, codec, framerate, getFrameSize(), isColor);
}
添加
writeNextFrame
方法处理帧写入:
void writeNextFrame(cv::Mat& frame) {
if (extension.length()) {
std::stringstream ss;
ss << outputFile << std::setfill('0')
<< std::setw(digits) << currentIndex++ << extension;
cv::imwrite(ss.str(),frame);
} else {
writer.write(frame);
}
}
添加另一个
setOutput
方法用于保存图像文件:
bool setOutput(const std::string &filename, const std::string &ext,
int numberOfDigits=3, int startIndex=0) {
if (numberOfDigits<0)
return false;
outputFile= filename;
extension= ext;
digits= numberOfDigits;
currentIndex= startIndex;
return true;
}
在
run
方法的视频捕获循环中添加写入步骤:
while (!isStopped()) {
if (!readNextFrame(frame))
break;
if (windowNameInput.length()!=0)
cv::imshow(windowNameInput,frame);
if (callIt) {
if (process)
process(frame, output);
else if (frameProcessor)
frameProcessor->process(frame,output);
fnumber++;
} else {
output= frame;
}
if (outputFile.length()!=0)
writeNextFrame(output);
if (windowNameOutput.length()!=0)
cv::imshow(windowNameOutput,output);
if (delay>=0 && cv::waitKey(delay)>=0)
stopIt();
if (frameToStop>=0 && getFrameNumber()==frameToStop)
stopIt();
}
5. 编解码器相关
当将视频写入文件时,会使用编解码器来保存。编解码器是能够对视频流进行编码和解码的软件模块,它定义了文件格式和用于存储信息的压缩方案。显然,使用特定编解码器编码的视频必须使用相同的编解码器进行解码。因此,引入了四字符代码来唯一标识编解码器,软件工具在写入视频文件时,通过读取这些代码来确定要使用的编解码器。
视频处理流程总结
graph TD;
A[选择输入源] --> B[读取视频帧];
B --> C{是否需要处理};
C -- 是 --> D[应用处理函数];
C -- 否 --> E[直接输出];
D --> F[输出处理后帧];
E --> F;
F --> G{是否保存输出};
G -- 是 --> H[写入视频文件或保存为图像];
G -- 否 --> I[结束];
H --> I;
通过以上步骤和方法,你可以在OpenCV中完成从视频读取、处理到写入的整个流程,并且可以根据实际需求进行灵活调整和扩展。
OpenCV视频处理全解:从读取到处理再到写入
6. 编解码器选择与常见问题
在选择编解码器时,需要考虑多个因素,如文件大小、兼容性和视频质量。不同的编解码器适用于不同的场景。以下是一些常见的编解码器及其特点:
| 编解码器 | 四字符代码 | 特点 |
| — | — | — |
| MPEG-4 | XVID | 广泛支持,压缩比高,文件大小适中,视频质量较好,适用于大多数情况。 |
| H.264 | avc1 | 高质量视频压缩,广泛用于网络视频和高清视频,兼容性强,但编码速度可能较慢。 |
| MPEG-1 | PIM1 | 简单的编码格式,兼容性好,但压缩比低,文件较大,适用于旧设备或对文件大小要求不高的情况。 |
在使用
cv::VideoWriter
时,如果遇到无法打开视频文件的问题,可能是由于编解码器不支持或未安装。可以尝试更换编解码器,或者确保系统中安装了相应的编解码器。另外,如果视频文件无法正常播放,可能是因为编码过程中出现错误,需要检查代码逻辑和编解码器参数。
7. 性能优化
在处理大量视频数据时,性能优化至关重要。以下是一些可以提高视频处理性能的方法:
-
减少不必要的处理
:在
run
方法中,检查是否真的需要对每一帧进行处理。如果某些帧不需要处理,可以跳过处理步骤,直接输出。
if (callIt && shouldProcessFrame()) {
if (process)
process(frame, output);
else if (frameProcessor)
frameProcessor->process(frame,output);
fnumber++;
}
- 并行处理 :对于多核处理器,可以使用并行计算库(如OpenMP)来并行处理视频帧,提高处理速度。
#include <omp.h>
void runParallel() {
cv::Mat frame;
cv::Mat output;
if (!isOpened())
return;
stop= false;
#pragma omp parallel
{
while (!isStopped()) {
#pragma omp critical
{
if (!readNextFrame(frame))
break;
}
if (callIt) {
if (process)
process(frame, output);
else if (frameProcessor)
frameProcessor->process(frame,output);
#pragma omp critical
{
fnumber++;
}
}
else {
output= frame;
}
if (windowNameOutput.length()!=0)
cv::imshow(windowNameOutput,output);
if (delay>=0 && cv::waitKey(delay)>=0)
stopIt();
if (frameToStop>=0 && getFrameNumber()==frameToStop)
stopIt();
}
}
}
- 优化算法 :选择高效的算法进行视频处理,避免使用复杂度高的算法。例如,在计算Canny边缘时,可以调整阈值参数,减少不必要的计算。
8. 实际应用案例
8.1 视频监控系统
在视频监控系统中,需要实时读取摄像头的视频流,对视频帧进行处理,如检测运动目标、识别物体等。可以使用
VideoProcessor
类来实现这个功能。
// 创建实例
VideoProcessor processor;
// 打开摄像头
processor.setInput(0);
// 设置帧处理函数,如运动检测函数
processor.setFrameProcessor(motionDetection);
// 设置输出视频文件
processor.setOutput("monitoring.avi");
// 开始处理
processor.run();
在这个案例中,
motionDetection
函数用于检测视频帧中的运动目标,并在输出视频中标记出来。
8.2 视频编辑
在视频编辑中,需要对视频进行裁剪、拼接、添加特效等操作。可以使用
VideoProcessor
类读取视频帧,应用相应的处理函数,然后将处理后的帧写入新的视频文件。
// 创建实例
VideoProcessor processor;
// 打开输入视频文件
processor.setInput("inputVideo.avi");
// 设置帧处理函数,如添加特效函数
processor.setFrameProcessor(addEffects);
// 设置输出视频文件
processor.setOutput("outputVideo.avi");
// 开始处理
processor.run();
在这个案例中,
addEffects
函数用于为视频帧添加特效,如模糊、色彩调整等。
9. 总结与展望
通过本文的介绍,我们学习了在OpenCV中进行视频处理的完整流程,包括视频读取、帧处理和视频写入。使用
VideoProcessor
类可以方便地封装视频处理任务,并且可以根据需要进行扩展和定制。
未来,随着计算机视觉技术的不断发展,视频处理的应用场景将越来越广泛。例如,在自动驾驶领域,需要对车载摄像头的视频进行实时处理,以识别道路、车辆和行人;在虚拟现实和增强现实领域,需要对视频进行实时渲染和处理,以提供更加逼真的体验。因此,掌握视频处理技术将变得越来越重要。
同时,我们也可以进一步探索视频处理的新技术和新方法,如深度学习在视频处理中的应用。通过使用深度学习模型,可以实现更复杂的视频处理任务,如视频分类、目标跟踪和视频生成等。
视频处理关键步骤总结
| 步骤 | 操作 | 代码示例 |
|---|---|---|
| 读取视频 | 选择输入源(文件、摄像头、网络),设置延迟 |
cv::VideoCapture capture("video.avi"); capture >> frame; cv::waitKey(delay);
|
| 处理视频帧 | 定义处理函数,设置回调函数 |
void processFrame(cv::Mat& img, cv::Mat& out); processor.setFrameProcessor(processFrame);
|
| 写入视频 |
创建
cv::VideoWriter
实例,指定参数,写入帧
|
cv::VideoWriter writer; writer.open(outputFile, codec, framerate, frameSize, isColor); writer.write(frame);
|
视频处理整体流程
graph LR;
A[初始化VideoProcessor] --> B[设置输入源];
B --> C[设置处理函数];
C --> D[设置输出方式];
D --> E[开始处理];
E --> F{是否结束};
F -- 否 --> B;
F -- 是 --> G[释放资源];
通过以上的总结和流程,你可以更加清晰地了解视频处理的整个过程,并且可以根据实际需求进行灵活应用和扩展。希望本文能帮助你在视频处理领域取得更好的成果。
超级会员免费看
1146

被折叠的 条评论
为什么被折叠?



