一.背景簡介
現在視頻類應用非?;馃?,直播、美妝、醫(yī)美應用層出不窮。用戶們在使用這類應用時都希望自己在屏幕上的樣子美美的,皮膚細膩光滑。本文就介紹一種實現簡單、效果很好的磨皮算法以及對它的優(yōu)化思路。
二.磨皮算法
磨皮的基本方法
磨皮算法的核心就是讓皮膚區(qū)域的像素變得平滑,過渡自然,這樣皮膚上的瑕疵就不容易察覺了,皮膚看起來十分光滑。這里包括了兩部分算法:
1. 圖像濾波算法;
2. 皮膚區(qū)域檢測算法。
整體的流程如下:
皮膚區(qū)域檢測可參考技術的真相 | 從AR口紅試妝了解人工智能試妝技術,此文不再贅述。
濾波算法一般有兩類作用:一是讓畫面變得平滑甚至模糊,二是去除畫面中的噪點。在磨皮算法里主要的作用是過濾掉畫面中微小的瑕疵、讓顏色過渡更加平滑。其主要的思想是讓中心像素顏色和周圍像素顏色取加權均值,然后更新中心像素的顏色值。濾波窗口中的各像素權重有很多種選取方式,由此產生了多種濾波算法。
常見的濾波算法
常用的濾波方法有盒型濾波(如均值濾波)、統(tǒng)計排序濾波(如中值濾波)、高斯濾波和保邊濾波(如雙邊濾波)等。不同濾波算法產生的效果會有很大區(qū)別。其中一些對去除噪點效果很好,如中值濾波;一些產生平滑的畫面效果,如均值濾波、高斯濾波、保邊濾波等。
為什么要用保邊的濾波算法
對比上面兩張圖片,可以看到非常明顯的差異。
左圖使用的是高斯濾波,權重符合二維高斯分布,中間權重高,越往外權重越低,在各個方向上距中心同一距離的位置上的權重是一致的。這樣的卷積核簡單粗暴,不管圖像顏色信息如何,統(tǒng)一對待,所以在輸出圖像上可見山石邊緣也變得非常模糊。
右圖使用的是雙邊濾波,卷積核在高斯核的基礎上增加了顏色差異的權重。對于顏色差異大的鄰近點,它的權重變小,對中心點的平滑影響減弱,邊緣可以被有效地保留下來。
人臉上有眉毛、眼睛、嘴等與皮膚顏色差異很大的區(qū)域,而且立體的五官上也會有明暗變化。這些邊緣如果使用高斯濾波會模糊成一團。但是使用保邊濾波,可以清晰地保留下五官和明暗區(qū)域,防止變平、失真。
讓我們來看一下雙邊濾波和高斯濾波應用在真實人像上的效果對比(點擊圖片可查看大圖):
這是一張原始圖片
應用高斯濾波算法(sigma=4.0),可以觀察到人物臉部變得朦朦朧朧,眼鏡邊緣、鼻子邊緣和嘴部邊緣都變得不清晰了。
應用雙邊濾波算法(spaceSigma=4.0,colorSigma=18),可以觀察到人物五官基本上仍然是清晰,眼鏡邊緣也沒有什么變化。
可見在對人像磨皮的算法選擇上,使用雙邊濾波更為合理。
三.雙邊濾波算法
雙邊濾波的原理雙邊濾波算法由C. Tomasi和R. Manduchi在1998年提出,論文名稱是Bilateral Filtering for Gray and Color Images。
雙邊濾波組合了空間上鄰近程度的權重和像素顏色值相似程度的權重。公式如下:
其中,f是原始圖像,h是濾波后圖像,x是卷積核中心點,ξ是x的相鄰點。卷積核包括了兩部分:
空間權重c(ξ,x)衡量幾何空間上的鄰近程度(GeometricCloseness),顏色權重s(f(ξ),f(x))衡量色彩空間上的相似程度(Photometric Similarity)。
權重c和權重s都遵循高斯分布:離中心點的幾何距離越遠,權重c越?。缓椭行狞c的顏色差異越大,權重s越小。
上圖從左到右依次為:輸入圖像(含有明顯的顏色變化邊界);空間卷積核c;顏色卷積核s;疊加了空間卷積核和顏色卷積核的完整卷積核c x s;濾波后的圖像(保留了顏色邊界)
雙邊濾波的參數和效果
讓我們對比一下高斯濾波和雙邊濾波的效果。這兩種濾波器在OpenCV中都有實現,所以直接調用OpenCV的接口得到如下結果(點擊圖片可查看大圖):
注:實驗圖片使用經典的lena圖像,512x512像素,tiff格式。
對比上面幾幅圖像,可以看到雙邊濾波可以明顯地去處原圖上的噪點,并且保留了相對清晰的物體輪廓。對比原圖和spaceSigma=4,colorSigma=32的雙邊濾波結果,人物臉上和肩膀上的斑點都消失了,皮膚看起來光滑水潤。隨著參數改變,我們看到濾波結果發(fā)生了很大變化。
由于spaceSigma和colorSigma都是卷積核的方差。某一部分的方差變大,鐘形曲線變得平緩,各個權重的差異變小,說明不強調這一部分的影響。
spaceSigma值越小,畫面越清晰,值越大,畫面越模糊,大到極限時,變?yōu)橹涤驗V波(閾值化)。
colorSigma值越小,邊緣越清晰,值越大,邊緣越模糊,大到極限時,變?yōu)楦咚篂V波。
如果spaceSigma相對于colorSigma變小,說明卷積核更強調距離中心點近的點。比如colorSigma=32,spaceSigma由16變?yōu)?時,可以看到顏色接近的區(qū)域內部變得沒那么模糊了。
如果colorSigma相對于spaceSigma變小,說明卷積核更強調與中心點顏色接近的點。比如spaceSigma=16,colorSigma由128變?yōu)?2時,可以看到邊緣沒那么模糊了。
注1:使用OpenCV時,如果不指定卷積核的尺寸,按照3通道sigma×6+1計算。因此計算此組圖片時,當spaceSigma=4時,卷積核尺寸為25x25;當spaceSigma=16時,卷積核尺寸為97x97。注2:PSNR(Peak Signal to Noise Ratio),是一種最普遍,最廣泛使用的評價圖像的客觀標準,單位是dB,其數值越大,失真越少。
雙邊濾波的缺點雙邊濾波的卷積核是非線性的,因此計算復雜度高。不同位置的卷積核不同,不能預先計算或者執(zhí)行FFT,計算起來比較費時。因此很多前輩做出了很多嘗試,提出了一些優(yōu)化方法可以近似雙邊濾波的效果。
雙邊濾波的實現和優(yōu)化
0. 基本實現
最基本的實現方式就是按照上文中的公式,對每個像素進行二維卷積計算。這里使用OpenCV實現基本的雙邊濾波算法,以RGB3通道為例:
void BilateralBlur(const Mat &srcImage, Mat &dstImage, int kernelSize, double sigmaColor, double sigmaSpace) {
int height = srcImage.rows;
int width = srcImage.cols;
int channel = srcImage.channels();
if (dstImage.rows == 0 || dstImage.cols == 0)
dstImage = Mat::zeros(srcImage.size(), srcImage.type());
double ct = -0.5 / (sigmaColor * sigmaColor);
double st = -0.5 / (sigmaSpace * sigmaSpace);
int radius = (kernelSize - 1) / 2;
double w;
double sw;
double cw;
for (int row = 0; row < height; ++row) {
for (int col = 0; col < width; ++col) {
double sumr = 0, sumg = 0, sumb = 0, sumw = 0;
Vec3b bgr0 = srcImage.at<Vec3b>(row, col);
for (int n = -radius; n <= radius; n += step) {
int y = clamp(row + n, 0, height - 1);
for (int m = -radius; m <= radius; m += step) {
int x = clamp(col + m, 0, width - 1);
Vec3b bgr = srcImage.at<Vec3b>(y, x);
sw = exp((m * m + n * n) * st);
double c = abs(bgr0[0] - bgr[0]) + abs(bgr0[1] - bgr[1]) + abs(bgr0[2] - bgr[2]); // sum of difference of each channel
cw = exp(c * c * ct);
double w = sw * cw;
sumb += b * w;
sumg += g * w;
sumr += r * w;
sumw += w;
}
}
dstImage.at<Vec3b>(row, col) = Vec3b(saturate_cast<uchar>(sumb / sumw),
saturate_cast<uchar>(sumg / sumw),
saturate_cast<uchar>(sumr / sumw));
}
}
}
1. 使用查找表減少計算在計算高斯函數的時候,因為含有指數運算,運算量比較大。同時,自變量部分都都是整數,所以可以預先計算好權重查找表,在卷積時直接找到對應的權重值。權重表計算如下:
void BilateralBlur(const Mat &srcImage, Mat &dstImage, int kernelSize, double sigmaColor, double sigmaSpace) {
int height = srcImage.rows;
int width = srcImage.cols;
int channel = srcImage.channels();
if (dstImage.rows == 0 || dstImage.cols == 0)
dstImage = Mat::zeros(srcImage.size(), srcImage.type());
double ct = -0.5 / (sigmaColor * sigmaColor);
double st = -0.5 / (sigmaSpace * sigmaSpace);
int radius = (kernelSize - 1) / 2;
double w;
double sw;
double cw;
for (int row = 0; row < height; ++row) {
for (int col = 0; col < width; ++col) {
double sumr = 0, sumg = 0, sumb = 0, sumw = 0;
Vec3b bgr0 = srcImage.at<Vec3b>(row, col);
for (int n = -radius; n <= radius; n += step) {
int y = clamp(row + n, 0, height - 1);
for (int m = -radius; m <= radius; m += step) {
int x = clamp(col + m, 0, width - 1);
Vec3b bgr = srcImage.at<Vec3b>(y, x);
sw = exp((m * m + n * n) * st);
double c = abs(bgr0[0] - bgr[0]) + abs(bgr0[1] - bgr[1]) + abs(bgr0[2] - bgr[2]); // sum of difference of each channel
cw = exp(c * c * ct);
double w = sw * cw;
sumb += b * w;
sumg += g * w;
sumr += r * w;
sumw += w;
}
}
dstImage.at<Vec3b>(row, col) = Vec3b(saturate_cast<uchar>(sumb / sumw),
saturate_cast<uchar>(sumg / sumw),
saturate_cast<uchar>(sumr / sumw));
}
}
}
2. 使用可分離的卷積近似計算對于卷積核可以分解成一個行向量乘以一個列向量的形式時,例如:
就可以把一次二維卷積分解成兩次一維卷積,單個像素卷積操作的計算復雜度從 下降為O(N)。對于較大的卷積核而言,優(yōu)化幅度是相當可觀的。實現時需要先對整幅圖像做一次一維卷積,把卷積結果保存到臨時圖像上,再對臨時圖像做第二次一維卷積。
對于雙邊濾波的權重s部分,雖然分離后的結果不完全一樣,但是誤差不大,可以以這種方法做近似計算。
3. 使用遞歸方法近似計算
R. Deriche在1993年提出了一種遞歸高斯濾波方法,論文名稱是Recursively implementating the Gaussian and its derivatives。在這種方法中,二維高斯卷積核被分解成兩個一維高斯卷積核相乘,每一次卷積計算包含6次乘法和6次加法,與卷積核的尺寸無關。計算復雜度下降為O(1)。對于大小超過7x7的卷積核,性能都可以有所提升,卷積核越大提升越明顯。
受到這篇論文的啟發(fā),很多學者都在探索如何遷移這種方法到雙邊濾波算法中。比如Q. Yang在2012年提出了遞歸雙邊濾波方法,論文名稱是Recursive Bilateral Filtering。本實驗使用了https://github.com/ufoym/recursive-bf提供的代碼。
以下是上述優(yōu)化方法的結果對比(點擊圖片可查看大圖):
實驗參數均為spaceSigma=4,colorSigma=32。
從PSNR數值來看,幾種優(yōu)化方法和標準算法區(qū)別不大。肉眼觀察,可見一些細微的差異,但整體效果接近。
4. 使用SIMD指令集在CPU上,還可以使用SIMD指令集做進一步的加速,加速比例一般可以做到接近于數據并行數目。一般來說,比如在64位寬的寄存器上使用8x8位數據進行運算,加速比可以達到6-8倍。受限于篇幅,本文不進行詳細討論??梢詤⒖糷ttps://github.com/Fig1024/OP_RBF提供的代碼。
三.參考文獻
- Bilateral Filtering for Gray and Color Images. C. Tomasi, R. ManduchiRecursively implementating the Gaussian and its derivatives. R. DericheRecursive Bilateral Filtering. Q. Yang