在Open4.2的GPU计算中,图像的滤波处理单独构成一个模块,本主题梳理相关Filter的使用,毕竟滤波运算是机器视觉非常基础的技术。OpenCV封装的还是常见的、应用比较成熟的滤波运算。最后写了一个小程序测试体验了一下,GPU的运算确实让视频的图像处理更加流畅。
OpenCV的Cuda滤波API
- 有OpenCV的cuda编程模式作为基础,OpenCV提供的Filter封装使用也就比较简单。
- OpenCV提供的cuda滤波与原来提供的CPU滤波在设计上已经有很大的不同。
-
使用模式
- 创建Filter,返回的是执政对象(共享指针)
- 使用Filter应用于图像。
-
OpenCV的cuda运算提供的Filter运算有(与CPU的运算需要的条件一致):
- createBoxFilter/createBoxMaxFilter/createBoxMinFilter
- createColumnSumFilter / createRowSumFilter
- createDerivFilter
- createGaussianFilter
- createLaplacianFilter
- createLinearFilter / createSeparableLinearFilter
- createMorphologyFilter
- createScharrFilter
- createSobelFilter
-
Ptr类型
- 这是继承C++11标准引入的共享指针的类型。
struct Ptr : public std::shared_ptr<T>
-
Filter接口
- 该接口提供一个函数实现Filter运算
virtual void apply (InputArray src, OutputArray dst, Stream &stream=Stream::Null())=0
Filter数学模型
createBoxFilter 盒式滤波
-
-
-
说明
- 如果是规范化的则采用均值滤波,否则计算每个像素邻域上的各种积分特性(用来计算方差、协方差,平方和等等)
- createBoxMaxFilter/createBoxMinFilter 的运算则是取区域内最小值与最大值,而不是均值。
createColumnSumFilter 列求和滤波
- 这是createBoxFilter滤波的垂直特例
- Creates a vertical 1D box filter.
createDerivFilter滤波
- 该函数计算并返回空间图像导数的滤波器系数。
- 如果 ksize=FILTER_SCHARR, 则返回Scharr 3×3 kernels.(Scharr后面介绍)
- 否则, 返回Sobel kernels (Sobel后面介绍).
createGaussianFilter滤波
-
-
-
-
其中
可以按照
分别设置为两个
-
可以查找高斯概率表得到结果。
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 滤波
-
二阶导数的滤波。
-
-
-
3阶核可以表示为矩阵
createLinearFilter滤波
- 这个函数对应OIpenCV的CPU实现函数filter2D,需要手工指定kernel。
- 所有滤波都可以使用这个函数实现。
- createSeparableLinearFilter函数把
分开指定核。
createMorphologyFilter滤波
- 是原图像与滤波的运算的结果,支持如下几种运算:
- cv::MORPH_ERODE = 0,
- cv::MORPH_DILATE = 1,
- cv::MORPH_OPEN = 2,
- cv::MORPH_CLOSE = 3,
- cv::MORPH_GRADIENT = 4,
- cv::MORPH_TOPHAT = 5,
- cv::MORPH_BLACKHAT = 6,
- cv::MORPH_HITMISS = 7
- hit or miss
- cv::MORPH_ERODE = 0,
createSobelFilter滤波
- Sobel算子就是一阶导数,表示成模板是:
createScharrFilter滤波
- Scharr算子与Sobel算子的差别在于增加了平滑运算。表示为模板就是:
Filter使用例子
- 这个例子没有实现所有的Filter。
- 程序文件结构
- 根目录c06_cuda_filter
- 主程序代码
- maio.cpp
- 工程编译脚本
- main.pro
- 对话窗体代码
- videodialog.h
- videodialog.cpp
- UI设计文件
- dlgvideo.ui
- 视频采集线程代码
- videothread.h
- videothread.cpp
- 主程序代码
-
编译执行过程
- qmake
- nmake (使用VC编译环境的记得执行vcvars64.bat)
-
主程序代码
- 注意:
- 信号传递的对象类型,在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();
}
- 对话窗体代码
- 注意:
- 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);
}
- UI设计文件
- 注意
- 组件关系。
- QButtonGroup的设计使用。
- QVBoxLayput的设计使用。
- UI文件会自动调用uic编译成:
ui_dlgvideo.h
- 注意
![](https://img.haomeiwen.com/i13618185/477e77e0ec29021e.png)
- 设计对话窗体的槽函数
- 槽函数处理收音机按钮的响应。
![](https://img.haomeiwen.com/i13618185/54cb5d4322f801aa.png)
- 信号与槽函数的链接编辑
- 需要把收音机按钮的clicked/toggled信号与槽函数绑定。
![](https://img.haomeiwen.com/i13618185/3c9273ff66299143.png)
- 视频采集线程代码
- 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 微妙
}
}
- 编译脚本
- 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
- 运行效果
![](https://img.haomeiwen.com/i13618185/e2f241bcfebf2bd2.png)
网友评论