Google OS実験室 ~Moonlight 明日香~

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

脈拍センシングにチャレンジ(1)

最近, ヘルスケア関連の業務をするようになり, 血圧, 脈拍や心電図などの測定に少し興味を持つようになってきた.
いろいろと調査している中で, 例えば「
Instant Heart Rate」のように, AndroidやiPhoneのカメラを使って心拍数を測定するようなアプリもいくつか出てきている.

そこで, スマフォのカメラを使ってどのように心拍(脈拍)数を測定しているか調べてみた.


1. 脈拍計測の原理[1]
 脈拍計測には, 血液中のヘモグロビンが光を吸収するという性質を利用しているようで, 血管が収縮しているときはヘモグロビンが少なく受光素子への入射光が多くなり, 血管が拡張しているときはヘモグロビンが多く受光素子への入射光が少なくなる.
 この入射光の変化から血中のヘモグロビン量の変化を測定することができ, 脈拍数を求めることができる.

sensor01
                出典:EPSONのHP[1]

 スマフォアプリも同じような原理で測定しており, LEDの光とカメラを使って輝度の変化から脈拍数を測定しているようである.

2. 脈波の測定
Androidのカメラを使って, 指を撮影すると, 以下のように見える.



映像を見ると, 鼓動に合わせて多少明るさが変化しているのがわかる.
そこで, プレビュー画像の中央付近の輝度を測定するプログラムを作成し, 輝度の変化を観測してみた.

sensor01

一応脈波のような波形が取得できた.

[コード]
package com.moonlight_aska.android.preview01;

import java.io.IOException;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.PorterDuff.Mode;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.os.Build;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class CameraView extends SurfaceView 
    implements SurfaceHolder.Callback, Camera.PreviewCallback {
  private static final int PREVIEW_WIDTH = 640;
  private static final int PREVIEW_HEIGHT = 480;
  private static final int FRAME_WIDTH = 50;
  private static final int SCALE = 20;
  private static final int VIEW_POINTS = 200;
  private static final float PEN_WIDTH = 3.0F;
  private int mBufSize;
  private float[] mVal;
  private int mSamples;
  private int mTop;
  private int mStep;
  private Camera mCamera = null;
  private SurfaceHolder mHolder = null;
  private SurfaceTexture mSurfaceTexture = null;
  private Paint mLinePaint = new Paint();
 
  public CameraView(Context context) {
    super(context);
        
    mLinePaint.setStyle(Style.STROKE);
    mLinePaint.setColor(Color.GREEN);
    mLinePaint.setStrokeWidth(PEN_WIDTH);
    mHolder = getHolder();
    mHolder.addCallback(this);
  }
   
  public void surfaceCreated(SurfaceHolder holder) {
    // TODO Auto-generated method stub
    mBufSize = getWidth();
    mVal = new int[mBufSize];
    mStep = (mBufSize - FRAME_WIDTH*2) / VIEW_POINTS;
       
    // カメラオープン
    mCamera = Camera.open();
    try {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        mSurfaceTexture = new SurfaceTexture(0);
        mCamera.setPreviewTexture(mSurfaceTexture);
      }
      else {
        mCamera.setPreviewDisplay(null);
      }
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
    
  public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    // TODO Auto-generated method stub
    stopPreview();
    // プレビュー画面のサイズ設定
    Camera.Parameters params = mCamera.getParameters();
    params.setPreviewSize(PREVIEW_WIDTH, PREVIEW_HEIGHT);
    params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
    mCamera.setParameters(params);
    // プレビュー開始
    mTop = 0;
    mSamples = 0;
    startPreview();
  }
    
  public void surfaceDestroyed(SurfaceHolder holder) {
    // TODO Auto-generated method stub
    stopPreview();
    mCamera.release();
    mCamera = null;
  }
   
  @Override
  public void onPreviewFrame(byte[] data, Camera camera) {
    // TODO Auto-generated method stub
    mVal[mSamples%mBufSize] = calcLuminance(data);
    mSamples++;
    drawLuminance();
  }
  
  // プレビュー開始
  private void startPreview(){
    mCamera.setPreviewCallback(this);
    mCamera.startPreview();
  }

   
  // プレビュー停止
  private void stopPreview(){
    mCamera.setPreviewCallback(null);
    mCamera.stopPreview();
  }
    
  // 輝度値の計算
  private float calcLuminance(byte[] data) {
    float sumVal = 0.0f;
    int cnt = 0;

    for (int y=PREVIEW_HEIGHT/4; y<PREVIEW_HEIGHT*3/4; y++) {
      for (int x=PREVIEW_WIDTH/4; x<PREVIEW_WIDTH*3/4; x++) {
        sumVal += (float)(data[y*PREVIEW_WIDTH+x] & 0xff);
        cnt++;
      }
    }
    sumVal /= cnt;
    return sumVal;
  }
    
  // 輝度グラフの描画
  private void drawLuminance() {
    int idx, idx_1;
  
    Canvas canvas = mHolder.lockCanvas();
    if (canvas != null) {
      canvas.drawColor(0, Mode.CLEAR);
      if (mSamples >= VIEW_POINTS) {
        mTop++;
      }
      for (int i=mTop, x=0; i<mSamples-1; i++, x++) {
        idx = i % mBufSize;
        idx_1 = (i+1) % mBufSize;
        canvas.drawLine(x*mStep+FRAME_WIDTH, (mVal[idx]-80.0)*SCALE, (x+1)*mStep+FRAME_WIDTH, (mVal[idx_1]-80.0)*SCALE, mLinePaint);
      }
      mHolder.unlockCanvasAndPost(canvas);
    } 
  }
}

観測されたデータの周期性を調べることで, 脈拍数を求めることができる.
脈拍数の求め方については, 次回以降に.....

----
参照URL:
 [1]
腕で測る! 高精度脈拍計測技術 | 技術・イノベーション | EPSON

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

写真を撮るとき, 被写体の一瞬の表情を見逃さずに撮影するのは難しいものである.
そこで, HVC-Cを使ってシャッターチャンスを逃さないカメラアプリを作成してみた.

特長は, 普通の表情では撮影できない仕組みとなっており, シャッターを切るには喜怒哀楽のある表情をする必要があること.

1. システム構成
システム構成は以下のようになっている.

camera01

2. コード
表情推定機能を使い, 喜び, 驚き, 怒り, 悲しみの表情でスコアが閾値以上のときにカメラのシャッターを切るようにする.

2.1 src/MainActivity.java
SimpleDemoをベースとしていて, 認識結果の関連部分は以下の通り.
    @Override
    public void onPostExecute(int nRet, byte outStatus) {
     : (省略)
        if ( (hvcRes.executedFunc & HVC.HVC_ACTIV_EXPRESSION_ESTIMATION) != 0 ) {
            str += String.format("  [Expression Estimation] : expression = %s, score = %d, degree = %d\n", 
                faceResult.exp.expression == HVC.HVC_EX_NEUTRAL ? "Neutral" :
                faceResult.exp.expression == HVC.HVC_EX_HAPPINESS ? "Happiness" :
                faceResult.exp.expression == HVC.HVC_EX_SURPRISE ? "Supprise" :
                faceResult.exp.expression == HVC.HVC_EX_ANGER ? "Anger" :
                faceResult.exp.expression == HVC.HVC_EX_SADNESS ? "Sadness" : "" ,
                faceResult.exp.score, faceResult.exp.degree);
            if (faceResult.exp.expression == HVC.HVC_EX_ANGER ||
                faceResult.exp.expression == HVC.HVC_EX_HAPPINESS ||
                faceResult.exp.expression == HVC.HVC_EX_SURPRISE ||
                faceResult.exp.expression == HVC.HVC_EX_SADNESS) {
                if (faceResult.exp.score > SCORE_THRESHOLD) {  // score > 80 
                   mView.takePicture();
                }      
            }
        }

2.2 カメラ処理[1]
Androidプログラマへの道 ~Moonlight 明日香~」のカメラの部分を参照.

3. 動作例


撮影した写真
camera02

顔の表情に応じて, 写真をデコレーション(例:「怒り」なら頭の位置に鬼の角の画像を貼り付ける)する機能を実装したかったが, 明日(12/15)が締切なのでいったんこれで Sensing Egg Project 事務局に連絡しよう~と.

デコレーション機能やGUIをちゃんと実装したら, またこちらで紹介します.

----
参照URL:
[1] Androidプログラマへの道 ~Moonlight 明日香~

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

記事検索



  • ライブドアブログ