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

举报
ShaderJoy 发表于 2022/01/01 01:05:08 2022/01/01
【摘要】 效果如图:   非局部均值滤波(Non Local Means)算法其出发点是——在同一幅图像中对具有相同性质的区域进行分类并加权平均得到的图片,应该降噪效果也会越好。意味着它使用的是图像中的所有像素(实际上是在一个搜索窗口内的所有像素),这些像素根据某种相似度进行加权平均。与双线性滤波、中值滤波等利用图像局部信息来滤...

效果如图:

 

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

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

 

代码所依据的论文是 

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


  
  1. __device__ __forceinline__ float norm2(const float& v) { return v*v; }
  2. __device__ __forceinline__ float norm2(const float2& v) { return v.x*v.x + v.y*v.y; }
  3. __device__ __forceinline__ float norm2(const float3& v) { return v.x*v.x + v.y*v.y + v.z*v.z; }
  4. __device__ __forceinline__ float norm2(const float4& v) { return v.x*v.x + v.y*v.y + v.z*v.z + v.w*v.w; }
  5. template<typename T, typename B>
  6. __global__ void nlm_kernel(const PtrStep<T> src, PtrStepSz<T> dst, const B b, int search_radius, int block_radius, float noise_mult)
  7. {
  8. typedef typename TypeVec<float, VecTraits<T>::cn>::vec_type value_type;
  9. const int i = blockDim.y * blockIdx.y + threadIdx.y;
  10. const int j = blockDim.x * blockIdx.x + threadIdx.x;
  11. if (j >= dst.cols || i >= dst.rows)
  12. return;
  13. int bsize = search_radius + block_radius;
  14. int search_window = 2 * search_radius + 1;
  15. float minus_search_window2_inv = -1.f/(search_window * search_window);
  16. // value_type: 比如 float4
  17. value_type sum1 = VecTraits<value_type>::all(0);
  18. float sum2 = 0.f;
  19. // 非边界情况
  20. if (j - bsize >= 0 && j + bsize < dst.cols && i - bsize >= 0 && i + bsize < dst.rows)
  21. {
  22. // 遍历搜索窗口的像素
  23. for(float y = -search_radius; y <= search_radius; ++y)
  24. for(float x = -search_radius; x <= search_radius; ++x)
  25. {
  26. float dist2 = 0;
  27. // 遍历邻域的像素
  28. for(float ty = -block_radius; ty <= block_radius; ++ty)
  29. for(float tx = -block_radius; tx <= block_radius; ++tx)
  30. {
  31. // 在搜索窗口内的邻域
  32. value_type bv = saturate_cast<value_type>(src(i + y + ty, j + x + tx));
  33. // 在当前像素的邻域
  34. value_type av = saturate_cast<value_type>(src(i + ty, j + tx));
  35. // 累加像素差分向量的内积
  36. dist2 += norm2(av - bv);
  37. }
  38. // 计算高斯权重(搜索窗口的像素离当前像素越远,权值越小;之前统计的二范数越大,权重越小)
  39. float w = __expf(dist2 * noise_mult + (x * x + y * y) * minus_search_window2_inv);
  40. /*if (i == 255 && j == 255)
  41. printf("%f %f\n", w, dist2 * minus_h2_inv + (x * x + y * y) * minus_search_window2_inv);*/
  42. // 加权平均
  43. sum1 = sum1 + w * saturate_cast<value_type>(src(i + y, j + x));
  44. // 累加权重
  45. sum2 += w;
  46. }
  47. }
  48. else // 边界情况
  49. {
  50. for(float y = -search_radius; y <= search_radius; ++y)
  51. for(float x = -search_radius; x <= search_radius; ++x)
  52. {
  53. float dist2 = 0;
  54. for(float ty = -block_radius; ty <= block_radius; ++ty)
  55. for(float tx = -block_radius; tx <= block_radius; ++tx)
  56. {
  57. //cudev::BrdConstant,
  58. //cudev::BrdReplicate,
  59. //cudev::BrdReflect,
  60. //cudev::BrdWrap,
  61. //cudev::BrdReflect101
  62. // b 是 OpenCV 提供的边界处理的情况
  63. value_type bv = saturate_cast<value_type>(b.at(i + y + ty, j + x + tx, src));
  64. value_type av = saturate_cast<value_type>(b.at(i + ty, j + tx, src));
  65. dist2 += norm2(av - bv);
  66. }
  67. float w = __expf(dist2 * noise_mult + (x * x + y * y) * minus_search_window2_inv);
  68. sum1 = sum1 + w * saturate_cast<value_type>(b.at(i + y, j + x, src));
  69. sum2 += w;
  70. }
  71. }
  72. dst(i, j) = saturate_cast<T>(sum1 / sum2);
  73. }
  74. template<typename T, template <typename> class B>
  75. void nlm_caller(const PtrStepSzb src, PtrStepSzb dst, int search_radius, int block_radius, float h, cudaStream_t stream)
  76. {
  77. dim3 block (32, 8);
  78. dim3 grid (divUp (src.cols, block.x), divUp (src.rows, block.y));
  79. // 超出边界时使用
  80. B<T> b(src.rows, src.cols);
  81. int block_window = 2 * block_radius + 1;
  82. float minus_h2_inv = -1.f/(h * h * VecTraits<T>::cn);
  83. float noise_mult = minus_h2_inv/(block_window * block_window);
  84. cudaSafeCall( cudaFuncSetCacheConfig (nlm_kernel<T, B<T> >, cudaFuncCachePreferL1) );
  85. nlm_kernel<<<grid, block>>>((PtrStepSz<T>)src, (PtrStepSz<T>)dst, b, search_radius, block_radius, noise_mult);
  86. cudaSafeCall ( cudaGetLastError () );
  87. if (stream == 0)
  88. cudaSafeCall( cudaDeviceSynchronize() );
  89. }

 

文章来源: panda1234lee.blog.csdn.net,作者:panda1234lee,版权归原作者所有,如需转载,请联系作者。

原文链接:panda1234lee.blog.csdn.net/article/details/88016834

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。