首頁 web前端 js教程 使用 Supabase 和 WebGazer.js 建立即時眼動追蹤體驗

使用 Supabase 和 WebGazer.js 建立即時眼動追蹤體驗

Dec 28, 2024 am 11:06 AM

TL;博士:

  • 使用 Supabase、React、WebGazer.js、Motion One、anime.js、穩定音訊建置
  • 利用 Supabase 即時呈現和廣播(完全不使用資料庫表格!)
  • GitHub 儲存庫
  • 網站
  • 示範影片

又一場 Supabase 啟動週黑客馬拉松和另一個實驗項目,名為 凝視深淵。 這最終成為了最簡單又最複雜的項目之一。幸運的是,我最近很喜歡 Cursor,所以我得到了一些幫助來完成它!我還想驗證我心中的一個問題:是否可以使用 Supabase 的即時功能而無需任何資料庫表? (也許有些明顯)答案是:是的,是的(愛你,即時團隊♥️)。因此,讓我們更深入地了解實現。

這個想法

有一天,我隨機想到了尼采關於深淵的名言,如果能夠以某種方式實際想像它會很好(而且很酷):你凝視著黑暗的屏幕,有東西在凝視著你。沒有更多了!

建構專案

最初我的想法是使用 Three.js 來製作這個項目,但我意識到這意味著我需要為 3D 眼睛創建或找到一些免費資源。我認為這有點太多了,特別是因為我沒有太多時間來處理專案本身,因此決定使用 SVG 進行 2D 製作。

我也不希望它只是視覺效果:如果有些音訊也會有更好的體驗。所以我有一個想法,如果參與者可以對著麥克風說話,而其他人可以聽到不合格的低語或風過時的聲音,那就太棒了。然而,這非常具有挑戰性,因此我決定完全放棄它,因為我無法將 WebAudio 和 WebRTC 很好地連接在一起。我的程式碼庫中確實有一個剩餘組件,如果您想看一下,它會監聽本地麥克風並為當前用戶觸發“風聲”。也許將來會添加一些東西?

即時房間

在處理任何視覺內容之前,我想測試一下我想要的即時設定。由於即時功能存在一些限制,我希望它能夠工作,以便:

  • 最多有。一個頻道一次有 10 位參與者
    • 表示如果一個新頻道已滿,您需要加入一個新頻道
  • 你應該只看到其他參與者的眼睛

為此,我想出了一個 useEffect 設置,它遞歸地加入到實時通道,如下所示:

登入後複製
登入後複製

這個 joinRoom 位於 useEffect 鉤子內,並在安裝房間元件時被呼叫。我在開發此功能時發現的一個警告是,currentPresences 參數在連接事件中不包含任何值,即使它可用。我不確定這是否是實施中的錯誤或按預期工作。因此,每當用戶加入時,需要手動獲取 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 (...args: 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)
    }
  }
}

登入後複製
登入後複製

遊標想出了這個小油門函數創建器,我將它與眼動追蹤廣播一起使用,如下所示:

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
})
登入後複製
登入後複製

這很有幫助!另外,在最初的版本中,我在存在狀態下發送了眼動追蹤訊息,但是廣播每秒允許更多訊息,所以我將實現切換到了這一點。這在眼動追蹤中尤其重要,因為相機會一直記錄一切。

眼球追蹤

當我第一次有了這個專案的想法時,我就遇到了 WebGazer.js。這是一個非常有趣的項目,而且效果出奇的好!

整個眼球追蹤功能是在 useEffect 掛鉤中的一個函數中完成的:

    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 < imageData.data.length; i += 16) {
              // Convert RGB to grayscale
              const r = imageData.data[i]
              const g = imageData.data[i + 1]
              const b = imageData.data[i + 2]
              total += (r + g + b) / 3
            }
            return total / (imageData.width * imageData.height / 4)
          }

          const rightEyeBrightness = calculateBrightness(patches.right.patch)
          const leftEyeBrightness = calculateBrightness(patches.left.patch)
          const avgBrightness = (rightEyeBrightness + leftEyeBrightness) / 2

          // Update rolling average
          if (brightnessSamples.current.length >= 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)
        }
      })
登入後複製

取得使用者正在查看的資訊很簡單,就像取得螢幕上的滑鼠位置一樣。然而,我還想添加眨眼檢測作為(一個很酷的)功能,這需要跳過一些環節。

當您在 google 上搜尋有關 WebGazer 和眨眼偵測的資訊時,您可以看到初始實作的一些剩餘內容。就像原始碼中甚至有註解掉的程式碼一樣。不幸的是,庫中不存在此類功能。您需要手動完成。

經過大量的試驗和錯誤,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 (...args: 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)
    }
  }
}

登入後複製
登入後複製

看到它的實際應用是一次非常酷的體驗!我對周圍的線條應用了相同的方法,並指示遊標將它們向中心“折疊”:它幾乎一氣呵成!

然後,眼睛將在一個簡單的 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
})
登入後複製
登入後複製

最後的潤飾

然後播放一些不錯的介紹螢幕和背景音樂,專案就可以開始了!

當您處理此類事情時,音訊總是可以改善體驗,因此我使用穩定音訊在使用者「進入深淵」時生成背景音樂。我用於音樂的提示如下:

環境、令人毛骨悚然、背景音樂、低語聲、風、慢節奏、怪異、深淵

我還覺得純黑的螢幕有點無聊,所以我在背景上添加了一些動畫 SVG 濾鏡。此外,我在螢幕中央添加了一個黑暗的、模糊的圓圈,以產生一些漂亮的淡入淡出效果。我可能可以使用 SVG 濾鏡來完成此操作,但我不想在這方面花費太多時間。然後為了有更多的運動,我讓背景繞著軸旋轉。有時使用 SVG 濾鏡製作動畫有點奇怪,所以我決定採取這種方式。

 <div>



<h2>
  
  
  結論
</h2>

<p>現在您已經了解了:相當直接地了解如何使用 Supabase 的即時功能實現程式化的眼球追蹤。就我個人而言,我發現這是一個非常有趣的實驗,並且在進行過程中沒有遇到太多問題。令人驚訝的是,在提交專案之前我不需要在最後一晚熬夜! </p>

<p>請隨意查看該專案或示範影片的結果。如果一群人同時使用它,可能會出現一些問題(很難測試,因為它需要多個設備和網路攝影機才能正確完成),但我想這是黑客馬拉松專案的時尚?如果您確實進行了測試,請記住,如果您看到一隻眼睛,那就是其他人透過網路在某個地方看著您! </p>


          </div>

            
        
登入後複製

以上是使用 Supabase 和 WebGazer.js 建立即時眼動追蹤體驗的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1664
14
CakePHP 教程
1423
52
Laravel 教程
1317
25
PHP教程
1268
29
C# 教程
1243
24
神秘的JavaScript:它的作用以及為什麼重要 神秘的JavaScript:它的作用以及為什麼重要 Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

JavaScript的演變:當前的趨勢和未來前景 JavaScript的演變:當前的趨勢和未來前景 Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

JavaScript引擎:比較實施 JavaScript引擎:比較實施 Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

Python vs. JavaScript:學習曲線和易用性 Python vs. JavaScript:學習曲線和易用性 Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

JavaScript:探索網絡語言的多功能性 JavaScript:探索網絡語言的多功能性 Apr 11, 2025 am 12:01 AM

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

如何使用Next.js(前端集成)構建多租戶SaaS應用程序 如何使用Next.js(前端集成)構建多租戶SaaS應用程序 Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

使用Next.js(後端集成)構建多租戶SaaS應用程序 使用Next.js(後端集成)構建多租戶SaaS應用程序 Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

從C/C到JavaScript:所有工作方式 從C/C到JavaScript:所有工作方式 Apr 14, 2025 am 12:05 AM

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

See all articles