Unity上で顔認識 ~Webカメラでモーションキャプチャーへの道~ Part2

どうも!洋梨🍐です!

前回の記事

[ Unity上で顔認識 ~Webカメラでモーションキャプチャーへの道~ Part1 ]

の続きになります!

マスク画像から輪郭を抽出

基本動作

UnityでのTextureのデーターは左下を0,0として保存されています。

そのため、マスクデーターも同じく左下を0,0として2次元配列として保存しています。

今回マスクデーターの配列はByte型を使用しました。bool型でも良かったのですがスタート位置をマークしたかった為、Byte型にしました。


※( Byte[ width , height ] px の引数としている部分 )

さて、ピクセルデーターから輪郭を抽出するには

このように周りのピクセルをたどっていくのが一般的なようなのでこの方法で行きたいと思います。

しかしこの処理は1フレーム事に行うため、この部分が重い処理だとフレームレート低下に繋がってしまいます。そのためアルゴリズムの最適化などが必要になります。

処理の流れ

左下の0,0からX軸方向にピクセルデータを取得していき、ヒットした点を起点として今回は反時計回りに探索していきます。

次に探索する方向が分かるように、図のように方向ごとに数字を割り振ります。

の数字を引数とした関数を作り、前回の探索した方向から次探し始めるべき方向を決めます。今回は大きい数字から少ない数字へと探索します

※たとえば4からの場合4321 9 … と探索します。

この関数では初めてピクセルにヒットし呼び出された時

4から探索を始めます。

なぜなら下から上へ・左から右へと探索してきたので、

5、6、7、8には存在しないためです。

このような法則を元に次の探索開始点を求める関数を別に用意します。

前の探索方向を元に次の開始方向を返す関数 GetNextWay( int )
private int GetNextWay(int bway){
 switch (bway)
 {
  case 1:
  case 2:
   return 3;
  case 3:
  case 4:
   return 5;
  case 5:
  case 6:
   return 7;
  case 7:
  case 8:
   return 1;
  case 9: // 開始時のみの方向
   return 4;
 }
 return 0; 
}

※また、前の数字が偶数だった場合5回、奇数だった場合6回で探索できない場合おかしい(エラー)という事も踏まえてこの段階で例外処理も入れておきます。

このように探索していき探索後、引数としてとったピクセルデーター配列の変数(byte[ , ])を(探索したピクセルを)1から2にしていきます。

そして探索開始点に戻ってきたら処理を終了します。

探索開始時呼び出す関数 StartScan( int , int ,ref byte[ , ] )
private List<Pixel_XY> StartScan(int x, int y, ref byte[,] px){
 List<Pixel_XY> list = new List<Pixel_XY>();
 int startX = x;
 int startY = y;
 int way = 9;
 do
 {
  way = Scan(way,ref x,ref y,ref px,ref list);
  if (way == 0) return null; // Exception
 }
 while ((startX != x) || (startY != y));
 return list;
}
探索するメイン関数(1) Scan(int , ref int ,ref int ,ref List<Pixel_XY>)
int Scan(int bWay, ref int x, ref int y, ref byte[,] px, ref List<Pixel_XY> ls){ // 戻り値 0 : NotFound 1-8 Way 
 int rCount;
 if (bWay % 2 == 0) rCount = 5;
 else rCount = 6;
 rCount++; // +1するのはひとつ前の点に戻る場合があるため

 if (bWay == 9) rCount = 4; // 初期は4回(1,2,3,4)のみ
 int way = GetNextWay(bWay);

 for (int i = 0; i < rCount; i++)
 {
  bool b = ScanPixel(ref x, ref y, ref px, ref way, ref ls);
  if (b) return way;
  WayCountDown(ref way);
 }
 return 0;
}
保存用クラス class Pixel_XY
public class Pixel_XY
{
    public int X { get; set; }
    public int Y { get; set; }
}

このままだとノイズの輪郭も判定してしまっているため小さい輪郭(輪郭のピクセル数がしきい値以下)は除外するなどの処理をします。

※これらの結果は Dictionary<int, List<Pixel_XY>> result として保存されます。
また、result.Countで輪郭の総数、result[ int ].Countで [int]つ目の輪郭のピクセル数が分かるようになっています。

このデーターを元に画面に囲ったボックスを表示すると

↓ このようになります ↓

かなりそれっぽくなってきたのでは無いでしょうか?

これで大体の顔を特定できたという事で、次は体の部分を特定できるようにしていきたいと思います!!

読んでくれてありがとうございます!

他にも様々な記事を書いていますので是非見ていってくださいね^^