使用 Java 对图像进行各种处理

it2026-01-20  6

接上一篇的使用 Java 提取图片的色彩边界,这一篇增加了去除噪点,锐化,模糊,S曲线加深,各向异性等处理,想做成类似于 PS 的人物卡通化的功能的,失败了,效果不行. 这其中参考了大量的网上例子,感谢各位大佬的分享,引用网址都在代码里面了.

引入jar包

<!-- java图片工具 https://mvnrepository.com/artifact/net.coobird/thumbnailator --> <dependency> <groupId>net.coobird</groupId> <artifactId>thumbnailator</artifactId> <version>0.4.12</version> </dependency>

实现代码

import net.coobird.thumbnailator.Thumbnails; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.*; import java.nio.charset.StandardCharsets; public class ImageUtil { // 获取图片线条边界用 // 阈值越大,空白越多 // 值3000000适用于色彩分明的图片,如卡通人物蜡笔小新 // 值1000000适用于色彩过渡平缓的图片,如3D卡通今年我们十七八岁 private static final int threshold = 3000000; // 生成文本宽度 // 值100适用于商标,头像等小图片 // 值400适用于复杂,内容丰富的大图片 private static final int picWidth = 500; public static void main(String[] args) throws IOException { String fromPic = "C:\\Users\\Public\\Pictures\\Sample Pictures\\QQ图片20201022091237.jpg"; String toTxt = "d:\\test5.txt"; BufferedImage bufferedImage = ImageIO.read(new File(fromPic)); // 压缩图片 BufferedImage compactImage = Thumbnails.of(bufferedImage).size(picWidth, 2000).asBufferedImage(); // 灰度化 //BufferedImage grayImage = grayingImage(compactImage); // 二值化 //BufferedImage binaryImage = binaryImage(grayImage); // 获取边界 //BufferedImage borderImage = getImageBorder(compactImage); // 去除噪点 //BufferedImage noNoiseImage = clearNoise(borderImage); // 锐化 //BufferedImage sharpenImage = sharpenImage2(compactImage); // 提取边缘,别人的 //BufferedImage sobelProcessIamge = sobelProcess(sharpenImage); // 均值滤波 //BufferedImage meanValueProcessIamge = meanValueProcess(sharpenImage); // 底片效果 //BufferedImage negativeImage = negativeProcess(bufferedImage); // S曲线加深 //BufferedImage curveImage = curveProcess(compactImage); // USM锐化 //BufferedImage usmSharpenImage = USMSharpen(compactImage); // 扩散,各向异性 /*BufferedImage anisotropyImage = anisotropyImage(compactImage); for (int i = 1; i < 10; i++) { anisotropyImage = anisotropyImage(anisotropyImage); }*/ // 5.输出到txt文本 //writeToTxt(noNoiseImage, toTxt); // 组合功能一: 提取色彩分明图像的边界,像卡通人物,logo,二维码等 BufferedImage curveImage = curveProcess(compactImage); BufferedImage grayImage = grayingImage(curveImage); BufferedImage binaryImage = binaryImage(grayImage); BufferedImage borderImage = getImageBorder(binaryImage); // 保存图片 File newFile = new File("d:\\test8.jpg"); ImageIO.write(borderImage, "jpg", newFile); } /** * 灰度化图片 * * @param bufferedImage 原图片 * @return 灰度化之后的图片 */ private static BufferedImage grayingImage(BufferedImage bufferedImage) { BufferedImage grayImage = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), BufferedImage.TYPE_BYTE_GRAY); // 以图片左上角点为坐标原点 for (int i = 0; i < bufferedImage.getWidth(); i++) { for (int j = 0; j < bufferedImage.getHeight(); j++) { int color = bufferedImage.getRGB(i, j); grayImage.setRGB(i, j, color); } } return grayImage; } /** * 二值化图片 * * @param bufferedImage 原图片 * @return 二值化后的图片 */ private static BufferedImage binaryImage(BufferedImage bufferedImage) { BufferedImage grayImage = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), bufferedImage.getType()); int threshold = getMeanThreshold(bufferedImage); for (int i = 0; i < bufferedImage.getWidth(); i++) { for (int j = 0; j < bufferedImage.getHeight(); j++) { // getRGB()方法,根据手册,其返回的int型数据(32位)为ARGB格式,其中ARGB各占8bit int color = bufferedImage.getRGB(i, j); int r = (color >> 16) & 0xff; int g = (color >> 8) & 0xff; int b = color & 0xff; int gray = (int) (0.3 * r + 0.59 * g + 0.11 * b); if (gray > threshold) { // 白色 grayImage.setRGB(i, j, 0xFFFFFF); } else { // 黑色 grayImage.setRGB(i, j, 0); } } } return grayImage; } /** * 获取图片的阀值,采用基于灰度平均值的阈值 * * @param bufferedImage 原图片 * @return 二值化的阈值 */ private static int getMeanThreshold(BufferedImage bufferedImage) { int w = bufferedImage.getWidth(); int h = bufferedImage.getHeight(); int num = 0; int sum = 0; for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) { int color = bufferedImage.getRGB(i, j); int r = (color >> 16) & 0xff; int g = (color >> 8) & 0xff; int b = color & 0xff; int gray = (int) (0.3 * r + 0.59 * g + 0.11 * b); sum += gray; num += 1; } } // 测试表明,阀值取平均值的1.2倍效果最好。 int threshold = sum / num; if (threshold * 1.2 < 255) { threshold = (int) (1.2 * sum / num); } System.out.println("width: " + w + " height: " + h + " threshold: " + threshold); return threshold; } /** * 输出 0,1 TXT文本 */ public static void writeToTxt(BufferedImage bufferedImage, String toSaveFilePath) { File file = new File(toSaveFilePath); try { Writer writer = new OutputStreamWriter(new FileOutputStream(file, true), StandardCharsets.UTF_8); StringBuilder builder = new StringBuilder(); for (int j = 0; j < bufferedImage.getHeight(); j++) { for (int i = 0; i < bufferedImage.getWidth(); i++) { int color = bufferedImage.getRGB(i, j); if (color == -1) { builder.append(" "); } else { builder.append("0 "); } } builder.append("\r\n"); } writer.write(builder.toString()); writer.close(); } catch (Exception e) { e.printStackTrace(); } } /** * 提取图片的边界 * 对二维码有奇效 * * @param bufferedImage 原图片 * @return 二值化后的图片 */ private static BufferedImage getImageBorder(BufferedImage bufferedImage) { BufferedImage borderImage = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), bufferedImage.getType()); //List<String> toDealPoints = new ArrayList<>(); int imgWidth = bufferedImage.getWidth(); int imgHeight = bufferedImage.getHeight(); for (int i = 1; i < imgWidth - 1; i++) { for (int j = 1; j < imgHeight - 1; j++) { // 当前点 int color = bufferedImage.getRGB(i, j); // 上点 int upColor = bufferedImage.getRGB(i, j - 1); // 下点 int downColor = bufferedImage.getRGB(i, j + 1); // 左点 int leftColor = bufferedImage.getRGB(i - 1, j); // 右点 int rightColor = bufferedImage.getRGB(i + 1, j); // 如果某个黑点的上下左右点都为黑点,就表示它不是边界,把它设为白点 if (isQualified(color, upColor, downColor, leftColor, rightColor)) { // 白色 borderImage.setRGB(i, j, 0xFFFFFF); } else { // 原色不变 borderImage.setRGB(i, j, color); } } } return borderImage; } /** * 根据设置的阈值,判断当前点是否是边界点 * 判断规则如下: * 如果当前点是白色的点,直接跳过 * 如果当前点不是白色,且它的上下左右4个点和它的差别都在阈值内, * 那么就认为它不是边界点,返回true,否则返回false; * * @param color 当前点 * @param upColor 上点 * @param downColor 下点 * @param leftColor 左点 * @param rightColor 右点 * @return 是否设置为白色 */ public static boolean isQualified(int color, int upColor, int downColor, int leftColor, int rightColor) { // color == -1 表示白色,白色的不需要再设置为白色 return color != -1 && (Math.abs(color - upColor) < threshold && Math.abs(color - downColor) < threshold && Math.abs(color - leftColor) < threshold && Math.abs(color - rightColor) < threshold); } /** * 判断一个像素点是不是噪点 * 只要它上下左右四个相邻的像素点中有3个及以上以上是白色像素,那么就认为它是噪点 * 缺陷: 对于一条斜线来说,会被当成噪点清除掉.可以添加条件,如果当前像素点的四个斜对角像素有3个或以上白色,就认为是噪点. * * @param color 当前像素 * @param upColor 上方像素 * @param downColor 下方像素 * @param leftColor 左边像素 * @param rightColor 右边像素 * @return 是否是噪点 */ public static boolean isNoise(int color, int upColor, int downColor, int leftColor, int rightColor) { // color == -1 表示白色,白色的不需要再设置为白色 if (color != -1) { int whiteCount = 0; if (upColor == -1) { whiteCount++; } if (downColor == -1) { whiteCount++; } if (leftColor == -1) { whiteCount++; } if (rightColor == -1) { whiteCount++; } return whiteCount >= 3; } return false; } /** * 清楚噪点 * 如果一个黑点,它的上下左右四个点中至少有三个点是白色的, * 那么就认为它是噪点,将它变成白色 * * @param bufferedImage 原图片 * @return 处理后的图片 */ private static BufferedImage clearNoise(BufferedImage bufferedImage) { BufferedImage noNoiseImage = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), bufferedImage.getType()); int imgWidth = bufferedImage.getWidth(); int imgHeight = bufferedImage.getHeight(); for (int i = 1; i < imgWidth - 1; i++) { for (int j = 1; j < imgHeight - 1; j++) { // 当前点 int color = bufferedImage.getRGB(i, j); // 上点 int upColor = bufferedImage.getRGB(i, j - 1); // 下点 int downColor = bufferedImage.getRGB(i, j + 1); // 左点 int leftColor = bufferedImage.getRGB(i - 1, j); // 右点 int rightColor = bufferedImage.getRGB(i + 1, j); // 如果某个黑点的上下左右点都为黑点,就表示它不是边界,把它设为白点 if (isNoise(color, upColor, downColor, leftColor, rightColor)) { // 白色 noNoiseImage.setRGB(i, j, 0xFFFFFF); } else { // 原色不变 noNoiseImage.setRGB(i, j, color); } } } return noNoiseImage; } private static final int[][] sharpen = new int[][]{{-1, -1, -1}, {-1, 9, -1}, {-1, -1, -1}}; /** * 锐化图片 * 将原图像和拉普拉斯图像叠加到一起,便可以得到锐化图像。 * 锐化卷积核模板 * 0 -1 0 * -1 5 -1 * 0 -1 0 * * @param bufferedImage 原图片 * @return 目标图片 */ private static BufferedImage sharpenImage(BufferedImage bufferedImage) { BufferedImage image = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), bufferedImage.getType()); int imgWidth = bufferedImage.getWidth(); int imgHeight = bufferedImage.getHeight(); int[][] pixels = new int[3][3]; for (int i = 1; i < imgWidth - 1; i++) { for (int j = 1; j < imgHeight - 1; j++) { pixels[0][0] = bufferedImage.getRGB(i - 1, j - 1); pixels[0][1] = bufferedImage.getRGB(i, j - 1); pixels[0][2] = bufferedImage.getRGB(i + 1, j - 1); pixels[1][0] = bufferedImage.getRGB(i - 1, j); pixels[1][1] = bufferedImage.getRGB(i, j); pixels[1][2] = bufferedImage.getRGB(i + 1, j); pixels[2][0] = bufferedImage.getRGB(i - 1, j + 1); pixels[2][1] = bufferedImage.getRGB(i, j + 1); pixels[2][2] = bufferedImage.getRGB(i + 1, j + 1); int newPixels = pixels[0][0] * sharpen[0][0] + pixels[0][1] * sharpen[0][1] + pixels[0][2] * sharpen[0][2] + pixels[1][0] * sharpen[1][0] + pixels[1][1] * sharpen[1][1] + pixels[1][2] * sharpen[1][2] + pixels[2][0] * sharpen[2][0] + pixels[2][1] * sharpen[2][1] + pixels[2][2] * sharpen[2][2]; image.setRGB(i, j, newPixels); } } return image; } /** * https://www.cnblogs.com/wangyong/p/8367623.html * * @param src * @return */ private static BufferedImage sharpenImage2(BufferedImage src) { // 拉普拉斯算子 int[] LAPLACE = new int[]{0, -1, 0, -1, 4, -1, 0, -1, 0}; int width = src.getWidth(); int height = src.getHeight(); int[] pixels = new int[width * height]; int[] outPixels = new int[width * height]; int type = src.getType(); if (type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB) { src.getRaster().getDataElements(0, 0, width, height, pixels); } src.getRGB(0, 0, width, height, pixels, 0, width); int k0 = 0, k1 = 0, k2 = 0; int k3 = 0, k4 = 0, k5 = 0; int k6 = 0, k7 = 0, k8 = 0; k0 = LAPLACE[0]; k1 = LAPLACE[1]; k2 = LAPLACE[2]; k3 = LAPLACE[3]; k4 = LAPLACE[4]; k5 = LAPLACE[5]; k6 = LAPLACE[6]; k7 = LAPLACE[7]; k8 = LAPLACE[8]; int offset = 0; int sr = 0, sg = 0, sb = 0; int r = 0, g = 0, b = 0; for (int row = 1; row < height - 1; row++) { offset = row * width; for (int col = 1; col < width - 1; col++) { r = (pixels[offset + col] >> 16) & 0xff; g = (pixels[offset + col] >> 8) & 0xff; b = (pixels[offset + col]) & 0xff; // red sr = k0 * ((pixels[offset - width + col - 1] >> 16) & 0xff) + k1 * ((pixels[offset - width + col] >> 16) & 0xff) + k2 * ((pixels[offset - width + col + 1] >> 16) & 0xff) + k3 * ((pixels[offset + col - 1] >> 16) & 0xff) + k4 * ((pixels[offset + col] >> 16) & 0xff) + k5 * ((pixels[offset + col + 1] >> 16) & 0xff) + k6 * ((pixels[offset + width + col - 1] >> 16) & 0xff) + k7 * ((pixels[offset + width + col] >> 16) & 0xff) + k8 * ((pixels[offset + width + col + 1] >> 16) & 0xff); // green sg = k0 * ((pixels[offset - width + col - 1] >> 8) & 0xff) + k1 * ((pixels[offset - width + col] >> 8) & 0xff) + k2 * ((pixels[offset - width + col + 1] >> 8) & 0xff) + k3 * ((pixels[offset + col - 1] >> 8) & 0xff) + k4 * ((pixels[offset + col] >> 8) & 0xff) + k5 * ((pixels[offset + col + 1] >> 8) & 0xff) + k6 * ((pixels[offset + width + col - 1] >> 8) & 0xff) + k7 * ((pixels[offset + width + col] >> 8) & 0xff) + k8 * ((pixels[offset + width + col + 1] >> 8) & 0xff); // blue sb = k0 * (pixels[offset - width + col - 1] & 0xff) + k1 * (pixels[offset - width + col] & 0xff) + k2 * (pixels[offset - width + col + 1] & 0xff) + k3 * (pixels[offset + col - 1] & 0xff) + k4 * (pixels[offset + col] & 0xff) + k5 * (pixels[offset + col + 1] & 0xff) + k6 * (pixels[offset + width + col - 1] & 0xff) + k7 * (pixels[offset + width + col] & 0xff) + k8 * (pixels[offset + width + col + 1] & 0xff); // 运算后的像素值和原图像素叠加 r += sr; g += sg; b += sb; outPixels[offset + col] = (0xff << 24) | (clamp(r) << 16) | (clamp(g) << 8) | clamp(b); } } BufferedImage dest = new BufferedImage(width, height, src.getType()); dest.setRGB(0, 0, width, height, outPixels, 0, width); return dest; } /** * Sobel提取边缘 * * @param src * @return */ public static BufferedImage sobelProcess(BufferedImage src) { // Sobel算子 int[] sobel_y = new int[]{-1, -2, -1, 0, 0, 0, 1, 2, 1}; int[] sobel_x = new int[]{-1, 0, 1, -2, 0, 2, -1, 0, 1}; int width = src.getWidth(); int height = src.getHeight(); int[] pixels = new int[width * height]; int[] outPixels = new int[width * height]; src.getRGB(0, 0, width, height, pixels, 0, width); int offset = 0; int x0 = sobel_x[0]; int x1 = sobel_x[1]; int x2 = sobel_x[2]; int x3 = sobel_x[3]; int x4 = sobel_x[4]; int x5 = sobel_x[5]; int x6 = sobel_x[6]; int x7 = sobel_x[7]; int x8 = sobel_x[8]; int k0 = sobel_y[0]; int k1 = sobel_y[1]; int k2 = sobel_y[2]; int k3 = sobel_y[3]; int k4 = sobel_y[4]; int k5 = sobel_y[5]; int k6 = sobel_y[6]; int k7 = sobel_y[7]; int k8 = sobel_y[8]; int yr = 0, yg = 0, yb = 0; int xr = 0, xg = 0, xb = 0; int r = 0, g = 0, b = 0; for (int row = 1; row < height - 1; row++) { offset = row * width; for (int col = 1; col < width - 1; col++) { // red yr = k0 * ((pixels[offset - width + col - 1] >> 16) & 0xff) + k1 * ((pixels[offset - width + col] >> 16) & 0xff) + k2 * ((pixels[offset - width + col + 1] >> 16) & 0xff) + k3 * ((pixels[offset + col - 1] >> 16) & 0xff) + k4 * ((pixels[offset + col] >> 16) & 0xff) + k5 * ((pixels[offset + col + 1] >> 16) & 0xff) + k6 * ((pixels[offset + width + col - 1] >> 16) & 0xff) + k7 * ((pixels[offset + width + col] >> 16) & 0xff) + k8 * ((pixels[offset + width + col + 1] >> 16) & 0xff); xr = x0 * ((pixels[offset - width + col - 1] >> 16) & 0xff) + x1 * ((pixels[offset - width + col] >> 16) & 0xff) + x2 * ((pixels[offset - width + col + 1] >> 16) & 0xff) + x3 * ((pixels[offset + col - 1] >> 16) & 0xff) + x4 * ((pixels[offset + col] >> 16) & 0xff) + x5 * ((pixels[offset + col + 1] >> 16) & 0xff) + x6 * ((pixels[offset + width + col - 1] >> 16) & 0xff) + x7 * ((pixels[offset + width + col] >> 16) & 0xff) + x8 * ((pixels[offset + width + col + 1] >> 16) & 0xff); // green yg = k0 * ((pixels[offset - width + col - 1] >> 8) & 0xff) + k1 * ((pixels[offset - width + col] >> 8) & 0xff) + k2 * ((pixels[offset - width + col + 1] >> 8) & 0xff) + k3 * ((pixels[offset + col - 1] >> 8) & 0xff) + k4 * ((pixels[offset + col] >> 8) & 0xff) + k5 * ((pixels[offset + col + 1] >> 8) & 0xff) + k6 * ((pixels[offset + width + col - 1] >> 8) & 0xff) + k7 * ((pixels[offset + width + col] >> 8) & 0xff) + k8 * ((pixels[offset + width + col + 1] >> 8) & 0xff); xg = x0 * ((pixels[offset - width + col - 1] >> 8) & 0xff) + x1 * ((pixels[offset - width + col] >> 8) & 0xff) + x2 * ((pixels[offset - width + col + 1] >> 8) & 0xff) + x3 * ((pixels[offset + col - 1] >> 8) & 0xff) + x4 * ((pixels[offset + col] >> 8) & 0xff) + x5 * ((pixels[offset + col + 1] >> 8) & 0xff) + x6 * ((pixels[offset + width + col - 1] >> 8) & 0xff) + x7 * ((pixels[offset + width + col] >> 8) & 0xff) + x8 * ((pixels[offset + width + col + 1] >> 8) & 0xff); // blue yb = k0 * (pixels[offset - width + col - 1] & 0xff) + k1 * (pixels[offset - width + col] & 0xff) + k2 * (pixels[offset - width + col + 1] & 0xff) + k3 * (pixels[offset + col - 1] & 0xff) + k4 * (pixels[offset + col] & 0xff) + k5 * (pixels[offset + col + 1] & 0xff) + k6 * (pixels[offset + width + col - 1] & 0xff) + k7 * (pixels[offset + width + col] & 0xff) + k8 * (pixels[offset + width + col + 1] & 0xff); xb = x0 * (pixels[offset - width + col - 1] & 0xff) + x1 * (pixels[offset - width + col] & 0xff) + x2 * (pixels[offset - width + col + 1] & 0xff) + x3 * (pixels[offset + col - 1] & 0xff) + x4 * (pixels[offset + col] & 0xff) + x5 * (pixels[offset + col + 1] & 0xff) + x6 * (pixels[offset + width + col - 1] & 0xff) + x7 * (pixels[offset + width + col] & 0xff) + x8 * (pixels[offset + width + col + 1] & 0xff); // 索贝尔梯度 r = (int) Math.sqrt(yr * yr + xr * xr); g = (int) Math.sqrt(yg * yg + xg * xg); b = (int) Math.sqrt(yb * yb + xb * xb); outPixels[offset + col] = (0xff << 24) | (clamp(r) << 16) | (clamp(g) << 8) | clamp(b); } } BufferedImage dest = new BufferedImage(width, height, src.getType()); dest.setRGB(0, 0, width, height, outPixels, 0, width); return dest; } /** * 模糊 * 计算以当前像素为中心3*3矩阵中所有像素点的和,除以9,就是模糊后的当前像素. * 对所有的像素点做同样的操作,最终得到的就是模糊图像. * 均值滤波 * * @param src * @return */ private static BufferedImage meanValueProcess(BufferedImage src) { int width = src.getWidth(); int height = src.getHeight(); int[] pixels = new int[width * height]; int[] outPixels = new int[width * height]; src.getRGB(0, 0, width, height, pixels, 0, width); // 均值滤波使用的卷积模板半径,这里使用5*5均值,所以半径使用2 int radius = 1; int total = (2 * radius + 1) * (2 * radius + 1); int r = 0, g = 0, b = 0; for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { int rSum = 0; int gSum = 0; int bSum = 0; for (int i = -radius; i <= radius; i++) { int roffset = row + i; roffset = (roffset < 0) ? 0 : (roffset >= height ? height - 1 : roffset); for (int j = -radius; j <= radius; j++) { int coffset = col + j; coffset = (coffset < 0) ? 0 : (coffset >= width ? width - 1 : coffset); int pixel = pixels[roffset * width + coffset]; r = (pixel >> 16) & 0XFF; g = (pixel >> 8) & 0xff; b = pixel & 0xff; rSum += r; gSum += g; bSum += b; } } r = rSum / total; g = gSum / total; b = bSum / total; outPixels[row * width + col] = (255 << 24) | (clamp(r) << 16) | (clamp(g) << 8) | clamp(b); } } BufferedImage dest = new BufferedImage(width, height, src.getType()); dest.setRGB(0, 0, width, height, outPixels, 0, width); return dest; } /** * 底片效果 * 获取当前像素的值,用255减去这个值,就是底片的效果. * * @param src 源图片 * @return 处理后的图片 */ public static BufferedImage negativeProcess(BufferedImage src) { int width = src.getWidth(); int height = src.getHeight(); int[] pixels = new int[width * height]; int[] outPixels = new int[width * height]; src.getRGB(0, 0, width, height, pixels, 0, width); int offset = 0; for (int row = 1; row < height - 1; row++) { offset = row * width; for (int col = 1; col < width - 1; col++) { int r = (pixels[offset + col] >> 16) & 0xff; int g = (pixels[offset + col] >> 8) & 0xff; int b = (pixels[offset + col]) & 0xff; // red r = 255 - r; g = 255 - g; b = 255 - b; outPixels[offset + col] = (0xff << 24) | (clamp(r) << 16) | (clamp(g) << 8) | clamp(b); } } BufferedImage dest = new BufferedImage(width, height, src.getType()); dest.setRGB(0, 0, width, height, outPixels, 0, width); return dest; } /** * 逻辑斯谛曲线 * S曲线加深对比度 * 效果: 浅色更浅,深色更深 * 缺点: 该曲线函数无法进行轻度的加深,需要另外找替代函数 * * @param src 源图像 * @return 处理后的图像 */ public static BufferedImage curveProcess(BufferedImage src) { int width = src.getWidth(); int height = src.getHeight(); int[] pixels = new int[width * height]; int[] outPixels = new int[width * height]; src.getRGB(0, 0, width, height, pixels, 0, width); int offset = 0; for (int row = 1; row < height - 1; row++) { offset = row * width; for (int col = 1; col < width - 1; col++) { int r = (pixels[offset + col] >> 16) & 0xff; int g = (pixels[offset + col] >> 8) & 0xff; int b = (pixels[offset + col]) & 0xff; // red r = getSCurve(r); g = getSCurve(g); b = getSCurve(b); outPixels[offset + col] = (0xff << 24) | (clamp(r) << 16) | (clamp(g) << 8) | clamp(b); } } BufferedImage dest = new BufferedImage(width, height, src.getType()); dest.setRGB(0, 0, width, height, outPixels, 0, width); return dest; } /** * USM锐化 * 原理: 先对图片进行均值滤波,再将原图片与模糊图片的差值加到原图片上,就得到了锐化后的图片,具体说明可以看下方网址. * 参数说明 * 数量:代表你要做锐化的强度。 * 半径:粒度,我们所希望强调画面细节的尺度。 * 阈值:是在Photoshop中指定,当反差小于某一个数字的时候,就不进行锐化处理。 * USM功能详解: https://baijiahao.baidu.com/s?id=1651538317157891526&wfr=spider&for=pc * * @param src 源图像 * @return 处理后的图像 */ private static BufferedImage USMSharpen(BufferedImage src) { int width = src.getWidth(); int height = src.getHeight(); int[] pixels = new int[width * height]; int[] outPixels = new int[width * height]; src.getRGB(0, 0, width, height, pixels, 0, width); // 均值滤波使用的卷积模板半径,这里使用5*5均值,所以半径使用2 int radius = 2; int total = (2 * radius + 1) * (2 * radius + 1); int r = 0, g = 0, b = 0; for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { int rSum = 0; int gSum = 0; int bSum = 0; for (int i = -radius; i <= radius; i++) { int roffset = row + i; roffset = (roffset < 0) ? 0 : (roffset >= height ? height - 1 : roffset); for (int j = -radius; j <= radius; j++) { int coffset = col + j; coffset = (coffset < 0) ? 0 : (coffset >= width ? width - 1 : coffset); int pixel = pixels[roffset * width + coffset]; r = (pixel >> 16) & 0XFF; g = (pixel >> 8) & 0xff; b = pixel & 0xff; rSum += r; gSum += g; bSum += b; } } r = 2 * r - rSum / total; g = 2 * g - gSum / total; b = 2 * b - bSum / total; outPixels[row * width + col] = (255 << 24) | (clamp(r) << 16) | (clamp(g) << 8) | clamp(b); } } BufferedImage dest = new BufferedImage(width, height, src.getType()); dest.setRGB(0, 0, width, height, outPixels, 0, width); return dest; } /** * 扩散滤镜,各向异性 * 采用流体理论,像素只在边界围成的区域内流动,不会跨过边界. * 这样就达到了既模糊了图片,有保留了边界的效果. * 适合于处理人脸上的麻子 * 参考网站: https://blog.csdn.net/qq_38784098/article/details/81605963 * https://blog.csdn.net/jia20003/article/details/78415384 * * @param srcImage 原图片 * @return 处理后的图片 */ public static BufferedImage anisotropyImage(BufferedImage srcImage) { BufferedImage anisotropyImage = new BufferedImage(srcImage.getWidth(), srcImage.getHeight(), srcImage.getType()); //整个公式需要先前设置的参数主要有三个,迭代次数t,根据情况设置; // 导热系数相关的k,取值越大越平滑,越不易保留边缘; // lambda同样也是取值越大越平滑。 /*double k = 15; double lambda = 0.25;*/ // 值越大,越平滑,越不易保留边缘. /*double k = 30; // 值越小,原图的细节就越清晰 double lambda = 0.25;*/ double k = 15; double lambda = 0.35; int width = srcImage.getWidth(); int height = srcImage.getHeight(); // 四邻域梯度 double rn = 0, rs = 0, re = 0, rw = 0; double gn = 0, gs = 0, ge = 0, gw = 0; double bn = 0, bs = 0, be = 0, bw = 0; // 四邻域系数 double rnc = 0, rsc = 0, rec = 0, rwc = 0; double gnc = 0, gsc = 0, gec = 0, gwc = 0; double bnc = 0, bsc = 0, bec = 0, bwc = 0; double k2 = k * k; for (int row = 1; row < width - 1; row++) { for (int col = 1; col < height - 1; col++) { // 当前点 int color = srcImage.getRGB(row, col); // 上点 int upColor = srcImage.getRGB(row - 1, col); // 下点 int downColor = srcImage.getRGB(row + 1, col); // 左点 int leftColor = srcImage.getRGB(row, col - 1); // 右点 int rightColor = srcImage.getRGB(row, col + 1); int r = (color >> 16) & 0xff; int g = (color >> 8) & 0xff; int b = color & 0xff; // 红色 rn = ((upColor >> 16) & 0xff) - ((color >> 16) & 0xff); rs = ((downColor >> 16) & 0xff) - ((color >> 16) & 0xff); re = ((leftColor >> 16) & 0xff) - ((color >> 16) & 0xff); rw = ((rightColor >> 16) & 0xff) - ((color >> 16) & 0xff); rnc = Math.exp(-rn * rn / k2); rsc = Math.exp(-rs * rs / k2); rec = Math.exp(-re * re / k2); rwc = Math.exp(-rw * rw / k2); // 绿色 gn = ((upColor >> 8) & 0xff) - ((color >> 8) & 0xff); gs = ((downColor >> 8) & 0xff) - ((color >> 8) & 0xff); ge = ((leftColor >> 8) & 0xff) - ((color >> 8) & 0xff); gw = ((rightColor >> 8) & 0xff) - ((color >> 8) & 0xff); gnc = Math.exp(-gn * gn / k2); gsc = Math.exp(-gs * gs / k2); gec = Math.exp(-ge * ge / k2); gwc = Math.exp(-gw * gw / k2); // 蓝色 bn = (upColor & 0xff) - (color & 0xff); bs = (downColor & 0xff) - (color & 0xff); be = (leftColor & 0xff) - (color & 0xff); bw = (rightColor & 0xff) - (color & 0xff); bnc = Math.exp(-bn * bn / k2); bsc = Math.exp(-bs * bs / k2); bec = Math.exp(-be * be / k2); bwc = Math.exp(-bw * bw / k2); // 最终结果 int fr = r + (int) (lambda * (rn * rnc + rs * rsc + re * rec + rw * rwc)); int fg = g + (int) (lambda * (gn * gnc + gs * gsc + ge * gec + gw * gwc)); int fb = b + (int) (lambda * (bn * bnc + bs * bsc + be * bec + bw * bwc)); int rgb = (clamp(fr) << 16) | (clamp(fg) << 8) | clamp(fb); anisotropyImage.setRGB(row, col, rgb); } } return anisotropyImage; } /** * 如果像素点的值超过了0-255的范围,予以调整 * * @param value 输入值 * @return 输出值 */ private static int clamp(int value) { return value > 255 ? 255 : (Math.max(value, 0)); } /** * S型曲线函数, 提高对比度, 反s形正好相反。 * 函数: y = 85*3/(1+100^(1.5-x/85)); * 作用: x轴为输入值,y轴为输出值 * 范围: 0 <= x <= 255, 0 <= y <= 255 * * @param x 入参 0-255 * @return 出参 0-255 * 调参网站: https://zh.numberempire.com/graphingcalculator.php?functions=85*3%2F(1%2B100%5E(1.5-x%2F85))&xmin=-126.649422&xmax=381.57014&ymin=-40.111112&ymax=298.702136&var=x * 曲线参考: https://blog.csdn.net/c80486/article/details/52499919 * 函数参考: https://blog.csdn.net/chengke1866/article/details/100695604 * 在线画函数: https://zh.numberempire.com/graphingcalculator.php?functions=85*3%2F(1%2B100%5E(1.5-x%2F85))&xmin=-126.649422&xmax=381.57014&ymin=-40.111112&ymax=298.702136&var=x * https://zh.numberempire.com/graphingcalculator.php?functions=6%2F(1%2B3%5E(3.5-x))&xmin=-1.428779&xmax=13.935977&ymin=-1.186938&ymax=9.509463&var=x * * 轻度加深函数: 255/(1+e^(5-x*2/51)) * 由f(x)=1/(1+e^-x)推导而来 */ private static int getSCurve(int x) { //double j = 255 / (1 + Math.pow(100, 1.5 - x / 85.0)); double j = 255 / (1 + Math.exp(5 - x * 2/ 51.0)); return (int) Math.round(j); } }

总结

里面的处理功能都是独立开来的,可以自由组合.另外各种参数的调整需要自己去慢慢调整了.然后还看了图像识别方面的文章,目前的图像识别主要是通过卷积神经网络进行的,需要大量的训练和参数调整,还要有个好显卡,有兴趣的可以了解一下,加入炼丹师大军,参考文章放在下面.

参考链接

基础图像处理之混合空间增强——(Java:拉普拉斯锐化、Sobel边缘检测、均值滤波、伽马变换) 为你详细解读USM锐化 掌握原理 就能让照片更锐利 图像各向异性滤波 各项异性扩散(Anisotropic diffusion)–算法简介(python)代码实现 用OpenCV实现Photoshop算法(三): 曲线调整 S型函数:Sigmoid 函数 函数图像绘制工具 CNN卷积神经网络原理讲解+图片识别应用 如何通过人工神经网络实现图像识别? CUDA——“从入门到放弃”

最新回复(0)