学习OpenCV2 C++ API(2)-- 简易视频播放器
2013-03-04 09:42 robturtle 阅读(3878) 评论(0) 收藏 举报参考第二章,花了一个晚上写出了一个简易视频播放器。
昨晚在看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中创建的窗口,即使在类内被重建,它仍然具有整个应用程序的生命周期。
浙公网安备 33010602011771号