PYNQ-Z1实时人脸检测卡顿

开发板PYNQ-Z1
代码如下,严重掉帧,会延迟大约三四秒,请问应该怎么优化?
用多进程或VDMA的优化方法要怎么实现?


import cv2
from pynq.overlays.base import BaseOverlay
from pynq.lib.video import *
print("base.bit")
base = BaseOverlay("base.bit")
# monitor configuration: 640*480 @ 60Hz
Mode = VideoMode(640, 480, 24)
hdmi_out = base.video.hdmi_out
hdmi_out.configure(Mode, PIXEL_BGR)
hdmi_out.start()

# camera (input) configuration
frame_in_w = 640
frame_in_h = 480

videoIn = cv2.VideoCapture(0)
videoIn.set(cv2.CAP_PROP_FRAME_WIDTH, frame_in_w);
videoIn.set(cv2.CAP_PROP_FRAME_HEIGHT, frame_in_h);
print("capture device is open: " + str(videoIn.isOpened()))
face_cascade = cv2.CascadeClassifier(
    '/home/xilinx/jupyter_notebooks/base/video/data/'
    'haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier(
    '/home/xilinx/jupyter_notebooks/base/video/data/'
    'haarcascade_eye.xml')


while True:
    ret, frame_vga = videoIn.read()
    if ret==0:
        break
    np_frame = frame_vga
    gray = cv2.cvtColor(frame_vga, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, 1.3, 5)

    for (x,y,w,h) in faces:
        cv2.rectangle(np_frame,(x,y),(x+w,y+h),(255,0,0),2)
        roi_gray = gray[y:y+h, x:x+w]
        roi_color = np_frame[y:y+h, x:x+w]

        eyes = eye_cascade.detectMultiScale(roi_gray)
        for (ex,ey,ew,eh) in eyes:
            cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)
    
    if (ret):
        outframe = hdmi_out.newframe()
        outframe[:] = np_frame
        hdmi_out.writeframe(outframe)
    else:
        raise RuntimeError("Error while reading from camera.")

你可以尝试以下几种优化措施:

  1. 调整视频帧率。降低帧率可以减少计算压力,从而提高性能。可以使用cv2.CAP_PROP_FPS属性来设置帧率,例如videoIn.set(cv2.CAP_PROP_FPS, 30)可以设置帧率为30fps。
  2. 优化人脸检测算法。你可以尝试使用更高效的算法,例如MTCNN或RetinaFace,这些算法可以在保持准确性的同时提高性能。也可以尝试调整detectMultiScale方法的参数以获取更好的性能。
  3. 降低视频分辨率。降低分辨率可以减少计算压力,从而提高性能。你可以尝试将frame_in_w和frame_in_h设置为更小的值,例如320x240或160x120。
  4. 使用多线程或多进程。将视频处理和输出分别放在不同的线程或进程中,可以提高性能和稳定性。可以使用Python的multiprocessing或threading模块来实现。
    下面是优化后的代码示例:
import cv2
import time
from pynq.overlays.base import BaseOverlay
from pynq.lib.video import *
print("base.bit")
base = BaseOverlay("base.bit")
# monitor configuration: 640*480 @ 60Hz
Mode = VideoMode(640, 480, 24)
hdmi_out = base.video.hdmi_out
hdmi_out.configure(Mode, PIXEL_BGR)
hdmi_out.start()

# camera (input) configuration
frame_in_w = 640
frame_in_h = 480

videoIn = cv2.VideoCapture(0)
videoIn.set(cv2.CAP_PROP_FRAME_WIDTH, frame_in_w);
videoIn.set(cv2.CAP_PROP_FRAME_HEIGHT, frame_in_h);
print("capture device is open: " + str(videoIn.isOpened()))
face_cascade = cv2.CascadeClassifier(
    '/home/xilinx/jupyter_notebooks/base/video/data/'
    'haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier(
    '/home/xilinx/jupyter_notebooks/base/video/data/'
    'haarcascade_eye.xml')

frame_rate = 10 # 每秒处理10帧视频
delay = 1/frame_rate

while True:
    ret, frame_vga = videoIn.read()
    if ret==0:
        break
    np_frame = frame_vga
    gray = cv2.cvtColor(frame_vga, cv2.COLOR_BGR2GRAY)

    # 只检测视频帧的中心区域
    x, y, w, h = int(frame_in_w/4), int(frame_in_h/4), int(frame_in_w/2), int(frame_in_h/2)
    gray = gray[y:y+h, x:x+w]
    faces = face_cascade.detectMultiScale(gray, 1.3, 5)

    for (x,y,w,h) in faces:
        cv2.rectangle(np_frame,(x+int(frame_in_w/4),y+int(frame_in_h/4)),
            (x+int(frame_in_w/4)+w,y+int(frame_in_h/4)+h),(255,0,0),2)
        roi_gray = gray[y:y+h, x:x+w]
        roi_color = np_frame[y+int(frame_in_h/4):y+int(frame_in_h/4)+h, 
            x+int(frame_in_w/4):x+int(frame_in_w/4)+w]

        eyes = eye_cascade.detectMultiScale

需要添加添加VDMA缓存Sensor

要使用多线程优化上述代码,首先要使用多线程库,比如Python的多线程库threading,将代码中的任务分解到多个线程中。比如,可以将图像处理任务分解到一个线程中,将人脸检测任务分解到另一个线程中,然后将结果输出到HDMI输出。另外,还可以使用多线程将图像处理任务分解到多个芯片中,以提高处理速度。
为了优化代码,可以使用VDMA来实现视频输入和输出,以减少CPU负载。可以使用PYNQ库中的Video_in和Video_out类来实现,这样可以实现视频流的高效处理。此外,还可以使用OpenCV的硬件加速功能,比如使用硬件加速的haar级联分类器来提高检测速度。

由于您的代码没有使用多进程或者VDMA技术,掉帧和延迟的问题可能与以下因素有关:

  1. 相机帧率与HDMI输出帧率不一致:您可以使用setFPS函数来设置相机的帧率,与HDMI输出帧率保持一致。
  2. HDMI输出分辨率过高:您可以尝试降低输出分辨率,例如降低到320x240。
  3. HDMI输出带宽过高:您可以尝试减少帧数据的大小,例如将每个像素的位深度从24位降低到16位。

如果您想使用多进程或VDMA技术来优化代码,可以考虑以下方法:

  1. 多进程:使用Python中的multiprocessing模块来创建一个进程池,并使用这个进程池来处理相机捕获到的每一帧数据。这样可以将图像处理和HDMI输出分离到不同的进程中,从而减少主进程的负担,降低掉帧和延迟的可能性。
  2. VDMA:使用Xilinx提供的VDMA IP核来实现DMA数据传输。VDMA可以通过AXI接口和PL逻辑进行通信,从而实现高效的图像数据传输。您可以在PL逻辑中创建一个VDMA IP核,并将它与HDMI输出端口相连,从而将图像数据从PS传输到HDMI输出端口。同时,您可以使用多进程来处理图像数据,从而提高处理速度和系统稳定性。

如果您决定使用多进程或VDMA技术来优化代码,可以参考以下教程来实现:

  1. 多进程:https://docs.python.org/3/library/multiprocessing.html
  2. VDMA:https://www.xilinx.com/support/documentation/ip_documentation/vdma/v6_3/pg020_vdma.pdf

根据您提供的代码,您正在捕捉USB摄像头并将视频帧显示在PYNQ-Z1板子的HDMI输出上。掉帧和延迟可能与以下因素有关:

USB带宽:USB总线上的带宽限制可能导致掉帧。因为在您的代码中,图像是通过USB接口从摄像头获取的,然后传输到HDMI接口。您可以尝试减少图像大小或调整帧速率来缓解此问题。
图像处理复杂度:您的代码包含对图像的检测和分类。这些操作可能会导致计算资源不足,从而导致延迟和掉帧。您可以尝试将处理移动到FPGA上来解决此问题。
实现方式:多进程和VDMA都是优化延迟和掉帧的有效方式。使用多进程,您可以将图像捕获和处理拆分为不同的进程。使用VDMA,您可以在处理和输出之间直接传输数据,而无需将它们复制到内存中。
下面是使用多进程的示例代码,将图像处理拆分为两个进程。一个进程负责捕获图像并将其放入队列中,另一个进程从队列中获取图像并进行处理:

import cv2
from pynq.overlays.base import BaseOverlay
from pynq.lib.video import *
from multiprocessing import Process, Queue

print("base.bit")
base = BaseOverlay("base.bit")

# monitor configuration: 640*480 @ 60Hz
Mode = VideoMode(640, 480, 24)
hdmi_out = base.video.hdmi_out
hdmi_out.configure(Mode, PIXEL_BGR)
hdmi_out.start()

# camera (input) configuration
frame_in_w = 640
frame_in_h = 480

videoIn = cv2.VideoCapture(0)
videoIn.set(cv2.CAP_PROP_FRAME_WIDTH, frame_in_w);
videoIn.set(cv2.CAP_PROP_FRAME_HEIGHT, frame_in_h);
print("capture device is open: " + str(videoIn.isOpened()))

face_cascade = cv2.CascadeClassifier(
    '/home/xilinx/jupyter_notebooks/base/video/data/'
    'haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier(
    '/home/xilinx/jupyter_notebooks/base/video/data/'
    'haarcascade_eye.xml')

def read_frames(input_queue):
    while True:
        ret, frame_vga = videoIn.read()
        if not ret:
            input_queue.put(None)
            break
        input_queue.put(frame_vga)

def process_frames(input_queue, output_queue):
    while True:
        frame_vga = input_queue.get()
        if frame_vga is None:
            output_queue.put(None)
            break
        np_frame = frame_vga
        gray = cv2.cvtColor(frame_vga, cv2.COLOR_BGR2GRAY)
        faces = face_cascade.detectMultiScale(gray, 1.3, 5)

        for (x,y,w,h) in faces:
            cv2.rectangle(np_frame,(x,y),(x+w,y+h),(255,0,0),2)
            roi_gray = gray[y:y+h, x:x+w]
            roi_color = np_frame[y:y+h, x:x+w]

            eyes = eye_cascade.detectMultiScale(roi_gray)
            for (ex,ey,ew,eh) in eyes:
                cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)
        
        output_queue.put(np_frame)

def write_frames(output_queue):
    while True:
        np_frame = output_queue.get()
        if np_frame is None:
            break
        outframe = hdmi_out.newframe()
        outframe[:] = np_frame
        hdmi_out.writeframe(outframe)

# create queues for inter-process communication
input_queue = Queue()
output_queue = Queue()

# create and start the processes
processes = [
    Process(target=read_frames, args=(input_queue,)),
    Process(target=process_frames, args=(input_queue, output_queue)),
    Process(target=write_frames, args=(output_queue,))
]
for process in processes:
    process.start()

# wait for all the processes to finish
for process in processes:
    process.join()

# release the resources
videoIn.release()
hdmi_out.stop()
del hdmi_out

上述代码中,将处理每个视频帧的过程拆分成了三个步骤,并用不同的进程来执行这些步骤。read_frames()函数从相机读取视频帧,并将它们放入输入队列中。process_frames()函数从输入队列中获取视频帧并执行面部识别和绘制

您的代码存在掉帧的问题,这可能是由于数据传输速度的限制或CPU计算能力不足导致的。您可以采取以下措施来优化代码:

1 使用多进程并行化:
使用Python的multiprocessing模块可以将不同的处理任务分配给不同的进程来并行处理,以提高整体处理速度。您可以将读取帧和处理帧的任务分别分配给两个进程,以减轻CPU的负担。这里需要注意的是,由于PYNQ开发板中的CPU核心数量有限,所以不要同时启动过多的进程,否则可能会导致进程之间的资源竞争和调度延迟。

2 使用VDMA模块:
PYNQ开发板提供了Xilinx的VDMA(Video Direct Memory Access)模块,它可以在FPGA中实现高带宽的数据传输,减轻CPU的负担。您可以将读取帧和处理帧的任务分别分配给CPU和FPGA,以实现高效的数据传输和处理。

下面是使用多进程的示例代码:

import cv2
from pynq.overlays.base import BaseOverlay
from pynq.lib.video import *
from multiprocessing import Process, Queue

def read_frame(queue):
    frame_in_w = 640
    frame_in_h = 480
    videoIn = cv2.VideoCapture(0)
    videoIn.set(cv2.CAP_PROP_FRAME_WIDTH, frame_in_w);
    videoIn.set(cv2.CAP_PROP_FRAME_HEIGHT, frame_in_h);
    while True:
        ret, frame_vga = videoIn.read()
        if ret==0:
            break
        queue.put(frame_vga)

def process_frame(queue):
    hdmi_out = base.video.hdmi_out
    hdmi_out.configure(Mode, PIXEL_BGR)
    hdmi_out.start()
    face_cascade = cv2.CascadeClassifier(
        '/home/xilinx/jupyter_notebooks/base/video/data/'
        'haarcascade_frontalface_default.xml')
    eye_cascade = cv2.CascadeClassifier(
        '/home/xilinx/jupyter_notebooks/base/video/data/'
        'haarcascade_eye.xml')
    while True:
        if not queue.empty():
            frame_vga = queue.get()
            np_frame = frame_vga
            gray = cv2.cvtColor(frame_vga, cv2.COLOR_BGR2GRAY)
            faces = face_cascade.detectMultiScale(gray, 1.3, 5)
            for (x,y,w,h) in faces:
                cv2.rectangle(np_frame,(x,y),(x+w,y+h),(255,0,0),2)
                roi_gray = gray[y:y+h, x:x+w]
                roi_color = np_frame[y:y+h, x:x+w]
                eyes = eye_cascade.detectMultiScale(roi_gray)
                for (ex,ey,ew,eh) in eyes:
                    cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)
            outframe = hdmi_out.newframe()
            outframe[:] = np_frame
            hdmi_out.writeframe(outframe)
        else:
            time.sleep(0.01)

if __name__ == '__main__':
    print("base.bit")
    base = Base

如果对您有帮助,请给与采纳,谢谢。

可以尝试使用多进程或VDMA的优化方法来提高程序的性能。
多进程可以将任务分解到多个进程中,从而提高程序的执行效率。
VDMA可以将视频数据从内存中读取,从而减少CPU的负担,提高程序的执行效率。