OpenCvSharpを使う その4 (フィルタリング)

今回はIplImageと並んで重要な配列オブジェクトであるCvMatについてご説明しようと思います。CvMatを使う例としてここではcvFilter2Dを用いたフィルタリング処理を行います。


以下のコードは、OpenCV-1.1pre リファレンス マニュアル(日本語訳)のサンプルコードから拝借し、OpenCvSharp仕様に書き換えたものです。
http://opencv.jp/sample/filter_and_color_conversion.html#filter2d
詳細についてはこちらのページをご覧ください。ここでは主に、OpenCvSharpではどう書くか、という内容に絞って説明します。あまりcvFilter2DやcvNormalizeの詳しい説明はしません(というかできない・・・)。

using System;
using OpenCvSharp;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            using (IplImage src = new IplImage("penguin.png", LoadMode.AnyDepth | LoadMode.AnyColor))
            using (IplImage dst = new IplImage(src.Size, src.Depth, src.NChannels))
            {
                float[] data = {    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
                                    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
                };
                CvMat kernel = new CvMat(1, 21, MatrixType.F32C1, data);

                Cv.Normalize(kernel, kernel, 1.0, 0, NormType.L1);
                Cv.Filter2D(src, dst, kernel, new CvPoint(0, 0));

                using (new CvWindow("src", src))
                using (new CvWindow("dst", dst))
                {
                    Cv.WaitKey(0);
                }
            }
        }
    }
}


フィルタのマスクを作成

フィルタのマスクをCvMatで定義します。行列のサイズと要素ごとのビットデプスとチャンネル数、そして行列データを指定します。ここでは行数は1なので、行列というよりはベクトルです。

float[] data = {    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
                    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
};
CvMat kernel = new CvMat(1, 21, MatrixType.F32C1, data);

このように、行列データを通常の配列で指定することができます。もしこのkernelが以後の処理でデータが書き換えられた場合、dataの方の値も書き変わります。

// data[0] = 2, kernel[0] = 2
Console.WriteLine("data[0] = {0}, kernel[0] = {1}", data[0], Cv.GetReal1D(kernel, 0));

// kernelの方を書き換えてみる
Cv.SetReal1D(kernel, 0, 777);

// data[0] = 777, kernel[0] = 777
Console.WriteLine("data[0] = {0}, kernel[0] = {1}", data[0], Cv.GetReal1D(kernel, 0));

内部的には、マネージ配列の先頭要素のアドレスをcvSetDataで割り当てているためにこのように連動する仕組みになっています。

マスクの正規化

Cv.Normalizeを用いて、kernelを正規化します。

Cv.Normalize(kernel, kernel, 1.0, 0, NormType.L1);

入力と出力に同じ変数を指定しています。このような使い方をインプレースモードといって、Normalizeのほかにも対応している関数はいくつかあります。出力先を用意しなくてよいので楽です。

正規化にはいくつか種類がありますが、ここではベクトルの各要素の絶対値の総和を1にするような処理を行います。数式で書くところの、
\sum_i \left| kernel_i \right| = 1
になるように各要素の値が書き変わります。Normalizeの後に実際に計算してみると、1になっていることが確認できます。

double sum = 0;
foreach (float item in data)
{
    sum += Math.Abs(item);
}
Console.WriteLine(sum);    // =>  0.999999970197678

フィルタリングの実行

マスクが用意できたら、後はそれを使ってcvFilter2Dを呼び出すだけです。詳細はリファレンスを参照してください。

Cv.Filter2D(src, dst, kernel, new CvPoint(0, 0));


ビジュアル情報処理CG・画像処理入門

ビジュアル情報処理CG・画像処理入門