根据掩码获取特定像素的RGB值的OpenCV方法

3

我的标题可能不够清晰,但请仔细阅读以下描述。先谢谢了。

我有一张RGB图像和一张二进制掩码图像:

Mat img = imread("test.jpg")
Mat mask = Mat::zeros(img.rows, img.cols, CV_8U);

给口罩上一些1,假设1的个数为N。现在已知非零坐标,基于这些坐标,我们可以确定原始图像相应像素的RGB值。我知道可以通过以下代码完成:

Mat colors = Mat::zeros(N, 3, CV_8U);
int counter = 0;
for (int i = 0; i < mask.rows; i++)
{
    for (int j = 0; j < mask.cols; j++)
    {
        if (mask.at<uchar>(i, j) == 1)
        {
            colors.at<uchar>(counter, 0) = img.at<Vec3b>(i, j)[0];
            colors.at<uchar>(counter, 1) = img.at<Vec3b>(i, j)[1];
            colors.at<uchar>(counter, 2) = img.at<Vec3b>(i, j)[2];
            counter++;
        }
    }
}

坐标将如下所示:点击此处查看图片

然而,这两层for循环的时间成本太高了。我想知道是否有更快的方法来获取颜色,希望大家能理解我的意思。

附:如果我可以使用Python,这只需要一句话就可以完成:

colors = img[mask == 1]

为什么目标Mat被称为coords,当你不是在其中存储坐标,而是从输入图像中存储像素值?| 此外,该Python代码是错误的,numpy数组不可调用。您是否意味着img[mask==1]?此外,它也不会产生坐标列表。 - Dan Mašek
谢谢您的友善提醒 :) - Terry
3个回答

1
如果您可以使用任何其他的C++开源库,建议尝试使用Armadillo。您可以使用它进行所有线性代数运算,而且还可以将上面的代码简化为一行(类似于您的Python代码片段)。
或者
尝试使用findNonZero()函数,并查找包含非零值的图像中的所有坐标。请参阅:https://dev59.com/_GIk5IYBdhLWcg3wdeDY#19244484

findNonZero(mask, nonzeroCoordinates); int counter = 0; for(int i=0; i<nonzeroCoordinates.total(); i++) { coords.at<uchar>(counter, 0) = img.at<Vec3b>(nonzeroCoordinates.at<Point>(i).x, nonzeroCoordinates.at<Point>(i).y)[0]; coords.at<uchar>(counter, 1) = img.at<Vec3b>(nonzeroCoordinates.at<Point>(i).x, nonzeroCoordinates.at<Point>(i).y)[1]; coords.at<uchar>(counter, 2) = img.at<Vec3b>(nonzeroCoordinates.at<Point>(i).x, nonzeroCoordinates.at<Point>(i).y)[2]; counter++; } - Terry
那就试试Armadillo库吧。我之前用过Armadillo来进行这样的操作,相比OpenCV要快一些。 - Jazz
你也可以在OpenCV中尝试以下方法:1. 将图像乘以掩码。2. 将图像矩阵转换为向量。3. 按升序排序向量的值。4. 拒绝所有零值坐标。如果您能提供图像和掩码,我可以尝试。 - Jazz
谢谢,我已经搜索了关于Armadillo的信息,发现它是一个非常强大的工具,但我的项目只允许我使用OpenCV。 - Terry
你可以自己定义掩码,这是图片链接:https://www.rd.com/wp-content/uploads/sites/2/2016/02/06-train-cat-shake-hands.jpg - Terry

1
.at() 方法是在 C++ 中访问 Mat 值最慢的方式。最快的方法是使用指针,但最佳实践是使用迭代器。请参见 OpenCV 扫描图像的教程
需要注意的是,即使 Python 的语法对于这样的操作很好,它最终仍然必须遍历所有元素---而且由于它在此之前有一些开销,因此它实际上比带有指针的 C++ 循环更慢。无论你使用哪个库,你都需要遍历所有元素,并为每个元素与掩码进行比较。

我觉得我没有表达清楚...如果掩码矩阵是稀疏的呢?你将花费很多时间遍历矩阵。一些现有的函数可能会有修改后的算法来处理这种情况,这就是我试图寻找的。 - Terry
稀疏矩阵的存储方式不同,非零元素会带有它们的索引一起存储,因此您可以直接使用它们进行索引。如果您想要用于索引的稀疏矩阵,则需要创建一个并将其用于索引。然而,一般的掩码不会被存储为稀疏矩阵。如果您想要从掩码中创建一个稀疏矩阵,则仍然需要循环遍历掩码以创建它。 - alkasm
假设您有一个稀疏的二进制掩码;也就是说,您只存储与非零元素对应的索引(请注意,这是使用findNonZero()获得的内容,正如其他答案所提到的)。然后,您只需在这些点处对图像进行索引。 - alkasm

0

启用优化编译,尝试对此版本进行分析,并告诉我们它是否更快:

vector<Vec3b> colors;
if (img.isContinuous() && mask.isContinuous()) {
    auto pimg = img.ptr<Vec3b>();
    for (auto pmask = mask.datastart; pmask < mask.dataend; ++pmask, ++pimg) {
        if (*pmask)
            colors.emplace_back(*pimg);
    }
}
else {
    for (int r = 0; r < img.rows; ++r) {
        auto prowimg = img.ptr<Vec3b>(r);
        auto prowmask = img.ptr(r);
        for (int c = 0; c < img.cols; ++c) {
            if (prowmask[c])
                colors.emplace_back(prowimg[c]);
        }
    }
}

如果您知道颜色的大小,请预先为其保留空间。

我尝试了你的版本,但是当我使用一个for循环来查找颜色时,即 for(int i=0; i<colors.size(); i++) cout << colors[i] << endl; 我得到了一个错误的结果。 - Terry
你是怎么检查的?我已经尝试过了,结果和你的代码一样。在这里你可以找到一系列的测试:https://ideone.com/1nJuBV - Costantino Grana

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接