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フィルタを調べて実装してみよう。
コメント