【问题标题】:ImageIO reading slightly different RGB values than other methodsImageIO 读取的 RGB 值与其他方法略有不同
【发布时间】:2017-01-31 15:16:37
【问题描述】:

我发现我在使用 Java(实际上是 paint.NET)时获得的 RGB 与使用 ImageMagick、Gimp、Python 和 Octave 时不同。最后4个都同意,所以我假设是正确的。

对于这些示例,我使用的是这个测试图像:http://farm3.static.flickr.com/2811/9177301733_9836174725_o.jpg

测试像素x=4144 y=2768

               R    G    B
Java        = (125, 107, 69)
Paint.NET   = (125, 107, 69)
ImageMagick = (128, 106, 67)
Python      = (128, 106, 67)
Octave      = (128, 106, 67)
Gimp        = (128, 106, 67)

什么给了?

这是一个使用 imagemagick 的快速测试:

convert image.jpg -crop 1x1+4144+2768 -depth 8 txt:

输出:

# ImageMagick pixel enumeration: 1,1,65535,srgb
0,0: (32896,27242,17219)  #806A43  srgb(128,106,67)

这里有一些 java 和 python 代码也演示了这个问题:

import org.apache.commons.io.FileUtils;
import org.junit.Test;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;

public class ImageIOTest {
    @Test
    public void can_read_file() throws IOException, InterruptedException, URISyntaxException {
        File tempFile = File.createTempFile("image", "jpg");
        FileUtils.copyURLToFile(new URL("http://farm3.static.flickr.com/2811/9177301733_9836174725_o.jpg"), tempFile);

        BufferedImage image = ImageIO.read(tempFile);

        int javaRGB = image.getRGB(4144, 2768);
        int javaRed = (javaRGB >> 16) & 0xFF;
        int javaGreen = (javaRGB >> 8) & 0xFF;
        int javaBlue = (javaRGB >> 0) & 0xFF;
        System.out.printf("rgb: (%d, %d, %d)", javaRed, javaGreen, javaBlue);
    }
}

这里是对应的python脚本:

from PIL import Image
import sys, urllib, cStringIO

file = cStringIO.StringIO(urllib.urlopen("http://farm3.static.flickr.com/2811/9177301733_9836174725_o.jpg").read())

im = Image.open(file)
pix = im.load()
print pix[4144, 2768]

我尝试过使用this 12monkeys library,希望能解决这个问题,但没有骰子。还有其他想法如何使用java提取正确的RGB值吗?当然我不是第一个遇到这个问题的人!

更新

我尝试了getRaster().getSample(),但得到了同样的无效结果:System.out.println(raster.getSample(4144, 2768, 0)+","+ raster.getSample(4144, 2768, 1)+","+ raster.getSample(4144, 2768, 2)); 输出:125,107,69

更多信息

下面是一些输出,显示了图像左上角的前 9 个(3x3 平方)像素由三种不同工具解码的 RGB 值。如您所见,Python 和 ImageMagick 是一致的。 Java 有时会匹配。我在java不同意的地方放了一个X...:

Tool          [x, y] = (R , G , B )
ImageIO     : [0, 0] = (86, 90, 93)
Python      : [0, 0] = (86, 90, 93)
ImageMagick : [0, 0] = (86, 90, 93)

ImageIO     : [1, 0] = (86, 90, 93)
Python      : [1, 0] = (86, 90, 93)
ImageMagick : [1, 0] = (86, 90, 93)

ImageIO     : [2, 0] = (90, 91, 95) X
Python      : [2, 0] = (88, 92, 95)
ImageMagick : [2, 0] = (88, 92, 95)

ImageIO     : [0, 1] = (85, 93, 95)
Python      : [0, 1] = (85, 93, 95)
ImageMagick : [0, 1] = (85, 93, 95)

ImageIO     : [1, 1] = (85, 93, 95) X
Python      : [1, 1] = (87, 92, 95)
ImageMagick : [1, 1] = (87, 92, 95)

ImageIO     : [2, 1] = (87, 92, 95)
Python      : [2, 1] = (87, 92, 95)
ImageMagick : [2, 1] = (87, 92, 95)

ImageIO     : [0, 2] = (83, 93, 94)
Python      : [0, 2] = (83, 93, 94)
ImageMagick : [0, 2] = (83, 93, 94)

ImageIO     : [1, 2] = (83, 93, 94) X
Python      : [1, 2] = (84, 92, 94)
ImageMagick : [1, 2] = (84, 92, 94)

ImageIO     : [2, 2] = (83, 91, 93)
Python      : [2, 2] = (83, 91, 93)
ImageMagick : [2, 2] = (83, 91, 93)

为什么 Java 会为某些像素提供不同的值?或者,是否有另一种(快速)使用本机 Java 代码生成正确值的方法?

2016 年 9 月 26 日更新:

我提交了演示此问题的代码并将其推送到 github (imageio-test),以便我可以轻松地在不同的机器上对其进行测试。事实证明,Java 在 OSX 和 Ubuntu Linux 上是一致的,但不一致的是 Python、ImageMagick 和 Octave。换句话说,在 Linux 机器上,所有工具都相互一致,因此,我现在认为 java 一直都是正确的,而在 OSX 上给出错误结果的是其他工具!我仍然不明白为什么,我也没有任何具体的证据证明哪些值是正确的,但我在某个地方......

【问题讨论】:

  • RGB 不是颜色空间,而是颜色模型(参见color model vs color space)。我知道 Java 总是使用 sRGB 颜色空间,但不确定其他颜色空间。如果色彩空间不同,您可能有不同的 RGB 值,但屏幕上的颜色仍然相同。
  • @haraldK,好地方。据我所知,Python 正在使用 sRGB(但无法证明)。 GIMP 正在使用 sRGB。我使用 imagemagick 添加了一个单线,它也给出了相同的结果,并且还报告说它正在使用 sRGB。
  • 我已经用 Paint.NET 打开了它。像素值为 125、107、69(与 Java 相同)。
  • 感谢您的帮助。我已经将测试代码推送到github.com/mattburns/imageio-test,如您所见,Linux 和 OSX 的结果不同,这表明 Java 实际上是正确的!蟒蛇公司给了我不同的结果!还是不明白为什么。
  • 主要区别在于编解码器 - ImageIO 调用以获取图像数据的本机代码。 Python、GIMP 等使用 libjpeg.9.dylib(在 Mac OSX 上),而 JDK 将使用它们随附的一个(例如:Library/Java/JavaVirtualMachines/jdk1.8.0_102.jdk/Contents/Home/jre/lib/ libjpeg.dylib)。不幸的是,您无法交换和更改它们。潜在地,您可以使用您想要的版本重新构建 Pillow(用于 Python)......虽然没有测试过。

标签: java jpeg rgb javax.imageio


【解决方案1】:

here:WARNING: Color space tagged as sRGB, without an embedded color profile. Windows and Mac browsers and apps treat the colors randomly.

编辑:根据版本的舍入误差和实现差异;他们根本不是这张图片的情况。 Mac 发生了一些神奇的事情,使颜色匹配曲线上的蓝色和绿色更亮。校正色彩空间,颜色匹配会得到相同的结果。我对 Andy Fedoroff 的回答投了赞成票,但我也注意到实际上没有人给你一个解决方案......你得出的结论是 Java 是正确的。随它去吧。 Libjpeg 已经很久没有改变了。它很稳定,可以在许多平台和环境中可靠地再现颜色。对陈旧的标准 jpeg 解码没有进行重大(以任何方式)更改。

编辑 2:尝试创建一个示例,根据您的项目生成与 Mac 配置文件相同的值。需要来自Library/ColorSync/Profiles 的 Mac 出厂 ICC 配置文件。

这就是我所在的位置。这是一个应用了 sRGB ICC v4 配置文件的示例。这在技术上是在 sRGB 上应用 sRGB,但它是在解释这个概念。

private ICC_Profile cp = ICC_Profile.getInstance("src/test/resources/sRGB_ICC_v4_Appearance.icc");
private ICC_ColorSpace cs = new ICC_ColorSpace(cp);

private int[] getRGBUsingImageIO2(File file, int x, int y) throws IOException {
    BufferedImage image = ImageIO.read(file);
    ColorConvertOp cco = new ColorConvertOp( cs, null );
    BufferedImage result = cco.filter( image, null );
    int javaRGB = result.getRGB(x, y);
    int javaRed = (javaRGB >> 16) & 0xFF;
    int javaGreen = (javaRGB >> 8) & 0xFF;
    int javaBlue = (javaRGB >> 0) & 0xFF;

    return new int[]{javaRed, javaGreen, javaBlue};
}
图像 IO 1:[0, 0] = [145, 146, 164] 图像 IO 2:[0, 0] = [145, 147, 165] 图像 IO 1:[1, 0] = [137, 138, 156] 图像 IO 2:[1, 0] = [137, 139, 157] 图像 IO 1:[2, 0] = [148, 147, 161] 图像 IO 2:[2, 0] = [148, 148, 162] 图像 IO 1:[0, 1] = [150, 153, 168] 图像 IO 2:[0, 1] = [150, 154, 169] 图像 IO 1:[1, 1] = [138, 141, 156] 图像 IO 2:[1, 1] = [138, 142, 157] 图像 IO 1:[2, 1] = [145, 147, 159] 图像 IO 2:[2, 1] = [145, 148, 160] 图像 IO 1:[0, 2] = [154, 160, 172] 图像 IO 2:[0, 2] = [154, 161, 173] 图像 IO 1:[1, 2] = [146, 152, 164] 图像 IO 2:[1, 2] = [146, 153, 165] 图像 IO 1:[2, 2] = [144, 148, 157] 图像 IO 2:[2, 2] = [144, 149, 158]

您能否将您的颜色配置文件提交到您的 imageio-test 存储库?

【讨论】:

  • 我将尝试添加一个 java 示例,在非 mac 平台上重现 mac 结果(将在 mac 平台上同步)...
  • 呸,抱歉,我今天不在笔记本电脑旁,所以无法在赏金结束前上传颜色配置文件。不过,我对这个假设真的很感兴趣,并且很想看看在非 Mac 机器上使用 Mac 颜色配置文件的结果。网上哪里有副本?如果有帮助,我正在使用最新的视网膜 macbook pro 和 osx...
  • 我确定有一个随 Mac OS X 映像一起分发的默认配置文件...我认为每个模型都可能有一些出厂调整,但我不确定;可能是从监视器的 EDID 或其他东西中提取的。这就是我要做的;将显示器与其他硬件分开校准,将配置文件插入 EDID,然后让驱动程序获取、存储和加载它……但我不知道 Mac 是如何做到的。
  • 如果您可以为 3 个 RGB 通道中的每一个创建一个只有 0 到 255 的 jpeg 并让它在 Mac 上生成工件,我们可以绘制它们并计算出每个通道的伽马曲线,这将与您的 ICC 配置文件完全匹配。
  • @mattburns 更新示例。提交到 github,将提出拉取请求。
【解决方案2】:

颜色一致性和 ICC 配置文件

上传图片时,Java 不尊重颜色配置文件。此外,不同的操作系统对 RGB 颜色的处理方式也不同。

这是 Oracle 写的关于 import java.awt.color 的内容:

通常,颜色或 ColorModel 将与 ICC 配置文件相关联,该 ICC 配置文件可以是输入、显示或输出配置文件。还有其他类型的 ICC 配置文件,例如抽象配置文件、设备链接配置文件和命名颜色配置文件,它们不包含适合表示颜色、图像或设备的颜色空间的信息。尝试从不适当的 ICC 配置文件创建 ICC_ColorSpace 对象是错误的。

ICC 配置文件表示从配置文件(例如显示器)的颜色空间到配置文件连接空间 (PCS) 的转换。用于标记图像或颜色的相关配置文件有一个 PCS,它是 ICC 配置文件格式规范中定义的与设备无关的空间之一(一个 CIEXYZ 空间和两个 CIELab 空间)。大多数感兴趣的配置文件要么具有可逆变换,要么明确指定双向变换。如果 ICC_ColorSpace 对象以需要从 PCS 转换到配置文件的原生空间的方式使用,并且没有足够的数据来正确执行转换,ICC_ColorSpace 对象将以指定类型的颜色空间(例如 TYPE_RGB、TYPE_CMYK 等)生成输出.),但输出数据的具体颜色值将是未定义的。

ICC_ColorSpace 类的细节对于简单的小程序并不重要,这些小程序在默认颜色空间中绘制或使用已知颜色空间操作和显示导入的图像。此类小程序最多需要通过ColorSpace.getInstance() 获取默认颜色空间之一。 (摘自 docs.oracle.com) https://docs.oracle.com/javase/7/docs/api/java/awt/color/ICC_ColorSpace.html

Java 中的色彩空间转换

色彩空间转换由图像读取和写入的目标类型控制。读取光栅时,不执行颜色空间转换,并且忽略任何目标类型。如果在这种情况下指定了目标类型,则会向任何侦听器发送警告。编写栅格时,将使用任何目标类型来解释波段。这可能会导致写入 JFIF 或 Adob​​e 标头,或者将不同的组件 ID 写入帧和扫描标头。如果元数据对象中的值与目标类型不匹配,则使用目标类型并向任何侦听器发送警告。 (摘自 docs.oracle.com) https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html

有用的链接

查看有关 RGB 转换的信息。 Rolf W. Rasmussen 的标准化浮点/整数颜色分量存在一些问题:

http://opensource.apple.com/source/gcc3/gcc3-1041/libjava/java/awt/image/ColorModel.java

阅读The Sad Story of PNG Gamma “Correction” (问题是:JPEG 和 TIFF 患有同样的“病”)。

https://hsivonen.fi/png-gamma/

看看 S.O.邮政。有可能的解决方案:

In Java converting an image to sRGB makes the image too bright

如果尝试后颜色仍然不一致,请尝试将图像转换为 sRGB 配置文件,但不要嵌入它们:

https://imageoptim.com/color-profiles.html

另外,我希望 Kenny Hunt 的书对您有所帮助

...下面的代码(发布于 www.physicsforums.com)让您可以看到各种 RGB 的样子:

import java.awt.*; 
import javax.swing.*; 

public class RGB { 
    public static void main(String[] args) { 
        JFrame frame = new JFrame("RGB"); 
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        RGBpanel panel = new RGBpanel(); 
        frame.getContentPane().add(panel); 
        frame.pack(); 
        frame.setVisible(true); 
    } 
} 

class RGBpanel extends JPanel { 
    public RGBpanel() { 
        setPreferredSize(new Dimension(300,300)); 
        int red = Integer.parseInt(JOptionPane.showInputDialog("Enter red value")); 
        int green = Integer.parseInt(JOptionPane.showInputDialog("Enter green value")); 
        int blue = Integer.parseInt(JOptionPane.showInputDialog("Enter blue value")); 
        Color colour = new Color(red,green,blue); 
        setBackground(colour); 
    } 
}

回顾

颜色不一致的问题源于颜色配置文件。尝试为所有图像分配统一的颜色配置文件,无论是手动(在 Photoshop 中)还是以编程方式(在 Java 中)。

【讨论】:

  • 投了赞成票。我担心其他答案是通过“指责”实现差异和编解码器错误来回避问题。 libjpeg代码在图像再现测试中长期稳定正确。它只是发生在 Mac 上的色彩空间和色彩匹配。
  • “上传图像时 Java 不尊重颜色配置文件” 不正确(或充其量是误导)。它也不是以下链接中关于 PNG gamma 校正的引用... 大多数 Java ImageIO 插件确实尊重 ICC 颜色配置文件,并将图像数据正确转换为 sRGB,这是“原生”Java 颜色空间。
  • @haraldK 您能否提供有关 Java ImageIO 插件信息的链接(什么是正确将图像数据转换为 sRGB)?
  • 我把误导性的句子放到了其他地方。谢谢。
  • 我确实写过它不是颜色配置文件。在发布我的答案之前,我访问了一堆 .icc 文件并使用ColorConvertOp 应用了它们。虽然他们确实更改了值,但他们没有将它们更改为正确的值。但是,我删除了它,因为实际上我认为 @haraldK 是 CMM 与 Little CMS 的最佳选择。
【解决方案3】:

实际上,我想扭转这个问题,并说我很惊讶这么多不同的平台和工具实际上产生了相同的值。 :-)

JPEG 有损

首先,JPEG 是一种有损图像压缩方法。这意味着复制原始的确切数据是不可能的。或者,如果您愿意,几个不同的像素值可能在某种程度上都是“正确的”。

并非所有 JPEG 软件都从同一源文件生成完全相同的值的技术原因通常是值的舍入/钳位不同,或浮点运算的整数近似值以获得更好的性能。其他变化可能源于用于恢复二次采样色度值的不同插值算法,例如(即,更平滑的图像可能看起来更悦目,但不一定更正确)。

另一个excellent answer 对类似问题指出“JPEG 标准不要求解码器实现产生逐位相同的输出图像”,并引用the Wikipedia JPEG entry

[...]de编码的精度要求[...];参考算法的输出不得超过:

  • 每个像素分量最多相差一位
  • 每个 8×8 像素块的均方误差低
  • 每个 8×8 像素块的平均误差非常低
  • 整个图像的均方误差非常低
  • 整个图像的平均误差极低

(请注意,上面只讨论了参考实现)。

但是,幸运的是,您的所有软件/工具似乎最终都使用(某些版本的)libjpeg。因为它们都使用 libjpeg,所以您看到的差异来源很可能与 JPEG 解码无关。

色彩空间

即使您的所有软件都将 JPEG 文件转换为使用 RGB 值的表示,它们用于此表示的颜色空间也可能存在差异。

似乎您使用的所有软件实际上都在sRGB color space 中显示RGB 值。这可能是主流计算中使用的最标准和最广泛使用的色彩空间,所以这并不奇怪。由于色彩空间始终是 sRGB,因此您看到的差异来源很可能不是色彩空间。

ICC 配置文件和颜色匹配

颜色差异的下一个可能来源是颜色匹配(由颜色匹配模块、CMM 或颜色管理系统、CMS 完成)不是 100% 精确的科学(例如参见 this document on black point compensation 或阅读一些更多技术帖子来自Little CMS blog)。

Mac OS X 上运行的软件很可能使用 Apple 的 CMM,而 Java 始终使用 Little CMS(来自 OpenJDK 7 或 Oracle JDK/JRE 8),Linux 平台上的大多数软件也可能使用开源Little CMS(根据 Little CMS 主页,“您可以在大多数 Linux 发行版中找到 Little CMS”)。 Windows 上的软件也可能会略有偏差(我无法验证 Paint.Net 是否使用 Little CMS、Windows 的内置 CMM 或其他东西)。当然,使用 Adob​​e 的 CMM(即 Photoshop)也可能会出现偏差。

同样,幸运的是,您测试的许多软件都使用相同的 CMM 或 CMS 引擎 Little CMS,因此您将再次获得许多相同的结果。但您测试的某些软件似乎使用了不同的 CMM,并且可能是轻微色差的来源。

总结

您看到的不同像素值都是“正确的”。 差异源于软件中算法的不同实现或近似值,但这并不一定意味着一个值是正确的而其他值是正确的错了。

PS:如果您需要在多个平台上重现完全相同的值,请在所有平台上使用相同的工具堆栈/相同的算法。

【讨论】:

  • 很好的答案。我收回我的并赞成你的,因为我觉得你的更简洁,更权威。我建议更清楚地说明您是 TwelveMonkeys 的作者并且知道您在说什么。 如果你从我的帖子中窃取了我的 TL;DR 解决方案,我也会感到荣幸 :-)
  • 如果我正在拍摄位图,并使用不同的 jpeg 编码器对其进行压缩,然后再次对其进行解码,我希望它们会有所不同。然而,我只是惊讶于相同的源 jpeg 文件可以以不同的方式解码。在视频编解码器世界中,编解码器标准只定义了如何解码文件(视频编码的方法可以不同,只要始终以相同的方式解码就没有关系)。我只是假设JPEG也是如此。看到不同的 jpeg 库将同一个 jpeg 文件解码为不同的值(尽管很小),我并不满意。哼哼。
  • 更多谷歌搜索提出了this excellent StackOverflow answer 类似的问题。请特别参阅 JPEG 标准中对输出精度要求的参考。如果您愿意,我可以在答案的 JPEG 部分中添加对该答案的引用。
  • 就像您在 Ubuntu 测试中证明的那样,它们的解码方式并没有不同。对于a long time 的图像再现,libjpeg 代码已经稳定且正确。色彩空间和配色是这里唯一的参与者。
  • @TylerY86 我并不是说 libjpeg 不稳定或存在漏洞。我只是说 JPEG 解码器不需要从相同的输入再现完全相同的值(如 ISO JPEG 规范中所述,并在 Wikipedia 文章中重复)。另请注意(如其他答案所述)libjpeg 确实具有会影响结果的选项。在大多数情况下,色彩空间被证明是相同的。但我同意 CMM/CMS 最有可能是 差异 的来源(重要:not 错误)。
【解决方案4】:

根据我的评论,您用于检索像素颜色值的各种应用程序/库之间的主要区别在于它们都使用不同版本的 libjpeg - 至少在 Mac OSX 上是这样。

当您在特定版本的 Ubuntu 上查看您的 Github 项目时,您会发现所有值的报告都相同。在这些情况下,python ImageMagick 和 Java JDK/JRE 使用相同的 libjpeg 实现。

在 Mac 上,如果您通过homebrew 安装了jpeg,或者通过pip 安装了Pillow,那么您会注意到它们使用的是libjpeg v9 (libjpeg.9.dylib),而Java 7 和8 JDK附带了他们自己的 libjpeg,它们完全不同。

Octave lists its jpeg dependencieslibjpeg8-dev

GIMP、Inkscape、Scribus 等也与他们自己的捆绑在一起。就我而言,GIMP 捆绑了与 python 和 ImageMagick 相同的版本,这将解释相似的值(即:/Applications/GIMP.app/Contents/Resources/lib/libjpeg.9.dylib

如果您想保证跨应用的值相同,您可以选择:

  1. 坚持使用相同的平台/堆栈(如 @haraldk 建议的那样) - 坚持在 Linux 平台上开发/运行您的东西,保证所有平台都使用相同的 libjpeg 版本
  2. 将您的 Java 代码绑定到其他应用正在使用的相同版本 - 即:加载 libjpeg.9.dylib 并在您的 Java 应用中使用它。不过,我不能 100% 确定你会如何做到这一点。
  3. 重新编译您的 JDK 以使用正确的版本 - this answer 引用的一个选项是使用 openjdk 并针对所需版本的 libjpeg 进行编译,这听起来更容易实现。

我承认选项 2 和 3 确实比选项 1 更难!

注意:
我绝对赞成@haraldk 的回答,因为他的结论几乎相同。

我也尝试过使用不同的 icc 配置文件,它们给出的答案完全不同。所以值得警惕。

我只是想添加一个更强调 libjpeg 实现的答案,因为我相信这是在您的特定实例中吸引您的原因。

更新

实际上,@haraldk 的回答中还提到了另一个主要区别,即 CMM 和 Little CMS 之间的区别。正如他所说:Java 使用 Little CMS,which is also used by Ubuntu

我实际上认为这更有可能是这里的答案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-07-10
    • 1970-01-01
    • 2011-12-15
    • 2014-08-06
    • 1970-01-01
    • 1970-01-01
    • 2017-10-05
    相关资源
    最近更新 更多