2016-02-19 33 views
5

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

+0

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

+0

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

Odpowiedz

2

JPEG koloru podpróbek może być realizowany w sposób prosty, ale funkcjonalny bez większego kodu. Podstawową ideą jest to, że twoje oczy są mniej wrażliwe na zmiany koloru w porównaniu ze zmianami luminancji, więc plik JPEG może być znacznie mniejszy poprzez wyrzucenie niektórych informacji o kolorach. Istnieje wiele sposobów na podsłuchiwanie informacji o kolorach, ale obrazy JPEG mają tendencję do używania 4 wariantów: brak, 1/2 poziomy, 1/2 pionowy i 1/2 poziomy + pionowy. Dostępne są dodatkowe opcje TIFF/EXIF, takie jak "punkt środkowy" koloru podpróbkowanego, ale dla uproszczenia użyjemy średniej techniki sumowania.

W najprostszym przypadku (bez podpróbkowania), każdy MCU (minimalna zakodowana jednostka) jest blokiem 8x8 pikseli złożonym z 3 komponentów - Y, Cb, Cr. Obraz jest przetwarzany w blokach 8x8 pikseli, gdzie 3 składniki kolorów są rozdzielane, przekazywane przez transformatę DCT i zapisywane do pliku w kolejności (Y, Cb, Cr). We wszystkich przypadkach podpróbkowania bloki DCT zawsze składają się ze współczynników 8x8 lub 64 wartości, ale znaczenie tych wartości zależy od podpróbkowania kolorów.

Następny najprostszy przypadek jest podpróbkowany w jednym wymiarze (poziomym lub pionowym). W tym przykładzie użyjmy 1/2 poziomo podpróbkowania. MCU ma teraz szerokość 16 pikseli i 8 pikseli wysokości. Skompresowane wyjście każdego MCU będzie teraz blokami DCT 4 8x8 (Y0, Y1, Cb, Cr). Y0 reprezentuje wartości luma lewego bloku 8x8 pikseli, a Y1 przedstawia wartości lumy prawego bloku 8x8 pikseli. Wartości Cb i Cr to 8 x 8 bloków na podstawie średniej wartości poziomych par pikseli. Nie mogłem znaleźć żadnych dobrych zdjęć do wstawienia tutaj, ale niektóre pseudo-kody mogą się przydać.

(aktualizacja: obraz, który może reprezentować subsampling :) enter image description here

Oto prosty pętla która robi subsampling kolor naszych 1/2 poziomej przypadku:

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 

Jak widać, nie ma nie bardzo do tego. Każda para pikseli Cb i Cr z obrazu źródłowego jest uśredniana w poziomie, tworząc nowy piksel Cb/Cr. Są to następnie transformowane DCT, zygzakowane i zakodowane w tej samej postaci co zawsze.

Wreszcie dla przypadku podpróbkowania 2x2, MCU ma teraz 16x16 pikseli, a zapisane bloki DCT to Y0, Y1, Y2, Y3, Cb, Cr. Gdzie Y0 reprezentuje górną lewą ośmiokąta 8x8 pikseli, Y1 górny prawy, Y2 lewy dolny i Y3 prawy dolny. Wartości Cb i Cr w tym przypadku reprezentują 4 piksele źródłowe (2x2), które zostały uśrednione razem. Na wypadek gdybyś się zastanawiał, wartości kolorów są uśredniane razem w przestrzeni kolorów YCbCr. Jeśli uśrednisz piksele razem w przestrzeni kolorów RGB, nie będzie działać poprawnie.

FYI - Adobe obsługuje obrazy JPEG w przestrzeni kolorów RGB (zamiast YCbCr). Obrazy te nie mogą korzystać z podpróbkowania kolorów, ponieważ R, G i B są równie ważne, a podpanowanie ich w tej przestrzeni kolorów doprowadziłoby do znacznie gorszych artefaktów wizualnych.

+0

Ah ... Myślę, że mam to teraz. Zajęło mi kilka odczytów, aby dowiedzieć się, że poziome próbkowanie w twoim przykładowym kodzie wzdłuż x było [0-1] [2-3] [4-5] [6-7] [8-9] [10-11] [12-13] [14-15] powtórzono. Będę musiał go wdrożyć. –

+0

@BitBank, jeśli obraz ma 8 pikseli x 8 pikseli, a MCU to 16 x 16 lub 16 x 8. Jak to działa? A co jeśli twój obraz nie jest wielokrotnością 8? – juFo

+0

@juFo - w tym przypadku niewykorzystana część MCU zostanie zignorowana przez koder i dekoder. Kod MCU 16x16 kodujący obraz 8x8 jest całkowicie poprawny. Niestety w tym przypadku nadal trzeba zakodować kompletny MCU składający się z 6 bloków DCT 8x8 (y0, y1, y2, y3, Cb, Cr). Prawdopodobnie nie warto w tym przypadku podpróbkować kolorów. – BitBank