为什么cv2.rectangle有时返回np.ndarray,而有时返回cv2.UMat?

16

我目前正在处理图像可视化,发现了opencv的cv2.rectangle的一些奇怪行为:

  • 当输入的图像是一个np.ndarray,比如arr时,cv2.rectangle()会返回一个np.ndarray,并在arr上绘制矩形。

  • 当输入的图像是arr的某个变体,比如arr[:, :, [2, 0, 1]],cv2.rectangle()将返回一个cv2.UMat,并且不会绘制矩形。

我当前的环境是:

  • Python 3.7
  • Opencv 4.1

以下是代码:

  1. 首先生成一个随机图像。
import numpy as np
import cv2
import copy

img = np.random.randint(0, 255, (100, 120, 3)).astype("uint8")
  1. 现在添加一个矩形
a = copy.deepcopy(img)
ret = cv2.rectangle(a, (0, 0), (10, 10), color=(255, 255, 255), thickness=2)
  1. 你会发现:

    • ret 是一个 np.ndarray
    • ret 的可视化,并且画了一个矩形
  2. 尝试另一种方式:

b = copy.deepcopy(img)
c = b[:, :, [2, 1, 0]]
ret = cv2.rectangle(c, (0, 0), (10, 10), color=(255, 255, 255), thickness=2)
  1. 你会发现:

    • ret 是一个 cv2.UMat
    • 可视化 ret 或 c 显示没有矩形被绘制

我非常好奇我的代码是否有问题?或者有什么隐藏的东西?


1
有趣的行为,听起来像是一个 bug... 也许你应该把它放在 OpenCV 的 Github 问题里... 此外,如果你使用 numpy 的 copy 函数复制数据,它会像正常情况一样工作... 就像 c = img[:, :, [2, 1, 0]].copy() - api55
5
我曾经看过这个问题。主要问题在于NumPy数组相对于内存中数据布局的灵活性更高。img[:, :, [2, 1, 0]只是创建到同一缓冲区的视图,并设置元数据,以便第三个轴以相反的顺序进行解释。不幸的是,无法为该缓冲区创建等效的cv::Mat标头。因此,尝试使用等效的Mat调用函数失败了。但是,在Python包装器中有超载分辨率,因此它尝试从缓冲区创建一个UMat... - Dan Mašek
2
由于我们得到了一个UMat而没有错误消息,这似乎是成功的。当我尝试result = ret.get();并可视化结果时,我看到那里有一个白色矩形。然而,原始输入(OP代码中的c)不包含矩形,这表明在创建UMat对象时涉及复制(有点意料之中)。| 使用ndarray.copy()进行数组的深度复制,就像您所做的那样,似乎会重新排列缓冲区,以便元数据再次“正常”(与cv :: Mat兼容)。 - Dan Mašek
@DanMašek,你应该把它写成一个答案,听起来非常完整,很好地解释了问题 :) 我知道 ndarray 的“view”,但我猜想 OpenCV 可以将其转换或者至少发出警告。不过还是很好知道的。 - api55
@api55 是的,虽然我现在还不能解释为什么它能将其转换为 UMat... 我需要留些时间来理解这一点。 - Dan Mašek
"img[:, :, [2, 1, 0] 只是创建了对同一缓冲区的视图" - 这不是真的 - 对于 img[:,:,2::-1],您会得到这种行为,但是对于使用索引数组进行索引,它总是会复制。 - Eric
1个回答

2
我会尽力回答这个问题,因为我经常遇到这个问题,在评论中我看到了很多正确的东西!
OpenCV 只能处理连续的数组,这意味着它们必须以某种方式排列在内存中。当切片一个 np.array 时,numpy 只是改变读取顺序以增加速度(而不是耗费时间进行复制),因此使其成为非连续的(在此处找到here)。
@Das Masek 和 @Eric 的陈述都是正确的。使用索引数组来切片一个 np.array 总是会创建一个副本,如here所述。然而,不幸的是,numpy 复制了数组,但不会将其改回连续的数组(对我来说似乎是不好的行为)。
解决方案有以下几种:
  1. 使用 copy() 函数复制 np.array 数组;通过显式复制,numpy 会将数组的布局更改为连续的,而使用索引数组切片则不会这样做。您可以使用 a.flags 等命令检查数组的标志。如果要自动化处理某些操作,则这显然是最昂贵的,因为每次都需要 字面上 复制。
  2. 对于我来说,更优雅的版本是使用 np.ascontiguousarray() 函数。此函数仅在数组已非连续时才更改数组的布局,并且不会进行复制。

另外一件事:根据文档,所有的OpenCV绘图函数实际上都没有返回值,因为它们是原地修改的函数。因此我建议使用它们时要注意这一点。

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