OpenCV 的 Non Local Means(CUDA 版) 源码解析

47 篇文章 13 订阅
40 篇文章 17 订阅

效果如图:

 

非局部均值滤波(Non Local Means)算法其出发点是——在同一幅图像中对具有相同性质的区域进行分类并加权平均得到的图片,应该降噪效果也会越好。意味着它使用的是图像中的所有像素(实际上是在一个搜索窗口内的所有像素),这些像素根据某种相似度进行加权平均。与双线性滤波、中值滤波等利用图像局部信息来滤波不同,它利用了整幅图像进行去噪。即以图像块(邻域)为单位在图像中寻找相似区域,再对这些区域取平均,较好地滤除图像中的高斯噪声。

图像邻域的相似度该怎么进行评价呢?由两两图像块(邻域)的像素颜色的欧氏距离来进行衡量,这也很容易理解,因为有噪声,单独的一个像素并不可靠,所以使用它们的邻域,只有邻域相似度高才能说这两个像素的相似度高。实际上在求欧式距离的时候,不同位置的像素的权重也是不一样的,距离块的中心越近,权重越大,距离中心越远,权重越小,权重服从高斯分布。

 

代码所依据的论文是 

 代码和注释如下(注意,src 的访问还是按照 row, col 的顺序)

        __device__ __forceinline__ float norm2(const float& v) { return v*v; }
        __device__ __forceinline__ float norm2(const float2& v) { return v.x*v.x + v.y*v.y; }
        __device__ __forceinline__ float norm2(const float3& v) { return v.x*v.x + v.y*v.y + v.z*v.z; }
        __device__ __forceinline__ float norm2(const float4& v) { return v.x*v.x + v.y*v.y + v.z*v.z  + v.w*v.w; }

        template<typename T, typename B>
        __global__ void nlm_kernel(const PtrStep<T> src, PtrStepSz<T> dst, const B b, int search_radius, int block_radius, float noise_mult)
        {
            typedef typename TypeVec<float, VecTraits<T>::cn>::vec_type value_type;

            const int i = blockDim.y * blockIdx.y + threadIdx.y;
            const int j = blockDim.x * blockIdx.x + threadIdx.x;

            if (j >= dst.cols || i >= dst.rows)
                return;

            int bsize = search_radius + block_radius;
            int search_window = 2 * search_radius + 1;
            float minus_search_window2_inv = -1.f/(search_window * search_window);

            // value_type: 比如 float4
            value_type sum1 = VecTraits<value_type>::all(0);
            float sum2 = 0.f;

            // 非边界情况
            if (j - bsize >= 0 && j + bsize < dst.cols && i - bsize >= 0 && i + bsize < dst.rows)
            {
                // 遍历搜索窗口的像素
                for(float y = -search_radius; y <= search_radius; ++y)
                    for(float x = -search_radius; x <= search_radius; ++x)
                    {
                        float dist2 = 0;
                        // 遍历邻域的像素
                        for(float ty = -block_radius; ty <= block_radius; ++ty)
                            for(float tx = -block_radius; tx <= block_radius; ++tx)
                            {
                                // 在搜索窗口内的邻域
                                value_type bv = saturate_cast<value_type>(src(i + y + ty, j + x + tx));                   
                                // 在当前像素的邻域
                                value_type av = saturate_cast<value_type>(src(i +     ty, j +     tx));
                                // 累加像素差分向量的内积
                                dist2 += norm2(av - bv);
                            }
                        // 计算高斯权重(搜索窗口的像素离当前像素越远,权值越小;之前统计的二范数越大,权重越小)
                        float w = __expf(dist2 * noise_mult + (x * x + y * y) * minus_search_window2_inv);

                        /*if (i == 255 && j == 255)
                            printf("%f %f\n", w, dist2 * minus_h2_inv + (x * x + y * y) * minus_search_window2_inv);*/
                        // 加权平均
                        sum1 = sum1 + w * saturate_cast<value_type>(src(i + y, j + x));
                        // 累加权重                                    
                        sum2 += w;
                    }
            }
            else // 边界情况
            {
                for(float y = -search_radius; y <= search_radius; ++y)
                    for(float x = -search_radius; x <= search_radius; ++x)
                    {
                        float dist2 = 0;
                        for(float ty = -block_radius; ty <= block_radius; ++ty)
                            for(float tx = -block_radius; tx <= block_radius; ++tx)
                            {
                             //cudev::BrdConstant,
                             //cudev::BrdReplicate,
                             //cudev::BrdReflect,
                             //cudev::BrdWrap,
                             //cudev::BrdReflect101
                             // b 是 OpenCV 提供的边界处理的情况
                                value_type bv = saturate_cast<value_type>(b.at(i + y + ty, j + x + tx, src));
                                value_type av = saturate_cast<value_type>(b.at(i +     ty, j +     tx, src));
                                dist2 += norm2(av - bv);
                            }

                        float w = __expf(dist2 * noise_mult + (x * x + y * y) * minus_search_window2_inv);

                        sum1 = sum1 + w * saturate_cast<value_type>(b.at(i + y, j + x, src));
                        sum2 += w;
                    }

            }

            dst(i, j) = saturate_cast<T>(sum1 / sum2);

        }

        template<typename T, template <typename> class B>
        void nlm_caller(const PtrStepSzb src, PtrStepSzb dst, int search_radius, int block_radius, float h, cudaStream_t stream)
        {
            dim3 block (32, 8);
            dim3 grid (divUp (src.cols, block.x), divUp (src.rows, block.y));

            // 超出边界时使用
            B<T> b(src.rows, src.cols);

            int block_window = 2 * block_radius + 1;
            float minus_h2_inv = -1.f/(h * h * VecTraits<T>::cn);
            float noise_mult = minus_h2_inv/(block_window * block_window);

            cudaSafeCall( cudaFuncSetCacheConfig (nlm_kernel<T, B<T> >, cudaFuncCachePreferL1) );
            nlm_kernel<<<grid, block>>>((PtrStepSz<T>)src, (PtrStepSz<T>)dst, b, search_radius, block_radius, noise_mult);
            cudaSafeCall ( cudaGetLastError () );

            if (stream == 0)
                cudaSafeCall( cudaDeviceSynchronize() );
        }

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShaderJoy

您的打赏是我继续写博客的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值