未来のサービスを作る基礎技術

第4回「脳波」真剣に科学してみる―開発編

前回は、Androidアプリケーションを開発する際に必要な環境構築方法について説明しました。今回は、実際にアプリケーションを開発していきます。Android用のDeveloper Toolを利用して、脳波データを取得するサンプルとして、脳波データをグラフで表示するアプリケーションを開発します。

図1 アプリケーション画面
図1 アプリケーション画面

Developer Toolsをダウンロードする

NeroSky社からAndroid用のDeveloper Toolsが無料で提供されていますので、こちらからダウンロードします。

図2 NeuroSky社から提供されているDeveloper Toolsの画面
図2 NeuroSky社から提供されているDeveloper Toolsの画面

[Add to Cart]をクリックします。

注)Developer Toolsは無料で利用できますが、利用する前にメールアドレスや本人情報の入力が必要になります。

ダウンロードが完了したら、ファイルを解凍して中身を確認します。ファイルは、以下のような構成になっています。

ThinkGear.jarDeveloper Tool本体
api_docThinkGear.jarのAPIドキュメント(Javadoc)
android_development_guide.pdf利用ガイド
srcDeveloper Toolsのサンプルソース(HelloEEG)

Android Projectの構築を行う

前回構築したEclipseの環境を利用して、Android Projectを構築しましょう。こちらからProjectファイルをダウンロードして解凍します。解凍したディレクトリを見ると、以下のような構成になっています。

図3 解凍したファイルのディレクトリ構成
図3 解凍したファイルのディレクトリ構成

主なファイルとディレクトリは次のとおりです。

AndroidManifest.xmlアプリケーションを構築する際に必要な情報を定義しています
srcディレクトリJavaのプログラムが格納するディレクトリです
resディレクトリ画像ファイルや画面のレイアウト定義などのリソースファイルを管理するディレクトリです
libsディレクトリ外部のjarファイルを格納するディレクトリです。

Projectをインポートする

解凍したAndroidのプロジェクトファイルを、インポートして実際に中身を見ていきます。

図4 ⁠ファイル][インポート][Android]を選択
図4 [ファイル]>[インポート]>[Android]を選択

Androidのフォルダから、Existing Android Code Into Workspaceを選択して、⁠次へ]をクリックします。

図5 Existing Android Code Into Workspaceダイアログ
図5 Existing Android Code Into Workspaceダイアログ

Existing Android Code Into Workspaceダイアログが表示されるので、⁠参照]をクリック後、先ほど解凍したディレクトリを選択して[次へ]をクリックします。

図6 プロジェクトが追加
図6 プロジェクトが追加

プロジェクトエクスプローラーの画面に「mainActivity」が追加されたら、プロジェクトのインポートに成功しています。

ThinkGear.jarをインポートする

Neurosky社からダウンロードした、Developer Toolsを利用できる状態にするため、ThinkGear.jarをインポートします。先ほど、解凍したフォルダの中に、ThinkGear.jarというファイルがありますので、ドラック&ドップで[Main Activity][libs]の中に入れてみましょう。そうするとインポートされると思います。

図7 ThinkGear.jarをインポートする
図7 ThinkGear.jarをインポートする

AndroidManifest.xmlを確認してみる

AndroidでBluetoothを利用する場合、AndroidManifest.xmlにBluetoothを利用することを記述しなければなりません。AndroidManifest.xmlに以下の一文を追記することで、アプリケーション内でBluetoothが利用できるようになります。

図8 AndroidManifest.xmlを選択
図8 AndroidManifest.xmlを選択
<uses-permission android:name="android.permission.BLUETOOTH" />

Bluetoothを利用するため、AndroidManifest.xmlに追記しています。

レイアウトを構築する

レイアウトの構築をするには色々な手順がありますが、今回は「Graphical Layout」を利用して、レイアウト上に画像を貼り付けて作成していきます。

図9 ⁠res][activity_main.xml][Graphical Layout]を選択
図9 [res]>[activity_main.xml]>[Graphical Layout]を選択

レイアウトに利用している画像ファイルは、⁠res][drawable-ldpi]ディレクトリの中にあります。

プログラムの中身を見てみる

[src]ディレクトリの中身を確認すると、2つのJavaプログラムがあります。

図10 ⁠src]ディレクトリの構成
図10 [src]ディレクトリの構成

それぞれのプログラムは、以下の役割を持っています。

  • ThinkGear.java:ThinkGear.javaは、脳波デバイスとBluetoothを接続したり、ThinkGearのDeveloper Toolsを利用して脳波データを取得する機能です。
  • MainActivity.java:MainActivity.javaは、主に脳波デバイスの接続状況や脳波データを受け取ってグラフをダイナミックに表示する機能です。

実際のソースコードを確認しながら、もう少し詳しく見ていきます。

脳波デバイスとBluetoothの接続を行う

Bluetooth接続してイベントハンドラを登録します。

private void createTGDevice(){
    // B3Bandとのbluetooth接続
    bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if(bluetoothAdapter == null) {
        // Alert user that Bluetooth is not available
        Log.w("EEGReader", "Bluetooth not available");
    }else {
        /* create the TGDevice */
        tgDevice = new TGDevice(bluetoothAdapter, handler);
        if(tgDevice == null){
            Log.w("EEGReader", "BrainBand not available");
        }
    }
}

BluetoothAdapter.getDefaultAdapterメソッドで、BluetoothAdapterオブジェクトが返却されます。BluetoothAdapterがnullの場合は、デバイスがBluetoothをサポートしていない状態になります。Bluetoothとの接続に成功すると、TGDeviceのコンストラクタで、イベントハンドラの登録を行います。

脳波デバイスからデータを受け取る

Handlerクラスでは、脳波デバイスから受け取ったメッセージに対応した処理を呼び出します。

受け取ったメッセージを処理します。

private final Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case TGDevice.MSG_STATE_CHANGE:
                switch (msg.arg1) {
                case TGDevice.STATE_IDLE:
                break;
            case TGDevice.STATE_CONNECTING:
                Log.d("EEGReader", "ThinkGear Connecting...");
                tgData.status("connecting");
                if(myView != null) {
                     myView.b3status(tgData.status());
                }    
                break;
            case TGDevice.STATE_CONNECTED:
                Log.d("EEGReader", "ThinkGear Connected");
                tgData.status("connected");
                if(myView != null) {
                     myView.b3status(tgData.status());
                }
                tgDevice.start();
                break;
            case TGDevice.STATE_NOT_FOUND:
                Log.w("EEGReader", "ThinkGear Can't find");
                break;
            case TGDevice.STATE_NOT_PAIRED:
                Log.w("EEGReader", "ThinkGear not paired");
                break;
            case TGDevice.STATE_DISCONNECTED:
                Log.d("EEGReader", "ThinkGear Disconnected");
                tgData.status("disconnected");
                if(myView != null) {
                                     myView.b3status(tgData.status());
                }
                break;

        case TGDevice.MSG_POOR_SIGNAL:
            tgData.poor_signal(msg.arg1);
            if(myView != null) myView.sigQ(tgData.poor_signal());
            break;

        case TGDevice.MSG_HEART_RATE:
            break;

        case TGDevice.MSG_ATTENTION:
            tgData.attention(msg.arg1);
            if(myView != null){
                    myView.attention(tgData.attention());
            }
            break;

        case TGDevice.MSG_MEDITATION:
            tgData.meditation(msg.arg1);
            if(myView != null){
                 myView.meditation(tgData.meditation());
            }
            break;
        case TGDevice.MSG_BLINK:
            tgData.blink(msg.arg1);
            if(myView != null){
                    myView.blink(tgData.blink());
            }
            break;
        case TGDevice.MSG_LOW_BATTERY:
            Log.w("EEGReader", "ThinkGear Low battery!");
            break;

        case TGDevice.MSG_EEG_POWER:
            tgEegPower = (TGEegPower)msg.obj;
            tgData.eeg(tgEegPower);
            if(myView != null) {
                myView.eeg(tgData.eeg());
            }
            break;

        default:
            break;
        }
    }
};

取得できるメッセージの概要は、以下のとおりです。

TGDevice.STATE_CHANGEデバイスとの接続の状態(後述)
TGDevice.MSG_POOR_SIGNALデバイスとの通信状態(0~200)
TGDevice.MSG_HEART_RATE心拍数
TGDevice.MSG_ATTENTION集中度
TGDevice.MSG_MEDITATIONリラックス度
TGDevice.MSG_BLINK瞬き
TGDevice.MSG_LOW_BATTERY脳波デバイスのバッテリーが少ない状態
TGDevice.MSG_EEG_POWER脳波データのより詳細なデータ(後述)

TGDevice.MSG_POOR_SIGNALが0の場合は、ノイズがない一番いい状態です。心拍数、集中、リラックス、瞬きの情報を取得する際には、TGDevice.MSG_POOR_SIGNALが0の状態になっている必要があります。0でない場合は、ノイズが入っている状態ですので、脳波デバイスの装着状態を確認してください。

脳波デバイスとの接続状態を取得する

TGDevice.STATE_CHANGEでは、デバイスとのステータスを取得することができます。

TGDevice.STATE_CONNECTING脳波デバイスと接続した
TGDevice.STATE_CONNETED脳波デバイスと接続が完了した
TGDevice.STATE_NOTFOUND脳波デバイスの信号を取得できない
TGDevice.STATE_NOTPAIRED脳波デバイスとのペアリングに失敗
TGDevice.STATE_DISCONNECTED脳波デバイスとの通信が切断

より詳細な脳波データを取得する

TGDevice.MSG_EEG_POWERでは、より詳細な脳波データを取得することができます。取得できるデータと簡単な説明は以下のとおりです。

delta深い眠りの状態
thetaリラックスしている状態
lowAlphaとても集中している状態
highAlphalowAlphaほどではないが、集中している状態
lowBeta日常生活
highBetaやや緊張している状態
lowGammaやや興奮状態
highGammaイライラしている状態

これら詳細データは、0~16777215の数値で格納されています。詳しくは、Developer Toolsに含まれるAPIドキュメントを参照してください。

では次に、データをViewに表示させましょう。

Viewを構築する

脳波デバイスからデータを取得することができるようになったので、取得したデータをアプリケーションに表示するようにします。

脳波デバイスとの接続状況を表示します。

public void b3status(String st){
    if (st.equals("connected")){
        b3status.connected();
    }else if(st.equals("disconnected")){
        b3status.disconnected();
    }else if(st.equals("connecting")){
        b3status.connecting();
    }else{
        b3status.set(st);
    }
}

脳波デバイスとの接続状況は、Stringで持っています。接続状態によって表示文言を変更させたいので、ステータスによってViewの表示を変更するメソッドを呼び出しています。集中力とリラックスのグラフアニメーションを描画します。

public void meditation(int to){
    if(to 

Scaleアニメーションを利用して画像を拡大縮小します。

private class Ball{
    ImageView img = null;
    float now = 1.0f;

    public Ball(int rid){
        img = (ImageView)findViewById(rid);
        img.setAlpha(100);
    }
    public void scale(float to){
        ScaleAnimation scale = new ScaleAnimation(
                now, to, // x
                now, to, // y
                Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 0.5f
        );
        scale.setDuration(1000);
        scale.setFillAfter(true);
        
        img.setAlpha(255);
        img.startAnimation(scale);
        now = to;
    }
}

波などの詳細データをグラフアニメーションで描画します。

public void eeg(int[] val){
    for (int i = 0; i  20.0) ? 20.0f : eegVal;
}

Scaleアニメーションを利用して画像を拡大縮小します。

private class Bar{
    ImageView img = null;
    float now = 1.0f;
    public Bar(int rid){
        img = (ImageView)findViewById(rid);
        img.setAlpha(100);
    }
    public void scale(float to){
        ScaleAnimation scale = new ScaleAnimation(
                now,  to,
                1.0f, 1.0f
        );
        scale.setDuration(1000);
        scale.setFillAfter(true);
        
        img.setAlpha(255);
        img.startAnimation(scale);
        now = to;
    }
}

円グラフや棒グラフをアニメーション的に表示する部分に関しては、画像を拡大したり縮小したりすることができる、ScaleAnimationを利用しています。このクラスを利用することで簡単にアニメーションを表示することが可能になります。

デバックする際の注意点

AVD(Android Virtual Device)ではBluetoothを利用したデバッグができません。Bluetoothを認識させてデバッグを行うには、実機にアプリケーションをインストールする必要があります。アプリケーションを実機にインストールするには、Android端末を販売しているメーカから提供されているUSBドライバをダウンロードしてください。

全3回にわたり、ATLの取り組みの1つである脳波に関して紹介をさせていただきました。次回からは、また新しい取り組みを紹介しますので楽しみにしてください。

おすすめ記事

記事・ニュース一覧