視線(実際には顔の動きの検出なので頭を大きく動かして青丸をコントロールです。)を利用して、青い丸をコントロールするゲーム。
このアイデアでは、赤い点の位置の変化(速度)に基づいて青い丸の位置を制御し、視線を利用した操作のシミュレーションを行います。
コードでは、青い丸の位置を赤い点の動き(速度)に応じて変化させるために、次の手順を実装します:
赤い点の動きを検出: 顔の動きから計算された赤い点の座標を使用します。
速度の計算: 前の赤い点の位置から現在の位置の変化量を使って速度を求めます。
青い丸の加速度に変換: この速度を使って青い丸の位置を加速度的に更新します。
論文風の解説。
-
はじめに
本研究では、視線の動きを利用したインタラクティブなユーザーインターフェースの開発を目的とし、ウェブカメラから取得した顔の中間点を基に視線の動きをリアルタイムで追跡し、その動きをオブジェクトの加速度として利用する手法を提案する。本論文では、具体的なアルゴリズムと計算内容について詳細に解説する。 -
視線追跡と動きの検出
BlazeFaceモデルを使用して顔のランドマークを検出し、両目の中間点をリアルタイムで計算する。具体的には、以下の手順で視線の動きを検出し、動きの情報を抽出する。
2.1 カメラセットアップ
ユーザーのデバイスからウェブカメラの映像を取得し、ビデオフレームの解像度を640×480ピクセルに設定する。この映像を元に顔のランドマークを検出するためのBlazeFaceモデルを呼び出し、顔の特徴点をリアルタイムで推定する。
2.2 顔の中間点の計算
BlazeFaceモデルが提供するランドマーク情報から、左目と右目の位置を取得する。両目のx座標およびy座標の平均をとり、目の中間点(視線の推定位置)を算出する。
これにより、視線の動きがリアルタイムで追跡される。
3 動きの検出と加速度への変換
視線の動きは、青い丸(オブジェクト)の動きに変換される。前フレームから現在のフレームへの視線の変化量を取得し、その変化量を青い丸の加速度として利用する。
3.1 視線の変化量の計算
現在の中間点の位置から前フレームの中間点の位置を差し引き、視線の変化量を求める。
3.2 加速度への変換
視線の変化量を元にオブジェクトの加速度を計算する。加速度はスケーリング係数0.45を用いて、視線の変化を調整する。
これにより、時間経過とともに速度が徐々に減速される。摩擦効果を加えることで、オブジェクトの動きが自然で滑らかなものとなる。
- オブジェクトの位置更新と境界チェック
新しい速度に基づいて青い丸の位置を更新する。
さらに、青い丸が画面外に出ないように境界チェックを行い、画面の端に達した場合はその位置に固定する。
-
結果と考察
本研究の手法では、ウェブカメラから取得した顔の動きに基づき、青い丸の動きをリアルタイムで制御することができた。スケーリング係数と摩擦効果の組み合わせにより、オブジェクトの動きを敏感すぎないように調整し、自然な動きを実現した。今後は、さらなる精度向上のために視線検出のアルゴリズムや摩擦係数の最適化などを検討していく。 -
結論
本研究では、視線入力によるオブジェクト制御の手法を提案し、ウェブ技術を活用してその効果を検証した。視線の動きをオブジェクトの加速度に変換することで、インタラクティブなユーザーインターフェースを実現できることを示した。 -
参考文献
TensorFlow.js Documentation
BlazeFace: On-device Real-time Joint Face Detection and Alignment
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>視線入力</title>
<style>
/* カメラ映像を非表示にする */
#video {
display: none;
}
/* キャンバスのスタイル */
#canvas {
border: 1px solid black;
}
/* 赤い点のスタイル */
#redDot {
position: absolute;
width: 20px;
height: 20px;
background-color: red;
border-radius: 50%;
}
/* 青い丸のスタイル */
#blueCircle {
position: absolute;
width: 50px;
height: 50px;
background-color: blue;
border-radius: 50%;
}
</style>
</head>
<body>
<video id="video" width="640" height="480" autoplay></video>
<canvas id="canvas" width="640" height="480"></canvas>
<div id="redDot"></div>
<div id="blueCircle"></div>
<!-- TensorFlow.jsとBlazeFaceの読み込み -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/blazeface"></script>
<script>
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const redDot = document.getElementById('redDot');
const blueCircle = document.getElementById('blueCircle');
// 前フレームの目の位置を記録
let prevGazeX = 0;
let prevGazeY = 0;
// 青い丸の初期位置を画面中央に設定
let blueCirclePosX = canvas.width / 2 - 25; // 青丸の幅の半分を引く
let blueCirclePosY = canvas.height / 2 - 25; // 青丸の高さの半分を引く
// 青い丸の速度
let velocityX = 0;
let velocityY = 0;
// カメラのセットアップ
async function setupCamera() {
video.srcObject = await navigator.mediaDevices.getUserMedia({
video: { width: 640, height: 480 },
});
return new Promise((resolve) => {
video.onloadedmetadata = () => {
resolve(video);
};
});
}
// 顔検出を行い、青い丸の位置を更新する関数
async function detectFace(model) {
const returnTensors = false;
const predictions = await model.estimateFaces(video, returnTensors);
if (predictions.length > 0) {
// カメラ映像をキャンバスに描画
context.drawImage(video, 0, 0, canvas.width, canvas.height);
predictions.forEach((prediction) => {
const leftEye = prediction.landmarks[0];
const rightEye = prediction.landmarks[1];
const gazeX = (leftEye[0] + rightEye[0]) / 2;
const gazeY = (leftEye[1] + rightEye[1]) / 2;
// 赤い点を目の中間位置に移動
redDot.style.left = `${gazeX - 10}px`;
redDot.style.top = `${gazeY - 10}px`;
// 赤い点の動きを加速度に変換
const deltaX = gazeX - prevGazeX;
const deltaY = gazeY - prevGazeY;
// 加速度のスケーリング係数を小さくする
velocityX += deltaX * 0.45; // 0.45で調整
velocityY += deltaY * 0.45; // 0.45で調整
// 速度に減速を加える(摩擦効果)
velocityX *= 0.9; // 90%の速度を維持
velocityY *= 0.9; // 90%の速度を維持
// 青い丸の位置を更新
blueCirclePosX += velocityX;
blueCirclePosY += velocityY;
// 境界チェック(画面からはみ出さないように)
blueCirclePosX = Math.max(0, Math.min(canvas.width - 50, blueCirclePosX));
blueCirclePosY = Math.max(0, Math.min(canvas.height - 50, blueCirclePosY));
// 青い丸を新しい位置に移動
blueCircle.style.left = `${blueCirclePosX}px`;
blueCircle.style.top = `${blueCirclePosY}px`;
// 前の位置を更新
prevGazeX = gazeX;
prevGazeY = gazeY;
});
}
}
// メイン関数
async function main() {
await setupCamera();
const model = await blazeface.load();
// 青い丸を画面中央に配置
blueCircle.style.left = `${blueCirclePosX}px`;
blueCircle.style.top = `${blueCirclePosY}px`;
video.play();
// 100ミリ秒ごとに顔検出を行う
setInterval(() => {
detectFace(model);
}, 100);
}
main();
</script>
</body>
</html>