You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flask睡眠检测网站中实现睡眠触发音频自动启停?

解决Flask睡眠检测网站的音频启停问题

核心问题分析

你之前尝试用playsound/winsound在后端播放音频失败,主要有两个原因:

  1. 阻塞机制:这类音频库是同步阻塞的,会卡住gen_frames的帧推送循环,导致视频流中断。
  2. 播放位置错误:后端代码运行在服务器端,音频会在服务器设备播放,而非访问网站的用户设备——这完全不符合网站场景的需求。

下面给出两种可行方案,优先推荐前端控制音频的方案,适配网站的通用场景。


方案一:前端控制音频(推荐,适配网站场景)

通过WebSocket实时推送睡眠状态到前端,由浏览器控制音频播放/暂停,确保声音在用户设备上输出。

步骤1:修改Flask后端,添加WebSocket支持

先安装依赖:

pip install flask-socketio

更新Python代码:

from flask import Flask, render_template, Response
import cv2
import numpy as np
from imutils import face_utils
import dlib
from flask_socketio import SocketIO, emit

app = Flask(__name__)
app.config['SECRET_KEY'] = 'sleep-detector-secret'
socketio = SocketIO(app, cors_allowed_origins="*")

camera = cv2.VideoCapture(0)
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")

last_status = ""  # 记录上一次状态,避免重复推送

def compute(ptA, ptB):
   dist = np.linalg.norm(ptA - ptB)
   return dist

def blinked(a, b, c, d, e, f):
   up = compute(b,d) + compute(c,e)
   down = compute(a,f)
   ratio = up/(2.0*down)
   if(ratio > 0.25):
      return 2
   elif(ratio > 0.21 and ratio <= 0.25):
      return 1
   else:
      return 0

def gen_frames():
   global last_status
   while True:
      success, frame = camera.read()
      if not success:
         break
      gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

      sleep = 0
      drowsy = 0
      active = 0
      status = ""
      color = (0, 0, 0)

      faces = detector(gray)
      for face in faces:
         x1, y1, x2, y2 = face.left(), face.top(), face.right(), face.bottom()
         cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)

         landmarks = face_utils.shape_to_np(predictor(gray, face))
         left_blink = blinked(landmarks[36], landmarks[37], landmarks[38], landmarks[41], landmarks[40], landmarks[39])
         right_blink = blinked(landmarks[42], landmarks[43], landmarks[44], landmarks[47], landmarks[46], landmarks[45])

         if(left_blink == 0 or right_blink == 0):
             sleep += 1
             if(sleep > 6):
                 status = "SLEEPING"
                 color = (255, 0, 0)
         elif(left_blink == 1 or right_blink == 1):
             drowsy += 1
             if(drowsy > 6):
                 status = "Drowsy"
                 color = (0, 0, 255)
         else:
             active += 1
             if(active > 6):
                 status = "Active"
                 color = (0, 255, 0)

         cv2.putText(frame, status, (100, 100), cv2.FONT_HERSHEY_SIMPLEX, 1.2, color, 3)
         for (x, y) in landmarks:
             cv2.circle(frame, (x, y), 1, (255, 255, 255), -1)

      # 仅当状态变化时推送至前端
      if status != last_status:
          last_status = status
          socketio.emit('sleep_status', {'status': status})

      ret, buffer = cv2.imencode('.jpg', frame)
      frame = buffer.tobytes()
      yield(b'--frame\r\n'
            b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')

@app.route('/')
def index():
   return render_template('index.html')

@app.route('/video')
def video():
   return Response(gen_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')

if __name__ == "__main__":
   socketio.run(app, debug=True)  # 用socketio启动服务

步骤2:修改前端HTML,添加音频控制逻辑

创建static文件夹,将alarm.wav放入其中,然后更新HTML:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SleepCatcher</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
</head>
<body>
    <div id="video">
        <img src="{{ url_for('video') }}" />
    </div>
    <!-- 隐藏的音频元素,循环播放闹钟 -->
    <audio id="alarmAudio" src="{{ url_for('static', filename='alarm.wav') }}" loop></audio>

    <script>
        const socket = io();
        const alarmAudio = document.getElementById('alarmAudio');
        let isPlaying = false;

        // 监听后端推送的状态
        socket.on('sleep_status', function(data) {
            const status = data.status;
            if (status === 'SLEEPING' && !isPlaying) {
                // 浏览器可能阻止自动播放,可添加页面点击交互触发
                alarmAudio.play().catch(err => console.log('音频播放被浏览器拦截,请点击页面后重试'));
                isPlaying = true;
            } else if (status !== 'SLEEPING' && isPlaying) {
                alarmAudio.pause();
                alarmAudio.currentTime = 0; // 重置音频到开头
                isPlaying = false;
            }
        });

        // 添加页面点击事件,解决浏览器自动播放限制
        document.body.addEventListener('click', () => {
            if (!isPlaying) {
                alarmAudio.load();
            }
        });
    </script>
</body>
</html>

方案二:后端本地播放音频(仅适用于本地运行Flask)

如果你只是在自己电脑上运行服务,想让电脑本身播放音频,可以用线程解决阻塞问题:

修改Python代码中的相关部分:

import threading
from playsound import playsound

is_alarm_playing = False

def play_alarm():
    global is_alarm_playing
    is_alarm_playing = True
    playsound('alarm.wav')
    is_alarm_playing = False

# 在gen_frames的sleep>6判断块中:
if(sleep > 6):
    status = "SLEEPING !!!"
    color = (255, 0, 0)
    if not is_alarm_playing:
        threading.Thread(target=play_alarm, daemon=True).start()

这种方式仅适合本地使用,部署到远程服务器时用户无法听到音频。

内容的提问来源于stack exchange,提问作者VeryNew

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 07:31:17