无损RGB到Y'CbCr的转换

11

我正在尝试对图像进行无损压缩,为了利用规律性,我想将图像从RGB转换为Y'CbCr。(这里RGB和Y'CbCr的确切细节并不重要; RGB数据由三个字节组成,我有三个字节来存储结果。)

转换过程本身很简单,但有一个问题:虽然变换在数学上是可逆的,但实际上会存在四舍五入误差。当然,这些误差很小,几乎不可察觉,但这意味着该过程不再是无损的。

我的问题是:是否存在一种转换方式,将三个八位整数(表示红、绿和蓝分量)转换为另外三个八位整数(表示类似于Y'CbCr的颜色空间,其中两个分量随着位置的变化只发生轻微变化,或者至少比在RGB颜色空间中少),且可以反转而不会丢失信息?

2个回答

17

YCoCg24

这里介绍一种我称之为“YCoCg24”的颜色转换方法,它将三个8位整数(代表红、绿、蓝三个分量)转换成另外三个8位(有符号)整数(代表类似于Y'CbCr的颜色空间),且是双射的(因此可以无损逆转换):

 G          R          B     Y          Cg         Co
 |          |          |     |          |          |
 |          |->-(-1)->(+)   (+)<-(-/2)<-|          |
 |          |          |     |          |          |
 |         (+)<-(/2)-<-|     |->-(+1)->(+)         |
 |          |          |     |          |          |
 |->-(-1)->(+)         |     |         (+)<-(-/2)<-|
 |          |          |     |          |          |
(+)<-(/2)-<-|          |     |          |->-(+1)->(+)
 |          |          |     |          |          |
 Y          Cg         Co    G          R          B

forward transformation       reverse transformation

或者用伪代码表示:
function forward_lift( x, y ):
    signed int8 diff = ( y - x ) mod 0x100
    average = ( x + ( diff >> 1 ) ) mod 0x100
    return ( average, diff )

function reverse_lift( average, signed int8 diff ):
    x = ( average - ( diff >> 1 ) ) mod 0x100
    y = ( x + diff ) mod 0x100
    return ( x, y )

function RGB_to_YCoCg24( red, green, blue ):
    (temp, Co) = forward_lift( red, blue )
    (Y, Cg)    = forward_lift( green, temp )
    return( Y, Cg, Co)

function YCoCg24_to_RGB( Y, Cg, Co ):
    (green, temp) = reverse_lift( Y, Cg )
    (red, blue)   = reverse_lift( temp, Co)
    return( red, green, blue )

一些示例颜色:
color        R G B     Y CoCg24
white      0xFFFFFF  0xFF0000
light grey 0xEFEFEF  0xEF0000
dark grey  0x111111  0x110000
black      0x000000  0x000000

red        0xFF0000  0xFF01FF
lime       0x00FF00  0xFF0001
blue       0x0000FF  0xFFFFFF

G、R-G、B-G 颜色空间

另一种颜色转换方式,将三个八位整数转换为另外三个八位整数。

function RGB_to_GCbCr( red, green, blue ):
    Cb = (blue - green) mod 0x100
    Cr = (red  - green) mod 0x100
    return( green, Cb, Cr)

function GCbCr_to_RGB( Y, Cg, Co ):
    blue = (Cb + green) mod 0x100
    red  = (Cr + green) mod 0x100
    return( red, green, blue )

一些示例颜色:
color        R G B     G CbCr
white      0xFFFFFF  0xFF0000
light grey 0xEFEFEF  0xEF0000
dark grey  0x111111  0x110000
black      0x000000  0x000000

评论

似乎有很多无损色彩空间转换。 Henrique S. Malvar等人在"基于提升的可逆色彩变换用于图像压缩"中提到了几种无损色彩空间变换; JPEG XR中还有无损色彩空间变换; 在几个“无损JPEG”提案中使用的原始可逆色彩变换(ORCT); G,R-G,B-G色彩空间; 等等。 Malvar等人对24位RGB像素的26位YCoCg-R表示非常感兴趣。

然而,几乎所有这些方法都需要存储超过24位的变换后的像素颜色。

我使用的 YCoCg24 中的 "lifting" 技术类似于 Malvar 等人所使用的技术,也类似于 JPEG XR 中的无损颜色空间转换。

由于加法是可逆的(且模 0x100 的加法是双射的),从 (a,b) 到 (x,y) 的任何变换,只要可以通过以下 Feistel 网络 产生,就是可逆和双射的:

 a        b
 |        |
 |->-F->-(+)
 |        |
(+)-<-G-<-|
 |        |
 x        y

其中 (+) 表示 8 位加法(模 0x100),a、b、x、y 都是 8 位值,而 F 和 G 表示任意的函数。

细节

为什么只有 3 字节来存储结果? 这听起来像是一种逆向效果的过早优化。 如果你的目标是将图像无损压缩成尽可能小的压缩文件,并在合理的时间内完成,那么中间阶段的大小是无关紧要的。 甚至可能会适得其反-- 一个“更大”的中间表示(例如可逆色彩转换或 26 位 YCoCg-R)可能会比“更小”的中间表示(例如 RGB 或 YCoCg24)产生更小的最终压缩文件大小。

编辑: 糟糕。 “(x) mod 0x100” 或 “(x) & 0xff” 中的任何一个都会给出我想要的结果。 但不知怎么的,我把它们搞混了,导致无法正常工作。


感谢您提供这么详细的答案。24位要求的原因是实际应用需要。我知道使用26位中间阶段可以提高压缩比(事实上,有多个中间阶段会增加流的大小)。然而,我使用的算法是基于单个字节操作的,而不是位。如果将位流视为字节流处理,则会失去颜色空间转换引入的良好特性。 - Ruud
是的,每当我将系统从字节流切换到位流时,系统通常需要花费大约8倍的时间来运行,并且更难调试。因此,我理解您希望尽可能坚持使用字节流的愿望。 - David Cary
David,这是一个非常酷的转换。非常感谢您提供它。需要注意的一点是,您的reverse_lift应该返回x和y,而不是平均值和差异。 - MoDJ
@DavidCary 在您的伪代码中,您将一些变量指定为“signed int8”,但在某些语言中,移位有符号整数是实现定义的。您对“diff >> 1”的预期行为是什么? - chew socks
@chewsocks:我原本打算用 "diff >> 1" 进行算术右移,在Java、JavaScript、Python、PHP、Ruby、Go、Julia、Swift等许多C实现中(如GCCLLVM),都是用符号 ">>" 表示。因此,"-1 >> 1" 的结果为 -1。然而,向零取整的除以二,使得 "-1 >> 1" 的结果为 0,只要在两个方向上使用相同的函数,就可以得到另一种无损转换。 - David Cary
@jpap:感谢您修复了一个损坏的链接。 :-) - David Cary

4
我找到了一种解决方案,由JPEG 2000使用。它被称为可逆色彩变换(RCT),在维基百科JPEG网站中有描述(尽管舍入方法不一致)。然而,结果不如不可逆的颜色变换好。
我还发现了一种更好的方法,在Soo-Chang Pei和Jian-Jiun Ding的论文《Improved Reversible Integer-to-integer Color Transforms》中有描述。然而,该论文中描述的方法以及JPEG 2000使用的方法需要额外的位来存储结果。这意味着转换后的值不再适合24位。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接