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();
結果画像
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」を探してクリックしてウィンドウを表示させる.
そうすると以下の様なウィンドウが出てきて,Mat型の変数とその中身の画像が表示される.
左側に画像一覧があるので,クリックすれば以下のようにその画像が表示される.
マウススクロールで拡大可能.
最大まで拡大すると各画素の画素値が数字で表示される.
画素を右クリックしたら,その画素のアドレスのコピーとかも出来るもよう.
以上のように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種類の文字認識方法があるらしい.
- オープンソースのOCRライブラリtesseract-ocrを呼び出す方法
- 隠れマルコフモデルによる認識方法
今回は,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]); } }
実行結果
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;
実行結果.
C#でGISデータ(shpファイル)を開く DotSpatialによる方法
DotSpatialは.NET向けのオープンソースの地理情報システムライブラリである.これを使ってGISで用いられるshpファイル(とセットのshxファイルとdbfファイル)を読み込む.
DotSpatialのインストール
Nuget経由でインストール可能.プロジェクトを右クリックして「NuGetパッケージの管理」を選択する.オンラインから「DotSpatial」で検索.たくさん出てくるが,ここでは「DotSpatial.Controls」選択してインストール.
Mapコントロールの追加
GISデータ表示用のコントロールが用意されているのでそれを使ってみる.
まず,一度プロジェクトをビルドする.そうするとDotSpatialのDLLが実行ファイルのディレクトリにコピーされる.
ツールボックスの適当なところでクリックして「アイテムの選択(I)...」を選択.
「.NET Framework コンポーネント」タブの「参照(B)...」ボタンを押して,「(実行ファイルのパス)\DotSpatial.Controls.dll」を選択したらOK.
そうすると「Map」というコントロールが追加されているのでフォームに追加する.
シェープファイルの読み込み
シェープファイルは「全国市区町村界データ | データ製品 | 製品 | ESRIジャパン株式会社」でESRI Japanが配布している全国市区町村界データを使う.ダウンロードして解答したら以下のコードを追加する.
map1.AddLayer(@"D:\Users\whoops\Downloads\japan_ver72\japan_ver72.shp");
実行すると以下のように地図が表示される.
属性情報の表示 Infoモード
上の状態だと地図が表示されるだけで何もできないためMapコントロールのモードを変える.以下のようにコードを追加してInfoモードにする.
map1.AddLayer(@"D:\Users\whoops\Downloads\japan_ver72\japan_ver72.shp"); map1.FunctionMode = DotSpatial.Controls.FunctionMode.Info;
これで実行すると以下のようにクリックした位置の市町村の情報を見れる.
領域の選択 Selectモード
Selectモードににするとドラッグで領域指定できる.
map1.FunctionMode = DotSpatial.Controls.FunctionMode.Select;
選択しただけだと何も表示されないので,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に情報が表示される.
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()); }
実行結果
コントロールを使わない方法だけを使う場合NuGetで「DotSpatial.Data」をインストールすればできる.
SVGアニメーションで初音ミクの一筆書き
SVGによるアニメーションの勉強メモと,作った初音ミクの一筆書きアニメーション.
アニメーションをSVGのanimateMotion要素と,CSSのanimationプロパティを使った.本当はCSS使わないでやりたかったのだけどそれだと面倒くさそうだったのでCSSも使った.
元画像は以下のサイトから持ってきた.
piapro(ピアプロ)|イラスト「一筆書きで 初音ミクを 描きました」
以下が作ったアニメーション.SVGとCSSのアニメーションの同期の取り方がわからなかったので,読み込みの度にタイミングが変わってしまう.
※SVGファイルを置くのに借りていたレンタルサーバーの期限が切れてしまったため、暇なとき別のところにアップロードしなおします。
SVG animateMotion要素
赤い円を移動させるのには,SVGのanimateMotion要素を使った.
animateMotion要素を用いると,図形をpathで指定した任意の曲線に沿って移動させることが出来る.また,mpath要素によって以下のようにpathの参照が可能.
<path d="M10,110 A120,120 -45 0,1 110 10 A120,120 -45 0,1 10,110" stroke="lightgrey" stroke-width="2" fill="none" id="theMotionPath"/> <circle cx="10" cy="110" r="3" fill="lightgrey" /> <circle cx="110" cy="10" r="3" fill="lightgrey" /> <!-- Here is a red circle which will be moved along the motion path. --> <circle cx="" cy="" r="5" fill="red"> <!-- Define the motion path animation --> <animateMotion dur="6s" repeatCount="indefinite"> <mpath xlink:href="#theMotionPath"/> </animateMotion> </circle>animateMotion - SVG | MDN
CSS animate要素
線を描いていく様子は,CSSのanimate要素を使って破線の間隔を変える方法を使った.
破線の間隔が変わることで線が伸びて行っているように見えるようだ.
How SVG Line Animation Works | CSS-Tricks
CSSに以下のように描いてやるといいようだが,stroke-dasharrayやstroke-dashoffsetの値を線の長さくらいにしなくてはならないらしく少し面倒.
path { stroke-dasharray: 1000; stroke-dashoffset: 1000; animation: dash 5s linear forwards; } @keyframes dash { to { stroke-dashoffset: 0; } }
d3.jsで作成
初音ミクの座標データは画像からこつこつと抽出した.以下からダウンロード可能.
http://whoopsidaisies.site-station.net/blog/miku.csv
SVG直打ちでもよかったが,せっかくなのでd3.jsでCSVファイルを読み込んでSVGを吐き出した.
d3.csv("miku2.csv", function (data) { var line = d3.svg.line() .x(function (d) { return Math.round(d.x * 10) / 10; }) .y(function (d) { return Math.round(d.y * 10) / 10; }) .interpolate("linear"); var svg = d3.select("body").append("svg").attr("width", 700).attr("height", 900); var path = svg.append("path") .attr({ "d": line(data), "stroke": "black", "stroke-width": 2, "fill": "none", "id": "mikupath" }); svg.append("circle") .attr({ "cx": "", "cy": "", "r": "5", "fill": "red" }) .append("animateMotion") .attr({ "dur": "20s", "repeatCount": "indefinite" }) .append("mpath") .attr("xlink:href", "#mikupath"); });
ソースコード全文は以下.
http://whoopsidaisies.site-station.net/blog/mikuanimation2.html
実行後のhtmlは以下.
http://whoopsidaisies.site-station.net/blog/mikuanimation.html
※レンタルサーバーの期限が切れてしまったため、暇なとき別のところにアップロードしなおします。
PowerPointのオートシェイプで好きな関数の曲線を作る C# NetOfficeによる方法
オートシェイプできれいな波形を作りたいときがたまにあるのだけど,C#を使って上の画像みたいにsin(x)/xのオートシェイプをわりときれいに作れたので手順をまとめる.
試した環境はPowerPoint2010とVisual Studio 2013.バージョンが違うと少し変わる可能性はあると思う.(わざわざC#でやらずともVBAマクロでも同じことは出来るけど,VBAは苦手なのでC#でやる.VBAでのやり方も後述します)
出来上がったPowerPointファイルを以下からダウンロードできるようにしておいた.
sinc.pptx - Google ドライブ
NetOffice.PowerPointのインストール
以前「C#でExcelファイルを作成・グラフを挿入する NetOfficeによる方法 - whoopsidaisies's diary」で紹介した,NetOfficeという.NET用のライブラリを使うとPowerPoint等にアクセスして色々出来るのでそれを利用する.
NetOfficeはNuGet経由でインストール可能.プロジェクトを右クリックして「NuGetパッケージの管理」を選択する.オンラインから「NetOffice.PowerPoint」を検索してインストール.
ソースコード
以下にソースコードを載せる.スライドを作ってAddCurveメソッドで曲線を追加しているだけである.
AddCurveメソッドの引数として,float[(点の数),2]に座標を格納して渡す.この点の座標を元にベジェ曲線を描くようである.点の数は3n+1個にしないと止まるので注意.
点の座標はスライドの左上が(0,0)でピクセルで指定する.スライドのオブジェクトのPageSetupプロパティにスライドの幅と高さが格納されているのでそれを元に大きさを調整した.
static void Main(string[] args) { // パワーポイントを開く using (var app = new NetOffice.PowerPointApi.Application()) { // プレゼンテーションの作成 var presentation = app.Presentations.Add(); // スライドの追加 var slide = presentation.Slides.Add(1, NetOffice.PowerPointApi.Enums.PpSlideLayout.ppLayoutBlank); // sin(x)/xの座標を得る var sinc = GetSinc(presentation.PageSetup.SlideWidth, presentation.PageSetup.SlideHeight); // 座標からベジェ曲線を描画する // 引数はfloatの二次元配列.座標の数は6n+1個じゃないといけないため注意. slide.Shapes.AddCurve(sinc); // プレゼンテーションの保存 presentation.SaveAs(@"D:\a.pptx"); // プレゼンテーションファイルを閉じる presentation.Close(); // パワーポイントの終了 作成したファイル以外も閉じられるので注意 //app.Quit(); } } /// <summary> /// sin(x)/x関数の座標をスライドの大きさに合わせて拡大, /// </summary> /// <param name="slideWidth">スライドの幅</param> /// <param name="slideHeight">スライドの高さ</param> /// <returns>sin(x)/x関数を描くための点の座標</returns> static float[,] GetSinc(float slideWidth, float slideHeight) { // xが-4π~4πまでのsin(x)/xを求める.601点. var xs = Enumerable.Range(-300, 601).Select(x => x * 4 * Math.PI / 300); var ys = xs.Select(x => x != 0 ? Math.Sin(x) / x : 1); // PowerPointのスライドのサイズに合わせて移動・拡大をする // 中心座標 var cx = slideWidth / 2; var cy = slideHeight / 2; // オブジェクトのだいたいの大きさ var width = slideWidth * 0.8; var height = slideHeight * 0.8; var pptXs = xs.Select(x => x / (4 * Math.PI) * width / 2 + cx).ToArray(); var pptYs = ys.Select(y => -y * height / 2 + cy).ToArray(); // AddCurveメソッドに渡すためにfloatの2次元配列に代入する var sinc = new float[pptXs.Length, 2]; for (int i = 0; i < pptXs.Length; ++ i) { sinc[i, 0] = (float)pptXs[i]; sinc[i, 1] = (float)pptYs[i]; } return sinc; }
実行すると,記事の最初の画像のpptxファイルが作成される.GetSincメソッドの部分を適当な座標を格納をするように変えれば自分の好きな曲線を引ける.
ちなみに出来た曲線はオートシェイプなので,以下の画像のように線の太さとか種類とか色とかも簡単に変えられる.
拡大してもきれい.
VBAマクロでのやり方
記事の最初にも述べたが,C#を使わずともVBAマクロで同じことが出来る.以下に参考になるコードを載せる(ここのサンプル見やすくしただけだけど).
これの点の座標を引きたい曲線の座標にしてやればOK.
Sub DrawCurve() ' 1枚目のスライドのオブジェクトを得る Dim slide As PowerPoint.slide Set slide = ActivePresentation.Slides(1) ' ベジェ曲線を引くための点の座標を配列に格納 ' ここの座標の値を適当に変えてやれば好きな曲線が引ける Dim points(1 To 7, 1 To 2) As Single points(1, 1) = 0 points(1, 2) = 0 points(2, 1) = 72 points(2, 2) = 72 points(3, 1) = 100 points(3, 2) = 40 points(4, 1) = 20 points(4, 2) = 50 points(5, 1) = 90 points(5, 2) = 120 points(6, 1) = 60 points(6, 2) = 30 points(7, 1) = 150 points(7, 2) = 90 ' 曲線の追加 slide.Shapes.AddCurve SafeArrayOfPoints:=points End Sub