Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

そのまえに

Slide 3

Slide 3 text

AR Fukuoka 100回目!

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

関連研究

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

RGB-Dセンサの活用 カラー画像の取得+奥行き(Depth)を計測

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Visual Studioを起動

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

動作確認 開始

Slide 17

Slide 17 text

動作確認 Close

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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が追加される ②自分で追加

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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) { } /*****以下省略*****/

Slide 53

Slide 53 text

アプリ終了時にKinectを止める ① をクリック ②FormClosing をダブルクリック

Slide 54

Slide 54 text

アプリ終了時に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(); }

Slide 55

Slide 55 text

確認 実行すると赤外線の 照射が始まる

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

動作確認

Slide 58

Slide 58 text

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画像取得

Slide 59

Slide 59 text

奥行きの上限を変えてみよう 【解説】  Depth画像を使うとKinectからの距離を 用いて処理すべき領域を絞ることができる。  通常はKinectSDKで取得したDepthに 対して処理するが、扱いが面倒なので、 KinectGrabberで処理した結果を使用 maxDepth=8 maxDepth=4 maxDepth=2

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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) { } /*****以下省略*****/

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

動作確認 Depth情報のみの二値化では手以外の情報も抽出されてしまう

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

色の抽出をしよう 【やりたいこと】  特定の色の領域を白、それ以外を黒で二値化 【問題点】  色情報は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

Slide 73

Slide 73 text

カラー/二値化画像用の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;

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

動作確認 今は真っ白くなる

Slide 76

Slide 76 text

演習:閾値を変更して実行 Scalar lower = new OpenCvSharp.Scalar(50, 0, 0); Scalar upper = new OpenCvSharp.Scalar(80, 255, 255); 緑の領域だけ白くなる

Slide 77

Slide 77 text

演習:閾値をもとに戻す Scalar lower = new OpenCvSharp.Scalar(0, 0, 0); Scalar upper = new OpenCvSharp.Scalar(180, 255, 255);

Slide 78

Slide 78 text

UIを使ってHの閾値を変更 ①ツールボックス ②GroupBox ③Form1をクリック

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

動作確認 動かす 二値化結果が連動

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

UIを使ってHの閾値を変更 動かす 二値化結果が連動

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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; }

Slide 107

Slide 107 text

動作確認 Sの値が大きい領域が残る Sの下限を変更 Sの値が大きい領域が消える Sの上限を変更 min max min max

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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; }

Slide 116

Slide 116 text

動作確認 Vの値が小さい領域が消える Vの下限を変更 Vの値が小さい領域が残る Vの下限を変更 min max min max

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

欠損修復とノイズ処理 depth HSV AND 【欠損修復とノイズ処理】 (1) Blurを用いて画像をぼかす(平滑化) (2) しきい値を用いた二値化処理 (1) (2) 欠損 ノイズ

Slide 122

Slide 122 text

平滑化と二値化 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(); }

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

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

Slide 125

Slide 125 text

手の輪郭取得の準備 //輪郭抽出 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; /*以下省略*/ }

Slide 126

Slide 126 text

手の輪郭取得の準備

Slide 127

Slide 127 text

手の輪郭を取得(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;

Slide 128

Slide 128 text

輪郭を描画 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;

Slide 129

Slide 129 text

輪郭の表示 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

Slide 130

Slide 130 text

動作確認

Slide 131

Slide 131 text

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

Slide 132

Slide 132 text

凸包近似 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) { /*省略*/ }

Slide 133

Slide 133 text

凸包近似 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(); /*以下省略*/ }

Slide 134

Slide 134 text

動作確認

Slide 135

Slide 135 text

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

Slide 136

Slide 136 text

クラスタリング クラスタリングというテクニックを用いて距離の近い点を同じグループとみなす 0 1 2 3 4 5

Slide 137

Slide 137 text

クラスタリング 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; }

Slide 138

Slide 138 text

クラスタリング 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; } }

Slide 139

Slide 139 text

動作確認

Slide 140

Slide 140 text

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

Slide 141

Slide 141 text

矩形の中心算出 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

Slide 142

Slide 142 text

動作確認

Slide 143

Slide 143 text

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

Slide 144

Slide 144 text

代表点の選択 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

Slide 145

Slide 145 text

動作確認

Slide 146

Slide 146 text

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

Slide 147

Slide 147 text

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

Slide 148

Slide 148 text

指を検出 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); } /*省略*/ }

Slide 149

Slide 149 text

指を検出 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

Slide 150

Slide 150 text

指とみなす条件 p1 tip p2 θ l1 l2 θ<70[deg] && l 1 +l 2 >60[pixel]

Slide 151

Slide 151 text

指を検出 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

Slide 152

Slide 152 text

指の検出 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++; } }

Slide 153

Slide 153 text

認識した指の本数を表示 ①ツールボックス ②Label ③Form1をクリック

Slide 154

Slide 154 text

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

Slide 155

Slide 155 text

認識した指の本数を表示 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(); /*省略*/ }

Slide 156

Slide 156 text

Complete!