模擬油畫和鉛筆畫的濾鏡效果

油畫效果

先上未經任何處理的原圖


原圖.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實現圖片的濾鏡效果

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容

  • 不同圖像灰度不同,邊界處一般會有明顯的邊緣,利用此特征可以分割圖像。需要說明的是:邊緣和物體間的邊界并不等同,邊緣...
    大川無敵閱讀 13,907評論 0 29
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,660評論 25 708
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,151評論 4 61
  • 近期考慮對CSS進行一些重構,看著以前自己的代碼整個人凌亂了。于是做了一些調研,看到一些不一樣的地方給大家分享一下...
    金小可閱讀 1,905評論 0 2
  • 快開學了這個夏天在炎熱中結束了。 為什么我討厭開學但是又為什么我有點激動 九月的我們進了不同的教室 認識了不同的人...
    想要的總會來閱讀 279評論 4 7