Próbuję napisać koder jpeg i natknę się na tworzenie algorytmów, które zbierają odpowiednie komponenty koloru Y, Cb i Cr, aby przejść do metody przeprowadzania transformacji.Algorytm podpróbkowania Chroma dla jpeg
Jak rozumiem dla czterech najczęstszych wariantów subsampling są ustawione w następujący sposób (mogę być daleko tutaj):
- 4: 4: 4 - Blok MCU z 8x8 pikseli z Y, Cb i Cr reprezentowany w każdym pikselu.
- 4: 2: 2 - Blok MCU 16x8 pikseli z Y w każdym pikselu i Cb, Cr co dwa piksele
- 4: 2: 0 - Blok MCU 16x16 pikseli z Y co dwa piksele i Cb, cr co cztery
jest najbardziej wyraźny opis laout znalazłem do tej pory opisano here
Co ja nie rozumiem, jak zebrać te składniki w odpowiedniej kolejności, aby przejść jako bloku 8x8 do transformacji i kwantowania.
Czy ktoś mógłby napisać przykład, (pseudokod byłby w porządku, jestem pewien, C# jeszcze lepiej), jak pogrupować bajty dla transformacji?
Dołączę bieżący, niepoprawny kod, który używam.
/// <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];
}
}
}
Kod ten jest częścią otwartej biblioteki źródłowej piszę na Github
Link, do którego się odwołujesz, zawiera dobre informacje, ale źle je zinterpretowałeś. Gdy występuje podpróbkowanie kolorów, zmienia się rozmiar pikseli MCU (np. 8x8, 16x8, 8x16, 16x16). Wewnątrz tego MCU należy poprawnie podbić dane koloru, a następnie ułożyć je w bloki DCT 8x8 w kolejności pokazanej w artykule (np. Y0, Y1, Y2, Y3, Cb, Cr). – BitBank
Dzięki, tak, zgadłem dużo. Po prostu nie mogę zrozumieć, jak wykonać tę podpróbkę. To znaczy. jakie piksele przechwycić z całej tablicy pikseli iw jakiej kolejności rozmieścić je w moich indywidualnych tablicach składowych. –