【问题标题】:From Mat to vector<Vec3f> and the other way around without corrupting the image从 Mat 到 vector<Vec3f> 和其他方式而不破坏图像
【发布时间】:2019-06-20 06:42:30
【问题描述】:

我需要使用 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));

即使我不进行像素处理阶段,结果也会出现损坏的图像、电视静态噪声等

感谢您的帮助

【问题讨论】:

  • 向我们展示你对像素所做的事情,也许还有与原始图像相比的输出。
  • @DimChtz 我实际上还没有修改图像,只是想让转换工作
  • 通过 cv::Mat floatMat; 读取后将 Mat 转换为浮点数; in->convertTo(floatMat, CV_32F);如果你没有充分的理由,不要使用 cv::Mat 作为指针。 cv::Mat 已经有点像智能指针了,因为它只保存头信息和数据内存的引用计数器。
  • Vec3f 是 3 个 32F 值,因此您可以在转换后将数据解释为 Vec3f 数组。 Atm 您正在解释 Vec3b 内存,就好像它是 Vec3f
  • 对不起,在飞机上,但我看到你的问题已经得到了回答。它也可以与 .assign 一起使用,我将发布一个也涵盖其他方式的答案(从 Mat 到 vector 再回到 Mat)

标签: c++ opencv vector c++17


【解决方案1】:

我们用一张随机的小图来演示一下:

// 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 iterators 来初始化向量。

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

选项 2

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

事实证明,可以创建cv:Mat header wrapping a vector,共享底层数据存储。

我们首先创建一个适当大小的向量:

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-&gt;assign(in-&gt;datastart, in-&gt;dataend);

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

    这将产生几个后果。首先,因为inCV_8UC3,所以元素的数量是原来的3 倍。此外,每个Vec3f 实例只会设置第一个条目,其他 2 个将为 0。

  2. src-&gt;insert(src-&gt;end(), in-&gt;ptr&lt;Vec3f&gt;(i), in-&gt;ptr&lt;Vec3f&gt;(i)+cols);

    回想一下,您已经将 src 初始化为 vector&lt;Vec3f&gt;(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 实例的指针,而不是它保存的实际数据。 (它编译,因为第四个参数是void*)。这意味着您实际上是在解释 vector 的元数据以及许多其他未知数据。结果充其量是垃圾(或者正如您发现的那样,SEGFAULT 或潜在的令人讨厌的安全错误)。


返回Mat

请注意,imshow 可以使用浮点 Mat,假设值在 [0,1] 范围内标准化。

我们可以利用采用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);

【讨论】:

  • 谢谢,但我还必须在处理完元素后将向量转换为垫子! Mat(image.rows, image.cols, CV_32FC3, &v2, cv::Mat::AUTO_STEP);返回一个段错误
  • @joyfantastic 是的,那肯定会出现段错误。请参阅我答案底部的第 3 点,这是相同的情况。我将添加有关如何正确执行此操作的示例。 |这就是我在使用指针时必须非常小心的意思。射击自己的脚是如此容易。 :)
  • 非常感谢你的帮助,你救了我!
【解决方案2】:

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

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

【讨论】:

    猜你喜欢
    • 2020-09-10
    • 2014-12-20
    • 2021-12-29
    • 1970-01-01
    • 2012-12-14
    • 1970-01-01
    • 1970-01-01
    • 2021-07-29
    • 2016-02-03
    相关资源
    最近更新 更多