jpeg的色度子采样算法

我正在尝试编写一个jpeg编码器,并且在创建收集适当的Y,Cb和Cr颜色分量的算法时感到磕磕绊,以便传递给执行变换的方法。

据我所知,四个最常见的子采样变体设置如下(我可能会离开这里):

  • 4:4:4 – 每个像素中包含Y,Cb和Cr的8×8像素的MCU块。
  • 4:2:2 – 一个16×8像素的MCU块,每个像素为Y,每两个像素为Cb,Cr
  • 4:2:0 – 一个16×16像素的MCU块,每两个像素为Y,每四个像素为Cb,Cr

到目前为止,我已经找到了关于laout的最明确的描述

我不明白的是如何以正确的顺序收集这些组件以作为8×8块进行转换和量化。

有人能够写一个例子,(伪代码可以,我确定,C#甚至更好),如何分组变换的字节?

我将包含我正在运行的当前不正确的代码。

///  /// Writes the Scan header structure ///  /// The image to encode from. /// The writer to write to the stream. private void WriteStartOfScan(ImageBase image, EndianBinaryWriter writer) { // Marker writer.Write(new[] { JpegConstants.Markers.XFF, JpegConstants.Markers.SOS }); // Length (high byte, low byte), must be 6 + 2 * (number of components in scan) writer.Write((short)0xc); // 12 byte[] sos = { 3, // Number of components in a scan, usually 1 or 3 1, // Component Id Y 0, // DC/AC Huffman table 2, // Component Id Cb 0x11, // DC/AC Huffman table 3, // Component Id Cr 0x11, // DC/AC Huffman table 0, // Ss - Start of spectral selection. 0x3f, // Se - End of spectral selection. 0 // Ah + Ah (Successive approximation bit position high + low) }; writer.Write(sos); // Compress and write the pixels // Buffers for each Y'Cb Cr component float[] yU = new float[64]; float[] cbU = new float[64]; float[] crU = new float[64]; // The descrete cosine values for each componant. int[] dcValues = new int[3]; // TODO: Why null? this.huffmanTable = new HuffmanTable(null); // TODO: Color output is incorrect after this point. // I think I've got my looping all wrong. // For each row for (int y = 0; y < image.Height; y += 8) { // For each column for (int x = 0; x < image.Width; x += 8) { // Convert the 8x8 array to YCbCr this.RgbToYcbCr(image, yU, cbU, crU, x, y); // For each component this.CompressPixels(yU, 0, writer, dcValues); this.CompressPixels(cbU, 1, writer, dcValues); this.CompressPixels(crU, 2, writer, dcValues); } } this.huffmanTable.FlushBuffer(writer); } ///  /// Converts the pixel block from the RGBA colorspace to YCbCr. ///  ///  /// The container to house the Y' luma componant within the block. /// The container to house the Cb chroma componant within the block. /// The container to house the Cr chroma componant within the block. /// The x-position within the image. /// The y-position within the image. private void RgbToYcbCr(ImageBase image, float[] yComponant, float[] cbComponant, float[] crComponant, int x, int y) { int height = image.Height; int width = image.Width; for (int a = 0; a = height) { py = height - 1; } for (int b = 0; b = width) { px = width - 1; } YCbCr color = image[px, py]; int index = a * 8 + b; yComponant[index] = color.Y; cbComponant[index] = color.Cb; crComponant[index] = color.Cr; } } } ///  /// Compress and encodes the pixels. ///  /// The current color component values within the image block. /// The componant index. /// The writer. /// The descrete cosine values for each componant private void CompressPixels(float[] componantValues, int componantIndex, EndianBinaryWriter writer, int[] dcValues) { // TODO: This should be an option. byte[] horizontalFactors = JpegConstants.ChromaFourTwoZeroHorizontal; byte[] verticalFactors = JpegConstants.ChromaFourTwoZeroVertical; byte[] quantizationTableNumber = { 0, 1, 1 }; int[] dcTableNumber = { 0, 1, 1 }; int[] acTableNumber = { 0, 1, 1 }; for (int y = 0; y < verticalFactors[componantIndex]; y++) { for (int x = 0; x < horizontalFactors[componantIndex]; x++) { // TODO: This can probably be combined reducing the array allocation. float[] dct = this.fdct.FastFDCT(componantValues); int[] quantizedDct = this.fdct.QuantizeBlock(dct, quantizationTableNumber[componantIndex]); this.huffmanTable.HuffmanBlockEncoder(writer, quantizedDct, dcValues[componantIndex], dcTableNumber[componantIndex], acTableNumber[componantIndex]); dcValues[componantIndex] = quantizedDct[0]; } } } 

此代码是我在Github上编写的开源库的一部分

JPEG颜色子采样可以以简单但function性的方式实现,而无需太多代码。 基本的想法是,您的眼睛对颜色变化对亮度变化的敏感度较低,因此丢弃一些颜色信息可以使JPEG文件小得多。 有许多方法可以对颜色信息进行二次采样,但JPEG图像倾向于使用4种变体:无,1/2水平,1/2垂直和1/2水平+垂直。 还有其他TIFF / EXIF选项,例如二次采样颜色的“中心点”,但为简单起见,我们将使用总和技术的平均值。

在最简单的情况下(没有子采样),每个MCU(最小编码单元)是由3个分量组成的8×8像素块–Y,Cb,Cr。 图像在8×8像素块中处理,其中3种颜色分量被分离,通过DCT变换并按顺序(Y,Cb,Cr)写入文件。 在所有子采样的情况下,DCT块总是由8×8系数或64个值组成,但这些值的含义因颜色子采样而变化。

下一个最简单的情况是在一个维度(水平或垂直)中进行二次采样。 让我们在这个例子中使用1/2水平子采样。 MCU现在是16像素宽,8像素高。 每个MCU的压缩输出现在将是4个8×8 DCT块(Y0,Y1,Cb,Cr)。 Y0表示左8×8像素块的亮度值,Y1表示右8×8像素块的亮度值。 基于水平像素对的平均值,Cb和Cr值均为8×8块。 我在这里找不到任何好的图像,但是一些伪代码可以派上用场。

(更新:可能代表子采样的图像:) 在此处输入图像描述

这是一个简单的循环,它对我们的1/2水平情况进行颜色子采样:

 unsigned char ucCb[8][8], ucCr[8][8]; int x, y; for (y=0; y<8; y++) { for (x=0; x<8; x++) { ucCb[y][x] = (srcCb[y][x*2] + srcCb[y][(x*2)+1] + 1)/2; // average each horiz pair ucCr[y][x] = (srcCr[y][x*2] + srcCr[y][(x*2)+1] + 1)/2; } // for x } // for y 

正如你所看到的,它并不多。 来自源图像的每对Cb和Cr像素被水平平均以形成新的Cb / Cr像素。 然后对它们进行DCT变换,锯齿形并以与以往相同的forms进行编码。

最后,对于2x2子样本情况,MCU现在是16x16像素,写入的DCT块将是Y0,Y1,Y2,Y3,Cb,Cr。 其中Y0表示左上8x8亮度像素,Y1表示右上角,Y2表示左下角,Y3表示右下角。 在这种情况下,Cb和Cr值表示已经一起平均的4个源像素(2×2)。 如果您想知道,颜色值在YCbCr颜色空间中一起平均。 如果在RGB颜色空间中将像素平均在一起,则无法正常工作。

仅供参考 - Adob​​e支持RGB颜色空间(而不是YCbCr)中的JPEG图像。 这些图像不能使用颜色子采样,因为R,G和B具有同等重要性,并且在这个颜色空间中对它们进行二次采样会导致更糟糕的视觉伪像。