基於邊緣保留濾波實現人臉磨皮的演算法 | 掘金技術徵文

Tony沈哲發表於2017-04-24

快速邊緣保留濾波

快速邊緣保留濾波是通過積分影象實現區域性均方差的邊緣保留模糊演算法,計算簡單而且可以做到計算量跟半徑無關。
首先區域性均方差濾波中計算區域性均值的公式如下:

基於邊緣保留濾波實現人臉磨皮的演算法 | 掘金技術徵文
計算區域性均值.png

當邊緣很弱的時候係數K趨近於0、該點的矯正之後的畫素值就接近平均值。而當邊緣很強的時候係數K趨近於1、該點的模糊之後的畫素值就接近等於輸入畫素值。上述計算中最中意的是視窗內畫素的均值與方差,計算均值可以根據積分影象很容易得到,而計算方差根據一系列的數學推導可以得到如下的結果

基於邊緣保留濾波實現人臉磨皮的演算法 | 掘金技術徵文
推導結果.png

演算法實現的步驟:

1. 快速邊緣保留濾波

核心的演算法如下:

    @Override
    public ImageProcessor filter(ImageProcessor src) {
        // initialization parameters
        int width = src.getWidth();
        int height = src.getHeight();
        xr = yr = (int)(Math.max(width, height) * 0.02);
        sigma = 10 + sigma * sigma * 5;

        // start ep process
        byte[] output = new byte[width*height];
        IntIntegralImage ii = new IntIntegralImage();
        for(int i=0; i<src.getChannels(); i++) {
            System.arraycopy(src.toByte(i), 0, output, 0, output.length);
            ii.setImage(src.toByte(i));
            ii.process(width, height, true);
            processSingleChannel(width, height, ii, output);
            System.arraycopy(output, 0, src.toByte(i), 0, output.length);
        }

        // release memory
        output = null;
        return src;
    }

    public void processSingleChannel(int width, int height, IntIntegralImage input, byte[] output) {
        float sigma2 = sigma*sigma;
        int offset = 0;
        int wy = (yr * 2 + 1);
        int wx = (xr * 2 + 1);
        int size = wx * wy;
        int r = 0;
        for (int row = yr; row < height-yr; row++) {
            offset = row * width;
            for (int col = xr; col < width-xr; col++) {
                int sr = input.getBlockSum(col, row, wy, wx);
                float a = input.getBlockSquareSum(col, row, wy, wx);
                float b = sr / size;
                float c = (a - (sr*sr)/size)/size;
                float d = c / (c+sigma2);
                r = (int)((1-d)*b + d*r);
                output[offset + col] = (byte)Tools.clamp(r);
            }
        }
    }複製程式碼

其中,IntIntegralImage封裝了積分影象的演算法,具體可以檢視 cv4j 中的實現。

2. 皮膚檢測

基於RGB顏色空間的簡單閾值膚色識別來實現皮膚檢測,演算法如下:
R>95 And G>40 And B>20 And R>G And R>B And Max(R,G,B)-Min(R,G,B)>15 And Abs(R-G)>15

public class DefaultSkinDetection implements ISkinDetection{
// RGB Color model pixel skin detection method
// (R, G, B) is classified as skin if:
// R > 95 and G > 40 and B > 20 and
// max(R, G, B) - min(R, G, B) > 15 and
// |R-G| > 15 and R > G and R > B
//===============================================

    @Override
    public boolean findSkin(int tr, int tg, int tb) {
        return isSkin(tr, tg, tb);
    }

    @Override
    public boolean isSkin(int tr, int tg, int tb) {
        int max = Math.max(tr, Math.max(tg, tb));
        int min = Math.min(tr, Math.min(tg, tb));
        int rg = Math.abs(tr - tg);
        if(tr > 95 && tg > 40 && tb > 20 && rg > 15 && 
                (max - min) > 15 && tr > tg && tr > tb) {
            return true;
        } else {
            return false;
        }
    }

}複製程式碼

3. 梯度濾波

梯度濾波器也叫高通濾波器。梯度濾波器有好幾種不同方式,在這裡用的是Sobel。
Sobel運算元根據畫素點上下、左右鄰點灰度加權差,在邊緣處達到極值這一現象檢測邊緣。對噪聲具有平滑作用,提供較為精確的邊緣方向資訊,邊緣定位精度不夠高。當對精度要求不是很高時,是一種較為常用的邊緣檢測方法。由於Sobel演算法簡單效率高,所以我們在這裡選擇它。

4. BeautySkinFilter

結合以上三步,在 cv4j 中實現人臉磨皮的濾鏡BeautySkinFilter

package com.cv4j.core.filters;

import com.cv4j.core.datamodel.ByteProcessor;
import com.cv4j.core.datamodel.ImageProcessor;

/**
 * Created by gloomy fish on 2017/4/23.
 */

public class BeautySkinFilter implements CommonFilter {
    @Override
    public ImageProcessor filter(ImageProcessor src) {
        int width = src.getWidth();
        int height = src.getHeight();
        byte[] R = new byte[width*height];
        byte[] G = new byte[width*height];
        byte[] B = new byte[width*height];
        System.arraycopy(src.toByte(0), 0, R, 0, R.length);
        System.arraycopy(src.toByte(1), 0, G, 0, G.length);
        System.arraycopy(src.toByte(2), 0, B, 0, B.length);

        FastEPFilter epFilter = new FastEPFilter();
        epFilter.filter(src);
        ISkinDetection skinDetector = new DefaultSkinDetection();
        int r = 0, g = 0, b = 0;
        for(int i=0; i<R.length; i++) {
            r = R[i]&0xff;
            g = G[i]&0xff;
            b = B[i]&0xff;
            if(!skinDetector.isSkin(r, g, b)) {
                src.toByte(0)[i] = (byte)r;
                src.toByte(1)[i] = (byte)g;
                src.toByte(2)[i] = (byte)b;
            }
        }

        byte[] gray = new byte[width*height];
        int c = 0;
        for(int i=0; i<R.length; i++) {
            r = R[i] & 0xff;
            g = G[i] & 0xff;
            b = B[i] & 0xff;
            c = (int)(0.299 *r + 0.587*g + 0.114*b);
            gray[i] = (byte)c;
        }

        GradientFilter gradientFilter = new GradientFilter();
        int[] gradient = gradientFilter.gradient(new ByteProcessor(gray, width, height));
        gray = null;
        for(int i=0; i<R.length; i++) {
            r = R[i]&0xff;
            g = G[i]&0xff;
            b = B[i]&0xff;
            if(gradient[i] > 50) {
                src.toByte(0)[i] = (byte)r;
                src.toByte(1)[i] = (byte)g;
                src.toByte(2)[i] = (byte)b;
            }
        }
        return src;
    }
}複製程式碼

5. 最終效果

BeautySkinFilter跟原先的濾鏡用法是一樣的,一行程式碼就可以實現想要的效果:)

RxImageData.bitmap(bitmap).addFilter(new BeautySkinFilter()).into(image1);複製程式碼

來看看在 Android 上的最終效果:

基於邊緣保留濾波實現人臉磨皮的演算法 | 掘金技術徵文
人臉磨皮效果.png

總結:

cv4jgloomyfish和我一起開發的影象處理庫,純java實現,目前還處於早期的版本。這次的人臉磨皮演算法也還有改進空間,未來我們還會繼續優化該演算法。

說來很慚愧,由於我們的工作都比較繁忙,沒有來得及完善開發文件。在馬上到來的五一期間,我們會補上文件,未來也會做出更加酷炫的功能。

先前的文章:
二值影象分析---案例實戰(文字分離+硬幣計數)
Java實現高斯模糊和影象的空間卷積
Java實現圖片濾鏡的高階玩法
Java實現圖片的濾鏡效果

掘金技術徵文:juejin.im/post/58d8e9…

相關文章