はじめに
ゲーム用で安価なTobii Eye Tracker 4Cを使って視線座標,目の位置座標,頭の位置座標と回転座標を取得して,簡単なゲームなどのソフト開発を行う.商用で使う場合には別途ライセンスが必要なため注意が必要.
なぜか開発用のストリームエンジンライブラリ:StreamEngine libraryが本当に見つけづらい.色々と面白い開発ができそうなので紹介する.
使用機材
Tobii Eye Tracker 4C :約45000円
環境
Windows10
Visual Studio professional 2015/2017 :動作確認済み
Win32コンソールアプリケーション
前提としてVisual Studioでの開発ができる.
初回の作業として
1.Tobii Eye Tracker 4Cのセットアップ
2.開発用のストリームエンジンライブラリをダウンロード
プロジェクトの作成として
3.インクルードを設定
実際に使ってみる
4.データを抜き出してみる
という流れ.
1. Tobii Eye Tracker 4Cのセットアップ
ダウンロードサイトからtobii EYETRACKINGを選択し,デバイスはTobii Eye Tracker 4Cを選んでDownloadを押す.
細かい設定は省略,基本的にはTobii Eye Tracker 4Cを挿して指示通りに設定する.
2. 開発用のストリームエンジンライブラリをダウンロード
Consumer Eye Trackers
↓
Stream Engine
↓
Getting Startedにアクセス.
ページ下部のDownload for Windowsから
Stream Engine x.x.x for Windows x86
をダウンロードする.
3.インクルードを設定
Stream Engine x.x.x for Windows x86をVisual Studioのプロジェクト内に解凍する
プロジェクト名\プロジェクト名\consumer_stream_engine_windows_x.x.x.xとする.
ここでは
Tobii4c\Tobii4c\stream_engine_windows_x86_4.1.0.3
としている.
Visual Studioのプロジェクトプロバディから,
C/C++の全般タブから
追加のインクルードディレクトリに
$(SolutionDir)\tobii4c\stream_engine_windows_x86_4.1.0.3\include\tobii;%(AdditionalIncludeDirectories)
を追加.
バージョンによって若干違うが,tobii.hがフォルダ内にあればok.
リンカーの全般タブから
追加のライブラリディレクトリに
$(SolutionDir)\tobii4c\stream_engine_windows_x86_4.1.0.3\lib\tobii;%(AdditionalLibraryDirectories)
を追加.
バージョンによって若干違うが,tobii_stream_engine.libとtobii_stream_engine.dllがフォルダ内にあればok.
リンカーの入力タブから
tobii_stream_engine.lib
を追加する.
Tobii4c\Tobii4c\stream_engine_windows_x86_4.1.0.3\lib\tobii
の中にあるtobii_stream_engine.libをソリューション内のフォルダに置く.
これで準備は完了.
4.データを抜き出してみる
計測前には1.でインストールしたTobiiのアプリからキャリブレーションを行ってから計測する.(個人差が大きいので面倒だがしっかりと設定する.)
プログラムの流れとしては,
デバイスの呼び出し
↓
取りたいデータを有効化
↓
デバイスを呼び出し(有効化したデータが取得される)
↓
デバイスを初期化して終了
計測範囲外のときは-1をデータとして入力しています.
Git Hubにもサンプルコードをアップしています.
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <assert.h>
# include <tobii.h>
# include <tobii_streams.h>
/*VSでfopenを使うためのおまじない*/
# pragma warning(disable: 4996)
//ファイル作成用ポインタ
static FILE *fp_tobii_eye, *fp_tobii_eyep, *fp_tobii_headp;
//各データ格納用変数
int eyedata;
int eyepositiondata_left, eyepositiondata_right;
double wink_left, wink_right;
int headpositiondata;
//頭のポジション
void head_pose_callback(tobii_head_pose_t const* head_pose, void* user_data)
{
if (head_pose->position_validity == TOBII_VALIDITY_VALID) {
fprintf(fp_tobii_headp, "Position:,%f,%f,%f,",
head_pose->position_xyz[0],
head_pose->position_xyz[1],
head_pose->position_xyz[2]);
if (headpositiondata == 0) {
headpositiondata = 1;
}
}
else {
fprintf(fp_tobii_headp, "Position:,-1,-1,-1,");
if (headpositiondata == 1) {
headpositiondata = 0;
}
}
if (head_pose->position_validity == TOBII_VALIDITY_VALID) {
fprintf(fp_tobii_headp, "Rotation:,%f,%f,%f\n",
head_pose->rotation_xyz[0],
head_pose->rotation_xyz[1],
head_pose->rotation_xyz[2]);
}
else {
fprintf(fp_tobii_headp, "Rotation:,-1,-1,-1\n");
}
}
//目の位置
void eye_position_callback(tobii_eye_position_normalized_t const* eye_pos, void* user_data)
{
if (eye_pos->left_validity == TOBII_VALIDITY_VALID) {
fprintf(fp_tobii_eyep, "Left:,%f,%f,%f,",
eye_pos->left_xyz[0],
eye_pos->left_xyz[1],
eye_pos->left_xyz[2]);
}
else {
fprintf(fp_tobii_eyep, "Left:,-1,-1,-1,");
if (eyepositiondata_left == 1) {
eyepositiondata_left = 0;
}
}
if (eye_pos->right_validity == TOBII_VALIDITY_VALID) {
fprintf(fp_tobii_eyep, "Right:,%f,%f,%f\n",
eye_pos->right_xyz[0],
eye_pos->right_xyz[1],
eye_pos->right_xyz[2]);
}
else {
fprintf(fp_tobii_eyep, "Right:,-1,-1,-1\n");
if (eyepositiondata_right == 1) {
eyepositiondata_right = 0;
}
}
}
//視線計測
static void gaze_point_callback(tobii_gaze_point_t const* gaze_point, void* user_data)
{
double x, y;
if (gaze_point->validity == TOBII_VALIDITY_VALID) {
fprintf(fp_tobii_eye, "%f,%f\n",
gaze_point->position_xy[0],
gaze_point->position_xy[1]);
x = double(gaze_point->position_xy[0]);
y = double(gaze_point->position_xy[1]);
}
else {
fprintf(fp_tobii_eye, "-1,-1\n");
}
}
static void url_receiver(char const* url, void* user_data)
{
char* buffer = (char*)user_data;
if (*buffer != '\0') return; // only keep first value
if (strlen(url) < 256)
strcpy(buffer, url);
}
int tobii()
{
/*ファイル用*/
char filename[3][50];
//ファイルを作成---------------------------------------------------
sprintf(filename[0], "eye.csv");
sprintf(filename[1], "eyep.csv");
sprintf(filename[2], "headp.csv");
fp_tobii_eye = fopen(filename[0], "a");
fp_tobii_eyep = fopen(filename[1], "a");
fp_tobii_headp = fopen(filename[2], "a");
//-----------------------------------------------------------------
eyedata = 0;
eyepositiondata_left = 0;
eyepositiondata_right = 0;
wink_left = 0;
wink_right = 0;
headpositiondata = 0;
tobii_api_t* api;
tobii_error_t error = tobii_api_create(&api, NULL, NULL);
assert(error == TOBII_ERROR_NO_ERROR);
char url[256] = { 0 };
error = tobii_enumerate_local_device_urls(api, url_receiver, url);
assert(error == TOBII_ERROR_NO_ERROR && *url != '\0');
tobii_device_t* device;
error = tobii_device_create(api, url, TOBII_FIELD_OF_USE_INTERACTIVE, &device);
assert(error == TOBII_ERROR_NO_ERROR);
//頭の位置計測を有効化
error = tobii_head_pose_subscribe(device, head_pose_callback, 0);
assert(error == TOBII_ERROR_NO_ERROR);
//目の位置計測を有効化
error = tobii_eye_position_normalized_subscribe(device, eye_position_callback, 0);
assert(error == TOBII_ERROR_NO_ERROR);
//目線の計測を有効化
error = tobii_gaze_point_subscribe(device, gaze_point_callback, 0);
assert(error == TOBII_ERROR_NO_ERROR);
//とりあえず100データぶん
for (int i = 0; i <= 100; i++) {
//計測を呼びだし(有効化したもの全部,結構重くなる)
error = tobii_wait_for_callbacks(1, &device);
assert(error == TOBII_ERROR_NO_ERROR || error == TOBII_ERROR_TIMED_OUT);
error = tobii_device_process_callbacks(device);
assert(error == TOBII_ERROR_NO_ERROR);
}
//デバイスの初期化
error = tobii_gaze_point_unsubscribe(device);
assert(error == TOBII_ERROR_NO_ERROR);
error = tobii_device_destroy(device);
assert(error == TOBII_ERROR_NO_ERROR);
error = tobii_api_destroy(api);
assert(error == TOBII_ERROR_NO_ERROR);
//エクセルデータをfclose処理(fcloseをしないとうまく書き出されないので注意)
fclose(fp_tobii_eye);
fclose(fp_tobii_eyep);
fclose(fp_tobii_headp);
return 1;
}
おまけ(とれるデータの話)
視線データはモニタ内に対して(0.0)から(1.1)の範囲でデータの書き出しを行っています.
結構高精度なのでゲームだけじゃなく様々なアプリケーション開発ができそうなのでぜひ試しに触ってみてください.
Web会議とかでうまく共有とかできないかな...
有料版じゃないと取れないデータなどもあるので最新のAPIリファレンスを見て検討してみてください.