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

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

本文关键字:cv Mat 存储容量 OpenCV c++      更新时间:2023-10-16

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

挖掘源代码,使用push_back:

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

从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. 为这个矩阵创建一个新的标题,包含足够的列
  4. this元素指向新分配的数据(释放旧分配的内存)

意味着,在您的情况下,它需要足够的空间(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吗?

cv::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;
  }
}

这实际上取决于分配器,取决于上面的代码如何处理不同的行和cols值。因此,应该考虑a的大小初始尺寸。

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

创建如下矩阵。使用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中分配足够的空间,甚至可能有一点额外的空间,以确保。如果您的问题是由重新分配引起的,这将有所帮助。
  • 如果您的应用程序是32位,正如您的评论建议,将其转换为64位以增加内存限制。
  • 即使对于32位应用程序,720Mb应该不是问题。但是,您的应用程序可能会为其他东西使用更多内存。如果是这种情况,将您的映像移到一个单独的进程中可能会有所帮助,这样它就可以获得单独的2Gb。然而,进程间通信是痛苦的。
  • 如果你仍然要处理不适合内存的文件,也许看看内存映射文件,我认为OpenCV至少有一些支持这些,但不能说更多,从来没有使用过它们。
  • 使用一组较小的矩阵,读取/写入/拆分/连接它们,这取决于你需要什么。