Python+OpenCVで特徴点抽出・使えるアルゴリズムまとめ OpenCV2.4.9と3.0 alpha

軽量プログラミング言語が苦手なので敬遠していたが,世間ではPythonからOpenCVを呼ぶのが流行っているようなので,練習がてらOpenCVで使える特徴点抽出アルゴリズムをまとめてみる.

OpenCV2.4.9とOpenCV3.0 alphaについてまとめる.3.0 betaはなぜか動かなかったのでいつか暇があれば調査.

環境

WindowsでOpenCV公式サイトのダウンロードページから2.4.9および3.0 alphaのビルド済みバイナリをダウンロードしてきて解凍したものを使う.

PythonからのOpenCVの呼び出し方はこことかを参照.

特徴点抽出の使い方

OpenCVで実装されている特徴点抽出のアルゴリズムはいくつかあるが,FeatureDetectorという共通インターフェースを使うことでどのアルゴリズムでも同様の記述で使える.使い方は以下.

# 画像読み込み
img = cv2.imread('gazou.bmp')
# FeatureDetectorのインスタンスを生成
detector = cv2.FeatureDetector_create(detector_type)
# 特徴点を抽出
keypoints = detctor.detect(img)

FeatureDetector_createメソッドの引数にアルゴリズム名を文字列で渡すことで,どのアルゴリズムで特徴点抽出するかを選択することが出来る.例えば,ORBアルゴリズムで特徴点を抽出して表示するのは以下のようになる.

import cv2

img = cv2.imread('gazou.bmp')
# アルゴリズム名を引数で渡す
detector = cv2.FeatureDetector_create('ORB')
keypoints = detctor.detect(img)
# 画像への特徴点の書き込み
out = cv2.drawKeypoints(img, keypoints, None)
# 表示
cv2.imshow(name, out)
cv2.waitKey(0)

結果は以下のようになる.
f:id:whoopsidaisies:20141201024751p:plain

ちなみに特徴点の書き込み部分を以下のようにすると,特徴点のサイズと方向を表す円が描画される.

out = cv2.drawKeypoints(img, keypoints, None, None, cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

f:id:whoopsidaisies:20141201024104p:plain

FeatureDetectorインターフェースで使えるアルゴリズム

FeatureDetectorインターフェースで指定できる特徴点抽出アルゴリズムはOpenCVのバージョンで変わるようなので,OpenCV2.4.9で使えるものとOpenCV3.0 alphaで使えるものを以下にまとめる.

バージョン アルゴリズム名
2.4.9 BRISK, Dense, FAST, FASTX, GFTT, HARRIS, MSER, ORB, SIFT, STAR, SURF, SURF_OCL, SimpleBlob
3.0 alpha AKAZE, BRISK, FAST, GFTT, HARRIS, KAZE, MSER, ORB, SimpleBlob


C++版については以下の記事にまとめてある.
OpenCV3.0.0-alphaの特徴抽出・マッチングまとめ - whoopsidaisies's diary
OpenCVで画像の特徴抽出・マッチングを行う - whoopsidaisies's diary

C#で画像をWavelet変換 Accord.NET

C#で画像のWavelet変換を行う.コンピュータビジョン等の.NET向けのオープンソースのライブラリであるAccord.NET Frameworkを使う.

Accord.NETのインストール

NuGet経由でインストール可能.プロジェクトを右クリックして「NuGetパッケージの管理」を選択する.オンラインから「Accord.Imaging」を検索・インストール.

WaveletTransformクラス

Accord.NETのWaveletTransformクラスによってWavelet変換を行える.入力画像はグレースケールで,解像度が2のべき乗でないといけない.

ソースコード

WaveletTransformクラスの初期化時にWavelet母関数を指定する.その引数でWavelet変換のレベルを指定できる.ここではHaar関数でレベル2とした.

// 入力画像
var img = new Bitmap(@"Penguins.jpg");
// 画像の解像度が2のべき乗になるように調整
var bitWidth = Convert.ToString(img.Width - 1, 2).Length;
var bitHeight = Convert.ToString(img.Height - 1, 2).Length;
var width = (int)Math.Pow(2, bitWidth);
var height = (int)Math.Pow(2, bitHeight);
// 隙間の部分はゼロ埋め
var imgPadded = new Bitmap(width, height, img.PixelFormat);
var graphics = Graphics.FromImage(imgPadded);
graphics.DrawImage(img, 0, 0);
graphics.Dispose();
// グレースケール化
var gray = new AForge.Imaging.Filters.Grayscale(0.2125, 0.7154, 0.0721).Apply(imgPadded);
// Wavelet変換クラスの作成
//  Harrクラスの初期化時にレベルを指定出来る
var wavelet = new Accord.Imaging.Filters.WaveletTransform(new Accord.Math.Wavelets.Haar(2));
// Wavelet変換
var imgWavelet = wavelet.Apply(gray);
pictureBox1.Image = imgWavelet;

結果

f:id:whoopsidaisies:20141130095415p:plain

逆変換

WaveletTransformクラスのBackwordプロパティをtrueにしてApplyメソッドを実行すると逆変換が出来る.

wavelet.Backward = true;
var gyaku = wavelet.Apply(imgWavelet);

C#で画像を高速フーリエ変換 AForge.NET

C#で画像の高速フーリエ変換(FFT)を行う.コンピュータビジョン等のライブラリであるAForge.NET Frameworkを使う.

AForge.NETのインストール

Nuget経由でインストール可能.プロジェクトを右クリックして「NuGetパッケージの管理」を選択する.オンラインから「AForge」で検索.「AForge.Imaging」を選択してインストール.

ComplexImageクラス

AForge.NETのComplexImageクラスを使うと画像のFFTおよび逆FFTが出来る.入力する画像はグレースケールで解像度が2のべき乗になっている必要がある.

ソースコード

画像のグレースケール化はGrayscaleクラスを使った.

// 入力画像
var img = new Bitmap(@"Lighthouse.jpg");
pictureBox1.Image = img;
// 画像の解像度が2のべき乗になるように調整
var bitWidth = Convert.ToString(img.Width - 1, 2).Length;
var bitHeight = Convert.ToString(img.Height - 1, 2).Length;
var width = (int)Math.Pow(2, bitWidth);
var height = (int)Math.Pow(2, bitHeight);
// 隙間の部分はゼロ埋め
var imgPadded = new Bitmap(width, height, img.PixelFormat);
var graphics = Graphics.FromImage(imgPadded);
graphics.DrawImage(img, 0, 0);
graphics.Dispose();
// グレースケール化
var gray = new AForge.Imaging.Filters.Grayscale(0.2125, 0.7154, 0.0721).Apply(imgPadded);
pictureBox2.Image = gray;
// FFT
var complexImage = AForge.Imaging.ComplexImage.FromBitmap(gray);
complexImage.ForwardFourierTransform();
pictureBox3.Image = complexImage.ToBitmap();

結果画像

f:id:whoopsidaisies:20141130083904p:plain

FFT後の値

ComplexImageクラスのDataプロパティは2次元配列でFFT後の値が格納されている.また,BackwardFourierTransformメソッドで逆FFTを行える.

Visual Studioでデバッグ時にOpenCVのMat等の画像を表示できるプラグインが便利

OpenCV公式サイトのNEWSを見てたら「Image Watch plugin for Visual Studio」というのを見つけた.使ってみたら便利そうだったのでメモ.もっと早く知りたかった.

わざわざimshowとかのコードを追加してウィンドウを表示させなくてもMatの中の画像を確認できる.また,拡大表示とか画素値の表示も出来るため通常のOpenCVの画像表示用のウィンドウより便利な気がする.

Image Watch plugin for Visual Studioのインストール

以下のサイトからダウンロードしてきて指示にしたがってインストールするだけ.
Image Watch extension

以下のサイトに詳しい手順等載ってます.
Image Watchの使い方 | dandelion's log

使ってみる

以下のコードで試してみた.画像を表示させるためのコードは特に必要ない.画像が入っている変数があれば自動で表示してくれる.

#include <opencv2/opencv.hpp>

using namespace cv;

void main()
{
	// 画像読み込み
	Mat img = imread("Penguins.jpg");
	// Sobelフィルタ
	Mat imgSobel;
	Sobel(img, imgSobel, img.depth(), 1, 1);
	// ラプラシアンフィルタ
	Mat imgLaplacian;
	Laplacian(img, imgLaplacian, img.depth());
	// Cannyアルゴリズム
	Mat imgCanny;
	Canny(img, imgCanny, 50, 200);
}

一番下の行にブレークポイントを置いて,デバッグ実行して止める.

「表示」メニューの中から「Image Watch」を探してクリックしてウィンドウを表示させる.
f:id:whoopsidaisies:20141128173607p:plain

そうすると以下の様なウィンドウが出てきて,Mat型の変数とその中身の画像が表示される.
f:id:whoopsidaisies:20141128165602p:plain

左側に画像一覧があるので,クリックすれば以下のようにその画像が表示される.
f:id:whoopsidaisies:20141128165507p:plain

マウススクロールで拡大可能.
f:id:whoopsidaisies:20141128173940p:plain

最大まで拡大すると各画素の画素値が数字で表示される.
f:id:whoopsidaisies:20141128174024p:plain

画素を右クリックしたら,その画素のアドレスのコピーとかも出来るもよう.

以上のようにOpenCVのMatの中の画像を簡単に確認できるので,Visual StudioでOpenCVを使う場合はかなり便利だと思う.

試してないけど配布サイトの説明によると,cv::Mat_<>, CvMat, _IplImageなんかにも対応しているらしい.

opencvで文字認識その1 Tesseractラッパ

OpenCV3.0系から文字認識モジュールが搭載されるようなので使ってみる.現状の3.0 alphaや3.0 betaでは,文字認識モジュールはメインレポジトリに組み込まれておらず開発用レポジトリのopencv_contribの方に入っているようで,opencv_contribと一緒にOpenCVをビルドする必要がある.

OpenCVの文字認識モジュール

OpenCVのドキュメントによると,以下の2種類の文字認識方法があるらしい.

  1. オープンソースのOCRライブラリtesseract-ocrを呼び出す方法
  2. 隠れマルコフモデルによる認識方法

今回は,1の方法について試してみる.

文字認識モジュールの準備

tesseract-ocrのダウンロード

https://code.google.com/p/tesseract-ocr/downloads/listからVC++からtesseract-ocrを呼び出す用のライブラリをダウンロード・解凍する.「tesseract-3.xx.xx-win32-lib-include-dirs.zip」って名前のやつ.今回は「tesseract-3.02.02-win32-lib-include-dirs.zip」を使った.

https://code.google.com/p/tesseract-ocr/downloads/listからtesseract-ocr用の学習済みのデータファイルをダウンロードしておく.今回は英語で試すため「tesseract-ocr-3.02.eng.tar.gz」をダウンロード・解凍する.

opencv_contribのダウンロード

https://github.com/itseez/opencv_contribからopencv_contribをダウンロード・解凍する.

OpenCVのビルド

CMakeを使ってビルドする.ここらへんを参考に.

「OPENCV_EXTRA_MODULES_PATH」パラメータの値を「(opencv_contribのパス)/modules」にセットしてConfigureする.

すると「Tesseract_INCLUDE_DIR」と「Tesseract_LIBRARY」というパラメータが出てくるので

  • Tesseract_INCLUDE_DIR ← (tesseract-ocrのパス)/include
  • Tesseract_LIBRARY ← (tesseract-ocrのパス)/lib

とセットする.

以上の設定でGenerateしてOpenCVをビルドすると文字認識モジュールが組み込まれる.で,インストールする.

動作確認

実際に画像を入力してやってみたソースコードと結果が以下.OpenCVのMat形式の画像を使って文字認識出来るため,tesseract-ocrのAPI直接叩くよりは使いやすいと思う.

追記)文字のかたまりの位置の出力が間違っていたためソースコードと結果画像を修正した.

ソースコード
#include <opencv2/opencv.hpp>
#include <opencv2/text.hpp>

void main()
{
	// 画像読み込み
	auto image = cv::imread("moji.jpg");
	// グレースケール化
	cv::Mat gray;
	cv::cvtColor(image, gray, COLOR_RGB2GRAY);
	// 文字認識クラスのインスタンス生成
	auto ocr = cv::text::OCRTesseract::create("(言語データのパス)/tessdata", "eng");

	std::string text;
	std::vector<cv::Rect> boxes;
	std::vector<std::string> words;
	vector<float> confidences;
	// 文字認識の実行
	ocr->run(gray, text, &boxes, &words, &confidences);

	// 結果出力
	printf("%s\n", text.c_str());
	// 文字のかたまりごとに出力
	printf(" 文字      | 位置       | 大きさ     | 信頼度\n");
	printf("-----------+------------+------------+----------\n");
	for (int i = 0; i < boxes.size(); i++)
	{
		printf("%-10s | (%3d, %3d) | (%3d, %3d) | %f\n",
			words[i].c_str(),
			boxes[i].x, boxes[i].y,
			boxes[i].width, boxes[i].height,
			confidences[i]);
	}
}
実行結果

f:id:whoopsidaisies:20141112103657p:plain

C#で細線化 AForge.NET

C#で画像の細線化処理を行って線画像を作成する.コンピュータビジョン等のライブラリであるAForge.NET Frameworkには,モルフォロジー処理が実装されているのでそれを利用する.

細線化のアルゴリズムは以下のサイトあたりを参照.
パターン認識の前処理に必要な二値画像の細線化:CodeZine
細線化アルゴリズム 画像処理ソリューション
technotype

AForge.NETのインストール

Nuget経由でインストール可能.プロジェクトを右クリックして「NuGetパッケージの管理」を選択する.オンラインから「AForge」で検索.「AForge.Imaging」を選択してインストール.

ソースコード

ヒット・ミス演算を行うHitAndMissクラスを使う.

// using AForge.Imaging.Filters; をソースコードに書いておく

// 元画像の読み込み
var bmp = new Bitmap(@"moji.png");
pictureBox1.Image = bmp;

// 二値化処理
var gray = new Grayscale(0.2125,0.7154,0.0721).Apply(bmp);
var binary = new Threshold().Apply(gray);
pictureBox2.Image = binary;            
            
// 細線化用の8つのフィルタを作成
var filterSequence = new FiltersSequence();
filterSequence.Add(new HitAndMiss(
    new short[,] { { 0, 0, 0 }, { -1, 1, -1 }, { 1, 1, 1 } },
    HitAndMiss.Modes.Thinning));
filterSequence.Add(new HitAndMiss(
    new short[,] { { -1, 0, 0 }, { 1, 1, 0 }, { -1, 1, -1 } },
    HitAndMiss.Modes.Thinning));
filterSequence.Add(new HitAndMiss(
    new short[,] { { 1, -1, 0 }, { 1, 1, 0 }, { 1, -1, 0 } },
    HitAndMiss.Modes.Thinning));
filterSequence.Add(new HitAndMiss(
    new short[,] { { -1, 1, -1 }, { 1, 1, 0 }, { -1, 0, 0 } },
    HitAndMiss.Modes.Thinning));
filterSequence.Add(new HitAndMiss(
    new short[,] { { 1, 1, 1 }, { -1, 1, -1 }, { 0, 0, 0 } },
    HitAndMiss.Modes.Thinning));
filterSequence.Add(new HitAndMiss(
    new short[,] { { -1, 1, -1 }, { 0, 1, 1 }, { 0, 0, -1 } },
    HitAndMiss.Modes.Thinning));
filterSequence.Add(new HitAndMiss(
    new short[,] { { 0, -1, 1 }, { 0, 1, 1 }, { 0, -1, 1 } },
    HitAndMiss.Modes.Thinning));
filterSequence.Add(new HitAndMiss(
    new short[,] { { 0, 0, -1 }, { 0, 1, 1 }, { -1, 1, -1 } },
    HitAndMiss.Modes.Thinning));

// フィルタ処理の繰り返し回数を指定
var filter = new FilterIterator(filterSequence, 20);

// 細線化処理の実行
var thinned = filter.Apply(binary);
pictureBox3.Image = thinned;

実行結果.

f:id:whoopsidaisies:20140915092901p:plain

C#でGISデータ(shpファイル)を開く DotSpatialによる方法

DotSpatialは.NET向けのオープンソースの地理情報システムライブラリである.これを使ってGISで用いられるshpファイル(とセットのshxファイルとdbfファイル)を読み込む.

DotSpatialのインストール

Nuget経由でインストール可能.プロジェクトを右クリックして「NuGetパッケージの管理」を選択する.オンラインから「DotSpatial」で検索.たくさん出てくるが,ここでは「DotSpatial.Controls」選択してインストール.

f:id:whoopsidaisies:20140914212117p:plain

Mapコントロールの追加

GISデータ表示用のコントロールが用意されているのでそれを使ってみる.

まず,一度プロジェクトをビルドする.そうするとDotSpatialのDLLが実行ファイルのディレクトリにコピーされる.

ツールボックスの適当なところでクリックして「アイテムの選択(I)...」を選択.
f:id:whoopsidaisies:20140914212749p:plain

「.NET Framework コンポーネント」タブの「参照(B)...」ボタンを押して,「(実行ファイルのパス)\DotSpatial.Controls.dll」を選択したらOK.

f:id:whoopsidaisies:20140914212821p:plain

f:id:whoopsidaisies:20140914213930p:plain

そうすると「Map」というコントロールが追加されているのでフォームに追加する.
f:id:whoopsidaisies:20140914213818p:plain

シェープファイルの読み込み

シェープファイルは「全国市区町村界データ | データ製品 | 製品 | ESRIジャパン株式会社」でESRI Japanが配布している全国市区町村界データを使う.ダウンロードして解答したら以下のコードを追加する.

map1.AddLayer(@"D:\Users\whoops\Downloads\japan_ver72\japan_ver72.shp");

実行すると以下のように地図が表示される.

f:id:whoopsidaisies:20140914214517p:plain

属性情報の表示 Infoモード

上の状態だと地図が表示されるだけで何もできないためMapコントロールのモードを変える.以下のようにコードを追加してInfoモードにする.

map1.AddLayer(@"D:\Users\whoops\Downloads\japan_ver72\japan_ver72.shp");
map1.FunctionMode = DotSpatial.Controls.FunctionMode.Info;

これで実行すると以下のようにクリックした位置の市町村の情報を見れる.

f:id:whoopsidaisies:20140914214930p:plain

領域の選択 Selectモード

Selectモードににするとドラッグで領域指定できる.

map1.FunctionMode = DotSpatial.Controls.FunctionMode.Select;

f:id:whoopsidaisies:20140915002531p:plain

選択しただけだと何も表示されないので,TextBoxに選択された領域の情報を表示してみる.MAPコントロールのMouseUpイベントを追加して以下のコードを書き込む.

private void map1_MouseUp(object sender, MouseEventArgs e)
{
    textBox1.Text = "";
    // MAPコントロールが保持しているレイヤーデータを取得
    var featureLayer = map1.Layers[0] as DotSpatial.Symbology.FeatureLayer;
            
    // すべてのデータについてループ
    for (int i = 0; i < featureLayer.DrawnStates.Length; ++i)
    {
        // 選択されているかどうか判別
        if (featureLayer.DrawnStates[i].Selected == true)
        {
            // i番目の属性情報取得
            var feature = featureLayer.FeatureSet.GetFeature(i);

            // DataRowからフィールド名でアクセス
            textBox1.Text += System.String.Format(
                "{0} : {1} {2} {3}人",
                feature.DataRow["JCODE"],
                feature.DataRow["KEN"],
                feature.DataRow["SIKUCHOSON"],
                feature.DataRow["P_NUM"]
                )
                + System.Environment.NewLine;
        }
    }
}

選択されているかどうかはレイヤーデータのDrawnStatesプロパティに格納されている.

属性情報はFeatureSetプロパティに格納されていてGetFeatureメソッドで指定した番号のデータを取得できる.

各属性情報へは,DataRawにフィールド名を指定することで取得できる.このサンプルで使っているフィールド名は「全国市区町村界データ | データ製品 | 製品 | ESRIジャパン株式会社」を参照.

実行してドラッグで領域選択すると以下のようにTextBoxに情報が表示される.
f:id:whoopsidaisies:20140915004858p:plain

MAPコントロールなしで読み込み

MAPコントロールを配置しなくてもDataManagerクラスのOpenFileメソッドを使えばシェープファイルは読み込める.

以下にシェープファイルを読み込んで,0番目のデータの座標を出力するコードを載せる.

// シェープファイルの読み込み
var layer = DotSpatial.Data.DataManager.DefaultDataManager.OpenFile(@"D:\Users\whoops\Downloads\japan_ver72\japan_ver72.shp") 
    as DotSpatial.Data.FeatureSet;
// 0番目のデータの座標を表示
foreach (var cordinate in layer.GetFeature(0).Coordinates)
{
    Console.WriteLine(cordinate.ToString());
}

実行結果
f:id:whoopsidaisies:20140915010455p:plain

コントロールを使わない方法だけを使う場合NuGetで「DotSpatial.Data」をインストールすればできる.