最近公司面试新员工,一位面试的学生(山东大学)介绍他熟悉的算法的时候,提到在HSV空间检测红色效果会比RGB空间更好一些。本人也做过这方面的研究。下面我们来看看openCV的源码是怎么处理的。
static CvStatus CV_STDCALL
icvBGRx2HSV_8u_CnC3R( const uchar* src, int srcstep, uchar* dst, int dststep,
CvSize size, int src_cn, int blue_idx )
{
const int hsv_shift = 12;
//查找表1.0/v * 255 * 4096 其中 v是r、g、b的最大值
static const int div_table[] = {
0, 1044480, 522240, 348160, 261120, 208896, 174080, 149211,
130560, 116053, 104448, 94953, 87040, 80345, 74606, 69632,
65280, 61440, 58027, 54973, 52224, 49737, 47476, 45412,
43520, 41779, 40172, 38684, 37303, 36017, 34816, 33693,
32640, 31651, 30720, 29842, 29013, 28229, 27486, 26782,
26112, 25475, 24869, 24290, 23738, 23211, 22706, 22223,
21760, 21316, 20890, 20480, 20086, 19707, 19342, 18991,
18651, 18324, 18008, 17703, 17408, 17123, 16846, 16579,
16320, 16069, 15825, 15589, 15360, 15137, 14921, 14711,
14507, 14308, 14115, 13926, 13743, 13565, 13391, 13221,
13056, 12895, 12738, 12584, 12434, 12288, 12145, 12006,
11869, 11736, 11605, 11478, 11353, 11231, 11111, 10995,
10880, 10768, 10658, 10550, 10445, 10341, 10240, 10141,
10043, 9947, 9854, 9761, 9671, 9582, 9495, 9410,
9326, 9243, 9162, 9082, 9004, 8927, 8852, 8777,
8704, 8632, 8561, 8492, 8423, 8356, 8290, 8224,
8160, 8097, 8034, 7973, 7913, 7853, 7795, 7737,
7680, 7624, 7569, 7514, 7461, 7408, 7355, 7304,
7253, 7203, 7154, 7105, 7057, 7010, 6963, 6917,
6872, 6827, 6782, 6739, 6695, 6653, 6611, 6569,
6528, 6487, 6447, 6408, 6369, 6330, 6292, 6254,
6217, 6180, 6144, 6108, 6073, 6037, 6003, 5968,
5935, 5901, 5868, 5835, 5803, 5771, 5739, 5708,
5677, 5646, 5615, 5585, 5556, 5526, 5497, 5468,
5440, 5412, 5384, 5356, 5329, 5302, 5275, 5249,
5222, 5196, 5171, 5145, 5120, 5095, 5070, 5046,
5022, 4998, 4974, 4950, 4927, 4904, 4881, 4858,
4836, 4813, 4791, 4769, 4748, 4726, 4705, 4684,
4663, 4642, 4622, 4601, 4581, 4561, 4541, 4522,
4502, 4483, 4464, 4445, 4426, 4407, 4389, 4370,
4352, 4334, 4316, 4298, 4281, 4263, 4246, 4229,
4212, 4195, 4178, 4161, 4145, 4128, 4112, 4096
};
//这是IPP的,惹不起呀,跳过这段
int i;
if( icvRGB2HSV_8u_C3R_p )
{
CvStatus status = icvBGRx2ABC_IPP_8u_CnC3R( src, srcstep, dst, dststep, size,
src_cn, blue_idx, icvRGB2HSV_8u_C3R_p );
if( status >= 0 )
{
size.width *= 3;
for( ; size.height--; dst += dststep )
{
for( i = 0; i <= size.width - 12; i += 12 )
{
uchar t0 = icvHue255To180[dst[i]], t1 = icvHue255To180[dst[i+3]];
dst[i] = t0; dst[i+3] = t1;
t0 = icvHue255To180[dst[i+6]]; t1 = icvHue255To180[dst[i+9]];
dst[i+6] = t0; dst[i+9] = t1;
}
for( ; i < size.width; i += 3 )
dst[i] = icvHue255To180[dst[i]];
}
}
return status;
}
//这里就到了我们能分析的代码段了
//大循环没什么好说的了,各位都会明白,我重点分析一下h是这么计算的,见文章尾部。
srcstep -= size.width*src_cn;
size.width *= 3;
for( ; size.height--; src += srcstep, dst += dststep )
{
for( i = 0; i < size.width; i += 3, src += src_cn )
{
int b = (src)[blue_idx], g = (src)[1], r = (src)[2^blue_idx];
int h, s, v = b;
int vmin = b, diff;
int vr, vg;
CV_CALC_MAX_8U( v, g );
CV_CALC_MAX_8U( v, r );
CV_CALC_MIN_8U( vmin, g );
CV_CALC_MIN_8U( vmin, r );
diff = v - vmin;
vr = v == r ? -1 : 0;
vg = v == g ? -1 : 0;
s = diff * div_table[v] >> hsv_shift;
h = (vr & (g - b)) +
(~vr & ((vg & (b - r + 2 * diff)) + ((~vg) & (r - g + 4 * diff))));
h = ((h * div_table[diff] * 15 + (1 << (hsv_shift + 6))) >> (7 + hsv_shift))\
+ (h < 0 ? 30*6 : 0);
dst[i] = (uchar)h;
dst[i+1] = (uchar)s;
dst[i+2] = (uchar)v;
}
}
return CV_OK;
}
以一种情况为例:vr == -1 的时候,根据程序 h = (g - b);
所以:h = (((g - b) * div_table[diff] * 15 + (1 << (hsv_shift + 6))) >> (7 + hsv_shift))\
+ (h < 0 ? 30*6 : 0);
替换掉 div_table[diff] = 1 / diff *255*4096 约等于1 / diff *pow(2,8)*pow(2,12) 注:pow为求次方函数
最后推导的结果是:
h = (g-b)/diff *(pow(2,5) - 2 -pow(2,5-3) + pow(2,-7)) + pow(2,-1) + (h < 0 ? 30*6 : 0);
h约等于 (g-b)/diff * 30 + 0.5 + (h < 0 ? 30*6 : 0)
说明一下:
opencv中的H分量范围是 0~180, 这样做是为了8位深的图像,使用的时候可以乘以2。
到这里就算结束了,我们可以看到这段程序的作者的编程技巧:
1)利用查找表规避掉大量的除法,提高了效率
2)应用移位算法,也大大提高了计算速度
最后提一点:
https://en.wikipedia.org/wiki/HSL_and_HSV