我们先看一下openCV的源码:
开始调用的是这个函数:
static CvStatus CV_STDCALL
icvHSV2BGRx_8u_C3CnR( const uchar* src, int srcstep, uchar* dst, int dststep,
CvSize size, int dst_cn, int blue_idx )
接下来:
static CvStatus CV_STDCALL
icvABC2BGRx_8u_C3CnR( const uchar* src, int srcstep, uchar* dst, int dststep,
CvSize size, int dst_cn, int blue_idx, CvColorCvtFunc2 cvtfunc_32f,
const float* pre_coeffs, int postscale )
再接下来就是核心运算函数:
static CvStatus CV_STDCALL
icvHSV2BGRx_32f_C3CnR( const float* src, int srcstep, float* dst,
int dststep, CvSize size, int dst_cn, int blue_idx )
我们分析一下,最后调用的核心函数的内容:
static CvStatus CV_STDCALL
icvHSV2BGRx_32f_C3CnR( const float* src, int srcstep, float* dst,
int dststep, CvSize size, int dst_cn, int blue_idx )
{
int i;
srcstep /= sizeof(src[0]);
dststep /= sizeof(dst[0]);
dststep -= size.width*dst_cn;
size.width *= 3;
for( ; size.height--; src += srcstep, dst += dststep )
{
for( i = 0; i < size.width; i += 3, dst += dst_cn )
{
float h = src[i], s = src[i+1], v = src[i+2];
float b, g, r;
//判断s 为零,本人觉得这个地方有点问题,因为浮点类型不可以这么对比
//可以修改为
//#define YX_FLOAT_EQUAL_ZERO(f) ((f) < FLT_EPSILON && (f) > -FLT_EPSILON)
//当然了,你先要添加这个宏定义才行啊
if( s == 0 )
b = g = r = v;
else
{
static const int sector_data[][3]=
{{1,3,0}, {1,0,2}, {3,0,1}, {0,2,1}, {0,1,3}, {2,1,0}};
float tab[4];
int sector;
//相当于 h /= 60; 因为:1.0 / 60 = 0.016666666666666666f;
h *= 0.016666666666666666f;
//这是把h界定在0~5之间
if( h < 0 )
do h += 6; while( h < 0 );
else if( h >= 6 )
do h -= 6; while( h >= 6 );
sector = cvFloor(h);
//取h的小数部分
h -= sector;
//下面的操作在定义中有了,不详细描述了(看下面的维基链接)
//https://en.wikipedia.org/wiki/HSL_and_HSV
tab[0] = v;
tab[1] = v*(1.f - s);
tab[2] = v*(1.f - s*h);
tab[3] = v*(1.f - s*(1.f - h));
b = tab[sector_data[sector][0]];
g = tab[sector_data[sector][1]];
r = tab[sector_data[sector][2]];
}
dst[blue_idx] = b;
dst[1] = g;
dst[blue_idx^2] = r;
if( dst_cn == 4 )
dst[3] = 0;
}
}
return CV_OK;
}
其实,程序很简单,到这里也就结束了,没什么奇怪的地方。
可是我把程序重新写了一遍,发现了很大的不同。
直接读代码吧:
这段代码是上面列举的第二个函数,分析在最后面
static CvStatus CV_STDCALL
icvABC2BGRx_8u_C3CnR( const uchar* src, int srcstep, uchar* dst, int dststep,
CvSize size, int dst_cn, int blue_idx, CvColorCvtFunc2 cvtfunc_32f,
const float* pre_coeffs, int postscale )
{
int block_size = MIN(1 << 8, size.width);
float* buffer = (float*)cvStackAlloc( block_size*3*sizeof(buffer[0]) );
int i, di, k;
CvStatus status = CV_OK;
dststep -= size.width*dst_cn;
for( ; size.height--; src += srcstep, dst += dststep )
{
for( i = 0; i < size.width; i += block_size )
{
const uchar* src1 = src + i*3;
di = MIN(block_size, size.width - i);
for( k = 0; k < di*3; k += 3 )
{
float a = CV_8TO32F(src1[k])*pre_coeffs[0] + pre_coeffs[1];
float b = CV_8TO32F(src1[k+1])*pre_coeffs[2] + pre_coeffs[3];
float c = CV_8TO32F(src1[k+2])*pre_coeffs[4] + pre_coeffs[5];
buffer[k] = a;
buffer[k+1] = b;
buffer[k+2] = c;
}
status = cvtfunc_32f( buffer, 0, buffer, 0, cvSize(di,1), 3, blue_idx );
if( status < 0 )
return status;
if( postscale )
{
for( k = 0; k < di*3; k += 3, dst += dst_cn )
{
int b = cvRound(buffer[k]*255.);
int g = cvRound(buffer[k+1]*255.);
int r = cvRound(buffer[k+2]*255.);
dst[0] = CV_CAST_8U(b);
dst[1] = CV_CAST_8U(g);
dst[2] = CV_CAST_8U(r);
if( dst_cn == 4 )
dst[3] = 0;
}
}
else
{
for( k = 0; k < di*3; k += 3, dst += dst_cn )
{
int b = cvRound(buffer[k]);
int g = cvRound(buffer[k+1]);
int r = cvRound(buffer[k+2]);
dst[0] = CV_CAST_8U(b);
dst[1] = CV_CAST_8U(g);
dst[2] = CV_CAST_8U(r);
if( dst_cn == 4 )
dst[3] = 0;
}
}
}
}
return CV_OK;
}
其实这段代码也没有什么,只是看上去有点奇怪,怪在哪里呢?
就是函数中本来只存在一个大循环(size.height==1),就是for( i = 0; i < size.width; i += block_size );
内部一个循环:for( k = 0; k < di*3; k += 3 );
问题出来了,为什么程序编写者不在内存循环中,把结果直接计算出来?实际上,作者是把内层的循环分成了3部分来完成。
第一部分:
for( k = 0; k < di*3; k += 3 )
{
float a = CV_8TO32F(src1[k])*pre_coeffs[0] + pre_coeffs[1];
float b = CV_8TO32F(src1[k+1])*pre_coeffs[2] + pre_coeffs[3];
float c = CV_8TO32F(src1[k+2])*pre_coeffs[4] + pre_coeffs[5];
buffer[k] = a;
buffer[k+1] = b;
buffer[k+2] = c;
}
第二部分:
status = cvtfunc_32f( buffer, 0, buffer, 0, cvSize(di,1), 3, blue_idx ); 即核心运算函数 icvHSV2BGRx_32f_C3CnR
第三部分:
for( k = 0; k < di*3; k += 3, dst += dst_cn )
{
int b = cvRound(buffer[k]);
int g = cvRound(buffer[k+1]);
int r = cvRound(buffer[k+2]);
dst[0] = CV_CAST_8U(b);
dst[1] = CV_CAST_8U(g);
dst[2] = CV_CAST_8U(r);
if( dst_cn == 4 )
dst[3] = 0;
}
这三部分完全一次计算出来呀,为什么要分开呢?
按照这个思路,本人把三个内循环程序拆开,直接计算出结果,最后得到的结论是:
之前openCV程序的效率是:0.2738秒(图像1920*1080 debug版),而我的计算是:0.3897秒(图像1920*1080 debug版),耗时约1.4倍于openCV的源码
从这里可以看出来,分块处理的有效性,大家有兴趣可以测试一下!
如果再加入一层循环,也就是说最外层的大循环size.height!=1 的话,此算法的效率会有所下降,但是也相差无几!
可以看出,算法优化的最根本的部分,在于内层循环的优化,外层循环影响并不是很大!