Sto tentando di scrivere un codificatore jpeg e sto inciampando nella creazione degli algoritmi che raccolgono le componenti Y, Cb e Cr appropriate per passare al metodo che esegue la trasformazione.Algoritmo di sottocampionamento del croma per jpeg
quanto mi risulta per i quattro varianti più comuni sottocampionamento sono settati come segue (ho potuto essere lontano qui):
- 4: 4: 4 - Un blocco MCU di pixel 8x8 con Y, Cb e Cr rappresentato in ciascun pixel.
- 4: 2: 2 - Un blocco MCU di 16x8 pixel con Y in ogni pixel e Cb, Cr ogni due pixel
- 4: 2: 0 - Un blocco MCU di 16x16 pixel con Y ogni due pixel e Cb, Cr ogni quattro
C'è più esplicita descrizione del laout che ho trovato finora è descritto here
Quello che non capisco è come raccogliere quei componenti nell'ordine corretto per passare come un blocco di 8x8 per trasformare e quantizzare.
Qualcuno sarebbe in grado di scrivere un esempio, (lo pseudocodice andrebbe bene sono sicuro, C# ancora meglio), di come raggruppare i byte per la trasformazione?
Includerò il codice corrente, non corretto, in esecuzione.
/// <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];
}
}
}
Questo codice è parte di una libreria open source che sto scrivendo su Github
Il collegamento di riferimento ha buone informazioni, ma l'hai interpretato erroneamente. Quando c'è un sottocampionamento del colore, le dimensioni del pixel dell'MCU cambiano (ad es. 8x8, 16x8, 8x16, 16x16). All'interno di questo MCU è necessario sottocampionare correttamente i dati di colore, quindi sistemarli in blocchi 8x8 DCT nell'ordine mostrato nell'articolo (ad esempio Y0, Y1, Y2, Y3, Cb, Cr) – BitBank
Grazie, sì, ho indovinato un po '. Non riesco proprio a capire come eseguire quel sottocampionamento. Cioè quali pixel afferrare dall'intero array di pixel e in quale ordine sistemarli nei miei singoli array di componenti. –