快速边缘保留滤波
快速边缘保留滤波是通过积分图像实现局部均方差的边缘保留模糊算法,计算简单而且可以做到计算量跟半径无关。
首先局部均方差滤波中计算局部均值的公式如下:
计算局部均值.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
总结:
cv4j 是gloomyfish和我一起开发的图像处理库,纯java实现,目前还处于早期的版本。这次的人脸磨皮算法也还有改进空间,未来我们还会继续优化该算法。