如何在C++版OpenCV中还原Caffe输出的[1x10x8x8]多维数组
嘿,我刚好踩过这个坑,帮你捋清楚怎么搞定OpenCV里的多维数组还原!
为什么会变成[640x1]的vector?
你说的[1x10x8x8]数组总共有1108*8=640个元素,Caffe输出的vector其实是把多维数组直接扁平化存储成了一维序列,所以转成Mat默认就是[640x1]的结构——这不是OpenCV的问题,只是数据存储形式的转换而已。
核心解决思路:把一维数组reshape回四维
OpenCV的Mat完全支持多维结构,关键是要匹配Caffe输出的内存布局来设置正确的维度顺序,别搞混了维度的先后顺序就行。
步骤1:将vector转为一维Mat
首先把Caffe输出的vector<float>转成一个一维Mat,注意数据类型要匹配(Caffe输出一般是float,所以用CV_32F):
// 假设你的Caffe输出存在这个vector里 std::vector<float> caffe_output; // 先验证元素总数是否符合预期(1*10*8*8=640) assert(caffe_output.size() == 640); // 转为一维Mat cv::Mat flat_mat(caffe_output.size(), 1, CV_32F, caffe_output.data());
步骤2:Reshape为四维Mat
这里要重点注意维度顺序:
Caffe的输出是NCHW(batch, channel, height, width)的内存布局,也就是内存中元素的顺序是:先遍历width,再height,再channel,最后batch。
而OpenCV的Mat多维尺寸数组是按「最内层维度(变化最快)到最外层维度(变化最慢)」定义的,所以对应的尺寸数组应该是{width, height, channel, batch},也就是{8,8,10,1}。
用reshape方法把一维Mat转成四维:
int new_dims = 4; int new_sizes[] = {8, 8, 10, 1}; // width, height, channel, batch cv::Mat four_d_mat = flat_mat.reshape(1, new_dims, new_sizes);
步骤3:访问多维数组元素
现在你就可以像Python里那样访问指定位置的元素了!OpenCV的Mat::at方法对于四维数组,参数顺序是从最外层到最内层,也就是(batch_idx, channel_idx, height_idx, width_idx),刚好对应Python里的output[batch][channel][height][width]:
// 对应Python中的 output[0][5][3][2] float target_val = four_d_mat.at<float>(0, 5, 3, 2);
用cv::NAryMatIterator遍历多维数组
如果你想用NAryMatIterator来遍历,现在四维Mat已经正确构建,迭代器就会按照多维结构来划分平面,而不是遍历整个一维数组了:
const cv::Mat* arrays[] = {&four_d_mat, nullptr}; cv::Mat planes[1]; cv::NAryMatIterator it(arrays, planes); // 遍历每个平面 for (int p = 0; p < it.nplanes; ++p, ++it) { float* plane_ptr = it.planes[0].ptr<float>(); int plane_size = it.size; // 遍历当前平面的所有元素 for (int i = 0; i < plane_size; ++i) { // 处理每个元素,比如打印或者计算 // std::cout << plane_ptr[i] << " "; } }
为什么之前用NAryMatIterator得到[640x1]?
因为你当时操作的还是扁平化的一维Mat,迭代器自然会把整个数组当成一个平面来遍历,所以看到的是[640x1]的结构。只有当Mat是正确的四维结构时,迭代器才会按照多维的层级来拆分平面。
内容的提问来源于stack exchange,提问作者Saeed Masoomi

