如何为 OpenCV 多核图像处理创建 TBB 任务调度程序? C++

How to create TBB Task Scheduler for OpenCV multicore image processing? C++

我正在学习使用 OpenCV 和 TBB。我需要学习如何使用图像的多处理,因为我有多核 CPU 并且想为我的程序创建 muticpu 支持。

我已阅读英特尔® 技术期刊论文中的一篇文章 "The Foundations for Scalable Multi-core Software in Intel® Threading Building Blocks"(您可以在此处的 pdf 中找到它 http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.71.8289&rep=rep1&type=pdf

他们使用法波纳奇数计算作为多处理的例子。在 TBB 包中的 TBB 示例中也有类似的 fabonacci number 示例(请参阅 ParallelTask​​Fib)。唯一的问题是计算非常简单,CPU 负担不大,因此当您 运行 对小数字进行多任务处理时,低 CutOff 效率不高,因为它需要太多开销。因此,要学习使用 TBB,我需要更多来自图像处理的实际示例。在我的概念中,我想使用 TBB Task Scheduler。我从 class FibTask 和我重命名的函数 ParallelFib 开始,更改了参数以处理图像向量。它的设计基本原则应该保持不变。 fabonacci 示例仅包含两个 children,称为 a 和 b。现在的问题是我不确定我是否可以在一个函数matTask(最初称为'execute')中使用两个以上的children。所以我尝试添加更多的调用,更多的指针和更多的等待spawn_and_wait_for_all()...在这个阶段我没有创建任何图像处理功能,因为我想问你这个设计是否正确,是否会有应该不是性能问题。它还没有完成。我会等待您的建议来修复我的概念中可能存在的错误。

我的基本想法是在 lena.jpg 上使用一些滤镜功能,例如高斯模糊。首先,我会传递一些线程。我有 8 个内核,所以最多只能传递 8 个线程。我计划将 lena 图像分成 8 个相同大小的条带,然后将像素复制到矢量(8 个基本矢量),然后它们应该被模糊。然后另一个阶段是我需要创建接下来的 7-8 个图像,这些图像与 8 个部分的边距重叠。我只想重复模糊动作。最后,可能是图像其余部分的区域需要再通过一次(source_image.rows()/8 中的剩余部分)。

我需要解决的主要问题(我不知道该怎么做)是停止无限循环。我应该为 1) 应对和 2) 模糊 3) 裁剪 4) 粘贴创建不同的 class 和不同的方法吗?或者我可以在一个电话中传递所有内容(复制+模糊)吗?这是与 fabonnaci 数字示例的区别,因为该代码做了同样的事情,但我需要做更多不同的事情......那么逻辑应该是什么,如何排序,如何命名函数?

更简单的解决方案是使用 8 个相同大小的条带......然后 7-8 个重叠区域。

下面的代码没有打印错误,但它不应该return正确的结果,因为它只是时间概念。

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

#include "tbb/task.h"
#include "tbb/task_scheduler_init.h"

#define CutOff 12

using namespace cv;

void SerialAction(int n){};

/**

**/
class matTask: public tbb::task {
public:
    int n;
    const int offset;
    std::vector<cv::Mat> main_layers;
    std::vector<cv::Mat> overlay_layers;

    matTask( std::vector<cv::Mat>main_layers_, std::vector<cv::Mat> overlay_layers_, int n_, const int offset_ ) :
        main_layers(main_layers_),
        overlay_layers(overlay_layers_),
        n(n_), offset(offset_)
        {}

        task* execute() {
        if( n<CutOff ) {
             SerialAction(n);
            } 
        else {
            // Main layers - copy regions
            matTask& a = *new( allocate_child() )
                matTask(main_layers,overlay_layers,n,0);
            matTask& b = *new( allocate_child() )
                matTask(main_layers,overlay_layers,n-1,0);
            matTask& c = *new( allocate_child() )
                matTask(main_layers,overlay_layers,n-2,0);
            matTask& d = *new( allocate_child() )
                matTask(main_layers,overlay_layers,n-3,0);
            matTask& e = *new( allocate_child() )
                matTask(main_layers,overlay_layers,n-4,0);
            matTask& f = *new( allocate_child() )
                matTask(main_layers,overlay_layers,n-5,0);
            matTask& g = *new( allocate_child() )
                matTask(main_layers,overlay_layers,n-6,0);
            matTask& h = *new( allocate_child() )
                matTask(main_layers,overlay_layers,n-7,0);

            spawn_and_wait_for_all( a );
            spawn_and_wait_for_all( b );
            spawn_and_wait_for_all( c );
            spawn_and_wait_for_all( d );
            spawn_and_wait_for_all( e );
            spawn_and_wait_for_all( f );
            spawn_and_wait_for_all( g );
            spawn_and_wait_for_all( h );
            // In the case of effect:
            // Overlay layers

            matTask& ab = *new( allocate_child() )
                matTask(main_layers,overlay_layers,n,offset);
            matTask& bc = *new( allocate_child() )
                matTask(main_layers,overlay_layers,n-1,offset);
            matTask& cd = *new( allocate_child() )
                matTask(main_layers,overlay_layers,n-2,offset);
            matTask& de = *new( allocate_child() )
                matTask(main_layers,overlay_layers,n-2,offset);
            matTask& ef = *new( allocate_child() )
                matTask(main_layers,overlay_layers,n-2,offset);
            matTask& gh = *new( allocate_child() )
                matTask(main_layers,overlay_layers,n-2,offset);

            // ... + crop .. depends on size of kernel

            set_ref_count(8);
            spawn( b );
            spawn_and_wait_for_all( a );
        }
    return NULL;
    }
};
void ParallelAction( std::vector<cv::Mat> main, std::vector<cv::Mat> overlays, int n, const int offset ) {
    matTask& a = *new(tbb::task::allocate_root())
    matTask(main, overlays, n,offset);
    tbb::task::spawn_root_and_wait(a);
}

int main( int argc, char** argv )
{       
    int threads = 8;

    std::vector<cv::Mat> main_layers;
    std::vector<cv::Mat> overlays;

    cv:: Mat sourceImg;
    sourceImg = imread( "../../data/lena.jpg");
    if ( sourceImg.empty() )
        return -1;

    const int offset = (int) sourceImg.rows / threads;


    cv::setNumThreads(0);
    ParallelAction(main_layers, overlays, threads, offset );

    // GaussianBlur( src, dst, Size(3,3), 0, 0, BORDER_DEFAULT );

    return 0;
}

编辑: 对 Anton 的回答的反应。如果我使用 operator() 重载,究竟什么时候应用 operator()?也可以向 ApplyFoo 添加一些方法吗? W()重载时,好像只能有一个方法

void Foo(float a){};

class ApplyFoo {
    float *const my_a;  
public:
    void operator()( const tbb::blocked_range<size_t>& r ) const {
        float *a = my_a;
        for( size_t i=r.begin(); i!=r.end(); ++i ) 
           Foo(a[i]);
    }
    ApplyFoo( float a[] ) :
        my_a(a) // initiate my_a
    {}
};

您所指的文章是 2007 年的!它非常过时(尽管仍然相关,因为 TBB 保持所有源兼容性)。 tbb::task 接口被认为是低级的,对于应用程序开发来说不是那么方便。请 refer to tbb::parallel_fortbb::parallel_invoke,尤其是直接支持取消的 tbb::task_group