2016-02-19 12 views
5

Ich versuche, einen JPEG-Encoder zu schreiben und stolpern bei der Erstellung der Algorithmen, die die entsprechenden Y, Cb und Cr-Farbkomponenten sammeln, um die Methode zur Durchführung der Transformation übergeben.Chroma Subsampling-Algorithmus für JPEG

Wie ich es für die vier häufigsten Subsampling Varianten sind Setup verstehen wie folgt (ich konnte weg hier sein):

  • 4: 4: 4 - Ein MCU Block von 8x8 Pixeln mit Y, Cb und Cr in jedem Pixel dargestellt.
  • 4: 2: 2 - Ein MCU-Block von 16x8 Pixel mit Y in jedem Pixel und Cb, Cr alle zwei Pixel
  • 4: 2: 0 - Ein MCU-Block von 16x16 Pixel mit Y alle zwei Pixel und Cb, Cr alle vier

Es deutlichste Beschreibung des LAOUT ich bisher gefunden haben, ist here beschrieben

Was ich nicht verstehe, ist, wie diese Komponenten in der richtigen Reihenfolge zu sammeln als 8x8-Block passieren zum Transformieren und Quantisieren.

Würde jemand in der Lage sein, ein Beispiel zu schreiben, (Pseudocode wäre in Ordnung, ich bin sicher, C# noch besser), wie die Bytes für die Transformation zu gruppieren?

Ich werde den aktuellen, inkorrekten Code, den ich verwende, einbeziehen.

/// <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]; 
     } 
    } 
} 

Dieser Code ist Teil eines Open-Source-Bibliothek ich schreibe auf Github

+0

Der Link, auf den Sie verweisen, hat gute Informationen, aber Sie haben ihn falsch interpretiert. Wenn es Farbunterabtastung gibt, ändert sich die MCU-Pixelgröße (z. B. 8 × 8, 16 × 8, 8 × 16, 16 × 16). Innerhalb dieser MCU müssen Sie die Farbdaten richtig unterteilen, dann ordnen Sie sie in 8x8 DCT Blöcke in der Reihenfolge an, die in dem Artikel gezeigt wird (z. B. Y0, Y1, Y2, Y3, Cb, Cr) – BitBank

+0

Danke ja, ich habe viel erraten. Ich kann einfach nicht verstehen, wie dieses Subsampling durchgeführt wird. I.e. welche Pixel aus dem gesamten Pixel-Array entnommen werden sollen und in welcher Reihenfolge sie in meinen einzelnen Komponenten-Arrays angeordnet werden sollen. –

Antwort

2

JPEG Farbsubsampling kann auf einfache, aber funktionale Weise ohne viel Code implementiert werden. Die Grundidee ist, dass Ihre Augen weniger empfindlich auf Veränderungen der Farbe als auf Änderungen der Luminanz reagieren. Daher kann die JPEG-Datei viel kleiner sein, wenn Sie einige Farbinformationen wegwerfen. Es gibt viele Möglichkeiten, die Farbinformationen zu unterteilen, JPEG-Bilder neigen jedoch dazu, vier Varianten zu verwenden: keine, 1/2 horizontal, 1/2 vertikal und 1/2 horizontal + vertikal. Es gibt zusätzliche TIFF/EXIF-Optionen, wie den "Mittelpunkt" der unterabgetasteten Farbe, aber der Einfachheit halber verwenden wir einen Durchschnitt der Summentechnik.

Im einfachsten Fall (kein Subsampling), wobei jede MCU (Minimum codierte Einheit) ist ein 8x8-Block von Pixeln aus 3 Komponenten aus - Y, Cb, Cr. Das Bild wird in Blöcken von 8 × 8 Pixeln verarbeitet, wobei die 3 Farbkomponenten getrennt, durch eine DCT-Transformation geleitet und in der Reihenfolge (Y, Cb, Cr) in die Datei geschrieben werden. In allen Fällen des Unterabtastens bestehen die DCT-Blöcke immer aus 8 × 8 Koeffizienten oder 64 Werten, aber die Bedeutung dieser Werte variiert aufgrund der Farbunterabtastung.

Der nächste einfachste Fall wird in einer Dimension (horizontal oder vertikal) unterabgetastet. Lassen Sie uns für dieses Beispiel 1/2 horizontales Subsampling verwenden. Die MCU ist jetzt 16 Pixel breit und 8 Pixel hoch. Die komprimierte Ausgabe jeder MCU wird nun 4 8 × 8 DCT-Blöcke (Y0, Y1, Cb, Cr) sein. Y0 repräsentiert die Luma-Werte des linken 8x8-Pixelblocks und Y1 repräsentiert die Luma-Werte des rechten 8x8-Pixelblocks. Die Cb- und Cr-Werte sind jeweils 8 × 8 Blöcke basierend auf dem Durchschnittswert von horizontalen Paaren von Pixeln. Ich konnte hier keine guten Bilder finden, aber ein Pseudo-Code kann nützlich sein.

(Update: Bild, das Subsampling :) enter image description here

Hier ist eine einfache Schleife, die funktioniert das Farbsubsampling unseres 1/2 horizontal Fall darstellen könnte:

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 

Wie Sie sehen können, gibt es nicht viel dazu. Jedes Paar von Cb- und Cr-Pixeln aus dem Quellbild wird horizontal gemittelt, um ein neues Cb/Cr-Pixel zu bilden. Diese werden dann DCT-transformiert, Zickzack-codiert und in der gleichen Form wie immer codiert.

Schließlich ist die MCU jetzt für den Unterabtastungsfall 2x2 16x16 Pixel und die geschriebenen DCT-Blöcke Y0, Y1, Y2, Y3, Cb, Cr. Wo Y0 die oberen linken 8x8 Luma Pixel darstellt, Y1 die obere rechte, Y2 die untere linke und Y3 die untere rechte. Die Cb- und Cr-Werte repräsentieren in diesem Fall 4 Quellpixel (2x2), die zusammen gemittelt wurden. Für den Fall, dass Sie sich fragen, werden die Farbwerte im YCbCr-Farbraum zusammen gemittelt. Wenn Sie die Pixel im RGB-Farbraum zusammen berechnen, funktioniert sie nicht korrekt.

FYI - Adobe unterstützt JPEG-Bilder im RGB-Farbraum (anstelle von YCbCr). Diese Bilder können kein Farb-Unterabtasten verwenden, da R, G und B von gleicher Wichtigkeit sind und ein Unterabtasten in diesem Farbraum zu viel schlechteren visuellen Artefakten führen würde.

+0

Ah ... Ich denke ich habe es jetzt. Ich brauchte ein paar Lesevorgänge, um herauszufinden, dass die horizontale Abtastung in Ihrem Beispiel Code entlang x war [0-1] [2-3] [4-5] [6-7] [8-9] [10-11] [12-13] [14-15] wiederholt. Ich werde es ausprobieren. –

+0

@BitBank, wenn Ihr Bild 8 Pixel x 8 Pixel groß ist und Ihre MCU 16x16 oder 16x8 ist. Wie wird das funktionieren? Und wenn Ihr Bild kein Vielfaches von 8 ist? – juFo

+0

@juFo - in diesem Fall wird der nicht verwendete Teil der MCU vom Encoder und Decoder ignoriert. Eine 16x16-MCU, die ein 8x8-Bild codiert, ist absolut gültig. Leider müssen Sie in diesem Fall immer noch eine komplette MCU bestehend aus 6 8x8 DCT-Blöcken (y0, y1, y2, y3, Cb, Cr) codieren. In diesem Fall ist es wahrscheinlich kein Farb-Subsampling wert. – BitBank