1 確認

  • OpenCVの各関数の使い方は適宜紹介しますが,
    必要に応じて検索するなどして,情報を各自で補完してください.
    特に,英語リファレンスを臆さず読むようにしてください.
  • ソースコードをcopy&pasteせずに,自分で入力するようにしてください.
  • 実習で紹介するやり方が唯一の正解ではありませんので,目的が
    達成されれば独自の手法を適用してもらうのは大いに歓迎します.

2 本日の実習

前回の実習までで,ある色のマーカ領域を(ノイズも少々あると思われるが)
抽出できているはずである.

まだの人はがんばってやってください.

本日の目標は,得られたマーカ領域の角を検出することである.

うまくいくと,以下のような感じ.

2015-10-06_08h25_46

3 ライブラリの設定変更

今日使用するOpenCVの関数のうちいくつか(具体的には,std::vectorを使用する関数)
が,大学の環境では実行時エラーで落ちることが判明した.

仕方ないので,私がビルドしたOpenCVライブラリをリンクして使うことにする.

3.1 ライブラリのインストール

以下に置いてあるzipファイルをダウンロードし,
内容を適当なフォルダに展開する.ここでは z:\ に展開するものとする.
展開すると z:\lib の下にライブラリファイル *.lib が並ぶはず.

lib.zip (35MB)

library

3.2 ライブラリの設定の変更

OpenCVの追加ライブラリがあるフォルダを変更する.
例によって,「表示」メニューからプロパティマネージャーを表示する
(表示 > その他のWindow > プロパティマネージャー にある場合と,
表示 > プロパティマネージャー の場合の二つがある).
Microsoft.Cpp.Win32.user のプロパティを開き,
リンカー の 追加のライブラリディレクトリ から現在登録されている
OpenCV-2.3.1のディレクトリを削除し,代わりに z:\lib を追加する.
プロパティを変更したら,フロッピーディスクのアイコンをクリックして
保存しておくこと.

newlib

3.3 プロジェクトのプロパティの変更

ライブラリの変更に伴って,コード生成の設定を変更する必要が生じる
(面倒になってすみません…).

デフォルトのままプロジェクトをビルドすると,
「’RuntimeLibrary’ の不一致が検出されました」
といったエラーメッセージが出る.

対処は,次の通りである.

プロジェクトを新規作成したら,ソリューションエクスプローラーの
プロジェクト名の上で右クリックし,プロパティを開き,
構成プロパティ > C/C++ > コード生成 を選択し,その中にある
「ランタイム ライブラリ」の値を変更する.

2015-10-06_08h29_04

プロパティウィンドウの左上の構成が Debug の場合には,
「マルチスレッド デバッグ (/MTd)」に,
構成が Release の場合には,「マルチスレッド (/MT)」に,設定する.

Debugの場合

2015-10-06_08h30_13

Releaseの場合

2015-10-06_08h31_34

(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 角検出の基本方針

次の順序で処理を進める.

  1. 色領域抽出(済)
  2. ノイズ除去
  3. 画像のエッジ抽出によりエッジ画像を得る
  4. Hough変換で直線を検出
  5. 直線の交点を求める

前回までのプログラムに,順次,2から5を追加していくことで,
プログラムの作成を進めていく.

5 ノイズ除去

得られているマーカ領域候補画像は,8ビット1チャネルで,
各画素値は0か255のいずれかである.

1画素や2画素の小領域はノイズであり,マーカである可能性は無いので,
前処理で除去する.

ノイズ除去の方法はいくつかあるが,

  • モルフォロジフィルタ(縮退 Erode -膨張 Dilate)を用いる
  • メディアンフィルタを用いる

のいずれかを試してみるとよいだろう.

5.1 モルフォロジフィルタ

縮退 Erode は,自分の画素値を,周辺の画素値のうちの最小値に設定する.
領域の端の画素を削る感じである.

膨張 Dilate は,自分の画素値を,周辺の画素値のうちの最大値に設定する.
領域の端を膨らませる感じである.

縮退させてから膨張させると,1画素のノイズを消すことができる.

モルフォロジによるノイズ除去の例(注: これ全体を打ち込む必要はありません.
erode と dilate の使い方の参考にしてください).

erode_dilate_src
erode_dilate_result

#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 メディアンフィルタ

メディアンは中央値という意味である.
周辺の画素値を大きさ順に並べた中央値に,自分の画素値を設定する.

メディアンフィルタによるノイズ除去の例(注: これも,
全体を打ち込む必要はありません.参考用です).

median_src
median_result

#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引数は,近傍領域のサイズである.

参照:
http://docs.opencv.org/modules/imgproc/doc/filtering.html#void%20medianBlur(InputArray%20src,%20OutputArray%20dst,%20int%20ksize)

5.3 発展

他にもノイズ除去手法は存在する.
各自調べて試してみるとよいだろう.
性能を比較するとなおよい.

6 エッジ抽出

画像のエッジ抽出には様々な方法があるが,ここでは Canny オペレータを使用する.

Canny(image, edge, threshold1, threshold2);
  • image は入力画像である.
  • edge は出力画像である.
  • threshold1 と threshold2 は,Cannyオペレータの閾値である.

省略できる引数は省略している.詳しくは以下を参照のこと.

閾値として,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変換の原理については,別途スライドを用いて説明する.

Hough変換と直線の交点の算出方法の解説(PDF)

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を用いる)適切な閾値を探してみよ.

hough_src
hough_result

#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,判別分析などを調べてみよ