Skip to content

即時心率估算器

NOTE

僅限網頁 SDK 即時估算功能目前僅適用於網頁版 SDK(JavaScript、React、Vue)。不適用於行動裝置 SDK(iOS、Android、Flutter、React Native)。

概述

即時估算器在影片擷取過程中提供即時心率回饋,最快可在 3-5 秒內顯示結果,無需等待完整的 30 秒掃描。這創造了更具吸引力的使用者體驗,並幫助使用者調整位置和光線以獲得最佳測量結果。

快速開始

typescript
import { createVitalSignCamera, RealtimeEstimatorType } from 'ts-vital-sign-camera';

const camera = createVitalSignCamera({
  realtimeEstimationConfig: {
    estimatorType: RealtimeEstimatorType.MeRppg, // 預設:AI 驅動
    earlyEstimation: true,
    minDuration: 3,
    minConfidence: 0.3
  },
  onVideoFrameProcessed: (event) => {
    // 即時估算可從事件中取得
    if (event.realtimeEstimation) {
      console.log(`心率:${event.realtimeEstimation.heartRate} BPM`);
      console.log(`信心度:${event.realtimeEstimation.confidence}`);
      console.log(`穩定:${event.realtimeEstimation.isStable}`);
    }
  }
});

可用的估算器

ME-rPPG 估算器(預設)🤖

AI 驅動的神經網路方法

  • 最適合:消費者應用程式、變化的環境、現代裝置
  • 準確度:最先進(±2-3 BPM)
  • 速度:約 3 秒內首次結果
  • 動作容忍度:優秀
  • 光線容忍度:優秀
  • 授權:開源
  • 需求:約 10 MB 模型檔案
typescript
const camera = createVitalSignCamera({
  realtimeEstimationConfig: {
    estimatorType: RealtimeEstimatorType.MeRppg
  }
});

FDA 估算器 📈

訊號處理方法

  • 最適合:醫療應用程式、即時初始化、最小套件大小
  • 準確度:醫療級(±2-3 BPM)
  • 速度:約 5 秒內首次結果
  • 動作容忍度:良好
  • 光線容忍度:良好
  • 授權:專有(PanopticAI)
  • 需求:無(即時初始化)
typescript
const camera = createVitalSignCamera({
  realtimeEstimationConfig: {
    estimatorType: RealtimeEstimatorType.Fda
  }
});

配置選項

基本配置

typescript
interface RealtimeEstimationConfig {
  // 要使用的估算器
  estimatorType?: RealtimeEstimatorType;
  
  // 在完整掃描完成前顯示結果
  earlyEstimation?: boolean;
  
  // 顯示結果前的最小秒數
  minDuration?: number;
  
  // 最小信心度閾值(0-1)
  minConfidence?: number;
  
  // 啟用除錯記錄
  debug?: boolean;
}

ME-rPPG 特定選項

typescript
{
  // 模型檔案路徑(可選,提供預設值)
  modelPath?: string;
  statePath?: string;
  welchPath?: string;
  hrPath?: string;
  
  // 時間正規化參數
  lambda?: number; // 預設:1.0
}

估算結果

RealtimeEstimation 介面

typescript
interface RealtimeEstimation {
  // 心率(BPM)
  heartRate: number;
  
  // 信心度(0-1)
  confidence: number;
  
  // 結果是否足夠穩定可顯示
  isStable: boolean;
  
  // 訊噪比(可選)
  snr?: number;
  
  // 訊號品質分數 0-100(可選)
  signalQuality?: number;
}

使用估算結果

typescript
camera.onVideoFrameProcessed = (event) => {
  const estimation = event.realtimeEstimation;
  
  if (!estimation) return;
  
  if (estimation.isStable && estimation.confidence > 0.6) {
    // 高信心度 - 顯著顯示
    displayHeartRate(estimation.heartRate);
  } else if (estimation.confidence > 0.3) {
    // 中等信心度 - 顯示指示器
    displayHeartRate(estimation.heartRate, '精煉中...');
  } else {
    // 低信心度 - 引導使用者
    showMessage('調整中... 請保持靜止');
  }
};

訊號視覺化

顯示原始 PPG 訊號以進行除錯或使用者回饋。

基本用法

typescript
import { SignalVisualizer } from 'ts-vital-sign-camera';

// 建立視覺化器
const container = document.getElementById('signal-container');
const visualizer = new SignalVisualizer(container, {
  width: 600,
  height: 200,
  visualizationType: 'PPG', // 或 'ECG' 進行模擬
  lineColor: '#00ff00',
  backgroundColor: '#000000'
});

// 使用影片幀事件中的訊號資料更新
camera.onVideoFrameProcessed = (event) => {
  if (event.signalData) {
    visualizer.updateSignal(event.signalData, event.realtimeEstimation);
  }
};

// 清理
visualizer.destroy();

配置選項

typescript
interface SignalVisualizerConfig {
  width?: number;              // 畫布寬度(預設:600)
  height?: number;             // 畫布高度(預設:200)
  maxPoints?: number;          // 最大資料點(預設:300)
  lineColor?: string;          // 訊號線顏色
  backgroundColor?: string;    // 畫布背景
  gridColor?: string;          // 網格線顏色
  visualizationType?: 'PPG' | 'ECG';  // 訊號類型
  debug?: boolean;             // 除錯記錄
}

視覺化類型

PPG 模式(預設):

  • 顯示來自相機的實際光體積描記訊號
  • 即時滾動波形
  • 適用於除錯訊號品質

ECG 模式

  • 模擬心電圖動畫
  • 測量期間的視覺回饋
  • 符合估算的心率
typescript
// PPG 視覺化(實際訊號)
const ppgVisualizer = new SignalVisualizer(container, {
  visualizationType: 'PPG'
});

// ECG 模擬(動畫)
const ecgVisualizer = new SignalVisualizer(container, {
  visualizationType: 'ECG',
  lineColor: '#ff6b6b'
});

SDK 行為

時間軸

0秒 ────────► 3秒 ────────► 10秒 ────────► 30秒
│            │            │             │
│            │            │             └─ 完整掃描完成
│            │            └─ 達到最佳準確度
│            └─ 首次即時估算(ME-rPPG)
└─ 掃描開始

估算生命週期

  1. 初始化(0-3秒)

    • 相機開始擷取
    • 人臉偵測啟動
    • 訊號緩衝區填充
  2. 早期估算(3-5秒)

    • 首次結果可用
    • 較低信心度
    • 持續精煉
  3. 穩定估算(10秒以上)

    • 高信心度結果
    • 最佳準確度
    • 準備顯示
  4. 掃描完成(30秒)

    • 伺服器端驗證
    • 最終結果可用
    • 即時估算器可重置

事件流程

typescript
// 掃描生命週期
camera.onVideoFrameProcessed = (event) => {
  // 即時估算在此可用
  if (event.realtimeEstimation) {
    console.log('即時更新:', event.realtimeEstimation);
  }
};

camera.startScanning(); // 開始掃描

// 最終結果來自健康結果
camera.onVideoFrameProcessed = (event) => {
  if (event.healthResult) {
    console.log('最終結果:', event.healthResult);
  }
};

最佳實踐

1. 選擇正確的估算器

typescript
// 適用於具有變化環境的消費者應用程式
const config = {
  estimatorType: RealtimeEstimatorType.MeRppg,
  earlyEstimation: true,
  minDuration: 3,
  minConfidence: 0.3
};

// 適用於需要即時初始化的醫療應用程式
const config = {
  estimatorType: RealtimeEstimatorType.Fda,
  earlyEstimation: false,
  minDuration: 10,
  minConfidence: 0.7
};

2. 提供使用者回饋

typescript
camera.onVideoFrameProcessed = (event) => {
  const estimation = event.realtimeEstimation;
  if (!estimation) return;
  
  // 顯示信心度指示器
  updateConfidenceBar(estimation.confidence);
  
  // 根據品質引導使用者
  if (estimation.confidence < 0.4) {
    showTip('改善光線或保持靜止');
  }
  
  // 穩定時顯示
  if (estimation.isStable) {
    displayHeartRate(estimation.heartRate);
  }
};

3. 處理邊緣情況

typescript
let noEstimationTimeout;
let hasReceivedEstimation = false;

camera.startScanning();

// 設定無估算的逾時
noEstimationTimeout = setTimeout(() => {
  if (!hasReceivedEstimation) {
    showError('無法偵測心率。請調整光線。');
  }
}, 15000); // 15 秒

camera.onVideoFrameProcessed = (event) => {
  const estimation = event.realtimeEstimation;
  
  if (estimation) {
    hasReceivedEstimation = true;
    clearTimeout(noEstimationTimeout);
    
    if (estimation.confidence > 0.5) {
      hideError();
    }
  }
};

4. 最佳化效能

typescript
// 節流 UI 更新
let lastUpdate = 0;
const UPDATE_INTERVAL = 500; // 500毫秒

camera.onVideoFrameProcessed = (event) => {
  const estimation = event.realtimeEstimation;
  if (!estimation) return;
  
  const now = Date.now();
  if (now - lastUpdate > UPDATE_INTERVAL) {
    updateUI(estimation);
    lastUpdate = now;
  }
};

常見使用案例

健身應用程式

typescript
const camera = createVitalSignCamera({
  realtimeEstimationConfig: {
    estimatorType: RealtimeEstimatorType.MeRppg,
    earlyEstimation: true,
    minDuration: 3,
    minConfidence: 0.3
  },
  onVideoFrameProcessed: (event) => {
    if (event.realtimeEstimation) {
      updateWorkoutDisplay(event.realtimeEstimation.heartRate);
      updateHeartRateZone(event.realtimeEstimation.heartRate);
    }
  }
});

醫療應用程式

typescript
const camera = createVitalSignCamera({
  realtimeEstimationConfig: {
    estimatorType: RealtimeEstimatorType.Fda,
    earlyEstimation: false,
    minDuration: 10,
    minConfidence: 0.7
  },
  onVideoFrameProcessed: (event) => {
    const estimation = event.realtimeEstimation;
    if (estimation?.isStable && estimation.confidence > 0.7) {
      recordMeasurement(estimation);
    }
  }
});

使用訊號視覺化

typescript
const visualizer = new SignalVisualizer(container, {
  visualizationType: 'PPG',
  width: 800,
  height: 300
});

camera.onVideoFrameProcessed = (event) => {
  if (event.signalData) {
    visualizer.updateSignal(event.signalData, event.realtimeEstimation);
  }
};

疑難排解

無即時估算

問題:事件中未收到即時估算

解決方案

  1. 確保設定 earlyEstimation: true
  2. 檢查人臉是否正確偵測
  3. 驗證光線條件是否充足
  4. 至少等待 minDuration
  5. 檢查 event.realtimeEstimation 不為 null

低信心度分數

問題:信心度持續低於閾值

解決方案

  1. 改善光線(自然光最佳)
  2. 確保使用者保持靜止
  3. 檢查人臉是否居中且穩定
  4. 降低 minConfidence 閾值
  5. 使用 ME-rPPG 以獲得更好的動作容忍度

ME-rPPG 模型未載入

問題:模型載入失敗

解決方案

  1. 驗證模型檔案在正確的目錄中
  2. 檢查 CORS 標頭允許模型載入
  3. 確保正確的檔案路徑(使用絕對路徑)
  4. 檢查瀏覽器控制台是否有錯誤

API 參考

RealtimeEstimator 介面

typescript
interface RealtimeEstimator {
  readonly estimation: RealtimeEstimation | null;
  readonly signalData: SignalData | null;
  
  configure(config?: RealtimeEstimationConfig): void;
  reset(): void;
}

SignalVisualizer 類別

typescript
class SignalVisualizer {
  constructor(container: HTMLElement, config?: SignalVisualizerConfig);
  
  updateSignal(
    signalData: SignalData | null,
    realtimeEstimation?: RealtimeEstimation | null
  ): void;
  
  resetScale(): void;
  destroy(): void;
}

VideoFrameProcessedEvent

typescript
interface VideoFrameProcessedEvent {
  videoFrameInfo: VideoFrameInfo;
  videoFrame: VideoFrame;
  facebox?: NormalizedFacebox;
  healthResult?: ScanResult;
  scanConditions?: ScanConditions;
  landmarks?: NormalizedLandmarkList;
  
  // 即時估算資料
  realtimeEstimation?: RealtimeEstimation | null;
  signalData?: SignalData | null;
}

下一步