将图像缩小为JAVA中给定颜色的固定数量

问题描述 投票:2回答:2

我想以特殊方式编辑图像:

我给出了40种颜色的清单。现在我有一张可能的颜色的图像。我想将图像减少到只有12种颜色(在40种可能的颜色中),并获得最佳效果。是否有一个好的档案库可以存档(JAVA)。

java image editing
2个回答
2
投票

在此答案中,我做出以下假设:

  • 您的列表包含40 不同颜色。
  • 每种颜色都包含24位信息(红色为8位,绿色为8位,蓝色为8位)。
  • 12-颜色解决方案应该是人类感知颜色的最佳组合。

由于您的目标是确定(提供的12中的)40种颜色最接近图像中的[[all颜色,因此可以使用以下算法:

    遍历所有输入颜色的40
  1. 对于每种颜色,迭代输入图像中的所有像素。
  2. 对于当前像素,计算其颜色与我们当前颜色之间的差异。
    • 这可能是算法中最困难的部分,因为我们需要一个函数来返回两种颜色之间的
    • 差异。
    • 我相信实现此目的的最有效方法是先将RGB颜色转换为XYZ颜色空间,然后将XYZ颜色转换为CIELAB颜色空间。
  3. 然后我们可以使用this formula计算两种CIELAB颜色之间的差异。
  • enter image description here

      将颜色映射到相应颜色的差异之和。
  • 按值排序Map<Integer, Double>(升序);解决方法是先按12键。
  • 这是实现此目的的Java代码:

    import javax.imageio.ImageIO; import java.awt.Color; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Paths; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; public class ColorDifference { public static void main(String[] args) { ConcurrentMap<Color, Double> colorDifferenceMap = new ConcurrentHashMap<>(); ExecutorService executorService = Executors.newFixedThreadPool( Runtime.getRuntime().availableProcessors()); BufferedImage inputImage; try { // Read in the input image inputImage = ImageIO.read(Paths.get("mostly_red.png").toFile()); } catch (IOException e) { throw new UncheckedIOException("Unable to read input image!", e); } // Generate the 40 colors Stream<Color> mostlyRedColors = generateInfiniteColors() .filter(color -> color.getRed() >= 200 && color.getGreen() <= 25 && color.getBlue() <= 25) .distinct().limit(12); Stream<Color> mostlyGreenAndBlueColors = generateInfiniteColors() .filter(color -> color.getRed() <= 25 && color.getGreen() >= 200 && color.getBlue() >= 200) .distinct().limit(28); // 'Stream.concat(...)' can be replaced with a 'List<Color>' Stream.concat(mostlyRedColors, mostlyGreenAndBlueColors).forEach(color -> { executorService.execute(() -> { CIELab cieLabColor = CIELab.from(color); double sum = 0d; for (int y = 0; y < inputImage.getHeight(); y++) { for (int x = 0; x < inputImage.getWidth(); x++) { Color pixelColor = new Color(inputImage.getRGB(x, y)); CIELab pixelCIELabColor = CIELab.from(pixelColor); sum += cieLabColor.difference(pixelCIELabColor); } } colorDifferenceMap.put(color, sum); }); }); executorService.shutdown(); try { executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); } catch (InterruptedException e) { e.printStackTrace(); } // The 12 solution colors are held in this list List<Color> colorSolutions = colorDifferenceMap.entrySet() .stream() .sorted(Map.Entry.comparingByValue()) .limit(12) .map(Map.Entry::getKey) .collect(Collectors.toList()); colorSolutions.forEach(System.out::println); } // Inspiration taken from https://stackoverflow.com/a/20032024/7294647 private static Stream<Color> generateInfiniteColors() { return Stream.generate(() -> new Color(ThreadLocalRandom.current().nextInt(0x1000000))); } static class CIELab { private final double L, a, b; public CIELab(double L, double a, double b) { this.L = L; this.a = a; this.b = b; } public double difference(CIELab cieLab) { return Math.sqrt(Math.pow(cieLab.L - L, 2) + Math.pow(cieLab.a - a, 2) + Math.pow(cieLab.b - b, 2)); } public static CIELab from(Color color) { int sR = color.getRed(); int sG = color.getGreen(); int sB = color.getBlue(); // Convert Standard-RGB to XYZ (http://www.easyrgb.com/en/math.php) double var_R = ( sR / 255d ); double var_G = ( sG / 255d ); double var_B = ( sB / 255d ); if ( var_R > 0.04045 ) var_R = Math.pow( ( var_R + 0.055 ) / 1.055, 2.4 ); else var_R = var_R / 12.92; if ( var_G > 0.04045 ) var_G = Math.pow( ( var_G + 0.055 ) / 1.055, 2.4 ); else var_G = var_G / 12.92; if ( var_B > 0.04045 ) var_B = Math.pow( ( var_B + 0.055 ) / 1.055, 2.4 ); else var_B = var_B / 12.92; var_R = var_R * 100; var_G = var_G * 100; var_B = var_B * 100; double X = var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805; double Y = var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722; double Z = var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505; // Convert XYZ to CIELAB (http://www.easyrgb.com/en/math.php double var_X = X / 96.422; double var_Y = Y / 100.000; double var_Z = Z / 82.521; if ( var_X > 0.008856 ) var_X = Math.pow( var_X, 1D / 3D ); else var_X = ( 7.787 * var_X ) + ( 16D / 116 ); if ( var_Y > 0.008856 ) var_Y = Math.pow( var_Y, 1D / 3D ); else var_Y = ( 7.787 * var_Y ) + ( 16D / 116 ); if ( var_Z > 0.008856 ) var_Z = Math.pow( var_Z, 1D / 3D ); else var_Z = ( 7.787 * var_Z ) + ( 16D / 116 ); double L = ( 116 * var_Y ) - 16; double a = 500 * ( var_X - var_Y ); double b = 200 * ( var_Y - var_Z ); return new CIELab(L, a, b); } } }


    为了测试代码,我生成了以下256x256图片(大部分填充有红色像素):

    enter image description here

    您可以在代码中看到,对于我的40初始颜色,我选择的12颜色主要是红色,其余28的颜色主要是绿色/蓝色。

    正如预期的那样,解决方案是大多数都是红色的12颜色:

    java.awt.Color[r=200,g=20,b=7] java.awt.Color[r=216,g=25,b=15] java.awt.Color[r=214,g=18,b=15] java.awt.Color[r=223,g=25,b=15] java.awt.Color[r=207,g=19,b=3] java.awt.Color[r=215,g=21,b=8] java.awt.Color[r=235,g=10,b=25] java.awt.Color[r=253,g=15,b=21] java.awt.Color[r=235,g=6,b=0] java.awt.Color[r=239,g=15,b=1] java.awt.Color[r=243,g=13,b=2] java.awt.Color[r=247,g=15,b=3]


    上面的代码可以在Java 8+上运行,并且在不到一秒钟的时间内完成了上面发布的图像。

  • 0
    投票
    解决方案不像给出的那样简单(请参见雅各的答案)。为了理解原因,我们假设图像中有10个像素:9个红色和1个绿色。假设我们在调色板中有39个微红色和1个绿色。最佳答案是11带红色和1绿色(完全匹配)。提出的算法将选择12个带红色。

    要获得真正的最佳结果,您需要遍历所有可能的12色调色板以找到一个使总误差(色距之和或平方距离之和最小)的调色板,因为通常的做法是最小化平方之和。错误)。

    [40个调色板中的12个调色板的数量是:

    C(40,12)= 40! /(12!* 28!)= 5,586,853,480

    很多,因此需要一些处理时间。当然,这取决于图像的大小。

    © www.soinside.com 2019 - 2024. All rights reserved.