【问题标题】:Fixing inefficient image conversion from PIL image to OpenCV Mat修复从 PIL 图像到 OpenCV Mat 的低效图像转换
【发布时间】:2020-05-24 10:02:00
【问题描述】:

我正在对大小为 800x600 的实时屏幕截图流运行神经网络。由于我只有大约 3fps,所以我进行了一些故障排除,发现每个步骤大约花费了多少时间:

  • 屏幕截图:12 毫秒
  • 图像处理:280ms
  • 对象检测和框可视化:16 毫秒
  • 显示图像:0.5ms

我正在使用 mss 截屏 (documentation)。

这是没有对象检测部分的代码:

import numpy as np
import cv2
from PIL import Image
import mss
monitor = {"top": 40, "left": 0, "width": 800, "height": 600}

with mss.mss() as sct:
    while True:

        # # Screenshot:
        image = sct.grab(monitor)

        # # Image processing:
        image = Image.frombytes("RGB", image.size, image.bgra, "raw", "RGBX")
        (im_width, im_height) = image.size
        image_np = np.array(image.getdata()).reshape((im_height, im_width, 3)).astype(np.uint8)

        # # Object detection and box visualisation:
        # ...

        # # Displaying image:
        cv2.imshow("Object Detection", image_np)

有什么想法可以让这更快吗?

【问题讨论】:

  • 如果没有需要 280 毫秒的整个“图像处理”代码,将无法为您提供帮助。
  • 我在代码中添加了一些 cmets 以显示哪些行做了什么。 “图像处理:”下面的 3 行需要 280 毫秒。你是这个意思吗?
  • 单独测量每一行以查明罪魁祸首:我的猜测是 Image.frombytes() 是导致滞后的原因。
  • frombytes() 只需要大约 2ms​​,.size 需要 0.5ms。第三行导致滞后。
  • 你为什么要从屏幕抓取字节制作 PIL 图像,然后从 PIL 图像中取出所有字节并列出错误的形状,然后制作一个 Numpy 数组,然后调整它的大小?您可以使用 img = np.array(sct.grab(monitor)) 直接获取 Numpy 数组。

标签: python-3.x numpy opencv python-imaging-library


【解决方案1】:

每帧处理时间为 280 毫秒,您将获得 3-4 帧/秒。你几乎只有两个选择。

或者分享你的代码,希望我们可以改进它。

或者,使用多处理,比如 4 个 CPU 核心,将第一帧给第一个核心,第二帧给第二个,依此类推,循环,你可以每 70 毫秒得到一个帧,导致14 帧/秒。

【讨论】:

  • 我当天没有选票,但我同意。我看不到那里有什么明显的坏事。
  • 感谢您的回复。我已经添加了完整的代码,但我真的不明白为什么这会有所帮助,因为我已经在第一段代码中包含了“图像处理”行。
【解决方案2】:

问题在于您的方法是从 BGRA 图像格式开始的。这是很多数据,可能没有必要。抓取屏幕截图并将其转换为 OpenCV 图像可能有更有效的方法。 Here's an approach 在我的慢速机器上大约需要 56ms

import ctypes
import datetime
import cv2
import numpy as np

from PIL import ImageGrab


# workaround to allow ImageGrab to capture the whole screen
user32 = ctypes.windll.user32
user32.SetProcessDPIAware()

# measure running time
start_time = datetime.datetime.now()

# take a full screenshot of the desktop
image = np.array(ImageGrab.grab( bbox= (40, 0, 800, 600) ))

# convert from RGB to BGR order so that colors are displayed correctly
mat = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

# compute elapsed time
delta = datetime.datetime.now() - start_time
elapsed_time_ms = int(delta.total_seconds() * 1000)
print('* Elapsed time:', elapsed_time_ms, 'ms')

cv2.imshow('mat', mat)
cv2.waitKey()

【讨论】:

  • 如果您不需要图像为 BGR 格式,您可以通过去掉 cv2.cvtColor() 并将其保留为 RGB 并使此解决方案更快。
  • 运行你的答案,结合自己尝试一些事情解决了我的问题。我用 mss 模块替换了 ImageGrab 来截屏(在我的机器上是 100 毫秒 vs 10 毫秒)。然后我使用了 RGBA2RGB,因为我遇到了一个错误:无法为形状为 '(?, ?, ?, 3)' 的张量“image_tensor:0”提供形状 (1, 600, 800, 4) 的值。现在它以大约 30fps 的速度运行。
  • 顺便说一句,您正在运行哪些硬件?您提到了 56 毫秒,但在我的笔记本电脑(i9-9980hk,rtx2080 max q)上,它需要大约两倍。我怀疑 ImageGrab 受到集成显卡 (UHD Graphics 630) 的限制。
  • 采用 Intel i5-8250U @1.6GHz 1.8GHz 的笔记本电脑。显卡是 Intel UHD Graphics 620。我们知道您提出的最终和理想的实现可能包含最初未在此线程中提及的改进,但如果您反思谁帮助您确定问题然后提供替代解决方案......您可能会得出结论这个答案值得选择它作为官方问题解决者的复选框。
  • 您的回答确实对我有所帮助,这就是我对其投赞成票的原因,但如果我将标题编辑为“将屏幕截图流输入对象检测的更快方法”,此页面对未来的访问者不会更有帮助神经网络”并将我自己的答案标记为解决方案?
【解决方案3】:

使用这些行而不是我第一篇文章中的“图像处理:”行解决了我的问题:

image = sct.grab(monitor)
image_np = np.array(image)
image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)

我之前已经尝试过只使用前 2 行,但我收到了这个错误:

ValueError: Cannot feed value of shape (1, 600, 800, 4) for Tensor 'image_tensor:0', which has shape '(?, ?, ?, 3)'

我没有想到将图像从 rgba 转换为 rgb 可以解决这个问题。我现在的速度约为 30fps。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-12-17
    • 1970-01-01
    • 2017-08-31
    • 2020-09-15
    • 1970-01-01
    • 2014-05-21
    • 1970-01-01
    相关资源
    最近更新 更多