方便起见,下面再贴一下上次的对比结果:
1、
2、
注意:由于上面两次比较不在同一系统上,Stopwatch.Frequency的值可能不同,故这两次的结果之间不具有可比性!!
——————————————————————————————————————————————————————
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的效率正来源于此!
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类进行同样的优化呢?且听下回分解~~~