如何从字节数组初始化 RGBA cv::Mat?

Posted

技术标签:

【中文标题】如何从字节数组初始化 RGBA cv::Mat?【英文标题】:How can I initialise an RGBA cv::Mat from a byte array? 【发布时间】:2019-05-29 09:51:20 【问题描述】:

我正在通过网络(来自 Python)发送图像,并希望在接收端(在 C++ 中)从它们创建 OpenCV Mats。

它们是这样创建的:

image = self.camera.capture_image()   # np.array of dtype np.uint8
h, w, c = image.shape   # 4 channels
image = np.transpose(image, (2, 0, 1)) # transpose because channels come first in OpenCV (?)
image = np.ascontiguousarray(image, dtype='>B')  # big-endian bytes
bytess = image.tobytes(order='C')

在此之后,我应该有一个数组,其中 3 个维度被展平,以便为每个通道附加单独的行,然后附加通道以形成最终的字节缓冲区。我已验证我的理解是正确的,以下成立

bytess[channel*height*width + i*wwidth + j] == image[channel, i, j]

[我觉得上面的部分其实并不重要,因为如果不正确,我会得到一个错误显示的图像,但至少我会有一个图像,比我现在更进一步。]

现在我正在尝试这样做:

char* pixel_data = … // retrieve array of bytes from message
// assume height, width and channels are known
const int sizes[3] = channels, width, height;
const size_t steps[3] = (size_t)height * (size_t)width, (size_t)height;
cv::Mat image(3, sizes, CV_8UC1, pixel_data, steps);

因此,我创建了一个具有三个维度的矩阵,其中元素类型为byte。我不太确定我是否正确确定了steps,但我认为它与documentation 匹配。

但是运行它只会崩溃

error: (-5:Bad argument) Unknown array type in function 'cvarrToMat'

将 RGBA(或 OpenCV 的 BGRA)图像序列化为字节缓冲区并使用 C++ API 从中创建cv::Mat 的正确方法是什么?

【问题讨论】:

我稍后会考虑这个问题,但目前我做了一些相关的事情,可能会给你一个想法......***.com/a/55313342/2836621 您确定您有足够的网络带宽来执行此操作吗?即使是 640x480 的 RGB 图像也接近 1MB,而 100Mb/s 的以太网最高可以提供 8MB/s,那么每秒 8 帧是否足够?还是您需要 JPEG 编码并获得 20 倍? @MarkSetchell 它是通过本地主机发送的,所以这个问题现在并不重要。我完全有兴趣让反序列化工作。 您的发送和接收代码在哪里? 为什么所有的恶作剧,numpy 数组的内存布局和cv::Mat 是相同的,您只需要获取底层数据缓冲区并为其创建一个具有相同尺寸的cv::Mat 标头和元素数据类型。最小的开销,这就是 Python OpenCV 绑定的工作方式。 【参考方案1】:

我有一个解决方案,可以绕过这个问题。这一行在这里:

cv::Mat image(3, sizes, CV_8UC1, pixel_data, steps);

假设我可以通过单个字节传递三个维度的大小,但我无法做到这一点。

改为使用different constructor

cv::Mat image(height, width CV_8UC4, pixel_data);

我可以将图像视为二维但具有矢量数据类型(4 字节元素大小而不是标量字节)。如果pixel_data 指针的布局正确,则此方法有效。

正确的布局并没有真正明确记录,但可以从official tutorials 之一推导出来

因此,数据的存储方式是一行接一行,一行的每个元素都拆分为n_channels 元素。使用CV_8UC4 等数据类型使矩阵在原始数据数组中的每个位置读取 4 个字节,并将指针前进 4 个字节。

所以在这种情况下,我只需将 numpy 数组重新排列为适当的序列:将行附加在一起,但将通道交错。我是这样做的,但我希望有一种方法不循环。

def array_to_cv_bytes(ary):
    assert ary.ndim == 3, 'Array must have 3 dimensions'
    h, w, c = ary.shape
    ary = ary[..., (2, 1, 0, 3)]
    output = np.empty(h * c * w, dtype=np.uint8)
    for channeld_idx in range(c):
        output[channeld_idx::c] = ary[..., channeld_idx].reshape(h*w)
    return output.tobytes(order='C')

【讨论】:

“正确的布局并没有真正明确记录”——它就在cv::Mat documentation的开头... 是的,没错,我也读过,但这并没有帮助我通过多个渠道走另一条路。也许我只是不明白。 我认为主要让我感到困惑的是元素大小的额外复杂性,它可能不是标量并且寻址公式忽略了(我猜它隐含在 + 运算符中,就像在 C 中一样)

以上是关于如何从字节数组初始化 RGBA cv::Mat?的主要内容,如果未能解决你的问题,请参考以下文章

如何将从 C++ 发送的 cv::MAT 字节数组转换为 Java 中的位图?

如何将多个(超过 4 个)`CV::Mat` 图像`Size(M, N), CV_8UC1` 加入到一个 RGBA 图像中?

如何使用模板函数从缓冲区(T* 数据数组)创建 cv::Mat?

如何从函数返回多维 Mat 数组

cv::Mat 使用数组初始化

从 3d 数组创建 cv::Mat 图像