Flask 应用使用 opencv 运行非常缓慢

3

我有一个Flask应用程序,它从相机读取帧并将其流式传输到网站。

Camera.py

from threading import Thread
from copy import deepcopy

import queue
import cv2

class Camera(Thread):
    def __init__(self, cam, normalQue, detectedQue):
        Thread.__init__(self)
        self.__cam = cam
        self.__normalQue = normalQue
        self.__detectedQue = detectedQue
        self.__shouldStop = False
        
    def __del__(self):
        self.__cam.release()
        print('Camera released')
        
    def run(self):
        while True:
            rval, frame = self.__cam.read()

            if rval:
                frame = cv2.resize(frame, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA)
                _, jpeg = cv2.imencode('.jpg', frame)

                self.__normalQue.put(jpeg.tobytes())
                self.__detectedQue.put(deepcopy(jpeg.tobytes()))

            if self.__shouldStop:
                break

    def stopCamera(self):
        self.__shouldStop = True

从你所看到的,我只是读取帧,改变大小并将其存储在两个不同的队列中。没有太复杂的东西。我还有两个类负责MJPEG流:

NormalVideoStream.py

from threading import Thread

import traceback
import cv2

class NormalVideoStream(Thread):
    def __init__(self, framesQue):
        Thread.__init__(self)
        self.__frames = framesQue
        self.__img = None

    def run(self):
        while True:
            if self.__frames.empty():
                continue

            self.__img = self.__frames.get()

    def gen(self):
        while True:
            try:
                if self.__img is None:
                    print('Normal stream frame is none')
                    continue

                yield (b'--frame\r\n'
                    b'Content-Type: image/jpeg\r\n\r\n' + self.__img + b'\r\n')
            except:
                traceback.print_exc()
                print('Normal video stream genenation exception')

and

DetectionVideoStream.py

from threading import Thread

import cv2
import traceback

class DetectionVideoStream(Thread):
    def __init__(self, framesQue):
        Thread.__init__(self)
        
        self.__frames = framesQue
        self.__img = None
        self.__faceCascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')

    def run(self):
        while True:
            if self.__frames.empty():
                continue
            
            self.__img = self.__detectFace()

    def gen(self):
        while True:
            try:
                if self.__img is None:
                    print('Detected stream frame is none')

                yield (b'--frame\r\n'
                    b'Content-Type: image/jpeg\r\n\r\n' + self.__img + b'\r\n')
            except:
                traceback.print_exc()
                print('Detection video stream genenation exception')
    
    def __detectFace(self):
        retImg = None

        try:
            img = self.__frames.get()

            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

            faces = self.__faceCascade.detectMultiScale(gray, 1.1, 4)

            for (x, y, w, h) in faces:
                cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)

            (_, encodedImage) = cv2.imencode('.jpg', img)

            retImg = encodedImage.tobytes()
        except:
            traceback.print_exc()
            print('Face detection exception')

        return retImg

从两个流中可以看出,我正在读取相机帧并在无限循环中将其插入队列。这两个类还有一个gen()方法,用于生成帧并将其发送到自身的网站。唯一的区别是,在检测流中,我还进行了人脸识别。

现在,在我的主文件中:

main.py

from flask import Blueprint, render_template, Response, abort, redirect, url_for
from flask_login import login_required, current_user
from queue import Queue
from . import db
from .Camera import Camera
from .NormalVideoStream import NormalVideoStream
from .DetectionVideoStream import DetectionVideoStream
from .models import User

import cv2

main = Blueprint('main', __name__)

# Queues for both streams
framesNormalQue = Queue(maxsize=0)
framesDetectionQue = Queue(maxsize=0)
print('Queues created')

# RPi camera instance
camera = Camera(cv2.VideoCapture(0), framesNormalQue, framesDetectionQue)
camera.start()
print('Camera thread started')

# Streams
normalStream = NormalVideoStream(framesNormalQue)
detectionStream = DetectionVideoStream(framesDetectionQue)
print('Streams created')

normalStream.start()
print('Normal stream thread started')
detectionStream.start()
print('Detection stream thread started')

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

@main.route('/profile', methods=["POST", "GET"])
def profile():
    if not current_user.is_authenticated:
        abort(403)

    return render_template('profile.html', name=current_user.name, id=current_user.id, detectionState=current_user.detectionState)

@main.route('/video_stream/<int:stream_id>')
def video_stream(stream_id):
    if not current_user.is_authenticated:
        abort(403)

    print(f'Current user detection: {current_user.detectionState}')

    global detectionStream
    global normalStream

    stream = None

    if current_user.detectionState:
        stream = detectionStream
        print('Stream set to detection one')
    else:
        stream = normalStream
        print('Stream set to normal one')

    return Response(stream.gen(), mimetype='multipart/x-mixed-replace; boundary=frame')

@main.route('/detection')
def detection():
    if not current_user.is_authenticated:
        abort(403)

    if current_user.detectionState:
        current_user.detectionState = False
    else:
        current_user.detectionState = True

    user = User.query.filter_by(id=current_user.id)
    user.detectionState = current_user.detectionState

    db.session.commit()

    return redirect(url_for('main.profile', id=current_user.id, user_name=current_user.name))

@main.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404

@main.errorhandler(403)
def page_forbidden(e):
    return render_template('403.html'), 403

我正在全局创建相机(camera)、问题(ques)和流(streams)对象。当用户登录网站时,他将能够看到实时视频流,并有一个按钮可以更改当前呈现的流。

整个项目运行良好,唯一的问题是:当我将流更改为检测流时,会有巨大的延迟(大约10/15秒),这使得整个应用程序无法正常工作。我尝试自己搜索错误/优化,但找不到任何东西。我特意在单独的线程上运行所有东西以减轻应用程序的负担,但似乎这还不够。1-2秒的延迟水平是可以接受的,但不是10+秒。因此,各位,也许您们能看到某些错误?或者知道如何进行优化吗?

此外,需要提及的是,整个应用程序都在RPi 4B 4GB上运行,我在桌面上访问网站。默认服务器已更改为Nginx和Gunicorn。从我所看到的,当应用程序工作时,Pi的CPU使用率达到了100%。在测试默认服务器时,行为相同。猜测1.5 GHz的CPU有足够的功率来使其运行更加流畅。


我以前尝试在树莓派上使用Open-Cv,但由于某些原因速度非常慢,因此我建议改用像picamera模块这样专为树莓派设计的东西。 - AyanSh
嗨@gawron103。你解决了吗?我有一个类似的问题:https://dev59.com/3MDqa4cB1Zd3GeqPnN4W - Kristada673
@gawron103,你解决了延迟问题吗? - Roy Amoyal
4个回答

1

我发现导致帧速变慢的主要原因是您使用了更高分辨率和帧速率。

为了解决这个问题,您可以将分辨率更改为640宽度和480高度,帧速率为30或更低,最高可达5fps(如果仅需要面部检测),并且可以通过OpenCV中的调整大小实现(cv2.resize()函数)将fx和fy的缩放因子设置为0.25。如果您不想使用更高分辨率流,请这样做。在opencv中可以平稳运行。我想尝试一下那个VideoStream代码(来自imutils),因为Adrian Rosebrock of PyImageSearch也使用了它。我以后会在其他项目中使用它。

供参考,以下是我的代码片段。特别感谢Adrian Rosebrock和ageitey face_recognition,他们的代码帮助我创建了它。

class Camera(object):

    SHRINK_RATIO = 0.25
    FPS = 5
    FRAME_RATE = 5
    WIDTH = 640
    HEIGHT = 480

def __init__(self):
    """ initializing camera with settings """
    self.cap = cv2.VideoCapture(0)
    self.cap.set(3,Camera.WIDTH)
    self.cap.set(4,Camera.HEIGHT)
    self.cap.set(5,Camera.FPS)
    self.cap.set(7,Camera.FRAME_RATE)

def get_frame(self):
""" get frames from the camera """
success, frame = self.cap.read()
    
    if success == True:
       
        # Resizing to 0.25 scale
        
        rescale_frame = cv2.resize(frame, None, fx= Camera.SHRINK_RATIO, fy=Camera.SHRINK_RATIO)
        cascPath = "haarcascade_frontalface_default.xml"
        faceCascade = cv2.CascadeClassifier(cascPath)
        gray = cv2.cvtColor(rescale_frame, cv2.COLOR_BGR2GRAY)

        # detecting faces 
        faces = faceCascade.detectMultiScale(
            gray,
            scaleFactor=1.1,
            minNeighbors=5,
            minSize=(30,30)
        )

        # Draw a rectangle around the faces
        if len(faces) != 0:
            for (x, y, w, h) in faces:
                x *=4
                y *=4
                w *=4
                h *=4
                # Draw rectangle across faces in current frame
                cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
        # return frame outside if-statement
        return frame

同时请记住,JPEG是加速编解码器,请使用:

cv2.imencode(".jpg",frame)[1].tobytes()

1

我曾经遇到的一个问题是关于编码和解码的。像OpenCV的编码器太慢了,所以建议使用来自simplejpeg的编码器。使用pip3 install simplejpeg安装,使用simplejpeg.encode_jpeg()代替cv2.imencode()


1
One option is using VideoStream VideoCapture速度慢的原因在于它花费大量时间读取并解码下一帧。当下一帧被读取、解码并返回时,OpenCV应用程序完全被阻塞。而VideoStream通过使用队列结构来解决这个问题,并同时读取、解码和返回当前帧。VideoStream支持PiCamerawebcam。您所需要做的就是:安装imutils
  • 对于虚拟环境: pip install imutils


  • 对于Anaconda环境: conda install -c conda-forge imutils

    1. main.py中初始化VideoStream

  • import time
    from imutils.video import VideoStream
    
    vs = VideoStream(usePiCamera=True).start()  # For PiCamera
    # vs = VideoStream(usePiCamera=False).start() # For Webcam
    
    camera = Camera(vs, framesNormalQue, framesDetectionQue)
    
    1. In your Camera.py

  • run(self)方法中:

* ```python
  def run(self):
      while True:
          frame = self.__cam.read()
          frame = cv2.resize(frame, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA)
            _, jpeg = cv2.imencode('.jpg', frame)

            self.__normalQue.put(jpeg.tobytes())
            self.__detectedQue.put(deepcopy(jpeg.tobytes()))

        if self.__shouldStop:
            break
  ```

1

针对你的问题,我并不感到意外。通常情况下,“检测”会消耗大量的计算时间,因为实施级联分类算法是一个要求计算的任务。 我找到了一篇比较级联分类算法性能的来源 link

一个简单的解决方案是,在处理检测时降低帧率。一个简单实现以降低性能需求可能会像跳过计数器这样的东西。

frameSkipFactor = 3 # use every third frame
frameCounter = 0

if (frameCounter%frameSkipFactor==0):
         #process
else:
         print("ignore frame", frameCounter)

frameCounter+=1

尽管如此,您仍将会有一定的滞后,因为检测计算总是会产生时间偏移。如果您计划构建一个“实时”分类摄像头系统,请寻找另一类更适合此用例的分类算法。我在这里关注了一次讨论: 实时分类算法
另一个解决方案可能是使用比树莓派更大的“硬件锤”,例如通过Cuda等GPU实现算法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接