OpenCV矩阵元素的转换是如何工作的
我很难理解OpenCV的内部工作原理。考虑下面的代码:OpenCV矩阵元素的转换是如何工作的,opencv,Opencv,我很难理解OpenCV的内部工作原理。考虑下面的代码: Scalar getAverageColor(Mat img, vector<Rect>& rois) { int n = static_cast<int>(rois.size()); Mat avgs(1, n, CV_8UC3); for (int i = 0; i < n; ++i) { // What is the correct way to assi
Scalar getAverageColor(Mat img, vector<Rect>& rois) {
int n = static_cast<int>(rois.size());
Mat avgs(1, n, CV_8UC3);
for (int i = 0; i < n; ++i) {
// What is the correct way to assign the color elements in
// the matrix?
avgs.at<Scalar>(i) = mean(Mat(img, rois[i]));
/*
This seems to always work, but there has to be a better way.
avgs.at<Vec3b>(i)[0] = mean(Mat(img, rois[i]))[0];
avgs.at<Vec3b>(i)[1] = mean(Mat(img, rois[i]))[1];
avgs.at<Vec3b>(i)[2] = mean(Mat(img, rois[i]))[2];
*/
}
// If I access the first element it seems to be set correctly.
Scalar first = avgs.at<Scalar>(0);
// However mean returns [0 0 0 0] if I did the assignment above using scalar, why???
Scalar avg = mean(avgs);
return avg;
}
Scalar getAverageColor(矩阵img、向量和ROI){
int n=静态_转换(rois.size());
材料平均值(1,n,CV_8UC3);
对于(int i=0;i
如果我使用avgs.at(I)=平均值(Mat(img,rois[I]))
对循环中的赋值,第一个元素看起来是正确的,但是平均值计算总是返回零(即使第一个元素看起来是正确的)。如果我用Vec3b手动分配所有颜色元素,这似乎是可行的,但为什么呢?注意:是一个typedef for,它派生自,它派生自。
类似地,iscv::Vec
源自cv::Matx
——这意味着我们可以在cv::Mat::at
中使用这3个选项中的任意一个,并获得相同(正确)的行为
重要的是要知道它位于基础数据数组上。您需要非常小心地为模板参数使用适当的数据类型,该数据类型对应于正在调用它的
cv::Mat
的元素类型(包括通道计数)
文件中提到以下内容:
请记住,at运算符中使用的大小标识符不能随机选择。这取决于您试图从中检索数据的图像。下表对此提供了更好的见解:
- 如果矩阵类型为
,则使用CV_8U
Mat.at(y,x)
- 如果矩阵类型为
,则使用CV_8S
Mat.at(y,x)
- 如果矩阵类型为
,则使用CV_16U
Mat.at(y,x)
- 如果矩阵类型为
,则使用CV_16S
Mat.at(y,x)
- 如果矩阵类型为
,则使用cv32s
Mat.at(y,x)
- 如果矩阵类型为
,则使用CV_32F
Mat.at(y,x)
- 如果矩阵类型为
,则使用CV_64F
Mat.at(y,x)
cv::Vec
(或者更确切地说是提供的typedef之一)cv::Vec
基本上是给定类型的N个值的固定大小数组的包装器
在您的例子中,矩阵
avgs
是CV_8UC3
——每个元素由3个无符号字节值组成(即总共3个字节)。但是,通过使用avgs.at(i)
,可以将每个元素解释为4个双精度(总共32个字节)。这意味着:
- 您试图写入的实际元素(如果解释正确)将只包含第一个通道(8字节浮点)平均值中的3个最高有效字节,即完全垃圾
- 实际上,您会用更多的垃圾覆盖接下来的10个元素(最后一个部分,第三个通道会安然无恙地逃逸)
- 在某种程度上,您肯定会使缓冲区溢出,并有可能破坏其他数据结构。这个问题相当严重
#include <opencv2/opencv.hpp>
int main()
{
cv::Mat test_mat(cv::Mat::zeros(1, 12, CV_8UC3)); // 12 * 3 = 36 bytes of data
std::cout << "Before: " << test_mat << "\n";
cv::Scalar test_scalar(cv::Scalar::all(1234.5678));
test_mat.at<cv::Scalar>(0, 0) = test_scalar;
std::cout << "After: " << test_mat << "\n";
return 0;
}
Before: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
After: [173, 250, 92, 109, 69, 74, 147, 64, 173, 250, 92, 109, 69, 74, 147, 64, 173, 250, 92, 109, 69, 74, 147, 64, 173, 250, 92, 109, 69, 74, 147, 64, 0, 0, 0, 0]
#include <opencv2/opencv.hpp>
typedef cv::Matx<uint8_t, 3, 1> Mat31b; // Convenience, OpenCV only has typedefs for double and float variants
int main()
{
cv::Mat test_mat(1, 12, CV_8UC3); // 12 * 3 = 36 bytes of data
test_mat = cv::Scalar(1, 1, 1); // Set all elements to 1
std::cout << "Before: " << test_mat << "\n";
cv::Scalar test_scalar{ 2,3,4,0 };
cv::Matx31d temp = test_scalar.get_minor<3, 1>(0, 0);
test_mat.at<Mat31b>(0, 0) = static_cast<Mat31b>(temp);
// or
// cv::Scalar_<uint8_t> temp(static_cast<cv::Scalar_<uint8_t>>(test_scalar));
// test_mat.at<Mat31b>(0, 0) = temp.get_minor<3, 1>(0, 0);
std::cout << "After: " << test_mat << "\n";
return 0;
}
这清楚地表明我们的写作方式比我们应该的要多
在调试模式下,错误使用at
也会触发断言:
OpenCV(3.4.3) Error: Assertion failed (((((sizeof(size_t)<<28)|0x8442211) >> ((traits::Depth<_Tp>::value) & ((1 << 3) - 1))*4) & 15) == elemSize1()) in cv::Mat::at, file D:\code\shit\so07\deps\include\opencv2/core/mat.inl.hpp, line 1102
注意:你可以去掉显式的临时符号,它们在这里只是为了更容易阅读
输出:
#include <opencv2/opencv.hpp>
int main()
{
cv::Mat test_mat(cv::Mat::zeros(1, 12, CV_8UC3)); // 12 * 3 = 36 bytes of data
std::cout << "Before: " << test_mat << "\n";
cv::Scalar test_scalar(cv::Scalar::all(1234.5678));
test_mat.at<cv::Scalar>(0, 0) = test_scalar;
std::cout << "After: " << test_mat << "\n";
return 0;
}
Before: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
After: [173, 250, 92, 109, 69, 74, 147, 64, 173, 250, 92, 109, 69, 74, 147, 64, 173, 250, 92, 109, 69, 74, 147, 64, 173, 250, 92, 109, 69, 74, 147, 64, 0, 0, 0, 0]
#include <opencv2/opencv.hpp>
typedef cv::Matx<uint8_t, 3, 1> Mat31b; // Convenience, OpenCV only has typedefs for double and float variants
int main()
{
cv::Mat test_mat(1, 12, CV_8UC3); // 12 * 3 = 36 bytes of data
test_mat = cv::Scalar(1, 1, 1); // Set all elements to 1
std::cout << "Before: " << test_mat << "\n";
cv::Scalar test_scalar{ 2,3,4,0 };
cv::Matx31d temp = test_scalar.get_minor<3, 1>(0, 0);
test_mat.at<Mat31b>(0, 0) = static_cast<Mat31b>(temp);
// or
// cv::Scalar_<uint8_t> temp(static_cast<cv::Scalar_<uint8_t>>(test_scalar));
// test_mat.at<Mat31b>(0, 0) = temp.get_minor<3, 1>(0, 0);
std::cout << "After: " << test_mat << "\n";
return 0;
}
这两个选项都会产生以下输出:
Before: [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
After: [ 2, 3, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
正如我们所看到的,只有前3个字节被更改,因此它的行为是正确的
关于表演的一些想法 很难猜测这两种方法中哪一种更好。强制转换首先意味着您为临时分配的内存量较小,但随后您必须执行4次而不是3次的饱和强制转换。必须进行一些基准测试(读者练习)。平均数的计算将大大超过平均数,因此它很可能是无关紧要的 考虑到我们并不真正需要
saturate\u cast
s,也许简单但更详细的方法(适用于您的优化版本)在紧密循环中可能会表现得更好
cv::Vec3b& current_element(avgs.at<cv::Vec3b>(i));
cv::Scalar current_mean(cv::mean(cv::Mat(img, rois[i])));
for (int n(0); n < 3; ++n) {
current_element[n] = static_cast<uint8_t>(current_mean[n]);
}
结果与之前相同。(at基本上是对数据指针的重新解释),因此
avgs
中的第一个元素基本上是第一个通道8字节平均值的3个MSB(其余元素溢出到后面的元素,或溢出接近末尾的缓冲区)。退出!写下来作为回答,我会很高兴地接受它!然而at(i)的平均值=Vec4b(平均值(Mat(img,rois[i]))代码>也有效。将标量(通过平均值(…)返回)分配给CV_8UC3 Mat的正确方法是什么?这有点棘手,请参见答案。任何类型的Scalar
都可以转换为4元素cv::Vec
,但再次在3通道垫子上使用Vec4b
会造成严重破坏(这对其他读者来说更重要,因为您之前的评论表明您了解这有多糟糕),也许你最好做一个avgs
aCV_64FC3
(将平均值保留为双倍,在该阶段不进行舍入/铺底),只转换所有ROI平均值的最终平均值。累积舍入错误的机会减少了。对不起,我似乎不知道该怎么做