Google OS実験室 ~Moonlight 明日香~

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

プログラミング

FLIR ONEで遊んでみる(3)

今回は, FLIR ONEを使って, 写したものの温度を測定してみようと思う.

2. 温度測定[1][2][3]
ImageTypeに"ThermalRadiometricKelvinImage”を指定すると"Radiometric centikelvin(cK) tmperature data"が取得できる.
セルシウス温度tとそれに等しい絶対温度Tとの間には以下の関係があり, 温度(℃)を求めることができる.
 t/℃ = T/K - 273.15

(1) ImageTypeの設定
[コード]
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_preview);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        mOverlayView = (OverlayView)findViewById(R.id.overlay_view_id);
        mThermalView = (ImageView)findViewById(R.id.thermal_view_id);
        mThermalView.setRotation(180.0f);
        RenderedImage.ImageType defImageType = RenderedImage.ImageType.BlendedMSXRGBA8888Image;
        RenderedImage.ImageType kelvinlImageType = RenderedImage.ImageType.ThermalRadiometricKelvinImage;
        mFrmProcessor = new FrameProcessor(this, this, EnumSet.of(defImageType, kelvinlImageType));
    }

(2) 温度に変換
[コード]
    public void onFrameProcessed(RenderedImage renderedImage) {
        Log.i(LOG_TAG, "Frame processing!");

        if (renderedImage.imageType() == RenderedImage.ImageType.BlendedMSXRGBA8888Image) {
            mThermalBitmap = renderedImage.getBitmap();
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if (mThermalView != null) {
                        mThermalView.setImageBitmap(mThermalBitmap);
                    }
                }
            });
        }
        else if (renderedImage.imageType() == RenderedImage.ImageType.ThermalRadiometricKelvinImage) {
            calcTemperature(renderedImage);  // 画像中央の温度計算

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if (mOverlayView != null) {
                        mOverlayView.drawTemperature(mOverlayView.getWidth()/2, mOverlayView.getHeight()/2, mAveTemperature);
                    }
                }
            });
        }
    }

    // 画面中央の10x10エリアの平均温度
    private void calcTemperature(RenderedImage renderedImage) {
        final int AREA_SIZE = 10;
        int width = renderedImage.width();
        int x0 = (width - AREA_SIZE) / 2;
        int y0 = (renderedImage.height() - AREA_SIZE) / 2;

        double averageTemp = 0.0;
        short[] shortPixels = new short[renderedImage.pixelData().length / 2];
        ByteBuffer.wrap(renderedImage.pixelData()).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shortPixels);
        for (int y = y0; y < y0 + AREA_SIZE; y++) {
            for (int x = x0; x < x0 + AREA_SIZE; x++) {
                // 注) データはunsigned shortなのでintに変換して加算
                averageTemp += (int)(shortPixels[width*y + x] & 0xffff);
            }
        }
        averageTemp /= (AREA_SIZE * AREA_SIZE);
        mAveTemperature = averageTemp / 100 - 273.15;
    }


[実行結果]
 - Nexus 7 (2013) / Android 4.4.4

flir04

flir03

とりあえず, 画面中央の位置の物の表面温度を測定できるようになった.
ただ, この表示温度がどこまで正確な温度かは別の問題である.
正しく温度を測定するためには, 測定する物質に合わせて放射率(Emissivity)を正しく設定するなど, いろいろと条件設定が必要そうだ...


----
参照URL:
[1]
ケルビン - Wikipedia
[2] FLIR One SDK Documentation
[3] FLIR One Software Development Kit

FLIR ONEで遊んでみる(2)

FLIR Oneを使ったアプリを考える前に, FLIR Oneについてもう少し理解を深めてみよう.

まずは, FLIR Oneのドキュメント[1]やサンプルコード[2]をベースに, 基本的な機能(サーマル画像表示, 温度測 - 定など)を実装し, FLIR Oneの基本的な動きを確認してみる.

1. サーマル画像表示
基本的な処理の流れは以下の通り.
flir02
[手順]
1) onCreateメソッド
 - 画像タイプを指定して, FrameProcessorのインスタンスを取得する.
2) onResumeメソッド
 - Device#startDiscoveryメソッドで, FLIR Oneとの接続を開始する.
  注) 例外処理(try-catch)をすること.
3) onStopメソッド
 - Device#stopDiscoveryメソッドで, FLIR Oneからのリスニングを中止し接続を停止する.
  注) stopDescoveryメソッド内で, closeメソッドも実行される.
4) onDeviceConnectedメソッド
 - デバイス情報を保持する.
 - Device#startFrameStreamメソッドで,On ストリーミングを開始する.
5) onDeviceDisconnectedメソッド
 - チューニング状態をUnknownにする.
 - Device#stopFrameStreamメソッドで, ストローミングを停止する.
6) onFrameReceivedメソッド
 - チューニング状態がInProgressでない場合に, FrameProcessor#processFrameメソッドで, フレームデータを処理する.
7) onFrameProcessedメソッド
 - RenderImage#getBitmapメソッドで, ビットマップ画像を取得する.
 - ImageView#setImageBitmapメソッドで, ビットマップ画像をビューにセットし, 画像を表示させる.
  注) UI表示はrunOnUiThread内で行うこと.

[コード]
package com.moonlight_aska.android.vision.flironedemo;

import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ImageView;
import android.widget.Toast;

import com.flir.flironesdk.Device;
import com.flir.flironesdk.Frame;
import com.flir.flironesdk.FrameProcessor;
import com.flir.flironesdk.RenderedImage;

import java.util.EnumSet;

public class PreviewActivity extends AppCompatActivity
        implements Device.Delegate, Device.StreamDelegate, FrameProcessor.Delegate {
    private static final String LOG_TAG = PreviewActivity.class.getSimpleName();

    private ImageView mThermalView = null;  // 表示用View
    private volatile Device mDevice = null;  // Flir Oneデバイス
    private FrameProcessor mFrmProcessor = null;  // フレーム処理プロセッサ
    private Device.TuningState mTuningState = Device.TuningState.Unknown;    // チューニング状態
    private Bitmap mThermalBitmap = null;   // 描画用ビットマップ画像

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_preview);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        mThermalView = (ImageView)findViewById(R.id.thermal_view_id);
        mThermalView.setRotation(180.0f);  // 表示上下反転
        RenderedImage.ImageType defImageType = RenderedImage.ImageType.BlendedMSXRGBA8888Image;  // Thermal + Visual画像
        mFrmProcessor = new FrameProcessor(this, this, EnumSet.of(defImageType));
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_preview, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

   @Override
    protected void onResume() {
        super.onResume();
        try {
            Log.i(LOG_TAG, "onPesume, starting discovery!");
            Device.startDiscovery(this, this);
        } catch(IllegalStateException e) {
            Log.e(LOG_TAG, "onPesume, startDiscovery() error!");
            e.printStackTrace();
        }
    }

    @Override
    protected void onStop() {
        Log.i(LOG_TAG, "onStop, stopping discovery!");
        Device.stopDiscovery();

        super.onStop();
    }

    @Override
    public void onTuningStateChanged(Device.TuningState tuningState) {

    }

    @Override
    public void onAutomaticTuningChanged(boolean b) {

    }

    @Override
    public void onDeviceConnected(Device device) {
        Log.i(LOG_TAG, "Device connected!");

        mDevice = device;
        mDevice.startFrameStream(this);  // ストリーミング開始
        // Debug
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getApplicationContext(), "Device connected.", Toast.LENGTH_LONG).show();
            }
        });
    }

    @Override
    public void onDeviceDisconnected(Device device) {
        Log.i(LOG_TAG, "Device disconnected!");

        mTuningState = Device.TuningState.Unknown;
        if (mDevice != null) {
            mDevice.stopFrameStream();
            mDevice = null;
            // Debug
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(getApplicationContext(), "Device disconnected.", Toast.LENGTH_LONG).show();
                }
            });
        }
    }

    @Override
    public void onFrameReceived(Frame frame) {
        Log.i(LOG_TAG, "Frame received!");

        if (mFrmProcessor != null) {
            if (mTuningState != Device.TuningState.InProgress) {  // チューン中でない
                mFrmProcessor.processFrame(frame);
            }
        }
    }

    @Override
    public void onFrameProcessed(RenderedImage renderedImage) {
        Log.i(LOG_TAG, "Frame processing!");

        if (renderedImage.imageType() == RenderedImage.ImageType.BlendedMSXRGBA8888Image) {
            mThermalBitmap = renderedImage.getBitmap();
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if (mThermalView != null) {
                        mThermalView.setImageBitmap(mThermalBitmap); // 画像表示
                    }
                }
            });
        }
    }
}

[実行結果]
 - Nexus 7(2013) / Android 4.4.4

flir01

一応, サーマル画像を表示できるようになった.
次は, 温度表示を行ってみようと思う.

----
参照URL:
[1] FLIR One SDK Documentation
[2] FLIR One Software Development Kit

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 明日香~

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

もうすぐクリスマスだ~.
ということで, HVC-Cを使ってクリスマスツリーのLEDイルミネーションの制御にチャレンジしてみた.

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

注) LEDイルミネーションは以下のものをばらして使用した.


2. コード[1]
考え方は, 「OKAO Visionがやってきた!!(3)」と同じである.

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 ) {
          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_HAPPINESS) {
              cmd[0] = '4';
          }
          else if (faceResult.exp.expression == HVC.HVC_EX_SURPRISE) {
              cmd[0] = '3';                   
          }
          else if (faceResult.exp.expression == HVC.HVC_EX_NEUTRAL) {
              cmd[0] = '2';                   
          }
          else if (faceResult.exp.expression == HVC.HVC_EX_ANGER) {
              cmd[0] = '1'; 
          }
          else { // HVC.HVC_EX_SADNESS
              cmd[0] = '0';      
          }
          mUsb.write(cmd, 1);
      }
 
2.2 Arudino側
Androidからのコマンドにより, LEDの点滅時間を変えている.
char mCtrlPin = 13;

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

void loop() {
  // put your main code here, to run repeatedly:
  int high[] = { 100, 300, 1000, 300, 50};
  int low[] = { 2000, 1000, 1000,300, 50};
  int level = 2;    // default  
  while (true) {
    if (Serial.available() > 0) {
      level = Serial.parseInt();
      if (level < 0)  level = 0;
      else if (level > 4)  level = 4;
    }
    if (high[level] > 0) {
      digitalWrite(mCtrlPin, HIGH);
      delay(high[level]);
    }
    if (low[level] > 0) {
      digitalWrite(mCtrlPin, LOW);
      delay(low[level]);
    }
  }
  delay(10);
}

3. 動作例


とりあえず第一弾のプロトタイプということで, Sensing Egg Project 事務局に連絡だ~.

----
参照URL:
 [1] OKAO Visionがやってきた!!(3)

プロフィール

明日香

アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

記事検索



  • ライブドアブログ