8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Tobii Eye Tracker 4cを使ってVisual Studioで視線座標,目の位置座標,頭の位置座標と回転座標を取得する.

Last updated at Posted at 2020-10-03

#はじめに
ゲーム用で安価な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を押す.

01-1.jpg
01-2.jpg

細かい設定は省略,基本的にはTobii Eye Tracker 4Cを挿して指示通りに設定する.

#2. 開発用のストリームエンジンライブラリをダウンロード

tobii/developer zone内の

Consumer Eye Trackers

Stream Engine

Getting Startedにアクセス.

ページ下部のDownload for Windowsから
Stream Engine x.x.x for Windows x86
をダウンロードする.

1.1系,2.1系,4.1系で動作確認済み.
01-3.jpg

#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
としている.

02-1.jpg

Visual Studioのプロジェクトプロバディから,
C/C++の全般タブから
追加のインクルードディレクトリに
$(SolutionDir)\tobii4c\stream_engine_windows_x86_4.1.0.3\include\tobii;%(AdditionalIncludeDirectories)
を追加.
バージョンによって若干違うが,tobii.hがフォルダ内にあればok.

03-1.jpg

リンカーの全般タブから
追加のライブラリディレクトリに
$(SolutionDir)\tobii4c\stream_engine_windows_x86_4.1.0.3\lib\tobii;%(AdditionalLibraryDirectories)
を追加.
バージョンによって若干違うが,tobii_stream_engine.libとtobii_stream_engine.dllがフォルダ内にあればok.

03-2.jpg

リンカーの入力タブから
tobii_stream_engine.lib
を追加する.

03-3.jpg

Tobii4c\Tobii4c\stream_engine_windows_x86_4.1.0.3\lib\tobii
の中にあるtobii_stream_engine.libをソリューション内のフォルダに置く.

03-4.jpg

これで準備は完了.

#4.データを抜き出してみる

計測前には1.でインストールしたTobiiのアプリからキャリブレーションを行ってから計測する.(個人差が大きいので面倒だがしっかりと設定する.)

プログラムの流れとしては,

デバイスの呼び出し

取りたいデータを有効化

デバイスを呼び出し(有効化したデータが取得される)

デバイスを初期化して終了

計測範囲外のときは-1をデータとして入力しています.

Git Hubにもサンプルコードをアップしています.

main.c
#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会議とかでうまく共有とかできないかな...

視線計測概要(日本語).jpg

有料版じゃないと取れないデータなどもあるので最新のAPIリファレンスを見て検討してみてください.

8
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?