Dieses Dokument führt cuobjdump. Nvdisasm. Und nvprune. Drei CUDA-Binär-Tools für Linux (x86 und ARM), Windows, Mac OS und Android. Eine CUDA-Binärdatei (auch als cubin bezeichnet) ist eine ELF-formatierte Datei, die aus CUDA-ausführbaren Codeabschnitten sowie anderen Abschnitten besteht, die Symbole, Relokatoren, Debug-Informationen usw. enthalten. Standardmäßig bettet der CUDA-Compiler-Treiber nvcc cubin-Dateien ein In die ausführbare Datei des Hosts. Sie können aber auch separat mit der Option - cubin von nvcc generiert werden. Cubin-Dateien werden zur Laufzeit von der CUDA-Treiber-API geladen. Hinweis: Weitere Informationen zu cubin-Dateien oder der CUDA-Kompilierungs-Trajektorie finden Sie im NVIDIA CUDA Compiler Driver NVCC. CUDA bietet zwei binäre Hilfsprogramme zum Prüfen und Disassemblieren von Cubin-Dateien und Host-Executables: cuobjdump und nvdisasm. Grundsätzlich akzeptiert cuobjdump sowohl Cubin-Dateien und Host-Binaries, während nvdisasm nur Cubin-Dateien akzeptiert, aber nvdisasm bietet umfangreichere Ausgabeoptionen. Heres einen schnellen Vergleich der beiden Werkzeuge: Tabelle 1. Vergleich von cuobjdump und nvdisasm cuobjdump extrahiert Informationen aus CUDA-Binärdateien (sowohl Standalone als auch diejenigen, die in Host-Binaries eingebettet sind) und präsentiert sie in einem lesbaren Format. Die Ausgabe von cuobjdump enthält CUDA-Assemblierungscode für jeden Kernel, CUDA-ELF-Abschnitt-Header, String-Tabellen, Relokatoren und andere CUDA-spezifische Abschnitte. Es extrahiert auch eingebetteten ptx-Text aus Host-Binaries. Eine Liste der CUDA-Installationsanweisungen der einzelnen GPU-Architekturen finden Sie unter Instruction Set Reference. Cuobjdump akzeptiert jeweils eine Eingabedatei. Die grundlegende Verwendung lautet wie folgt: Zur Demontage eines eigenständigen Cubins oder Cubins, die in einer Host-Executable eingebettet sind und CUDA-Assembly der Kernel anzeigen, verwenden Sie den folgenden Befehl: Verwenden Sie den folgenden Befehl, um cuda elf-Abschnitte in einem von Menschen lesbaren Format aus einer cubin-Datei zu entfernen : Um ptx-Text aus einer Host-Binärdatei zu extrahieren, verwenden Sie den folgenden Befehl: Heres eine Beispiel-Ausgabe von cuobjdump. Wie in der Ausgabe gezeigt, enthält die a. out-Host-Binärdatei cubin - und ptx-Code für sm20. Um cubin-Dateien in der Host-Binärdatei aufzurufen, verwenden Sie die Option - lelf: Um alle Cubins als Dateien aus der Host-Binärdatei zu extrahieren, verwenden Sie - xelf alle Option: Um den Cubin mit dem Namen addnew. sm30.cubin zu extrahieren. Um nur die Cubins zu extrahieren, die in ihren Namen alt sind: Sie können beliebige Substring an - xelf und - xptx Optionen übergeben. Nur die Dateien mit dem Teilstring im Namen werden aus der Eingabe-Binärdatei extrahiert. Gehen Sie wie folgt vor, um gemeinsame und pro Funktionsressourcenverwendungsinformationen zu löschen: Beachten Sie, dass der Wert für REG, TEXTURE, SURFACE und SAMPLER den Zähler angibt und für andere Ressourcen nein steht. Der verwendeten Byte (s). Tabelle 2 enthält unterstützte Befehlszeilenoptionen von cuobjdump. Zusammen mit einer Beschreibung, was jede Option tut. Jede Option hat einen langen Namen und einen kurzen Namen, der austauschbar verwendet werden kann. Tabelle 2. cuobjdump Befehlszeilenoptionen --extract-elf ltpartial file namegt. Auszug ELF-Datei (en) Name mit ltpartial Datei namegt und speichern als Datei (en). Verwenden Sie alle, um alle Dateien zu extrahieren. Um die Liste der ELF-Dateien zu erhalten, verwenden Sie die Option - lelf. Funktioniert mit Host Executable / Objekt / Bibliothek und externem fatbin. Alle Dump - und Listenoptionen werden mit dieser Option ignoriert. --extract-ptx ltpartiale Datei namegt. Extrahieren Sie PTX-Datei (en) Namen mit ltpartial Datei namegt und speichern als Datei (en). Verwenden Sie alle, um alle Dateien zu extrahieren. Um die Liste der PTX-Dateien zu erhalten, verwenden Sie die Option - lptx. Funktioniert mit Host Executable / Objekt / Bibliothek und externem fatbin. Alle Dump - und Listenoptionen werden mit dieser Option ignoriert. --Funktion ltFunktion namegt. Geben Sie Namen von Gerätefunktionen an, deren dicke Binärstrukturen abgelegt werden müssen. --funktion-index ltfunktion indexgt. Geben Sie den Symboltabellenindex der Funktion an, deren dicke Binärstrukturen entleert werden müssen. --gpu-architecture ltgpu architecture namegt GPU-Architektur angeben, für die Informationen gedumpt werden sollen. Zulässige Werte für diese Option: sm20, sm21, sm30, sm32, sm35, sm37, sm50, sm52, sm53, sm60, sm61. Diese Hilfeinformationen für dieses Tool ausdrucken. Auflisten aller ELF-Dateien, die im fatbin verfügbar sind. Funktioniert mit Host Executable / Objekt / Bibliothek und externem fatbin. Alle anderen Optionen werden mit diesem Flag ignoriert. Dies kann verwendet werden, um bestimmte ELF mit - xelf Option später auszuwählen. Liste aller PTX-Dateien, die im fatbin verfügbar sind. Funktioniert mit Host Executable / Objekt / Bibliothek und externem fatbin. Alle anderen Optionen werden mit diesem Flag ignoriert. Mit dieser Option können Sie die PTX-Option mit der Option - xptx später auswählen. Geben Sie Befehlszeilenoptionen aus der angegebenen Datei ein. Nvdisasm extrahiert Informationen aus eigenständigen Cubin-Dateien und präsentiert sie in einem lesbaren Format. Die Ausgabe von nvdisasm enthält CUDA-Baugruppencode für jeden Kernel, Auflistung von ELF-Datensektionen und anderen CUDA-spezifischen Abschnitten. Ausgabeart und Optionen werden über die Befehlszeilenoptionen nvdisasm gesteuert. Nvdisasm kontrolliert auch die Flußanalyse, um Sprung - / Zweigziele zu annotieren und macht die Ausgabe leichter zu lesen. Anmerkung: nvdisasm benötigt vollständige Relocation-Informationen, um die Kontrollflussanalyse durchzuführen. Wenn diese Informationen in der CUDA-Binärdatei fehlen, verwenden Sie entweder die nvdisasm - Option - ndf, um die Kontrollflussanalyse zu deaktivieren, oder verwenden Sie die ptxas - und nvlink-Option - preserve-relocs, um die cubin-Datei erneut zu generieren. Eine Liste der CUDA-Installationsanweisungen der einzelnen GPU-Architekturen finden Sie unter Instruction Set Reference. Nvdisasm akzeptiert eine einzelne Eingabedatei jedes Mal, wenn sie ausgeführt wird. Die grundlegende Verwendung ist wie folgt: Um das Steuerelement Flussdiagramm eines Kernels zu erhalten, verwenden Sie Folgendes: Heres eine Beispiel-Ausgabe von nvdisasm. Nvdisasm in der Lage ist, einen Steuerfluß der CUDA-Anordnung im Format der DOT-Graphbeschreibungssprache zu erzeugen. Die Ausgabe des Steuerflusses von nvdisasm kann direkt in ein DOT-Diagramm-Visualisierungswerkzeug wie Graphviz importiert werden. Hinweis: Diese Funktion wird nur für Cubins unterstützt, die für Compute Capability 3.0 und höher generiert wurden. Heres, wie Sie ein PNG-Bild (cfg. png) des Kontrollflusses des obigen Cubin (a. cubin) mit nvdisasm und Graphviz erzeugen können: Heres den erzeugten Graphen: Abbildung 1. Control Flow Graph nvdisasm ist in der Lage, das Register ( CC, Allgemeines und Prädikat). Für jede Zeile der CUDA-Baugruppe zeigt nvdisasm an, ob ein bestimmtes Gerät registriert, zugegriffen, live oder neu zugeordnet wurde. Außerdem wird die Gesamtzahl der verwendeten Register angezeigt. Dies ist nützlich, wenn der Benutzer an der Lebensdauer eines bestimmten Registers interessiert ist, oder registrieren Sie die Nutzung im Allgemeinen. Hinweis: Diese Funktion wird nur für Cubins unterstützt, die für Compute Capability 3.0 und höher generiert wurden. Heres eine Beispielausgabe (linke Spalten werden weggelassen): Tabelle 3 enthält die unterstützten Befehlszeilenoptionen von nvdisasm. Zusammen mit einer Beschreibung, was jede Option tut. Jede Option hat einen langen Namen und einen kurzen Namen, der austauschbar verwendet werden kann. Tabelle 3. nvdisasm Befehlszeilenoptionen nvprune akzeptiert bei jeder Ausführung eine einzelne Eingabedatei und gibt eine neue Ausgabedatei aus. Die grundlegende Verwendung lautet wie folgt: Die Eingabedatei muss entweder ein umsetzbares Hostobjekt oder eine statische Bibliothek (nicht eine ausführbare Programmdatei für den Host) sein, und die Ausgabedatei ist dasselbe Format. Entweder die Option --arch oder --generate-code muss verwendet werden, um das / die Ziel (e) festzulegen. Alle anderen Geräte-Code wird aus der Datei verworfen. Die Ziele können entweder ein smNN Bogen (cubin) oder computeNN Bogen (ptx) sein. Zum Beispiel wird das folgende libcublasstatic. a beschneiden, um nur sm35 cubin anstatt alle Ziele zu enthalten, die normalerweise vorhanden sind: Beachten Sie, dass das bedeutet, dass libcublasstatic35.a auf keiner anderen Architektur ausgeführt wird, also sollte nur verwendet werden, wenn Sie erstellen Eine einzige Architektur. Tabelle 7 enthält unterstützte Befehlszeilenoptionen von nvprune. Zusammen mit einer Beschreibung, was jede Option tut. Jede Option hat einen langen Namen und einen kurzen Namen, der austauschbar verwendet werden kann. Tabelle 7. nvprune Befehlszeilenoptionen ALLE NVIDIA BAUVORSCHRIFTEN, Referenzboards, FILES, Zeichnungen, DIAGNOSE, Listen und anderen Unterlagen (zusammen und einzeln, MATERIALS) vorgesehen sind, wie sich es. NVIDIA ÜBERNIMMT KEINE GARANTIEN, AUSDRÜCKLICH, IMPLIZIERT, GESETZLICH, ODER ANDERWEITIG IN BEZUG AUF DIE MATERIALIEN UND SCHLIESST AUSDRÜCKLICH ALLE IMPLIZITEN GARANTIEN VON NICHTVERLETZUNG, MARKTGÄNGIGKEIT UND FITNESS FÜR EINEN BESTIMMTEN ZWECK. Es wird angenommen, dass die gelieferten Informationen korrekt und zuverlässig sind. Die NVIDIA Corporation übernimmt jedoch keine Verantwortung für die Folgen der Nutzung dieser Informationen oder für jegliche Verletzung von Patenten oder sonstigen Rechten Dritter, die sich aus ihrer Verwendung ergeben können. Eine Lizenz wird nicht gewährt, wenn ansonsten keine Patentrechte der NVIDIA Corporation bestehen. Die in dieser Druckschrift genannten Spezifikationen können ohne vorherige Ankündigung geändert werden. Diese Veröffentlichung ersetzt und ersetzt alle zuvor bereitgestellten Informationen. NVIDIA Corporation Produkte sind nicht als kritische Komponenten in Lebens-Support-Geräte oder Systeme ohne ausdrückliche schriftliche Zustimmung der NVIDIA Corporation zugelassen. Marken NVIDIA und das NVIDIA-Logo sind Marken oder eingetragene Marken der NVIDIA Corporation in den USA und anderen Ländern. Andere Firmen - und Produktnamen können Marken der jeweiligen Unternehmen sein, mit denen sie assoziiert sind. Modul binär Im Umgang mit Netzwerk-Sockets oder Binärdateien ist es notwendig, in Byte-Streams zu lesen und zu schreiben. JavaScript selbst stellt keine native Darstellung von binären Daten zur Verfügung, so dass dieses Modul zwei Klassen für dieses Manko anbietet. Die Umsetzung folgt dem CommonJS Binary / B Vorschlag. ByteArray implementiert einen modifizierbaren und veränderbaren Bytepuffer. ByteString implementiert eine unveränderliche Bytefolge. Beide Klassen haben eine gemeinsame Basisklasse Binary. Die Basisklasse kann nicht instanziiert werden. Es existiert nur zu bestätigen, dass ByteString und ByteArray Instanzen von Binary. Wird an eine Java-Methode übergeben, die ein Byte erwartet. Instanzen dieser Klassen werden automatisch ausgepackt. Klasse Binäre Klasse ByteArray Instanzmethoden Instanzeigenschaft Statische Methoden Klasse ByteString Instanzmethoden Instanzeigenschaft Statische Methoden Klasse String Instanzmethoden Abstrakte Basisklasse für ByteArray und ByteString. Der Binär-Typ existiert nur, um zu bestätigen, dass ByteString und ByteArray-Instanzen von Binary. ByteArray (contentOrLength, charset) Erstellt ein beschreibbares und wachstumsfähiges Bytearray. Wenn das erste Argument für diesen Konstruktor eine Zahl ist, legt es die Anfangslänge des ByteArray in Bytes fest. Else, das Argument definiert den Inhalt des ByteArray. Wenn das Argument ein String ist, benötigt der Konstruktor ein zweites Argument, das den Namen der Zeichenkodierung enthält. Wird ohne Argumente aufgerufen, wird ein leeres ByteArray zurückgegeben. ParametersExploring JPEG Diese Datei ist sowohl eine HTML-Datei als auch ein literiertes Haskell Programm. Wenn Sie es um. lhs umbenennen, können Sie es mit GHC 6.6 kompilieren. Dies ist ein funktionaler, wenn auch begrenzter JPEG-Decoder. Es dekodiert nur Graustufen, 8-Bit-Bilder und ist sehr empfindlich auf die verwendeten Optionen. Ich dachte, dass die Leute gerne ein wenig über den JPEG-Standard zu lernen. Ehrlich gesagt, können Sie einfach den ganzen Code überspringen, wenn Sie nicht verstehen, Haskell. Ein JPEG-Bitstrom besteht aus mehreren Segmenten. Jedes Byte in der Datei gehört zu einem Segment und jedes Segment folgt einem gemeinsamen Format: 0x01. 0xfe (Segmenttyp) Header Länge (oft eingeschlossen, aber nicht immer) Nach Header-Länge Bytes kann ein codierter Strom von Bytes kommen. Enthält der codierte Strom von Bytes eine 0xff. Ein 0x00 wird danach gefüllt, um sicherzustellen, dass die 0xff nicht mit dem Anfang des nächsten Headers verwechselt wird. Im gehend, in den ersten Teil des Codes jetzt einzusetzen, also muss ich das Modulmaterial aus dem Weg erhalten. Sie können dieses ignorieren, das sind alle Standardmodule außer BitSyntax, das ein Helfermodul für das Analysieren von binären Formaten ist. Nun, hier ist der Code, der eine JPEG-Datei und gibt eine Liste der Segmente. Es sucht nach der Segment-Startmarke und gibt eine Liste der Segmenttypen und den Körper dieses Segments zurück. Sein ein wenig lose, weil es nicht überspringen das Segment Header für die Segmente, die eine Länge haben. So ist es möglich, dass es eine Markierung in den Headern finden wird. Allerdings genügt es für unsere Bedürfnisse. An dieser Stelle stellen wir unser Testbild vor: Das Ausführen von jpegSegments auf dieser Datei gibt die folgenden Segmente aus. Segmenttyp Länge des Segments Das Segment APP0 markiert diese Datei als JFIF / JPEG. JFIF definiert einige zusätzliche Felder (wie die Bildauflösung und eine optionale Miniaturansicht) und Sie können mehr darüber erfahren Sie hier. Fast alle in der Wildnis gefundenen JPEGs werden JFIF-Dateien sein. Der Umriss der JPEG-Datei besteht darin, dass er einen Rahmen (Bild) enthält und dieser Rahmen einen von mehreren Scans aufweist. Scans können schrittweise mehr Details geben oder können unterschiedliche Achsen des Farbraums usw. sein. Für dieses Beispiel haben wir nur einen Scan und nur eine Achse des Farbraums (Luminanz, weil seine Graustufen). Die Quantisierungs - und Huffman-Tabellen werden für die Decodierung des Bildes benötigt, sodass wir zuerst die Informationen analysieren und speichern. Gut speichern die Informationen in einer Struktur: Es können viele Huffman und Quantisierung Tabellen definiert und verschiedene Scans könnten verschiedene Tabellen verwenden. So speichern wir eine Karte von Tabellen, obwohl wir nur eine erwarten. Zuerst analysieren wir die Quantisierungstabelle (das Segment heißt DQT in der JPEG-Spezifikation und youll sehen diese Triplet-Namen, die hin und wieder auftauchen). Dont worry über das, was eine Quantisierungstabelle ziemlich noch ist, gut zu dem später erhalten, sein im Grunde nur ein Array von 64 Werten. Das Quantisierungstabellensegment sieht wie aus: 0xdb (Art des Segments) Länge der Segmentpräzision in Bits 64 Elemente von u8 oder U16 Quantisierungswerte in Zickzackreihenfolge Die letzten drei Zeilen werden für die Länge des Segments wiederholt. Heres die Parsing-Code Huffman-Codierung Im gehen zu vermuten, dass Sie wissen, was Huffman-Codierung ist, wenn nicht können Sie bis zu lesen. JPEG definiert einen Modus für Huffman und arithmetische Kodierung, aber aufgrund von Patentproblemen wird nur Huffman in der Wildnis gesehen. Arithmetische Codierung ist langsamer, aber bekommt etwa 10 bessere Komprimierung für JPEG-Dateien. Die Huffman-Bäume in JPEG sind nie tiefer als 16 Elemente und sie werden mit zwei Listen kommuniziert. Die erste ist 16 Elemente lang und gibt die Anzahl der Werte bei jeder Tiefe im Baum an. (Also das erste Element gibt die Anzahl der Werte, die nur einen Schritt von der Wurzel des Baumes, die zweite die Zahl, die zwei Schritte etc. sind). Die zweite Liste ist eine Liste von Elementen in der Reihenfolge der Tiefe. Der JPEG-Standard gibt eine sehr komplexe Definition, wie die Huffman-Bäume zu konstruieren sind (was ein Fall der vorzeitigen Optimierung zu sein scheint). Der eigentliche Algorithmus lautet: Starten Sie mit einem leeren Baum Für jedes Element (Element, Tiefe) in der Liste der Elemente (in der Reihenfolge): Gehen Sie die Tiefenschritte von der Wurzel, die immer bei jedem Schritt links, wenn theres Raum, dies zu tun. (Elemente sind immer an den Blättern der Bäume, also, wenn der linke Teilbaum ein Element ist oder ein Baum vollständig durch Elemente beendet ist, können Sie nicht nach links gehen) Wo Sie aufhören, legen Sie das Element Heres unsere Baumstruktur: Jeder Knoten in der Baum ist entweder Leer. Voll (ein Blattknoten, mit in Int-Element) oder ein Baum mit einer Fülle-Flagge und leftright Kinder. Das Fülle-Flag für jeden Baumknoten ist wahr, wenn keine weiteren Elemente in ihn eingefügt werden können. Das spart uns von jemals Notwendigkeit, Backtrack. Der Branch-Typ wird verwendet, um die Schritte zu speichern, die wir zum Einfügen des Elements getroffen haben. Dies ist erforderlich, wenn youre gehen, um eine schnelle Tabelle Lookup implementieren (wir arent in diesem Fall, so seine nur ein Beispiel.) Heres die Funktion für das Hinzufügen eines Elements auf den Baum. Sie können die Muster überprüfen, wenn Sie mögen: Der Aufbau eines Baumes ist dann nur ein Fall des Aufrufs huffTreeAdd für jedes (Tiefe, Element) Paar in der Huffman-Tabelle. Wir werfen den Weg von Branch es weg, aber dieses ist, wo Sie eine Nachschlagetabelle konstruieren würden, wenn Sie Geschwindigkeit wollten. Auf der Suche nach Werten aus dem Baum ist sehr einfach, gehen Sie einfach den Baum, bis Sie ein Element getroffen. Diese Funktion verwendet eine Liste von Bool s als Liste von Bits, wobei true den rechten Teilbaum enthält. Es gibt das Element und die verbleibende Liste der Bits zurück. Nun, da Sie die Theorie kennen, gibt es nur die Frage, wie die Bytes zu lesen. Denken Sie daran, dass wir nur die Liste der Längen und die Liste der Elemente benötigen, von denen die Segment-Layout ist offensichtlich: Elemente, in der Reihenfolge der Tiefe Wie die Quantisierung Tabelle werden die letzten vier Zeilen für die Länge des Segments wiederholt. Heres die Parsing-Funktion, gibt es die Klasse, id und eine Liste der (Tiefe, Element) Paare: Es gibt zwei andere Segmente, die wir analysieren müssen, der Beginn des Frames und Start des Scans. Ihre genauen Formate sind wichtig, sie enthalten, was Sie erwarten würden: Breite und Höhe des Bildes, Anzahl der Komponenten (Achse des Farbraums) und welche Tabellen für die Dekodierung verwendet werden sollen: Theres auch eine Funktion, die die Rückgabewerte dieser Parsing übersetzt Funktionen in Änderungen in der JpegState-Struktur (die wir oben definiert haben, wenn Sie sich erinnern): Die Diskrete Kosinus-Transformation Wäre fast bereit zu starten Dekodierung des Herzens von JPEG - die DCT. Beim Codieren eines Bildes wird es in 8 × 8 Quadrate von Pixeln aufgeteilt. Jedes dieser Quadrate wird dann über die DCT (die vollständig reversibel ist) transformiert. Sie können an die DCT als Änderung der Pixel von der Zeit in den Frequenzbereich denken: Die Zeit vs Amplituden-Diagramm einer Wellenform ist im Zeitbereich und die Frequenz vs Amplituden-Diagramm (wie Sie auf einem grafischen Equalizer sehen) ist in der Frequenz Domain. Das DCT ist vollständig reversibel. Sie können auch die DCT als eine Funktion, die ein 8x8 Pixel-Raster in eine lineare Kombination der folgenden, grafische Basis-Funktionen verwandelt denken: Es gibt 64, 8x8 Bilder gibt. Die obere linke ist die DC-Komponente und alle übrigen sind AC-Komponenten. Hoffentlich können Sie sehen, dass ein solider 8x8 Block ist immer etwas Bruchteil der linken oberen Fliese. Wenn der 8x8 Block nach unten verschwindet, dann fügen Sie ein wenig von der Fliese gerade unten hinzu. Es kann nicht offensichtlich sein, dass alle 8x8 Pixelblöcke als lineare Kombinationen dieser Fliesen dargestellt werden können, aber es ist wahr. Die DC-Komponente ist die Cosinuswelle, die keine Zyklen über dem 8 × 8-Block macht. Eine Fliese links, das ist die Cosinuswelle, die einen halben Zyklus über den Block macht. Nächste Fliese entlang macht einen Zyklus über den Block und so weiter. Nach links gehen erhöht die horizontale Frequenz und nach unten steigt die vertikale Frequenz. Das menschliche Auge hat jedoch unterschiedliche Empfindlichkeiten bei diesen Frequenzen. Die meisten Hochfrequenzfliesen können ohne merkliche Wirkung weggeworfen werden. Dieses Wegwerfen wird verwaltet, indem die Amplitude jeder Fliese durch eine feste Zahl in der Hoffnung aufgeteilt wird, dass viele von ihnen auf Null gehen. Je größer der Divisor, desto niedriger die Qualität und die Divisoren werden gewichtet, so dass die hochfrequenten Komponenten durch eine größere Zahl geteilt werden. Die Liste der Divisoren ist die Quantisierungstabelle, die wir vorher behandelt haben. Sie können die DCT durch Korrelation der Pixelwerte mit den Cosinusfunktionen durchführen, die für den 1-d-Fall die gleiche wie die Multiplikation mit der DCT-Matrix ist: Da jede Zeile orthogonal ist, ist die Umkehrung dieser Matrix nur ihre Transponierung. Wir stellen eine Matrix als eine Liste von Zeilen dar, wobei jede Zeile eine Liste von Werten ist. Dies ermöglicht es uns, die inverse 8x8 DCT-Matrix zu definieren: DCT-Codierung Jetzt verstehen wir, dass zur Decodierung des Bildes nur die DCT-Koeffizienten für jeden 8x8-Block benötigt werden, multipliziert mit der Quantisierung Tabelle und führen Sie die inverse DCT, um die Pixelwerte zu erhalten. Die weitere Frage ist, wie die DCT-Koeffizienten zu dekodieren. Zuerst werden die 8 · 8 Blöcke in Rasterreihenfolge (von links nach rechts, von oben nach unten) codiert, und wenn das Bild keine Ganzzahl von 8 · 8 Blöcken ist, werden die am weitesten rechts liegenden und untersten Pixel wiederholt, um es so zu machen. Für jeden 8 × 8-Block wird der DC-Koeffizient zuerst unter Verwendung einer Huffman-Tabelle codiert, und die AC-Koeffizienten folgen (alle 63 von ihnen) unter Verwendung einer anderen Huffman-Tabelle (und eines anderen Codierungsschemas). Sowohl DC als auch AC verwenden das gleiche Integer-Codierungsschema, was jedoch als nächstes gut abdeckt: Eine Ganzzahl wird von Huffman kodiert, der seinen Bereich kodiert, und dann zwei Komplement, das den Offset innerhalb dieses Bereichs kodiert. Die Bereiche sehen folgendermaßen aus: SSSS (Bereichsnummer) Der SSSS-Wert ist sowohl das Element des Huffman-Baums (für DC) als auch die Anzahl der nachlaufenden Bits. Wenn Sie also 3 aus dem Huffman-Baum decodieren, nehmen Sie die folgenden drei Bits aus dem Bitstrom. Das erste dieser Bits ist wahr, wenn youre Zählung von der Unterseite des positiven Teilbereichs und false, wenn youre Zählung von der Unterseite des negativen Teilbereichs. Die letzten zwei sind die Zahl, die auf diesen Basiswert addiert wird: Der DC-Wert jedes DCT-Blocks wird als die Differenz von dem DC-Wert des letzten blockierten decodiert codiert. (Der DC-Wert des Blocks vor dem ersten wird als 0 angenommen). Dieser Code tut nicht den Unterschied, es decodiert nur den Wert: AC-Werte sind ein wenig anders. Erstens, theres 63 von ihnen in jedem Block und es gibt in der Regel lange Läufe von Nullen zwischen Werten. Somit dekodiert der AC-Huffman-Baum zwei Werte: einen SSSS-Wert (in den unteren 4 Bits) wie oben und einen RRRR-Wert (in den oberen vier Bits), der die Anzahl der vorhergehenden Nullen ergibt. Wenn also der AC-Huffman-Baum einen Wert von 0x53 decodiert, ergibt dies SSSS 3 und RRRR 5 und ergibt die Werte 0, 0, 0, 0, 0, x. Wobei x im Bereich -31 ..- 16,16..31 liegt. Sie decodieren die folgenden drei Bits, um den exakten Wert für x zu finden. Es gibt auch spezielle Codes für 16 Nullen (0x00) und die AC-Werte für diesen Block (0xf0) zu beenden, da Blöcke oft mit vielen Nullwerten enden. Offensichtlich funktioniert die Huffman-Kodierung der AC-Werte am besten, wenn es viele Läufe von Nullen und viele Nullen am Ende gibt. Hochfrequente Komponenten werden wahrscheinlich auf Null quantisiert, aber wenn wir die AC-Werte in Rasterreihenfolge annehmen, sind die höchsten Frequenzkomponenten immer am Ende. Somit werden die AC-Werte in Zickzack-Reihenfolge codiert. Heres ein Diagramm: Und heres den Code: Wir sind, endlich bereit, das JPEG zu decodieren, aber zuerst muss ich einige Helfer-Funktionen aus dem Weg zu bekommen. Die ersten beiden (groupSize und d8ru) sind trivial. Die letzten BytesToBits konvertieren einen Block von Speicherdaten in eine Liste von Bits, wobei die gefüllten 0x00 nach jedem 0xff entfernt werden (denken Sie daran, dass diese gefüllten Bytes gesetzt werden, um die Huffman-Daten zu stoppen, die wie der Anfang des nächsten Segments aussehen). Nun die Decodierfunktion. Ive addiert Anmerkungen am Ende von einigen der Linien, also können Sie entlang folgen. Zuerst haben wir herausgefunden, wie viele DCT-Blöcke es gibt (0), dies ist nur die Anzahl der Blöcke in einer Reihe mal die Anzahl der Zeilen über die Höhe eines Blocks. Als nächstes decodieren wir das DC-Delta und addieren es zum letzten DC-Wert (2). Dann können wir alle AC-Werte (3) decodieren und alle DCT-Koeffizienten mit der Quantisierungstabelle (4) multiplizieren. Schließlich dezigzag und führen die inverse DCT (5) und wurden getan. Dies geschieht für jeden DCT-Block in der Datei und wir am Ende mit einer Liste von 8x8, Pixel-Wert-Matrizen. Diese Pixelwerte laufen von -128 bis 128 (weil die DCT besser arbeitet, wenn sie symmetrisch um 0 sind). Aufgrund einiger Rundungsfehler können die tatsächlichen Werte diesen Bereich überschreiten und müssen daher zur Ausgabezeit geklemmt werden. Finishing up Die letzte Funktion gibt die Liste der Pixelwertmatrizen in einer PGM-Datei aus. Wir gehen jede Reihe von DCT-Werten achtmal, um jede Abtastzeile (0) zu erhalten, und für jeden Pixelwert addieren wir 128 und klemmen den Bereich (1). Und dann theres die Hauptfunktion, die nur ein Beispiel Decoder, der aus test. jpeg liest und schreibt zu out. pgm. Heres das Ergebnis (umgewandelt in ein PNG):
No comments:
Post a Comment