我在将 cv::Mat 保存到 1D 数组时遇到问题



我想将cv::Mat数据保存到线性数组中,但我不知道为什么会有错误。图像颜色为灰度(CV_8UC1)。这是代码:

uchar* imgToArray(cv::Mat& img)
{
int n = img.rows;
int m = img.cols;
uchar* res = new uchar(m * n);
for(int row = 0; row < m; row++)
{
for(int col = 0; col < n; col++)
{
res[row * n + col] = img.at<uchar>(row, col);
}
}
return res;
}

提到的调试信息,

Program received signal SIGSEGV, Segmentation fault.
0x000055555555c0e9 in imgToArray (img=..., n=512, m=512) at ../conversion.cpp:10
10              res[row * n + col] = img.at<uchar>(row, col);

我对此感到非常困惑。感谢任何提前提供建议的人!

你已经创建了一个值为m * n的 int 对象

uchar* res = new uchar(m * n);

不是数组,应该是

uchar* res = new uchar[m * n];

除了 rafix07 指出的明显不正确的分配之外,您的代码中存在多个问题。一个问题是你是否真的需要拥有uchar数组的所有权(如果没有,那么有更简单的方法可以解决这个问题),但我假设你这样做。让我们从顶部开始。

uchar* imgToArray(cv::Mat& img, int n, int m);

  1. 返回原始指针 (uchar*)。

首先,我们要返回一个数组,所以让我们明确这一点。原始指针容易出现内存泄漏,C++11 已经存在了一段时间——我们可以做得更好。让我们把它做成一个std::unique_ptr,它也可以正确处理数组。

然后我们可以分配std::make_unique<uchar[]>(size),在那里你不太可能犯那种错误。

  1. 返回一个没有大小的原始数组。

第一个没有第二个是如果你想以某种方式处理返回的数据,就会自找麻烦。依靠用户必须调用另一个函数来获取它,或者必须自己计算它远非理想。因此,让我们使用std::pair将大小与智能指针打包在一起。

typedef std::pair<std::unique_ptr<uchar[]>, size_t> uchar_array_t;
uchar_array_t imgToArray(cv::Mat& img, int n, int m);
  1. img作为非常量引用,即使您不打算修改它。

如果您真的想避免复制标头的开销,请使用 const 引用来明确您的意图。

  1. 参数int nint m是多余的。它们也未经验证。

除了与要转换为"线性数组"的区域相对应的cv::Mat之外,没有理由为函数提供任何其他内容。OpenCV已经提供了一种获得投资回报率的有效方法:cv::Mat::operator()。让用户使用它,并删除多余的参数。

typedef std::pair<std::unique_ptr<uchar[]>, size_t> uchar_array_t;
uchar_array_t imgToArray(cv::Mat const& image);
  1. Code 假设img的数据类型为CV_8UC1(即单个通道矩阵,其中每个元素都是无符号字节),但不会验证情况确实如此。

这意味着当您使用不同类型的图像调用它时,您将胡说八道。这是不可取的。您可以添加一些断言来处理此问题,但恕我直言,最好在签名中明确说明。让我们使用cv::Mat1b而不是普通cv::Mat.

typedef std::pair<std::unique_ptr<uchar[]>, size_t> uchar_array_t;
uchar_array_t imgToArray(cv::Mat1b const& image);
  1. 当代码给定一个空矩阵时,它不会考虑场景。

让我们为这个前提条件添加一个断言:CV_Assert(!image.empty());

  1. 分配不正确,正如 rafix07 所指出的。

自从我们更改了返回类型以来,这变得毫无意义。我们可以这样重写它:

uchar_array_t result;
result.second = image.rows * image.cols;
result.first = std::make_unique<uchar[]>(result.second);
  1. 通过"手动"遍历像素并调用cv::Mat::at来复制数据。

这部分有很多悲观情绪,以及我们在前面几点中讨论过的潜在崩溃或UB。

  • cv::Mat::at涉及不必要的开销,当我们知道我们正在访问有效像素时。即使矩阵不是连续的,我们也可以通过为每行使用cv::Mat::ptr获取指针并递增它来做得更好。
  • res[row * n + col]-- 我们按顺序写入数组,那么为什么要重新计算位置而不是简单地增加指针呢?
  • 这样做
  • - OpenCV允许我们使用"外部数据"创建一个cv::Mat,这意味着我们自己分配的数组。根据设计,它以衬垫方式存储此数组中的数据,就像您想要的那样。它还提供了一种将数据从一个矩阵复制到另一个矩阵的便捷方法。

因此,让我们利用这一点:

cv::Mat1b temp(image.rows, image.cols, result.first.get());
image.copyTo(temp);

让我们把这些加在一起。

#include <opencv2/opencv.hpp>
#include <memory>
typedef std::pair<std::unique_ptr<uchar[]>, size_t> uchar_array_t;
uchar_array_t to_array(cv::Mat1b const& image)
{
CV_Assert(!image.empty());
uchar_array_t result;
result.second = image.rows * image.cols;
result.first = std::make_unique<uchar[]>(result.second);
cv::Mat1b temp(image.rows, image.cols, result.first.get());
image.copyTo(temp);
return result;
}
void observe_data(uchar const[], size_t)
{
// ...
}
void modify_data(uchar[], size_t)
{
// ...
}
void take_ownership(uchar data[], size_t)
{
// ...
delete[] data;
}
int main()
{
cv::Mat image(cv::imread("itYxy.png", cv::IMREAD_GRAYSCALE));
uchar_array_t data_1(to_array(image));
uchar_array_t data_2(to_array(image(cv::Rect(10, 10, 40, 50))));
observe_data(data_2.first.get(), data_2.second);
observe_data(data_2.first.get(), data_2.second);
observe_data(data_2.first.release(), data_2.second);
return 0;
}

您可以使用std::unique_ptr::get来观察阵列,也可以使用std::unique_ptr::release来转移所有权。避免了泄漏,我们有可用的大小,因此我们可以避免访问超出分配数组范围的数据,并测试了先决条件。


当然,如果您不需要拥有阵列,则可以避免其中的大部分情况。最多一个矩阵的clone(如果它不是连续的),然后调用cv::Mat::ptr

相关内容

  • 没有找到相关文章