opencv - Flask 视频流、多处理、CPU 使用率上限为单核级别

标签 opencv flask video-streaming flask-socketio

我正在按照 https://blog.miguelgrinberg.com/post/video-streaming-with-flask 的说明尝试使用 Flask 进行视频流传输.它正在运行,但我注意到一些事情,以及一些与多处理有关的奇怪行为。

下面是代码。后台进程从相机源获取并将帧保存在共享数组 frame 中。前台循环将帧数组编码为 .jpg 字节。 Web 服务器在后台守护线程中运行,路由 /video_feed 发送长连接多部分响应。

from multiprocessing import Process, Lock, Value, Array
from threading import Thread
import sys
import ctypes
import numpy as np
import cv2
from flask import Flask, render_template, Response, request
from flask_socketio import SocketIO, send, emit

SCREEN_WIDTH = 1280
SCREEN_HEIGHT = 720

frame = np.ctypeslib.as_array(Array(ctypes.c_uint8, SCREEN_HEIGHT * SCREEN_WIDTH * 3).get_obj()).reshape(SCREEN_HEIGHT, SCREEN_WIDTH, 3)
stopped = Value(ctypes.c_bool, False)

def get_from_stream():
    stream = cv2.VideoCapture(0)
    stream.set(cv2.CAP_PROP_FPS, 30)

    while True:
        if stopped.value:
            stream.release()
            return

        _, frame_raw = stream.read()
        frame[:] = frame_raw

Process(target=get_from_stream).start()

# web server
app = Flask(__name__)
# socketio = SocketIO(app, async_mode=None)

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

def gen():
    while True:
        yield (b'--frame\r\n'
               # b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n\r\n')
               b'Content-Type: image/jpeg\r\n\r\n' + cv2.imencode('.jpg', frame_marked)[1].tobytes() + b'\r\n\r\n')

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

# @socketio.on('quit')
# def quit():
#     stopped.value = True

thread_flask = Thread(target=app.run, kwargs=dict(debug=False, threaded=True))  # threaded Werkzeug server
# thread_flask = Thread(target=socketio.run, args=(app,), kwargs=dict(debug=False, log_output=True))  # eventlet server
thread_flask.daemon = True
thread_flask.start()

while True:
    if stopped.value:
        sys.exit(0)
    frame_bytes = cv2.imencode('.jpg', frame)[1].tobytes()
    frame_marked = frame

注释掉的代码部分显示了我的实验。我注意到如果生成器 gen() 得到已经编码的(通过主循环)frame_bytes

b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n\r\n')

而不是直接在生成器内部对其进行编码,

b'Content-Type: image/jpeg\r\n\r\n' + cv2.imencode('.jpg', frame_marked)[1].tobytes() + b'\r\n\r\n')

在双核 Macbook Pro 上,如果正在访问 /video_feed,总 CPU 使用率始终低于 100%。如果没有人访问 /video_feed,或者有人访问但生成器自己对帧进行编码,则主进程占用 90% 以上的 CPU,后台 get_from_stream 占用 20%,总共大于一个核心使用级别。

我可以确定这不是由于 CPU 使用率估计的不精确 (htop),如果有一些其他后台进程消耗大量 CPU,那么这些进程会发生相同的上限那么总的 CPU 使用率低于 100%,同样,如果正在访问 /video_feed 并且生成器获取已经编码的 frame_bytes

安装了 eventlet 并自动打开的 flask-socketio 服务器出现了同样的情况。此外,当我注释掉这些行并运行 eventlet 服务器时,在生成器中编码帧时,只能处理一个提要请求,所有其他页面访问和 socketio 消息基本上被阻止。当我重新启动程序时,那些被阻塞的 socketio 消息得到处理,因为我没有刷新页面并且它们一定已经在前端的某个地方排队。这是 eventlet 服务器独有的,因为当我使用上面的线程 Werkzeug 服务器时,可能会同时发生多个页面/提要请求,尽管每个提要自己编码帧,导致非常大的 CPU 消耗(总接近双-核心水平)。 但是,对于 eventlet 服务器,当发生上述 CPU 上限时(生成器获取 frame_bytes),多个请求(同时多个页面/提要请求),以及socketio 消息异步处理没有问题。

flask-socketio 的处理只是我试错过程中发生的相关事情。主要问题在于普通的 Werkzeug 服务器。我不想让每个 /video_feed 请求自己编码帧,对吗?让 View 函数直接获取编码字节似乎微不足道,但奇怪的是,总 CPU 上限正在影响其他进程。

最佳答案

这是一个非常有趣的问题。

在方法 #1 中,您让主线程将原始视频帧编码为 jpeg,然后视频流线程将它们传送给客户端。

在方法 #2 中,您让主线程只存储原始视频帧,而视频流线程对帧进行编码并将其传送给客户端。

当您只有一个客户端观看流时,您会认为#1 应该等于或快于#2,但是您的测试(以及我的测试)表明方法#2 在 CPU 使用方面更有效.

您没有考虑的是线程编码帧的速率。在方法#1 中,主线程尽可能快地对 jpeg 帧进行编码,而不管相机的帧速率如何,并且可以将帧速率编码的帧推送到客户端。 video feed 线程除了向客户端推送帧外没有其他事情可做,因此它可以比主线程运行得更快并推送重复的帧,这只会增加开销而没有任何好处。在方法 #2 中,网络写入会减慢 jpeg 帧的编码速度,因此整体上每单位时间编码的帧较少,占用的 CPU 较少。

我认为要使#1 尽可能高效,您需要做出的改变有两个:

  • 确保只以不高于相机帧速率的速率对 jpeg 帧进行编码。
  • 确保视频源仅以不快于主线程编码帧的速率向客户端推送新帧。

所以基本上,您需要添加信号机制。 opencv 捕获线程应在捕获新帧时向主线程发出信号。然后主线程应该对帧进行编码,并向视频馈送线程发送信号以将其传递给客户端。您可以使用 Event例如,这些信号的对象。

关于opencv - Flask 视频流、多处理、CPU 使用率上限为单核级别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45525030/

相关文章:

python - pip install sqlalchemy-migrate 莫名崩溃烧毁

python - 由于缺少 CSRF,表单验证失败

c# - 在 C# 项目中使用 FFmpeg

android - Android 后台任务的流媒体视频播放性能问题

python - 检测位于图像opencv侧面的对象

python - 使用opencv和python计算相机到物体的距离

python - 如何存储从请求收到的cookie?

javascript - 带有视频时间的链接按钮

openCV - ffmpeg H264 和 Webm 错误

java - eclipse :java.lang.UnsatisfiedLinkError