OpenCVSharpによる差分検出方法in development

 
date:2015.01.13   posted by:lee
 

こんにちは、リーです。

さて、今日はOpenCVについて話したいと思います。

OpenCVは画像処理をする時によく使われる技術です。
しかし公式ではC、C++、PythonしかなくてC#慣れている自分は困っていました。

とその時!”opencv c#“でG先生に聞いたところ、それらしい情報が出てきました!やった!

今回はopenCVで何を検出したいかというと(題に書いてありますが。。)
そうです!差分検出です!

差分検出とは、背景差分のことで、要は「背景と違うものを抽出する」という物です。
この分野の研究は1990年代(諸説あり)から今でも続いています。

今回は、USBカメラと差分検出技術を使って、背景と違うものを検出したいと思います!

環境:
Visual Studio Express 2013

使用言語:
c#

OpenCvSharpインストール方法:
Nugetで”Opencvsharp”を検索し、そのままインストール。楽勝!

まずopencvsharpのnamespaceを導入–『重要』–

using OpenCvSharp;

そしてカメラを起動するコードは以下となります

private void ShowCamera()
{
	//カメラ映像を表示
	int cameraPort = 0;
	using (var capture = Cv.CreateCameraCapture(cameraPort))
	{
	    IplImage frame = new IplImage();

	    //  W640 x H480 (default resolution)のウィンドウを作る
	    int w = 640, h = 480;

	    Cv.SetCaptureProperty(capture, CaptureProperty.FrameWidth, w);
	    Cv.SetCaptureProperty(capture, CaptureProperty.FrameHeight, h);

	    //ESCキー押さない限り
	    while (Cv.WaitKey(1) != 27)
	    {
	        frame = Cv.QueryFrame(capture);

	        if (frame != null)
	        {
	            //frameを表示
	            Cv.ShowImage("Liveカメラ(元)", frame);
	        }
	    }
	    //ESCキーが押されたら
	    //全ての画像やwindowを削除
	    Cv.DestroyAllWindows();
	    capture.Dispose();
	}
}

ShowCamera()メソッドを実行すると、以下のようになります。

*camePortはお使いのPCのusbカメラの番号になります。大体は0か1です。


次にこのカメラの生映像にフィルターをかけます。
使用するのはCannyという物体のエッジを検出するフィルターです。
ShowCamera()メソッドを少し追加していきます。

private void ShowCamera()
{
	//カメラ映像を表示
	int cameraPort = 0;
	using (var capture = Cv.CreateCameraCapture(cameraPort))
	{
	    //省略

	    //ESCキー押さない限り
	    while (Cv.WaitKey(1) != 27)
	    {
	        frame = Cv.QueryFrame(capture);

	        if (frame != null)
	        {
	        	int threshold1 = 120;
	        	int threshold2 = 80;

	        	//trimmingするための座標
	        	int trimmingX = 50;
                	int trimmingY = 20;
	                int trimmingW = 300;
	                int trimmingH = 300;

        	        IplImage edgeFrame = new IplImage(frame.Width, frame.Height, BitDepth.U8, 1);
               	 	frame.Canny(edgeFrame, threshold1, threshold2);

                	//frameをトリミングする
	                IplImage trimmedFrame = trimming(edgeFrame, trimmingX, trimmingY, trimmingW, trimmingH);

        	        Cv.ShowImage("Liveカメラ(元)", frame);
                	Cv.ShowImage("Liveカメラ(Edge)", edgeFrame);
	                Cv.ShowImage("抽出した映像", trimmedFrame);

                	//メモリ解放
                	Cv.ReleaseImage(trimmedFrame);
                	Cv.ReleaseImage(edgeFrame);
                	Cv.ReleaseImage(frame);
	        }
	    }

	    //省略
	}
}

//frameをtrimmingする
private IplImage trimming(IplImage src, int x, int y, int width, int height)
{
    IplImage dest = new IplImage(width, height, src.Depth, src.NChannels);
    Cv.SetImageROI(src, new CvRect(x, y, width, height));
    dest = Cv.CloneImage(src);
    Cv.ResetImageROI(src);
    return dest;
}

メソッドを実行すると、
三つのwindowが出てくるはずです。
左:生映像
中:Cannyフィルターをかけた映像
右:トリミングした映像

*結果は環境や使うカメラの解像度によって少し変わる


ここからが本題です。画像データは取得できたが、ではどうやって数値化して検出すればいいのか?
まずは各画像の正規値を出してみましょう。

private void ShowCamera()
{
	//カメラ映像を表示
	int cameraPort = 0;
	using (var capture = Cv.CreateCameraCapture(cameraPort))
	{
	    //省略

	    double valueOriginal, valueEdge, valueTrimmed;

	    //ESCキー押さない限り
	    while (Cv.WaitKey(1) != 27)
	    {
	        frame = Cv.QueryFrame(capture);

	        if (frame != null)
	        {
	        	//省略

	        	//任意ところに値を表示する
	        	liveCamera_original_lbl.Text = valueOriginal.ToString();
                	liveCamera_edge_lbl.Text = valueEdge.ToString();
	                trimmedEdge_lbl.Text = valueTrimmed.ToString();

        	        //メモリ解放
	                Cv.ReleaseImage(trimmedFrame);
	                Cv.ReleaseImage(edgeFrame);
	                Cv.ReleaseImage(frame);
	        }
	    }

	    //省略
	}
}

実行するとこのような画面になるはずです。

*実際の値はリアルタイムで変化しています


一番左の画像では、カラーのため値が一番高く、真ん中は黒と白しかないため値はやや少ないです。
一番右の画像では、真ん中の写真から抽出したがサイズが小さいため、値が真ん中の画像より少ないです。

では今度は一番右の写真を使って、背景と違う物体があるときを検出したいと思います。
検出するためには、背景となる写真を一回取って、正規値を取得しなければいけないので、
以下のメソッドを追加します。

private double TakeBackgroundImage()
{
    int cameraPort = 0;
    using (var capture = Cv.CreateCameraCapture(cameraPort))
    {
        IplImage frame = new IplImage();
        IplImage edgeFrame = new IplImage();
        IplImage trimmedFrame = new IplImage();

        double valueBackground;

        //20回を撮る
        for (int i = 0; i < 20; i++)
        {
            //  W640 x H480 (default resolution)のウィンドウを作る
            int w = 640, h = 480;
            Cv.SetCaptureProperty(capture, CaptureProperty.FrameWidth, w);
            Cv.SetCaptureProperty(capture, CaptureProperty.FrameHeight, h);

            frame = Cv.QueryFrame(capture);

            if (frame != null)
            {
                int threshold1 = 120;
                int threshold2 = 80;

                int trimmingX = 50;
                int trimmingY = 20;
                int trimmingW = 300;
                int trimmingH = 300;

                edgeFrame = new IplImage(frame.Width, frame.Height, BitDepth.U8, 1);
                frame.Canny(edgeFrame, threshold1, threshold2);

                //frameをトリミングする
                trimmedFrame = trimming(edgeFrame, trimmingX, trimmingY, trimmingW, trimmingH);

            }
        }

        //画像を保存
        trimmedFrame.SaveImage("background.bmp");
        valueBackground = Cv.Norm(trimmedFrame, null, NormType.L1);
        bgImage_lbl.Text = valueBackground.ToString();

        return valueBackground;
    }
}

20回取る理由は、カメラが安定するまでのFrame数です。回数を変更して保存した画像で確認してみてください。画像は実行フォルダに保存されます。
(今はedgeの画像を保存して、わかりづらいですが、trimmedFrameからframeに変更すればわかりやすいです)

このメソッドを追加したら、ShowCamera()メソッドに適応します。

private void ShowCamera()
{
    int cameraPort = 0;
    //カメラ映像を表示
    using (var capture = Cv.CreateCameraCapture(cameraPort))
    {
        //省略

        double valueBackground = TakeBackgroundImage();

        //ESCキー押さない限り
        while (Cv.WaitKey(1) != 27)
        {

            frame = Cv.QueryFrame(capture);

            if (frame != null)
            {
                //省略

                if (valueTrimmed >= valueBackground * 1.2)
                {
                    //検出した処理
                    detect_lbl.Text = "検出";
                }
                else
                {
                    //検出しなかった処理
                    detect_lbl.Text = "なし";
                }

                //メモリ解放
                Cv.ReleaseImage(trimmedFrame);
                Cv.ReleaseImage(edgeFrame);
                Cv.ReleaseImage(frame);
            }
        }

        //省略
    }
}

実行すると以下のような動作になります。
*画質悪くてすみません!

ご覧のとおり、影のエッジでもしっかり捉えています。コード途中の1.2は検出感度の係数で、係数が小さければ感度が敏感になります。

この手法は処理した画像(エッジ)の正規値を検出しているので、人であろうが物体であろうが、エッジが検出してから反応します。一方、生映像の正規値で物体検出すると、物体はもちろん検出できますが、光の影響で環境に影響を与えると、誤検出が発生してしまします。

とりあえず設定範囲内でなにかしら物体検出したいときは、この方法が一番早いと思います!
ぜひぜひ試してみてください!!!!!!!

サンプルがほしい方はこちらどうぞ!!
サンプル

 
Copyright © TheDesignium inc. powered by WordPress & mootools.
Relative Keyword|none