问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

C++ OpenCV 图像数据格式转换:从HWC到CHW

创作时间:
作者:
@小白创作中心

C++ OpenCV 图像数据格式转换:从HWC到CHW

引用
1
来源
1.
https://www.cnblogs.com/harrymore/p/18380596

在使用OpenCV处理图像时,经常会遇到需要将图像数据从HWC(Height-Width-Channel)格式转换为CHW(Channel-Height-Width)格式的需求,特别是在深度学习模型的输入要求中。本文将详细介绍这种转换的具体实现方法。

1. OpenCV的读取格式

众所周知,OpenCV读取图片后,在内存中数据是以HWC的顺序进行排列的,但是在深度学习模型中,一般需要将其转为CHW格式(准确来说是NCHW)再进行推断。

在Python中,OpenCV读取后的数据类型是NumPy的ndarray,这个时候只要调用NumPy的transpose方法就可以解决了:

img_np_t = img_np.transpose(2, 0, 1)

然而,在C++中就没这么简单了,虽然在OpenCV 4.6之后出了一个函数transposeND,但是却有一个限制,即输入必须是单通道的矩阵,因此也无法直接调用。

2. 数据格式与内存

2.1. 数据格式

假设有一张图片img,有三个通道(m1,m2, m3),每个通道有2行2列,如下图所示:


图1

如果这三个通道是以CHW格式(322)排列的,则排列后效果如下:


图2

如果这三个通道是以HWC(223)格式排列的,则排列后效果如下:


图3

可以看出区别还是挺大的,我们的目的就是实现下面的转换:


图4

2.2. 内存

以上面的m1为例,无论m1的形状为22还是14,只要行数*列数的结果一样,他们在内存中的排列顺序都是一样的,我们可以创建一个简易程序,然后看看在内存中的排列:

int main() {
 cv::Mat m1 = (cv::Mat_<uchar>(2, 2) << 1, 2, 3, 4);
 cv::Mat m1_2 = (cv::Mat_<uchar>(1, 4) << 1, 2, 3, 4);
 return 0;
}

无论m1还是m2_2,查看它们在内存中存储的数据(data指针指向的地址),都是以01020304这种方式进行排列的,如下图所示:

图5

因此我们可以看看img的不同排列在内存中的实际情况。

CHW:

图6

HWC:

图7

因此,要从HWC转为CHW,本质上就是需要将HWC中的内存排列转为CHW中的内存排列。

3. 转换

总的来说,转换有两步:

  • 分离图片通道;
  • 按照通道顺序拼接数据。

在具体实现方面,分离图片通道数据这里使用split函数,拼接数据使用hconcat。

hconcat函数(文档)的主要作用,是将多个矩阵,沿着1轴的方向进行拼接,效果如下:

图8

在内存中的详情如下:

图9

明显不是我们想要的结果。

因此,在分离通道之后,我们还需要将通道数据展平(flatten),然后再使用hconcat进行拼接,实际的代码如下:

cv::Mat hwc2chw(const cv::Mat& src_mat) {
 std::vector<cv::Mat> bgr_channels(3);
 cv::split(src_mat, bgr_channels);
 for (size_t i = 0; i < bgr_channels.size(); i++)
 {
 bgr_channels[i] = bgr_channels[i].reshape(1, 1); // reshape为1通道,1行,n列
 }
 cv::Mat dst_mat;
 cv::hconcat(bgr_channels, dst_mat);
 return dst_mat;
}

其中,reshape(文档)将每个通道进行flatten操作,即从22展平为14,然后再沿1轴进行拼接,如下图所示:


图10

这个dst_mat的内存数据就是我们想要的结果:

图11

假如这个时候不需要进行其他操作,直接返回dst_mat就可以了,如果还需要进行基于shape的相关操作,还需要再reshape一次: dst_mat = dst_mat.reshape(3, {2,2}); 。

如果是对接其他模型,如Triton backend,就不需要另外转了,因为reshape只是改变了读取数据的方式,并没有对数据进行任何操作,而最后传给Triton的也只是data指针。

当然还有很多方法可以进行转换,如使用vector数组将reshape后的channel按照顺序复制到数组中,其本质也是一样的。

4. 参考

https://cloud.tencent.com/developer/ask/sof/107600649

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号