我们用一张随机的小图来演示一下:
// 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]
代码问题
-
在src->assign(in->datastart, in->dataend);
src 的元素是Vec3f,但datastart 和dataend 是指向uchar 的指针。
这将产生几个后果。首先,因为in 是CV_8UC3,所以元素的数量是原来的3 倍。此外,每个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 实例的指针,而不是它保存的实际数据。 (它编译,因为第四个参数是void*)。这意味着您实际上是在解释 vector 的元数据以及许多其他未知数据。结果充其量是垃圾(或者正如您发现的那样,SEGFAULT 或潜在的令人讨厌的安全错误)。
返回Mat
请注意,imshow 可以使用浮点 Mat,假设值在 [0,1] 范围内标准化。
我们可以利用采用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);