2012年9月9日日曜日

KINECTサンプル(C#) OpenCV for SDK1.5

今日のサンプルはOpenCVを使っての初歩的な画像処理を行います。
KINECTそのものはあくまでもセンサーなので、受け取ったデータはPCで処理をしなければいけません。その中でメインはやはり画像処理になってくるのでしょうが、Windows自体は画像処理は得意な方ではありません。そこで外部ライブラリを利用することになります。よく使われているのがOpenCVというライブラリになります。
OpenCVはもともとC++およびC言語向けとしてプログラミングインターフェイスを公開しているので.NetFrameworkからの利用は難しくなります。そこでOpenCVSharpというラッパーライブラリを利用することにします。

まずはOpenCVのインストールです。

現在OpenCVの最新バージョンは2.4.2(2012/09/09現在)となりますが、少し前のバージョン2.3.1を使います。
http://sourceforge.net/projects/opencvlibrary/files/opencv-win/2.3.1/より「OpenCV-2.3.1-win-superpack.exe」をダウンロードしてください。
ダウンロードしたファイルを解凍すると、opencvフォルダができますので、好きな場所にコピーします。今回はC:\にコピーしました。
次にシステム環境変数のPathを設定をします。ここでは使っているOSの種類やOpenCVの種類によって指定するPathが変わるのですが、今回は32bit向けの設定にします。(OSが64ビットでも動きます。)
「コンピュータ」のプロパティを開き、「システムの詳細設定」>「詳細設定」タブ>「環境変数」ボタンを押します。システム環境変数の「Path」の選び編集ボタンを押します。変数値の一番最後に「c:\opencv\build\x86\vc10\bin;c:\opencv\build\common\tbb\ia32\vc10」と2つのパスを追加します。かならず現在の変数値の最後に追加するようにしてください。誤ってほかの箇所を変更してしまうと動かなくなるアプリケーションが出るかもしれません。また追加する際にパスの区切りである「;」(セミコロン)を忘れずにつけましょう。
以上でOpenCVの設定は終わりです。

続いてOpenCVSharpの設定です。
http://code.google.com/p/opencvsharp/の左メニューの「Downloads」から「OpenCvSharp-2.3.1-x86-20120519.zip」をダウンロードして適当な場所に解凍します。
このままでもいいのですが、解凍したフォルダ内のDLLと「XmlDoc-Japanese」内のファイルを同一フォルダにまとめておくと、プログラム入力中に日本語でツールチップが表示されるので便利です。

ではサンプルプログラムです。
サンプルではKINECTのカラーイメージとOpenCVでCannyを用いた2値化画像を同時に表示しています。

MainWindow.xaml
------------------------------------------------------------------------------

<Window x:Class="KinectOpenCV.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignWidth="1308" SizeToContent="WidthAndHeight" d:DesignHeight="519">
    <Grid>
        <Image Height="480" HorizontalAlignment="Left"  Name="image1" Stretch="Fill" VerticalAlignment="Top" Width="640" />
        <Image Height="480" HorizontalAlignment="Left" Margin="646,0,0,0" Name="image2" Stretch="Fill" VerticalAlignment="Top" Width="640" />
    </Grid>
</Window>
------------------------------------------------------------------------------
イメージコントロールを2つ横に並べて配置しています。



参照設定がいくつか必要になります。
「参照設定」>「参照の追加」の「.NET」タブから「Microsoft.Kinect」と「System.Drawing」を追加してください。次に参照タブから先ほどOpenCVSharpを保存したフォルダを選択して、「OpenCvSharp.dll」と「OpenCvSharp.Extensions.dll」を選択してください。


MainWindow.xaml.cs
------------------------------------------------------------------------------

using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

using System.Drawing;

using Microsoft.Kinect;
using OpenCvSharp;
using OpenCvSharp.Extensions;

namespace KinectOpenCV
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private KinectSensor kinect;
        private WriteableBitmap srcImg;
        private WriteableBitmap distImg;
        private Int32Rect bitmapRect;
        private IplImage CvImg;
        private IplImage CvGrayImg;
        private IplImage CannyImg;

        /// <summary>
        ///
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            if (KinectSensor.KinectSensors.Count == 0)
            {
                MessageBox.Show("KINECTがみつかりません。");
                Close();
            }

            kinect = KinectSensor.KinectSensors[0];

            kinect.ColorStream.Enable();

            kinect.ColorFrameReady += new EventHandler<ColorImageFrameReadyEventArgs>(kinect_ColorFrameReady);

            kinect.Start();

            //OpenCV用
            srcImg=new WriteableBitmap(kinect.ColorStream.FrameWidth,kinect.ColorStream.FrameHeight,96,96,PixelFormats.Bgra32,null);
            //OpenCV処理後用
            distImg = new WriteableBitmap(kinect.ColorStream.FrameWidth, kinect.ColorStream.FrameHeight, 96, 96, PixelFormats.Bgr32, null);
            //画像のサイズ(640*480)
            bitmapRect=new Int32Rect(0,0,kinect.ColorStream.FrameWidth,kinect.ColorStream.FrameHeight);

            //OpenCV用画像
            CvImg = Cv.CreateImage(new CvSize(640, 480), BitDepth.U8, 3);
            //OpenCV用グレースケール画像
            CvGrayImg = new IplImage(CvImg.Size, BitDepth.U8, 1);
            //OpenCV用Canny処理画像
            CannyImg = new IplImage(CvImg.Size, BitDepth.U8, 1);
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void kinect_ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
        {
            using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
            {
                if (colorFrame != null)
                {
                    //KINECTからのカラーイメージデータ
                    byte[] colorPixel = new byte[colorFrame.PixelDataLength];
                    colorFrame.CopyPixelDataTo(colorPixel);

                    //OpenCV向けWritablebitmapに書込み
                    srcImg.WritePixels(bitmapRect, colorPixel, colorFrame.Width * colorFrame.BytesPerPixel, 0);
                    //通常画像むけWritablebitmapに書込み
                    distImg.WritePixels(bitmapRect, colorPixel, colorFrame.Width * colorFrame.BytesPerPixel, 0);

                    //WritablebitmapをIplImageに変換
                    CvImg = srcImg.ToIplImage();
                 
                    //グレースケール処理
                    Cv.CvtColor(CvImg, CvGrayImg, ColorConversion.BgrToGray);
                    //Canny処理(2値化)
                    Cv.Canny(CvGrayImg, CannyImg, 50, 200);

                    //通常画像を表示
                    image1.Source = distImg;
                    //Canny処理後の画像を変換して表示
                    image2.Source = CannyImg.ToWriteableBitmap();

                }
            }                
        }
    }
}
------------------------------------------------------------------------------


以前のRGBカメラサンプルではイメージコントロールにBitmapSourceを使っていましたが、今回はWritableBitmapを使っています。
OpenCVでは画像処理形式としてIplImageというデータ形式を使っています。IplImageではBGR32データは受け取れないのでRGB24形式に変換するプログラムを書くか、BGRA32形式のデータを使うかしなければいけません。
今回はBGRA32形式のWritabebitmapを変換してOpenCVに渡すようにしています。



3 件のコメント:

  1. はじめまして、記事を読ませていただきました。
    質問があるのですが、Cannyフィルタによって得られるエッジの座標情報がリアルタイムに更新
    されていると思うのですが、どこにその情報が存在しているのでしょうか。
    その座標情報を配列に入れて使いたいと考えています。
    よろしければ、ご回答の方宜しくお願いします。

    返信削除
    返信
    1. 初めまして。
      サンプルは古いので現在ではちょっと変わっているかもしれませんけども、基本的な手順の説明ということになります。

      実際の画像処理はkinect_ColorFrameReadyイベントルーチン内で行っています。

      処理の流れとして KinectRGBデータ>Writablebitmapに変換>IplImageに変換>グレースケールに変換>Canny処理となっています。

      Canny処理を施した画像データはCannyImgに入っていることになります。
      CannyImgを配列に入れることで、利用することはできるかと思います。

      ただ、C#では画像<>配列の変換が必要になってくるため、どうしても処理時間がかかってしまうことになります。
      やはりある程度のリアルタイム処理を行いたい場合にはC++でプログラムを組む方がいいのではと思います。(C++では元々イメージデータは配列で処理するので・・・)

      削除
    2. 迅速な対応をありがとうございます。
      おっしゃられていた通り座標情報はCannyImgのなかに見つけました。
      加えてご指摘の通り重すぎて全く動きませんでした・・・。
      for (int y = 0; y < CannyImg.Height; y++){
      for (int x = 0; x < CannyImg.Width; x++){
      if (CannyImg[x + CannyImg.Width * y] > 128) {
      }}}
      のようなプログラムでエッジ座標を探索しただけで止まってしまいます。
      なのでC++に移行するか、リアルタイムをあきらめるなのかなと思いますが、プログラミング初心者ということもありC++には精通していないのでリアルタイムをあきらめることにします!
      ありがとうございました!

      削除