我的uniapp的黑暗逻辑代码如下:
<template>
<view class="container">
<!-- 摄像头预览 -->
<camera
v-if="hasCameraPermission"
device-position="back"
:flash="flashStatus"
class="camera"
@ready="handleCameraReady">
</camera>
<view v-else class="permission-error">
<text>请允许访问摄像头以使用心率测量功能</text>
<button @click="requestCameraPermission" open-type="getAuthorize">授权摄像头权限</button>
</view>
<!-- 环境光状态 -->
<view class="status-info">
<view class="light-status">
<text class="status-label">环境光:</text>
<text :class="ambientLight > 150 ? 'warning' : ambientLight > 50 ? 'good' : 'warning'">
{{ ambientLight > 150 ? '过强' : ambientLight > 50 ? '适中' : '偏暗' }}
</text>
</view>
</view>
<!-- 心率显示区域 -->
<view class="heart-rate-display">
<view v-if="!isMeasuring && heartRate > 0">
<text>您的心率是</text>
<text class="heart-rate-value">{{ heartRate }}</text>
<text>BPM</text>
<view class="signal-quality">
信号质量: {{ signalQuality }}%
<progress :percent="signalQuality" stroke-width="4" active-color="#4CAF50"/>
</view>
<view class="measurement-conditions">
<text>测量环境: {{ ambientLight > 150 ? '光线过强' : ambientLight > 50 ? '光线适中' : '光线偏暗' }}</text>
</view>
</view>
<view v-if="isMeasuring" class="measuring-status">
<text>测量中...</text>
<text>{{ statusText }}</text>
<view class="finger-position-hint">
<text>请保持手指稳定覆盖摄像头</text>
<text>不要按压太用力</text>
</view>
<view class="environment-hint" v-if="ambientLight !== 0">
<text v-if="ambientLight > 150">光线过强,建议遮挡部分环境光</text>
<text v-else-if="ambientLight < 50">光线偏暗,已自动开启补光</text>
<text v-else>光线环境良好,请保持稳定</text>
</view>
</view>
<view v-if="!isMeasuring && heartRate === 0" class="ready-status">
<text>准备测量</text>
<text>{{ statusText || '请将手指轻轻覆盖摄像头和闪光灯' }}</text>
</view>
</view>
<!-- 测量进度 -->
<progress
:percent="progress"
stroke-width="6"
active-color="#FF5252"
background-color="#f5f5f5"
:active="true"
:show-info="true"
style="margin: 20px 0;" />
<!-- 控制按钮 -->
<view class="button-group">
<button
type="primary"
@click="startMeasurement"
:disabled="isMeasuring"
style="background-color: #FF5252; border: none;">
开始测量
</button>
<button
type="warn"
@click="stopMeasurement"
:disabled="!isMeasuring">
停止测量
</button>
<button
type="default"
@click="toggleFlash"
:class="isFlashAutoControlled ? 'auto-control' : 'manual-control'">
<view class="flash-text">{{ flashStatus === 'torch' ? '关闭' : '打开' }}</view>
<text class="control-mode">{{ isFlashAutoControlled ? '(自动)' : '(手动)' }}</text>
</button>
</view>
<!-- 使用说明 -->
<view class="instructions">
<text>使用方法:</text>
<text>1. 将手指轻轻覆盖在后置摄像头和闪光灯上</text>
<text>2. 点击"开始测量"按钮</text>
<text>3. 保持手指稳定约30秒</text>
</view>
<!-- 免责声明 -->
<view class="disclaimer">
<text class="disclaimer-title">免责声明</text>
<text>1. 本小程序仅供娱乐和参考使用,不作为任何医疗诊断依据</text>
<text>2. 测量结果可能存在误差,不能替代专业医疗设备</text>
<text>3. 如果您对自己的健康状况有疑虑,请及时就医</text>
<text>4. 本小程序不会收集、存储任何用户信息</text>
<text>5. 如将本程序的测试结果作为专业依据所造成的任何问题和损失,本程序不承担任何责任,包括法律责任</text>
<text>6. 使用本小程序即表示您同意以上声明内容</text>
</view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { useCamera } from './composables/useCamera'
import { useHeartRate } from './composables/useHeartRate'
// 主逻辑状态
const {
hasCameraPermission,
isCameraReady,
cameraContext,
requestCameraPermission
//onCameraReady
} = useCamera()
const {
isMeasuring,
heartRate,
progress,
statusText,
flashStatus,
ambientLight,
signalQuality,
isFlashAutoControlled,
startMeasurement,
stopMeasurement,
toggleFlash
} = useHeartRate(cameraContext)
// 处理摄像头就绪事件
const handleCameraReady = () => {
//onCameraReady();
isCameraReady.value = true
}
我导入的useCamera是相机处理的相关逻辑,我已确认没有问题,我的心率测量及处理逻辑useHeartRate.js的内容如下:
import { ref, reactive, onUnmounted, toRefs } from 'vue'
import * as SignalProcessor from './signalProcessor'
import * as Validator from './validator'
export function useHeartRate(cameraContext) {
// 状态管理
const state = reactive({
isMeasuring: false,
heartRate: 0,
progress: 0,
flashStatus: 'off',
signalQuality: 0,
statusText: '将手指轻轻覆盖摄像头和闪光灯',
isFlashAutoControlled: false,
flashStabilizationPeriod: false,
ambientLight: 0,
lastFlashToggleTime: 0,
cameraSettings: {
iso: 100,
brightness: 0,
exposure: 0
}
})
// 内部状态
let frameListener = null
let progressInterval = null
let signalData = []
let consecutiveNoFingerFrames = 0
let consecutiveFingerFrames = 0
let lastFrameTime = 0
const fps = 30
// 清理资源
const cleanup = () => {
if (frameListener) {
frameListener.stop()
frameListener = null
}
if (progressInterval) {
clearInterval(progressInterval)
progressInterval = null
}
signalData = []
consecutiveNoFingerFrames = 0
consecutiveFingerFrames = 0
}
// 开始测量
const startMeasurement = () => {
if (state.isMeasuring) return
// 确保摄像头已准备好
if (cameraContext.value===null) {
console.error('摄像头上下文未初始化')
state.statusText = '摄像头未准备好,请重试'
//state.statusText = '请等待摄像头初始化完成后再开始测量';
return
}
cleanup()
Object.assign(state, {
isMeasuring: true,
heartRate: 0,
progress: 0,
signalQuality: 0,
isFlashAutoControlled: true,
lastFlashToggleTime: Date.now(),
statusText: '正在检测环境光...',
flashStabilizationPeriod: true
})
signalData = []
try {
// 创建帧监听器
frameListener = cameraContext.value.onCameraFrame((frame) => {
processFrame(frame)
})
frameListener.start()
} catch (error) {
console.error('启动帧监听失败:', error)
state.statusText = '启动测量失败,请重试'
cleanup()
return
}
// 模拟进度更新
progressInterval = setInterval(() => {
if (state.progress < 100) {
state.progress += 1
} else {
clearInterval(progressInterval)
calculateHeartRate()
}
}, 100)
}
// 停止测量
const stopMeasurement = () => {
if (!state.isMeasuring) return
cleanup()
Object.assign(state, {
isMeasuring: false,
flashStatus: 'off'
})
}
// 处理帧数据(核心逻辑)
const processFrame = (frame) => {
const now = Date.now()
if (now - lastFrameTime < 1000 / fps) return
lastFrameTime = now
// 1. 检测环境光
const ambientLight = detectAmbientLight(frame)
// 2. 调整相机设置
const { settings, needFlash } = adjustCameraSettings(ambientLight)
state.ambientLight = ambientLight
state.cameraSettings = settings
// 3. 检测手指位置
const { isFingerPresent } = detectFinger(frame)
if (!isFingerPresent) {
state.statusText = '请将手指完全覆盖摄像头和闪光灯'
consecutiveNoFingerFrames = consecutiveNoFingerFrames ? consecutiveNoFingerFrames + 1 : 1
if (consecutiveNoFingerFrames >= 15) {
stopMeasurement()
state.progress = 0
state.statusText = '未检测到手指,请重新测量'
return
}
return
} else {
consecutiveFingerFrames = consecutiveFingerFrames ? consecutiveFingerFrames + 1 : 1
if (consecutiveFingerFrames >= 3) {
consecutiveNoFingerFrames = 0
consecutiveFingerFrames = 0
}
}
// 4. 处理信号数据
const redValue = getFrameRedValue(frame)
signalData.push({
time: now,
value: redValue,
ambientLight
})
// 5. 保持数据量
if (signalData.length > 300) {
signalData.shift()
}
// 6. 更新状态文本
updateStatusText()
}
// 检测环境光强度
const detectAmbientLight = (frame) => {
const data = new Uint8Array(frame.data)
let totalBrightness = 0
const samplingStep = 20 // 采样步长,提高性能
let samplingCount = 0
// 采样图像中心区域的亮度
const centerX = Math.floor(frame.width / 2)
const centerY = Math.floor(frame.height / 2)
const sampleRadius = Math.min(frame.width, frame.height) * 0.3
for (let y = centerY - sampleRadius; y <= centerY + sampleRadius; y += samplingStep) {
for (let x = centerX - sampleRadius; x <= centerX + sampleRadius; x += samplingStep) {
if (x >= 0 && x < frame.width && y >= 0 && y < frame.height) {
const index = (y * frame.width + x) * 4
// 计算YUV亮度: Y = 0.299R + 0.587G + 0.114B
const brightness = (data[index] * 0.299 + data[index + 1] * 0.587 + data[index + 2] * 0.114)
totalBrightness += brightness
samplingCount++
}
}
}
return samplingCount > 0 ? totalBrightness / samplingCount : 0
}
// 动态调整相机参数
const adjustCameraSettings = (ambientLight) => {
const settings = { ...state.cameraSettings }
let needFlash = false
// 调整环境光阈值,避免频繁切换
if (ambientLight < 40) { // 降低暗光阈值
// 低光环境
settings.iso = 400
settings.exposure = 1.5 // 降低曝光调整幅度
settings.brightness = 0.3 // 降低亮度调整幅度
needFlash = true
} else if (ambientLight < 100) { // 正常光环境
settings.iso = 200
settings.exposure = 1
settings.brightness = 0
needFlash = false
} else { // 强光环境
settings.iso = 100
settings.exposure = 0
settings.brightness = -0.3 // 降低亮度调整幅度
needFlash = false
}
// 如果不是自动控制模式,不建议开启闪光灯
if (!state.isFlashAutoControlled) {
needFlash = false
}
return { settings, needFlash }
}
// 检测手指是否正确放置
const detectFinger = (frame) => {
const data = new Uint8Array(frame.data)
const width = frame.width
const height = frame.height
const centerRegionSize = 0.6 // 检测中心60%的区域
let totalRed = 0
let totalGreen = 0
let totalBlue = 0
let pixelCount = 0
// 计算中心区域的边界
const startX = Math.floor(width * (1 - centerRegionSize) / 2)
const endX = Math.floor(width * (1 + centerRegionSize) / 2)
const startY = Math.floor(height * (1 - centerRegionSize) / 2)
const endY = Math.floor(height * (1 + centerRegionSize) / 2)
// 分析中心区域的RGB通道值
for (let y = startY; y < endY; y += 2) { // 隔行采样提高性能
for (let x = startX; x < endX; x += 2) { // 隔列采样提高性能
const i = (y * width + x) * 4
totalRed += data[i] // 红色通道值
totalGreen += data[i + 1] // 绿色通道值
totalBlue += data[i + 2] // 蓝色通道值
pixelCount++
}
}
const avgRed = totalRed / pixelCount
const avgGreen = totalGreen / pixelCount
const avgBlue = totalBlue / pixelCount
// 计算红色通道占比 - 手指覆盖时红色通道值应明显高于其他通道
const redRatio = avgRed / (avgGreen + avgBlue + 1) // 防止除零
// 计算整体亮度
const brightness = (avgRed + avgGreen + avgBlue) / 3
// 判断手指是否存在的多重条件
const isFingerPresent = (redRatio > 1.2) &&
(brightness > 30 && brightness < 220) &&
(avgRed > 60)
return { isFingerPresent }
}
// 计算红色通道值(优化版)
const getFrameRedValue = (frame) => {
if (!frame.data || frame.width <= 0 || frame.height <= 0) return 0
const data = new Uint8Array(frame.data)
let redSum = 0
let pixelCount = 0
const sampleArea = Math.min(frame.width, frame.height) * 0.3 // 采样区域大小
// 采样中心区域,避免边缘噪声
const centerX = Math.floor(frame.width / 2)
const centerY = Math.floor(frame.height / 2)
// 使用圆形采样区域,减少手指边缘影响
for (let y = centerY - sampleArea; y <= centerY + sampleArea; y++) {
for (let x = centerX - sampleArea; x <= centerX + sampleArea; x++) {
// 检查是否在圆形区域内
const distance = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2))
if (distance <= sampleArea &&
x >= 0 && x < frame.width &&
y >= 0 && y < frame.height) {
const index = (y * frame.width + x) * 4
// 增加红色通道权重,减少其他通道干扰
redSum += data[index] * 0.8 - data[index+1] * 0.1 - data[index+2] * 0.1
pixelCount++
}
}
}
return pixelCount > 0 ? redSum / pixelCount : 0
}
// 更新状态文本
const updateStatusText = () => {
if (signalData.length > 30) {
const recentData = signalData.slice(-30)
const variance = SignalProcessor.calculateSignalVariance(recentData.map(d => d.value))
// 更新状态文本
if (variance < 0.1) {
state.statusText = '请稍微增加按压力度'
} else if (variance > 2.0) {
state.statusText = '请稍微减小按压力度'
} else {
state.statusText = '信号良好,请保持稳定'
}
}
}
// 计算心率
const calculateHeartRate = () => {
if (signalData.length < fps * 5) {
state.heartRate = 0
state.progress = 100
state.statusText = "测量时间不足,请保持手指稳定"
stopMeasurement()
return
}
// 简化版心率计算
state.heartRate = Math.floor(Math.random() * 40) + 60
state.signalQuality = Math.floor(Math.random() * 40) + 60
state.statusText = "测量完成"
}
// 闪光灯控制
const toggleFlash = () => {
const newStatus = state.flashStatus === 'torch' ? 'off' : 'torch'
const now = Date.now()
if (now - state.lastFlashToggleTime > 1000) {
state.flashStatus = newStatus
state.isFlashAutoControlled = false
state.lastFlashToggleTime = now
if (newStatus === 'torch') {
setTimeout(() => {
state.isFlashAutoControlled = true
}, 3000)
}
}
}
// 组件卸载时清理
onUnmounted(() => {
cleanup()
})
return {
...toRefs(state),
startMeasurement,
stopMeasurement,
toggleFlash
}
}
// 导出工具函数
export * from './signalProcessor'
export * from './validator'
我在真机上调试的时候,授权了相机权限,点击开始测量按钮后就出现了问题,控制台信息如下:
WAServiceMainContext.js:1 MiniProgramError
(void 0) is not a function
TypeError: (void 0) is not a function
at updateStatusText (https://usr/app-service.js:8946:15)
at processFrame (https://usr/app-service.js:8842:5)
at Function.<anonymous> (https://usr/app-service.js:8778:9)
at <CameraContext.onCameraFrame callback function>
at https://lib/WAServiceMainContext.js:1:432607
at Ht.o (https://lib/WAServiceMainContext.js:1:1749601)
at Ht.emit (https://lib/WAServiceMainContext.js:1:986787)
at Object.emit (https://lib/WAServiceMainContext.js:1:992138)
at https://lib/WAServiceMainContext.js:1:993357
at https://lib/WAServiceMainContext.js:1:983327
在测量过程中检测手指功能正常,根据环境光强度控制手电筒功能不正常,我只能提供这些信息了,请你帮我找出问题所在并解决