【问题标题】:Any faster algorithm to transform from RGB to CMYK任何更快的从 RGB 转换为 CMYK 的算法
【发布时间】:2014-07-22 20:39:22
【问题描述】:

这就是我使用更“正确”的方式(即使用 ICC 颜色配置文件)从 RGB 转换为 CMYK 的方法。

// Convert RGB to CMYK with level shift (minus 128)
private void RGB2CMYK(int[] rgb, float[][] C, float[][] M, float[][] Y, float[][] K, int imageWidth, int imageHeight) throws Exception {
    ColorSpace instance = new ICC_ColorSpace(ICC_Profile.getInstance(JPEGWriter.class.getResourceAsStream(pathToCMYKProfile)));
    float red, green, blue, cmyk[];
    //
    for(int i = 0, index = 0; i < imageHeight; i++) {
        for(int j = 0; j < imageWidth; j++, index++) {
            red = ((rgb[index] >> 16) & 0xff)/255.0f;
            green = ((rgb[index] >> 8) & 0xff)/255.0f;
            blue = (rgb[index] & 0xff)/255.0f;
            cmyk = instance.fromRGB(new float[] {red, green, blue});
            C[i][j] = cmyk[0]*255.0f - 128.0f;
            M[i][j] = cmyk[1]*255.0f - 128.0f;
            Y[i][j] = cmyk[2]*255.0f - 128.0f;
            K[i][j] = cmyk[3]*255.0f - 128.0f;
        }
    }
}

我的问题是:给定大图像,它的速度非常慢。在一种情况下,我花了大约 104 秒而不是通常的 2 秒将数据写入 JPEG 图像。事实证明,上述转换是最耗时的部分。

我想知道是否有任何方法可以使它更快。 注意:我不会使用可以从网上找到的廉价转换算法。

更新:根据 haraldK 的建议,修改后的版本如下:

private void RGB2CMYK(int[] rgb, float[][] C, float[][] M, float[][] Y, float[][] K, int imageWidth, int imageHeight) throws Exception {
    if(cmykColorSpace == null)
        cmykColorSpace = new ICC_ColorSpace(ICC_Profile.getInstance(JPEGWriter.class.getResourceAsStream(pathToCMYKProfile)));
    DataBuffer db = new DataBufferInt(rgb, rgb.length);
    WritableRaster raster = Raster.createPackedRaster(db, imageWidth, imageHeight, imageWidth,  new int[] {0x00ff0000, 0x0000ff00, 0x000000ff}, null);
    ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);

    ColorConvertOp cco = new ColorConvertOp(sRGB, cmykColorSpace, null);

    WritableRaster cmykRaster = cco.filter(raster, null);
    byte[] o = (byte[])cmykRaster.getDataElements(0, 0, imageWidth, imageHeight, null);

    for(int i = 0, index = 0; i < imageHeight; i++) {
        for(int j = 0; j < imageWidth; j++) {
            C[i][j] = (o[index++]&0xff) - 128.0f;
            M[i][j] = (o[index++]&0xff) - 128.0f;
            Y[i][j] = (o[index++]&0xff) - 128.0f;
            K[i][j] = (o[index++]&0xff) - 128.0f;
        }
    }
}

更新:我还发现在 BufferedImage 而不是 Raster 上进行过滤要快得多。看到这个帖子:ARGB int array to CMYKA byte array convertion

【问题讨论】:

  • 微小的优化,但如果你从imageHeight - 数到0,你的循环会更快。i&gt;= 0i &lt; imageHeight 更快检查。
  • CMYK 的结构方式对于缓存来说并不理想,但也不可怕。
  • 这与 fork-join 池很好地并行。
  • 这里真的需要float 精度吗?大多数基于 CMYK 的文件格式每个通道使用 8 位,所以它可能是矫枉过正...

标签: java image transform rgb cmyk


【解决方案1】:

您可能应该使用ColorConvertOp。它在大多数平台上使用优化的本机代码,并支持 ICC 配置文件转换。

不确定使用基于Rasters 的float 时它的工作速度有多快,但它确实可以。

类似:

ICC_Profile cmyk = ...;
ICC_Profile sRGB = ...;

ColorConvertOp cco = new ColorConvertOp(sRGB, cmyk);

Raster rgbRaster = ...;
WritableRaster cmykRaster = cco.filter(rgbRaster, null); 

// Or alternatively, if you have a BufferedImage input
BufferedImage rgbImage = ...;
BufferedImage cmykImage = cco.filter(rgbImage, null);

【讨论】:

  • 从我的 int 数组创建栅格并应用 ColorConvertOp。编码时间下降到不到 4 秒。由于我没有在图像中嵌入任何 ICC 配置文件,因此生成的图像颜色有点偏。非常感谢。
  • 酷! 4s 仍然是颜色转换的一些时间,但至少比 104s 好......转换时间将取决于配置文件的复杂性。是的,您需要嵌入您转换到的相同 ICC 配置文件,但这很简单。 ICC_Profile 有一个 getData() 方法,您只需将该数组转储到 APP2/ICC_PROFILE 块(+ 索引/计数并确保遵守 JFIF 段大小)。
  • 我已经完成了。只需要写索引和总段。我承认这让解码器更快乐。但是我认为如果它读取所有的APP2,它可以解决它。非常感谢!
【解决方案2】:

如果你可以负担内存消耗,你可以创建一个查找表:

private void RGB2CMYK(int[] rgb, float[][] C, float[][] M, float[][] Y, float[][] K, int imageWidth, int imageHeight) throws Exception {
    ColorSpace cs = new ICC_ColorSpace(...);
    int[] lookup = createRGB2CMYKLookup(cs);
    for(int y = 0, index = 0; y < imageHeight; y++) {
        for(int x = 0; x < imageWidth; x++, index++) {
            int cmyk = lookup[rgb[index]];
            C[y][x] = ((cmyk >> 24) & 255) - 128F;
            M[y][x] = ((cmyk >> 16) & 255) - 128F;
            Y[y][x] = ((cmyk >>  8) & 255) - 128F;
            K[y][x] = ((cmyk      ) & 255) - 128F;
        }
    }
}

static int[] createRGB2CMYKLookup(ColorSpace cs) {
    int[] lookup = new int[16 << 20]; // eats 16m times 4 bytes = 64mb
    float[] frgb = new float[3];
    float fcmyk[];
    for (int rgb=0; rgb<lookup.length; ++rgb) {
        frgb[0] = ((rgb >> 16) & 255) / 255F;
        frgb[1] = ((rgb >>  8) & 255) / 255F;
        frgb[2] = ((rgb      ) & 255) / 255F;
        fcmyk = cs.fromRGB(frgb);
        int c = (int) (fcmyk[0] * 255F);
        int m = (int) (fcmyk[1] * 255F);
        int y = (int) (fcmyk[2] * 255F);
        int k = (int) (fcmyk[3] * 255F);
        int icmyk = (c << 24) | (m << 16) | (y << 8) | k; 
    }
    return lookup;
}

现在这实际上可能恶化小图像的性能。仅当您可以为多个图像重新使用查找表时,它才会有所帮助,但正如您的示例所示,您实际上一遍又一遍地使用相同的 ICC 配置文件。因此,您可以缓存查找表并只支付一次初始化成本:

 static int[] lookup;
 static {
    ColorSpace cs = new ICC_ColorSpace(...);
    lookup = createRGB2CMYKLookup(cs);
 }

// convert always using (the same) lookup table
private void RGB2CMYK(int[] rgb, float[][] C, float[][] M, float[][] Y, float[][] K, int imageWidth, int imageHeight) throws Exception {
    for(int y = 0, index = 0; y < imageHeight; y++) {
        for(int x = 0; x < imageWidth; x++, index++) {
            int cmyk = lookup[rgb[index]];
            C[y][x] = ((cmyk >> 24) & 255) - 128F;
            M[y][x] = ((cmyk >> 16) & 255) - 128F;
            Y[y][x] = ((cmyk >>  8) & 255) - 128F;
            K[y][x] = ((cmyk      ) & 255) - 128F;
        }
    }
}

【讨论】:

  • 除了内存问题之外,如果图像都以 CMYK 色彩空间写入,那么您是对的。事实是,大部分时间它可能是 YCbCr
【解决方案3】:

您应该摆脱最内层循环中的内存分配。 new 是一项非常昂贵的操作。此外,它还可能会启动垃圾收集器,这会增加更多的惩罚。

【讨论】:

  • 除非逃逸分析可以将其放入堆栈内存中,但可以。
  • @dragon66 你只是重用了数组。
  • @DavidEhrmann:在我的测试中,根本没有改进:少了 1 秒,我不会考虑改进。我认为最大的问题是对 fromRGB 的方法调用次数过多
  • @dragon66 当你对其进行分析时,每个人的时间百分比是多少?
猜你喜欢
  • 2018-07-04
  • 2016-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-26
  • 1970-01-01
  • 2020-07-03
  • 2020-05-31
相关资源
最近更新 更多