从垫子到矢量,<Vec3f>反之亦然,而不会损坏图像



我需要使用OpenCV来读取图像,将其转换为Vec3f的矢量,处理像素,然后将其转换回Mat以使其可视化。

我正在使用C++17。

这里的代码到目前为止:

Mat* in = new Mat;
*in = imread(filepath);
int rows = in->rows;
int cols = in->cols;
//MAT -> VECTOR
vector<Vec3f>* src = new vector<Vec3f>(rows * cols);
if (in->isContinuous()) {
src->assign(in->datastart, in->dataend);
} 
else {
for (int i = 0; i < rows; ++i) {
src->insert(src->end(), in->ptr<Vec3f>(i), in->ptr<Vec3f>(i)+cols);
}
}
//---USE THE VECTOR TO TRASFORM EVERY PIXEL GRAY---
//SHOW 
imshow("out", cv::Mat(rows, cols, CV_8U, src, cv::Mat::AUTO_STEP));

结果是一个损坏的图像,电视静态噪声像,即使我不做像素处理阶段

感谢的帮助

让我们使用一个小的随机图像进行演示:

// Generate random input image
cv::Mat image(5, 5, CV_8UC3);
cv::randu(image, 0, 256);

选项1

由于输入是CV_8UC3(即每个元素都是cv::Vec3b),并且我们希望元素为cv::Vec3f,因此我们首先需要使用convertTo来将Mat转换为CV_32FC3。我们将结果存储在一个临时矩阵中,为了方便(因为我们知道元素类型),我们可以显式使用cv::Mat3f

// First convert to 32bit floats
cv::Mat3f temp;
image.convertTo(temp, CV_32FC3);

现在我们可以使用Mat迭代器来初始化向量。

// Use Mat iterators to construct the vector.
std::vector<cv::Vec3f> v1(temp.begin(), temp.end());

选项2

上一个选项最终分配了一个临时数组。只要有一点创造力,我们就可以避免这种情况。

事实证明,可以创建一个包裹向量的cv:Mat标头,共享底层数据存储。

我们从装箱一个适当大小的矢量开始:

std::vector<cv::Vec3f> v2(image.total());

根据这样的向量创建的Mat将具有1列,并且具有与元素一样多的行。因此,我们将把输入矩阵reshape变成相同的形状,然后使用convertTo,直接写入向量。

image.reshape(3, static_cast<int>(image.total())).convertTo(v2, CV_32FC3);

整个程序:

#include <opencv2/opencv.hpp>
#include <vector>
template<typename T>
void dump(std::string const& label, T const& data)
{
std::cout << label << ":n";
for (auto const& v : data) {
std::cout << v << " ";
}
std::cout << "n";
}
int main()
{
// Generate random input image
cv::Mat image(5, 5, CV_8UC3);
cv::randu(image, 0, 256);
// Option 1
// ========
// First convert to 32bit floats
cv::Mat3f temp;
image.convertTo(temp, CV_32FC3);
// Use Mat iterators to construct the vector.
std::vector<cv::Vec3f> v1(temp.begin(), temp.end());
// Option 2
// ========
std::vector<cv::Vec3f> v2(image.total());
image.reshape(3, static_cast<int>(image.total())).convertTo(v2, CV_32FC3);
// Output
// ======
dump("Input", cv::Mat3b(image));
dump("Vector 1", v1);
dump("Vector 2", v2);
return 0;
}

样本输出:

Input:
[246, 156, 192] [7, 165, 166] [2, 179, 231] [212, 171, 230] [93, 138, 123] [80, 105, 242] [231, 239, 174] [174, 176, 191] [134, 54, 234] [69, 25, 147] [24, 67, 124] [158, 203, 206] [89, 144, 210] [51, 31, 132] [123, 250, 234] [246, 204, 74] [111, 208, 249] [149, 234, 37] [55, 147, 143] [29, 214, 169] [215, 84, 190] [204, 110, 239] [216, 103, 137] [248, 173, 53] [221, 251, 29]
Vector 1:
[246, 156, 192] [7, 165, 166] [2, 179, 231] [212, 171, 230] [93, 138, 123] [80, 105, 242] [231, 239, 174] [174, 176, 191] [134, 54, 234] [69, 25, 147] [24, 67, 124] [158, 203, 206] [89, 144, 210] [51, 31, 132] [123, 250, 234] [246, 204, 74] [111, 208, 249] [149, 234, 37] [55, 147, 143] [29, 214, 169] [215, 84, 190] [204, 110, 239] [216, 103, 137] [248, 173, 53] [221, 251, 29]
Vector 2:
[246, 156, 192] [7, 165, 166] [2, 179, 231] [212, 171, 230] [93, 138, 123] [80, 105, 242] [231, 239, 174] [174, 176, 191] [134, 54, 234] [69, 25, 147] [24, 67, 124] [158, 203, 206] [89, 144, 210] [51, 31, 132] [123, 250, 234] [246, 204, 74] [111, 208, 249] [149, 234, 37] [55, 147, 143] [29, 214, 169] [215, 84, 190] [204, 110, 239] [216, 103, 137] [248, 173, 53] [221, 251, 29]

代码问题

  1. src->assign(in->datastart, in->dataend);

    src的元素是Vec3f,然而datastartdataend是指向uchar的指针。

    这将产生几个后果。首先,由于inCV_8UC3,因此会有3x的元素。此外,每个Vec3f实例将只设置第一个条目,其他2个将为0。

  2. src->insert(src->end(), in->ptr<Vec3f>(i), in->ptr<Vec3f>(i)+cols);

    回想一下,您已经将src初始化为vector<Vec3f>(rows * cols);——也就是说,向量已经具有与源图像中像素一样多的元素。但是,在循环中,您可以继续在末尾添加更多元素。这意味着生成的向量将具有两倍多的元素,其中前半部分为零。

    此外,inCV_8UC3,但您将数据解释为cv::Vec3f。这意味着你取4个连续像素的字节值,并将其积分为3个32位浮点数的序列。结果只能是垃圾。

    这也意味着您最终会访问有效区域之外的数据,可能会超过缓冲区的末尾。

  3. cv::Mat(rows, cols, CV_8U, src, cv::Mat::AUTO_STEP)中。。。

    首先,src包含Vec3f元素,但您将Mat创建为CV_8U(这也是一个问题,因为您也需要在此处提供通道计数,所以它实际上被解释为CV_8UC1)。因此,您不仅会有错误数量的通道,而且由于类型不匹配,它们还会包含垃圾。

    更大的问题是传递src作为第四个参数。现在,这是指向std::vector实例的指针,而不是指向它所包含的实际数据。(由于第4个参数是void*,因此它进行编译)。这意味着您实际上正在解释vector的元数据,以及许多其他未知数据。结果充其量是垃圾(或者正如您所发现的,SEGFAULT,或者潜在的讨厌的安全漏洞)。


返回Mat

注意,假设值在[0,1]范围内归一化,则imshow可能是浮点Mat

我们可以利用采用vectorMat构造函数,并将生成的矩阵重塑为原始形状。

cv::Mat result(cv::Mat(v2).reshape(3, image.rows));

请注意,在这种情况下,底层数据存储与源vector共享,因此您需要确保它与Mat一样保持在作用域中。如果不希望共享数据,只需将true作为第二个参数传递给构造函数即可。

cv::Mat result(cv::Mat(v2, true).reshape(3, image.rows));

当然,如果您想返回到CV_8UC3,只需添加一个convertTo即可。在这种情况下,不需要复制矢量数据,因为数据类型会更改,并且会自动分配新的存储阵列。

cv::Mat result;
cv::Mat(v2).reshape(3, image.rows).convertTo(result, CV_8UC3);

这是带有.assign和.insert的版本,类似于您给定的代码。它还涵盖了单元测试和从向量到Mat的方法,以及测试非连续Mat的一种方法。我不知道哪个版本更快,这个版本还是丹·马塞克的版本。请随意尝试。

int main()
{
cv::Mat in = cv::imread("C:/StackOverflow/Input/Lenna.png"); // this is a CV_8UC3 image, which is cv::Vec3b format
cv::Mat inFloat;
in.convertTo(inFloat, CV_32F);
// choose this line if you want to test non-continuous:
//inFloat = inFloat(cv::Rect(0, 0, 100, 100));
int rows = inFloat.rows;
int cols = inFloat.cols;
std::vector<cv::Vec3f> src;

if (inFloat.isContinuous())
{
std::cout << "continuous image data" << std::endl;
src.assign((cv::Vec3f*)inFloat.datastart, (cv::Vec3f*)inFloat.dataend);
}
else 
{
std::cout << "non-continuous image data" << std::endl;
for (int i = 0; i < inFloat.rows; ++i) 
{
src.insert(src.end(), inFloat.ptr<cv::Vec3f>(i), inFloat.ptr<cv::Vec3f>(i) + inFloat.cols);
}
}
// UNIT TEST:
bool testSuccess = true;
//const float epsilon = 0.01;
for(int j=0; j<rows; ++j)
for (int i = 0; i < cols; ++i)
{
cv::Vec3b & pixelIn = in.at<cv::Vec3b>(j, i);
cv::Vec3f & pixelInFloat = inFloat.at<cv::Vec3f>(j, i);
cv::Vec3f & pixelSrc = src.at(j*cols + i);
if (pixelInFloat != pixelSrc)
{
std::cout << "different values in: [" << i << "," << j << "]: " << pixelInFloat << " vs. " << pixelSrc << std::endl;
testSuccess = false;
}
}
if (testSuccess)
{
std::cout << "conversion from imread to vector<cv::Vec3f> successful." << std::endl;
}
else
{
std::cout << "Conversion failed." << std::endl;
}
// now test converting the vector back to a cv::Mat:
cv::Mat outFloat = cv::Mat(rows, cols, CV_32FC3, src.data());
// if you want to give the vector memory free later, choose this deep copy version instead:
// cv::Mat outFloat = cv::Mat(rows, cols, CV_32FC3, src.data()).clone();
cv::Mat out;
outFloat.convertTo(out, CV_8U);
cv::imshow("out", out);
cv::imshow("in", in);
cv::waitKey(0);

//std::cin.get();

return 0;
}

最新更新