首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >基于jpeg的色度次采样算法

基于jpeg的色度次采样算法
EN

Stack Overflow用户
提问于 2016-02-19 04:02:47
回答 1查看 5.9K关注 0票数 6

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

据我所知,四种最常见的次抽样变体的设置如下(我可能在这里离这里很远):

  • 4:4-一个8x8像素的MCU块,每个像素中代表Y、Cb和Cr。
  • 4:2:2 -一个16×8像素的MCU块,每个像素有Y,每两个像素有Cb、Cr。
  • 4:2:0 -一个16×16像素的单片机块,Y每两个像素,Cb,Cr每四个

到目前为止,我发现的关于laout的最明确的描述是这里

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

有人能写一个例子吗?(我肯定,C#会更好地使用伪代码),如何将字节分组以进行转换?

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

代码语言:javascript
运行
复制
/// <summary>
/// Writes the Scan header structure
/// </summary>
/// <param name="image">The image to encode from.</param>
/// <param name="writer">The writer to write to the stream.</param>
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);
}

/// <summary>
/// Converts the pixel block from the RGBA colorspace to YCbCr.
/// </summary>
/// <param name="image"></param>
/// <param name="yComponant">The container to house the Y' luma componant within the block.</param>
/// <param name="cbComponant">The container to house the Cb chroma componant within the block.</param>
/// <param name="crComponant">The container to house the Cr chroma componant within the block.</param>
/// <param name="x">The x-position within the image.</param>
/// <param name="y">The y-position within the image.</param>
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 < 8; a++)
    {
        // Complete with the remaining right and bottom edge pixels.
        int py = y + a;
        if (py >= height)
        {
            py = height - 1;
        }

        for (int b = 0; b < 8; b++)
        {
            int px = x + b;
            if (px >= 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;
        }
    }
}

/// <summary>
/// Compress and encodes the pixels. 
/// </summary>
/// <param name="componantValues">The current color component values within the image block.</param>
/// <param name="componantIndex">The componant index.</param>
/// <param name="writer">The writer.</param>
/// <param name="dcValues">The descrete cosine values for each componant</param>
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上编写的开源库的一部分。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2016-03-04 08:31:29

JPEG颜色次采样可以实现简单,但功能的方式,没有太多的代码。基本思想是你的眼睛对颜色的变化和亮度的变化不那么敏感,所以JPEG文件可以通过丢弃一些颜色信息来小得多。有许多方法可以对颜色信息进行子采样,但是JPEG图像倾向于使用4种变体:无、1/2水平、1/2垂直和1/2 horizontal+vertical。还有额外的TIFF/EXIF选项,例如亚采样颜色的“中心点”,但是为了简单起见,我们将使用和技术的平均值。

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

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

(更新:可能代表次采样的图像:)

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

代码语言:javascript
运行
复制
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变换,锯齿和编码的形式与以往一样。

最后,对于2x2子样例,单片机现在是16×16像素,所写的离散余弦变换块将是Y0、Y1、Y2、Y3、Cb、Cr。其中,Y0表示左上8x8卢马像素,Y1表示右上角,Y2表示左下角,Y3表示右下角。在这种情况下,Cb和Cr值表示4个源像素(2x2),它们被平均在一起。万一您想知道,颜色值在YCbCr颜色空间中是平均的。如果在RGB颜色空间中将像素平均起来,它将不能正常工作。

Adobe支持RGB颜色空间中的JPEG图像(而不是YCbCr)。这些图像不能使用颜色二次采样,因为R、G和B是同等重要的,在这个颜色空间中对它们进行过采样会导致更糟糕的视觉伪影。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/35497075

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档