Google OS実験室 ~Moonlight 明日香~

GoogleのAndroidで遊び始めて, すでに6年以上が経った. Androidは思った以上の発展を遂げている. この技術を使って, 新しいことにチャレンジだ!!

プログラミング

OKAO Visionがやってきた!!(3)

オムロンのHVC-Cを活用して何作ろうか?
う~ん, HVC-Cの認識には結構制約もあるので, HVC-Cを活かしたアイデアがなかなかまとまらない.

今回はお試しで, HVC-C+Android+Arduino UNOを使って, 顔の表情でLEDをコントロールしてみることにした.

1. デモ仕様
1.1 構成
 ・HVC-CとNexus 7をBluetooth LEで接続.
 ・Nexus 7とArudino UNO R3(互換ボード)をUSBケーブルで接続
 ・Arudino UNOの各11, 12, 13ピンにそれぞれR(220Ω)及びLED(青/緑/赤)のプラスに接続
 ・各LEDのマイナスをArudino UNOのGNDに接続
1. 2 動作仕様
 ・Nexus 7でBluetoothデバイスの検出を行い, HVC-Cに接続する.
 ・HVC-Cに顔表情の検出を指示する.
 ・検出のコールバック関数の中で, 顔表情に応じてArudino UNOに対してシリアルでコマンドを送信する.
   Neutral : 緑LED点灯
   Happiness, Supprise : 赤LED点灯
   Anger, Sadness : 青LED点灯
 ・Arudino UNOは, コマンドに応じたLEDの点灯/消灯を行う.

2. コード
2.1 Android側
SimpleDemoをベースとしていて, 認識結果の関連部分は以下の通り.
     @Override
      public void onPostExecute(int nRet, byte outStatus) {
      byte[] cmd = new byte[2];
     : (省略)
      if ( (hvcRes.executedFunc & HVC.HVC_ACTIV_EXPRESSION_ESTIMATION) != 0 ) {
         if (faceResult.exp.expression == HVC.HVC_EX_HAPPINESS ||
               faceResult.exp.expression == HVC.HVC_EX_SURPRISE) {
           cmd[0] = 'r';
         }
         else if (faceResult.exp.expression == HVC.HVC_EX_ANGER ||
               faceResult.exp.expression == HVC.HVC_EX_SADNESS) {
           cmd[0] = 'b';            
         }
         else {
           cmd[0] = 'g';
         }
      }
      else {
         cmd[0] = 'c';                    
      }
      mUsb.write(cmd, 1);
     :

2.2 Arudino側
char mRedPin = 13;
char mGreenPin = 12;
char mBluePin = 11;

char mSerialRecv;

void setup() {
  // put your setup code here, to run once:
  pinMode(mRedPin, OUTPUT);
  pinMode(mGreenPin, OUTPUT);
  pinMode(mBluePin, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
  while (Serial.available() > 0) {
    mSerialRecv = Serial.read();
    switch (mSerialRecv) {
      case 'b':
      digitalWrite(mBluePin, HIGH);
      digitalWrite(mRedPin, LOW);
      digitalWrite(mGreenPin, LOW);
      break;
      case 'g':
      digitalWrite(mGreenPin, HIGH);
      digitalWrite(mRedPin, LOW);
      digitalWrite(mBluePin, LOW);
      break;
      case 'r':
      digitalWrite(mRedPin, HIGH);
      digitalWrite(mGreenPin, LOW);
      digitalWrite(mBluePin, LOW);
      break;
      case 'c':
      digitalWrite(mRedPin, LOW);
      digitalWrite(mGreenPin, LOW);
      digitalWrite(mBluePin, LOW);     
    }
    Serial.println(mSerialRecv);
  }
  delay(10);
}

3. 動作例
okao01
  出典:瀬間友里加オフィシャルブログ

12/15まであと2週間. 今週末までにはアイデアまとめるぞ~.

----

OKAO Visionがやってきた!!(2)

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

OKAO Visionがやってきた!!

オムロンの「Sensing Egg Project[1]で「Make your own prototype!」に応募したところ, OKAO Vision HVC-C(Human Vision Components - Consumer model)のベータ版が送られてきた.

HVC-Cは, オムロンが開発した人の状態を認識する独自の顔画像センシング技術「OKAO Vision」とカメラモジュールをコンパクトに一体化し、センシング結果をBluetoothでAndroidやiPhoneに送信してくれる小型ユニットである.


okao00

つまり, これを使うだけで, 以下の9種類の画像センシングが簡単に行える.
 ・ 人体検出
 ・ 顔検出
 ・ 手検出
 ・ 顔向き推定
 ・ 視線推定
 ・ 目つむり推定
 ・ 年齢推定
 ・ 性別推定
 ・ 表情推定

まずは, サンプルを動作させてみた.

テスト端末:
 Nexus 7 (2013) / Android 4.4.4

1. サンプルアプリを動作させる
 1) HVC-Cに電源を供給し, SDKに含まれるSimpleDemoを起動する.

okao01

 2) 「You can select a device.」というトーストが表示された後, 「Find」を選択しデバイスを検索する.

okao02

 3) デバイスを選択すると, 「Your selected device is XX」とトーストが表示される.
 4) 「Start」を選択すると, 人体, 顔等の認識がスタートする.
 5) 以下の画像をモニタに表示し, ユニットのカメラを画面に向けると認識結果が表示された.
 [入力]

photo
  出典:瀬間友里加オフィシャルブログ

 [結果]
okao03

モニタ終了(~'14/12/15)まであと一か月. 
何かプロトタイプアプリ作らなきゃ...

-----
参照URL:
[1]
Sensing Egg Project -人認識センサーHVC-Cを使ってスマートフォンアプリを開発しよう!


動きセンシングにチャレンジ(5)

今回は, 加速度センサーを使って歩数の測定にチャレンジしてみる.

1. アルゴリズム[1]
加速度センサーの値を使って歩数を計測するアルゴリズムを説明する.
hokou01
1) XYZ軸の合成加速度A(=√(Ax^2+Ay^2+Az^2))が閾値Highより小さく, かつ閾値Lowより大きい場合, 合成加速度Aの最小値を求める.
2) 合成加速度Aが閾値Highより大きくなったら, 状態を歩行中とし, 歩行中の合成加速度Aの最大値を求める.
3) 歩行中の合成加速度Aが減少して閾値Higyより小さくなり, 直前の最大値と最小値の差または最大値とその時点での合成加速度Aの差が閾値以上の場合1歩とカウントする.

2. コード
歩数の計測部分のコードを示す.

   float fSumG = (float)Math.sqrt(x * x + y * y + z * z);
   if (fSumG >= GRAVITY * 1.1) {  // 閾値High以上?
    if (fMaxG < fSumG) { // 最大値計算
     fMaxG = fSumG;
    }
    myview.stat[idx] = 1;
   }
   else if (fSumG <= GRAVITY * 0.9) {  // 閾値Low以下?
    myview.stat[idx] = -1;
   }
   else {
    float fDup = fMaxG - fMinG;
    float fDdown = fMaxG - fSumG;
    if ((fDup >= GRAVITY * 0.25) || (fDdown >= GRAVITY * 0.25)) {
     myview.count++;  // 歩数更新
     int idx_1 = (myview.samples - 1) % MyView.BUFFER_SIZE;
     myview.stat[idx_1] = 2;
     fMaxG = 0.0F;
     fMinG = 100.0F;
     myview.stat[idx] = 0;
    }
    else {
     myview.stat[idx] = 0;
     if (fMinG > fSumG) { // 最小値計算
      fMinG = fSumG;
     }
    }
   }

3. 測定結果
今回作成した歩数計測Appを使って, 歩数計測の実験を行った.
測定方法:
1) Android端末でAppを起動して, 端末の状態による違いについて測定する.
2) Android端末とiPod nanoでAppを起動して ズボンのポケットに入れ, 歩行形態の違いについて測定する.

結果:
1) Android端末の状態による違い調査
 i) 手に持つ
hosuu04
 ii) ズボンのポケットに入れる
hosuu03
ズボンのポケットに入れた方が合成加速度が細かく振れている.
ポケットの中で端末が移動可能であるため, 歩行中に端末自体が動き, その動きが加速度に反映されているものと思われる.

2) 歩行形態による違い調査
 i) 通常歩行(10歩)
hosuuNorm
 ii) 階段上り(14段)
hosuuUp
 iii) 階段下り(14段)
hosuuDown
Android/iPod nano共に実際の歩数よりも多く計測される場合が多かった.
これは, ズボンのポケットに端末を入れて測定したため, 歩行中の端末自体の動きにより歩数が余分にカウントされたものと思われる.

4. 最後に
当初, 合成加速度Aが1歩毎に閾値Lowより小さくなると予想していたが, 取得したデータを見ると閾値Lowより小さくならない場合も散見された.
hosuu02
そこで, 今回は閾値Lowを使わないで歩数計測してみた.
また, 歩数カウントを単純に合成加速度Aの振れ幅のみで行っているため, iPod nanoに比べちょっとした衝撃でも歩数としてカウントしてしまう.
もう少し歩数計測の精度を上げるには, この辺りの改良が必要と思われる.

----
参照URL:
 [1] 3軸加速度センサーアプリケーションノート - 北陸電気

livedoor プロフィール
アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

記事検索



  • ライブドアブログ