在OpenCV中从RGB图像访问频道的更快方法?

时间:2018-01-08 22:19:43

标签: c++ image opencv

在我使用1409x900和960x696图像的试验中,在我的64位6核3.2 GHz Windows机器中使用OpenCV分割RGB图像的通道平均需要2.5 ms。

vector<cv::Mat> channels;
cv::split(img, channels);

我发现其他图像处理的时间差不多(布尔操作+形态开放)。

考虑到我的代码只使用了分割中的通道图像,我想知道是否有更快的方法从RGB图像中提取单个通道,最好使用OpenCV。

更新

正如@DanMašek指出的那样,还有另一个函数mixChannels可以从多通道中提取单个通道图像。我测试了大约2000张相同尺寸的图像。 mixChannels平均花了大约1毫秒。现在,我对结果感到满意。但是如果你能更快地发布你的答案。

cv::Mat channel(img.rows, img.cols, CV_8UC1);
int from_to[] = { sel_channel,0 };
mixChannels(&img, 1, &channel, 1, from_to, 1);

1 个答案:

答案 0 :(得分:4)

这里有两个简单的选择。

  1. 您提到您对从相机拍摄的图像反复执行此操作。因此可以安全地假设图像总是相同的大小。

    cv::Mat的分配具有不可忽略的开销,因此在这种情况下重用信道Mat将是有益的。 (即,当您收到第一帧时分配目标图像,然后只覆盖后续帧的内容)

    这种方法的额外好处(很可能)减少了内存碎片。这可能成为32位代码的真正问题。

  2. 您提到您只对一个特定频道感兴趣(用户可以任意选择)。这意味着您可以使用cv::mixChannels,这使您可以灵活地选择要提取的渠道以及如何提取它们。

    这意味着您可以只提取单个通道的数据,理论上(取决于实现 - 研究源代码以获取更多详细信息),避免提取和/或复制您不感兴趣的通道的数据的开销英寸

  3. 让我们制作一个测试程序,评估上述方法的4种可能组合。

    • 变体0:cv::split无需重复使用
    • 变式1:cv::split并重复使用
    • 变体2:cv::mixChannels无需重复使用
    • 变式3:cv::mixChannels并重复使用

    注意:我这里只是简单地使用static,通常我会在包含算法的类中创建此成员变量。

    #include <opencv2/opencv.hpp>
    
    #include <chrono>
    #include <cstdint>
    #include <iostream>
    #include <vector>
    
    #define SELECTED_CHANNEL 1
    
    cv::Mat variant_0(cv::Mat const& img)
    {
        std::vector<cv::Mat> channels;
        cv::split(img, channels);
        return channels[SELECTED_CHANNEL];
    }
    
    cv::Mat variant_1(cv::Mat const& img)
    {
        static std::vector<cv::Mat> channels;
        cv::split(img, channels);
        return channels[SELECTED_CHANNEL];
    }
    
    cv::Mat variant_2(cv::Mat const& img)
    {
        // NB: output Mat must be preallocated
        cv::Mat channel(img.rows, img.cols, CV_8UC1);
        int from_to[] = { SELECTED_CHANNEL, 0 };
        cv::mixChannels(&img, 1, &channel, 1, from_to, 1);
        return channel;
    }
    
    cv::Mat variant_3(cv::Mat const& img)
    {
        // NB: output Mat must be preallocated
        static cv::Mat channel(img.rows, img.cols, CV_8UC1);
        int from_to[] = { SELECTED_CHANNEL, 0 };
        cv::mixChannels(&img, 1, &channel, 1, from_to, 1);
        return channel;
    }
    
    template<typename T>
    void timeit(std::string const& title, T f)
    {
        using std::chrono::high_resolution_clock;
        using std::chrono::duration_cast;
        using std::chrono::microseconds;
    
        cv::Mat img(1024,1024, CV_8UC3);
        cv::randu(img, 0, 256);
    
        int32_t const STEPS(1024);
    
        high_resolution_clock::time_point t1 = high_resolution_clock::now();
        for (uint32_t i(0); i < STEPS; ++i) {
            cv::Mat result = f(img);
        }
        high_resolution_clock::time_point t2 = high_resolution_clock::now();
    
        auto duration = duration_cast<microseconds>(t2 - t1).count();
        double t_ms(static_cast<double>(duration) / 1000.0);
        std::cout << title << "\n"
            << "Total = " << t_ms << " ms\n"
            << "Iteration = " << (t_ms / STEPS) << " ms\n"
            << "FPS = " << (STEPS / t_ms * 1000.0) << "\n"
            << "\n";
    }
    
    int main()
    {
        for (uint8_t i(0); i < 2; ++i) {
            timeit("Variant 0", variant_0);
            timeit("Variant 1", variant_1);
            timeit("Variant 2", variant_2);
            timeit("Variant 3", variant_3);
            std::cout << "--------------------------\n\n";
        }
    
        return 0;
    }
    

    第二遍的输出(因此我们避免任何预热成本)。

    注意:使用OpenCV 3.1.0(64位,MSVC12.0),Windows 10 - YMMV在i7-4930K上运行此功能,特别是对于具有AVX2的CPU

    Variant 0
    Total = 1518.69 ms
    Iteration = 1.48309 ms
    FPS = 674.267
    
    Variant 1
    Total = 359.048 ms
    Iteration = 0.350633 ms
    FPS = 2851.99
    
    Variant 2
    Total = 820.223 ms
    Iteration = 0.800999 ms
    FPS = 1248.44
    
    Variant 3
    Total = 427.089 ms
    Iteration = 0.417079 ms
    FPS = 2397.63
    

    有趣的是,重用的cv::split在这里获胜。随意编辑答案并添加来自不同平台/ CPU代的时间(特别是如果比例根本不同)。

    似乎在我的设置中,这些都没有很好地并行化,所以这可能是另一种可能的加速(类似cv::parallel_for_)的道路。