Upgrade to Pro — share decks privately, control downloads, hide ads and more …

AR Fukuoka: OpenCVとRGB-Dセンサで始めるハンドトラッキング

AR Fukuoka: OpenCVとRGB-Dセンサで始めるハンドトラッキング

3/2に開催した勉強会資料
https://eventon.jp/16146/

357c9e58ecce2865f9eb748192e5143f?s=128

TakashiYoshinaga

March 02, 2019
Tweet

More Decks by TakashiYoshinaga

Other Decks in Technology

Transcript

  1. ARコンテンツ作成勉強会 OpenCVで学ぶハンドトラッキングの基礎 #AR_Fukuoka

  2. そのまえに

  3. AR Fukuoka 100回目!

  4. 本日作成するコンテンツ https://youtu.be/HX6mvVICaDk

  5. 関連研究

  6. 画像処理によるハンドトラッキング 【基本的な手順】

  7. 問題点  顔などの手以外の肌色領域があると誤認識の原因になる 例) 顔、背景の色  手軽に実装できる対処法は・・・ 方法1) 手の検出をする領域を事前に設定しておく 方法2)

    顔を事前に認識しておき事前に除外しておく
  8. RGB-Dセンサの活用 カラー画像の取得+奥行き(Depth)を計測

  9. RGB-Dセンサの活用 使う 使わない

  10. RGB-Dセンサの活用 使う 使わない

  11. ハンズオンの手順 Input Depth Color Binarization Depth Value Skin Color AND

    Detection
  12. こちらをダウンロード http://arfukuoka.lolipop.jp/kinect2019mar/sample.zip

  13. Visual Studioを起動

  14. プロジェクトを作成 (1/2) ①ファイル ②新規作成 ③プロジェクト

  15. プロジェクトを作成 (2/2) ①VisualC# ②Windows フォームアプリケーション ③OK

  16. 動作確認 開始

  17. 動作確認 Close

  18. 自動スケール設定 ②プロパティ ③AutoScaleMode ④None ①クリック

  19. 画像表示領域を作成 (1/11) ①ツールボックス ②PictureBox ③フォーム内をクリック

  20. 画像表示領域を作成 (2/11) ①PictureBoxを選択 ②Sizeを640,480

  21. 画像表示領域を作成 (3/11) SizeModeをZoomに変更

  22. 画像表示領域を作成 (4/11) Formを選択してサイズを広げる

  23. 画像表示領域を作成 (5/11) PictureBoxを選択して[Ctrl+C]→[Ctrl+V]

  24. 画像表示領域を作成 (6/11) ①2つ目のPictureBoxを選択 ②Sizeを200,150

  25. 画像表示領域を作成 (7/11) 左下に移動

  26. 画像表示領域を作成 (8/11) [Ctrl+C]→[Ctrl+V]

  27. 画像表示領域を作成 (9/11) 下の中央に配置

  28. 画像表示領域を作成 (10/11) [Ctrl+C]→[Ctrl+V]

  29. 画像表示領域を作成 (11/11) 右下に配置

  30. 各PictureBoxの役割 ← 最終的な結果 ← 各種二値化の結果

  31. OpenCvSharpの導入 (1/4) ①ツール ②NuGet パッケージマネージャー ③ソリューションのNuGetパッケージの管理

  32. OpenCvSharpの導入 (2/4) ①参照 ②nuget.org

  33. OpenCvSharpの導入 (3/4) ①OpenCVsharp ②OpenCvSharp3 AnyCPU ③チェックをON ④インストール

  34. OpenCvSharpの導入 (4/4) NuGetを閉じる

  35. KinectSDKの導入 (1/3) ①参照を右クリック ②参照の追加

  36. KinectSDKの導入 (2/3) Kinectで検索

  37. KinectSDKの導入 (3/3) ①MicrosoftKinect 1.8.0.0をチェック ②OK

  38. 今回はカラー画像、Depth画像を活用して演習を行う Kinect SDKのデータは少々扱いづらいので、こちらで変換プログラムを提供

  39. KinectGrabberの導入 (1/4) KinectGrabber.csをForm1.csと 同じディレクトリにドラッグ&ドロップ sample folder Your project

  40. KinectGrabberの導入 (2/4) プロジェクトを右クリック

  41. KinectGrabberの導入 (3/4) ①追加 ②既存の項目

  42. KinectGrabberの導入 (4/4) ①KinectGrabber.cs ②追加

  43. Timerを用いた更新周期の設定 Timerを使うと任意に設定した間隔で処理(画像の取得・表示)を実行させられる ①ツールボックス ②Timer

  44. Timerを用いた更新周期の設定 ①Form上をクリック ②Timerが追加される ➂Intervalを30[ms] に変更(なんでもOK)

  45. 一定時間毎に呼び出される関数を作成 ①timer1をダブルクリック

  46. 一定時間毎に呼び出される関数を作成 timer1_Tick関数が追加される 一定時間おきにtimer1_Tick関数の内部に書いた処理が実行される(予定)

  47. Formが表示されたらTimerをスタートさせる ①Form1[デザイン] ②Form1を選択

  48. Formが表示されたらTimerをスタートさせる ① をクリック ②Shownをダブルクリック

  49. Formが表示されたらTimerをスタートさせる namespace ARFukuokaCV { public partial class Form1 : Form

    { public Form1() { InitializeComponent(); } private void timer1_Tick(object sender, EventArgs e) { } private void Form1_Shown(object sender, EventArgs e) { timer1.Start(); } } } ①Form1_Shownが追加される ②自分で追加
  50. ハンズオンの手順 Input Depth Color Binarization Depth Value Skin Color AND

    Detection
  51. ハンズオンの手順 Binarization Depth Value Skin Color AND Detection Input Depth

    Color
  52. Kinectと接続 using KinectSample; //KinectGrabberの機能をインポート namespace Kinect_ARFukuoka { public partial class

    Form1 : Form { KinectGrabber kinect = new KinectGrabber(); public Form1() { InitializeComponent(); } private void timer1_Tick(object sender, EventArgs e) { } /*****以下省略*****/
  53. アプリ終了時にKinectを止める ① をクリック ②FormClosing をダブルクリック

  54. アプリ終了時にKinectを止める KinectGrabber kinect = new KinectGrabber(); public Form1() { InitializeComponent();

    } private void timer1_Tick(object sender, EventArgs e) { } private void Form1_Shown(object sender, EventArgs e) { timer1.Start(); } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { timer1.Stop(); kinect.Close(); }
  55. 確認 実行すると赤外線の 照射が始まる

  56. Kinectの画像を表示 KinectGrabber kinect = new KinectGrabber(); public Form1() { InitializeComponent();

    } private void timer1_Tick(object sender, EventArgs e) { Bitmap color = kinect.GetColorImage(); //カラー画像取得 Bitmap depth = kinect.GetDepthImage(); //Depth画像取得 pictureBox1.Image = color; //カラー画像を表示 pictureBox2.Image = depth; //Depth画像を表示 } private void Form1_Shown(object sender, EventArgs e) { timer1.Start(); }
  57. 動作確認

  58. Depth画像で使用する距離の範囲を指定 KinectGrabber kinect = new KinectGrabber(); public Form1() { InitializeComponent();

    } private void timer1_Tick(object sender, EventArgs e) { float maxDepth = 8; Bitmap color = kinect.GetColorImage(); //カラー画像取得 Bitmap depth = kinect.GetDepthImage(); //Depth画像取得 pictureBox1.Image = color; //カラー画像を表示 pictureBox2.Image = depth; //Depth画像を表示 } private void Form1_Shown(object sender, EventArgs e) { timer1.Start(); } Bitmap depth = kinect.GetDepthImage(maxDepth); //Depth画像取得
  59. 奥行きの上限を変えてみよう 【解説】  Depth画像を使うとKinectからの距離を 用いて処理すべき領域を絞ることができる。  通常はKinectSDKで取得したDepthに 対して処理するが、扱いが面倒なので、 KinectGrabberで処理した結果を使用 maxDepth=8

    maxDepth=4 maxDepth=2
  60. 奥行きの上限を変更 顔がほとんど写らなくなる深度(maxDepth)を各自で見つけてみましょう ex) maxDepth=1 ex) maxDepth=0.7f NG Good

  61. この後やりたいこと 処理すべき画素と無視すべき画素を明確に分ける

  62. ここからOpenCVによる画像処理

  63. OpenCVの読み込み using KinectSample; //KinectGrabberの機能をインポート using OpenCvSharp; using OpenCvSharp.Extensions; namespace Kinect_ARFukuoka

    { public partial class Form1 : Form { KinectGrabber kinect = new KinectGrabber(); public Form1() { InitializeComponent(); } private void timer1_Tick(object sender, EventArgs e) { } /*****以下省略*****/
  64. ハンズオンの手順 Binarization Depth Value Skin Color AND Detection Input Depth

    Color
  65. ハンズオンの手順 AND Detection Input Depth Color Binarization Depth Value Skin

    Color
  66. グレースケール画像を二値化してみよう 0 255 0 255 0~255の輝度値を、ある値(しきい値)以上か未満かで分離することを二値化と呼ぶ。 画像処理すべき領域か否かをはっきり分けることができるため非常に重要なテクニック。

  67. Bitmap画像をMatに変換 //Depth画像を格納する変数 Mat depthMat; public Form1() { InitializeComponent(); } private

    void timer1_Tick(object sender, EventArgs e) { float maxDepth = 0.75f; Bitmap color = kinect.GetColorImage(); Bitmap depth = kinect.GetDepthImage(maxDepth); //BitmapをOpenCV用の画像フォーマットMatに変換 depthMat = BitmapConverter.ToMat(depth); pictureBox1.Image = color; pictureBox2.Image = depth; //メモリを解放 depthMat.Release(); } pictureBox2.Image = depth; pictureBox2.Image = depthMat.ToBitmap();
  68. Depth画像に対する二値化処理 //二値化を行うための関数 private void Binarization() { //BGR→GrayScale depthMat = depthMat.CvtColor(ColorConversionCodes.BGR2GRAY);

    //しきい値=20で二値化 depthMat = depthMat.Threshold(20, 255, ThresholdTypes.Binary); } private void timer1_Tick(object sender, EventArgs e) { float maxDepth = 0.75f; Bitmap color = kinect.GetColorImage(); Bitmap depth = kinect.GetDepthImage(maxDepth); depthMat = BitmapConverter.ToMat(depth); //二値化処理を呼び出す Binarization(); pictureBox1.Image = color; pictureBox2.Image = depthMat.ToBitmap(); depthMat.Release(); }
  69. 動作確認 Depth情報のみの二値化では手以外の情報も抽出されてしまう

  70. ハンズオンの手順 AND Detection Input Depth Color Binarization Depth Value Skin

    Color
  71. ハンズオンの手順 AND Detection Input Depth Color Binarization Depth Value Skin

    Color
  72. 色の抽出をしよう 【やりたいこと】  特定の色の領域を白、それ以外を黒で二値化 【問題点】  色情報はRGBの組み合わせで表現されている  RGBでは色の境目を直線的に引くことが困難 【解決策】

     色空間としてHSVを使用する  H(Hue)は色相で0~360°で表現される ※OpenCVの場合0~180  S(Saturation)は彩度、0~100%で表現。 値が大きいほうがその色の鮮やかさが強い。 ※OpenCVの場合0~255  V(Value)は明度、 0~100%で表現。 値が大きいほど明るい ※OpenCVの場合0~255 RGB HSV
  73. カラー/二値化画像用のMatの準備 //カラー画像を格納する変数を追加 Mat depthMat; public Form1() { InitializeComponent(); } private

    void timer1_Tick(object sender, EventArgs e) { float maxDepth = 0.75f; Bitmap color = kinect.GetColorImage(); Bitmap depth = kinect.GetDepthImage(maxDepth); depthMat = BitmapConverter.ToMat(depth); bgrMat = BitmapConverter.ToMat(color); Binarization(); pictureBox1.Image = color; pictureBox2.Image = depthMat.ToBitmap(); depthMat.Release(); bgrMat.Release(); binMat.Release(); } Mat depthMat, bgrMat, binMat;
  74. HSV画像に対する二値化 //HSVの閾値の最小値・最大値 Scalar lower = new Scalar(0, 0, 0); Scalar

    upper = new Scalar(180, 255, 255); public Form1() { InitializeComponent(); } private void Binarization() { depthMat = depthMat.CvtColor(ColorConversionCodes.BGR2GRAY); depthMat = depthMat.Threshold(20, 255, ThresholdTypes.Binary); //HSVに変換 Mat hsv = bgrMat.CvtColor(ColorConversionCodes.BGR2HSV); //HSVに対する閾値処理 binMat = hsv.InRange(lower, upper); //二値化結果を可視化 pictureBox3.Image = binMat.ToBitmap(); hsv.Release(); } lower upper
  75. 動作確認 今は真っ白くなる

  76. 演習:閾値を変更して実行 Scalar lower = new OpenCvSharp.Scalar(50, 0, 0); Scalar upper

    = new OpenCvSharp.Scalar(80, 255, 255); 緑の領域だけ白くなる
  77. 演習:閾値をもとに戻す Scalar lower = new OpenCvSharp.Scalar(0, 0, 0); Scalar upper

    = new OpenCvSharp.Scalar(180, 255, 255);
  78. UIを使ってHの閾値を変更 ①ツールボックス ②GroupBox ③Form1をクリック

  79. UIを使ってHの閾値を変更 ①ウィンドウ右側に配置 ② をクリック

  80. UIを使ってHの閾値を変更 ①Sizeを450,190に変更 ②TextをHに変更

  81. UIを使ってHの閾値を変更 ①ツールボックス ②Label ③GloupBoxをクリック

  82. UIを使ってHの閾値を変更 ①Labelをグループボックス内の左上に配置 ②TextをMinに変更

  83. UIを使ってHの閾値を変更 ①ツールボックス ②TrackBar ③GloupBoxをクリック

  84. UIを使ってHの閾値を変更 ①GoupeBox内の右上に配置 ②NameをminHに変更

  85. UIを使ってHの閾値を変更 Maximum:180 Minimum: 0 Value: 0

  86. UIを使ってHの閾値を変更 ①TrackBarを選択 ② をクリック ②Scrollをダブルクリック

  87. UIを使ってHの閾値を変更 //minHを動かすと呼び出される private void minH_Scroll(object sender, EventArgs e) { //閾値の下限のHの値を変更

    lower.Val0 = minH.Value; } 開始
  88. 動作確認 動かす 二値化結果が連動

  89. UIを使ってHの閾値を変更 SHIFTを押しながらLabelとTrackBarを選択

  90. UIを使ってHの閾値を変更 [Ctrl+C]→[Ctrl+V]

  91. UIを使ってHの閾値を変更 ①Labelを選択 ③TextをMaxに変更 ② をクリック

  92. UIを使ってHの閾値を変更 TrackBarを選択 NameをmaxHに変更

  93. UIを使ってHの閾値を変更 TrackBarを選択 Valueを180に変更

  94. UIを使ってHの閾値を変更 ①2つめのTrackbarを選択 ② をクリック ③Scrollをダブルクリック

  95. UIを使ってHの閾値を変更 //maxHを動かすと呼び出される private void maxH_Scroll(object sender, EventArgs e) { //閾値の上限のHの値を変更

    upper.Val0 = maxH.Value; } 開始
  96. UIを使ってHの閾値を変更 動かす 二値化結果が連動

  97. 演習:肌色を見つけよう ノイズが残る

  98. UIを使ってSの閾値を変更 GroupBoxを [Ctrl+C]→[Ctrl+V]

  99. UIを使ってSの閾値を変更 ①groupBox2を配置 ②Textを S に変更 ② をクリック

  100. UIを使ってSの閾値を変更 ①Minの横のTrackBar ②NameをminSに変更

  101. UIを使ってSの閾値を変更 Maximumを255に変更

  102. UIを使ってSの閾値を変更 ①Maxの横のTrackBar ②NameをmaxS

  103. UIを使ってSの閾値を変更 ①Maxの横のTrackBar ②Maximumを255に変更 ③Valueを255に変更

  104. UIを使ってSの閾値を変更 ①Minの横のTrackBar ③Scrollをダブルクリック ② をクリック

  105. UIを使ってSの閾値を変更 ①Maxの横のTrackBar ③Scrollをダブルクリック ② をクリック

  106. UIを使ってSの閾値を変更 //minSを動かすと呼び出される private void minS_Scroll(object sender, EventArgs e) { //閾値の下限のSの値を変更

    lower.Val1 = minS.Value; } //maxSを動かすと呼び出される private void maxS_Scroll(object sender, EventArgs e) { //閾値の上限のSの値を変更 upper.Val1 = maxS.Value; }
  107. 動作確認 Sの値が大きい領域が残る Sの下限を変更 Sの値が大きい領域が消える Sの上限を変更 min max min max

  108. UIを使ってVの閾値を変更 2つめのGroupBoxを [Ctrl+C]→[Ctrl+V]

  109. UIを使ってVの閾値を変更 ①2つめのGroupBoxの下に配置

  110. UIを使ってVの閾値を変更 ②Textを V に変更 ① をクリック

  111. UIを使ってVの閾値を変更 ①Minの横のTrackBar ②NameをminVに変更

  112. UIを使ってVの閾値を変更 ②Scrollをダブルクリック ① をクリック

  113. UIを使ってVの閾値を変更 ①Maxの横のTrackBar ③NameをmaxVに変更 ① をクリック

  114. UIを使ってVの閾値を変更 ②Scrollをダブルクリック ① をクリック

  115. UIを使ってVの閾値を変更 //minVを動かすと呼び出される private void minV_Scroll(object sender, EventArgs e) { //閾値の下限のVの値を変更

    lower.Val2 = minV.Value; } //maxVを動かすと呼び出される private void maxV_Scroll(object sender, EventArgs e) { //閾値の上限のVの値を変更 upper.Val2 = maxV.Value; }
  116. 動作確認 Vの値が小さい領域が消える Vの下限を変更 Vの値が小さい領域が残る Vの下限を変更 min max min max

  117. 手を抽出してみよう 色情報のみの二値化では同じ色の手以外のエリアも抽出されてしまう

  118. ハンズオンの手順 AND Detection Input Depth Color Binarization Depth Value Skin

    Color
  119. ハンズオンの手順 Detection Input Depth Color Binarization Depth Value Skin Color

    AND
  120. 2種類の二値化画像に対するAND演算 private void Binarization() { //Depth画像を二値化 depthMat = depthMat.CvtColor(ColorConversionCodes.BGR2GRAY); //しきい値=20で二値化

    depthMat = depthMat.Threshold(20, 255, ThresholdTypes.Binary); //HSVに変換 Mat hsv = bgrMat.CvtColor(ColorConversionCodes.BGR2HSV); //HSVに対する閾値処理 binMat = hsv.InRange(lower, upper); //二値化結果を可視化 pictureBox3.Image = binMat.ToBitmap(); //HSVおよびDepthに対する二値化結果のANDをbinMatに代入 Cv2.BitwiseAnd(binMat, depthMat, binMat); //AND演算の結果を可視化 pictureBox4.Image = binMat.ToBitmap(); hsv.Release(); }
  121. 欠損修復とノイズ処理 depth HSV AND 【欠損修復とノイズ処理】 (1) Blurを用いて画像をぼかす(平滑化) (2) しきい値を用いた二値化処理 (1)

    (2) 欠損 ノイズ
  122. 平滑化と二値化 private void Binarization() { //Depth画像を二値化 depthMat = depthMat.CvtColor(ColorConversionCodes.BGR2GRAY); depthMat

    = depthMat.Threshold(20, 255, ThresholdTypes.Binary); //HSVに変換 Mat hsv = bgrMat.CvtColor(ColorConversionCodes.BGR2HSV); //HSVに対する閾値処理 binMat = hsv.InRange(lower, upper); //二値化結果を可視化 pictureBox3.Image = binMat.ToBitmap(); Cv2.BitwiseAnd(binMat, depthMat, binMat); //周囲9×9pixelの画素を用いて平滑化 binMat = binMat.Blur(new OpenCvSharp.Size(9, 9)); //二値化 binMat = binMat.Threshold(60, 256, ThresholdTypes.Binary); pictureBox4.Image = binMat.ToBitmap(); hsv.Release(); }
  123. ハンズオンの手順 Detection Input Depth Color Binarization Depth Value Skin Color

    AND
  124. ハンズオンの手順 Input Depth Color Binarization Depth Value Skin Color AND

    Detection Next
  125. 手の輪郭取得の準備 //輪郭抽出 private Point[] ContourExtraction() { Point[] contour = null;

    return contour; } private void timer1_Tick(object sender, EventArgs e) { float maxDepth = 0.75f; Bitmap color = kinect.GetColorImage(); Bitmap depth = kinect.GetDepthImage(maxDepth); depthMat = BitmapConverter.ToMat(depth); bgrMat = BitmapConverter.ToMat(color); Binarization(); //抽出した輪郭の点をcontourに格納 Point[] contour = ContourExtraction(); pictureBox1.Image = color; /*以下省略*/ }
  126. 手の輪郭取得の準備

  127. 手の輪郭を取得(ContorExtraction関数内) Point[] contour = null; Point[][] lines; HierarchyIndex[] h; //複数の輪郭抽出

    binMat.FindContours(out lines, out h, RetrievalModes.External, ContourApproximationModes.ApproxSimple); double maxArea = -1; for (int i = 0; i < lines.Length; i++) { double area = Cv2.ContourArea(lines[i]); //面積最大の輪郭を手と仮定する if (area > maxArea) { maxArea = area; contour = lines[i]; } } return contour;
  128. 輪郭を描画 private void DrawPolyLine(Point[] points, Scalar color) { /*内容は次のページにて*/ }

    private void timer1_Tick(object sender, EventArgs e) { /*省略*/ bgrMat = BitmapConverter.ToMat(color); Binarization(); Point[] contour = ContourExtraction(); if (contour != null) { DrawPolyLine( contour, Scalar.Green); } pictureBox1.Image = color; pictureBox1.Image = bgrMat.ToBitmap(); /*省略*/ } pictureBox1.Image = color;
  129. 輪郭の表示 private void DrawPolyLine(Point[] points, Scalar color) { Point p

    = points.Last(); for (int i = 0; i < points.Length; i++) { Cv2.Line(bgrMat, p, points[i], color, 2); p = points[i]; } } 0 1 2 3 4 points[0] 1 2 3 p p points[1] 2 3 4 0 1 2 p points[4] ・・・ i = 0 i = 1 i = 4
  130. 動作確認

  131. ハンズオンの手順 Input Depth Color Binarization Depth Value Skin Color AND

    Detection Next
  132. 凸包近似 private int[] Polygonization(Point[] contour) { //凸包近似。戻り値は使っている点のインデックス int[] hullIdx =

    Cv2.ConvexHullIndices(contour); //凸包近似。戻り値は実際の点の座標 Point[] hullPt = Cv2.ConvexHull(contour); DrawPolyLine(hullPt, Scalar.Blue); return hullIdx; } private void timer1_Tick(object sender, EventArgs e) { /*省略*/ }
  133. 凸包近似 private void timer1_Tick(object sender, EventArgs e) { float maxDepth

    = 0.75f; Bitmap color = kinect.GetColorImage(); Bitmap depth = kinect.GetDepthImage(maxDepth); depthMat = BitmapConverter.ToMat(depth); bgrMat = BitmapConverter.ToMat(color); Binarization(); Point[] contour = ContourExtraction(); if (contour != null) { DrawPolyLine( contour, Scalar.Green); int[] hull = Polygonization(contour); } pictureBox1.Image = bgrMat.ToBitmap(); pictureBox2.Image = depthMat.ToBitmap(); /*以下省略*/ }
  134. 動作確認

  135. 凸包の頂点を表示 private int[] Polygonization(Point[] contour) { int[] hullIdx = Cv2.ConvexHullIndices(contour);

    Point[] hullPt = Cv2.ConvexHull(contour); for(int i=0;i<hullIdx.Length;i++) { int index = hullIdx[i]; bgrMat.Circle(contour[index], 10, Scalar.Red, 2); } DrawPolyLine(bgr, hullPt, Scalar.Blue); return hullIdx; }  凸包の頂点付近に多数の 点が存在する。  指の先端の位置を求める際 には1点にまとめた方が楽
  136. クラスタリング クラスタリングというテクニックを用いて距離の近い点を同じグループとみなす 0 1 2 3 4 5

  137. クラスタリング private int[] Polygonization(Point[] contour) { int[] hullIdx = Cv2.ConvexHullIndices(contour);

    Point[] hullPt = Cv2.ConvexHull(contour); //凸包を形成する点のラベル番号を格納 int[] labels; //Predicateで設定した条件(後述)に従って分類 Cv2.Partition(hullPt, out labels, Predicate); for (int i = 0; i < hullIdx.Length; i++) { int index = hullIdx[i]; bgrMat.Circle(contour[index], 10, Scalar.Red, 2); //各点のラベル番号を表示 bgrMat.PutText(labels[i].ToString(), contour[index], HersheyFonts.HersheyDuplex, 1, Scalar.White); } DrawPolyLine(hullPt, Scalar.Blue); return hullIdx; }
  138. クラスタリング private int[] Polygonization(Point[] contour) { /*省略*/ Cv2.Partition(hullPt, out labels,

    Predicate); /*省略*/ } private bool Predicate(Point t1, Point t2) { //2点の距離が20pixelより小さければ同ラベル if (t1.DistanceTo(t2) < 20) { return true; } else { return false; } }
  139. 動作確認

  140. ラベルごとの代表点を取得 Center Point 【処理手順】 Step1: 手を内包する最小の矩形を求める Step2: 矩形の中心座標を算出 Step3: 中心点と凸包頂点の距離を算出

    Step4: 最も遠い点を各ラベルの代表点とする Min Area Rectangle
  141. 矩形の中心算出 private int[] Polygonization(Point[] contour) { int[] hullIdx = Cv2.ConvexHullIndices(contour);

    Point[] hullPt = Cv2.ConvexHull(contour); int[] labels = new int[hullIdx.Length]; Cv2.Partition(hullPt, out labels, Predicate); RotatedRect rect = Cv2.MinAreaRect(hullPt); bgrMat.Circle((int)rect.Center.X, (int)rect.Center.Y, 10, Scalar.Cyan, 3); for (int i=0;i<hullIdx.Length;i++) { int index = hullIdx[i]; bgrMat.Circle(contour[index], 10, Scalar.Red, 2); bgrMat.PutText(labels[i].ToString(), contour[index], HersheyFonts.HersheyDuplex, 1, Scalar.White); } DrawPolyLine(bgr, hullPt, Scalar.Blue); return hullIdx; }
  142. 動作確認

  143. ラベルごとの代表点を取得 Center Point 【処理手順】 Step1: 手を内包する最小の矩形を求める Step2: 矩形の中心座標を算出 Step3: 中心点と凸包頂点の距離を算出

    Step4: 最も遠い点を各ラベルの代表点とする Min Area Rectangle
  144. 代表点の選択 private int[] Polygonization(Point[] contour) { /*コード省略*/ RotatedRect rect =

    Cv2.MinAreaRect(hullPt); bgrMat.Circle((int)rect.Center.X, (int)rect.Center.Y, 10, Scalar.Cyan, 3); hullIdx = HullSimplification(contour, hullIdx, labels, rect.Center); for (int i=0;i<hullIdx.Length;i++) { int index = hullIdx[i]; bgrMat.Circle(contour[index], 10, Scalar.Red, 2); } /*コード省略*/ } copy_and_paste.textの内容をこの辺りに張り付ける
  145. 動作確認

  146. ハンズオンの手順 Input Depth Color Binarization Depth Value Skin Color AND

    Detection
  147. ハンズオンの手順 Input Depth Color Binarization Depth Value Skin Color AND

    Detection
  148. 指を検出 private int FingerDetection(Point[] contour, int[] hull) { int num

    = 0;//指の本数 return num; } private void timer1_Tick(object sender, EventArgs e) { int fingerNum = 0; /*省略*/ Binarization(); Point[] contour = ContourExtraction(); if (contour != null) { DrawPolyLine( contour, Scalar.Green); int[] hull = Polygonization(contour); fingerNum = FingerDetection(contour, hull); } /*省略*/ }
  149. 指を検出 private int FingerDetection(Point[] contour, int[] hull) { int num

    = 0; //最も凹んでいる点を計算。引数は全輪郭点と凸包の頂点 Vec4i[] defects = Cv2.ConvexityDefects(contour, hull); //Item2の座標を可視化 foreach (Vec4i v in defects) { bgrMat.Circle(contour[v.Item2], 5, Scalar.White, 2); } return num; } 【解説】 ConvexityDefetsは凸包の欠陥性、 つまり内側への凹みを計算。 戻り値のVec4iは下記の順にデータを格納。 Item0: 始点のインデックス Item1: 終点のインデックス Item2: 最も遠い点のインデックス Item3: 最も遠い点までの距離 Item0 Item2 Item1
  150. 指とみなす条件 p1 tip p2 θ l1 l2 θ<70[deg] && l

    1 +l 2 >60[pixel]
  151. 指を検出 private int FingerDetection(Point[] contour, int[] hull) { Vec4i[] defects

    = Cv2.ConvexityDefects(contour, hull); foreach (Vec4i v in defects) { bgr.Circle(contour[v.Item2], 5, Scalar.White, 2); } //指かどうかを判定 for (int i = 0; i < defects.Length; i++) { Vec4i d1 = defects[i]; Vec4i d2 = defects[(i + 1) % defects.Length]; //指先と指間腔の点を取得 Point p1 = contour[d1.Item2]; Point p2 = contour[d2.Item2]; Point tip = contour[d1.Item1]; //指先から指間腔へのベクトルを計算 p1 = p1 - tip; p2 = p2 - tip; } } p1 tip p2
  152. 指の検出 for (int i = 0; i < defects.Length; i++)

    { Vec4i d1 = defects[i]; Vec4i d2 = defects[(i + 1) % defects.Length]; Point p1 = contour[d1.Item2]; Point p2 = contour[d2.Item2]; Point tip = contour[d1.Item1]; p1 = p1 - tip; p2 = p2 - tip; //p1,p2の長さを計算 double l1 = Math.Sqrt(p1.X * p1.X + p1.Y * p1.Y); double l2 = Math.Sqrt(p2.X * p2.X + p2.Y * p2.Y); //内積の計算と角度の計算 double dot = Point.DotProduct(p1, p2) / (l1 * l2); double angle = Math.Acos(dot) * 180.0 / Math.PI; if (angle < 70 && (l1+l2) > 60) { bgrMat.Circle(tip, 5, Scalar.Orange, 3); num++; } }
  153. 認識した指の本数を表示 ①ツールボックス ②Label ③Form1をクリック

  154. 認識した指の本数を表示 ①PictureBoxの左上に配置 ②FontのSizeを20に変更

  155. 認識した指の本数を表示 private void timer1_Tick(object sender, EventArgs e) { float maxDepth

    = 0.75f; Bitmap color = kinect.GetColorImage(); Bitmap depth = kinect.GetDepthImage(maxDepth); depthMat = BitmapConverter.ToMat(depth); bgrMat = BitmapConverter.ToMat(color); Binarization(); Point[] contour = ContourExtraction(); int fingerNum = 0; if (contour != null) { DrawPolyLine( contour, Scalar.Green); int[] hull = Polygonization(contour); fingerNum = FingerDetection(contour, hull); } label7.Text = fingerNum.ToString(); pictureBox1.Image = bgrMat.ToBitmap(); pictureBox2.Image = depthMat.ToBitmap(); /*省略*/ }
  156. Complete!