RobotVision勉強会5: フィルタ処理

 

RobotVision勉強会第5回の内容メモです。開発環境はUbuntu16.04、OpenCV3.2.0です。第5回はフィルタ処理です。平滑化フィルタとソーベルフィルタをC++言語とOpenCV APIで実装しましょう。

以下のサンプルプログラムは「OpenCVによる画像処理入門、小枝、上田、中村著、講談社」のサンプルコードを参考に作成しました。ここでは平滑化フィルタとして平均化オペレータを使っています。平均化オペレータは注目している画素値とその近傍の画素値の平均を、注目している画素の新しい画素値とします。

ソーベル(sobel)フィルタは微分フィルタと平滑化フィルタを組み合わせたもので、輪郭検出に使われます。
この本教科書のサンプルプログラムではソーベルフィルタは横方向オペレータだけですが、ここでは縦方向のオペレータも加えており、横方向と縦方向のエッジを検出できます。

なお、エッジ検出にはソーベルよりノイズに強く、エッジがきれいに抽出できるCannyフィルタが良く使われます。

1.サンプルコード

// Robot Vision勉強会 sample5.cpp
// 2017-11-16
// フィルタ処理のサンプルプログラム 

#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/core/core.hpp>
#include <iostream>

// 平滑化フィルタ
void myAverageFilter(cv::Mat img_src, cv::Mat& img_dst)
{
  double op[3][3] = {{1.0/9.0, 1.0/9.0, 1.0/9.0},
		     {1.0/9.0, 1.0/9.0, 1.0/9.0},
		     {1.0/9.0, 1.0/9.0, 1.0/9.0}};
  double sum = 0.0;

  int height = img_src.rows;       // 画像の高さ[pixel]
  int width  = img_src.cols;       // 画像の幅[pixel] 
  
  for (int y =0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      sum = 0.0;
      for (int m = -1; m <= 1; m++) {
	if (y + m < 0) continue;
	if (y + m >= height) continue;
	for (int n = -1; n <=1; n++) {
	  if (x + n < 0) continue;
	  if (x + n >= width) continue;
	  sum += img_src.data[(y + m) * width + (x + n)] * op[m + 1][n + 1];
	}
      }
      img_dst.data[y * width + x] = sum;
    }
  }
}

// ソーベルフィルタ
void mySobelFilter(cv::Mat img_src, cv::Mat& img_dst)
{
  double op_x[3][3] = {{-1.0, 0.0, 1.0},
		       {-2.0, 0.0, 2.0},
		       {-1.0, 0.0, 1.0}};
  double op_y[3][3] = {{-1.0,-2.0,-1.0},
		       { 0.0, 0.0, 0.0},
		       { 1.0, 2.0, 1.0}};
  
  double sum, sum_x = 0, sum_y = 0, min = 0, max = 0;

  int height = img_src.rows;       // 画像の高さ[pixel]
  int width  = img_src.cols;       // 画像の幅[pixel]

  cv::Mat img_tmp = cv::Mat::zeros(height, width, CV_8U);
  
  for (int y =0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      sum_x = 0;
      sum_y = 0;
      for (int m = -1; m <= 1; m++) {
	if (y + m < 0) continue;
	if (y + m >= height) continue;
	for (int n = -1; n <=1; n++) {
	  if (x + n < 0) continue;
	  if (x + n >= width) continue;
	  sum_x += img_src.data[(y + m) * width + (x + n)] * op_x[m + 1][n + 1];
	  sum_y += img_src.data[(y + m) * width + (x + n)] * op_y[m + 1][n + 1];
	}
      }
      sum = sqrt(sum_x * sum_x + sum_y * sum_y);
      img_tmp.data[y * width + x] =  sum;
      if (sum < min) min = sum;
      if (sum > max) max = sum;
    }
  }
  
  for (int i=0; i < width * height; i++) {
    if (max - min == 0) continue;
    double val = (double) (img_tmp.data[i] - min)/(max - min)*255.0;
    img_dst.data[i] =  (unsigned char) val;
  }
}


int main(void)
{
  cv::Mat src;

  std::string WINDOW_NAME_ORG        = "Original image";
  std::string WINDOW_NAME_GRAY       = "Gray scale";
  std::string WINDOW_NAME_AVERAGE    = "Average Filter";
  std::string WINDOW_NAME_SOBEL      = "Sobel Filter";
  std::string WINDOW_NAME_MY_AVERAGE = "My Average Filter";
  std::string WINDOW_NAME_MY_SOBEL   = "My Sobel Filter";
  
  
  // 画像のロード
  src = cv::imread("winkit2.jpg", cv::IMREAD_COLOR);
  if(src.empty()) {
    std::cerr << "Failed to open image file." << std::endl;
    return -1;
  }

  int height = src.rows;       // 画像の高さ[pixel]
  int width  = src.cols;       // 画像の幅[pixel]
  int step   = src.step;       // 1行のチャンネル総数
  int c      = src.channels(); // チャンネル数   

  cv::Mat tmp = src.clone();
  cv::Mat gray_img;
  
  // ウィンドウの生成
  cv::namedWindow(WINDOW_NAME_ORG,        CV_WINDOW_AUTOSIZE);
  cv::namedWindow(WINDOW_NAME_GRAY,       CV_WINDOW_AUTOSIZE);
  cv::namedWindow(WINDOW_NAME_AVERAGE,    CV_WINDOW_AUTOSIZE);
  cv::namedWindow(WINDOW_NAME_MY_AVERAGE, CV_WINDOW_AUTOSIZE);
  cv::namedWindow(WINDOW_NAME_SOBEL,      CV_WINDOW_AUTOSIZE);
  cv::namedWindow(WINDOW_NAME_MY_AVERAGE, CV_WINDOW_AUTOSIZE);

  
  while (true){
    src.copyTo(tmp);
    cvtColor(tmp, gray_img, CV_RGB2GRAY);
    
    cv::Mat average_img;
    cv::Mat sobel_img;  
    cv::Mat my_average_img  = cv::Mat::zeros(height, width, CV_8U);
    cv::Mat my_sobel_img    = cv::Mat::zeros(height, width, CV_8U);
    
    myAverageFilter(gray_img, my_average_img);
    mySobelFilter(gray_img, my_sobel_img);

    cv::blur(gray_img,average_img, cv::Size(3, 3));
    cv::Mat tmp_img;
    cv::Sobel(gray_img, tmp_img, CV_32F, 1, 0, 3);
    cv::convertScaleAbs(tmp_img, sobel_img,1, 0);

    cv::imshow(WINDOW_NAME_ORG, tmp);
    cv::imshow(WINDOW_NAME_GRAY, gray_img);
    cv::imshow(WINDOW_NAME_AVERAGE,    average_img);
    cv::imshow(WINDOW_NAME_MY_AVERAGE, my_average_img);
    cv::imshow(WINDOW_NAME_SOBEL,      sobel_img);
    cv::imshow(WINDOW_NAME_MY_SOBEL,   my_sobel_img);
    
    cv::waitKey(1);

  }

  return 0;
}

2.ハンズオン

(1) このソースコードsample5.tgzをダウンロードして次のコマンドで解凍、コンパイルして実行しよう。

$ tar xvzf sample5.tgz
$ cd sample5
$ cmake .
$ make
./sample5

(2) メディアン(median, 中央値)フィルタを実装してみよう。

(3) cannyフィルタを調べて実装してみよう。

コメントを残す

メールアドレスが公開されることはありません。