C++ OpenCV 最大存储容量cv::Mat

C++ OpenCV Max storage capacity of cv::Mat

在我的程序中,我加载了一些图像,从中提取了一些特征并使用 cv::Mat 来存储这些特征。根据图像数量,我知道 cv::Mat 的大小为 700.000 x 256(行 x 列),约为 720Mb。但是,当我 运行 我的程序达到大约 400.000 x 256 (400Mb) 并尝试添加更多时,它只会因致命错误而崩溃。谁能确认 400Mb 确实是 cv::Mat 存储容量的极限?我应该检查更多问题吗?解决这个问题的可能方法?

创建了一个矩阵如下。使用 CV_8UC4,因为它提供大约 700 MB。没问题。所以不,400Mb 不是限制。 700Mb 不是限制。尝试了两倍(1400000 行,1.4Gb)——仍然没有达到极限(不过我的默认图像查看器无法显示生成的 BMP 文件)。

const unsigned int N_rows = 700000;
const unsigned int N_cols = 256;
cv::Mat m(N_rows, N_cols, CV_8UC4);
for (int r = 0; r < m.rows; ++r)
{
    for (int c = 0; c < m.cols; ++c)
    {
        m.data[(r*N_cols + c) * 4] =  c % 256;
    }
}
cv::imwrite("test.bmp", m);

解决问题的可能方法:

  • 一开始在cv::Mat中分配足够的space,甚至可能多一点,以确保。如果您的问题是由重新分配引起的,这将有所帮助。
  • 如果您的应用程序是 32 位的,如您的评论所建议的那样,请将其转换为 64 位以增加内存限制。
  • 即使对于 32 位应用程序,720Mb 也应该不是问题。但是,您的应用程序可能会为其他内容使用更多内存。如果是这种情况,将您的图像移到一个单独的进程中可能会有所帮助,因此它会获得自己单独的 2Gb。不过,进程间通信很痛苦。
  • 如果您仍然需要处理内存中放不下的文件,也许可以查看内存映射文件,我认为 OpenCV 至少对这些文件有一些支持,但不能说更多,从未使用过它们.
  • 使用一组较小的矩阵,reading/writing/splitting/joining 它们,具体取决于您的需要。

acv::Mat的大小没有严格限制。只要内存可用,您就应该能够分配内存。

这是一个小程序,显示了当 运行 cv::Mat::push_back 多次时数据指针会发生什么。在最终抛出内存不足异常之前,使用 rowscols 的值可能会导致为 a.data 打印一个或多个值。

#include <opencv2/opencv.hpp>
int main()
{
  int rows = 1, cols = 10000;
  cv::Mat a(rows, cols, CV_8UC1);
  while(1) {
    a.push_back(cv::Mat(1, cols, CV_8UC1));
    std::cout << (size_t) a.data << std::endl;
  }
}

这实际上取决于分配器,上面的代码对行和列的各种值做了什么。因此,应考虑 a.

的小初始尺寸和大初始尺寸

请记住,与 C++11 std::vector 一样,cv::Mat 中的元素是连续的。可以通过 cv::Mat::data 成员获取对基础数据的访问。连续调用 std::vector::push_backcv::Mat::push_back 可能会导致重新分配底层内存。在这种情况下,必须将内存移动到新地址,并且可能需要大约两倍的内存量才能将旧地址移动到新地址(除非使用任何使用较少内存的棘手算法)。

挖掘源代码,使用 push_back:

它检查是否有足够的 space 用于新元素,如果没有,它重新分配矩阵,space for (current_size * 3 + 1) / 2 (see here)。在您的示例中,大约 400,000 * 256(总共 102,400,000 个元素)它尝试另一个分配,因此它尝试为 307,200,001 / 2 = 153,600,000 元素分配 space。但是为了移动这个,它需要分配一个新的space然后复制数据

来自matrix.cpp

Mat m(dims, size.p, type());
size.p[0] = r;
if( r > 0 )
{
    Mat mpart = m.rowRange(0, r);
    copyTo(mpart);
}

*this = m;

所以它本质上是:

  1. 分配一个新矩阵,对所有新创建的元素使用默认构造函数
  2. 复制数据,然后删除旧数据
  3. 为此矩阵创建一个新的 header,具有足够的列
  4. this元素指向新分配的数据(释放旧分配的内存)

意思是,在您的情况下,它需要足够的 space 用于 (600,000 + 400,000) * 256 - 1GB 数据,使用 4 字节整数。而且,它还创建了一个一行的辅助矩阵,在这种情况下,有 600,000 列,占 2,400,000 个额外字节。

因此,在下一次迭代中,当达到 600,000 列时,它会尝试分配 900,000x256 个元素 (900Mb) + 600,000x256 个元素 (600Mb) + 600,000 (~3.4Mb)。因此,仅通过这种方式分配(使用 push_back),您将进行多次重新分配。

换句话说:既然你已经知道矩阵的大概大小,那么使用reserve是必须的。它快了几倍(将避免重新分配和复制)。

此外,作为一种解决方法,您可以尝试插入转置矩阵,然后在该过程完成后,再次转置它。

附带问题:此实现不应该使用 realloc 而不是 malloc/memcpy 吗?