2016-02-19 21 views
5

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

+0

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

+0

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. –

risposta

2

JPEG colore subsampling può essere implementato in modo semplice, ma funzionale senza molto codice. L'idea di base è che i tuoi occhi sono meno sensibili ai cambiamenti di colore rispetto ai cambiamenti di luminanza, quindi il file JPEG può essere molto più piccolo lanciando via alcune informazioni sul colore. Esistono molti modi per sottocampionare le informazioni sul colore, ma le immagini JPEG tendono a utilizzare 4 varianti: nessuna, 1/2 orizzontale, 1/2 verticale e 1/2 orizzontale + verticale. Ci sono altre opzioni TIFF/EXIF ​​come il "punto centrale" del colore sottocampionato, ma per semplicità useremo una media della tecnica somma.

Nel caso più semplice (nessun sottocampionamento), ogni MCU (unità codificata minima) è un blocco di pixel 8x8 costituito da 3 componenti: Y, Cb, Cr. L'immagine viene elaborata in blocchi di 8x8 pixel in cui le 3 componenti di colore vengono separate, passate attraverso una trasformazione DCT e scritte nel file nell'ordine (Y, Cb, Cr). In tutti i casi di sottocampionamento, i blocchi DCT sono sempre composti da 8x8 coefficienti o 64 valori, ma il significato di tali valori varia a causa del sottocampionamento del colore.

Il caso successivo è il sottocampionamento in una dimensione (orizzontale o verticale). Usiamo 1/2 sottocampionamento orizzontale per questo esempio. La MCU ora è larga 16 pixel per 8 pixel di altezza. L'output compresso di ogni MCU ora sarà di 4 blocchi 8x8 DCT (Y0, Y1, Cb, Cr). Y0 rappresenta i valori luma del blocco sinistro 8x8 pixel e Y1 rappresenta i valori luma del blocco destro 8x8 pixel. I valori Cb e Cr sono ciascuno di 8x8 blocchi in base al valore medio di coppie orizzontali di pixel. Non sono riuscito a trovare nessuna immagine valida da inserire qui, ma alcuni pseudo-codice possono tornare utili.

(aggiornamento: un'immagine che potrebbe rappresentare subsampling :) enter image description here

Ecco un semplice ciclo che fa il sottocampionamento colore del nostro caso orizzontale 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 

Come si può vedere, c'è non molto ad esso. Ogni coppia di pixel Cb e Cr dell'immagine sorgente viene calcolata in media orizzontalmente per formare un nuovo pixel Cb/Cr. Questi sono quindi trasformati in DCT, zigzagati e codificati nella stessa forma di sempre.

Infine per il caso di sottoesempio 2x2, l'MCU è ora 16x16 pixel ei blocchi DCT scritti saranno Y0, Y1, Y2, Y3, Cb, Cr. Dove Y0 rappresenta i pixel della luma 8x8 in alto a sinistra, Y1 in alto a destra, Y2 in basso a sinistra e Y3 in basso a destra. I valori Cb e Cr in questo caso rappresentano 4 pixel sorgente (2x2) che sono stati mediati insieme. Nel caso ve lo stiate chiedendo, i valori dei colori vengono calcolati in media insieme nello spazio colore YCbCr. Se si media i pixel insieme nello spazio cromatico RGB, non funzionerà correttamente.

FYI - Adobe supporta immagini JPEG nello spazio di colori RGB (anziché YCbCr). Queste immagini non possono utilizzare il sottocampionamento del colore perché R, G e B sono di uguale importanza e il loro sottocampionamento in questo spazio cromatico porterebbe a artefatti visivi molto peggiori.

+0

Ah ... penso di averlo adesso. Ci sono volute alcune letture per capire che il campionamento orizzontale nel tuo codice di esempio lungo x era [0-1] [4-5] [6-7] [8-9] [10-11] [12-13] [14-15] ripetuto. Ho intenzione di implementarlo. –

+0

@BitBank se l'immagine ha una dimensione di 8 pixel x 8 pixel e la MCU è 16x16 o 16x8. Come funzionerà? E se la tua immagine non fosse un multiplo di 8? – juFo

+0

@juFo: in questo caso, la parte inutilizzata della MCU verrà ignorata dall'encoder e dal decodificatore. Una MCU 16x16 che codifica un'immagine 8x8 è perfettamente valida. Sfortunatamente in questo caso, hai ancora bisogno di codificare un MCU completo composto da 6 blocchi 8x8 DCT (y0, y1, y2, y3, Cb, Cr). Probabilmente non vale la pena di sottocampionare il colore in questo caso. – BitBank