Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/opencv/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
OpenCV矩阵元素的转换是如何工作的_Opencv - Fatal编程技术网

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

我很难理解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 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,它派生自,它派生自。 类似地,is
cv::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
a
CV_64FC3
(将平均值保留为双倍,在该阶段不进行舍入/铺底),只转换所有ROI平均值的最终平均值。累积舍入错误的机会减少了。对不起,我似乎不知道该怎么做