C# jpeg中的色度子采样算法

C# jpeg中的色度子采样算法,c#,algorithm,jpeg,C#,Algorithm,Jpeg,我正在尝试编写一个jpeg编码器,但在创建收集适当Y、Cb和Cr颜色分量的算法时遇到了障碍,以便传递给执行变换的方法 据我所知,四种最常见的子采样变体的设置如下(我可能离这里很远): 4:4:4-一个8x8像素的MCU块,每个像素中代表Y、Cb和Cr 4:2:2-一个16x8像素的MCU块,每个像素有Y,每两个像素有Cb、Cr 4:2:0-一个16x16像素的MCU块,每两个像素有Y,每四个像素有Cb、Cr 到目前为止,我所发现的关于拉乌特人最明确的描述是 我不明白的是如何以正确的顺序收集这

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

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

  • 4:4:4-一个8x8像素的MCU块,每个像素中代表Y、Cb和Cr
  • 4:2:2-一个16x8像素的MCU块,每个像素有Y,每两个像素有Cb、Cr
  • 4:2:0-一个16x16像素的MCU块,每两个像素有Y,每四个像素有Cb、Cr
到目前为止,我所发现的关于拉乌特人最明确的描述是

我不明白的是如何以正确的顺序收集这些组件,以作为8x8块传递,用于变换和量化

是否有人能够编写一个示例(我相信伪代码会很好,C#甚至更好),说明如何对字节进行分组以进行转换

我将包括当前运行的错误代码

/// <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];
        }
    }
}
//
///写入扫描头结构
/// 
///要从中编码的图像。
///编写器必须向流中写入。
专用void writeStartOffscan(ImageBase图像,EndianBinaryWriter writer)
{
//标记
Write.Write(新[]{JpegConstants.Markers.XFF,JpegConstants.Markers.SOS});
//长度(高字节、低字节)必须为6+2*(扫描中的组件数量)
writer.Write((短)0xc);//12
字节[]sos={
3,//扫描中的组件数,通常为1或3
1,//组件Id Y
0,//直流/交流哈夫曼表
2,//组件Id Cb
0x11,//直流/交流哈夫曼表
3,//组件Id Cr
0x11,//直流/交流哈夫曼表
0,//Ss-光谱选择的开始。
0x3f,//Se-光谱选择结束。
0//Ah+Ah(逐次逼近位位置高+低)
};
写作(sos);
//压缩并写入像素
//每个Y'Cb Cr组件的缓冲器
float[]yU=新的float[64];
浮动[]cbU=新浮动[64];
浮动[]crU=新浮动[64];
//每个成分的描述余弦值。
int[]dcValues=新的int[3];
//TODO:为什么为空?
this.huffmanTable=新的huffmanTable(null);
//TODO:在此点之后颜色输出不正确。
//我想我的循环都搞错了。
//每行
对于(int y=0;y=高度)
{
py=高度-1;
}
对于(int b=0;b<8;b++)
{
int px=x+b;
如果(px>=宽度)
{
px=宽度-1;
}
YCbCr颜色=图像[px,py];
整数指数=a*8+b;
yComponant[索引]=color.Y;
cbcomponent[index]=color.Cb;
crcomponent[索引]=color.Cr;
}
}
}
/// 
///压缩和编码像素。
/// 
///图像块中的当前颜色分量值。
///成分索引。
///作者。
///每个成分的描述余弦值
私有void CompressPixels(float[]componentvalues、int componentindex、EndianBinaryWriter writer、int[]dcValues)
{
//TODO:这应该是一个选项。
byte[]horizontalFactors=JpegConstants.chromaFortwoZeroHorizontal;
byte[]verticalFactors=JpegConstants.chromaFortwoZeroVertical;
byte[]quantizationTableNumber={0,1,1};
int[]dcTableNumber={0,1,1};
int[]acTableNumber={0,1,1};
对于(int y=0;y

这段代码是我正在写的一个开源库的一部分,JPEG颜色子采样可以以一种简单但功能强大的方式实现,无需太多代码。其基本思想是,与亮度变化相比,眼睛对颜色变化的敏感度较低,因此通过丢弃一些颜色信息,JPEG文件可以小得多。有许多方法可以对颜色信息进行子采样,但JPEG图像倾向于使用4种变体:无、1/2水平、1/2垂直和1/2水平+垂直。还有其他TIFF/EXIF选项,例如子采样颜色的“中心点”,但为了简单起见,我们将使用总和的平均值
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