zoukankan      html  css  js  c++  java
  • 对比使用C# unsafe代码和OpenCV进行图像处理的效率(中)

            方便起见,下面再贴一下上次的对比结果:

            1、


            2、




            注意:由于上面两次比较不在同一系统上,Stopwatch.Frequency的值可能不同,故这两次的结果之间不具有可比性!!


    ——————————————————————————————————————————————————————

            先分析下Add/Sub两个方法,在Image类里使用的是指针+TPL(.NET 并行任务库),使用Parallel.For方法对每一行进行循环,这个应该很容易看懂,就不多讲了。下面来看下OpenCV是如何实现的:

        

    //文件:opencv\modules\core\src
    void cv::add( InputArray src1, InputArray src2, OutputArray dst,
              InputArray mask, int dtype )
    {
        arithm_op(src1, src2, dst, mask, dtype, addTab );
    }
    
    void cv::subtract( InputArray src1, InputArray src2, OutputArray dst,
                   InputArray mask, int dtype )
    {
        arithm_op(src1, src2, dst, mask, dtype, subTab );
    }
    
    

        原来这两个方法只是个代理,真正干活的是arithm_op,这个方法太长,就不在这贴了,不然有占版面的嫌疑,有兴趣的可以自己去看。沿着这个方法一路追踪,我们可以看到类似于这样的代码:

        #if CV_SSE2
            if( USE_SSE2 )
            {
                for( ; x <= sz.width - 32; x += 32 )
                {
                    __m128i r0 = _mm_loadu_si128((const __m128i*)(src1 + x));
                    __m128i r1 = _mm_loadu_si128((const __m128i*)(src1 + x + 16));
                    r0 = op8(r0,_mm_loadu_si128((const __m128i*)(src2 + x)));
                    r1 = op8(r1,_mm_loadu_si128((const __m128i*)(src2 + x + 16)));
                    _mm_storeu_si128((__m128i*)(dst + x), r0);
                    _mm_storeu_si128((__m128i*)(dst + x + 16), r1);
                }
                for( ; x <= sz.width - 8; x += 8 )
                {
                    __m128i r0 = _mm_loadl_epi64((const __m128i*)(src1 + x));
                    r0 = op8(r0,_mm_loadl_epi64((const __m128i*)(src2 + x)));
                    _mm_storel_epi64((__m128i*)(dst + x), r0);
                }
            }
        #endif

        一切真相大白了,原来OpenCV使用了SSE2汇编指令集。这里简单说下SSE,SSE是由Intel引入的一种单指令多数据指令集,它包括若干128位的寄存器,可以在一个频率周期内同时同时执行128位乘法、128位加法、128位数据加载与128位数据回存,或着是4个32位单倍浮点精确度乘法与4个32位单倍浮点精确度加法运算。OpenCV的效率正来源于此!

        那么为什么在第一台电脑上,Image和OpenCV比分约为2:1,第二台就变成5:1了呢?因为OpenCV是单线程运算,而Image使用了最新的TPL,TPL是在.NET 4.0中引入的一个并行任务库,Parallel.For类似于使用多线程来进行For循环,并且能够更新CPU核心数来决定创建的线程数,也就是说,Image使用了多线程构架,这使得它在多核CPU上有优势。第一台电脑4核,第二台电脑双核,原因就在于此!

        再看Mul方法,在第一台电脑上,Image类居然以1:2的成绩奇迹般的胜过了OpenCV,有点不可思议!我们看看OpenCV的代码:

    void cv::multiply(InputArray src1, InputArray src2,
                      OutputArray dst, double scale, int dtype)
    {
        arithm_op(src1, src2, dst, noArray(), dtype, mulTab, true, &scale);
    }

    mulTab是一个针对不同数据类型的方法列表,随便挑一个看看,就看mul8u吧:

    static void mul8u( const uchar* src1, size_t step1, const uchar* src2, size_t step2,
                       uchar* dst, size_t step, Size sz, void* scale)
    {
        mul_(src1, step1, src2, step2, dst, step, sz, (float)*(const double*)scale);
    }

    template<typename T, typename WT> static void
    mul_( const T* src1, size_t step1, const T* src2, size_t step2,
          T* dst, size_t step, Size size, WT scale )
    {
        step1 /= sizeof(src1[0]);
        step2 /= sizeof(src2[0]);
        step /= sizeof(dst[0]);
    
    
        if( scale == (WT)1. )
        {
            for( ; size.height--; src1 += step1, src2 += step2, dst += step )
            {
                int i;
                for( i = 0; i <= size.width - 4; i += 4 )
                {
                    T t0;
                    T t1;
                    t0 = saturate_cast<T>(src1[i  ] * src2[i  ]);
                    t1 = saturate_cast<T>(src1[i+1] * src2[i+1]);
                    dst[i  ] = t0;
                    dst[i+1] = t1;
    
    
                    t0 = saturate_cast<T>(src1[i+2] * src2[i+2]);
                    t1 = saturate_cast<T>(src1[i+3] * src2[i+3]);
                    dst[i+2] = t0;
                    dst[i+3] = t1;
                }
    
    
                for( ; i < size.width; i++ )
                    dst[i] = saturate_cast<T>(src1[i] * src2[i]);
            }
        }
        else
        {
            for( ; size.height--; src1 += step1, src2 += step2, dst += step )
            {
                int i;
                for( i = 0; i <= size.width - 4; i += 4 )
                {
                    T t0 = saturate_cast<T>(scale*(WT)src1[i]*src2[i]);
                    T t1 = saturate_cast<T>(scale*(WT)src1[i+1]*src2[i+1]);
                    dst[i] = t0; dst[i+1] = t1;
    
    
                    t0 = saturate_cast<T>(scale*(WT)src1[i+2]*src2[i+2]);
                    t1 = saturate_cast<T>(scale*(WT)src1[i+3]*src2[i+3]);
                    dst[i+2] = t0; dst[i+3] = t1;
                }
    
    
                for( ; i < size.width; i++ )
                    dst[i] = saturate_cast<T>(scale*(WT)src1[i]*src2[i]);
            }
        }
    }

        也是直接相乘,并未使用SSE(不知道为什么,有知道的告诉下),但是它是以4个为间隔进行循环,减少了循环次数,因此效率会得到提升;但受限于单线程运算,无法充分利用多核优势,因此在4核电脑上,其成绩落后Image一大截; 在双核电脑上,OpenCV反超Image,看来我需要对Image也是用这种优化方法(--!!)。


        再看Threshold,在第一台电脑上,两者半斤八两,OpenCV险胜,第二台电脑,Image 2.5:1败北。来看看OpenCV的代码:

    //文件:opencv\modules\imgproc\src\thresh.cpp
    
    double cv::threshold( InputArray _src, OutputArray _dst, double thresh, double maxval, int type )
    {
        Mat src = _src.getMat();
        bool use_otsu = (type & THRESH_OTSU) != 0;
        type &= THRESH_MASK;
    
        if( use_otsu )
        {
            CV_Assert( src.type() == CV_8UC1 );
            thresh = getThreshVal_Otsu_8u(src);
        }
      
        _dst.create( src.size(), src.type() );
        Mat dst = _dst.getMat();
        
        if( src.depth() == CV_8U )
        {
            int ithresh = cvFloor(thresh);
            thresh = ithresh;
            int imaxval = cvRound(maxval);
            if( type == THRESH_TRUNC )
                imaxval = ithresh;
            imaxval = saturate_cast<uchar>(imaxval);
    
            if( ithresh < 0 || ithresh >= 255 )
            {
                if( type == THRESH_BINARY || type == THRESH_BINARY_INV ||
                    ((type == THRESH_TRUNC || type == THRESH_TOZERO_INV) && ithresh < 0) ||
                    (type == THRESH_TOZERO && ithresh >= 255) )
                {
                    int v = type == THRESH_BINARY ? (ithresh >= 255 ? 0 : imaxval) :
                            type == THRESH_BINARY_INV ? (ithresh >= 255 ? imaxval : 0) :
                            type == THRESH_TRUNC ? imaxval : 0;
                    dst.setTo(v);
                }
                else
                    src.copyTo(dst);
            }
            else
                thresh_8u( src, dst, (uchar)ithresh, (uchar)imaxval, type );
        }
        else if( src.depth() == CV_32F )
            thresh_32f( src, dst, (float)thresh, (float)maxval, type );
        else
            CV_Error( CV_StsUnsupportedFormat, "" );
    
        return thresh;
    }

        这仍然只是一个代理,下面看thresh_8u的代码(由于太长,所以只贴其中关键部分片段):

    //下面是针对TEGRA处理器的优化方法,跳过
    #ifdef HAVE_TEGRA_OPTIMIZATION
        switch( type )
        {
        case THRESH_BINARY:
            if(tegra::thresh_8u_binary(_src, _dst, roi.width, roi.height, thresh, maxval)) return;
    。。。。。。
    
    //从下面可以看出,OpenCV采用的是查表法
        switch( type )
        {
        case THRESH_BINARY:
            for( i = 0; i <= thresh; i++ )
                tab[i] = 0;
            for( ; i < 256; i++ )
                tab[i] = maxval;
            break;
        case THRESH_BINARY_INV:
            for( i = 0; i <= thresh; i++ )
                tab[i] = maxval;
            for( ; i < 256; i++ )
                tab[i] = 0;
            break;
        case THRESH_TRUNC:
            for( i = 0; i <= thresh; i++ )
                tab[i] = (uchar)i;
            for( ; i < 256; i++ )
                tab[i] = thresh;
            break;
        case THRESH_TOZERO:
            for( i = 0; i <= thresh; i++ )
                tab[i] = 0;
            for( ; i < 256; i++ )
                tab[i] = (uchar)i;
            break;
        case THRESH_TOZERO_INV:
            for( i = 0; i <= thresh; i++ )
                tab[i] = (uchar)i;
            for( ; i < 256; i++ )
                tab[i] = 0;
            break;
        default:
            CV_Error( CV_StsBadArg, "Unknown threshold type" );
        }
    
    
    //如果CPU支持SSE的话,会使用SSE进行运算
    #if CV_SSE2
        if( checkHardwareSupport(CV_CPU_SSE2) )
        {
            __m128i _x80 = _mm_set1_epi8('\x80');
            __m128i thresh_u = _mm_set1_epi8(thresh);
    。。。。。。
    
    //否则会使用普通方法,以4为单位进行循环运算
        if( j_scalar < roi.width )
        {
            for( i = 0; i < roi.height; i++ )
            {
                const uchar* src = (const uchar*)(_src.data + _src.step*i);
                uchar* dst = (uchar*)(_dst.data + _dst.step*i);
                
                for( j = j_scalar; j <= roi.width - 4; j += 4 )
                {
    。。。。。。


            下面是Smooth,所谓Smooth,就是遍历图像中的每个像素,将其值设为以它为中心的一个方格内所有像素的平均值。直接按照定义写算法会非常慢,Image类的算法主要是利用图像像素排列上的连续性来排除重复计算,其效率比直接按定义算提高了200多倍!!!OpenCV里的算法没看明白,谁看明白了告诉我下,万分感谢!!文件在opencv\modules\imgproc\src\smooth.cpp


            至于AddWeighted,代码也很简单,无非就是SSE+for循环优化(以4位步长),就不细说了。


            通过上边的分析,我们知道了OpenCV算法优化的原理,那么能不能对Image类进行同样的优化呢?且听下回分解~~~

  • 相关阅读:
    【NOIP2016提高组】 Day1 T3 换教室
    【NOIP2016 Day1 T2】天天爱跑步
    web@前端--html,css,javascript简介、第一个页面(常用标签简介)
    MySQL--视图view、触发器trigger、事务(start transaction)、存储过程(特殊的数据逻辑处理函数)、流程控制(if,case....)
    MySQL--pymysql模块
    MySQL--(了解)可能会用到的内置函数
    MySQL--详细查询操作(单表记录查询、多表记录查询(连表查询)、子查询)
    MySQL--表操作(约束条件foreign key关联表 多对1,多对多,1对1)
    MySQL--表操作(innodb表字段数据类型、约束条件)、sql_mode操作
    MySQL基本语句、存储引擎
  • 原文地址:https://www.cnblogs.com/wmesci/p/2736007.html
Copyright © 2011-2022 走看看