読者です 読者をやめる 読者になる 読者になる

メモ帳DPA

ぐぐってあまり引っかからないような何かがあったら書いたりする

The Eye Tribe 買ったので視線制御で手足を使わず本を読む

The Eye Tribe買った

これ

The Eye Tribe

これは何か

$99で買える視線追跡デバイスです。
目の動きを検出し入出力に使えるようになります。

これまでこの手のデバイスは専門的なくっそ高いものを使うか赤外線webcamを頭に固定して自作するかの二択だったんですが、
専用の安価な装置はこれが初なんじゃないかと思います。


先駆者としては以下が詳しい。

The Eye Tribe Trackerを使った視線の記録 - ならば
http://strangelet.hatenablog.jp/entry/2014/03/30/134912

気になったこと

置き場所に結構制約がある

It is important that the Tracker is centered relative to the monitor. The monitor must be max 24”. Note the Tracker should not be positioned above or next to the monitor as the tracking will not function optimally.

http://dev.theeyetribe.com/start/

画面は24インチまで、モニタの中央かつ下限定です。Kinectのように画面上に置くと認識しません。
自分と画面の間に三脚で立つ形になるので常設すると視界の端に入って若干気になります。

実際のところ39インチモニタでも使えましたが、画面端での精度が落ちます。

ちょっと重い

Core i7 3770で常時CPU20〜40%くらい使います。
今のところ別に困るレベルではないものの貧弱なPCだときついかも。

認識エリアが限られる

http://dev.theeyetribe.com/start/

図の位置に頭が入っている必要があります。
姿勢を崩したりするとすぐ外れるのでエリアを意識する必要があり若干面倒ではあります。
ベッドで寝ながら使いたかったんですがしょっちゅう外れるので無理でした。
(それでも完全固定が必要なITU Gaze Trackerに比べれば大幅に認識域が広いのは確かなのですが)

Win/Mac限定

iPadあたりの適当な非対応機器でも視線記録くらいには使えるんじゃないかと甘く見てましたが無理でした。

うまいこと調整できれば使えないこともなさそうですが、

  • VNCで誤魔化してキャリブレーションしてみたがあまり正確でない
  • 液晶下に置く必要があるという制約により操作時に手で領域を塞ぐ
  • 画面まで近いので認識エリアが狭い

のでなかなか厳しかったです。


実用編

何やるにしても自作しないとならない

ソフトは通信用のサーバとキャリブレーションツールしかないので全部自作する必要があります。
最初に文句ばかり書いてしまいましたがそれ以外さして不満もなく精度も結構高いです。

Basics | eyetribe-docs

仕様などはここにありますが、要は視線座標を得るくらいしかないので結構組み込みは簡単です。

割り切り大事

マウス操作はそこそこ思い通りに追従してはくれるものの細かいところの操作までは厳しいので実用的でありませんでした。
とりあえずはある程度割り切って、Leeyesで自炊本を読むときのページ送りに使うことにします。

画面左下を見たらページ送り(下キー)、画面右上を見たらページ戻り(上キー)を送るだけ。
サンプルにちょこっと手加えただけの超単純なものです。

普通に読み進めるだけで何もしなくてもページが切り替わってくのでなかなか快適です。

import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.KeyEvent;

import com.theeyetribe.client.GazeManager;
import com.theeyetribe.client.GazeManager.ApiVersion;
import com.theeyetribe.client.GazeManager.ClientMode;
import com.theeyetribe.client.IGazeListener;
import com.theeyetribe.client.data.GazeData;

public class eyeControl
{

	//角から何px範囲で操作するか
	final static int areasize  = 700;
	//最速何ms間隔で操作するか
	final static int sleeptime = 1000;
	//何回同エリアで操作するか
	final static int threshold = 5;

	//位置調整
	final static int prevOffsetX = -100;


	//解像度取得
	static java.awt.GraphicsEnvironment env = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
	static java.awt.DisplayMode displayMode = env.getDefaultScreenDevice().getDisplayMode();
	static int width = displayMode.getWidth();
	static int height = displayMode.getHeight();

	//右上
	static int prevx1 = width  - areasize +prevOffsetX;
	static int prevx2 = width  +prevOffsetX;
	static int prevy1 = 0;
	static int prevy2 = areasize;

	//左下
	static int nextx1 = 0;
	static int nextx2 = areasize;
	static int nexty1 = height - areasize;
	static int nexty2 = height;


	//操作ごとに登録
	static keySend prev = new keySend(prevx1,prevy1, prevx2,prevy2, KeyEvent.VK_UP);
	static keySend next = new keySend(nextx1,nexty1, nextx2,nexty2, KeyEvent.VK_DOWN);


	public static void main(String[] args)
	{
		System.out.println(nextx1 + "," + nextx2 + "/" + nexty1 + "," + nexty2);
		System.out.println(prevx1 + "," + prevx2 + "/" + prevy1 + "," + prevy2);

		final GazeManager gm = GazeManager.getInstance();
		boolean success = gm.activate(ApiVersion.VERSION_1_0, ClientMode.PUSH);

		final GazeListener gazeListener = new GazeListener();
		gm.addGazeListener(gazeListener);

		Runtime.getRuntime().addShutdownHook(new Thread()
		{
			@Override
			public void run()
			{
				gm.removeGazeListener(gazeListener);
			 gm.deactivate();
			}
		});
	}

	static class keySend{
		int fromX;
		int fromY;
		int toX;
		int toY;
		int keycode;

		int count;

		static Robot robot;
		static boolean waitflg;

		keySend(int X1, int Y1, int X2, int Y2,int key){
			fromX = X1; toX   = X2;
			fromY = Y1; toY   = Y2;
			keycode = key;

			count = 0;
			waitflg = false;

			try {
				robot = new Robot();
			} catch (AWTException e) { e.printStackTrace(); }
		}
		void send(int Xpos, int Ypos){
			if (waitflg){ return; }

			if( (fromX < Xpos) && (Xpos < toX) && (fromY < Ypos) && (Ypos < toY)  ){
				count++;
				if ( count < threshold ){ return; }

				waitflg=true;

				robot.keyPress(keycode);
				robot.keyRelease(keycode);

				System.out.println(keycode + " \t X:" + Xpos + "\t Y:"+ Ypos);

				try{
					Thread.sleep(sleeptime);
				}catch(InterruptedException e){}

				count=0;
				waitflg=false;
			}else{
				count=0;
			}
		}

	}

	private static class GazeListener implements IGazeListener
	{
		@Override
		public void onGazeUpdate(GazeData gazeData)
		{
			int Xpos = (int) gazeData.smoothedCoordinates.x;
			int Ypos = (int) gazeData.smoothedCoordinates.y;

			prev.send(Xpos,Ypos);
			next.send(Xpos,Ypos);

		}
	}
}

操作よりは記録向きだと思う

アイディア次第でいろいろ作れそうな気はしますが、視線操作を積極的に取り入れるべき場面はあまり無いように思います。
公式のPVでは、レシピ動画送り、楽譜送り、ページ送り、ゲームなどを紹介していましたが、実際のところゲーム以外フットペダルで済みます。
ゲームは何かしら視線制御に対応したものが出ない限り現状ではどう考えても手でやったほうがましです。


記録用途についてはいろいろと調べようがありそうです。
今のところ環境が整ってないので出来てませんが音ゲーの視線位置の追跡なんかは面白そうな気がします。気が向いたらそのうちやる。


なおエロゲーにつきましてはおおむね以下に近い結果になりましたことをここに報告して記事を終えます。

http://strangelet.hatenablog.jp/entry/2014/03/26/205039