我需要使用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]
代码问题
在
src->assign(in->datastart, in->dataend);
中src
的元素是Vec3f
,然而datastart
和dataend
是指向uchar
的指针。这将产生几个后果。首先,由于
in
是CV_8UC3
,因此会有3x的元素。此外,每个Vec3f
实例将只设置第一个条目,其他2个将为0。在
src->insert(src->end(), in->ptr<Vec3f>(i), in->ptr<Vec3f>(i)+cols);
中回想一下,您已经将
src
初始化为vector<Vec3f>(rows * cols);
——也就是说,向量已经具有与源图像中像素一样多的元素。但是,在循环中,您可以继续在末尾添加更多元素。这意味着生成的向量将具有两倍多的元素,其中前半部分为零。此外,
in
是CV_8UC3
,但您将数据解释为cv::Vec3f
。这意味着你取4个连续像素的字节值,并将其积分为3个32位浮点数的序列。结果只能是垃圾。这也意味着您最终会访问有效区域之外的数据,可能会超过缓冲区的末尾。
在
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
。
我们可以利用采用vector
的Mat
构造函数,并将生成的矩阵重塑为原始形状。
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;
}