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

Posted

技术标签:

【中文标题】在 OpenCV 中从 RGB 图像访问通道的更快方法?【英文标题】:Faster method of accessing a channel from RGB image in OpenCV? 【发布时间】:2018-01-08 22:19:43 【问题描述】:

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

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);

【问题讨论】:

那么,您只对 3 个频道中的一个感兴趣? (因为您说“频道的图像”)|你会重复拆分吗? @DanMašek 是的,我只需要 3 个通道中的一个,然后我用 200 FPS 相机的图像反复执行此操作。 好的。一方面,分配一个新的cv::Mat 确实会带来开销。根据我的经验,尽可能重用已分配的Mats 是有益的。由于您只需要一个频道,我会尝试cv::mixChannels overloads 之一。 一般来说,分离通道会比简单的算术或逻辑运算更昂贵,矢量化它有点棘手(并且 3 个值的组并不容易)。我并没有真正研究这两个 OpenCV 函数的实现,但如果这是一个瓶颈,那么使用 SIMD 内在函数为这个特定场景编写自己的自定义版本可能是值得的(特别是如果你只需要支持非常小的子集平台)。 @DanMašek 要回答您的问题,在我的代码中,用户选择了一个频道,因此我无法决定使用哪个频道。但是一旦确定,程序将只使用该频道。感谢您的回答。我会试试你的建议。我建议您发布您的答案。 【参考方案1】:

这里有两个简单的选择。

    您提到您对从相机捕获的图像重复执行此操作。因此可以安全地假设图像的大小始终相同。

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

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

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

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


让我们编写一个测试程序来评估上述方法的 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;


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

注意:在 i7-4930K 上运行此程序,使用 OpenCV 3.1.0(64 位,MSVC12.0),Windows 10 -- YMMV,尤其是带有 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_)。

【讨论】:

使用cv::UMat 会不会更快? @karlphillip 值得一试。取决于 OP 是否也可以在 GPU 上完成其余的处理。我的直觉(我可能在这里说出来了,因为我还没有玩过它)说,仅仅为了这个,移动数据的开销可能太大了。待会再玩,有点晚了。

以上是关于在 OpenCV 中从 RGB 图像访问通道的更快方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 OpenCV 2 中从图像中获取通道数?

OpenCV:获取 3 通道 RGB 图像,分割通道并仅使用 R+G 查看图像

将两个 RGB 图像组合成一个 6 通道图像 - openCV

想要从图像中获取绿色 RGB 通道时出错

在openCV中,如何替换图像中的RGB ROI

rgb 到 yuv 的转换和访问 Y、U 和 V 通道