OpenCV初探

一、头文件

在安装OpenCV库以及设置好编程环境之后,下一个任务就是用代码来做一些有趣的事情。下面介绍一下OpenCV中的头文件:

头文件 功能
#include “opencv2/core/core_c.h” 旧式C风格的结构及运算
#include “opencv2/core/core.hpp” 新式C++风格的结构及数学运算
#include “opencv2/flann/miniflann.hpp” 最邻近搜索匹配函数
#include “opencv2/improc/improc_c.h” 旧式C风格的图像处理函数
#include “opencv2/improc/improc.hpp” 新式C++风格图像处理函数
#include “opencv2/video/photo.hpp” 操作和恢复照片相关算法
#include “opencv2/video/video.hpp” 视觉追踪以及背景分割
#include “opencv2/features2d/features2d.hpp” 用于追踪的二维特征
#include “opencv2/objdetect/objdetect.hpp” 级联人脸分类器、latent SVM分类器、HoG特征和平面片检测器
#include “opencv2/calib3d/calib3d.hpp” 校准以及双目视觉相关
#include “opencv2/ml/ml.hpp” 机器学习、聚类以及 模式识别相关
#include “opencv2/highgui/highgui_c.h” 旧式C风格的显示、滑动条、鼠标操作以及输入输出相关
#include “opencv2/highgui/highgui.hpp” 新式C++风格的显示、滑动条、鼠标操作以及输入输出相关
#include “opencv2/contrib/contrib.hpp” 用户贡献的代码、皮肤检测、模糊Mean-Shift追踪、spin image算法以及自相似特征等

注意:使用opencv.hpp可以包含所有可能在OpenCV函数中用到的头文件,但是这会减慢编译速度。


二、第一个程序:显示图片

#include "mainwindow.h"
#include 

#include 

int main(int argc, char *argv[])
{
     
    QApplication a(argc, argv);

    cv::Mat image = cv::imread("D:/Qt/Project/OpenCV_Test/Resources/test.jpg");
    if(image.empty())
    	return -1;
    cv::namedWindow("Display window",cv::WINDOW_NORMAL);
    cv::imshow("Display window",image);
    cv::waitKey(0);
    cv::destroyWindow("Display window");
    
    return a.exec();
}

代码详解:

  • cv::imread("xxx"):读取图像文件,依据文件名来决定载入图像格式,支持BMP、DIP、JPE、PNG、PBM、PGM、PPM、SR、RAS以及TIFF等,函数会返回一个cv::Mat结构,这个结构是OpenCV中将接触到最多的自带结构,OpenCV使用这个结构来处理所有类型的图像:单通道、多通道、整型、浮点数以及各种类型;
  • cv::nameWindow("Display window",cv::WINDOW_NORMAL):本函数由HighGUI模块提供,会将一个名称赋予窗口(在这里窗口名为“Display window”),未来HighGUI和这个窗口的交互函数将由这个名称来指定要与哪个窗口交互。第二个参数说明的Windows的特性,这可以全部设置为0(默认情况下),也可以设置为cv::WINDOW_AUTOSIZE;
  • cv::imshow("Display windos", image):cv::imshow()将建一个窗口(如果这个窗口不存在,它会自动调用cv::nameWindow()新建一个窗口),窗口将被重绘上要求的图片(只要cv::Mat拥有一个图像结构,都可以通过cv::imshow()进行显示),并且窗口会按照要求自动调整大小(如果使用cv::WINDOW_AUTOSIZE参数);
  • cv::waitKey(0);:告诉系统暂停并且等待键盘事件。如果其传入一个大于0的参数,它将会等待等同于该参数的毫秒时间,然后继续执行程序;如果参数被设置为0或者一个负数,程序将会无限等待直到有键被按下;
  • cv::destoryWindow("Display window"):此函数将会关闭窗口并且释放掉相关联的内存空间。cv::Mat结构,图像将会在生命周期结束的时候自动释放。

注意:OpenCV的函数都是在cv命名空间下,所以要么使用using namespace cv;或者在每个函数前加上cv::

OpenCV初探_第1张图片


三、第二个程序:视频

用openCV播放视频就像显式图像一样简单。唯一不同的是,我们需要某种循环来读取视频序列中的每一帧。

#include "mainwindow.h"
#include 
#include 

#include 
#include 

using namespace cv;

int main(int argc, char *argv[])
{
     
    QApplication a(argc, argv);

    namedWindow("Display", WINDOW_AUTOSIZE);
    VideoCapture cap;
    cap.open("D:/Qt/Project/OpenCV_Test/Resources/test.mp4");
    if(cap.isOpened())
        qDebug()<<"Sucess!";
    else
        qDebug()<<"False!";
    Mat frame;
    for(;;)
    {
     
        cap >> frame;
        if(frame.empty())
            break;
        imshow("Display", frame);
        if(waitKey(33) >= 0)
            break;
    }
    
    return a.exec();
}

OpenCV中使用cv::VideoCapture结构来读取视频,视频读取结构将会包含所有的关于这个视频文件可以读取的属性,包括状态信息,使用cv::VideoCapture::isOpened()来检查视频读取是否成功。一旦内部的while()循环开始执行,视频文件会按照视频流中被读取。这个程序通过if(frame.empty())检查数据是不是真的从视频中读了出来,如果没有,程序将会退出。如果视频帧被成功读取,将通过cv::imshow()显示。

一旦显示了这帧图片,程序将会等待33毫秒。如果用户这段时间在键盘有任何输入,程序将退出循环。如果没有发生,33毫秒之后会跳过并执行下一个循环。在退出的时候,所有数据的内存空间将会由于生命周期的结束被自动释放掉。


四、跳转

上述视频程序,实现了播放视频的功能,但是不能快速在视频中跳转,所以需要添加一个滑动条,使得用户可以进行视频跳转。

HighGUI工具提供了许多简单的工具用于处理视频和图像,一个特别有用的工具就是滑动条,它能够使用户轻松从视频的一部分跳转到另一个部分。要创建一个滑动条,需要调用cv::createTrackbar()并指明要在哪个窗口进行显示,为了完成所需要的功能,还需要一个能够执行重新定位的回调函数。

#include "mainwindow.h"
#include 
#include 

#include 
#include 

using namespace cv;

int g_slider_position = 0;
VideoCapture g_cap;

void onTrackbarSlide(int pos, void *)
{
     
    g_cap.set(CAP_PROP_POS_FRAMES, pos);
}

int main(int argc, char *argv[])
{
     
    QApplication a(argc, argv);

    namedWindow("Display", WINDOW_NORMAL);
    g_cap.open("D:/Qt/Project/OpenCV_Test/Resources/test.mp4");
    if(g_cap.isOpened())
        qDebug()<<"Read Video Sucess!";
    else
        qDebug()<<"Read Video False!";

    int frames = (int)g_cap.get(CAP_PROP_FRAME_COUNT);  //获取当前视频的总帧数
    int tmpw = (int)g_cap.get(CAP_PROP_FRAME_WIDTH);    //获取当前视频分辨率宽度
    int tmph = (int)g_cap.get(CAP_PROP_FRAME_HEIGHT);   //获取当前视频分辨率高度
    qDebug()<<"Frames_count: "<<frames<<" width: "<<tmpw<<" height: "<<tmph;

    //创建进度条:参数1(进度条名称)、参数2(进度条需要显示在哪个窗口)、参数3(进度条的起始值)、参数4(视频总帧数)、参数5(进度条的回调函数)
    createTrackbar("Position", "Display", &g_slider_position, frames, onTrackbarSlide);

    Mat frame;
    for(;;)
    {
     
        g_cap >> frame;
        if(frame.empty())
            break;
        int current_pos = (int)g_cap.get(CAP_PROP_POS_FRAMES); //获取视频当前帧
        setTrackbarPos("Position", "Display", current_pos);    //更新进度条
        imshow("Display", frame);

        char c = (char)waitKey(2);
        if(c == 'q')
            break;
    }
	
    destroyWindow("Display"); //回收窗口垃圾,Esc被按下,程序将会终止
    return a.exec();
}

在上述程序中,代码中添加了一个全局变量来表示滑动条的位置并且添加了一个回调函数来更改这个变量来重新定位视频读取的位置。回调函数将会传入一个32位的整型数值以表示当前位置,这将是进度条存在的新位置。在回调函数的内部,通过调用cv::VideoCapture::set()来真正使进度条移到我们希望的位置。视频开始播放之后程序将会委托滑动条的回调函数来更新滑动块在滑动块上的位置以便显示给用户。

OpenCV初探_第2张图片


五、简单的变换

通过上述学习,已经基本了解了OpenCV播放视频文件的流程,但是我们的关注点在计算机视觉,所以希望做一些计算机视觉相关的工作。许多基础的计算机视觉工作都包括对视频流使用滤波器,一个最简单的操作就是对图像的平滑处理,这将由高斯核或者其他核卷积减小图像的信息量。OpenCV使得这样使用高斯核的卷积非常容易实现。

//加载图像并且在显示之前平滑处理图像
#include "mainwindow.h"
#include 
#include 

#include 

using namespace cv;

int main(int argc, char *argv[])
{
     
    QApplication a(argc, argv);

    namedWindow("Display-in", WINDOW_NORMAL);
    namedWindow("Display-out", WINDOW_NORMAL);

    Mat image = cv::imread("D:/Qt/Project/OpenCV_Test/Resources/test.jpg");
    if(image.empty())
        return -1;
    imshow("Display-in", image);

    Mat out;
    //could use GaussianBlur(), blur(), medianBlur() or bilateralFilter()
    GaussianBlur(image, out, Size(5,5), 3, 3);
    GaussianBlur(out, out, Size(5,5), 3, 3);

    imshow("Display-out", out);

    waitKey(0);

    return a.exec();
}

该程序创建了两个窗口:Display-in、Display-out。Display-in中显示未经任何处理的图像,Display-out中显示经过高斯核模糊处理的图像。

输入图像被5x5大小的高斯核模糊并且被写入out变量中。高斯核的大小必须是奇数,因为高斯核卷积会在其覆盖区域的中心输出结果。在下一次调用cv::GaussianBlur()的时候,由于被分配了临时的存储空间,所以out可以同时作为输入和输出。执行了两次模糊操作的图像被作为结果输出,在终止之前等待用户进行键盘事件,然后在对象生命周期结束的时候清理申请的内存。

C++对象cv::Mat使我们的工作变得轻松,只需要实例化一个输出矩阵,这将由程序自行管理并在合适的时候自动分配,释放内存。


六、不那么简单的变换

6.1、变换操作1

在上述程序中,我们没有任何特殊目的地使用高斯模糊。现在我们将使用高斯模糊来对一张图像实现基2的降采样。如果我们对图像进行多次降采样,就要建立一个尺度空间(也称为“图像金字塔”),这一方法是计算机视觉用于处理传感器和目标尺度变化的常用手段之一。对信号的降采样等效于和一系列脉冲函数进行卷积(将这些函数视为“峰值”)。这样的采样会把高频分量引入输出信号(图像),为了避免这样的事情发生,我们希望首先通过一个高通滤波器来限制信号带宽,使其能够在采样频率之内。在OpenCV中,高斯模糊以及降采样通过cv::pyrDown()函数来实现。

//使用cv::pyrDown()来创建一个新的图像,其宽高均为原始图像的一半
#include "mainwindow.h"
#include 
#include 

#include 

using namespace cv;

int main(int argc, char *argv[])
{
     
    QApplication a(argc, argv);

    Mat img1,img2;

    namedWindow("Display1", WINDOW_NORMAL);
    namedWindow("Display2", WINDOW_NORMAL);

    img1 = imread("D:/Qt/Project/OpenCV_Test/Resources/test.jpg");
    imshow("Displat1", img1);

    pyrDown(img1, img2);
    imshow("Display2", img2);

    waitKey(0);

    return a.exec();
}

OpenCV初探_第3张图片


6.2、变换操作2

边缘检测器cv::Canny()通过cv::cvtColor()函数生成一个和原图一样大小但只有一个通道的图像,从而将图像从RGB三通道图像转换成灰度图,这个操作在OpenCV中定义为宏cv::COLOR_BGR2GRAY

#include "mainwindow.h"
#include 
#include 

#include 

using namespace cv;

int main(int argc, char *argv[])
{
     
    QApplication a(argc, argv);

    Mat img_rgb,img_gray,img_cny;

    namedWindow("Display1", WINDOW_NORMAL);
    namedWindow("Display2", WINDOW_NORMAL);

    img_rgb = imread("D:/Qt/Project/OpenCV_Test/Resources/test.jpg");
    cvtColor(img_rgb, img_gray, COLOR_BGR2GRAY);
    imshow("Display1", img_gray);

    Canny(img_gray, img_cny, 10, 100, 3, true);
    imshow("Display2", img_cny);

    waitKey(0);

    return a.exec();
}

OpenCV初探_第4张图片


七、从摄像头中读取

“视觉”在计算机世界宏可以表示很多东西,在一些情况下,我们分析从任意地方加载的静止的图像。在另一些情况下,我们会分析从硬盘中读取的视频。然而在更多的情况下,我们想要和从某种摄像头中读取的实时数据进行交互。

OpenCV中的HighGUI模块,为我们提供了一个简单的方式来驾驭这种情况。这个方法很接近于我们使用cv::VideoCapture从硬盘读取视频的方式,事实上,cv::VideoCapture对磁盘上的文件和摄像头是有一致接口的。对于前者来说,需要给它一个指示读取文件名的路径,对于后者来说,需要给它一个相机ID号(如果只有一个摄像头,这个ID号通常为0),ID的默认值为-1,这意味着“随意选择一个”,当然,当只有一个摄像头可以选择的时候这能够很好地工作。

#include "mainwindow.h"
#include 
#include 

#include 

using namespace cv;

int main(int argc, char *argv[])
{
     
    QApplication a(argc, argv);

    namedWindow("Camera", WINDOW_NORMAL);
    VideoCapture cap;
    cap.open(0); //如果只有一个相机,ID一般为0
    if(cap.isOpened())
        qDebug()<<"打开成功";
    else
        qDebug()<<"打开失败";

    for(;;)
    {
     
        Mat framge;
        cap >> framge;
        if(framge.empty())
            break;
        imshow("Camera", framge);
        waitKey(30);
    }

    return a.exec();
}


八、写入AVI文件

在许多应用中,我们都希望记录一个输入流或完全不同的一些图像到视频流中。OpenCV提供了一种简单的方法来实现这一点。正如我们创建一个从视频流中捕获帧的对象一样,我们可以创建一个写入对象以便将帧依次输入到一个视频文件中。允许我们进行这个工作的对象是cv::VideoWriter

一旦将其实例化,我们就可以将每一帧图像输入到cv::VideoWriter对象中,然后在完成写入之后调用cv::VideoWriter.release()方法。下面的例子会打开一个视频文件,读取它的内容后将其转换为对数极坐标(log-polar)形式(类似于人眼真正捕捉到的图像的形式),然后将对数极坐标图像写入一个新的视频文件。

#include "mainwindow.h"
#include 
#include 
#include 

#include 

using namespace cv;

int main(int argc, char *argv[])
{
     
    QApplication a(argc, argv);

    namedWindow("Example", WINDOW_NORMAL);
    namedWindow("Log-Polor", WINDOW_NORMAL);

    VideoCapture cap;
    cap.open("D:/Qt/Project/OpenCV_Test/Resources/test.mp4");
    if(cap.isOpened())
        qDebug()<<"打开成功";
    else
        qDebug()<<"打开失败";

    double fps = cap.get(CAP_PROP_FPS);
    Size size((int)cap.get(CAP_PROP_FRAME_WIDTH), (int)cap.get(CAP_PROP_FRAME_HEIGHT));

    VideoWriter writer;
    writer.open("D:/Qt/Project/OpenCV_Test/Resources/test_out.avi",CV_FOURCC('M','J','P','G'), fps, size);

    Mat logplor_frame, bgr_frame;
    for(;;)
    {
     
        cap >> bgr_frame;
        if(bgr_frame.empty())
            break;
        imshow("Example", bgr_frame);

        logPolar(bgr_frame, logplor_frame, Point2f(bgr_frame.cols/2,bgr_frame.rows/2), 40, WARP_FILL_OUTLIERS);

        imshow("Log-Polor", logplor_frame);
        writer << logplor_frame;

        waitKey(10);

    }
    writer.release();
    destroyWindow("Example");
    destroyWindow("Log-Polor");

    return a.exec();
}

上述程序,首先打开视频并且读取一些在cv::VideoWriter建立时用到的属性(每秒播放的帧数以及图像的宽高)。然后逐帧读取视频,并将每一帧转换为对数极坐标形式,然后将转换的对数极坐标图像写入新的视频文件,循环操作直到源文件读完或者用户按下Esc键。

VideoWriter writer;
writer.open("D:/Qt/Project/OpenCV_Test/Resources/test_out.avi",CV_FOURCC('M','J','P','G'), fps, size);

cv::VideoWriter的调用有几个需要理解的参数:

参数 解释
“D:/Qt/Project/OpenCV_Test/Resources/test_out.avi” 新建视频文件的文件名
CV_FOURCC(‘M’,‘J’,‘P’,‘G’) 视频编码方式,指明视频将以何种方式进行压缩
fps 帧率
size 图像大小

在本例中,我们选择了通用的MJPG编码器,我们通过使用OpenCV所提供的宏CV_FOURCC()指定它,这个宏将四个字符作为参数,所有编码器都有类似的四个字符作为其标识。

OpenCV初探_第5张图片
OpenCV初探_第6张图片

你可能感兴趣的:(OpenCV)