使用OpenCV从图像序列中获取中位数图片

9

我有一系列图片,想要计算中位数图像(以去除移动元素)。直觉上,硬编码循环遍历所有像素会有很大的运行时间和相当大的内存使用。在OpenCV中是否有一种简单的方法来实现这一点?(我不感兴趣的是平均值,我需要进行中位数计算)。我正在为Android编写此代码(使用OpenCV4Android),因此计算能力有限。


谷歌搜索高效中位数 - Micka
这是一个有趣的问题,但听起来对于你提到的那种硬件来说太具有挑战性了(不算一般情况下Java比C++慢)。你想要涉及多少图像来计算中值?这些图像的分辨率是多少?顺便问一下,你是否已经检查过在每个单独帧内计算中值滤波器是否已经提供了足够好的数据? - Antonio
没有反馈?我有一些想法,但你应该提供更多信息(请参见我的上面的问题)。 - Antonio
2个回答

3
如果平均值没问题:
Mat result(CV_64FC3, listImages[0].size());
for(int i = 0; i < listImages.size(); i++) {
    result += listImages[i];
}
result /= listImages.size();
result.convertTo(result, CV_8UC3);

编辑:

这个快速的伪中位数应该能解决问题:

// Following algorithm will retain the pixel which is the closest to the mean
// Computing Mean
Mat tmpResult = Mat.zeros(listImages[0].size(), CV_64FC3);
for(int i = 0; i < listImages.size(); i++) {
    tmpResult += listImages[i];
}
tmpResult /= listImages.size();
tmpResult.convertTo(tmpResult, CV_8UC3);
// We will now, for each pixel retain the closest to the mean
// Initializing result with the first image
Mat result(listImages[0].clone());
Mat diff1, diff2, minDiff;
for(int i = 1; i < listImages.size(); i++) {
    // Computing diff between mean/newImage and mean/lastResult
    absdiff(tmpResult, listImages[i], diff1);
    absdiff(tmpResult, result, diff2);
    // If a pixel of the new image is closer to the mean, it replaces the old one
    min(diff1, diff2, minDiff);
    // Get the old pixels that are still ok
    result = result & ~(minDiff - diff2);
    // Get the new pixels
    result += listImages[i] & (minDiff - diff2);
}

经典的方法也非常快。它的时间复杂度为O(nb^2 * w * h),其中nb是图像数量,w和h是它们的宽度和高度。上述方法中使用了更多的Mats操作,其时间复杂度为O(nb * w * h)。

以下是经典方法的代码(几乎所有计算都在本地进行):

Mat tmp;
// We will sorting pixels where the first mat will get the lowest pixels and the last one, the highest
for(int i = 0; i < listImages.size(); i++) {
    for(int j = i + 1; j < listImages.size(); j++) {
        listImages[i].copyTo(tmp);
        min(listImages[i], listImages[j], listImages[i]);
        max(listImages[j], tmp, listImages[j]);
    }
}
// We get the median
Mat result = listImages[listImages.size() / 2];

1
OP说:“我对求平均值不感兴趣。” - Antonio
1
为了提高效率,在clone之后考虑使用min max,可能会有一个高效的向量化(SSE/Neon)实现。cv::min(listImages[i],listImages[j],listImages[i]); cv::max(listImages[j],tmp,listImages[j]); (当然,您不需要计算cmp)。 - Antonio
1
“Closest to mean” 可能与中位数非常不同。我认为这并不是 OP 想要的。 - user2983637
1
第二个算法每像素使用O(nb^2)的时间,因为这实际上是朴素排序。这似乎不是非常高效,因为每像素的中位数可以在O(nb)内完成。 - user2983637
1
你可以通过类似于快速排序的方式从数组中获取中位数。但与快速排序不同的是,在每次分区后,你只关心较低一半或较高一半,而不是两者都要。你可以在std::nth_element的文档中了解更多关于这种方法的细节。 - user2983637
显示剩余3条评论

3
据我所知,OpenCV没有创建图像序列中位数的函数。几年前,我也需要相同的功能,不得不自己实现。它相对较慢,因为对于每个像素,您需要从多个图像中提取相关像素(内存访问效率低)并计算中位数(也是一个耗时的过程)。
提高效率的可能方法包括:
  • 无需从所有图像中计算中位数,小的图像子集就足够了。
  • 您可以找到更有效的算法来查找一些小组的中位数。例如,我使用的算法可以在九个值的组中高效地查找中位数。

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