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

部署在DigitalOcean的Flask+OpenCV手语识别应用摄像头调用失败

解决Flask+OpenCV手语识别应用部署后摄像头访问错误

问题本质

你当前的代码逻辑完全错误:cv2.VideoCapture(0)是在DigitalOcean服务器端执行的,它尝试访问的是服务器本身的摄像头,但云服务器几乎没有外接摄像头设备,所以必然报“无法找到索引对应的摄像头”错误。本地运行时代码在你的笔记本上执行,自然能访问本地摄像头,这就是本地正常、部署后失效的核心原因。

要实现调用客户端(你的笔记本)摄像头的需求,必须改成客户端采集画面→传输到服务器处理→服务器返回结果→客户端展示的架构。

解决方案步骤

1. 修改Flask后端:移除服务器端摄像头调用,改为接收前端帧并处理

删除全局的camera = cv2.VideoCapture(0),以及所有相关的摄像头初始化/释放逻辑,新增接收前端帧的路由,并处理后返回结果。

修改后的核心后端代码示例:

from flask import Flask, render_template, request, jsonify
import cv2
import numpy as np
from cvzone.ClassificationModule import Classifier
import sign_model
from cvzone.HandTrackingModule import HandDetector
from flask_cors import CORS  # 需安装:pip install flask-cors

app = Flask(__name__)
CORS(app)  # 解决跨域问题

# 初始化模型和检测器,仅加载一次
detector = HandDetector(maxHands=1, detectionCon=0.7)
classifier = Classifier("model/TESTWITH643_2.h5", "model/labels.txt")

@app.route("/level-one")
def level_one():
     return render_template('level-one.html')

@app.route("/level-two")
def level_two():
     return render_template('level-two.html')

@app.route("/process_frame_easy", methods=["POST"])
def process_frame_easy():
    # 接收前端传来的Base64格式帧
    data = request.json
    frame_data = data['frame'].split(',')[1]
    img_bytes = bytes.fromhex(frame_data)
    np_arr = np.frombuffer(img_bytes, np.uint8)
    img = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)

    width = img.shape[1]
    height = img.shape[0]

    # 执行简单模式识别逻辑
    try:
        imgOutput, score = sign_model.easy_mode(width, height, img, classifier)
        hands, _ = detector.findHands(imgOutput, draw=True)
    except cv2.error as e:
        return jsonify({"error": str(e)}), 400

    # 将处理后的帧转为Base64返回前端
    _, buffer = cv2.imencode('.png', imgOutput)
    img_base64 = buffer.tobytes().hex()
    return jsonify({"frame": f"data:image/png;base64,{img_base64}", "score": score})

@app.route("/process_frame_hard", methods=["POST"])
def process_frame_hard():
    # 接收前端传来的Base64格式帧
    data = request.json
    frame_data = data['frame'].split(',')[1]
    img_bytes = bytes.fromhex(frame_data)
    np_arr = np.frombuffer(img_bytes, np.uint8)
    img = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)

    width = img.shape[1]
    height = img.shape[0]

    # 执行困难模式识别逻辑
    try:
        imgOutput, score = sign_model.hard_mode(width, height, img, classifier)
        hands, _ = detector.findHands(imgOutput, draw=True)
    except cv2.error as e:
        return jsonify({"error": str(e)}), 400

    # 将处理后的帧转为Base64返回前端
    _, buffer = cv2.imencode('.png', imgOutput)
    img_base64 = buffer.tobytes().hex()
    return jsonify({"frame": f"data:image/png;base64,{img_base64}", "score": score})

if __name__ == "__main__":
    app.run(host='0.0.0.0', debug=True)

2. 修改前端HTML(以level-one.html为例):用WebRTC获取摄像头并传输帧

添加视频元素和JavaScript逻辑,实现摄像头访问、帧采集、后端请求和结果展示:

<!DOCTYPE html>
<html>
<head>
    <title>Level One - 简单模式</title>
</head>
<body>
    <h1>手语识别 - 简单模式</h1>
    <video id="video" width="640" height="480" autoplay></video>
    <canvas id="canvas" style="display:none;"></canvas>
    <div id="score">当前得分:0</div>

    <script>
        const video = document.getElementById('video');
        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');
        const scoreElement = document.getElementById('score');

        // 访问客户端摄像头
        navigator.mediaDevices.getUserMedia({ video: true })
            .then(stream => {
                video.srcObject = stream;
                // 每隔100ms采集一帧发送到后端
                setInterval(sendFrame, 100);
            })
            .catch(err => {
                console.error("摄像头访问失败:", err);
                alert("无法访问摄像头,请检查权限设置");
            });

        function sendFrame() {
            // 将视频帧绘制到canvas
            canvas.width = video.videoWidth;
            canvas.height = video.videoHeight;
            ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
            
            // 转为Base64并发送到后端
            const frameData = canvas.toDataURL('image/png').split(',')[1];
            fetch('/process_frame_easy', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({ frame: frameData })
            })
            .then(response => response.json())
            .then(data => {
                // 展示处理后的帧
                video.src = data.frame;
                // 更新得分
                scoreElement.textContent = `当前得分:${data.score}`;
            })
            .catch(err => console.error("处理帧失败:", err));
        }
    </script>
</body>
</html>

level-two.html只需将请求路由改为/process_frame_hard即可。

3. 调整sign_model.py的逻辑

确保easy_mode和hard_mode函数不再依赖服务器端的摄像头对象,直接使用传入的img参数即可(你的现有代码已经满足这一点,无需大改)。

关键注意事项

  • 部署时需确保DigitalOcean防火墙允许应用端口(默认5000)的HTTP/HTTPS流量
  • 生产环境建议配置HTTPS,浏览器仅允许HTTPS网站访问摄像头
  • 可根据需求调整帧传输间隔(示例为100ms),平衡延迟和服务器性能

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 08:09:55