图像中的颜色值有多稳定/一致?
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 是无损的,但我特别担心库可能会自动应用的色彩空间转换/校正。例如,我假设带有gAMA
、cHRM
、iCCP
或sRGB
块的图像可能会根据解码器进行不同的解码,并且规范还说 “当传入图像具有未知伽马 ( ...),选择一个可能的默认伽玛值”.
根据我的测试,至少 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
块和各种形式的颜色管理(例如 cHRM
、iCCP
、sRGB
块)进行伽马校正,库似乎经常忽略它们。
我创建了一个包含#808080
grey 的图像,添加了各种gAMA
值,并将以下所有报告(128、128、128)作为所有文件的颜色值:
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 - 如何根据其值有条件地仅更改表格单元格(而不是行)的背景颜色?