为啥 cv2.rectangle 有时会返回 np.ndarray,而有时会返回 cv2.UMat

Posted

技术标签:

【中文标题】为啥 cv2.rectangle 有时会返回 np.ndarray,而有时会返回 cv2.UMat【英文标题】:Why cv2.rectangle sometimes return np.ndarray, while sometimes cv2.UMat为什么 cv2.rectangle 有时会返回 np.ndarray,而有时会返回 cv2.UMat 【发布时间】:2019-12-26 10:14:58 【问题描述】:

我目前正在对一些图像进行可视化,并发现了 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

代码如下:

    首先生成一张随机图片。
import numpy as np
import cv2
import copy

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

    你会发现:

    ret 是一个 np.ndarray ret 的可视化和显示一个矩形被绘制

    尝试其他方式:

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

    你会发现:

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

我真的很好奇我的代码有什么问题吗?还是背后隐藏着什么?

【问题讨论】:

有趣的行为,对我来说这听起来像是一个错误......也许你应该把它放在 OpenCV github 问题中......另外,如果你用 numpy copy 复制数据,它看起来像正常...像c = img[:, :, [2, 1, 0]].copy() @api55 我以前见过这个。主要问题是 numpy 数组在数据在内存中的布局方式方面更加灵活。 img[:, :, [2, 1, 0] 只是为同一个缓冲区创建一个视图,并设置元数据,以便以相反的顺序解释第三个轴。不幸的是,无法为该缓冲区创建等效的 cv::Mat 标头。因此,尝试使用 Mat 等效调用函数失败。然而,在 Python 包装器中完成了重载解析,因此它尝试从缓冲区创建一个 UMat... 这(因为我们得到了一个 UMat 并且没有错误消息)似乎成功了。当我尝试result = ret.get(); 并可视化结果时,我看到那里有一个白色矩形。然而,原始输入(OPs 代码中的c)不包含矩形,这表明在创建 UMat 对象时涉及到一个副本(某种预期)。 |像您一样使用ndarray.copy() 制作数组的深层副本似乎会重新排列过程中的缓冲区,以便元数据再次“正常”(与cv::Mat 兼容)。 @DanMašek 你应该把它写成答案,这听起来很完整并且很好地解释了问题:) 我知道 ndarray 的“视图”,但我猜 OpenCV 可以转换它或至少发出警告。不过很高兴知道 @api55 是的,虽然我还不能真正解释为什么它能够把它变成UMat...我得预留一些时间来理解这一点。 【参考方案1】:

我会努力回答这个问题,因为我经常偶然发现这个问题,并且在 cmets 中我看到了很多正确的东西!

OpenCV 只能处理连续数组,这意味着它们必须以某种方式在内存中布局。在对np.array 进行切片时,numpy 只是更改读取顺序以提高速度(而不是耗时的复制),并因此使其不连续(找到 here)。

@Das Masek 和@Eric 的陈述都是正确的。使用索引数组对np.array 进行切片会始终创建一个副本,如文档中的here 所述。但是,不幸的是,numpy 复制了数组,但不会将其更改回连续数组(在我看来,这似乎是不好的行为)。

解决方案是以下之一:

    copy()np.array;通过显式复制,numpy 将布局更改回连续,而不是使用索引数组切片。您可以使用a.flags 等检查阵列的flags。如果您想自动化某些东西,这显然是最昂贵的,因为您字面上每次都在复制。 对我来说更优雅的版本是使用np.ascontiguousarray()。此函数仅在数组已经不连续时才更改其布局,并且不会copy 它。

另外说明:根据documentation,所有OpenCV 绘图函数实际上都有一个None 返回值,因为它们是inplace 函数。因此我会推荐使用它们。

【讨论】:

以上是关于为啥 cv2.rectangle 有时会返回 np.ndarray,而有时会返回 cv2.UMat的主要内容,如果未能解决你的问题,请参考以下文章

python+opencv: 论文插图局部放大并拼接

opencv-函数--画框写字

利用cv2.rectangle()绘制矩形框(python)

cv2.rectangle() 调用重载方法,虽然我给出了其他参数

cv2.rectangle() 是不是有一个名为“rec”的参数?

cv2.rectangle:TypeError:由名称('厚度')和位置(4)给出的参数