





which expands to
Both sums can be generated from two Integral Images over and
respectively.
for (Y = 0; Y < Height; Y++)
{
LinePS = Src->Data + Y * Src->WidthStep;
LinePD = Skin->Data + Y * Skin->WidthStep;
for (X = 0; X < Width; X++)
{
if (LinePS[0] >20 && LinePS[1] > 40 && LinePS[2] > 50) // 皮膚識別有很多算法,但沒有一個能完美的包含所有的皮膚區域,我認為寧願多處理一些非皮膚 區域,也比少處理更合理些
LinePD[X] = 255; // 為什么取這些數據,我只能告訴這些數據是前人的一些經驗沒有什么數學公式來推導和證明的
LinePS += 3;
}
} // 以上處理得到的是硬邊界,直接使用會到底結果圖有較為明顯的痕跡,因此可使用模糊平滑下
為了讓識別的區域邊界不是特別生硬,需要對識別區域進行一定的處理,一般就是用羽化算法,標准的羽化算法是高斯模糊,但是高斯模糊有浮點計算,一種替代方式就是用方框模糊來替代,實際上兩種方式得到的結果在視覺上是沒有太多的區別的,但是方框模糊只有整數運算,效率上會高很多。
這樣的識別處理后,把處理后圖和原圖進行Alpha混合,這樣即保留了磨皮的光滑區域,又能保證頭發等部位不被平滑掉。
for (Y = 0; Y < Height; Y++)
{
LinePS = Src->Data + Y * Src->WidthStep;
LinePD = Dest->Data + Y * Dest->WidthStep;
LinePM = Mask->Data + Y * Mask->WidthStep;
for (X = 0; X < Width; X++)
{
Alpha = LinePM[0];
if (Alpha != 255)
{
InvertAlpha = 255 - Alpha; // AlphaBlend的混合過程,使用DIV255來提高速度
LinePD[0] = Div255(LinePD[0] * Alpha + LinePS[0] * InvertAlpha); // Blue分量
LinePD[1] = Div255(LinePD[1] * Alpha + LinePS[1] * InvertAlpha); // Green分量
LinePD[2] = Div255(LinePD[2] * Alpha + LinePS[2] * InvertAlpha); // Red分量
}
LinePS += 3; // 移動到下一個像素,24位
LinePD += 3;
LinePM++; // 移動到下一個Mask值
}
}
在處理完成后,從視覺角度考慮,整體圖還是有點模糊,這個時候應該進行了適度的銳化來增強圖像的整體銳度,最合適的是USM銳化,但是USM銳化是基於高斯模糊的,因此又非常耗時,這個時候可以考慮用最簡答的領域銳化方式來處理,比如借助於下面的卷積矩陣,就能獲得不錯的視覺效果。
-1 0 -1
0 8 0 / 4
-1 0 -1
考慮到這個算法對每個像素的亮度值的改變並不是很大,對於彩色圖像,如果能夠轉換到其他的包含亮度分量的顏色空間后,只對亮度進行處理,然后在轉換回來,應該對視覺的影響不大,這樣去除顏色空間的轉換時間,可以提高三倍的速度,是相當可觀的,常見的包含亮度的顏色空間有LAB,HSV,YUV,YCbCr等,其中YCbCr和RGB的轉換公式非常簡單,沒有浮點計算,對整體的效率影響不大,因此可以選用這個空間。
再者,經過實驗,發現也可以把銳化操作放在對亮度處理這步,而不是放在最后,這樣,銳化操作也只需要處理一個分量,同樣能提高效率。
貼出我處理的函數的流程:
/// <summary>
/// 實現圖像的磨皮。
/// <param name="Src">需要處理的源圖像的數據結構。</param>
/// <param name="Dest">需要處理的源圖像的數據結構。</param>
/// <param name="DenoiseMethod">磨皮的算法,0為雙邊磨皮,1為均方差磨皮。</param>
/// <param name="DenoiseLevel">磨皮的程度,有效范圍[1,10],數據越大,磨皮越明顯。</param>
/// <param name="WhiteMethod">美白的算法,0為Log曲線美白,1為色彩平衡磨皮。</param>
/// <param name="NonSkinLevel">美白的程度,有效范圍[1,10],數據越大,美白越明顯。</param>
/// <remarks>原圖、目標圖必須都是24位的。</remarks>
IS_RET __stdcall SkinBeautification(TMatrix *Src, TMatrix *Dest, int DenoiseLevel, int WhiteMethod, int WhiteLevel)
{
if (Src == NULL || Dest == NULL) return IS_RET_ERR_NULLREFERENCE;
if (Src->Data == NULL || Dest->Data == NULL) return IS_RET_ERR_NULLREFERENCE;
if (Src->Width != Dest->Width || Src->Height != Dest->Height || Src->Channel != Dest->Channel || Src->Depth != Dest->Depth) return IS_RET_ERR_PARAMISMATCH;
if (Src->Channel != 3) return IS_RET_ERR_NOTSUPPORTED;
if ((DenoiseLevel < 1 || DenoiseLevel > 10)) return IS_RET_ERR_ARGUMENTOUTOFRANGE;
if ((WhiteMethod != 0 && WhiteMethod != 1) || (WhiteLevel < 1 || WhiteLevel > 10)) return IS_RET_ERR_ARGUMENTOUTOFRANGE;
IS_RET Ret = IS_RET_OK;
TMatrix *Skin = NULL, *Y = NULL, *Cb = NULL, *Cr = NULL, *YY = NULL;
unsigned char *Table = (unsigned char *)IS_AllocMemory(256, true);
Ret = IS_CreateMatrix(Src->Width, Src->Height, Src->Depth, 1, &Skin); // 分配皮膚區域的內存
if (Ret != IS_RET_OK) goto Done;
Ret = IS_CreateMatrix(Src->Width, Src->Height, Src->Depth, 1, &Y);
if (Ret != IS_RET_OK) goto Done;
Ret = IS_CreateMatrix(Src->Width, Src->Height, Src->Depth, 1, &Cb);
if (Ret != IS_RET_OK) goto Done;
Ret = IS_CreateMatrix(Src->Width, Src->Height, Src->Depth, 1, &Cr);
if (Ret != IS_RET_OK) goto Done;
Ret = IS_CreateMatrix(Src->Width, Src->Height, Src->Depth, 1, &YY);
if (Ret != IS_RET_OK) goto Done;
Ret = RGBToYCbCr(Src, Y, Cb, Cr); // 第一步: 將RGB轉換到YCbCr空間
if (Ret != IS_RET_OK) goto Done;
int SpaceError = 10 + DenoiseLevel * DenoiseLevel * 5;
Ret = LeeAdditvieNoiseFilter(Y, YY, max(Src->Width, Src->Height) * 0.02, SpaceError); // 第二步:對Y分量進行加性噪音的去除
if (Ret != IS_RET_OK) goto Done;
Ret = Sharpen(YY, YY); // 第三步:對處理后的Y分量進行銳化
if (Ret != IS_RET_OK) goto Done;
YCbCrToRGB(YY, Cb, Cr, Dest); // 第四步:將圖像從YCbCr空間轉換會RGB空間
if (WhiteMethod == 0)
{
for (int V = 0; V < 256; V++)
{
// Table[V] = .........
}
}
else if (WhiteMethod == 1)
{
for(int V = 0; V < 256; V++)
{
// Table[V] = ............
}
}
Ret = Curve(Dest, Dest, Table, Table, Table); // 第五步:在RGB空間對磨皮后的圖進行美白處理
if (Ret != IS_RET_OK) goto Done;
Ret = GetRawSkinRegion(Src, Skin); // 第六步:分析圖像大概的皮膚區域
if (Ret != IS_RET_OK) goto Done;
Ret = BlendImageWithMask(Src, Dest, Skin); // 第七步:對全局磨皮、美白后的圖和原始圖按照屬於皮膚區域的程度進行混合
if (Ret != IS_RET_OK) goto Done;
Done:;
IS_FreeMatrix(&Skin); // 注意釋放內存
IS_FreeMatrix(&Y);
IS_FreeMatrix(&Cb);
IS_FreeMatrix(&Cr);
IS_FreeMatrix(&YY);
IS_FreeMemory(Table);
return IS_RET_OK;
}
第五步的美白處理如果放在對亮度分量的處理過程中,圖像整體會有所偏色,似乎有一種膚色紅潤的效果,但是對部分圖像會感覺顏色不自然。
各部分耗時比例見下圖,測試圖像大小是500*600。
序號 |
函數名 |
用時(ms) |
百分比(%) |
備注 |
1 |
RGBToYCbCr |
2 |
10 |
|
2 |
LeeAdditvieNoiseFilter |
7 |
35 |
|
3 |
Sharpen |
2 |
10 |
|
4 |
YCbCrToRGB |
2 |
10 |
|
5 |
Curve |
1 |
5 |
|
6 |
GetRawSkinRegion |
4 |
20 |
|
7 |
BlendImageWithMask |
2 |
10 |
|
8 |
合計 |
20 |
100 |
|
各種效果比較如下:
原圖 磨皮后的圖
刪除第三步后的圖 刪除第六第七步后的圖
這個算法最大的優點是整個過程的任何函數都沒有浮點計算,這對於某些硬件來說是很重要的,但是一個缺點是優化后的算法不能並行,在我的I3筆記本電腦上30W的像素處理時間20ms,完全能實現實時效果。
有興趣的朋友可根據我的描述自己實現,測試程序: 快速磨皮。
可調參數如下界面所示:
****************************作者: laviewpbt 時間: 2015.7.27 聯系QQ: 33184777 轉載請保留本行信息**********************