2012年9月13日木曜日

KINECTサンプル(C#) HoGを使った人認識

今回はちょっと難しい(自分でも仕組みを全部理解できていません)サンプルです。
内容はSUBARUのEyeSightみたいに人物(EyeSightでは人物以外の物体も認識していますが)を認識するプログラムです。
もともとはFaceBookで知り合いが人認識できないかなという話題から始まって、たしかOpenCVにサンプルがあったことを思い出したので、それを参考に作ってみました。(最終的に参考にしたのはOpenCVSharpのサンプルプログラムですけど)

ではサンプルです。

まず、参照設定ですが、以前のOpenCVサンプルで追加した参照以外に今回は「OpenCvsharp.CPlusPlus」を追加してください。あと「OpenCvSharpExtern.dll」をプロジェクトにコピーして、コピーしたファイルプロパティ内の「出力ディレクトリにコピー」を「常にコピーする」に変更してください。


MainWindow.xaml
<Window x:Class="Kinect_HoG.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="519" Width="662">
    <Grid>
        <Image Height="480"  Name="image1" Stretch="Uniform"  Width="640" />
        <Canvas Height="480" HorizontalAlignment="Left" Name="canvas1" VerticalAlignment="Top" Width="640" />
    </Grid>
</Window>

イメージコントロールとキャンバスコントロールを重ねて配置しています。


MainWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

using System.Drawing;
using Microsoft.Kinect;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using OpenCvSharp.CPlusPlus;
using CPP = OpenCvSharp.CPlusPlus;

namespace Kinect_HoG
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private KinectSensor kinect;
        private byte[] colorPixel;
        private IplImage CvImg;
        private WriteableBitmap srcImg;
        private Int32Rect bitmapRect;

        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();

            //カラー画像用
            srcImg=new WriteableBitmap(kinect.ColorStream.FrameWidth,kinect.ColorStream.FrameHeight,96,96,PixelFormats.Rgb24,null);
            bitmapRect =new Int32Rect(0,0,kinect.ColorStream.FrameWidth,kinect.ColorStream.FrameHeight);

        }

        void kinect_ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
        {
            using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
            {
                if (colorFrame != null)
                {
                    colorPixel = new byte[colorFrame.PixelDataLength];
                    colorFrame.CopyPixelDataTo(colorPixel);

                    //カラーイメージ表示
                    srcImg.WritePixels(bitmapRect, CvtBGR32toRGB24(colorPixel),srcImg.BackBufferStride, 0);
                    image1.Source = srcImg;

                    //10フレームごとにHoG処理
                    if (colorFrame.FrameNumber % 10 == 0)
                    {
                        Hog(srcImg);
                    }
                }
            }
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="srcImg"></param>
        private void Hog(WriteableBitmap srcImg)
        {
            //画像をMat配列に読み込み
            CPP.Mat CvImg = new CPP.Mat(srcImg.ToIplImage());
          
            //HoG検出器の準備
            CPP.HOGDescriptor hog = new CPP.HOGDescriptor();
            //人物検出用分類器の準備
            hog.SetSVMDetector(CPP.HOGDescriptor.GetDefaultPeopleDetector());
            //人物検出の実行(パラメーターは元のサンプルのまま)
            CvRect[] found = hog.DetectMultiScale(CvImg, 0, new CvSize(8, 8), new CvSize(24, 16), 1.05, 2);

            //人物を認識したときはキャンバスに矩形を表示する
            //キャンバスのクリア
            canvas1.Children.Clear();

            //検出時のみ矩形を描画
            if (found.Length !=0)
            {
                foreach (CvRect rect in found)
                {
                    canvas1.Children.Add(new System.Windows.Shapes.Rectangle()
                    {
                        Margin = new Thickness(rect.X + (int)Math.Round(rect.Width * 0.1), rect.Y + (int)Math.Round(rect.Height * 0.1), 0, 0),
                        Stroke = new SolidColorBrush(Colors.Red),
                        StrokeThickness = 3,
                        Width = (int)Math.Round(rect.Width * 0.8),
                        Height = (int)Math.Round(rect.Height * 0.8)
                    });
                }
            }
        }

        /// <summary>
        /// BGR32からRGB24に変換
        /// </summary>
        /// <param name="pixels"></param>
        /// <returns></returns>
        private byte[] CvtBGR32toRGB24(byte[] pixels)
        {
            var bytes = new byte[pixels.Length / 4 * 3];
            for (int i = 0; i < pixels.Length / 4; i++)
            {
                bytes[i * 3] = pixels[i * 4+2];
                bytes[i * 3 + 1] = pixels[i * 4+1];
                bytes[i * 3 + 2] = pixels[i * 4];
            }

            return bytes;
        }
    }
}
カラーイメージを表示している所はRGBサンプルとほぼ同じです。
Hog関数でカラーイメージをOpenCVを利用して処理しています。
カラーイメージ内で人を認識できた結果は矩形座標となるので、それを元にキャンバスコントロールに矩形を描画しています。
Hog関数自体はWritableBitmap形式のデータを与えれば動作するのでちょっと変更すればKINECT以外のデータ(静止画とかWEBカメラの映像イメージ)も処理できます。

人認識のアルゴリズムについては理解できていないところが多いですが、赤字の部分ですべての処理ができるようです。認識精度などはパラメーターを変更すればあげられるかもしれません。

HoGを使った処理ですがやはり相当重いようです。使っているPCはCorei7-2600Kですが、動作中はすべてのコアの使用率が軒並み50%以上になりました。
30FPSで処理するとまともに画像が表示されません。
C++でプログラムを記述するほうがいいのかもしれませんね。(HoG処理自体C++のインターフェイス向けなので)



0 件のコメント:

コメントを投稿