油畫效果
先上未經(jīng)任何處理的原圖
然后使用油畫風(fēng)格的濾鏡OilPaintFilter看看效果河咽,OilPaintFilter的使用方式就一句話:)
RxImageData.bitmap(bitmap).addFilter(new OilPaintFilter()).into(image);
OilPaintFilter在處理人物圖片和風(fēng)景圖片時(shí)具有比較好的效果论咏。
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;
}
}
其原理是使用邊緣保留濾波旭绒,邊緣保留濾波有很多種钻心,可以參考之前的一篇文章基于邊緣保留濾波實(shí)現(xiàn)人臉磨皮的算法舵稠。這里主要用的是Mean shift算法史飞,修改局部的像素權(quán)重從而實(shí)現(xiàn)圖像的像素模糊牵舱,以達(dá)到近似油畫的效果。
鉛筆畫效果
我們還開發(fā)了另一款濾鏡StrokeAreaFilter赖歌,用于模擬鉛筆畫的效果枉圃。
RxImageData.bitmap(bitmap).addFilter(new StrokeAreaFilter()).into(image);
看下效果
對(duì)于鉛筆畫而言可能有點(diǎn)牽強(qiáng),那再組合一個(gè)隨機(jī)噪聲的濾鏡試試庐冯。
RxImageData.bitmap(bitmap)
.addFilter(new StrokeAreaFilter())
.addFilter(new GaussianNoiseFilter())
.into(image);
效果也不是特別好孽亲,那再換一個(gè)USMFilter試試。
RxImageData.bitmap(bitmap)
.addFilter(new StrokeAreaFilter())
.addFilter(new USMFilter())
.into(image);
終于展父,這次效果比前面兩幅效果更好了返劲。
但是,由于是兩個(gè)濾鏡的疊加栖茉,速度會(huì)慢很多篮绿。再者,USMFilter它是繼承高斯濾鏡的吕漂。所以亲配,在實(shí)際使用中只需單獨(dú)使用StrokeAreaFilter即可,細(xì)節(jié)多少可以根據(jù)參數(shù)來(lái)調(diào)節(jié)惶凝。
總結(jié)
本文所使用的兩款濾鏡OilPaintFilter和StrokeAreaFilter都在cv4j中吼虎。
cv4j 是gloomyfish和我一起開發(fā)的圖像處理庫(kù),純java實(shí)現(xiàn)苍鲜,目前還處于早期的版本思灰,目前已經(jīng)更新了濾鏡的文檔。
上周末我們做了兩款濾鏡混滔,效果還算是蠻酷的洒疚,但是速度在移動(dòng)端還不夠理想歹颓,未來(lái)會(huì)想辦法對(duì)算法做一些改進(jìn),以便更好地滿足移動(dòng)端的體驗(yàn)拳亿。
該系列先前的文章:
二值圖像分析之輪廓分析
基于邊緣保留濾波實(shí)現(xiàn)人臉磨皮的算法
二值圖像分析:案例實(shí)戰(zhàn)(文本分離+硬幣計(jì)數(shù))
Java實(shí)現(xiàn)高斯模糊和圖像的空間卷積
Java實(shí)現(xiàn)圖片濾鏡的高級(jí)玩法
Java實(shí)現(xiàn)圖片的濾鏡效果