图像中的颜色值有多稳定/一致?

Posted

技术标签:

【中文标题】图像中的颜色值有多稳定/一致?【英文标题】:How stable/consistent are color values in images? 【发布时间】:2020-10-30 16:27:39 【问题描述】:

假设我用多个不同的库解析一个图像文件,并向库询问像素 (20, 30) 处的 RGB 值。

在什么条件下我可以期望在库和库版本之间获得一致的结果?

直观地说,我会假设使用更简单的格式,如 PPM 或(有一些限制)BMP,我可能会期望得到一致的结果,而使用 JPEG,即使在无法避免的相对简单的情况下,我也会在所有地方得到结果那个。

这让我想到了 PNG:如果我获取输入图像,将其转换为具有定义颜色深度的 PNG(例如,每通道 8 位 RGBA,所有透明度值都设置为完全不透明)并且没有颜色个人资料,我应该可以期待:

    所有通用库都以相同的方式解释生成的 PNG(在读取文件时产生相同的 RGB(A) 值数组)?

    所有通用库都能够将所述 RGB(A) 值数组转换回所有通用库将以相同方式解释的 PNG?

(显然,由于元数据、数据包的顺序等,文件字节本身可能会有所不同。我在这里只讨论像素值。此外,如果原始输入,显然初始转换步骤可能会改变图像有颜色配置文件等)

例如,如果你得到这个示例文件:

wget https://upload.wikimedia.org/wikipedia/commons/thumb/7/7a/Sun_getting_through_fog_in_the_New_Zealand_bush%2C_Bryant_Range.jpg/500px-Sun_getting_through_fog_in_the_New_Zealand_bush%2C_Bryant_Range.jpg

然后用 Python 解码:

import PIL.Image                                                                               
img = PIL.Image.open('500px-Sun_getting_through_fog_in_the_New_Zealand_bush,_Bryant_Range.jpg')
print(img.getpixel((100,100)))  # prints (73, 50, 60)

你会得到与 Golang 不同的结果:

package main

import (
    "fmt"
    "image"
    "log"
    "os"

    "image/color"
    _ "image/jpeg"
    _ "image/png"
)

func main() 
    reader, err := os.Open("500px-Sun_getting_through_fog_in_the_New_Zealand_bush,_Bryant_Range.jpg")
    if err != nil 
        log.Fatal(err)
    
    m, _, err := image.Decode(reader)
    if err != nil 
        log.Fatal(err)
    
    c := m.At(100, 100).(color.YCbCr)
    fmt.Printf("%+v\n", c)
    r, g, b := color.YCbCrToRGB(c.Y, c.Cb, c.Cr)
    fmt.Printf("%v %v %v\n", r, g, b)  // prints 72 50 59

如果您在配置文件对话框中选择“保留”,GIMP 会将像素 (100, 100) 解码为 (73, 50, 60),即与 PIL 相同。

【问题讨论】:

没有理由为什么不同的库会为任何文件中的相同像素提供不同的值。 JPEG 文件使用有损压缩,但在保存时会发生丢失,一旦保存,任何兼容的阅读器都会重新加载相同的数据。 PNG 是无损的。您可以在任何地方读取和写入它们,并且不会改变像素值。 参见例如this lengthy thread 关于可能导致 JPEG 解码差异的各种问题。虽然我知道 PNG 是无损的,但同一个 PNG 在不同的应用程序中通常会以不同的颜色显示,例如基于color profiles, gamma correction, etc.。正如我所提到的,这只是我目前的理解,所以我当然可能是错的。 添加示例。 您看到的差异可能是由于应用或不应用存储在文件中的伽马和/或颜色配置文件造成的。有一种解码 JPEG 数据的方法,没有理由不能始终如一地做到这一点。链接的线程讨论了舍入错误(不太可能导致重要差异)和实现错误。所以是的,某些软件可能会错误地执行 JPEG 标准,从而产生错误的像素值。没办法。 【参考方案1】:

PNG 的advantages 包括:

无损:无损失:过滤和压缩保留所有信息。

因此,假设 filtering 和 compression 正确(未)完成,则保证 PNG 中的颜色值是一致的。

重要的是,transparency (alpha) 存储在 PNG -预乘中;因此,如果您想要原始值,请确保您使用的每种语言中的 getpixel() 等价物不会预乘。 (如果一致熟化的值就足够了,请确保函数 all 预乘。)

注意:您在 Golang 中观察到的差异可能是由于从 RGB 转换为 YCbCr。

权威来源:https://www.w3.org/TR/PNG(包含15个匹配“loss”)

【讨论】:

“[W]所有透明度值都设置为完全不透明”,预乘并不重要。 谢谢!我知道 PNG 是无损的,但我特别担心库可能会自动应用的色彩空间转换/校正。例如,我假设带有gAMAcHRMiCCPsRGB 块的图像可能会根据解码器进行不同的解码,并且规范还说 “当传入图像具有未知伽马 ( ...),选择一个可能的默认伽玛值”. 根据我的测试,至少 convert,Golang 的默认图像库和 Python 的 PIL 似乎返回原始值并忽略 gAMA,所以在实践中我希望没问题。感谢您向我指出规范。您是否知道这种特定行为(未应用伽马校正的非显示解码器等)是否是 a) 通用的,b) 在某处指定/记录的?我只发现了一个警告,不要更改规范 encoder 部分中的值。 我已在单独的答案中添加了我的研究。我暂时不开放赏金;如果有人添加了有关图书馆如何处理此问题的权威答案,我将奖励您和他们的答案。再次感谢您! 不客气(再次)! a) 我没有。 b) 我愿意:***.com/a/63046854/13744178 ;)【参考方案2】:

JPEG

由于涉及各种转换(JPEG 在 YCbCr 中编码颜色),解码后的颜色可能会有所不同,如问题 (see also) 中的示例代码所示。

PNG

TL;DR:实际上,PNG 似乎能够跨语言/库可靠地保留准确的颜色值。

PNG 存储 RGB(或灰度)值(可能使用调色板)。虽然它确实支持例如使用 gAMA 块和各种形式的颜色管理(例如 cHRMiCCPsRGB 块)进行伽马校正,库似乎经常忽略它们。

我创建了一个包含#808080grey 的图像,添加了各种gAMA 值,并将以下所有报告(128、128、128)作为所有文件的颜色值:

ImageMagick (convert file.png -crop 1x1+5+5 -depth 8 txt:-) GIMP(打开图像并使用信息面板时,所有图像也同样亮) Python PIL/枕头 Golang 默认模式下的 Python ImageIO。

ImageIO 很有趣,因为它有一个默认为 True 的 ignoregamma option。禁用它会导致从 GIMP 保存的图像中的灰度值被解释为 186 而不是 128。这是一个足够极端的差异,已建立的库不太可能突然改变其默认行为:

Golang PNG reader source code 确认似乎没有解释额外的颜色信息块。

省略这些块可能是个好主意。 Firefox 将没有 gAMA 块的 PNG 解释为具有颜色(128、128、128)。带有原始gAMA 块的PNG 也被正确解释;一个 GIMP 嵌入 ICC 配置文件的 PNG 已经偏离 1,显示为 (127, 127, 127),并且具有编辑 gamma 值的 PNG 非常不同(如预期的那样)。

【讨论】:

为了安心,您应该从 PNG 中删除所有非关键块。 critical chunks 是 IHDR、(PLTE、) IDAT 和 IEND。

以上是关于图像中的颜色值有多稳定/一致?的主要内容,如果未能解决你的问题,请参考以下文章

获取图像最常见的颜色

ReactJS 7 - 如何根据其值有条件地仅更改表格单元格(而不是行)的背景颜色?

Python图像resize前后颜色不一致问题

shopify 产品的颜色样本/变体下拉列表

如何根据 s-s-rS 中单独字段的值有条件地格式化整行的文本颜色?

UIToolbar中的背景图像颜色丢失