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