如何实时计算OpenCV轮廓的平均强度

12
我有一张图片,上面有大约50到100个小轮廓。我希望实时找出每个轮廓的平均亮度[1]。我想到的一些方法是:
  1. 对每个轮廓使用FILLED选项绘制轮廓;将每个图像用作原始图像上的掩模,从而找到平均值。但乍一看,我认为这种方法不会是实时的。

  2. 学习OpenCV中drawContour函数的实现,并以相同方式访问轮廓包围的像素。但是代码似乎非常复杂,不能很快地理解。

  3. 计算最小区域矩形,使用转换找到矩形内的所有点,并找到非零点的平均值。再次,这似乎是一种复杂的方法。

有没有更简单、更有效的方法来做到这一点呢?

[1] 包围每个非重叠轮廓的所有像素强度的平均值

[2] 在2.66 GHz桌面PC上,每秒约25个(960 x 480)像素图像


你能保证你的轮廓不重叠吗? - Hammer
这里时间变化的是什么?是轮廓吗?图片?还是两者都包括? - Hammer
你是想找到每个轮廓本身的平均值,还是找到轮廓所包围区域的平均值? - Aurelius
@Hammer 是的,轮廓不重叠。图像随时间变化,因此从图像计算出的轮廓也会随之变化。 - Ashok
我想不出比1更快的简单方法,抱歉。 - Hammer
显示剩余2条评论
1个回答

15

我无法提出任何与您提出的方法大不相同的方法。然而,我能够进行一些计时,这可能有助于指导您的决策。所有计时都在iMac上运行,针对1280*720像素大小的图像,限制为寻找100个轮廓。计时在您的计算机上当然会有所不同,但相对计时应该是有用的。

对于每个测试用例,声明如下:

std::vector<std::vector<cv::Point>> cont;  // Filled by cv::findContours()
cv::Mat labels = cv::Mat::zeros(image.size(), CV_8UC1);     
std::vector<float> cont_avgs(cont.size(), 0.f); // This contains the averages of each contour

方法一:19.0毫秒

方法一在概念上是最简单的,但也是最慢的。通过为每个轮廓分配唯一的颜色来标记每个轮廓。通过遍历图像中的每个像素,对每个标记组件的值进行求和。

for (size_t i = 0; i < cont.size(); ++i)
{
    // Labels starts at 1 because 0 means no contour
    cv::drawContours(labels, cont, i, cv::Scalar(i+1), CV_FILLED);
}

std::vector<float> counts(cont.size(), 0.f);
const int width = image.rows;
for (size_t i = 0; i < image.rows; ++i)
{
    for (size_t j = 0; j < image.cols; ++j)
    {
        uchar label = labels.data[i*width + j];

        if (label == 0)
        {
            continue;   // No contour
        }
        else
        {
            label -= 1; // Make labels zero-indexed
        }

        uchar value = image.data[i*width + j];
        cont_avgs[label] += value;
        ++counts[label];
    }
}
for (size_t i = 0; i < cont_avgs.size(); ++i)
{
    cont_avgs[i] /= counts[i];
}

方法三:15.7毫秒

未经修改的方法三具有最简单的实现方式,并且速度最快。所有轮廓都被填充以用作查找平均值的掩码。计算每个轮廓的边界矩形,然后在边界框内使用掩码计算平均值。

警告:如果任何其他轮廓位于所需轮廓的边界矩形内,则此方法将给出不正确的结果。

cv::drawContours(labels, cont, -1, cv::Scalar(255), CV_FILLED);

for (size_t i = 0; i < cont.size(); ++i)
{
    cv::Rect roi = cv::boundingRect(cont[i]);
    cv::Scalar mean = cv::mean(image(roi), labels(roi));
    cont_avgs[i] = mean[0];
}

修改后的方法3:17.8毫秒

对方法3进行轻微修改会稍微增加执行时间,但可以获得正确结果无论轮廓位置如何。每个轮廓都被单独标记,并且只使用该轮廓的掩码计算平均值。

for (size_t i = 0; i < cont.size(); ++i)
{
    cv::drawContours(labels, cont, i, cv::Scalar(i), CV_FILLED);
    cv::Rect roi = cv::boundingRect(cont[i]);
    cv::Scalar mean = cv::mean(image(roi), labels(roi) == i);
    cont_avgs[i] = mean[0];
}

非常有用的分析!我喜欢你修改后的方法3,比我的好。我现在会使用这个,并尝试学习OpenCV如何实现drawContours的CV_FILLED选项,以便进行比较。谢谢! - Ashok

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