美文网首页机器视觉图片处理Android知识
模拟油画和铅笔画的滤镜效果

模拟油画和铅笔画的滤镜效果

作者: fengzhizi715 | 来源:发表于2017-05-12 18:45 被阅读1148次

    油画效果

    先上未经任何处理的原图


    原图.png

    然后使用油画风格的滤镜OilPaintFilter看看效果,OilPaintFilter的使用方式就一句话:)

    RxImageData.bitmap(bitmap).addFilter(new OilPaintFilter()).into(image);
    
    油画效果.png

    OilPaintFilter在处理人物图片和风景图片时具有比较好的效果。

    OilPaintFilter的源码如下:

    import com.cv4j.core.datamodel.ColorProcessor;
    import com.cv4j.core.datamodel.ImageProcessor;
    
    /**
     * Created by Tony Shen on 2017/5/7.
     */
    
    public class OilPaintFilter extends BaseFilter {
    
        private int radius = 15; // default value
        private int intensity = 40; // default value
    
        public OilPaintFilter() {
            this(15, 40);
        }
    
        public OilPaintFilter(int radius, int graylevel) {
            this.radius = radius;
            this.intensity = graylevel;
        }
    
        public int getRadius() {
            return radius;
        }
    
        public void setRadius(int radius) {
            this.radius = radius;
        }
    
        public int getIntensity() {
            return intensity;
        }
    
        public void setIntensity(int intensity) {
            this.intensity = intensity;
        }
    
        @Override
        public ImageProcessor doFilter(ImageProcessor src) {
    
            byte[][] output = new byte[3][R.length];
    
            int index = 0;
            int subradius = this.radius / 2;
            int[] intensityCount = new int[intensity+1];
            int[] ravg = new int[intensity+1];
            int[] gavg = new int[intensity+1];
            int[] bavg = new int[intensity+1];
    
            for(int i=0; i<=intensity; i++) {
                intensityCount[i] = 0;
                ravg[i] = 0;
                gavg[i] = 0;
                bavg[i] = 0;
            }
    
            for(int row=0; row<height; row++) {
                int ta = 0, tr = 0, tg = 0, tb = 0;
                for(int col=0; col<width; col++) {
    
                    for(int subRow = -subradius; subRow <= subradius; subRow++)
                    {
                        for(int subCol = -subradius; subCol <= subradius; subCol++)
                        {
                            int nrow = row + subRow;
                            int ncol = col + subCol;
                            if(nrow >=height || nrow < 0)
                            {
                                nrow = 0;
                            }
                            if(ncol >= width || ncol < 0)
                            {
                                ncol = 0;
                            }
                            index = nrow * width + ncol;
                            tr = R[index] & 0xff;
                            tg = G[index] & 0xff;
                            tb = B[index] & 0xff;
                            int curIntensity = (int)(((double)((tr+tg+tb)/3)*intensity)/255.0f);
                            intensityCount[curIntensity]++;
                            ravg[curIntensity] += tr;
                            gavg[curIntensity] += tg;
                            bavg[curIntensity] += tb;
                        }
                    }
    
                    // find the max number of same gray level pixel
                    int maxCount = 0, maxIndex = 0;
                    for(int m=0; m<intensityCount.length; m++)
                    {
                        if(intensityCount[m] > maxCount)
                        {
                            maxCount = intensityCount[m];
                            maxIndex = m;
                        }
                    }
    
                    // get average value of the pixel
                    int nr = ravg[maxIndex] / maxCount;
                    int ng = gavg[maxIndex] / maxCount;
                    int nb = bavg[maxIndex] / maxCount;
                    index = row * width + col;
                    output[0][index] = (byte) nr;
                    output[1][index] = (byte) ng;
                    output[2][index] = (byte) nb;
    
                    // post clear values for next pixel
                    for(int i=0; i<=intensity; i++)
                    {
                        intensityCount[i] = 0;
                        ravg[i] = 0;
                        gavg[i] = 0;
                        bavg[i] = 0;
                    }
    
                }
            }
    
            ((ColorProcessor) src).putRGB(output[0], output[1], output[2]);
            output = null;
    
            return src;
        }
    }
    

    其原理是使用边缘保留滤波,边缘保留滤波有很多种,可以参考之前的一篇文章基于边缘保留滤波实现人脸磨皮的算法。这里主要用的是Mean shift算法,修改局部的像素权重从而实现图像的像素模糊,以达到近似油画的效果。

    铅笔画效果

    我们还开发了另一款滤镜StrokeAreaFilter,用于模拟铅笔画的效果。

    RxImageData.bitmap(bitmap).addFilter(new StrokeAreaFilter()).into(image);
    

    看下效果


    铅笔画效果.png

    对于铅笔画而言可能有点牵强,那再组合一个随机噪声的滤镜试试。

    RxImageData.bitmap(bitmap)
            .addFilter(new StrokeAreaFilter())
            .addFilter(new GaussianNoiseFilter())
            .into(image);
    
    铅笔画效果2.png

    效果也不是特别好,那再换一个USMFilter试试。

    RxImageData.bitmap(bitmap)
           .addFilter(new StrokeAreaFilter())
           .addFilter(new USMFilter())
           .into(image);
    

    终于,这次效果比前面两幅效果更好了。

    铅笔画效果3.png

    但是,由于是两个滤镜的叠加,速度会慢很多。再者,USMFilter它是继承高斯滤镜的。所以,在实际使用中只需单独使用StrokeAreaFilter即可,细节多少可以根据参数来调节。

    总结

    本文所使用的两款滤镜OilPaintFilter和StrokeAreaFilter都在cv4j中。

    cv4jgloomyfish和我一起开发的图像处理库,纯java实现,目前还处于早期的版本,目前已经更新了滤镜的文档。

    上周末我们做了两款滤镜,效果还算是蛮酷的,但是速度在移动端还不够理想,未来会想办法对算法做一些改进,以便更好地满足移动端的体验。

    该系列先前的文章:
    二值图像分析之轮廓分析
    基于边缘保留滤波实现人脸磨皮的算法
    二值图像分析:案例实战(文本分离+硬币计数)
    Java实现高斯模糊和图像的空间卷积
    Java实现图片滤镜的高级玩法
    Java实现图片的滤镜效果

    相关文章

      网友评论

      本文标题:模拟油画和铅笔画的滤镜效果

      本文链接:https://www.haomeiwen.com/subject/pfnatxtx.html