Table of Contents
1 確認
- OpenCVの各関数の使い方は適宜紹介しますが,
必要に応じて検索するなどして,情報を各自で補完してください.
特に,英語リファレンスを臆さず読むようにしてください. - ソースコードをcopy&pasteせずに,自分で入力するようにしてください.
- 実習で紹介するやり方が唯一の正解ではありませんので,目的が
達成されれば独自の手法を適用してもらうのは大いに歓迎します.
2 本日の実習
3 ライブラリの設定変更
今日使用するOpenCVの関数のうちいくつか(具体的には,std::vectorを使用する関数)
が,大学の環境では実行時エラーで落ちることが判明した.
仕方ないので,私がビルドしたOpenCVライブラリをリンクして使うことにする.
3.1 ライブラリのインストール
以下に置いてあるzipファイルをダウンロードし,
内容を適当なフォルダに展開する.ここでは z:\ に展開するものとする.
展開すると z:\lib の下にライブラリファイル *.lib が並ぶはず.
3.2 ライブラリの設定の変更
3.3 プロジェクトのプロパティの変更
ライブラリの変更に伴って,コード生成の設定を変更する必要が生じる
(面倒になってすみません…).
デフォルトのままプロジェクトをビルドすると,
「’RuntimeLibrary’ の不一致が検出されました」
といったエラーメッセージが出る.
対処は,次の通りである.
プロジェクトを新規作成したら,ソリューションエクスプローラーの
プロジェクト名の上で右クリックし,プロパティを開き,
構成プロパティ > C/C++ > コード生成 を選択し,その中にある
「ランタイム ライブラリ」の値を変更する.
プロパティウィンドウの左上の構成が Debug の場合には,
「マルチスレッド デバッグ (/MTd)」に,
構成が Release の場合には,「マルチスレッド (/MT)」に,設定する.
Debugの場合
Releaseの場合
(Visual Studioではデバッグ情報が付属した Debug 版と,
デバッグ情報の無い Release 版が作成できることは,以前話した)
3.4 cv_lib_macro.h の更新
またOpenCV用ライブラリを指定するマクロ cv_lib_macro.h にも
変更が加えられている.本日からの新しい環境でプログラムを
生成するときは,cv_lib_macro.h を以下に置いてあるものに
置き換える.前回のプログラムを再度ビルドする場合も置き換えが
必要である.
新しいcv_lib_macro.hをダウンロードして置き換え
cv_lib_macro.h
※リンク時にライブラリがみつからないという類のエラーがでている場合はご一報を
4 角検出の基本方針
次の順序で処理を進める.
- 色領域抽出(済)
- ノイズ除去
- 画像のエッジ抽出によりエッジ画像を得る
- Hough変換で直線を検出
- 直線の交点を求める
前回までのプログラムに,順次,2から5を追加していくことで,
プログラムの作成を進めていく.
5 ノイズ除去
得られているマーカ領域候補画像は,8ビット1チャネルで,
各画素値は0か255のいずれかである.
1画素や2画素の小領域はノイズであり,マーカである可能性は無いので,
前処理で除去する.
ノイズ除去の方法はいくつかあるが,
- モルフォロジフィルタ(縮退 Erode -膨張 Dilate)を用いる
- メディアンフィルタを用いる
のいずれかを試してみるとよいだろう.
5.1 モルフォロジフィルタ
縮退 Erode は,自分の画素値を,周辺の画素値のうちの最小値に設定する.
領域の端の画素を削る感じである.
膨張 Dilate は,自分の画素値を,周辺の画素値のうちの最大値に設定する.
領域の端を膨らませる感じである.
縮退させてから膨張させると,1画素のノイズを消すことができる.
モルフォロジによるノイズ除去の例(注: これ全体を打ち込む必要はありません.
erode と dilate の使い方の参考にしてください).
#include <iostream> #include <opencv2/opencv.hpp> #include "cv_lib_macro.h" #pragma CV_LIBRARY(core) #pragma CV_LIBRARY(imgproc) #pragma CV_LIBRARY(highgui) using namespace cv; using namespace std; int main(void) { // source image Mat image = Mat::zeros(Size(256, 256), CV_8UC1); circle(image, Point(128, 128), 80, 255, -1); // add noise for (int i = 0; i < 256; i++) { int x = (i % 16) * 16; int y = (i / 16) * 16; image.at<unsigned char>(y, x) = 255; } // denoise Mat result; erode(image, result, Mat()); dilate(result, result, Mat()); // output imwrite("src.png", image); imwrite("result.png", result); return 0; }
erode と dilate の第3引数では,
本来は近傍領域を指定することができるが,
空の Mat を渡しておくと3×3の矩形領域が選択される.
参照:
http://docs.opencv.org/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.html
5.2 メディアンフィルタ
メディアンは中央値という意味である.
周辺の画素値を大きさ順に並べた中央値に,自分の画素値を設定する.
メディアンフィルタによるノイズ除去の例(注: これも,
全体を打ち込む必要はありません.参考用です).
#include <iostream> #include <opencv2/opencv.hpp> #include "cv_lib_macro.h" #pragma CV_LIBRARY(core) #pragma CV_LIBRARY(imgproc) #pragma CV_LIBRARY(highgui) using namespace cv; using namespace std; int main(void) { // source image Mat image = Mat::zeros(Size(256, 256), CV_8UC1); circle(image, Point(128, 128), 80, 255, -1); // add noise for (int i = 0; i < 256; i++) { int x = (i % 16) * 16; int y = (i / 16) * 16; image.at<unsigned char>(y, x) = 255; } // denoise Mat result; medianBlur(image, result, 3); // output imwrite("src.png", image); imwrite("result.png", result); return 0; }
medianBlurの第3引数は,近傍領域のサイズである.
5.3 発展
他にもノイズ除去手法は存在する.
各自調べて試してみるとよいだろう.
性能を比較するとなおよい.
6 エッジ抽出
画像のエッジ抽出には様々な方法があるが,ここでは Canny オペレータを使用する.
Canny(image, edge, threshold1, threshold2);
- image は入力画像である.
- edge は出力画像である.
- threshold1 と threshold2 は,Cannyオペレータの閾値である.
省略できる引数は省略している.詳しくは以下を参照のこと.
- http://docs.opencv.org/modules/imgproc/doc/feature_detection.html?highlight=canny#void%20Canny(InputArray%20image,%20OutputArray%20edges,%20double%20threshold1,%20double%20threshold2,%20int%20apertureSize,%20bool%20L2gradient)
- http://docs.opencv.org/doc/tutorials/imgproc/imgtrans/canny_detector/canny_detector.html?highlight=canny
- https://en.wikipedia.org/wiki/Canny_edge_detector
閾値として,threshold1には0-100,threshold2はthreshold1の2から3倍程度が
よいとされている.
(なのだが,OpenCV 2.3.1のCannyは,パラメータによる結果の変化が無い?)
以下,Cannyの使い方の例(これも,このまま打ち込むのではなくて,Cannyを
使っている部分を参考にして自分のプログラムに組み込む).
#include <iostream> #include <opencv2/opencv.hpp> #include "cv_lib_macro.h" #pragma CV_LIBRARY(core) #pragma CV_LIBRARY(imgproc) #pragma CV_LIBRARY(highgui) using namespace cv; using namespace std; int main(void) { char *window_input = "input"; namedWindow(window_input, CV_WINDOW_AUTOSIZE); char *window_binary = "binary"; namedWindow(window_binary, CV_WINDOW_AUTOSIZE); char *window_edge = "edge"; namedWindow(window_edge, CV_WINDOW_AUTOSIZE); int binary_threshold = 128; int canny_low_threshold = 50; int canny_high_threshold = 150; createTrackbar("threshold", window_edge, &binary_threshold, 255); createTrackbar("canny low", window_edge, &canny_low_threshold, 100); createTrackbar("canny high", window_edge, &canny_high_threshold, 500); VideoCapture cap; cap.open(0); if (!cap.isOpened()) { cerr << "cannot find camera" << endl; return -1; } while (1) { Mat frame; cap >> frame; Mat gray; cvtColor(frame, gray, CV_BGR2GRAY); Mat binary; threshold(gray, binary, binary_threshold, 255, CV_THRESH_BINARY); Mat edge; Canny(binary, edge, canny_low_threshold, canny_high_threshold); imshow(window_input, frame); imshow(window_binary, binary); imshow(window_edge, edge); int key = waitKey(10); if (key == 3 || key == 27 || key == 'q') { break; } } return 0; }
6.1 発展
他にもエッジ抽出手法はいろいろある.OpenCVの関数一つでできるものもあるので,適宜試してみる.
7 直線検出: Hough変換
直線検出の有名な方法の一つがHough(ハフ)変換である.
ここではHough変換を用いてマーカの辺を検出する.
Hough変換の原理については,別途スライドを用いて説明する.
7.1 Hough変換の出力とvector
Hough変換の出力は,vector<Vec2f> で得られる.この vector<> とは何か?
vector は STL (Standard Template Library)というC++の標準ライブラリの
一つである.宣言後でも長さ(要素数)を変えられる可変長配列と考えてよい.
int hoge[10]; vector<int> hoge(10)
<>の中に型名を入れるのは,テンプレートというC++の機能である.
ここでは,xxx型を格納するためのvectorを宣言するなら vector<xxx> と
書く,と覚えておく.
vector<>が配列より優れている点の一つは,長さを取得できることである.
vector<int> hoge に対して,hoge.size() とすると,hogeの要素数を知ることが
できる.
一方Vec2fは,OpenCVが用意している,2成分のベクトルを
保持するためのクラスで,各成分には配列と同じように添字でアクセス
できる.
vector<Vec2f> lines; int num = lines.size(); // 直線数 for (int i = 0; i < num; i++) { cout << "rho = " << lines[i][0] << ", theta = " << lines[i][1] << endl; }
std::vector については,C++のスタンダードテンプレートライブラリ(STL)を
検索すれば様々な情報が得られるので各自確認せよ.
STLにはリストやキュー,マップといったよく使うデータ構造もある.
構文がやや複雑怪奇になるのが残念だが,非常に便利である.
7.2 Hough変換の使い方
vector<Vec2f> lines; HoughLines(image, lines, 1, CV_PI/180, 100, 0, 0);
引数は以下の通り:
- image: 入力画像 Matクラス 8ビット 1チャネル
- lines: 出力.vecotr<Vec2f> 直線のパラメータ
- rho(この例では1): 原点からの距離の解像度
- theta(この例ではCV_PI/180=π/180): 傾きの解像度
- threshold(この例では100): 何点投票があれば直線と判断するかの閾値.大きい程長い直線のみ抽出
- 残りはマルチスケールHough変換の際のパラメータ
Hough変換の使い方の例を以下に示す.
検出結果を画面に描画するところも参考にせよ.
閾値を様々に変えられるようにして(Trackbarを用いる)適切な閾値を探してみよ.
#include <iostream> #include <opencv2/opencv.hpp> #include "cv_lib_macro.h" #pragma CV_LIBRARY(core) #pragma CV_LIBRARY(imgproc) #pragma CV_LIBRARY(highgui) using namespace cv; using namespace std; int main(void) { Mat src = imread("hough_src.jpg"); Mat gray; cvtColor(src, gray, CV_BGR2GRAY); threshold(gray, gray, 128, 255, CV_THRESH_BINARY_INV); // Hough Transform const int hough_threshold = 130; vector<Vec2f> lines; HoughLines(gray, lines, 1, CV_PI / 180.0, hough_threshold); for (int i = 0; i < lines.size(); i++ ) { float rho = lines[i][0]; float theta = lines[i][1]; double a = cos(theta); double b = sin(theta); double x0 = a * rho, y0 = b * rho; Point pt1(cvRound(x0 + 1000*(-b)), cvRound(y0 + 1000*(a))); Point pt2(cvRound(x0 - 1000*(-b)), cvRound(y0 - 1000*(a))); line(src, pt1, pt2, CV_RGB(255, 0, 0), 1, 8); } imwrite("hough_result.jpg", src); return 0; }
参考: http://docs.opencv.org/doc/tutorials/imgproc/imgtrans/hough_lines/hough_lines.html
7.3 発展
OpenCVにはHoughLinesPという線分を出力してくれる関数もある.
こちらも試してみよ.
適切な閾値を自動的に設定することはできるだろうか.
Hough変換自体を自分で実装してみると,動作原理がよくわかる.
時間があれば試してみよ.
8 角の検出
直線と直線との交点を求めると,角の座標を得ることができる.理想的には.
Hough変換を使ってみるとわかるが,本来なら1本の直線として検出されるはずの
エッジが,複数の直線として検出される場合が多いことがわかる.
複数の直線のうち,同じ直線だと考えられるものを,一つの直線とすれば
よいと考えられる.各自,実装してみよ.
Hough変換と,得られる直線のパラメータ,および交点の算出方法については,
以下のPDFを参照してください.
Hough変換と直線の交点の算出方法の解説(PDF) (7節冒頭のものと同じです)
検出された交点には円を描くとよいだろう.
画像(Mat)frameの座標(x,y)に,半径5,太さ3で,赤(255,0,0)の円を描く例は以下の通り.
Point pt(x, y); circle(frame, pt, 5, CV_RGB(255, 0, 0), 3);
9 発展的課題
- マーカの四隅のソート(反時計回りに並べる)
- 複数の色のマーカの検出
- マーカ検出の安定化:
例: 閾値を状況に合わせて変化させる.adaptiveThreshold,判別分析などを調べてみよ