Side Window Filtering 边窗滤波

news2024/11/25 9:38:55

原理分析

        通常用常规图像算法做检测类的算法需要将图像特征增强,其中就需要滤波,把噪点去掉,如果直接用滤波,像高斯滤波,中值滤波,均值滤波等等,不仅会把噪点过滤掉,也会把图像的一些细节,包括边缘给模糊掉,从而造成图像信息丢失。有时候为了保留边缘及一些细节信息,就会用到保边滤波算法。之前用过的保边滤波包括双边滤波,表面模糊等算法。这两个算法都比较慢,对于检测类的算法来说,性价比不高。

        传统滤波算法以滤波器的核中心与要处理的像素对齐进行计算,不考虑要处理的像素是否是在边缘区域,导致滤波窗口在跨越边缘时,引入对来自于边缘两侧像素值的加权计算。一旦经过这样的计算,将会导致边缘信息在沿边缘法线的方向出现扩散,导致边缘信息减弱或者丢失。比如对于均值滤波来说:

 

中心像素处在边缘处经过均值滤波后,像素值会被拉高

        为了避免传统滤波算法对边缘的弱化,现在考虑将待处理的像素不一定非要与滤波器核心对齐。

       下面拿均值滤波来说明。对于平坦区域,均值滤波不管滤波器用什么形状的滤波核,其计算结果都跟原像素相差不大。对于处在边缘的像素,若想尽可能的保留边缘,我们需要尽可能的保留跟当前要计算的像素相似的区块中的像素进行计算,而跟当前要计算的像素相差很大的区块中的像素要尽可能抛掉。这个思路跟表面模糊算法很像。这里稍微介绍下表面模糊算法:

 

        从上面公式可以看出,表面模糊算法的主要思想还是计算当前像素X的邻域范围内不同像素的加权求和,如果跟中心像素差的越多,则权值越小,跟中心像素相差越小,则权值越大,以此来保留边缘信息。上面公式中如果Y值取得非常大,那么表面模糊算法跟均值滤波算法就是一样的。

        如下图是典型的理想边缘模型,分别是阶跃边缘、斜坡边缘和屋顶型边缘。我们做均值滤波,计算下图中红点所在像素的新像素值时,肯定希望尽可能用到跟红点在同一个面上的区块像素,而抛掉跟红点不在同一个平面的区块像素,这样就可以尽可能的保留红点所在的边缘,而不是弱化红点所在的边缘。

 

        为了达到上述目的,我们可以预设一些滤波器,这些滤波器内部就有边缘,并且边缘类型就是上图中阶跃边缘类型,它被一切为2块。这样在对图像的边缘块的像素进行计算时,就可以让这样滤波器模型跟实际要计算的像素块所处的边缘模型进行匹配,如果能够匹配上,那么红点处计算的像素值将不会有太大变化,如果匹配是相反的,那么红点处计算的新值将和原来的值差很多。

       滤波器中边缘模型有很多种,为了便于计算,只取其中8种。其它形状的滤波器由于有锯齿形状,不好遍历计算(尤其是在滤波核半径如果比较大的时候,那时如果有锯齿形状就只能一行行遍历,不能for循环)因此没有被采用。

 

        上图8类滤波器中每个滤波器被明显分成两个块,即是有边缘的,并且形状不一,最重要的是其中的值分布是呈正方形或者长方形,非常便于遍历计算。我们只需要用上述面8种滤波器分别跟待计算的像素做匹配,然后计算对应的新的像素值,当该新的像素值跟原像素值相差的最小时,说明对应的滤波器的边缘形状,跟待计算的像素所在区块的边缘形状最接近,这时采取的均值滤波对于边缘处的像素弱化最小。

        上面是我根据自己的理解对侧窗滤波算法的介绍。原论文里面有公式推导,也有分析滤波器侧窗的形状的公式,比较严谨,但是感觉不太直观。我按照自己的思考提供一种理解方式吧,如果有疑问,或者认为我的理解有误的,欢迎指正哈,谢谢!

#include <fstream>
#include <opencv2/opencv.hpp>
#include <string>
using namespace std;
using namespace cv;
 
#include <math.h>
#include <cstdio>
#include <cstdlib>
enum class SIDE_DIRECTIONS : int {
  L = 0,
  R = 1,
  U = 2,
  D = 3,
  NW = 4,
  NE = 5,
  SW = 6,
  SE = 7,
  SIDE_DIRECTIONS_COUNT = 8
};
 
const int side_directions_count = 8;
 
enum class XY_START_END : int {
  WIDTH_START = 0,
  WIDTH_END = 1,
  HEIGHT_START = 2,
  HEIGHT_END = 3,
  XY_START_END_COUNT = 4
};
 
//表面模糊处理
void SurfaceBlur(float *data, int height, int width, int r, int threshold,
                 float *result) {
  for (int i = 0; i < height; ++i) {
    for (int j = 0; j < width; ++j) {
      float sumx = 0.f;
      float sumy = 0.f;
      for (int m = i - r; m < i + r; ++m) {
        for (int n = j - r; n < j + r; ++n) {
          sumx = sumx + (1 - fabs(data[i * width + j] - data[m * width + n]) /
                                 (2.5 * threshold)) *
                            data[m * width + n];
          sumy = sumy + (1 - fabs(data[i * width + j] - data[m * width + n]) /
                                 (2.5 * threshold));
        }
      }
      result[i * width + j] = sumx / sumy;
    }
  }
}
 
std::vector<std::vector<int> > m_side_window_params;
std::vector<std::vector<float> > m_side_window_filter_results;
void init(int height, int width, int radius) {
  int panel_size = height * width;
 
  m_side_window_params.resize(side_directions_count);
 
  m_side_window_params[static_cast<int>(SIDE_DIRECTIONS::L)] = {
      -radius, 0, -radius, radius};
  m_side_window_params[static_cast<int>(SIDE_DIRECTIONS::R)] = {
      0, radius, -radius, radius};
  m_side_window_params[static_cast<int>(SIDE_DIRECTIONS::U)] = {-radius, radius,
                                                                -radius, 0};
  m_side_window_params[static_cast<int>(SIDE_DIRECTIONS::D)] = {-radius, radius,
                                                                0, radius};
  m_side_window_params[static_cast<int>(SIDE_DIRECTIONS::NW)] = {-radius, 0,
                                                                 -radius, 0};
  m_side_window_params[static_cast<int>(SIDE_DIRECTIONS::NE)] = {0, radius,
                                                                 -radius, 0};
  m_side_window_params[static_cast<int>(SIDE_DIRECTIONS::SW)] = {-radius, 0, 0,
                                                                 radius};
  m_side_window_params[static_cast<int>(SIDE_DIRECTIONS::SE)] = {0, radius, 0,
                                                                 radius};
 
  m_side_window_filter_results.resize(side_directions_count);
  for (int i = 0; i < side_directions_count; ++i) {
    m_side_window_filter_results[i].resize(panel_size);
  }
}
 
void sideWindowFilterImpl(const float *input, const int radius,
                          const int height, const int width,
                          const SIDE_DIRECTIONS direction, float *output) {
  const int w_start =
      m_side_window_params[static_cast<int>(direction)]
                          [static_cast<int>(XY_START_END::WIDTH_START)];
  const int w_end =
      m_side_window_params[static_cast<int>(direction)]
                          [static_cast<int>(XY_START_END::WIDTH_END)];
  const int h_start =
      m_side_window_params[static_cast<int>(direction)]
                          [static_cast<int>(XY_START_END::HEIGHT_START)];
  const int h_end =
      m_side_window_params[static_cast<int>(direction)]
                          [static_cast<int>(XY_START_END::HEIGHT_END)];
 
  const int w_first_loop_end = std::max(0, -w_start);
  const int w_second_loop_start = w_first_loop_end;
  const int w_second_loop_end = width - 1 - w_end;
  const int w_third_loop_start = width - w_end;
 
  int nn = 0;
  int remain_start = (nn << 2) + w_second_loop_start;
 
  int out_idx = 0;
  int threshold = 10000;
  for (int h = 0; h < height; ++h) {
    const int h_lower_bound = std::max(0, h + h_start);
    const int h_upper_bound = std::min(height - 1, h + h_end);
    const int h_interval = h_upper_bound - h_lower_bound + 1;
 
    //第一个for循环是处理图像边缘
    for (int w = 0; w < w_first_loop_end; ++w) {
      const int w_left_bound = std::max(0, w + w_start);
      const int w_right_bound = std::min(width - 1, w + w_end);
      const int arr_len = h_interval * (w_right_bound - w_left_bound + 1);
 
      int idx = 0;
      float sumx = 0.f;
      float sumy = 0.f;
      float center_pixel = input[h * width + w];
      for (int i = h_lower_bound; i <= h_upper_bound; ++i) {
        const int h_idx = i * width;
        for (int j = w_left_bound; j <= w_right_bound; ++j) {
          sumx = sumx + (1 - fabs(center_pixel - input[h_idx + j]) /
                                 (2.5 * threshold)) *
                            input[h_idx + j];
          sumy = sumy + (1 - fabs(center_pixel - input[h_idx + j]) /
                                 (2.5 * threshold));
        }
      }
      output[out_idx++] = sumx / sumy;
    }
 
    //这里是处理图像中间的任意像素
    for (int w = remain_start; w <= w_second_loop_end; ++w) {
      const int w_left_bound = std::max(0, w + w_start);
      const int w_right_bound = std::min(width - 1, w + w_end);
 
      int idx = 0;
      float sumx = 0.f;
      float sumy = 0.f;
      float center_pixel = input[h * width + w];
      for (int i = h_lower_bound; i <= h_upper_bound; ++i) {
        const int h_idx = i * width;
        for (int j = w_left_bound; j <= w_right_bound; ++j) {
          sumx = sumx + (1 - fabs(center_pixel - input[h_idx + j]) /
                                 (2.5 * threshold)) *
                            input[h_idx + j];
          sumy = sumy + (1 - fabs(center_pixel - input[h_idx + j]) /
                                 (2.5 * threshold));
        }
      }
      output[out_idx++] = sumx / sumy;//这里是基于表面模糊的侧窗滤波算法,
      //如果你想改成基于均值滤波的侧窗滤波算法,那么就统计下上面那两个for循环的
      //像素个数,及累加的像素值的和,然后在这里求下比值赋值给output[out_idx++]就行了
    }
 
    for (int w = w_third_loop_start; w < width; ++w) {
      const int w_left_bound = std::max(0, w + w_start);
      const int w_right_bound = std::min(width - 1, w + w_end);
      const int arr_len = h_interval * (w_right_bound - w_left_bound + 1);
 
      int idx = 0;
      float sumx = 0.f;
      float sumy = 0.f;
 
      float center_pixel = input[h * width + w];
      for (int i = h_lower_bound; i <= h_upper_bound; ++i) {
        const int h_idx = i * width;
        for (int j = w_left_bound; j <= w_right_bound; ++j) {
          sumx = sumx + (1 - fabs(center_pixel - input[h_idx + j]) /
                                 (2.5 * threshold)) *
                            input[h_idx + j];
          sumy = sumy + (1 - fabs(center_pixel - input[h_idx + j]) /
                                 (2.5 * threshold));
        }
      }
      output[out_idx++] = sumx / sumy;
    }
  }
}
 
void SideWindowFilter(const float *input, const int radius, const int height,
                      const int width, float *output) {
  int panel_size = height * width;
 
  for (int i = 0; i < side_directions_count; ++i) {
    sideWindowFilterImpl(input, radius, height, width,
                         static_cast<SIDE_DIRECTIONS>(i),
                         m_side_window_filter_results[i].data());
  }
 
  for (int i = 0; i < panel_size; ++i) {
    float tmp = m_side_window_filter_results[0][i] - input[i];
    float min = tmp * tmp;
    int min_idx = 0;
    for (int j = 1; j < side_directions_count; ++j) {
      tmp = m_side_window_filter_results[j][i] - input[i];
      tmp = tmp * tmp;
      if (min > tmp) {
        min_idx = j;
        min = tmp;
      }
    }
 
    output[i] = m_side_window_filter_results[min_idx][i];
  }
}
 
int main() {
  cv::Mat sourceimg = imread("src_img/降噪test.jpg", 1);
  cv::Mat resultimg = imread("src_img/降噪test.jpg", 1);
  cv::imwrite("0_sourceimg.jpg", sourceimg);
 
  int img_h = sourceimg.rows;
  int img_w = sourceimg.cols;
 
  cv::Mat temp_img;
  temp_img.create(img_h, img_w, CV_32FC3);
 
  sourceimg.convertTo(temp_img, CV_32FC3);
 
  float *input_r = new float[img_h * img_w];
  float *input_g = new float[img_h * img_w];
  float *input_b = new float[img_h * img_w];
 
  int radius = 10;
  int iteration = 10;
  for (int ite = 10; ite <= 10; ++ite) {
    iteration = 5;
    for (int rad = 3; rad <= 3; rad +=1) {
      radius = 2;
 
      for (int i = 0; i < img_h; ++i) {
        for (int j = 0; j < img_w; ++j) {
          input_r[i * img_w + j] = sourceimg.data[i * img_w * 3 + j * 3 + 0];
          input_g[i * img_w + j] = sourceimg.data[i * img_w * 3 + j * 3 + 1];
          input_b[i * img_w + j] = sourceimg.data[i * img_w * 3 + j * 3 + 2];
        }
      }
 
      // SurfaceBlur(input,img_h,img_w, 1, 250, result);
 
      init(img_h, img_w, radius);
      for (int m = 0; m < iteration; ++m) {
        float *temp_result_r = new float[img_h * img_w];
        float *temp_result_g = new float[img_h * img_w];
        float *temp_result_b = new float[img_h * img_w];
        SideWindowFilter(input_r, radius, img_h, img_w, temp_result_r);
        SideWindowFilter(input_g, radius, img_h, img_w, temp_result_g);
        SideWindowFilter(input_b, radius, img_h, img_w, temp_result_b);
        for (int i = 0; i < img_h; ++i) {
          for (int j = 0; j < img_w; ++j) {
            input_r[i * img_w + j] = temp_result_r[i * img_w + j];
            input_g[i * img_w + j] = temp_result_g[i * img_w + j];
            input_b[i * img_w + j] = temp_result_b[i * img_w + j];
          }
        }
        delete (temp_result_r);
        delete (temp_result_g);
        delete (temp_result_b);
      }
      for (int i = 0; i < img_h; ++i) {
        for (int j = 0; j < img_w; ++j) {
          resultimg.data[i * img_w * 3 + j * 3 + 0] = input_r[i * img_w + j];
          resultimg.data[i * img_w * 3 + j * 3 + 1] = input_g[i * img_w + j];
          resultimg.data[i * img_w * 3 + j * 3 + 2] = input_b[i * img_w + j];
        }
      }
      char str[100];
      sprintf(str, "iteration_%d_radius_%d_threshold_50.jpg", iteration,
              radius);
      cv::imwrite(str, resultimg);
    }
  }
  // cv::imwrite("SurfaceBlur.jpg", resultimg);
  delete (input_r);
  delete (input_g);
  delete (input_b);
  waitKey(-1);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/459411.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

An error occurred during installation: No such plugin: cloudbees-folder

An error occurred during installation: No such plugin: cloudbees-folder Index of /packages/jenkins/plugins/cloudbees-folder 下载文件【cloudbees-folder.hpi】

Machine Learning-Ex6(吴恩达课后习题)Support Vector Machines

目录 1. Support Vector Machines 1.1 Example Dataset 1 1.2 SVM with Gaussian Kernels 1.2.1 Gaussian Kernel 1.2.2 Example Dataset 2 1.2.3 Example Dataset 3 2. Spam Classification 2.1 Preprocessing Emails 2.1.1 Vocabulary List 2.2 Extracting Feature…

ffmpeg学习发现av_err2str使用报错问题

最近在学习ffmpeg,照着书上敲代码,发现有个av_err2str错误.书上环境是linux系统的,我使用的windows系统编译器使用的是VS2015.可能是微软的编译器和GCC编译器不太一样这个宏函数用不了. 会报这个错误. 网上找资料超级少,找到一个类似的按照上面修改ffmpeg代码.上面的错误没有了…

Java——装箱和拆箱

一.装箱和拆箱的概念 基本数据(Primitive)类型的自动装箱(autoboxing)、拆箱(unboxing)是自J2SE 5.0开始提供的功能。Java语言规范中说道&#xff1a;在许多情况下包装与解包装是由编译器自行完成的&#xff08;在这种情况下包装称为装箱&#xff0c;解包装称为拆箱&#xff09…

360文件恢复怎么做?3种文件恢复方法分享!

案例&#xff1a;360文件恢复怎么做&#xff1f; 【为了防止病毒入侵和更好的保护电脑&#xff0c;我在电脑上安装了360杀毒软件&#xff0c;但是我昨天在进行垃圾扫描时&#xff0c;软件把我一个很重要的文件删除了&#xff0c;有没有朋友遇到过这种情况呀&#xff1f;我应该…

高数三重积分+离散二元关系+线代矩阵解线性方程

&#x1f442; 梦寻古镇 - 羽翼深蓝Wings - 单曲 - 网易云音乐 &#x1f442; 老男孩 - 1个球 - 单曲 - 网易云音乐 目录 &#x1f33c;前言 &#x1f33c;高数 &#x1f418;B站 -- 三重积分 &#x1f418;课本 -- 7种曲面 公式 &#x1f418;PPT -- 知识点 例题 &a…

【Android FrameWork(五)】- ServiceManager

文章目录 前言源码分析1.service_mananger流程2.Binder通信流程&#xff08;ServiceManager.addService&#xff09;3.Binder的cmd流程图 拓展知识总结1.service_manager2.Binder 前言 接上一篇文章 源码分析 1.service_mananger流程 2.Binder通信流程&#xff08;ServiceMan…

VScode好用的设置(鼠标滚动缩进字体大小等等)

首先我们打开VScode软件&#xff0c;找到左下角的设置 点击设置&#xff0c;找到setting.json&#xff0c;然后点进去 把下面的复制进去&#xff0c;如果想看&#xff0c;可以鼠标悬浮在上面点击看详情 { "workbench.startupEditor": "none", "files.…

电商平台架构演变

大家好&#xff0c;我是易安&#xff01; 今天&#xff0c;我以国内这些年来电商平台的架构的角度&#xff0c;来具体说明下&#xff0c;电商架构是如何一步步演进的。 从2003年淘宝上线开始&#xff0c;国内电商平台经历了高速的发展&#xff0c;在这个过程中&#xff0c;系统…

dwebsocket实现后端数据实时刷新

执行定时任务的时候&#xff0c;我们需要了解执行百分比或者实时数据返回&#xff0c;这时候可以采用的方法1.ajax请求后端服务器&#xff0c;然后前端页面局部渲染获取百分比 2.使用webscoket进行长连接交流刷新 ajax使用方法使用interval函数来实现定时请求&#xff0c;本次这…

【三十天精通Vue 3】第十七天 Vue 3的服务器渲染详解

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: 三十天精通 Vue 3 文章目录 引言一、Vue 3 服务器端渲染概述1.1 服务器端渲染的概念1.2 Vue 3…

【SpringBoot实践】Web请求中日期字符串与LocalDate、LocalDateTime的通用转换

1.背景 最近在做一个后台项目&#xff0c;在这个项目中涉及到了多种与日期相关的请求参数&#xff0c;在请求对象中使用了LocalDate与LocalDateTime两个日期类型。通过日期字符串与前端进行交互&#xff0c;也包含两种格式&#xff1a;yyyy-MM-dd HH:mm:ss与yyyy-MM-dd。 在以…

STM32 学习笔记_5 调试方法;外部中断

调试 OLED&#xff1a;方便&#xff0c;试试更新&#xff0c;但是显示框小。 串口&#xff1a;数据全&#xff0c;但是带电脑不方便。 MDK 自带 debug OLED 调试 4个引脚的&#xff1a;3.3~5V 电压&#xff0c;地&#xff0c;SCL SDA 的 IIC。 我们把 GND-VCC-SCL-SDA 接…

微信小程序国际化

参考文件: 国际化&#xff08;微信小程序TS 微信小程序国际化 一、环境目录 注意:一定要注意项目目录结构&#xff0c;新建文件夹miniprogram&#xff0c;并把前面新建的文件移到这个目录中 二、安装根目录依赖 在NEW-FAN-CLOCK1 中安装根目录依赖 npm i -D gulp minipro…

provide和inject,Teleport,Fragment

作用:实现祖孙组件间通信 套路:父组件有一个provide选项来提供数据&#xff0c;子组件有一个inject选项来开始使用这些数据 父组件 只要provide了&#xff0c;那么后代都能拿到&#xff0c;父子之间一般使用props&#xff0c;祖孙组件一般采用provide 响应式数据判断 isRef:…

TIA博途中文本列表的具体使用方法介绍与示例

TIA博途中文本列表的具体使用方法介绍与示例 一、 文本列表的概念 应用场景:设备的当前操作模式、当前的运行流程、当前的运行状态等 功能介绍:  根据信号数值的不同,显示不同的文本  通过下拉列表选择不同的文本给变量赋值  通过文本列表实现配方元素数值的选择输入…

直播系统开发中如何优化API接口的并发

概述 在直播系统中&#xff0c;API接口并发的优化是非常重要的&#xff0c;因为它可以提高系统的稳定性和性能。本文将介绍一些优化API接口并发的方法。 理解API接口并发 在直播系统中&#xff0c;API接口是用于处理客户端请求的关键组件。由于许多客户端同时连接到系统&…

云计算与多云混合云架构的比较与选择

第一章&#xff1a;引言 随着互联网的迅速发展&#xff0c;云计算逐渐成为了一个热门话题。而随着企业的不断发展&#xff0c;多云混合云架构逐渐成为了一种重要的技术架构。本文将从中国国内资深IT专家的角度&#xff0c;对云计算和多云混合云架构进行比较和选择的分析&#…

IPSec数据报文封装格式详解

以下遵循GMT 0022-2014 IPSec VPN 技术规范。 IPsec提供两种封装协议AH&#xff08;鉴别头&#xff0c;Authentication Header&#xff09;和ESP&#xff08;封装安全载荷&#xff0c;Encapsulation Security Payload&#xff09;。 AH可以提供无连接的完整性、数据源鉴别和抗重…

Linux 进程通讯 - 共享内存机制

共享内存机制&#xff0c;就是在物理内存中划分一段内存&#xff0c;多个进程间可以共同访问这段内存&#xff0c;也可对这段内存做修改操作&#xff0c;从而达到进程通讯的效果&#xff01; 共享内存机制是允许两个或多个进程&#xff08;不相关或有亲缘关系&#xff09;访问…