學習OpenCV2 C++ API(2)-- 簡易視頻播放器


參考第二章,花了一個晚上寫出了一個簡易視頻播放器。

昨晚在看cv:createTrackbar的時候,才真正算理解了回調函數的用法,於是模仿它的寫法,在播放器類也設計了類似的回調函數接口與外界互動。寫着寫着,突然覺得,怎么感覺這么像在寫 win32 的程序,而且編寫的過程中也極容易出現寫 win32 程序時出現的錯誤,如窗口未初始化,未檢查窗口組件狀態等疏忽導致崩潰等等。

而且自己寫時才深刻地感受到,使用回調函數接口的方式操作控制流,使得代碼看起來非常地亂。而且因為設計時的一時疏忽,我並沒有在類中保存Play()下的回調函數相應資源,為了使播放器能從暫停中恢復,不得不使用了非常丑陋的實現方式。接下來的改進,要不使這相應資源成為類成員,要不讓類成員緩存相應資源。

總而言之,我感覺回調函數的互動方式是非常低抽象而且丑陋的。自己嘗試了一下,才發覺,之前覺得一文不值的MFC的消息映射方式也有它的長處。昨晚的這一次嘗試讓我預感,如果對這些接口繼續包裝完善下去,最終的解決方案可能也將是一個類似消息映射的交換機制。究其原因,恐怕還是說到GUI下的邏輯關系比較復雜。講到這里,不得不提在收尾階段的時候,為了添加攝像頭支持,又加入了很多丑陋的控制語句。雖然視頻文件和攝像頭很“優雅”地能夠被同一個類打開讀取,但二者之間允許操作還是有很多互不相交。

罷了, 反正就是一個花了一個晚上的粗制品,以后慢慢打磨就是了。

目前的解決方案。VideoPlayer 設計了兩個回調函數的接口,一個是用於在顯示圖像前對圖像進一步處理的,另一個是控制按鍵響應的。感覺可以把第二個接口再抽象一點,讓它可以很容易地接入GUI控制的前端。回調函數的簽名方面,模仿cv::createTrackbar,增加了void*類型的參數用於傳入用戶數據。函數簽名如下:

using img_postprocessor_t = std::function<void(cv::Mat&, void*)>;
using keydown_callback_t = std::function<void(char, VideoPlayer&, void*)>;

編譯要求

1. C++11 支持;

2. boost庫, opencv2, ffmpeg;

3. 工具代碼 “scpp_assert.hpp",可以使用如下代碼確保正確編譯

// scpp_assert.hpp
#ifndef SCPP_ASSERT_HPP
#define SCPP_ASSERT_HPP
#define <assert.h>

#define SCPP_ASSERT(expr, msg) assert(expr)
#define SCPP_TEST_ASSERT(expr, msg) assert(expr)

#endif /*SCPP_ASSERT_HPP*/

4.工具代碼"cppcommon.hpp", 用到的代碼如下:

#ifndef CPPCOMMON_HPP
#define CPPCOMMON_HPP
#include <iostream>
namespace cliout {

using std::cout;
using std::endl;
using std::cerr;

#define USAGE(expr, options) \
    if (!(expr)) {
        std::cout << "Usage: " << argv[0]
                  << " " << options << std::endl;
        return 1;
    }

} // namespace

#endif

  

簡易播放器

目前並沒有測試完所有接口,也沒有寫測試套件,源代碼如下:

// TODO: Multi thread support
// TODO: better inter-class communication, at least better callback signature
#ifndef  VIDEOPLAYER_HPP
#define  VIDEOPLAYER_HPP

#include    "opencv2/opencv.hpp"
#include    "boost/dynamic_bitset.hpp"
#include    "scpp_assert.hpp"
#include  <string>
#include  <iostream>// test

#define capture(...) \
    try {\
        __VA_ARGS__\
    } catch (...) {\
        std::cout << __LINE__ << std::endl;\
    }

class VideoPlayer;

static void onKeyDown(char key, VideoPlayer& vp, void * data);

static void setProgress(int pos, void * pCap)
{
    using vc = cv::VideoCapture;
    vc cap = * (vc*) pCap;

    cap.set(CV_CAP_PROP_POS_FRAMES, pos);
}

class VideoPlayer {
    // Display
    cv::VideoCapture        cap_;
    cv::Mat                 frame_;
    int                     frame_pos_ = 0;
    int                     frame_count_;
      // msec per frame_, set by file / device original settings
    int                     mspf0_;
    int                     mspf_;
      // play_rate = current_fps : original_fps
    double                  play_rate_ = 1.0;

    // Window
    std::string             win_name_;

    // Trackbar
    std::string             tb_name_;

    // flags
    boost::dynamic_bitset<> flags_ =
      boost::dynamic_bitset<>(4);
    enum flag { showTrackbar, paused, fromCameral, quit };

    // Keydown handler
public:
    using keydown_callback_t =
          std::function<void(char, VideoPlayer&, void*)>;
private:
    keydown_callback_t keydownCallback_;
    void *             keydownData_;

    // Implementation
    void initialize()
    {
        cv::destroyWindow(win_name_);

        if (flags_.test(fromCameral)) {
            frame_pos_ = frame_count_ - 1;
            mspf_ = mspf0_ = 30;
        } else {
            frame_count_ = cap_.get(CV_CAP_PROP_FRAME_COUNT);
            SCPP_ASSERT(frame_count_ > 0,
              "frame count " << frame_count_
              << " must greater than 0");

            int fps = cap_.get(CV_CAP_PROP_FPS);
            mspf_ = mspf0_ = 1000 / fps;
        }

        cv::namedWindow(win_name_);

        if (flags_.test(showTrackbar) &&
            !flags_.test(fromCameral)) {
            cv::createTrackbar(tb_name_, win_name_,
              /* current pos = */ &frame_pos_,
              /*     max pos = */ frame_count_,
              /*    postprocess = */ setProgress,
              /*postprocess arg = */ (void*) &cap_);
        }

        if (!frame_.empty())
            cv::imshow(win_name_, frame_);
    }
public:
    VideoPlayer(const std::string & file,
                const std::string & windowName   = "Video Player",
                bool                isPaused     = false,
                bool                hasTrackbar  = true,
                const std::string & trackbarName = "Progress",
                keydown_callback_t  key_callback = onKeyDown,
                void *              key_data     = 0)
    : cap_(file)
    , win_name_(windowName)
    , tb_name_(trackbarName)
    , keydownCallback_(key_callback)
    , keydownData_(key_data)
    {
        SCPP_ASSERT(cap_.isOpened() && cap_.grab(),
          "Bad Video file: " << file);

        flags_.set(fromCameral, false);
        flags_.set(paused, isPaused);
        flags_.set(showTrackbar, hasTrackbar);

        initialize();
    }

    VideoPlayer(int                 device,
                const std::string & windowName   = "Cameral",
                bool                isPaused     = false,
                bool                hasTrackbar  = true,
                const std::string & trackbarName = "Progress",
                keydown_callback_t  key_callback = onKeyDown,
                void *              key_data     = 0)
    : cap_(device)
    , win_name_(windowName)
    , tb_name_(trackbarName)
    , keydownCallback_(key_callback)
    , keydownData_(key_data)
    {
       SCPP_ASSERT(cap_.isOpened() && cap_.grab(),
          "Can not connect to cameral: " << device);

        flags_.set(fromCameral, true);
        flags_.set(paused, isPaused);
        flags_.set(showTrackbar, hasTrackbar);

        initialize();
    }

    ~VideoPlayer()
    {
        cv::destroyWindow(win_name_);
    }

    using img_postprocessor_t = std::function<void(cv::Mat&, void*)>;
    void Play(img_postprocessor_t postprocess = 0,
              void * post_arg = 0)
    {
        while (frame_pos_ < frame_count_) {
            if (flags_.test(quit)) break;
            if (flags_.test(paused)) {
                char c = cv::waitKey(0);
                keydownCallback_(c, *this, keydownData_);
                continue;
            }

            cap_ >> frame_;
            if (postprocess)
                postprocess(frame_, post_arg);

            cv::imshow(win_name_, frame_);

            if (!flags_.test(fromCameral))
                synchronize(++frame_pos_);

            char c = cv::waitKey(mspf_);
            keydownCallback_(c, *this, keydownData_);
        }
    }

    inline void Pause()
    { flags_.flip(paused); }

    inline void Quit()
    { flags_.set(quit, true); }

    inline bool isFromCameral()
    { return flags_.test(fromCameral); }

    void DrawTrackbar()
    {
        if (flags_.test(fromCameral)) return;
        flags_.flip(showTrackbar);
        initialize();
    }

    void setPlayRate(double rate)
    {
        if (play_rate_ != rate) {
            play_rate_ = rate;
            mspf_ = mspf0_ / play_rate_;
        }
    }

    void stepForward(int frames)
    {
        if (frame_pos_ + frames >= frame_count_) {
            frame_pos_ = frame_count_ - 1;
        } else {
            frame_pos_ += frames;
        }

        synchronize(frame_pos_);
    }

    void StepBackward(int frames)
    {
        if (frame_pos_ - frames < 0) {
            frame_pos_ = 0;
        } else {
            frame_pos_ -= frames;
        }

        synchronize(frame_pos_);
    }

private:
    void synchronize(int frame_pos)
    {
        if (flags_.test(showTrackbar)) {
            cv::setTrackbarPos(tb_name_, win_name_,
                               frame_pos);
        } else {
            cap_.set(CV_CAP_PROP_POS_FRAMES,
                               frame_pos);
        }
    }
};

void onKeyDown(char key, VideoPlayer& vp, void * data)
{
    key = std::tolower(key);

    switch (key) {
    case 'q':
        vp.Quit();
        break;
    case ' ': // space
        vp.Pause();
        break;
    case 't':
        vp.DrawTrackbar();
        break;
    default:
        break;
    }
}

#endif  /*VIDEOPLAYER_HPP*/

演示代碼

下面的代碼對最主要的幾個接口進行了演示:

#include    "VideoPlayer.hpp"
#include    "cppcommon.hpp"
#include  <string>
#include  <iostream>// test
#include  <vector>

void blur(cv::Mat& img, void *nouse)
{
    cv::GaussianBlur(img, img, cv::Size(0, 0), 3, 3); 
}

void canny(cv::Mat& img, void *pConf)
{
    std::vector<double> conf;
    if (pConf)
        conf = * (std::vector<double>*) pConf;
    else
        conf = {7, 7, 1.5, 1.5, 0, 30, 3}; 

    cv::cvtColor(img, img, CV_8U, 1); 
    cv::GaussianBlur(img, img, 
                     cv::Size(conf[0],conf[1]),
                     conf[2], conf[3]);
    cv::Canny(img, img, conf[4], conf[5], conf[6]);
}

int main(int argc, char *argv[])
{
    using namespace cliout;
    USAGE(argc==2, "VideoFile");

    // use default window, has class's life cycle
    {   
        VideoPlayer player(argv[1]);
        player.Play(canny);
    }

    // use exsited window, has function's life cycle
    std::string winName("Existed window");
    cv::namedWindow(winName);
    VideoPlayer newpl(argv[1], winName);
    newpl.Play(blur);

    // use cameral and passing user data
    std::vector<double> conf = {3, 3, 2.5, 2.5, 3, 15, 3};
    VideoPlayer cam(0);
    cam.Play(canny, (void*) &conf);
}

效果圖:

1: 采用默認參數的canny回調函數, 使用類內構造的窗口

 

2: 使用高斯模糊回調函數, 使用main中構造的窗口

 

3: 從攝像頭讀取,使用自定義參數調用canny,並且在main中創建的窗口,即使在類內被重建,它仍然具有整個應用程序的生命周期。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM