前回に続き、再度OpenCvSharpネタです。
色々使ってみたので、使い方などを一通りメモしときます。
参考情報
OpenCvSharpのC++API対応についての説明
http://schima.hatenablog.com/entry/2014/03/29/140106
OpenCvSharpのWiki。ここのC++APIの部分など。
https://github.com/shimat/opencvsharp/wiki
http://opencv.jp/reference_manual
OpenCvSharp3ではC APIは廃止して、C++APIを使うようになっています。
まずは、この辺のページを見て、OpenCVのC++APIをざっと把握するとよいと思います。
画像の入出力
以下の関数で、画像ファイルからMatクラスのデータを作ったり、、Matクラスのインスタンスの内容をファイルに書き出したりできます。
- Cv2.Imread・・・画像ファイルの読み込み
- Cv2.Imwrite・・・画像ファイルへの書き出し
var src = Cv2.ImRead(@"Images/1.jpg"); Cv2.ImWrite(@"output.jpg", src);
画像データの形式(Matクラス)
Imreadで読み込んだデータは、Matという型のインスタンスとして返されます。
これは、画像データをはじめ、各種行列形式のデータを汎用的に扱うための型とのこと。
詳しいことは、以下のリンクなどを参照してください。
http://opencv.jp/cookbook/opencv_mat.html
http://opencv.jp/opencv-2svn/cpp/core_basic_structures.html#mat
Matクラス作成時の画像読み込み
Imreadメソッドを使わなくても、以下のようにMatクラスのコンストラクタで、画像ファイルのパスを指定することで画像の読み込みができます。
インスタンス作成時に、画像読み込みまでしたい場合は、こちらの方が便利。
この呼び出し方はOpenCV本家のものではなく、OpenCvSharpnの独自拡張かな?たぶん。
細かい部分ですが、使いやすくなるよう配慮されてていいライブラリですね。
var img = new Mat(@"Images/1.jpg"); Cv2.ImShow("test", img); Cv2.WaitKey();
他にも、OpenCV本家と同様な、多数のコンストラクタのオーバーロードがあります。
こんな風に、塗りつぶし色を指定して、Matのインスタンスを作ることもできます。
var img = new Mat(240, 320, MatType.CV_8UC3, new Scalar(0, 0, 255)); Cv2.ImShow("test", img); Cv2.WaitKey();
ウィンドウ表示
今までもサラッとImShow
などの関数を使ってきましたが、
ここらで改めて、ウィンドウ表示関連の機能をまとめておきます。
以下のような関数があります。
- Cv2.ImShow・・・・・引数で指定した内容をウィンドウ表示する
- Cv2.WaitKey・・・・・キー入力が行われるまで、処理を止めて待機
- Cv2.DestroyWindow・・引数で指定したウィンドウを破棄
- Cv2.DestroyAllWindows・・OpenCVで作成したすべてのウィンドウを破棄
var img = new Mat(@"Images/1.jpg"); // 「test」という名前のウィンドウを作りimgの内容を表示 Cv2.ImShow("test", img); // キー入力があるまで待機 Cv2.WaitKey(); // 明示的に全画像の破棄を指示 Cv2.DestroyAllWindows();
Windowクラスを使ったウィンドウ表示
OpenCvSharpでは、Windowというクラスが定義されており、以下のようにWinowを作成して表示することができます。
var img = new Mat(@"Images/1.jpg"); using(var win = new Window("Sample Window", img)) { Cv2.WaitKey(); }
前回の記事でも書きましたが、WPF環境などで使う場合は、Windowというクラス名がSystem.Windows
とOpenCvSharp
それぞれの名前空間に存在するので、両方の名前空間をusingしている場合には注意が必要です。
Matクラスへのアクセス
各種画像の情報取得
画像の幅・高さや、チャンネル数・ビット深度情報、などなどの情報を表示してみます。
// Matクラスの各種情報を確認 var img = new Mat(@"Images/1.jpg"); Console.WriteLine($"Width: {img.Width}, Height: {img.Height}"); Console.WriteLine($"Cols: {img.Cols}, Rows: {img.Rows}"); Console.WriteLine($"Channels: {img.Channels()}, Depth: {img.Depth()}");
このDepthメソッドで取得できる値は、Matの各要素のビット深度を表します。ただし、この値はビット深度をビット数やバイト数で表した数値ではなく、OpenCV内で定義されたビット深度の種別を示す定数値となっているので注意が必要です。
http://opencv.jp/opencv-2svn/cpp/core_basic_structures.html#cv-mat-depth
このサンプルでは「0」という値になっていますが、これはOpenCvで定義されている定数のCV_8Uにあたるもので、8ビット符号なしデータとなります。
画像の加工/コピーなど
var src = new Mat(@"Images/1.jpg"); var dst = new Mat(); // リサイズ後の画像サイズを指定してリサイズする場合 Cv2.Resize(src, dst, new Size(320, 240), 0, 0, InterpolationFlags.Cubic); // 倍率指定でリサイズする場合 Cv2.Resize(src, dst, Size.Zero, 1, 0.5, InterpolationFlags.Cubic); // X軸方向に画像反転 Cv2.Flip(src, dst, FlipMode.X); // Y軸方向に画像反転 Cv2.Flip(src, dst, FlipMode.Y); // 画像データの複製 var clone = src.Clone(); // 画像の一部分を切り出して複製 var partialClone = src.Clone(new Rect(100, 100, 200, 150));
ピクセルデータの取得、設定
続いて、Matクラスとして扱っている画像データの、各ピクセルデータにアクセスしてみます。
ここでは何通りかの方法を用いてピクセルデータを直接操作し、ネガポジ反転を行ってみます。
// Get/Setメソッドを使ってアクセス var src = new Mat(@"Images/1.jpg"); for (var y = 0; y < src.Height; y++) { for (var x = 0; x < src.Width; x++) { var px = src.Get<Vec3b>(y, x); px[0] = (byte)(255 - px[0]); px[1] = (byte)(255 - px[1]); px[2] = (byte)(255 - px[2]); src.Set(y, x, px); } } Cv2.ImShow("image", src); Cv2.WaitKey();
// インデクサを使ったアクセス var src = new Mat(@"Images/1.jpg"); var indexer = src.GetGenericIndexer<Vec3b>(); for (var y = 0; y < src.Height; y++) { for (var x = 0; x < src.Width; x++) { var px = indexer[y, x]; px[0] = (byte)(255 - px[0]); px[1] = (byte)(255 - px[1]); px[2] = (byte)(255 - px[2]); indexer[y, x] = px; } } Cv2.ImShow("image", src); Cv2.WaitKey();
// 特定のデータ型に特化したMat派生クラスを使ったアクセス var src = new Mat(@"Images/1.jpg"); var mat3 = new MatOfByte3(src); var indexer = mat3.GetIndexer(); for (var y = 0; y < src.Height; y++) { for (var x = 0; x < src.Width; x++) { var px = indexer[y, x]; px[0] = (byte)(255 - px[0]); px[1] = (byte)(255 - px[1]); px[2] = (byte)(255 - px[2]); indexer[y, x] = px; } } Cv2.ImShow("image", src); Cv2.WaitKey();
↓のページを見てみると、MatOfByte3
など、特定のデータ型に特化したMat派生クラスを通してアクセスするのが最も高速なようです。
https://github.com/shimat/opencvsharp/wiki/%5BCpp%5D-Accessing-Pixel
とりあえず、最低限このようなピクセルデータへのアクセスだけできれば、自分で画像処理のロジックをガリガリ書いて色々遊べますね。
OpenCVの機能を使ってネガポジ反転
上で書いたサンプルでは、ピクセルデータを直接操作してネガポジ反転を行いましたが、単純にネガポジ反転するだけであれば、以下のようにOpenCVの機能でシンプルに書くこともできます。
// ネガポジ反転するだけなら、以下のようにNot演算で可能 var src = new Mat(@"Images/1.jpg"); var result1 = new Mat(); Cv2.BitwiseNot(src, result1); // ↓こんな書き方もあり var result2 = ~src;
各種アルゴリズム
Matクラスのデータに対して、様々な処理を行う関数が多数用意されてます。
ここでは初めの一歩として、ぼかし効果を与えるblurメソッドを呼び出してみたいと思います。
var src = new Mat(@"Images/1.jpg"); var dst = new Mat(); Cv2.Blur(src, dst, new Size(5, 5)); Cv2.ImShow("blur", dst); Cv2.WaitKey();
他にも多数のアルゴリズムが実装されています。
それぞれの使い方は、必要に応じてリファレンスを参照すればよいかと思います。
図形の描画
OpenCVでは、Mat型の画像に対して、線分やテキストなどの図形データを描画する方法が用意されてます。
こんな風に呼び出して、図形などを描画することができます。
var img = new Mat(240, 320, MatType.CV_8UC3, new Scalar(0, 0, 0)); // 直線を描画 Cv2.Line(img, new Point(10, 10), new Point(300, 10), new Scalar(0, 0, 255)); Cv2.Line(img, new Point(10, 30), new Point(300, 30), new Scalar(0,255, 0), 2); // 矩形を描画 Cv2.Rectangle(img, new Rect(50, 50, 100, 100), new Scalar(255, 0, 0), 2); // 文字を描画 Cv2.PutText(img, "Hello OpenCvSharp!!", new Point(10, 180), HersheyFonts.HersheyComplexSmall, 1, new Scalar(255, 0, 255), 1, LineTypes.AntiAlias); Cv2.ImShow("image", img); Cv2.WaitKey();
Matクラスのお作法
今までは、特に何も注意せずにMatクラスのインスタンスを作って使ってきましたが、ここではMat型のデータのメモリ管理を少し意識してみたいと思います。
OpenCvSharpでは、MatクラスをIDisposableなクラスとして定義しています。
なので、using構文などを用いて、リソース破棄のタイミングを厳密に管理することができます。
逆に言うと、using使ったり、Dispose()メソッドの呼び出しをしたりしないと、
Matクラスを作って確保されたメモリ領域がいつ破棄されるかは、.NetのGCがかかるタイミング次第、、、という事になってしまいます。
Matで扱うデータは、主に画像などのメモリを大量に消費するものになります。
基本的にはちゃんとusing使って、破棄のタイミングを適切に管理するのが望ましいかと思います。
using (var src = new Mat(@"Images/1.jpg")) using (var dst = new Mat()) { // ぼかしをかける Cv2.Blur(src, dst, new Size(5, 5)); // 結果表示用のウィンドウ作成using (var win = new Window("result", dst)) { Cv2.WaitKey(); } }
動画データを扱う
最後に動画データの扱いについてサラッと試してみます。
動画ファイルや、カメラからのキャプチャなど、動画データのソースとなるものを読み込むには、VideoCaptureクラスを用いることで簡単に利用できるようになります。
動画ファイルの読み込み
まずは、動画ファイルを読み込みしてみます。
このようにVideoCaptureクラスのコンストラクタで、動画ファイルのパスを渡すと、動画の各フレームデータを取得できるようになります。
以下のようなコードで動画ファイルの再生ができるはず、、、なのですが、
自分の環境では再生できませんでした。
OpenCvSharp2.4では、同様のコードで再生できたんだけど・・・
この辺は、原因調査中です。。。
var capture = new VideoCapture(@"Path/To/MovieFile.xxx"); using (var win = new Window("capture")) using (var mat = new Mat()) { while (true) { capture.Read(mat); // 読み込めるフレームがなくなったら終了if (mat.Empty()) { break; } win.ShowImage(mat); Cv2.WaitKey(33); } }
Webカメラからのキャプチャ
Webカメラからのキャプチャも、画像ファイルを扱うのと同じような感じでコーディングできます。
以下のように、VideoCaptureクラスのコンストラクタに、デバイスIDの数値を渡すことで、指定したデバイスでキャプチャを開始することができます。
このとき、「0」を指定するとデフォルトのデバイスをオープンします。
// デフォルトのカメラをオープン var capture = new VideoCapture(0); using (var win = new Window("capture")) using (var mat = new Mat()) { while (true) { capture.Read(mat); win.ShowImage(mat); if (Cv2.WaitKey(30) >= 0) { break; } } }
こちらは、OpenCvSharp3系のバージョンでもうまく動きました。
動画データの保存
以下のように、VideoWriterクラスを使って、動画データの保存ができます。
ただし、こちらも動画読み込みと同じように、自分の環境では正しく動画データの出力できず・・・
実行すると、エンコーダの選択画面が出るのですが、選んだものによってはファイルが出力されない、、とか、ファイル出力されても、再生できなかったり、、、などなど。
この辺も、もう少し調べてみようと思います。
// デフォルトのカメラをオープンusing (var capture = new VideoCapture(0)) using (var writer = new VideoWriter("test.avi", FourCC.Default, capture.Fps, new Size(capture.FrameWidth, capture.FrameHeight))) using (var win = new Window("capture")) using (var mat = new Mat()) using (var dst = new Mat()) { while (true) { capture.Read(mat); Cv2.CvtColor(mat, dst, ColorConversionCodes.BGR2GRAY); win.ShowImage(dst); writer.Write(dst); if (Cv2.WaitKey(30) >= 0) { break; } } }