検索
ホームページウェブフロントエンドjsチュートリアルSupabase と WebGazer.js を使用してリアルタイムの視線追跡エクスペリエンスを構築する

TL;DR:

  • Supabase、React、WebGazer.js、Motion One、anime.js、Stable Audio で構築
  • Supabase のリアルタイム プレゼンスとブロードキャストを活用します (データベース テーブルはまったく使用されません!)
  • GitHub リポジトリ
  • ウェブサイト
  • デモビデオ

さらに別の Supabase Launch Week Hackathon と、Gaze into the Abyss と呼ばれるもう 1 つの実験的プロジェクトです。 これは、最終的には最も単純なプロジェクトであると同時に、最も複雑なプロジェクトの 1 つとなりました。幸いなことに、私は最近 Cursor をかなり楽しんでいたので、なんとかやり遂げるのにいくつかの助けがありました。また、私の心の中の疑問を検証したいと思いました。データベース テーブルを使わず、Supabase のリアルタイム機能だけを使用することは可能ですか? (多少明白かもしれない) 答えは次のとおりです: はい、そのとおりです (リアルタイム チーム、愛しています ♥️)。それでは、実装についてもう少し詳しく見てみましょう。

アイデア

私はある日、深淵についてのニーチェの名言について考えていました。それを何らかの形で実際に視覚化できたら素敵 (そしてクール) だろう、と考えていました。暗い画面を見つめると、何かがあなたを見つめ返します。それ以上のことは何もありません!

プロジェクトの構築

最初は Three.js を使用してこのプロジェクトを作成しようと考えていましたが、3D の目用に無料のアセットを作成または検索する必要があることに気付きました。特にプロジェクト自体に取り組む時間があまりなかったので、それは少しやりすぎだと判断し、代わりに SVG を使用して 2D で行うことにしました。

また、視覚だけのものにはしたくありませんでした。音声もあればより良い体験になるでしょう。そこで私は、参加者がマイクに向かって話せ、他の人にはそれが不適格なささやき声や通り過ぎる風として聞こえたら素晴らしいだろうというアイデアを思いつきました。しかし、これは非常に困難であることが判明し、WebAudio と WebRTC をうまく接続できなかったため、完全に削除することにしました。コードベースには、ローカルのマイクをリッスンして現在のユーザーに「風の音」をトリガーするコンポーネントが残っていますので、ご覧になりたい場合はご覧ください。将来何か追加されるかもしれません?

リアルタイムルーム

ビジュアル関連の作業をする前に、考えていたリアルタイム設定をテストしたいと思いました。リアルタイム機能にはいくつかの制限があるため、次のように機能するようにしました。

  • 最大数があります。 1 つのチャンネルに一度に 10 人の参加者が参加可能
    • つまり、チャンネルがいっぱいの場合は、新しいチャンネルに参加する必要があります
  • 他の参加者の目だけを見るべきです

このために、次のようなリアルタイム チャネルに再帰的に参加する useEffect セットアップを思いつきました。





この joinRoom は useEffect フック内に存在し、ルーム コンポーネントがマウントされるときに呼び出されます。この機能に取り組んでいるときに気づいた注意点の 1 つは、currentPresences パラメータが利用可能であっても、join イベントに値が含まれていないということでした。それが実装のバグなのか、それとも意図したとおりに動作しているのかはわかりません。したがって、ユーザーが参加するたびにルームの参加者の数を取得するには、手動で room.presenceState フェッチを実行する必要があります。

参加者数を確認し、現在のルームから登録を解除して別のルームへの参加を試みるか、現在のルームから続行します。同期が遅すぎるため、これを参加イベントで行います (イベントの参加または退出後にトリガーされます)。

ブラウザでたくさんのタブを開いてこの実装をテストしましたが、どれも充実しているようでした。

その後、マウス位置の更新を使用してソリューションをデバッグしたいと思いましたが、チャネル内で送信するメッセージが多すぎるという問題がすぐに発生しました。解決策: 通話を抑制します。

/**
 * Creates a throttled version of a function that can only be called at most once 
 * in the specified time period.
 */
function createThrottledFunction<t extends unknown> unknown>(
  functionToThrottle: T,
  waitTimeMs: number
): (...args: Parameters<t>) => void {
  let isWaitingToExecute = false

  return function throttledFunction(...args: Parameters<t>) {
    if (!isWaitingToExecute) {
      functionToThrottle.apply(this, args)
      isWaitingToExecute = true
      setTimeout(() => {
        isWaitingToExecute = false
      }, waitTimeMs)
    }
  }
}

</t></t></t>

Cursor はこの小さなスロットル関数作成ツールを思いつき、これを次のような視線追跡ブロードキャストで使用しました。

const throttledBroadcast = createThrottledFunction((data: EyeTrackingData) => {
  if (currentChannel) {
    currentChannel.send({
      type: 'broadcast',
      event: 'eye_tracking',
      payload: data
    })
  }
}, THROTTLE_MS)

throttledBroadcast({
 userId: userId.current,
 isBlinking: isCurrentlyBlinking,
 gazeX,
 gazeY
})

とても役に立ちました!また、初期のバージョンではアイトラッキング メッセージをプレゼンス付きで送信していましたが、ブロードキャストでは 1 秒あたりにより多くのメッセージが許可されるため、代わりに実装をそれに切り替えました。カメラは常にすべてを記録するため、視線追跡では特に重要です。

アイトラッキング

このプロジェクトのアイデアを最初に思いついたとき、しばらく前に WebGazer.js に遭遇しました。これは非常に興味深いプロジェクトで、驚くほどうまくいきます!

視線追跡機能全体は、useEffect フック内の 1 つの関数で実行されます。

    window.webgazer
      .setGazeListener(async (data: any) => {
        if (data == null || !currentChannel || !ctxRef.current) return

        try {
          // Get normalized gaze coordinates
          const gazeX = data.x / windowSize.width
          const gazeY = data.y / windowSize.height

          // Get video element
          const videoElement = document.getElementById('webgazerVideoFeed') as HTMLVideoElement
          if (!videoElement) {
            console.error('WebGazer video element not found')
            return
          }

          // Set canvas size to match video
          imageCanvasRef.current.width = videoElement.videoWidth
          imageCanvasRef.current.height = videoElement.videoHeight

          // Draw current frame to canvas
          ctxRef.current?.drawImage(videoElement, 0, 0)

          // Get eye patches
          const tracker = window.webgazer.getTracker()
          const patches = await tracker.getEyePatches(
            videoElement,
            imageCanvasRef.current,
            videoElement.videoWidth,
            videoElement.videoHeight
          )

          if (!patches?.right?.patch?.data || !patches?.left?.patch?.data) {
            console.error('No eye patches detected')
            return
          }

          // Calculate brightness for each eye
          const calculateBrightness = (imageData: ImageData) => {
            let total = 0

            for (let i = 0; i = SAMPLES_SIZE) {
            brightnessSamples.current.shift() // Remove oldest sample
          }
          brightnessSamples.current.push(avgBrightness)

          // Calculate dynamic threshold from rolling average
          const rollingAverage = brightnessSamples.current.reduce((a, b) => a + b, 0) / brightnessSamples.current.length
          const dynamicThreshold = rollingAverage * THRESHOLD_MULTIPLIER
          // Detect blink using dynamic threshold
          const blinkDetected = avgBrightness > dynamicThreshold

          // Debounce blink detection to avoid rapid changes
          if (blinkDetected !== isCurrentlyBlinking) {
            const now = Date.now()
            if (now - lastBlinkTime > 100) { // Minimum time between blink state changes
              isCurrentlyBlinking = blinkDetected
              lastBlinkTime = now
            }
          }

          // Use throttled broadcast instead of direct send
          throttledBroadcast({
            userId: userId.current,
            isBlinking: isCurrentlyBlinking,
            gazeX,
            gazeY
          })

        } catch (error) {
          console.error('Error processing gaze data:', error)
        }
      })

ユーザーが見ている場所の情報を取得するのは簡単で、画面上のマウスの位置を取得するのと同じように機能します。ただし、まばたき検出を (クールな) 機能として追加したいとも思いましたが、これにはいくつかの困難を乗り越える必要がありました。

WebGazer とまばたき検出に関する情報を Google で検索すると、初期実装の名残がいくつか確認できます。ソース内にコメントアウトされたコードがあるように。残念ながら、この種の機能はライブラリには存在しません。手動で行う必要があります。

多くの試行錯誤の後、Cursor と私は、眼帯データからピクセルと輝度レベルを計算して、ユーザーがいつまばたきしているかを判断するソリューションを思いつくことができました。また、(少なくとも私にとっては)照明によってはウェブカメラがユーザーのまばたきを常に認識するとは限らないことに気づいたため、動的な照明調整もいくつかあります。私の場合、写真や部屋が明るいほどうまく機能しませんが、暗い照明ではうまくいきます (図をご覧ください)。

視線追跡機能 (WebGazer には、画面上に赤い点を表示して、どこを見ているかを視覚化する非常に優れた .setPredictionPoints 呼び出しがあります) をデバッグしているときに、調整しない限り、追跡があまり正確ではないことに気付きました。 これは、プロジェクトがルームに参加する前に行うように求めていることです。







/**
 * Creates a throttled version of a function that can only be called at most once 
 * in the specified time period.
 */
function createThrottledFunction<t extends unknown> unknown>(
  functionToThrottle: T,
  waitTimeMs: number
): (...args: Parameters<t>) => void {
  let isWaitingToExecute = false

  return function throttledFunction(...args: Parameters<t>) {
    if (!isWaitingToExecute) {
      functionToThrottle.apply(this, args)
      isWaitingToExecute = true
      setTimeout(() => {
        isWaitingToExecute = false
      }, waitTimeMs)
    }
  }
}

</t></t></t>

これが実際に動作しているのを見るのは、とても素晴らしい経験でした。同じアプローチを周囲の線にも適用し、それらを中心に向かって「折りたたむ」ように Cursor に指示しました。これはほぼ 1 回の操作で完了しました!

その後、目は、部屋全体が大きな目のように見えるようにセルが配置された単純な CSS グリッド内にレンダリングされます。

const throttledBroadcast = createThrottledFunction((data: EyeTrackingData) => {
  if (currentChannel) {
    currentChannel.send({
      type: 'broadcast',
      event: 'eye_tracking',
      payload: data
    })
  }
}, THROTTLE_MS)

throttledBroadcast({
 userId: userId.current,
 isBlinking: isCurrentlyBlinking,
 gazeX,
 gazeY
})

最後の仕上げ

その後、素敵なイントロ画面と BGM を追加すれば、プロジェクトは準備完了です!

このような作業をしているとき、オーディオは常にエクスペリエンスを向上させます。そのため、ユーザーが「深淵に入る」ときに、安定したオーディオを使用して BGM を生成しました。私が音楽に使用したプロンプトは次のとおりです:

アンビエント、不気味、BGM、ささやき声、風、スローテンポ、不気味、深淵

また、ただの黒い画面では少し退屈だと思ったので、背景にアニメーションの SVG フィルターを追加しました。さらに、画面の中央に暗くぼやけた円を追加して、素敵なフェード効果を加えました。おそらく SVG フィルターを使用してこれを行うこともできましたが、これにあまり時間をかけたくありませんでした。次に、さらに動きを持たせるために、背景を軸を中心に回転させました。 SVG フィルターを使用してアニメーションを実行するのは少し不安定な場合があるため、代わりにこの方法で行うことにしました。

 <div>



<h2>
  
  
  結論
</h2>

<p>これで終わりです。Supabase のリアルタイム機能を使用して様式化された視線追跡を実装する方法を非常に簡単に説明しました。個人的には、これは非常に興味深い実験であり、作業中にあまり問題はありませんでした。そして驚くべきことに、プロジェクトを提出する前の最後の夜に徹夜する必要はありませんでした!</p>

<p>プロジェクトやその結果がどうなったかをデモビデオで気軽にチェックしてください。大勢の人が同時にそれを使用している場合、いくつかの問題が発生する可能性があります(適切に実行するには複数のデバイスとウェブカメラが必要なため、テストが非常に困難です)が、それはハッカソンプロジェクトのやり方だと思いますか?そして、それをテストする場合は、目が見えたら、それはインターネット上のどこかで別の誰かがあなたを見ているということを忘れないでください!</p>


          </div>

            
        

以上がSupabase と WebGazer.js を使用してリアルタイムの視線追跡エクスペリエンスを構築するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
node.jsは、型を使用してストリーミングしますnode.jsは、型を使用してストリーミングしますApr 30, 2025 am 08:22 AM

node.jsは、主にストリームのおかげで、効率的なI/Oで優れています。 ストリームはデータを段階的に処理し、メモリの過負荷を回避します。大きなファイル、ネットワークタスク、リアルタイムアプリケーションの場合。ストリームとTypeScriptのタイプの安全性を組み合わせることで、パワーが作成されます

Python vs. JavaScript:パフォーマンスと効率の考慮事項Python vs. JavaScript:パフォーマンスと効率の考慮事項Apr 30, 2025 am 12:08 AM

PythonとJavaScriptのパフォーマンスと効率の違いは、主に以下に反映されています。1)解釈された言語として、Pythonはゆっくりと実行されますが、開発効率が高く、迅速なプロトタイプ開発に適しています。 2)JavaScriptはブラウザ内の単一のスレッドに限定されていますが、マルチスレッドおよび非同期I/Oを使用してnode.jsのパフォーマンスを改善でき、両方とも実際のプロジェクトで利点があります。

JavaScriptの起源:その実装言語の調査JavaScriptの起源:その実装言語の調査Apr 29, 2025 am 12:51 AM

JavaScriptは1995年に発信され、Brandon Ikeによって作成され、言語をCに実現しました。 2。JavaScriptのメモリ管理とパフォーマンスの最適化は、C言語に依存しています。 3. C言語のクロスプラットフォーム機能は、さまざまなオペレーティングシステムでJavaScriptを効率的に実行するのに役立ちます。

舞台裏:JavaScriptをパワーする言語は何ですか?舞台裏:JavaScriptをパワーする言語は何ですか?Apr 28, 2025 am 12:01 AM

JavaScriptはブラウザとnode.js環境で実行され、JavaScriptエンジンに依存してコードを解析および実行します。 1)解析段階で抽象的構文ツリー(AST)を生成します。 2)ASTをコンパイル段階のバイトコードまたはマシンコードに変換します。 3)実行段階でコンパイルされたコードを実行します。

PythonとJavaScriptの未来:傾向と予測PythonとJavaScriptの未来:傾向と予測Apr 27, 2025 am 12:21 AM

PythonとJavaScriptの将来の傾向には、1。Pythonが科学コンピューティングの分野での位置を統合し、AI、2。JavaScriptはWebテクノロジーの開発を促進します。どちらもそれぞれのフィールドでアプリケーションシナリオを拡大し続け、パフォーマンスをより多くのブレークスルーを行います。

Python vs. JavaScript:開発環境とツールPython vs. JavaScript:開発環境とツールApr 26, 2025 am 12:09 AM

開発環境におけるPythonとJavaScriptの両方の選択が重要です。 1)Pythonの開発環境には、Pycharm、Jupyternotebook、Anacondaが含まれます。これらは、データサイエンスと迅速なプロトタイピングに適しています。 2)JavaScriptの開発環境には、フロントエンドおよびバックエンド開発に適したnode.js、vscode、およびwebpackが含まれます。プロジェクトのニーズに応じて適切なツールを選択すると、開発効率とプロジェクトの成功率が向上する可能性があります。

JavaScriptはCで書かれていますか?証拠を調べるJavaScriptはCで書かれていますか?証拠を調べるApr 25, 2025 am 12:15 AM

はい、JavaScriptのエンジンコアはCで記述されています。1)C言語は、JavaScriptエンジンの開発に適した効率的なパフォーマンスと基礎となる制御を提供します。 2)V8エンジンを例にとると、そのコアはCで記述され、Cの効率とオブジェクト指向の特性を組み合わせて書かれています。3)JavaScriptエンジンの作業原理には、解析、コンパイル、実行が含まれ、C言語はこれらのプロセスで重要な役割を果たします。

JavaScriptの役割:WebをインタラクティブでダイナミックにするJavaScriptの役割:WebをインタラクティブでダイナミックにするApr 24, 2025 am 12:12 AM

JavaScriptは、Webページのインタラクティブ性とダイナミズムを向上させるため、現代のWebサイトの中心にあります。 1)ページを更新せずにコンテンツを変更できます。2)Domapiを介してWebページを操作する、3)アニメーションやドラッグアンドドロップなどの複雑なインタラクティブ効果、4)ユーザーエクスペリエンスを改善するためのパフォーマンスとベストプラクティスを最適化します。

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

SublimeText3 Linux 新バージョン

SublimeText3 Linux 新バージョン

SublimeText3 Linux 最新バージョン

MantisBT

MantisBT

Mantis は、製品の欠陥追跡を支援するために設計された、導入が簡単な Web ベースの欠陥追跡ツールです。 PHP、MySQL、Web サーバーが必要です。デモおよびホスティング サービスをチェックしてください。

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser は、オンライン試験を安全に受験するための安全なブラウザ環境です。このソフトウェアは、あらゆるコンピュータを安全なワークステーションに変えます。あらゆるユーティリティへのアクセスを制御し、学生が無許可のリソースを使用するのを防ぎます。

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

Eclipse を SAP NetWeaver アプリケーション サーバーと統合します。

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境