如何对色度分量进行下采样

信息处理 图像处理 下采样 压缩 颜色 图像压缩
2022-02-07 11:45:31

因此,我正在为大学做一个图像压缩项目,并试图创建一个视觉演示,说明下采样色度分量如何减少数字信息量,同时对普通人来说几乎不明显。在为这个项目做研究时,我看到了这个视频,这个视频演示了下采样,导致他用作示例的图像中的颜色减少了100 倍。我希望做类似的事情,但不确定他如何能够将图像中的色度分量在每个方向上降低10 倍。

查看色度子采样(/下采样)的标准比率,我能找到的最好的似乎是4:2:0 ,这将在每个方向上将色度下采样2倍。这可能足以进行演示,但我希望我可以将它推得更远一点。

作为一个附带问题,是否有人对如何最好地将下采样颜色应用于我的原始图像有任何建议?我有一些涉及 Photoshop 或 GIMP 的想法,但我想知道是否有人有更好的建议。

4个回答

出于演示目的,GIMP 很好。它可以将图像拆分为三个单独的 YUV/YCbCr 图像。然后,您可以手动调整 UV/CbCr 图像的大小,以减少对色度的采样。然后上采样回到原始分辨率并再次重新组合图像。

您似乎对您想要做的事情、使用视频执行此操作的现有标准以及您可能用于使用静态图像执行此操作的工具之间的区别感到困惑。

你想做什么

你想分离出色度通道,然后你想在 10x10 块中平均它(系数为 100),然后你想制作 10x10 块所有相同的色度,然后你想混合灰度。有很多方法可以做到这一点。请注意,色度可能会奇怪地增加——最好将亮度 (Y) 通道设置为恒定灰度,然后对生成的图像进行二次采样,然后将“真实”亮度放回原处。

视频标准

这些旨在响应市场力量,旨在向消费者提供不太糟糕的视频。所以是的,色度子采样的数量将受到限制。选择视频标准作为您执行此操作的工具可能并不明智。

使用工具

使用 Gimp(或 Photoshop,如果你用这种方式)的答案对我来说看起来不错。如果我要在电影上执行此操作,而不是单个静态图片,我会使用 ffmpg 将事物分成帧,使用 imagemagik 进行二次采样,然后使用 ffmpg 将它们重新组合在一起——在你问之前,不,我不知道如何做到这一点的细节;我有 99.44% 的把握认为这是可能的,而且我可以弄清楚。

JPEG 压缩依赖于多种技术,同时减少了图像的存储大小。主要是 DCT 阶段导致总比特减少。这个阶段由质量参数控制。然而,颜色也有如下优点。

实验证明,我们的眼睛对亮度分辨率比对色度(颜色)分辨率更敏感。为了利用这一特性,原始 RGB 图像数据首先被转换为 YUV(或数字对应 YCbCr)颜色空间,其中 Y 表示灰度亮度信息,UV(Cb-Cr)分量表示色度;颜色色调和颜色饱和度信息。然后,您可以通过低通滤波来降低 UV 分量信号带宽(这相当于降低有效颜色空间分辨率),然后看到生成的图像重建看起来几乎相同。

请注意,这不是图像视频压缩编解码器中采用的方法。正如我所说,它们基于 2D-DCT 变换减少了信息,这将导致更好的图像质量和更多的数据减少。但是请记住,在 DCT 域(在 8x8 图像块上)执行的操作在某种程度上等同于空间域上的可变截止低通滤波。但它在保持高质量的同时实现比特减少非常有效,这与下面展示的一次对整个通道的完整低通滤波不同。

以下 MATLAB 代码演示了该效果。

I = double( imread('C:\matlab\...\Vegatable256.bmp') ); % read the Vegatable image

R = I(:,:,1);   % Red channel
G = I(:,:,2);   % Green channel
B = I(:,:,3);   % Blue channel


% S1: JFIF COLOR-SPACE CONVERSION RGB-YCbCr MATRIX:
% ------------------------------------------------
% Y'CbCr (601) from "digital 8-bit R'G'B'  all signals in {0, 1, 2, ..., 255}
M = [0.299    ,  0.587     , 0.114 ; 
    -0.168736 , -0.331254  , 0.5   ;
     0.5      , -0.418688  ,-0.081312; ];

% inverse matrix is used to convert YCbCr into RGB
Mi = inv(M); % use with (Cb-128, Cr-128) to get RGB

% S2 - Convert RGB to YCbCr :
% ---------------------------
Y  = 0.299*R + 0.587*G + 0.114*B;
Cb = 128  - 0.168736*R - 0.331264*G + 0.5*B;
Cr = 128  + 0.5*R - 0.418688 *G- 0.081312*B;

% S3 - Design a simple 2D lowpass filter :
b1 = fir1(32,0.1);
b2 = b1'*b1;            

% S4 - Apply lowpass filtering on the Y,Cb,Cr channles.
Y2 = conv2(Y,b2,'same');
Cb2 = conv2(Cb-128,b2,'same');
Cr2 = conv2(Cr-128,b2,'same');


% S5 - Reconstruct various coombinations :
% ----------------------------------------
% 1 - I1: only the color Cb, Cr is lowpass filtered
I1 = zeros(S(1),S(2),3);
I1(:,:,1) = Mi(1,1)*Y + Mi(1,2)*Cb2 + Mi(1,3)*Cr2; % R;
I1(:,:,2) = Mi(2,1)*Y + Mi(2,2)*Cb2 + Mi(2,3)*Cr2; % G;
I1(:,:,3) = Mi(3,1)*Y + Mi(3,2)*Cb2 + Mi(3,3)*Cr2; % B;
I1 (I1 > 255) = 255;
I1( I1 < 0) = 0;

% 2 - Only Luminance Y is lowpass filtered 
I2 = zeros(S(1),S(2),3);
I2(:,:,1) = Mi(1,1)*Y2 + Mi(1,2)*(Cb-128) + Mi(1,3)*(Cr-128); % R;
I2(:,:,2) = Mi(2,1)*Y2 + Mi(2,2)*(Cb-128) + Mi(2,3)*(Cr-128); % G;
I2(:,:,3) = Mi(3,1)*Y2 + Mi(3,2)*(Cb-128) + Mi(3,3)*(Cr-128); % B;
I2 (I2 > 255) = 255;
I2( I2 < 0) = 0;

% 3- All of then lowpass filtered
I3 = zeros(S(1),S(2),3);
I3(:,:,1) = Mi(1,1)*Y2 + Mi(1,2)*Cb2 + Mi(1,3)*Cr2; % R;
I3(:,:,2) = Mi(2,1)*Y2 + Mi(2,2)*Cb2 + Mi(2,3)*Cr2; % G;
I3(:,:,3) = Mi(3,1)*Y2 + Mi(3,2)*Cb2 + Mi(3,3)*Cr2; % B;
I3 (I3 > 255) = 255;
I3( I3 < 0) = 0;


% DISPLAY THEM:
%I4 = zeros(2*S(1),2*S(2));
I4 = [I , I1 ; I2 , I3];
figure,imshow(I4/255);
title('UL: original, UR: Chroma only LPF to 0.1 bandwidth');
xlabel('LL: Y only LPF, LR: All channels LPF');  

输出将是:

在此处输入图像描述

如您所见,当您将色度带宽降低到默认值的十分之一(在两个方向上,总共为 1/100)时(在右上角),几乎没有图像质量损失。保留清晰度和颜色。请注意,最初 Cb 和 Cr 通道是低通类型的信号;即,它们在被滤波器排除的高频区域中包含很少的能量。

但是,当您使用相同的滤波器对亮度(亮度或 Y)通道进行低通滤波时,结果是图像质量显着下降,如右下角所示。这是因为亮度通道具有显着的高能量含量,我们的眼睛对它很敏感。

这演示了直到 Cb、Cr 下采样人眼无法检测到的水平:

from PIL import Image
import numpy as np

def downsample_cb_cr(ycbcr, ds_level):
    w, h = ycbcr.size
    ycbcr_np = np.asarray(ycbcr)
    ycbcr_res = ycbcr.resize((w//ds_level, h//ds_level))
    ycbcr_ds = ycbcr_res.resize((w, h))
    ycbcr_ds_np = np.array(ycbcr_ds)
    ycbcr_ds_np[:,:,0] = ycbcr_np[:,:,0]
    return Image.fromarray(ycbcr_ds_np, mode='YCbCr')

def main():
    img = Image.open("images/lena.jpeg")
    img_np = np.array(img)
    ycbcr = img.convert('YCbCr')
    for level in [2,5,10,20,30,40,50,80,100,200]:
        ycbcr_new = downsample_cb_cr(ycbcr, level)
        ycbcr_new.save("images/lena_chroma_ds_level_{}.jpeg".format(level))

if __name__=='__main__':
    main()