27、OpenCV视频处理全解:从读取到处理再到写入

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[释放资源];

通过以上的总结和流程,你可以更加清晰地了解视频处理的整个过程,并且可以根据实际需求进行灵活应用和扩展。希望本文能帮助你在视频处理领域取得更好的成果。

分布式微服务企业级系统是一个基于Spring、SpringMVC、MyBatis和Dubbo等技术的分布式敏捷开发系统架构。该系统采用微服务架构和模块化设计,提供整套公共微服务模块,包括集中权限管理(支持单点登录)、内容管理、支付中心、用户管理(支持第三方登录)、微信平台、存储系统、配置中心、日志分析、任务和通知等功能。系统支持服务治理、监控和追踪,确保高可用性和可扩展性,适用于中小型企业的J2EE企业级开发解决方案。 该系统使用Java作为主要编程语言,结合Spring框架实现依赖注入和事务管理,SpringMVC处理Web请求,MyBatis进行数据持久化操作,Dubbo实现分布式服务调用。架构模式包括微服务架构、分布式系统架构和模块化架构,设计模式应用了单例模式、工厂模式和观察者模式,以提高代码复用性和系统稳定性。 应用场景广泛,可用于企业信息化管理、电子商务平台、社交应用开发等领域,帮助开发者快速构建高效、安全的分布式系统。本资源包含完整的源码和详细论文,适合计算机科学或软件工程专业的毕业设计参考,提供实践案例和技术文档,助力学生和开发者深入理解微服务架构和分布式系统实现。 【版权说明】源码来源于网络,遵循原项目开源协议。付费内容为本人原创论文,包含技术分析和实现思路。仅供学习交流使用。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值