天天看點

《OpenCV2 計算機視覺程式設計手冊》視訊處理一

本文主要結合《OpenCV2 計算機視覺程式設計手冊》第10章的内容,學習OpenCV 處理視訊圖像的一般方法,包括讀入,處理,寫出。

1.頭檔案

#ifndef HEAD_H_
#define HEAD_H_

#include <iostream>
#include <iomanip>// 控制輸出格式
#include <sstream>// 檔案流控制
#include <string>
#include <vector>

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

#endif // HEAD_H_      

2. VideoProcessor頭檔案

#ifndef VPROCESSOR_H_
#define VPROCESSOR_H_

#include "head.h"

void canny(cv::Mat& img, cv::Mat& out)
{
  cv::cvtColor(img, out, CV_BGR2GRAY);                      // 灰階轉換
  cv::Canny(out, out, 100, 200);                            // Canny邊緣檢測
  cv::threshold(out, out, 128, 255, cv::THRESH_BINARY_INV); // 二值圖像反轉, 小于128設定為255, 否則為0;即邊緣為黑色
}

// 抽象類FrameProcessor中純虛函數process必須在子類(其繼承類)中重新定義。
// 幀處理器接口
class FrameProcessor 
{
  public:
  // 處理方法,定義為純虛函數, 讓其子類實作具體的接口
  virtual void process(cv:: Mat &input, cv:: Mat &output)= 0;
};


class VideoProcessor 
{
  private:
    cv::VideoCapture capture;           // OpenCV視訊采集對象(object)
    void (*process)(cv::Mat&, cv::Mat&);// 每幀處理的回調函數, 函數指針調用
    FrameProcessor *frameProcessor;     // 基類(含純虛函數的抽象類)FrameProcessor接口指針, 指向子類的實作
    bool callIt;                        // 啟動回調函數與否的bool判斷, true:調用, false:不調用
    std::string windowNameInput;        // 輸入顯示視窗名字
    std::string windowNameOutput;       // 輸出顯示視窗名字
    int delay;                          // 幀間處理延遲
    long fnumber;                       // 已處理幀總數
    long frameToStop;                   // 在該幀停止    
    bool stop;                          // 停止處理标志位!
  
    std::vector<std::string> images;               // 輸入的圖像集或者圖像向量(vector容器)
    std::vector<std::string>::const_iterator itImg;// 圖像集的疊代器

    cv::VideoWriter writer;             // OpenCV視訊寫出對象
    std::string outputFile;             // 輸出視訊檔案名字
    int currentIndex;                   // 輸出圖像集的目前索引
    int digits;                         // 輸出圖像檔案名字的數字 
    std::string extension;              // 輸出圖像集的擴充名

    // Getting the next frame which could be: video file; camera; vector of images
    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;
        }
      }
    }

    // Writing the output frame which could be: video file or images
    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);
      }
    }

  public:
    // 構造函數
    VideoProcessor() : callIt(false), delay(-1), 
      fnumber(0), stop(false), digits(0), frameToStop(-1), 
        process(0), frameProcessor(0) {}

    // 設定視訊檔案的名字
    bool setInput(std::string filename) 
    {
    fnumber= 0;
    // In case a resource was already 
    // associated with the VideoCapture instance
    capture.release();            // 釋放之前打開過的資源
    images.clear();               // 釋放之前打開過的資源
    return capture.open(filename);// 打開視訊檔案
    }

    // 設定相機ID
    bool setInput(int id) 
    {
    fnumber= 0;
    // In case a resource was already 
    // associated with the VideoCapture instance
    capture.release();
    images.clear();
    // 打開視訊檔案
    return capture.open(id);
    }

    // 設定輸入的圖像集
    bool setInput(const std::vector<std::string>& imgs) 
    {
    fnumber= 0;
    // In case a resource was already 
    // associated with the VideoCapture instance
    capture.release();//釋放之前打開過的資源

    // 輸入的是圖像集
    images= imgs;
    itImg= images.begin();
    return true;
    }

    // 設定輸出視訊檔案, 預設參數和輸入的一樣
    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);                  // 是否為彩色視訊
    }

    // 設定輸出是圖像集, 字尾必須是".jpg", ".bmp" ...
    bool setOutput(const std::string &filename, // 檔案名字首
      const std::string &ext,                 // 圖像檔案字尾 
      int numberOfDigits=3,                   // 數字位數
      int startIndex=0)                       // 開始索引000
    {     
      if (numberOfDigits<0)                   // 數字位數必須是正數
        return false;

      outputFile = filename;                  // 檔案名
      extension  = ext;                       // 公共字尾名

      digits       = numberOfDigits;          // 檔案名中的數字位數 
      currentIndex = startIndex;              // 開始索引

      return true;
    }

    // 設定每一幀的回調函數
    void setFrameProcessor(void (*frameProcessingCallback)(cv::Mat&, cv::Mat&)) 
    {
      // invalidate frame processor class instance 使FrameProcessor執行個體無效化
      frameProcessor = 0;
      process = frameProcessingCallback;
      callProcess();
    }

    // 設定FrameProcessor接口執行個體
    void setFrameProcessor(FrameProcessor* frameProcessorPtr) 
    {
      // invalidate callback function 使回調函數無效化
      process = 0;
      frameProcessor= frameProcessorPtr;
      callProcess();
    }

    // 在frame幀停止
    void stopAtFrameNo(long frame) 
    {
      frameToStop= frame;
    }

    // 處理回調函數
    void callProcess() 
    {
      callIt= true;
    }

    // 不調用回調函數
    void dontCallProcess() 
    {
      callIt= false;
    }

    // 顯示輸入的圖像幀
    void displayInput(std::string wn) 
    {
      windowNameInput= wn;
      cv::namedWindow(windowNameInput);
    }

    // 顯示處理的圖像幀
    void displayOutput(std::string wn) 
    {
      windowNameOutput= wn;
      cv::namedWindow(windowNameOutput);
    }

    // 不顯示處理的圖像幀
    void dontDisplay() 
    {
      cv::destroyWindow(windowNameInput);
      cv::destroyWindow(windowNameOutput);
      windowNameInput.clear();
      windowNameOutput.clear();
    }

    // 設定幀間延遲時間
    // 0 means wait at each frame
    // negative means no delay
    void setDelay(int d) 
    {
      delay= d;
    }

    // 處理幀的總數
    long getNumberOfProcessedFrames() 
    {
      return fnumber;
    }

    // 傳回視訊幀的大小
    cv::Size getFrameSize() 
    {
    if (images.size()==0) 
    {
      // get size of from the capture device
      int w= static_cast<int>(capture.get(CV_CAP_PROP_FRAME_WIDTH));
      int h= static_cast<int>(capture.get(CV_CAP_PROP_FRAME_HEIGHT));

      return cv::Size(w,h);
    } 
    else 
    { // if input is vector of images
      cv::Mat tmp= cv::imread(images[0]);
      if (!tmp.data) return cv::Size(0,0);
      else return tmp.size();
    }
    }

    // 傳回下一幀的幀數
    long getFrameNumber() 
    {
    if (images.size()==0) 
    {
      // get info of from the capture device
        long f= static_cast<long>(capture.get(CV_CAP_PROP_POS_FRAMES));
        return f; 
    } 
    else 
    { // if input is vector of images
      return static_cast<long>(itImg-images.begin());
    }
    }

    // return the position in ms
    double getPositionMS() 
    {
      // undefined for vector of images
      if (images.size()!=0) return 0.0;

      double t= capture.get(CV_CAP_PROP_POS_MSEC);
      return t; 
    }

    // 傳回幀率
    double getFrameRate() 
    {
      // undefined for vector of images
      if (images.size()!=0) return 0;

      double r= capture.get(CV_CAP_PROP_FPS);
      return r; 
    }

    // 傳回視訊中圖像的總數
    long getTotalFrameCount()
    {
      // for vector of images
      if (images.size()!=0) return images.size();

      long t= capture.get(CV_CAP_PROP_FRAME_COUNT);
      return t; 
    }

    // 擷取輸入視訊的編解碼器
    int getCodec(char codec[4]) 
    {
      // 未制定的圖像集
      if (images.size()!=0) return -1;

      union 
      {// 4-char編碼的資料結果
        int value;
        char code[4]; 
      } returned;
      // 擷取編碼
      returned.value= static_cast<int>(capture.get(CV_CAP_PROP_FOURCC));
      // 獲得4字元
      codec[0]= returned.code[0];
      codec[1]= returned.code[1];
      codec[2]= returned.code[2];
      codec[3]= returned.code[3];
      // 傳回對應的整數
      return returned.value;
    }
    
    // 設定幀位置
    bool setFrameNumber(long pos) 
    {
      // for vector of images
      if (images.size()!=0) 
      {
        // move to position in vector
        itImg= images.begin() + pos;
        // is it a valid position?
        if (pos < images.size())
          return true;
        else
          return false;

      } 
      else 
      { // if input is a capture device
      return capture.set(CV_CAP_PROP_POS_FRAMES, pos);
      }
    }

    // go to this position
    bool setPositionMS(double pos) 
    {
      // not defined in vector of images
      if (images.size()!=0) 
        return false;
      else 
          return capture.set(CV_CAP_PROP_POS_MSEC, pos);
    }

    // go to this position expressed in fraction of total film length
    bool setRelativePosition(double pos) 
    {
      // for vector of images
      if (images.size()!=0) 
      {
        // move to position in vector
        long posI= static_cast<long>(pos*images.size()+0.5);
        itImg= images.begin() + posI;
        // is it a valid position?
        if (posI < images.size())
          return true;
        else
          return false;
      } 
      else 
      { // if input is a capture device
        return capture.set(CV_CAP_PROP_POS_AVI_RATIO, pos);
      }
    }

    // 停止運作
    void stopIt() 
    {
      stop= true;
    }
    // 是否已停止運作?
    bool isStopped() 
    {
      return stop;
    }


    // 判斷是否是視訊捕獲裝置或圖像集
    bool isOpened() 
    {
      return capture.isOpened() || !images.empty();
    }
    
    // 擷取并處理圖像
    void run() 
    {
      cv::Mat frame;  // 目前幀
      cv::Mat output; // 輸出幀

      // if no capture device has been set
      if (!isOpened())
        return;

      stop= false;
      while (!isStopped()) 
      {
        // 讀取下一幀
        if (!readNextFrame(frame))
          break;

        // 顯示輸出幀
        if (windowNameInput.length()!=0) 
          cv::imshow(windowNameInput,frame);

          // 調用幀處理回調函數或FrameProcessor執行個體
        if (callIt) 
        {  
        // 處理目前幀
        if (process)             // 如果是回調函數
            process(frame, output);
        else if (frameProcessor) //如果是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();
      }
    }
};

#endif // VPROCESSOR_H_      

3. 主函數

#include "head.h"
#include "videoprocessor.h"


int main()
{
    //----Zero Test----
    cv::VideoCapture capture("../bike.avi"); // 打開視訊/攝像頭0
    if (!capture.isOpened())
        return 1;
    double rate= capture.get(CV_CAP_PROP_FPS);// 擷取幀率
    bool stop(false);
    cv::Mat frame;                            // 目前幀
    cv::namedWindow("Extracted Frame");
    int delay= 1000/rate;                     // 延遲的毫秒
    //int delay = 1000;
    // 處理視訊所有幀
    while (!stop) 
    {
        // read next frame if any
        if (!capture.read(frame))
            break;
        cv::imshow("Extracted Frame",frame);
        if (cv::waitKey(delay)>=0)            // 延遲等待直到cv::waitKey(delay)<0
                stop= true;
    }
    capture.release();                       // 因為capture自動調用析構函數,是以capture.release不是必須的!
    cv::waitKey();
        
    //----First Test----
    VideoProcessor processor;                           // 建立VideoProcessor類執行個體 processor
    processor.setInput("../bike.avi");                  // 打開視訊檔案bike.avi
    processor.displayInput("Input Video");              // 聲明輸入視訊顯示視窗
    processor.displayOutput("Output Video");            // 聲明輸出視訊顯示視窗
    processor.setDelay(1000./processor.getFrameRate()); // 設定播放視訊為原始輸入視訊幀率
    processor.setFrameProcessor(canny);                 // 設定幀處理器的回調函數--canny
    processor.run();                                    // 開始處理視訊檔案
    cv::waitKey();                                      // 等待按鍵響應
    
    //----Second test----
    processor.setInput("../bike.avi");                            // 重新設定打開視訊
    cv::Size size= processor.getFrameSize();                      // 擷取視訊檔案的基本資訊
    std::cout << size.width << " " << size.height << std::endl;   // 視訊圖像的寬度(列)和高度(行)
    std::cout << processor.getFrameRate() << std::endl;           // 視訊的幀率
    std::cout << processor.getTotalFrameCount() << std::endl;     // 視訊總的幀數
    std::cout << processor.getFrameNumber() << std::endl;         // 視訊幀的編号
    std::cout << processor.getPositionMS() << std::endl;          // 視訊幀的位置(ms)
    processor.dontCallProcess();                                   // 不處理打開視訊檔案
    // 輸出.jpg視訊圖像到output檔案夾, 圖像名字為bikeOut000.jpg~bikeOut118.jpg
    processor.setOutput("../output/bikeOut",".jpg");               
    processor.run(); 
    cv::waitKey();
    // 輸出bike.avi視訊到output檔案夾,編解碼器為:XVID, 基于MPEG-4視訊标準的開源解碼庫
    char codec[4];                                                 // 編解碼器辨別
    processor.setOutput("../output/bike.avi",processor.getCodec(codec),processor.getFrameRate());
    std::cout << "Codec: " << codec[0] << codec[1] << codec[2] << codec[3] << std::endl;
    processor.run();
    cv::waitKey();


    //----Three test----
    processor.setInput("../bike.avi");
    processor.displayInput("Input Video");              // 聲明輸入視訊顯示視窗
    processor.displayOutput("Output Video");            // 聲明輸出視訊顯示視窗
    processor.setFrameNumber(80);                       // 設定幀的位置
    processor.stopAtFrameNo(120);                       // 停止的幀位置
    processor.setDelay(1000./processor.getFrameRate());
    processor.run();
    cv::waitKey();

    return 0;
}      

Canny邊緣檢測

《OpenCV2 計算機視覺程式設計手冊》視訊處理一

視訊寫出結果(包含檔案和視訊)

《OpenCV2 計算機視覺程式設計手冊》視訊處理一

制定開始幀和結束幀位置

《OpenCV2 計算機視覺程式設計手冊》視訊處理一