代码改变世界

学习OpenCV2 C++ API(1)

2013-03-04 01:43  robturtle  阅读(12492)  评论(3编辑  收藏  举报

O'Reilly 的《Learning OpenCV》的例子果然还是过时了。书中使用的还是第一代的基于C的代码。于是一边照着书本,一边对照着官方手册,打算将书中的示例代码用OpenCV2的C++API重写一遍。

今天的内容有:

  1. Display Image (Exp 02-01, P19)
  2. AVI Player & Trackbar (Exp 02-02~03, P21-23)
  3. Gaussian Smooth (Exp 02-04, P26)

其中的页码对应清华大学出版社翻译的第一版《学习OpenCV》。

工具代码

  • 使用了USAGE宏,省去书写检测参数数量代码的重复工作;
  • 使用命名空间 cliout 将cout, endl, cerr 等通过using导入;
  • 使用了《C++编译调试密笈》中的 SCPP_ASSERT 和 SCPP_TEST_ASSERT 宏,主要功能就是自定义断言信息。

USAGE 宏和 namespace cliout 均在文件 cppcommon.hpp,相关代码如下:

// cppcommon.hpp 
#ifndef  CPPCOMMON_HPP 
#define  CPPCOMMON_HPP 
 
namespace cliout { 
    using std::cout; 
    using std::endl; 
    using std::cerr; 

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

SCPP_XXX_ASSERT 宏在文件 scpp_assert.hpp下,就不给出了,不过可以使用下面的定义使代码正常运行:

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

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


#endif  /*SCPP_ASSERT_HPP*/

1. Display Image (Exp 02-01, P19)

包含文件

第二代中,包含头文件"opencv2/opencv.hpp“将使用opencv的所有功能,也可以有选择性地包含需要的文件"opencv2/highgui.hpp“。

数据结构

储存图像的数据类型不再是 IplImage 的指针, 使用 cv::Mat 即可。

读取图像

读取图像的函数改为:

cv::imread(const string & FileName, int flag)

其中,可选的 flag 有

  • CV_LOAD_IMAGE_ANYDEPTH      如果图像具有32/64位深度,使用图像对应的色彩深度
  • CV_LOAD_IMAGE_COLOR            转换为彩色图像 (默认)
  • CV_LOAD_IMAGE_GRAYSCALE    转换为灰度图像

创建窗口

创建窗口的函数改为

cv::namedWindow(const string & windowName, int flag)

其中,可选的 flag 有

  • CV_WINDOW_NORMAL or CV_WINDOW_AUTOSIZE:  normal 模式下可以手动调整窗口大小, 而 auto 模式(默认)下窗口将自动适应图像大小,无法手动调整。(gtk3, qt 下可用)

  • CV_WINDOW_FREERATIO or CV_WINDOW_KEEPRATIO:  free_ratio 模式下调整图像时不考虑其比率, 而 keep_ratio 模式将保持图像的缩放比率 (qt下可用)

  • CV_GUI_NORMAL or CV_GUI_EXPANDED: expanded 模式下将显示额外的工具栏和按钮(NOTE:仅qt下可用)

waitKey

由 cvWaitKey() 改为 cv::waitKey(), 变化不大

Sumary

在C++API下,终于不用手动释放资源了。记得在MFC下绘图的时候,因为忘记释放DC,导致程序运行时 Stack Overflow,调试了两天才找出bug,实在不是什么美好的回忆。

示例 2-1 代码如下:

// Exp 02-01 Display Image in C++ style
#include    "opencv2/opencv.hpp"
#include    "cppcommon.hpp"
#include  <string>

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

    string winName = "C++ Style OpenCV2!";
    cv::namedWindow(winName, CV_WINDOW_NORMAL);

    // show grayscale image
    cv::Mat img = cv::imread(argv[1], CV_LOAD_IMAGE_GRAYSCALE);
    cv::imshow(winName, img);
    cv::waitKey(0);

    // show default color type image
    img = cv::imread(argv[1], CV_LOAD_IMAGE_COLOR);// default
    cv::imshow(winName, img);
    cv::waitKey(0);
}

2. AVI Player & Trackbar (Exp 02-02~03, P21-23)

VideoCapture

同样,视频捕获的数据也从指针改成类 cv::VideoCapture 了。构造函数接受的参数有:

  • std::string   : 文件名
  • int               : 设备号 ( 当参数为0时, 打开默认的摄像头)

由capture类输出图像的方法现在可以使用重载的 operator>>(cv::Mat imageOut) ,示例如下:

cv::Mat img;
cv::VideoCapture cap("test.avi");
cap >> img;

grab()方法将抓取下一帧,retrieve()函数则将抓取的帧解码,如果抓取失败, retrieve()将返回 false。

double get(int PropertyID)和 bool set(int PropID, double value) 可以用来设置类内部的属性,可选的属性ID如下:

  • CV_CAP_PROP_POS_MSEC 当前位置(单位:ms)

  • CV_CAP_PROP_POS_FRAMES 当前位置(单位:帧数,从0开始计)

  • CV_CAP_PROP_POS_AVI_RATIO 当前位置(单位:比率, 0表示开始,1表示结尾)

  • CV_CAP_PROP_FRAME_WIDTH 帧宽度

  • CV_CAP_PROP_FRAME_HEIGHT 帧高度

  • CV_CAP_PROP_FPS 帧速率

  • CV_CAP_PROP_FOURCC 4-字符表示的视频编码(如:’M‘, ’J‘, ’P‘, ’G‘)

  • CV_CAP_PROP_FRAME_COUNT 总帧数

  • CV_CAP_PROP_FORMAT retrieve().调用返回的矩阵格式

  • CV_CAP_PROP_MODE 后端变量指示的当前捕获的模式

  • CV_CAP_PROP_BRIGHTNESS 明亮度(仅用于摄像头)

  • CV_CAP_PROP_CONTRAST 对比度(仅用于摄像头)

  • CV_CAP_PROP_SATURATION 饱和度(仅用于摄像头)

  • CV_CAP_PROP_HUE 色调(仅用于摄像头)

  • CV_CAP_PROP_GAIN 增益(仅用于摄像头)

  • CV_CAP_PROP_EXPOSURE 曝光度 (仅用于摄像头)

  • CV_CAP_PROP_CONVERT_RGB 是否应该将图像转化为RGB图像(布尔值)

  • CV_CAP_PROP_WHITE_BALANCE 白平衡(暂不支持 v2.4.3)

  • CV_CAP_PROP_RECTIFICATION 立体摄像头标定 (目前仅支持 DC1394 v 2.x 后端)

滚动条

相关函数有:

int createTrackbar(const string& trackbarname, const string& winname, int* value, int count, TrackbarCallback onChange=0, void* userdata=0)

int getTrackbarPos(const string& trackbarname, const string& winname)

void setTrackbarPos(const string& trackbarname, const string& winname, int pos)

构造函数中,value指示当前的滚动条坐标,count指示坐标的最大值(最小值为0),onChange指向坐标改变时的回调函数,而userdata将作为参数传递给回调函数。

回调函数的签名如下:

void foo(int pos, void * userdata)

其中,pos指示滚动条当前坐标。

示例代码如下:

// Exp 02-02~03 Video Player
#include    "opencv2/opencv.hpp"
#include    "cppcommon.hpp"
#include    "scpp_assert.hpp"
#include  <string>

void onTrackbarChange(int pos, void * userdata)
{
    cv::VideoCapture cap = * (cv::VideoCapture *) userdata;
    cap.set(CV_CAP_PROP_POS_FRAMES, pos);
}

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

    string winName = "Video Player";
    cv::namedWindow(winName);
    
    cv::VideoCapture cap(argv[1]);
    SCPP_ASSERT(cap.isOpened(),
            "Can not open Video Capture from file: " << argv[1]);

    int frameCount = cap.get(CV_CAP_PROP_FRAME_COUNT);
    SCPP_ASSERT(frameCount > 0 && cap.grab(),
            "Bad Video File: " << argv[1] << "with frame count " << frameCount);

    int fps = cap.get(CV_CAP_PROP_FPS);
    int mspf = 1000 / fps;

    string trackbarName = "Progress";
    int pos = 0;
    cv::createTrackbar(/* tbName = */trackbarName, 
                       /* winName = */winName,
                       /* current pos = */ &pos,
                       /* max pos = */ frameCount,
                       // void callback(int pos, void* userdata)
                       /* callback = */ onTrackbarChange,
                       /* userdata = */ (void*) &cap
    );

    cv::Mat frame;
    while (pos < frameCount) {
        cap >> frame;
        cv::imshow(winName, frame);
        cv::setTrackbarPos(trackbarName, winName, ++pos);

        char c = cv::waitKey(mspf);
        if (std::tolower(c) == 'q') break;
    }
}

3. Gaussian Smooth (Exp 02-04, P26)

GaussianBlur

第一代中的 cvSmooth() 已经被舍弃,取而代之的是 cv::blur(), cv::GaussianBlur() 等一系列函数。下面仅讲解GaussianBlur(),其原型如下:

cv::GaussianBlur(InputArray src, OutputArray dest, cv::Size ksize, int SigmaX, int SigmaY)

InputArray 和 OutputArray 可以看作是OpenCV 对 Mat 类型进行读/写权限设置以防止误操作,这个工作是自动完成的,用户只需要填入Mat类型的参数即可,不需要自行定义 InputArray 和 OutputArray 类型的变量。

其中,ksize 指示核(kernel)的大小, 如果Size被设置为0, 核的尺寸将由 SigmaX 和 SigmaY 自动推导。

示例代码如下:

// Exp 02-04 Gaussian Smooth
#include    "opencv2/opencv.hpp"
#include    "cppcommon.hpp"
#include    "scpp_assert.hpp"
#include  <string>
 
int main(int argc, char *argv[])
{   
    using namespace cliout;
    using cv::Mat;
    using std::string;
    
    USAGE(argc == 2, "ImageFile");
    
    Mat imgIn = cv::imread(argv[1]);
    SCPP_ASSERT(!imgIn.empty(),
      "Bad Image File: " << argv[1]);

    // Do some Image Processing
    Mat imgOut;
    cv::GaussianBlur(imgIn, imgOut,
      /* Kernel Size = */ cv::Size(0,0), // if set to 0, it's computed from sigma
      /* Sigma X     = */ 3,
      /* Sigma Y     = */ 3);

    // Display
    string winIn ("Original Image"),
           winOut("After Gaussion Blur");

    cv::namedWindow(winIn, CV_WINDOW_NORMAL);
    cv::imshow(winIn, imgIn);
    cv::waitKey(0);

    cv::namedWindow(winOut, CV_WINDOW_NORMAL);
    cv::imshow(winOut, imgOut);
    cv::waitKey(0);
}