サイド ウィンドウ フィルタリング サイド ウィンドウ フィルタリング

原理分析

        通常、画像の特徴を強調するための検出アルゴリズムには従来の画像アルゴリズムが使用され、ノイズを除去するためのフィルタリングが必要になります。ガウス フィルタリング、メディアン フィルタリング、平均値フィルタリングなどのフィルタリングを直接使用すると、ノイズが除去されるだけでなく、エッジを含む画像の細部がぼやけて画像情報が失われます。エッジや一部の詳細情報を保存するために、エッジ保存フィルタリング アルゴリズムが使用される場合があります。以前に使用されたエッジ保存フィルタリングには、両側フィルタリングや表面ぼかしなどのアルゴリズムが含まれています。これら 2 つのアルゴリズムは比較的低速であり、検出アルゴリズムとしては費用対効果が高くありません。

        従来のフィルタリング アルゴリズムは、処理対象のピクセルがエッジ領域にあるかどうかに関係なく、フィルターのカーネル中心を処理対象のピクセルに合わせて計算するため、フィルター ウィンドウはエッジを横切るときにエッジの両側からのピクセル値の重み付け計算を導入します。このような計算が実行されると、エッジ情報はエッジ法線に沿った方向に拡散し、結果としてエッジ情報が弱められたり、損失されたりすることになります。たとえば、平均値フィルタリングの場合は次のようになります。

 

中央のピクセルがエッジの平均値でフィルタリングされた後、ピクセル値が引き上げられます

        従来のフィルタリング アルゴリズムによるエッジの弱化を回避するために、現在では、処理されるピクセルは必ずしもフィルターのコアと位置合わせする必要はないと考えられています。

       平均値フィルターを例に挙げてみましょう。平坦な領域の場合、平均値フィルターでどのような形状のフィルター カーネルが使用されても、計算結果は元のピクセルとあまり変わりません。エッジ上の画素について、エッジをできるだけ残したい場合には、現在計算対象の画素に近いブロック内の画素をできるだけ計算に残し、ブロック内の現在計算対象の画素と大きく異なる画素はできるだけ破棄する必要がある。この考え方は、表面ぼかしアルゴリズムと非常に似ています。ここでは、表面ぼかしアルゴリズムについて少し紹介します。

 

        上記の式から、表面ぼかしアルゴリズムの主な考え方は、現在のピクセル X の近傍にあるさまざまなピクセルの重み付けされた合計を計算することであることがわかります。上の式の Y 値が非常に大きい場合、表面ぼかしアルゴリズムは平均フィルター アルゴリズムと同じになります。

        次の図は、典型的な理想的なエッジ モデルです。ステップ エッジ、スロープ エッジ、ルーフ エッジです。下の図で赤い点が位置するピクセルの新しいピクセル値をフィルタリングして計算するとき、赤い点があるエッジを弱めるのではなく、赤い点があるエッジをできるだけ維持できるように、赤い点と同じ平面上のブロック ピクセルをできる限り多く使用し、赤い点と同じ平面上にないブロック ピクセルを破棄したいと考えています。

 

        上記の目的を達成するために、内部にエッジを持つフィルターをいくつかプリセットすることができます。エッジ タイプは、上図のステップ エッジ タイプで、2 つのブロックに分割されています。このようにして、画像のエッジブロックのピクセルを計算する際に、フィルターモデルと実際に計算するピクセルブロックのエッジモデルをマッチングさせることができ、マッチングできれば赤い点で計算されるピクセル値はあまり変化しませんが、マッチングが逆になると赤い点で計算される新たな値は元の値と大きく異なります。

       フィルターには多くの種類のエッジ モデルがありますが、計算の便宜上、そのうちの 8 つだけが取り上げられます。他の形状のフィルターは、鋸歯状であるため、走査および計算が容易ではありません (特に、フィルター カーネルの半径が比較的大きい場合、鋸歯状がある場合、ラインごとにのみ走査でき、ループさせることができません)。そのため、採用されていません。

 

        上図の 8 種類のフィルターは、それぞれエッジがあり形状の異なる 2 つのブロックに明確に分割されており、最も重要なのは、その中の値の分布が正方形または長方形であることです。これはトラバーサル計算に非常に便利です。上記 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);
}

おすすめ

転載: blog.csdn.net/cyy1104/article/details/130359401