前置内容
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拆分
这些功能中都只需要对单个像素进行处理: 灰度化, 二值化, 去除深色背景, 负片, 复古, 明亮
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);
}