前言

同样,这篇文章仍然可以看成是阅读了ImageShop的https://www.cnblogs.com/Imageshop/p/7364115.html 总结。

肤色检测普通实现

这个算法的原理我已经在之前的一篇文章讲过了:https://blog.csdn.net/just_sort/article/details/93134960 ,这里先直接给一下C语言实现的普通代码。

#define IM_Max(a, b) (((a) >= (b)) ? (a): (b))
#define IM_Min(a, b) (((a) >= (b)) ? (b): (a))
#define _mm_cmpge_epu8(a, b) _mm_cmpeq_epi8(_mm_max_epu8(a, b), a)

void IM_GetRoughSkinRegion(unsigned char *Src, unsigned char *Skin, int Width, int Height, int Stride) {
	for (int Y = 0; Y < Height; Y++) {
		unsigned char *LinePS = Src + Y * Stride;
		unsigned char *LinePD = Skin + Y * Stride;
		for(int X = 0; X < Width; X++) {
			int Blue = LinePS[0], Green = LinePS[1], Red = LinePS[2];
			if (Red >= 60 && Green >= 40 && Blue >= 20 && Red >= Blue && (Red - Green) >= 10 && IM_Max(IM_Max(Red, Green), Blue) - IM_Min(IM_Min(Red, Green), Blue) >= 10) {
				LinePD[X] = 255;
			}
			else
				LinePD[X] = 16;
		}
	}
}

SSE优化

这里我先给出源代码再来一句句分析:

void IM_GetRoughSkinRegion_SSE(unsigned char *Src, unsigned char *Skin, int Width, int Height, int Stride) {
	const int NonSkinLevel = 10; //非肤色部分的处理程序,本例取16,最大值取100,那样就是所有区域都为肤色,毫无意义
	const int BlockSize = 16;
	int Block = Width / BlockSize;
	for (int Y = 0; Y < Height; Y++) {
		unsigned char *LinePS = Src + Y * Stride;
		unsigned char *LinePD = Skin + Y * Width;
		for (int X = 0; X < Block * BlockSize; X += BlockSize, LinePS += BlockSize * 3, LinePD += BlockSize) {
			__m128i Src1, Src2, Src3, Blue, Green, Red, Result, Max, Min, AbsDiff;
			Src1 = _mm_loadu_si128((__m128i *)(LinePS + 0));
			Src2 = _mm_loadu_si128((__m128i *)(LinePS + 16));
			Src3 = _mm_loadu_si128((__m128i *)(LinePS + 32));

			Blue = _mm_shuffle_epi8(Src1, _mm_setr_epi8(0, 3, 6, 9, 12, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1));
			Blue = _mm_or_si128(Blue, _mm_shuffle_epi8(Src2, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, 2, 5, 8, 11, 14, -1, -1, -1, -1, -1)));
			Blue = _mm_or_si128(Blue, _mm_shuffle_epi8(Src3, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 4, 7, 10, 13)));

			Green = _mm_shuffle_epi8(Src1, _mm_setr_epi8(1, 4, 7, 10, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1));
			Green = _mm_or_si128(Green, _mm_shuffle_epi8(Src2, _mm_setr_epi8(-1, -1, -1, -1, -1, 0, 3, 6, 9, 12, 15, -1, -1, -1, -1, -1)));
			Green = _mm_or_si128(Green, _mm_shuffle_epi8(Src3, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 5, 8, 11, 14)));

			Red = _mm_shuffle_epi8(Src1, _mm_setr_epi8(2, 5, 8, 11, 14, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1));
			Red = _mm_or_si128(Red, _mm_shuffle_epi8(Src2, _mm_setr_epi8(-1, -1, -1, -1, -1, 1, 4, 7, 10, 13, -1, -1, -1, -1, -1, -1)));
			Red = _mm_or_si128(Red, _mm_shuffle_epi8(Src3, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 3, 6, 9, 12, 15)));

			Max = _mm_max_epu8(_mm_max_epu8(Blue, Green), Red); //IM_Max(IM_Max(Red, Green), Blue)
			Min = _mm_min_epu8(_mm_min_epu8(Blue, Green), Red); //IM_Min(IM_Min(Red, Green), Blue)
			Result = _mm_cmpge_epu8(Blue, _mm_set1_epi8(20)); //Blue >= 20
			Result = _mm_and_si128(Result, _mm_cmpge_epu8(Green, _mm_set1_epi8(40))); //Green >= 40
			Result = _mm_and_si128(Result, _mm_cmpge_epu8(Red, _mm_set1_epi8(60))); //Red >= 60
			Result = _mm_and_si128(Result, _mm_cmpge_epu8(Red, Blue)); //Red >= Blue
			Result = _mm_and_si128(Result, _mm_cmpge_epu8(_mm_subs_epu8(Red, Green), _mm_set1_epi8(10))); //(Red - Green) >= 10 
			Result = _mm_and_si128(Result, _mm_cmpge_epu8(_mm_subs_epu8(Max, Min), _mm_set1_epi8(10))); //IM_Max(IM_Max(Red, Green), Blue) - IM_Min(IM_Min(Red, Green), Blue) >= 10
			Result = _mm_or_si128(Result, _mm_set1_epi8(16)); 
			_mm_storeu_si128((__m128i*)(LinePD + 0), Result);
		}
		for (int X = Block * BlockSize; X < Width; X++, LinePS += 3, LinePD++)
		{
			int Blue = LinePS[0], Green = LinePS[1], Red = LinePS[2];
			if (Red >= 60 && Green >= 40 && Blue >= 20 && Red >= Blue && (Red - Green) >= 10 && IM_Max(IM_Max(Red, Green), Blue) - IM_Min(IM_Min(Red, Green), Blue) >= 10)
				LinePD[0] = 255;									//	全为肤色部分																			
			else
				LinePD[0] = 16;
		}
	}
}

这里的SSE优化的代码就是对上面普通C语言实现的翻译,这里我学到的最重要的应该就是如何处理比较运算符。

  • 首先和https://blog.csdn.net/just_sort/article/details/95998524 一样将RGB变量分别提取到一个SSE变量中。
  • 首先来看Red >= 60 && Green >= 40 && Blue >= 20,我们需要一个unsigned char类型的比较函数,而sse只提供了singed char类型的SSE比较函数,这个问题在:http://www.alfredklomp.com/programming/sse-intrinsics/ 这里可以找到解决办法,我截图如下:- 我们再来看这行(Red - Green) >= 10,如果计算Red-Green,则需要把他们转换为ushort类型才可能满足可能存在负数的情况,但如果使用_mm_subs_epu8这个计算饱和函数,当Red<Green时,Red-Green就被截断为0了,这个时候(Red-Green)>=10就会返回false了,而如果Red-Green>0就不会发生截断。刚好满足。
  • 最后一个条件IM_Max(IM_Max(Red, Green), Blue) - IM_Min(IM_Min(Red, Green), Blue) >= 10。这个是最简单的一个,直接用_mm_max_epu8_mm_min_epu8获得B/G/R三分量的最大值和最小值,这个时候很明显max>min,因此有可以直接使用_mm_subs_epu8函数生产不会截断的正确结果。
  • 注意到sse的比较函数只有0和255这两种结果,因此上面的6个判断条件直接进行and操作就可以获得最后的组合值了,满足所有的条件的像素结果就是255,否则就是0。
  • 最后还有一个操作是不满条件的像素被设置成了16或者其他值,这里作者提供的方式直接与其他数或起来即可,因为255和其他数进行or操作还是255,而0和其他数进行or操作就会变为其他数。

速度测试

https://github.com/BBuf/Image-processing-algorithm-Speed

加速了5倍的样子,SSE还是很强大的。

效果图


这张原图是我在百度图片里面找到的,不算侵权吧。

欢迎关注我的github:https://github.com/BBuf/Image-processing-algorithm-Speed