cv::connectedComponents 与 cv::findContours 的实现和速度差异

n00*_*dle 5 c++ opencv connected-components

我正在尝试优化我们的一些计算机视觉算法,并决定cv::connectedComponents对cv::findContours(和cv::drawContours) 进行基准测试以实现类似的结果。

本质上,我们需要做的就是在二值图像中找到斑点,然后选择最大的斑点 - 这是一个相当标准的操作。

我对 OpenCV 的效率有点不太了解,过去几年我只将它用于 Python 中的算法原型设计,所以我决定运行上述两种方法的基准测试。

我对我的结果有点困惑,因为这个评论似乎表明findContours应该慢得多,这与我观察到的相反(结果在帖子的下方)。我怀疑,事实上我的结果表明,findContours在二进制图像上使用,然后将每个轮廓绘制为不同的索引比运行完整的连接组件分析要快一些。

他们还表明,仅计算这些轮廓的面积,而不是计算完整的统计数据,速度connectedComponentsWithStats要快得多。

我是否误解了这里发生的事情?我希望这两种方法会产生相似的结果。


计时结果:

Starting simple benchmark (100000 iterations) ...
2668ms to run 100000 iterations of findContours
3358ms to run 100000 iterations of connectedComponents
Starting area calculation benchmark (100000 iterations) ...
2691ms to run 100000 iterations of findContours
11285ms to run 100000 iterations of connectedComponentsWithStats
AVERAGE TIMES (ms): 
findContours:           0.0267
connectedComps:         0.0336
findContours (areas):   0.0269
connectedComps (areas): 0.113
Run Code Online (Sandbox Code Playgroud)

基准测试代码如下:

#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <chrono>
#include <iomanip>

typedef std::chrono::high_resolution_clock Clock;

cv::Mat src;
cv::Mat src_hsv;
cv::Mat hueChannel;

int threshLow = 230;
int threshHigh = 255;

long numRuns = 100000;
long benchmarkContours(long numRuns, cv::Mat &mask, bool calculateAreas = false) {

    auto start = Clock::now();

    std::vector<std::vector<cv::Point>> contours;
    std::vector<cv::Vec4i> hierarchy;
    std::vector<double> areas;

    for (long run = 0; run < numRuns; ++run) {
        cv::Mat markers = cv::Mat::zeros(mask.size(), CV_8UC1);
        cv::findContours(mask.clone(), contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
        if (calculateAreas) {
            areas = std::vector<double>(contours.size());
        }

        for (unsigned int i = 0; i < contours.size(); i++) {
            if (calculateAreas) {
                areas.push_back(cv::contourArea(contours[i]));
            }
            cv::drawContours(markers, contours, i, cv::Scalar::all(i), -1);
        }
    }

    auto end = Clock::now();

    return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
}

long benchmarkConnComp(long numRuns, cv::Mat &mask, bool calculateAreas = false) {

    auto start = Clock::now();

    cv::Mat labeledImage;
    cv::Mat stats;
    cv::Mat centroids;
    for (long run = 0; run < numRuns; ++run) {
        if (calculateAreas) {
            cv::connectedComponentsWithStats(mask, labeledImage, stats, centroids);
        } else {
            cv::connectedComponents(mask, labeledImage);
        }
    }

    auto end = Clock::now();
    return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();

}

int main(int, char **argv) {
    src = cv::imread(argv[1]);
    if (src.empty()) {
        std::cerr << "No image supplied ..." << std::endl;
        return -1;
    }

    cv::cvtColor(src, src_hsv, cv::COLOR_BGR2HSV_FULL);

    std::vector<cv::Mat> hsvChannels = std::vector<cv::Mat>(3);
    cv::split(src, hsvChannels);

    hueChannel = hsvChannels[0];

    cv::Mat mask;
    cv::inRange(hueChannel, cv::Scalar(threshLow), cv::Scalar(threshHigh), mask);

    std::cout << "Starting simple benchmark (" << numRuns << " iterations) ..." << std::endl;
    long findContoursTime = benchmarkContours(numRuns, mask);
    std::cout << findContoursTime << "ms to run " << numRuns << " iterations of findContours" << std::endl;

    long connCompTime = benchmarkConnComp(numRuns, mask);
    std::cout << connCompTime << "ms to run " << numRuns << " iterations of connectedComponents" << std::endl;
    std::cout << "Starting area calculation benchmark (" << numRuns << " iterations) ..." << std::endl;

    long findContoursTimeWithAreas = benchmarkContours(numRuns, mask, true);
    std::cout << findContoursTimeWithAreas << "ms to run " << numRuns << " iterations of findContours" << std::endl;

    long connCompTimeWithAreas = benchmarkConnComp(numRuns, mask, true);
    std::cout << connCompTimeWithAreas << "ms to run " << numRuns << " iterations of connectedComponentsWithStats" << std::endl;

    std::cout << "AVERAGE TIMES: " << std::endl;
    std::cout << "findContours:           " << std::setprecision(3) << (1.0f * findContoursTime) / numRuns << std::endl;
    std::cout << "connectedComps:         " << std::setprecision(3) << (1.0f * connCompTime) / numRuns <<  std::endl;
    std::cout << "findContours (areas):   " << std::setprecision(3) << (1.0f * findContoursTimeWithAreas) / numRuns << std::endl;
    std::cout << "connectedComps (areas): " << std::setprecision(3) << (1.0f * connCompTimeWithAreas) / numRuns <<  std::endl;
}
Run Code Online (Sandbox Code Playgroud)

Nic*_*las -1

我还没有真正研究过 OpenCV 中的这两个函数,但我认为该connectedcomponents()函数更多地依赖于图像大小,因为它可能会对图像进行某种多线程光栅化(逐行处理)。然而该findcontour()函数可能会以某种方式沿着轮廓行走,因此性能将取决于斑点本身的复杂性和大小,而不是图像大小。