我怎样才能更快?(C/C++)OpenCV

How can I make this faster? (C/C++) OpenCV

本文关键字:C++ OpenCV      更新时间:2023-10-16

我正在处理视频中的帧并实时显示。算法很快,但我想知道我是否可以做任何优化,使其更加无缝。我不知道我的算法中哪些函数占用的时间最多,我猜是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];
        }
    }
}

谢谢!

更新好的,这样做是在图像中的每个像素上循环,并计算像素颜色和绿色之间的欧几里得距离。所以,总的来说,这是一个绿屏算法。

我做了一些基准测试,没有使用这个算法的帧速率是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的表达式甚至都没有求值。

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

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

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


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

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

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

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

与其试图优化这段特定的代码,不如先检查你的性能是否在程序的其他地方受到瓶颈影响,然后检查你是否可以从一开始就避免调用这个函数。只有当你确定除了优化这个代码之外别无选择时,你才应该开始考虑优化这个代码。

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

好吧,在不知道算法的作用的情况下,如果你想稍微改进一下,你可以取消sqrt调用。只需更换:

double euc = sqrt(sum);
if (euc < threshold) {
    ....
}

签字人:

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

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

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

sqrt相当慢。为什么不在外循环之前计算double threshold_sq = threshold * threshold;,并使用sum < threshold_sq进行比较。此外,restrict关键字可能对您有帮助,也可能没有帮助。

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

鉴于您对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存储为int):

      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;

如果您在Linux上,请查看opfile和实用程序perf(随内核源代码提供)。

顺便说一句,UPDATE2中的代码可能根本没有任何作用,它是编译出来的,因为驱逐的效果没有存储在任何地方。在这种情况下,编译器决定根本不把它放在输出中。使用-S(汇编程序输出)编译代码并查看。

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