OPENCV + CUDA 实现小波滤波

opencv中有cpu 和 gpu版本的DFT函数,及傅里叶变换的函数,可以实现dft滤波。但opencv中没有DWT,及小波变换。下面将介绍一下实现的方法。

傅里叶变换与小波变换都能实现滤波,不好说那个更好。但傅里叶变换有个缺点,对于图像处理来说,其在处理图像锐利边缘时,很容易出现边缘抖动的情况。原因如下图:

傅里叶变换只能使用余弦波,很难拟合出突变。具体的解释和小波变换的特点。可以参考知乎上的文章 https://zhuanlan.zhihu.com/p/44215123

代码部分主要参考了GitHub上实现方法,经过一些简单的修改以适应vc++,和高频滤波的需要。原链接https://github.com/pierrepaleo/PDWT

一. 安装cuda开发环境
网上可以方法找到,我装的是CUDA11.1

二. 通过VS新建一个CUDA项目

三.考入PDWT源代码

新建的cuda项目中有个默认的cu文件,可以删了。然后导入PDWT的源代码

四.修改部分代码以适配VC++

原代码是在linux,gcc下开发的,直接编译会报错。需要对其中几个地方进行修改

1.修改utils.h

在文件开发加入下面一段

#include <vector> //为了修改VC++必须常数初始化数组的问题,导入vector

#define uint unsigned int 
#define __intptr_t intptr_t 

#ifdef _MSC_VER
#define strcasecmp stricmp
#define strncasecmp  strnicmp 
#endif

2.修改必须常数初始化数值的问题

VC++ 有限制,必须使用常数初始化数值,而gcc是没有这样的要求的。所以代码中好几处使用变量初始化数组的地方都要修改,修改方法如下。整个源码中有好几处,全部修改即可。

//int tNr[levels+1]; tNr[0] = Nr;
//int tNc[levels+1]; tNc[0] = Nc;
std::vector<int> std_tNr(levels+1);std::vector<int> std_tNc(levels+1);
int* tNr = &std_tNr[0]; tNr[0] = Nr;
int* tNc = &std_tNc[0]; tNc[0] = Nc;

3.修改软阈值函数

原PDWT代码可能是用于压缩的,对所有层都进行了阈值处理。由于是要进行高频滤波,只对第一层进行阈值处理,所以对代码进行了简单修改。如果需要完善,应该新开一个函数,可以对任意层进行阈值处理,这里就偷懒了,只改了一个函数。文件名 common.cu,第239行

/*
修改nlevels - > 1 ,只处理第一层
*/    
for (int i = 0; i < 1; i++) {
//    for (int i = 0; i < nlevels; i++) {
        if (!do_swt) {
            if (ndims > 1) w_div2(&Nr);
            w_div2(&Nc);
        }
        if (normalize > 0) beta /= SQRT_2;
        n_blocks = dim3(w_iDivUp(Nc, tpb), w_iDivUp(Nr, tpb), 1);
        if (ndims > 1) w_kern_soft_thresh<<<n_blocks, n_threads_per_block>>>(d_coeffs[3*i+1], d_coeffs[3*i+2], d_coeffs[3*i+3], beta, Nr, Nc);
        else w_kern_soft_thresh_1d<<<n_blocks, n_threads_per_block>>>(d_coeffs[i+1], beta, Nr, Nc);
    }

五.配置OPENCV

这个也不详细介绍了,需要注意,要使用但GPU OPENCV,下面的代码直接用GpuMat 传递数据

六.opencv 调用PDWT

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "wt.h"
#include "io.h"
#include <opencv2/imgcodecs.hpp>
#include <iostream>

using namespace cv;

Mat img = imread("demo.png",IMREAD_GRAYSCALE); //读入图片
img.convertTo(img,CV_32FC1); //转换为float类型
cuda::GpuMat img_gpu;
img_gpu.upload(img); //图片读入GPU
/*
初始化小波变换类,第一个参数设置为0 因为图片数据我是直接用GPU传入而不是使用CPU内存传入
小波基函数使用了haar,当然可以修改,支持的类型可以在filters.cpp 里看到
小波变换的层数使用了3 高频滤波好像够了 其他参数默认就好了
*/
Wavelets W(0, img.rows, img.cols, "haar", 3, 1, 1, 0, 0); 
/*
传入图片,第一个参数是图片的gpu显存指针,第二参数设1,表明传入的是显存指针,如果设0,就是内存指针。
*/
W.set_image((float*)img_gpu.cudaPtr(),1); 
W.forward(); //进行小波变换
W.soft_threshold(750.0, 0, 0);//阈值可以自行修改,由于前面修改了代码,这里只处理第一层
W.inverse();//反变换
/*
这个get_image函数也是修改过的,参考了set_image,使其支持gpu显存指针。具体修改就不贴出来了,很简单。
这样处理的原因是为了整个变换都在显存中进行。实际应用时,小波变换必定是图像处理的其中一个环节,而频繁使用内存 显存切换 是非常耗时间的。
*/
W.get_image((float*)img_gpu.cudaPtr(),1);
/*
想看一下效果。可以参考下面代码保存变换后的图片
*/
Mat savemat;
img_gpu.download(savemat);
savemat.convertTo(savemat,CV_8UC1);
imwrite("inverse.png",savemat);

效果

变换前

变换后

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注