OKAO Vision(HVC-C)について少し調べ, そして試してみた.

1. OKAO Visionの機能[1][2]
OKAO Visionの機能は以下の通り.

機能
出力
人体検出
顔検出
手検出
・検出個数(最大35個)
・検出中心座標(画像上のpixel)
・検出サイズ(pixelサイズ)
・信頼度(結果の確からしさ)
顔向き推定・左右角度(deg)
・上下角度
・傾き角度(回転角度)
・信頼度
視線推定・左右角度
・上下角度
目つむり推定・目つむり度合い
年齢推定・年齢
・信頼度
性別推定・性別
・信頼度
表情推定・5表情(真顔, 喜び, 驚き, 怒り, 悲しみ)判定結果
・スコア
・ポジティブ/ネガティブ度合


2. サンプルコードの改変
サンプルコードを少し改変して, 結果を図形表示するように変更してみた.
・顔 --- 楕円
・手 --- 三角
・体 --- 四角

[コード]

package com.moonlight_aska.android.okaovision.hand01;

import java.util.ArrayList;

import omron.HVC.HVC_RES.DetectionResult;
import omron.HVC.HVC_RES.FaceResult;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MyView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
 private final static int OFFSET_Y = 256;
 private final static int OFFSET_X = 1280*3/4;
 private Thread mThread = null;
 private ArrayList<DetectionResult> mBody = null;
 private ArrayList<DetectionResult> mHand = null;
 private ArrayList<FaceResult> mFace = null;
 private Paint mPaint = null;
 private boolean mDsp = false;
 
 public MyView(Context context, AttributeSet attrs) {
  super(context, attrs);
  // TODO Auto-generated constructor stub
  // SurfaceView描画のコールバック登録
  getHolder().addCallback(this);
 }

 @Override
 public void surfaceCreated(SurfaceHolder arg0) {
  // TODO Auto-generated method stub
  // 描画準備
  mPaint = new Paint();
  mPaint.setStrokeWidth(5);
  mPaint.setAntiAlias(true);
  mPaint.setStyle(Paint.Style.STROKE);
  // 描画スレッド準備
  mThread = new Thread(this);
 }

 @Override
 public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
  // TODO Auto-generated method stub
  if (mThread != null) {
   mThread.start();
  }  
 }
 
 @Override
 public void surfaceDestroyed(SurfaceHolder holder) {
  // TODO Auto-generated method stub
  mThread = null;
 }
 
 private void doDraw(SurfaceHolder holder) {
  boolean cls = false;
  Canvas canvas = holder.lockCanvas();
  if (mBody != null) {
   if (!cls) {
          canvas.drawColor(Color.BLACK);
          cls = true;
   }
   // Draw Body
   for (DetectionResult bodyResult : mBody) {
    //Log.v("Body", "x=" + bodyResult.posX + ", y=" + bodyResult.posY + ", size=" + bodyResult.size);
    mPaint.setColor(Color.GREEN);
    canvas.drawRect(OFFSET_X-(bodyResult.posX-bodyResult.size/2), bodyResult.posY-bodyResult.size/2+OFFSET_Y,
      OFFSET_X-(bodyResult.posX+bodyResult.size/2), bodyResult.posY+bodyResult.size/2+OFFSET_Y, mPaint);
   }
  }
  if (mHand != null) {
   if (!cls) {
          canvas.drawColor(Color.BLACK);
          cls = true;
   }
   // Draw Hand
   for (DetectionResult handResult : mHand) {
    //Log.v("Hand", "x=" + handResult.posX + ", y=" + handResult.posY + ", size=" + handResult.size);
    mPaint.setColor(Color.BLUE);
    Path path = new Path();
    path.moveTo(OFFSET_X-handResult.posX, handResult.posY+handResult.size/2+OFFSET_Y);
    path.lineTo(OFFSET_X-(handResult.posX-handResult.size/2), handResult.posY-handResult.size/2+OFFSET_Y);
    path.lineTo(OFFSET_X-(handResult.posX+handResult.size/2), handResult.posY-handResult.size/2+OFFSET_Y);
    path.lineTo(OFFSET_X-handResult.posX, handResult.posY+handResult.size/2+OFFSET_Y);
    canvas.drawPath(path, mPaint);
   }
  }
  if (mFace != null) {
   if (!cls) {
          canvas.drawColor(Color.BLACK);
          cls = true;
   }
   // Draw Face
   for (FaceResult faceResult : mFace) {
    //Log.v("Face", "x=" + faceResult.posX + ", y=" + faceResult.posY + ", size=" + faceResult.size);
    mPaint.setColor(Color.RED);
    RectF oval = new RectF(OFFSET_X-(faceResult.posX-faceResult.size/2), faceResult.posY-faceResult.size+OFFSET_Y,
      OFFSET_X-(faceResult.posX+faceResult.size/2), faceResult.posY+faceResult.size+OFFSET_Y);
    canvas.drawOval(oval, mPaint);
   }
  }
  holder.unlockCanvasAndPost(canvas);
 }

 @Override
 public void run() {
  // TODO Auto-generated method stub
  while (mThread != null) {
   if (mDsp) {
    doDraw(getHolder());
    mDsp = false;
   }
   try {
    Thread.sleep(20);
   } catch (Exception e){
    
   }
  }
 }

 public void setBody(ArrayList<DetectionResult> body) {
  Log.v("Body", "body = " + body.size());
  mBody = body;
  mDsp = true;
 }
 
 public void setHand(ArrayList<DetectionResult> hand) {
  Log.v("Hand", "hand = " + hand.size());
  mHand = hand;
  mDsp = true;
 }
 
 public void setFace(ArrayList<FaceResult> face) {
  Log.v("Face", "face = " + face.size()); 
  mFace = face;
  mDsp = true;
 }
}

[動作例]
Nexus7 (2013) / Android 4.4.4


okao01

少し試してみて, まず気になったのは以下の2点である.
1) 検出が安定しない. 
  例えば, ユニットの前に手をずっと出していても, 下記のように認識したり/しなかったりする.
 
  11-18 23:30:21.299: V/Hand(16283): hand = 1
  11-18 23:30:23.911: V/Hand(16283): hand = 1
  11-18 23:30:26.504: V/Hand(16283): hand = 1
  11-18 23:30:29.096: V/Hand(16283): hand = 1
  11-18 23:30:31.419: V/Hand(16283): hand = 0
  11-18 23:30:33.891: V/Hand(16283): hand = 1
  11-18 23:30:36.293: V/Hand(16283): hand = 0
  11-18 23:30:38.696: V/Hand(16283): hand = 1
  11-18 23:30:41.138: V/Hand(16283): hand = 0
  11-18 23:30:43.520: V/Hand(16283): hand = 0

2) 検出に時間がかかる.
  上記は, 顔/手/人体のみに限定して処理しているが, LogCatの出力からも分かるように1回の認識に2秒程度かかっている.

したがって, 上記のことを考慮してプロトタイプのアイデアを考える必要が出てきた.


追記:2014/11/20
上記2点についてオムロン様に問い合わせたところ, 以下の回答を得た.
1) HVC_PRMにあるDetectionParamのThresholdの値を変更すれば, 検出が変わる.
   値を下げて頂くと検出しやすくなり(誤検出もしやすくなる), 値を上げて頂くと検出しにくくなる.
⇒ Thresholdの値を500⇒100に変更して試してみたところ, 少し検出しやすくなった.

2) 顔のみや, 手のみ, 人体のみで実行いただくと処理時間は変わってくる.
⇒ 想定された回答でした.


----
参照URL:
[1] 製品仕様/開発情報 | Sensing Egg Project
[2] HVC-P-S OMRON