为什么图片反复压缩后会普遍会变绿而不是变其他颜色

更新时间:02-02 教程 由 白满川 分享

为什么图片反复压缩后会普遍会变绿而不是变其他颜色?

Android 系统自起诞生以来就引入了名为 Skia 的图像库(Google 自家产品),用于处理图像,其中包括把图片压缩成 JPEG(平时说的 JPG)。而 Skia 又是调用 libjpeg-turbo 来实现真正的压缩过程的。为了达到更好的压缩效果,JPEG 算法本身,将通常屏幕上表示颜色的 RGB(红绿蓝)数值,转换为 YUV 数值(亮度,蓝色分量,红色分量)。正常情况下这个算法是轻微有损的。

但是 Skia 不走寻常路,将这个变换算法的各个常数复制到自己的代码里(当然是合法地),然后降低了精度,以达到更高的速度(专业准确地说,从 16 位定点数,降低到了 8 位定点数),这导致了更大的损伤。

最可怕的是……在进行这个变换运算的最后一步,需要除以 256,而代码中,采用了右移操作代替除法以提高执行速度(看不懂可以跳过):

int y = ( CYR*r + CYG*g + CYB*b ) >> CSHIFT;

int u = ( CUR*r + CUG*g + CUB*b ) >> CSHIFT;

int v = ( CVR*r + CVG*g + CVB*b ) >> CSHIFT;

// C?? 是已经扩大到 2^CSHIFT 倍的矩阵参数(-0.5 ~ 0.5),CSHIFT = 8

这个操作并没有什么问题,数学意义就是除以 256。但是问题出在:

1、直接截断了小数部分,等价于 trunc()。如果符号数是用补码实现的。即全部往负数方向取整。如:1.2 → 1; 3.9 → 3;0.0 → 0;-5.1 → -6.

2、较冒险的符号数移位:根据规范的定义,对符号数(可正可负的数)使用移位的效果将由具体的编译器明确定义决定(implementation-defined)。因为移位是一个符号无关的操作,对符号数移位将依赖于符号数的具体表现形式。而这个形式 C++ 没有给出一个限定,由具体的编译器自行决定,对于非“补码”(2's complement)的情况结果可能并不是所期待的那样数值整除2的幂。这里假设了编译器都能“正确”理解

# YUV 值向负方向取整导致什么?

复习一下 YUV 的定义:

Y,亮度,0.0 ~ 1.0;

U,或者叫做 Cb,蓝色分量,-0.5 ~ 0.5;

V,或者叫做 Cr,红色分量,-0.5 ~ 0.5。

在 Skia 的代码里,YUV 三个值均对应到 0~255 的范围。

因为向下取整,所以误差在 1 一个单位以内:0/256 到 1/256 也就是,YUV 三个值都变小 0.00% 到 0.39% 这个范围。

看一下 U, V 这两个决定颜色的值是如何变化的:

显然,YUV 值向负方向取整,结果是呼之欲出的:变暗,变绿。(这里的变暗是 YUV 里的 Y 减小,并不完全准确对应人类视觉的明暗概念)

这个错误的舍入,使得:所有在 0 ~ 255 范围内非整数的 YUV 值都受到影响。那么某个像素被舍入到整数之后,下一次再压缩 JPEG 应该会好一些吧?很不幸的是,随之而来的大量其他有损操作(比如 DCT 变换之后滤去高频)又会使得 YUV 值发生变化:如果发生变化,假设随机产生关于 0 对称的误差,那么实际上也有 50% 的机率使得这个数值 -1,因为只要比原来的值小,都会被向下舍去。

这使得,图片随着 Skia 缺陷的色彩空间变换算法反复压缩,越来越绿。

声明:关于《为什么图片反复压缩后会普遍会变绿而不是变其他颜色》以上内容仅供参考,若您的权利被侵害,请联系13825271@qq.com
本文网址:http://www.25820.com/tutorial/14_2292039.html