图像滤镜功能Java实现
前置内容
-
RGB图片: 对于图片中的像素, 使用Red, Green, Blue 三种颜色共同得到一个最终的显示颜色, 其数值均在0 - 255 之间
-
在Java中对于图像的处理可以在使用IO流得到的BufferedImage类的实例操作, 也可以读入矩阵进行操作
-
BufferedImage类实例中会提供一个Graphics实例, 可以直接对BufferedImage实例进行操作
操作前的原图
对单点像素处理的滤镜算法
在这类方法中, 对于每个像素的处理只与这个像素本身有关, 与图片的其他信息无关, 于是可以简单对每个像素进行遍历, 按照预定的方法进行逐个处理
public void pixelRasterize(BufferedImage bufImg, String op) {
Graphics g = bufImg.getGraphics();
for (int i = 0; i < bufImg.getWidth(); i++) {
for (int j = 0; j < bufImg.getHeight(); j++) {
int pixelVal = bufImg.getRGB(i, j);
int red = (pixelVal >> 16) & 0xFF;
int green = (pixelVal >> 8) & 0xFF;
int blue = pixelVal & 0xFF;
Color color = pixelShader(red, green, blue, op);
bufImg.setRGB(i, j, color.getRGB());
}
}
this.bufImg = bufImg;
}
RGB拆分
- 对于图片中的像素, RGB数值使用一个int来表示, 由于其每种颜色只需要0 - 255, 所以仅使用了 1 byte 进行封装, 最高的8位置1, 次高的每8位依次为Red, Green, Blue
这些功能中都只需要对单个像素进行处理: 灰度化, 二值化, 去除深色背景, 负片, 复古, 明亮
public Color pixelShader(int red, int green, int blue, String op) {
switch (op) {
case "Original":
case "Second":
return new Color(red, green, blue);
case "Gray":
int gray = (red + green + blue) / 3;
return new Color(gray, gray, gray);
case "Binary":
gray = (red + green + blue) / 3;
if (gray < 70)
return new Color(0, 0, 0);
else return new Color(255, 255, 255);
case "Rid Bg":
gray = (red + green + blue) / 3;
if (gray > 63)
return new Color(red, green, blue);
else return new Color(225, 225, 225);
case "Negative":
red = 255 - red;
green = 255 - green;
blue = 255 - blue;
return new Color(red, green, blue);
case "OldFashion":
int R = (int) (0.393 * red + 0.469 * green + 0.049 * blue);
int G = (int) (0.349 * red + 0.586 * green + 0.068 * blue);
int B = (int) (0.272 * red + 0.534 * green + 0.031 * blue);
return new Color(R, G, B);
case "Bright":
red = Math.min(255, red + 10);
blue = Math.min(255, blue + 10);
green = Math.min(255, green + 10);
return new Color(red, green, blue);
}
return null;
}
-
灰度化
对于一个像素, 仅保留一个色彩通道的值, 使其三个色彩混合后只留下明暗信息
常见做法为, 使用一个通道的数值代替所有通道, 或者平均其在三个通道上的数值
-
二值化
将整个图片的像素颜色进行二分类, 使其被转变为纯黑或者纯白的信息, 分类标准一般按照需求来制定
常见做法为, 像素的灰度值高于一定数值将被认为是黑色, 否则为白色
-
去除背景
不基于机器学习的简单背景去除往往也不需要进行轮廓勾勒, 可以根据一定的需求去除一定范围内的颜色
比如, 灰度值高于一定数值可以渲染原色, 而低于一定数值会被去除
-
负片
负片的效果为颜色反转, 只需要使用其RGB的颜色使用最大深度减去原色数值即可
这图突然有点奇怪
-
复古
此类特效属于数值调整类特效, 一般做法只需要按照目前成熟的参数进行调整即可
-
明亮
此特效需要整体上调所有通道的数值使其更偏向于白色
常用方法为给所有通道加上固定的值, 但注意不要超过255
对区域进行采样后实现的滤镜算法
一些算法需要对图片中的某些像素进行采样, 然后用采样并处理过的数据进行滤镜操作, 这类算法一定是有损图片质量的
public void areaSamplingRasterize(BufferedImage bufImg, String op, boolean recOrOval, int sampleSize, int fillSize) {
Random rand = null;
BufferedImage orgImg = new BufferedImage(bufImg.getWidth(), bufImg.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics g = orgImg.getGraphics();
g.drawImage(bufImg, 0, 0, null);
g = bufImg.getGraphics();
if (op == "Oil")
rand = new Random();
for (int i = 0; i < orgImg.getWidth(); i += sampleSize) {
for (int j = 0; j < orgImg.getHeight(); j += sampleSize) {
g.setColor(pixelShader(orgImg.getRGB(i, j), op));
if (recOrOval) {
g.fillRect(i, j, fillSize, fillSize);
} else {
int fs = fillSize;
if (op.equals("Oil"))
fs = rand.nextInt(fillSize) + sampleSize;
g.fillOval(i, j, fs, fs);
}
}
}
this.bufImg = bufImg;
}
-
油画
油画风格的特点是着色精细度相对较粗糙, 所以可以通过对图片进行一定的随机区域着色的方法来进行制作
常见方法为, 对一定的采样大小区域内, 填充随机大小的圆形区域, 颜色为区域内的一个采样点的颜色, 注意填充时应尽量覆盖采样区域以免留下空白(或者使用原画作为底片), 也可以进行一点点的偏色处理
public Color pixelShader(int pixelVal, String op) {
switch (op) {
case "Mosaic":
case "Points":
case "Oil":
Random rand = new Random();
int red = (pixelVal >> 16) & 0xFF + (rand.nextInt(2) - 1);
int green = (pixelVal >> 8) & 0xFF + (rand.nextInt(2) - 1);
int blue = pixelVal & 0xFF + (rand.nextInt(2) - 1);
return new Color(Math.min(255, red), Math.min(255, green), Math.min(255, blue));
}
return null;
}
-
马赛克
马赛克的简单实现就是将一个采样区域内填充成同一种采样色, 同样可以进行适当的偏色
对区域处理实现的滤镜算法
图像卷积
使用一定的卷积窗口(权重矩阵) 对一个区域内的图像进行特征的提取, 综合或过滤
public void areaConvRasterize(BufferedImage bufImg, String op, int kernelSize, double[] rights) {
BufferedImage orgImg = new BufferedImage(bufImg.getWidth(), bufImg.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics g = orgImg.getGraphics();
g.drawImage(bufImg, 0, 0, null);
for (int i = 0; i < orgImg.getWidth(); i++) {
for (int j = 0; j < orgImg.getHeight(); j++) {
int reds = 0, greens = 0, blues = 0;
int edgeSize = (int) Math.sqrt(kernelSize);
int mx = Math.min(orgImg.getWidth(), i + edgeSize);
int my = Math.min(orgImg.getHeight(), j + edgeSize);
ArrayList<Integer> pixelVal = new ArrayList(kernelSize);
for (int nx = i; nx < mx; nx++) {
for (int ny = j; ny < my; ny++) {
pixelVal.add(orgImg.getRGB(nx, ny));
}
}
while (pixelVal.size() < kernelSize) {
pixelVal.add(orgImg.getRGB(i, j));
}
int x = Math.min(mx - 1, i + edgeSize / 2);
int y = Math.min(my - 1, j + edgeSize / 2);
bufImg.setRGB(x, y, convPixelShader(kernelSize, pixelVal, op, new int[]{x, y}).getRGB());
}
}
this.bufImg = bufImg;
}
常见的卷积一般以卷积窗口覆盖的中心作为目标对象, 所以对于边界来说, 处理方法一般为认为图片无限重复或者直接抛弃边界
public Color convPixelShader(int kernelSize, ArrayList<Integer> pixelVal, String op, int[] centerPos) {
int radius = (int) Math.sqrt(kernelSize);
double[] rights = new double[kernelSize];
int reds = 0, greens = 0, blues = 0;
switch (op) {
case "Sharpen":
Arrays.fill(rights, 0, kernelSize, -1);
rights[kernelSize >> 1] = 9;
break;
case "Blur":
int l = 0;
for (int x = -radius >> 1; x < radius >> 1; x++) {
for (int y = 0; y < radius; y++) {
rights[l++] = Math.pow((int) Math.E, -(x * x + y * y) / (2 * GuassBlur.sigma * GuassBlur.sigma))
/ (2 * pi * GuassBlur.sigma * GuassBlur.sigma);
}
}
break;
}
for (int i = 0; i < kernelSize; i++) {
reds += ((pixelVal.get(i) >> 16) & 0xFF) * rights[i];
greens += ((pixelVal.get(i) >> 8) & 0xFF) * rights[i];
blues += (pixelVal.get(i) & 0xFF) * rights[i];
}
reds += 20;
greens += 20;
blues += 20;
reds = Math.min(255, reds);
reds = Math.max(0, reds);
greens = Math.min(255, greens);
greens = Math.max(0, greens);
blues = Math.min(255, blues);
blues = Math.max(0, blues);
return new Color(reds, greens, blues);
}
-
锐化
锐化操作的目的是增强图片中不同颜色间边界的对比度, 突出边界
其卷积核相对固定,常用为 3x3 卷积 的 (1,1)位置为9, 其他位置为 -1 的权重矩阵
-
模糊
模糊操作的实现有很多, 以高斯模糊为例, 其需要通过计算某像素相邻位置对该像素的影响来得到结果,其公式为
(x,y)为卷积核的中心坐标, 一般作为坐标原点
效果一般取决于模糊半径和σ