美文网首页
CUDA04_OpenCV的GPU滤波

CUDA04_OpenCV的GPU滤波

作者: 杨强AT南京 | 来源:发表于2020-02-02 17:38 被阅读0次

      在Open4.2的GPU计算中,图像的滤波处理单独构成一个模块,本主题梳理相关Filter的使用,毕竟滤波运算是机器视觉非常基础的技术。OpenCV封装的还是常见的、应用比较成熟的滤波运算。最后写了一个小程序测试体验了一下,GPU的运算确实让视频的图像处理更加流畅。


    OpenCV的Cuda滤波API

    • 有OpenCV的cuda编程模式作为基础,OpenCV提供的Filter封装使用也就比较简单。
      • OpenCV提供的cuda滤波与原来提供的CPU滤波在设计上已经有很大的不同。
    1. 使用模式

      1. 创建Filter,返回的是执政对象(共享指针)
      2. 使用Filter应用于图像。
    2. OpenCV的cuda运算提供的Filter运算有(与CPU的运算需要的条件一致):

      1. createBoxFilter/createBoxMaxFilter/createBoxMinFilter
      2. createColumnSumFilter / createRowSumFilter
      3. createDerivFilter
      4. createGaussianFilter
      5. createLaplacianFilter
      6. createLinearFilter / createSeparableLinearFilter
      7. createMorphologyFilter
      8. createScharrFilter
      9. createSobelFilter
    3. Ptr类型

      • 这是继承C++11标准引入的共享指针的类型。
      • struct Ptr : public std::shared_ptr<T>
    4. Filter接口

      • 该接口提供一个函数实现Filter运算
      • virtual void apply (InputArray src, OutputArray dst, Stream &stream=Stream::Null())=0

    Filter数学模型

    createBoxFilter 盒式滤波

    • \texttt{K} = \alpha \begin{bmatrix} 1 & 1 & 1 & \cdots & 1 & 1 \\ 1 & 1 & 1 & \cdots & 1 & 1 \\ \dots & \dots & \dots & \dots & \dots & \dots \\ 1 & 1 & 1 & \cdots & 1 & 1 \end{bmatrix}

    • \alpha = \begin{cases} \dfrac{1}{kernel.width \times kernel.height} & \text{正则化=true} \\1 & \text{正则化=false} \end{cases}

    • 说明

      • 如果是规范化的则采用均值滤波,否则计算每个像素邻域上的各种积分特性(用来计算方差、协方差,平方和等等)
      • createBoxMaxFilter/createBoxMinFilter 的运算则是取区域内最小值与最大值,而不是均值。

    createColumnSumFilter 列求和滤波

    • 这是createBoxFilter滤波的垂直特例
      • Creates a vertical 1D box filter.

    createDerivFilter滤波

    • 该函数计算并返回空间图像导数的滤波器系数。
      • 如果 ksize=FILTER_SCHARR, 则返回Scharr 3×3 kernels.(Scharr后面介绍)
      • 否则, 返回Sobel kernels (Sobel后面介绍).

    createGaussianFilter滤波

    • G_x= \alpha *e^{-(x-( \texttt{ksize} -1)/2)^2/(2* \sigma^2)}

    • \alpha = \dfrac{1}{2 \pi \sigma}

    • G(x, y) = G_x \ast G_y

    • 其中\sigma可以按照x,y分别设置为两个\sigma_x, \sigma_y

    • 可以查找高斯概率表得到结果。

    kernel阶数 𝜎 kernel系数值
    1 0.5 1
    3 0.8 0.2390 0.5220 0.2390
    5 1.1 0.0708 0.2445 0.3695 0.2445 0.0708
    7 1.4 0.0290 0.1028 0.2232 0.2880 0.2232 0.1028 0.0290

    createLaplacianFilter 滤波

    • 二阶导数的滤波。

    • \texttt{dst} = \Delta \texttt{src} = \frac{\partial^2 \texttt{src}}{\partial x^2} + \frac{\partial^2 \texttt{src}}{\partial y^2}

    • \begin{aligned} \Delta^2 f &= \dfrac{\partial^2 f}{\partial x^2} + \dfrac{\partial^2 f}{\partial y^2} \\ &= f(x+1,y) + f(x-1,y) - 2f(x,y) + f(x,y+1) + f(x,y-1) - 2f(x,y) \\ &= (f(x+1,y) + f(x-1,y) + f(x,y+1) + f(x,y-1)) - 4f(x,y) \end{aligned}

    • 3阶核可以表示为矩阵

      • \begin{bmatrix} {0}&{1}&{0}\\{1}&{-4}&{1}\\{0}&{1}&{0}\end{bmatrix}

    createLinearFilter滤波

    • 这个函数对应OIpenCV的CPU实现函数filter2D,需要手工指定kernel。
      • 所有滤波都可以使用这个函数实现。
      • createSeparableLinearFilter函数把x,y分开指定核。

    createMorphologyFilter滤波

    • 是原图像与滤波的运算的结果,支持如下几种运算:
      1. cv::MORPH_ERODE = 0,
        • \texttt{dst} (x,y) = \min _{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y')
      2. cv::MORPH_DILATE = 1,
        • \texttt{dst} (x,y) = \max _{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y')
      3. cv::MORPH_OPEN = 2,
        • \texttt{dst} = \mathrm{open} ( \texttt{src} , \texttt{element} )= \mathrm{dilate} ( \mathrm{erode} ( \texttt{src} , \texttt{element}))
      4. cv::MORPH_CLOSE = 3,
        • \texttt{dst} = \mathrm{close} ( \texttt{src} , \texttt{element} )= \mathrm{erode} ( \mathrm{dilate} ( \texttt{src} , \texttt{element} ))
      5. cv::MORPH_GRADIENT = 4,
        • \texttt{dst} = \mathrm{morph\_grad} ( \texttt{src} , \texttt{element} )= \mathrm{dilate} ( \texttt{src} , \texttt{element} )- \mathrm{erode} ( \texttt{src} , \texttt{element} )
      6. cv::MORPH_TOPHAT = 5,
        • \texttt{dst} = \mathrm{tophat} ( \texttt{src} , \texttt{element} )= \texttt{src} - \mathrm{open} ( \texttt{src} , \texttt{element} )
      7. cv::MORPH_BLACKHAT = 6,
        • \texttt{dst} = \mathrm{blackhat} ( \texttt{src} , \texttt{element} )= \mathrm{close} ( \texttt{src} , \texttt{element} )- \texttt{src}
      8. cv::MORPH_HITMISS = 7
        • hit or miss

    createSobelFilter滤波

    • Sobel算子就是一阶导数,表示成模板是:
      • \texttt{Sobel}_x = \begin{bmatrix} {1}&{0}&{-1}\\{2}&{0}&{-2}\\{1}&{0}&{-1}\end{bmatrix}

    createScharrFilter滤波

    • Scharr算子与Sobel算子的差别在于增加了平滑运算。表示为模板就是:
      • \texttt{Scharr}_x = \begin{bmatrix} {3}&{0}&{-3} \\ {10}&{0}&{-10}\\ {3}&{0}&{-3}\end{bmatrix}

    Filter使用例子

    • 这个例子没有实现所有的Filter。
    1. 程序文件结构
    • 根目录c06_cuda_filter
      • 主程序代码
        • maio.cpp
      • 工程编译脚本
        • main.pro
      • 对话窗体代码
        • videodialog.h
        • videodialog.cpp
      • UI设计文件
        • dlgvideo.ui
      • 视频采集线程代码
        • videothread.h
        • videothread.cpp
    1. 编译执行过程

      1. qmake
      2. nmake (使用VC编译环境的记得执行vcvars64.bat)
    2. 主程序代码

      • 注意:
        1. 信号传递的对象类型,在Qt中下是需要注册类型的。main函数中注册类型的代码实际是这种编程模式的套路。
    • main.cpp
    
    #include <opencv2/opencv.hpp>
    #include <QtWidgets/QApplication>
    #include "videodialog.h"
    #include<QtCore/QMetaType>
    
    Q_DECLARE_METATYPE(cv::Mat);     // 申明可以使用信号传递的元数据类型
    
    int main(int argc, char *argv[]){
    
        QApplication  app(argc, argv);
        qRegisterMetaType<cv::Mat>("Mat");    // 注册数据类型:Mat
        qRegisterMetaType<cv::Mat>("Mat&");   // 注册数据类型:Mat& 引用类型
        YQDlgVideo dlg;
        dlg.show();
        return app.exec();
    }
    
    
    
    1. 对话窗体代码
      • 注意:
        1. Ui::dlg_video来自UI文件编译后的C++类。
    • videodialog.h
    #ifndef DIALOG_VIDEO_H
    #define DIALOG_VIDEO_H
    #include <opencv2/opencv.hpp>
    #include <QtWidgets/QDialog>
    #include "ui_dlgvideo.h"
    #include "videothread.h"
    
    class YQDlgVideo : public QDialog{
        Q_OBJECT
    private:
        Ui::dlg_video  *ui;
        YQThVideo *th;
        cv::Ptr<cv::cuda::Filter> filters[8];  // 滤波对象
        int filter_idx = 7;                    // 根据不同的收音机按钮对应不同的滤波,默认是最后一个
    public:
        YQDlgVideo(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
        virtual ~YQDlgVideo();
    
    public slots:
        void change_filter(bool);
        void show_video(cv::Mat);   // 不使用引用,注意引用的对象的生命周期
    };
    #endif
    
    
    • videodialog.cpp
    #include "videodialog.h"
    #include <iostream> 
    #include <sstream>
    
    YQDlgVideo::YQDlgVideo(QWidget *parent, Qt::WindowFlags f):
        ui(new Ui::dlg_video()), 
        QDialog(parent, f), 
        th(new YQThVideo()){
        ui->setupUi(this);
        // 初始化滤波对象 - 创建8个滤波,最后一个时NULL
        /*cv::cuda::createSobelFilter (int srcType, int dstType, int dx, int dy, int ksize=3, double scale=1, int rowBorderMode=BORDER_DEFAULT, int columnBorderMode=-1)*/
        filters[0] = cv::cuda::createSobelFilter(CV_8UC1, CV_8UC1, 1, 1, 3);
        /*cv::cuda::createLaplacianFilter (int srcType, int dstType, int ksize=1, double scale=1, int borderMode=BORDER_DEFAULT, Scalar borderVal=Scalar::all(0))*/
        filters[1] = cv::cuda::createLaplacianFilter(CV_8UC1, CV_8UC1, 3);
        /*cv::cuda::createDerivFilter (int srcType, int dstType, int dx, int dy, int ksize, bool normalize=false, double scale=1, int rowBorderMode=BORDER_DEFAULT, int columnBorderMode=-1)*/
        filters[2] = cv::cuda::createDerivFilter(CV_8UC1, CV_8UC1, 1, 1, 3);
        /*cv::cuda::createLinearFilter (int srcType, int dstType, InputArray kernel, Point anchor=Point(-1, -1), int borderMode=BORDER_DEFAULT, Scalar borderVal=Scalar::all(0))*/
        cv::Mat kernel = (cv::Mat_<float>(3,3) << -1.0f, 0.0f, 1.0f, -2.0f, 0.0f, 2.0f, -1.0f, 0.0f, 1.0f );
        filters[3] = cv::cuda::createLinearFilter(CV_8UC1, CV_8UC1, kernel);
        /*cv::cuda::createGaussianFilter (int srcType, int dstType, Size ksize, double sigma1, double sigma2=0, int rowBorderMode=BORDER_DEFAULT, int columnBorderMode=-1)*/
        filters[4] = cv::cuda::createGaussianFilter(CV_8UC1, CV_8UC1, cv::Size(3, 3), 1.0, 1.0);
        /*cv::cuda::createScharrFilter (int srcType, int dstType, int dx, int dy, double scale=1, int rowBorderMode=BORDER_DEFAULT, int columnBorderMode=-1)*/
        filters[5] = cv::cuda::createScharrFilter(CV_8UC1, CV_8UC1, 1, 0);  // dx >= 0 && dy >= 0 && dx+dy == 1
        /*cv::cuda::createMorphologyFilter (int op, int srcType, InputArray kernel, Point anchor=Point(-1, -1), int iterations=1)*/
        filters[6] = cv::cuda::createMorphologyFilter(cv::MORPH_GRADIENT , CV_8UC1, kernel);
        filters[7] = NULL;
    
        // 链接线程与窗体之间的信号
        QObject::connect(th, SIGNAL(show_video(cv::Mat)), this, SLOT(show_video(cv::Mat)));
        // 启动线程
        th->start();
    }
    YQDlgVideo::~YQDlgVideo(){
        // 窗体释放前,先终止线程
        th->terminate();
        delete th;
        delete ui;
        
    }
    void YQDlgVideo::change_filter(bool){
        // 获取选择的按钮
        QRadioButton *btn = (QRadioButton*)ui->bgp_filter->checkedButton();
        // 默认id从-2开始
        int btn_id = ui->bgp_filter->checkedId();
        switch(btn_id){
            case -2:
                filter_idx = 0;
                break;
            case -3:
                filter_idx = 1;
                break;
            case -4:
                filter_idx = 2;
                break;
            case -5:
                filter_idx = 3;
                break;
            case -6:
                filter_idx = 4;
                break;
            case -7:
                filter_idx = 5;
                break;
            case -8:
                filter_idx = 6;
                break;
            case -9:
                filter_idx = 7;
                break;
        }
        std::stringstream str;
        str << "选择的滤波:" << btn->text().toStdString() << "(idx = " << filter_idx << " )"; 
        this->setWindowTitle(str.str().data());
    }
    void  YQDlgVideo::show_video(cv::Mat video){
        if(video.empty()) return;   // 图像非空才显示
    
        // OpenCV到Qt的颜色格式转换
        cv::cvtColor(video, video, cv::COLOR_BGR2GRAY);   // 交换颜色通道
        // -----------------------------------
        if(filters[filter_idx]){
            cv::cuda::GpuMat  img_gpu, out_gpu;
            img_gpu = cv::cuda::GpuMat(video);
            filters[filter_idx]->apply(img_gpu, out_gpu);
            out_gpu.download(video);
        }
        // -----------------------------------
        // 转换为Qt图像
        QImage i_out(video.data, video.cols, video.rows, QImage::Format_Grayscale8);
        // 转换为组件能处理的像素格式
        QPixmap p_out = QPixmap::fromImage(i_out);
        // 显示图像
        ui->lbl_video->setPixmap(p_out);
        // 缩放图像适应组件大小
        ui->lbl_video->setScaledContents(true);
    }
    
    
    1. UI设计文件
      • 注意
        1. 组件关系。
        2. QButtonGroup的设计使用。
        3. QVBoxLayput的设计使用。
        4. UI文件会自动调用uic编译成:ui_dlgvideo.h
    UI元素设计
    • 设计对话窗体的槽函数
      • 槽函数处理收音机按钮的响应。
    添加槽函数
    • 信号与槽函数的链接编辑
      • 需要把收音机按钮的clicked/toggled信号与槽函数绑定。
    信号与槽的链接
    1. 视频采集线程代码
    • videothread.h
    #ifndef VIDEO_THREAD_H
    #define VIDEO_THREAD_H
    #include <opencv2/opencv.hpp>
    #include <QtCore/QThread>                   // 这个头文件需要在opencv的头文件后面
    
    class YQThVideo : public QThread{
        Q_OBJECT
    public:
        YQThVideo(QObject *parent = nullptr);
        virtual ~YQThVideo();
    
    protected:
        virtual void run();
    
    // 定义信号原型,不需要访问限定修饰符
    signals:
        void show_video(cv::Mat video_img);
    
    private:
        // 定义视频录制对象
        cv::VideoCapture  *device;
    };
    
    #endif
    
    
    • videothread.cpp
    #include "videothread.h"
    
    YQThVideo::YQThVideo(QObject *parent):
        device(new cv::VideoCapture(0)), QThread(parent){
    }
    
    YQThVideo::~YQThVideo(){
        device->release();
        delete device;
    }
    
    void YQThVideo::run(){
        while(true){
            cv::Mat img;            
            device->read(img);          // 录制视频图像
            emit show_video(img);       // 发送信号
            QThread::usleep(100);       // 停顿 100毫秒 = 100 000 微妙
        }
    }
    
    
    1. 编译脚本
    • main.pro
    # -------------编译时选项设置
    QMAKE_CXXFLAGS += /source-charset:utf-8
    QMAKE_CXXFLAGS += /execution-charset:utf-8
    
    # -------------QT的配置
    TEMPLATE        = app
    
    CONFIG         += debug
    CONFIG         += console
    CONFIG         += thread
    CONFIG         += qt
    
    QT             += core
    QT             += gui
    QT             += widgets
    
    # -------------OpenCV的配置
    INCLUDEPATH    += "C:\opencv\install\include"
    
    LIBS           += -L"C:\opencv\install\x64\vc16\lib" 
    LIBS           += -lopencv_core420d 
    LIBS           += -lopencv_imgcodecs420d 
    LIBS           += -lopencv_highgui420d 
    LIBS           += -lopencv_imgproc420d
    LIBS           += -lopencv_cudafilters420d
    LIBS           += -lopencv_cudaimgproc420d
    LIBS           += -lopencv_cudafeatures2d420d
    LIBS           += -lopencv_cudaobjdetect420d
    LIBS           += -lopencv_videoio420d
    
    # -------------源代码,头文件,ui文件
    SOURCES        += main.cpp
    SOURCES        += videodialog.cpp
    SOURCES        += videothread.cpp
    
    HEADERS        += videodialog.h
    HEADERS        += videothread.h
    
    FORMS          += dlgvideo.ui
    
    # -------------编译的最终执行文件
    TARGET          = main
    
    
    • 运行效果
    运行效果

    相关文章

      网友评论

          本文标题:CUDA04_OpenCV的GPU滤波

          本文链接:https://www.haomeiwen.com/subject/qbmqxhtx.html