【问题标题】:How can I make this faster? (C/C++) OpenCV我怎样才能让它更快? (C/C++) OpenCV
【发布时间】:2012-03-23 10:37:50
【问题描述】:

我正在处理视频中的帧并实时(实时)显示。该算法很快,但我想知道是否可以进行任何优化以使其更加无缝。我不知道我的算法中哪些函数占用的时间最多,我猜是 sqrt() 函数,因为它显然做了一些查找,但我不确定。

这是我的算法:

IplImage *videoFrame = cvCreateImage(cvSize(bufferWidth, bufferHeight), IPL_DEPTH_8U, 4);
videoFrame->imageData = (char*)bufferBaseAddress;
int channels = videoFrame->nChannels;
int widthStep = videoFrame->widthStep;
int width = videoFrame->width;
int height = videoFrame->height;

for(int i=0;i<height;i++){

    uchar *col = ((uchar *)(videoFrame->imageData + i*widthStep));

    for(int j=0;j<width;j++){

        double pRed     = col[j*channels + 0];                      
        double pGreen   = col[j*channels + 1];       
        double pBlue    = col[j*channels + 2];       

        double dRed     = green.val[0] - pRed;
        double dGreen   = green.val[1] - pGreen;
        double dBlue    = green.val[2] - pBlue;

        double sDRed    = dRed * dRed;
        double sDGreen  = dGreen * dGreen;
        double sDBlue   = dBlue * dBlue;


        double sum = sDRed + sDGreen + sDBlue;

        double euc = sqrt(sum);
        //NSLog(@"%f %f %f", pRed, pGreen, pBlue);

        if (euc < threshold) {
            col[j*channels + 0] = white.val[0];
            col[j*channels + 1] = white.val[1];
            col[j*channels + 2] = white.val[2];
        }

    }
}

谢谢!

更新 好的,它的作用是遍历图像中的每个像素,并计算像素颜色和绿色之间的欧几里得距离。所以,总的来说这是一个绿屏算法。

我做了一些基准测试,没有使用这个算法的 fps 是 30.0fps。使用这个算法,它下降到大约 8fps。但是,大部分 for drop 来自 col[j*channels + 0]; 如果算法不做任何其他事情并使用数组选择的访问,它会下降到大约 10fps。

更新 2 好的,这很有趣,我正在从双循环内的内容中删除随机行,以查看导致更大开销的原因,这就是我发现的:在堆栈上创建变量会导致 FPS 大幅下降。考虑这个例子:

for(int i=0;i<height;i++){

    uchar *col = ((uchar *)(data + i*widthStep));

    for(int j=0;j<width;j++){

        double pRed     = col[j*channels + 0];                      
        double pGreen   = col[j*channels + 1];       
        double pBlue    = col[j*channels + 2];       

    }
}

这会将 fps 降至 11-ish。

另一方面,现在:

for(int i=0;i<height;i++){

    uchar *col = ((uchar *)(data + i*widthStep));

    for(int j=0;j<width;j++){

        col[j*channels + 0];                      
        col[j*channels + 1];       
        col[j*channels + 2];       

    }
}

根本不会降低 FPS! FPS 保持在 30.0 左右。认为我应该更新这个,让你们知道这是真正的瓶颈,使变量不是他的堆栈。我想知道我是否内联了所有可能获得纯 30.0fps 的内容。

Nvm...也许没有分配给 var 的表达式甚至没有被计算。

【问题讨论】:

  • 您在自行分析之前问这个问题有什么原因吗?
  • 在网上搜索“数据驱动设计”;一些文章讲述了如何优化处理器的数据和指令缓存。
  • 第二次更新...发现了一些有趣的东西...
  • @PragmaOnce:这些表达式没有被评估,因为它们没有副作用。

标签: c++ c algorithm image-processing opencv


【解决方案1】:

sqrt 是一个单调递增函数,您似乎只在阈值测试中使用它。

由于单调性,sqrt(sum) &lt; threshold 等价于sum &lt; threshold * threshold(假设阈值为正)。

没有更昂贵的平方根,编译器会将乘法移到循环之外。


下一步,您可以从内部循环中删除昂贵的乘法 j * channels。编译器应该足够聪明,只执行一次并使用结果 3 次,但它仍然是一个乘法,其余的计算都依赖于它,因此会损害流水线。

还记得乘法与重复加法相同吗?通常做更多的操作更昂贵,但在这种情况下,由于循环,你已经有了重复部分。所以使用:

for(int j=0;j<width;j++){
    double pRed     = col[0];
    double pGreen   = col[1];
    double pBlue    = col[2];

    double dRed     = green.val[0] - pRed;
    double dGreen   = green.val[1] - pGreen;
    double dBlue    = green.val[2] - pBlue;

    double sDRed    = dRed * dRed;
    double sDGreen  = dGreen * dGreen;
    double sDBlue   = dBlue * dBlue;


    double sum = sDRed + sDGreen + sDBlue;

    //NSLog(@"%f %f %f", pRed, pGreen, pBlue);

    if (sum < threshold * threshold) {
        col[0] = white.val[0];
        col[1] = white.val[1];
        col[2] = white.val[2];
    }

    col += channels;
}

接下来,您在uchardouble 之间进行了昂贵的转换。阈值测试不需要这些:

int j = width;
do {
    int_fast16_t const pRed   = col[0];
    int_fast16_t const pGreen = col[1];
    int_fast16_t const pBlue  = col[2];

    int_fast32_t const dRed   = green.val[0] - pRed;
    int_fast32_t const dGreen = green.val[1] - pGreen;
    int_fast32_t const dBlue  = green.val[2] - pBlue;

    int_fast32_t const sDRed   = dRed * dRed;
    int_fast32_t const sDGreen = dGreen * dGreen;
    int_fast32_t const sDBlue  = dBlue * dBlue;

    int_fast32_t const sum = sDRed + sDGreen + sDBlue;

    //NSLog(@"%f %f %f", pRed, pGreen, pBlue);

    if (sum < threshold * threshold) {
        col[0] = white.val[0];
        col[1] = white.val[1];
        col[2] = white.val[2];
    }

    col += channels;
} while (--j);

【讨论】:

  • 我认为最好在开始时说设置阈值 *= 阈值,而不是每次都计算阈值。如果你改变这个,你的答案会比我的好。
  • @Saeed:我认为编译器足够聪明,可以做到这一点。
  • @SaeedAmiri:这是最简单且成本最低的优化之一,如果它不包含在-O1 中,我会感到非常惊讶。
【解决方案2】:

过早的优化总是一件坏事——如果真的有必要,需要有确凿的证据支持。在几乎所有情况下,编译器都会很好地优化代码的细节——降低高级函数的复杂性是你的工作。

与其尝试优化这段特定的代码,不如先检查一下你的性能在程序的其他地方没有出现瓶颈,然后检查你是否可以避免一开始就调用这个函数地方。只有当您确定除了优化此代码之外别无他法时,您才应该开始考虑优化此代码。

如果您真的必须优化此代码,最好的方法是使用 MMX 和 SIMD 指令将所有双“三元组”基本矢量化为单指令。

【讨论】:

  • 这是去掉平方根后的一个很好的下一步。
【解决方案3】:

好吧,在不知道你的算法做什么的情况下,如果你想稍微改进一下,你可以摆脱sqrt 调用。只需替换:

double euc = sqrt(sum);

if (euc < threshold) {
    ....
}

作者:

if (sum < threshold_2) {
    ....
}

其中threshold_2 等于threshold * threshold,您可以预先计算并退出循环。

这会给它带来一点性能提升,但不要期望太高。

【讨论】:

    【解决方案4】:

    sqrt 很慢。为什么不在外循环之前计算double threshold_sq = threshold * threshold; 并使用sum &lt; threshold_sq 进行比较。此外,restrict 关键字可能对您有所帮助,也可能无济于事。

    【讨论】:

      【解决方案5】:

      我建议研究一下 Valgrind 之类的东西。它有一堆有用的测试,几乎可以分析你的每一段代码。

      【讨论】:

        【解决方案6】:

        鉴于您对col[j*channels + 0]; 花费大量时间的评论:channels 总是 3 吗?甚至总是4?如果是这样,您可以通过仅推进指针来避免偏移数学,如下所示:

        for(int i=0;i<height;i++){
           uchar *col = ((uchar *)(videoFrame->imageData + i*widthStep));   
           for(int j=0;j<width;j++){
              double dRed     = green.val[0] - *col++;   
              double dGreen   = green.val[1] - *col++;  
              double dBlue    = green.val[2] - *col++; 
        
           //math here
        
           if (euc < thresholdSqrd) {
             *(col-3) = white.val[0];
             *(col-2) = white.val[1];
             *(col-1) = white.val[2];
           }
           col++; //do this only if `channels`==4
        }
        

        此外,由于您的原始数据似乎是 rgb 作为连续字节,您可以使用 *(int32_t*)(col-3) |= 0xFFFFFF; 将像素设置为白色

        并且以整数形式进行减法可能会稍微快一些(将 green 存储为整数):

              int16_t iRed     = green.val[0] - *col++;   
              int16_t iGreen   = green.val[1] - *col++;  
              int16_t iBlue    = green.val[2] - *col++; 
              double euc = (double)iRed*iRed + iGreen*iGreen + iBlue*iBlue;
        

        【讨论】:

        • AShelly,感谢您的意见。我喜欢指针算法,我现在正在使用它。但我发现了一些非常有趣的东西,我会更新我原来的问题。有点令人惊讶....
        【解决方案7】:

        如果您使用 Linux,请查看 oprofile 和实用程序 perf(随内核源代码一起提供)。

        顺便说一句,UPDATE2 中的代码可能根本不做任何事情,它被编译出来,因为表达式的效果没有存储在任何地方。在这种情况下,编译器决定根本不把它放在输出中。用-S(汇编器输出)编译代码,看看。

        【讨论】:

          【解决方案8】:

          您正在使用嵌套的for loops,但我根本没有看到您使用外部循环中的变量。如果所写的内容实际上是正确的,我建议您修改外部for loop,这会将您的运行时间从O(n^2) 更改为O(n)

          【讨论】:

          • 啊,你是对的。但是,如果height &lt;= width 他可以将cols 包含在一个循环中,对吗?
          • @noMAD:可以使用一个循环,但它的复杂性将是O(width * height),就像嵌套循环一样。
          • @BenVoigt:我不同意所有情况。例如,我上面提到的那个,你只需要O(width)
          • @noMAD:他正在测试和更新width*height 不同的像素,O(width) 没有办法做到这一点。
          猜你喜欢
          • 2011-06-14
          • 1970-01-01
          • 1970-01-01
          • 2014-07-05
          • 2020-12-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-05-27
          相关资源
          最近更新 更多