美文网首页
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滤波

      在Open4.2的GPU计算中,图像的滤波处理单独构成一个模块,本主题梳理相关Filter的使用,毕竟滤波运算...

  • OpenCV For iOS(六)方框、均值、高斯、中值、双边滤

    本节主要记录OpenCV 两类五种常见的滤波方式: 线性滤波:方框滤波、均值滤波、高斯滤波非线性滤波: 中值滤波、...

  • 2018-02-26周一~图像处理 滤波

    滤波,就是卷积,还可以等于频率函数相乘 滤波,分空间滤波和频域滤波 空间滤波,就是卷积 频域滤波,...

  • 7.6 2D卷积

    OpencV提供了多种滤波方式,来实现平滑图像的效果,例如均值滤波、方框滤波、高斯滤波、中值滤波等,大多数滤波方式...

  • opencv python版-lesson 16

    均值滤波,高斯滤波,双边滤波

  • OpenCV系列七 --- 非线性滤波

    上一篇我们学习了了线性滤波(方框滤波、均值滤波以及高斯滤波),这节呢,我们来学习一下非线性滤波(中值滤波、双边滤波...

  • OpenCV-Python学习(九):图像滤波

    目录: 1.滤波的相关概念 2.卷积操作 3.平滑操作(低通滤波)均值滤波中值滤波高斯滤波双边滤波 4.锐化操作(...

  • OpenCV学习笔记(七)中值、双边滤波

    一、线性滤波与非线性滤波 之前一篇文章说的方框滤波、均值滤波和高斯滤波都是线性滤波器的原始数据与滤波结果是一种线性...

  • 滤波算法总结

    1、限幅滤波法(又称程序判断滤波法) 2、中位值滤波法 3、算术平均滤波法 4、递推平均滤波法(又称滑动平均滤波法...

  • 数字图像处理之空间域滤波

    空间域滤波大体分为两类:平滑滤波、锐化滤波 1、平滑滤波:模糊处理,用于减小噪声,实际上是低通滤波。典型的滤波器是...

网友评论

      本文标题:CUDA04_OpenCV的GPU滤波

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