Industriële fabricage
Industrieel internet der dingen | Industriële materialen | Onderhoud en reparatie van apparatuur | Industriële programmering |
home  MfgRobots >> Industriële fabricage >  >> Industrial programming >> VHDL

BMP-bestand bitmapafbeelding lezen met TEXTIO

Het converteren van het afbeeldingsbestand naar een bitmap-indeling zorgt voor de gemakkelijkste manier om een ​​afbeelding te lezen met VHDL. Ondersteuning voor het BMP-rasterafbeeldingsbestandsformaat is ingebouwd in het Microsoft Windows-besturingssysteem. Dat maakt BMP een geschikt beeldformaat voor het opslaan van foto's voor gebruik in VHDL-testbanken.

In dit artikel leert u hoe u een binair afbeeldingsbestand zoals BMP leest en de gegevens opslaat in dynamisch geheugen in de simulator. We zullen een voorbeeldbeeldverwerkingsmodule gebruiken om de afbeelding naar grijswaarden te converteren, dit zal ons te testen apparaat zijn (DUT). Ten slotte schrijven we de uitvoer van de DUT naar een nieuwe afbeelding die we visueel kunnen vergelijken met het origineel.

Deze blogpost maakt deel uit van een serie over het gebruik van de TEXTIO-bibliotheek in VHDL. Lees hier de andere artikelen:

Hoe RAM vanuit een bestand te initialiseren met TEXTIO

Stimulusbestand gelezen in testbench met TEXTIO

Waarom bitmap het beste formaat is voor VHDL

De meest voorkomende bestandsindelingen voor afbeeldingen op internet zijn JPEG en PNG. Beiden gebruiken compressie, JPEG is lossy terwijl PNG lossless is. De meeste formaten bieden een vorm van compressie omdat dit de opslaggrootte van de afbeelding drastisch kan verminderen. Hoewel dit prima is voor normaal gebruik, is het niet ideaal om in een VHDL-testbank te lezen.

Om een ​​afbeelding in software of hardware te kunnen verwerken, moet u binnen uw applicatie toegang hebben tot de onbewerkte pixeldata. U wilt dat de kleur- en luminantiegegevens worden opgeslagen in een matrix van bytes, dit wordt bitmap- of rasterafbeeldingen genoemd.

De meeste bekende afbeeldingseditors zoals Photoshop of GIMP zijn op rasters gebaseerd. Ze kunnen een breed scala aan afbeeldingsindelingen openen, maar ze worden allemaal intern in de editor geconverteerd naar rasterafbeeldingen.

Je kunt dit ook in VHDL doen, maar dat zou een aanzienlijke codeerinspanning vergen omdat er geen kant-en-klare oplossingen zijn voor het decoderen van gecomprimeerde afbeeldingen. Een betere oplossing is om de testinvoerafbeeldingen handmatig te converteren naar een bitmap-indeling zoals BMP of door deze op te nemen in het script dat uw testbench start.

Het BMP-beeldbestandsformaat

Het BMP-bestandsformaat is goed gedocumenteerd op Wikipedia. Dit formaat heeft veel verschillende varianten, maar we gaan het eens worden over een aantal specifieke instellingen die het een stuk makkelijker voor ons zullen maken. Om onze invoerafbeeldingen te maken, openen we ze in Microsoft Paint, dat vooraf is geïnstalleerd met Windows. Vervolgens klikken we op Bestand → Opslaan als , selecteer Opslaan als type:24-bits bitmap (*bmp; *.dib) . Geef het bestand een naam die eindigt met het achtervoegsel .bmp en klik op opslaan.

Door ervoor te zorgen dat het bestand op deze manier wordt gemaakt, kunnen we aannemen dat de header altijd de 54 bytes lange BITMAPINFOHEADER-variant met pixelformaat RGB24 is die op de Wikipedia-pagina wordt vermeld. Bovendien geven we alleen om een ​​paar geselecteerde velden in de koptekst. De onderstaande tabel toont de koptekstvelden die we gaan lezen.

Offset (december) Maat (B) Verwacht (Hex) Beschrijving
0 2 “BM” (42 4D) ID-veld
10 4 54 (36 00 00 00) Pixel-array-offset
14 4 40 (28 00 00 00) Kopgrootte
18 4 Lees waarde Beeldbreedte in pixels
22 4 Lees waarde Afbeeldingshoogte in pixels
26 1 1 (01) Aantal kleurvlakken
28 1 24 (18) Aantal bits per pixel

De groen gemarkeerde waarden zijn de enige waar we echt naar moeten kijken omdat we weten welke waarden we kunnen verwachten in de andere kopvelden. Als u ermee instemt om elke keer alleen afbeeldingen van vooraf gedefinieerde vaste afmetingen te gebruiken, kunt u de hele koptekst overslaan en beginnen met lezen bij byte offset nummer 54 in het BMP-bestand, dat is waar de pixelgegevens zullen worden gevonden.

Desalniettemin zullen we controleren of de andere vermelde waarden zijn zoals verwacht. Het is niet moeilijk om te doen, omdat we de kop al aan het lezen zijn. Het biedt ook een beveiliging tegen gebruikersfouten, mocht u of een van uw collega's in de toekomst een afbeelding van de verkeerde codering aan de testbench leveren.

De testcase

Deze blogpost gaat over het lezen van een afbeelding uit een bestand in een VHDL-testbench, maar voor de volledigheid heb ik een voorbeeld DUT bijgevoegd. We zullen de pixelgegevens door de DUT streamen terwijl we de afbeelding lezen. Ten slotte schrijven we de resultaten naar een ander BMP-uitvoerbestand dat kan worden bekeken in uw favoriete fotoviewer.

entity grayscale is
  port (
    -- RGB input
    r_in : in std_logic_vector(7 downto 0);
    g_in : in std_logic_vector(7 downto 0);
    b_in : in std_logic_vector(7 downto 0);

    -- RGB output
    r_out : out std_logic_vector(7 downto 0);
    g_out : out std_logic_vector(7 downto 0);
    b_out : out std_logic_vector(7 downto 0)
  );
end grayscale; 

De bovenstaande code toont de entiteit van onze TU Delft. De grijswaardenmodule neemt de 24-bits RGB-gegevens voor één pixel als invoer en converteert deze naar een grijswaardenweergave die op de uitvoer wordt weergegeven. Houd er rekening mee dat de uitvoerpixel een grijstint vertegenwoordigt die nog steeds binnen de RGB-kleurruimte valt. We converteren de BMP niet naar een grijswaarden-BMP die een ander formaat heeft.

De module is puur combinatorisch, er is geen klok- of reset-ingang. Het resultaat verschijnt direct op de uitgang als er iets aan de ingang wordt toegewezen. Voor de eenvoud gebruikt de conversie naar grijswaarden een benadering met een vast punt van de luma (helderheid) waarde volgens het ITU-R BT.2100 RGB naar luma coderingssysteem.

U kunt de code voor de grijswaardenmodule en het hele project downloaden via het onderstaande formulier.

De afbeelding van de Boeing 747 die u hieronder ziet, is onze voorbeeldinvoerafbeelding. Dat wil zeggen, het is niet de daadwerkelijke BMP-afbeelding die in deze blogpost is ingesloten, dat zou niet mogelijk zijn. Het is een JPEG-weergave van de BMP-afbeelding die we in onze testbank gaan lezen. U kunt de originele BMP-afbeelding aanvragen door uw e-mailadres in het bovenstaande formulier achter te laten en u ontvangt deze meteen in uw inbox.

De testafbeelding is 1000 x 1000 pixels groot. Hoewel de code in dit artikel zou moeten werken met alle afbeeldingsafmetingen, zolang deze in het BITMAPINFOHEADER 24-bit BMP-formaat is. Het inlezen van een grote afbeelding kost echter veel simulatietijd omdat de toegang tot bestanden in de meeste VHDL-simulators traag is. Deze afbeelding is 2930 kB en duurt een paar seconden om in ModelSim te laden.

Importeer de TEXTIO-bibliotheek

Om te lezen van of te schrijven naar bestanden in VHDL moet u de TEXTIO-bibliotheek importeren. Zorg ervoor dat u de regels uit de onderstaande lijst bovenaan uw VHDL-bestand opneemt. We moeten ook de finish . importeren trefwoord uit het standaardpakket om de simulatie te stoppen wanneer alle tests zijn voltooid.

use std.textio.all;
use std.env.finish;

De bovenstaande verklaringen vereisen dat VHDL-2008 of nieuwer wordt gebruikt.

Aangiften van aangepast type

We zullen een paar aangepaste typen declareren aan het begin van de declaratieve regio van onze testbench. Het formaat van de datastructuren voor het opslaan van pixeldata hangt af van wat voor soort invoer de TU Delft verwacht. De grijswaardenmodule verwacht drie bytes die elk een van de kleurcomponenten rood, groen en blauw vertegenwoordigen. Omdat het op één pixel tegelijk werkt, zijn we vrij om de pixelset op te slaan zoals we willen.

Zoals we aan de onderstaande code kunnen zien, declareren we eerst een header_type array waarin we alle headergegevens zullen opslaan. We zullen enkele velden in de header onderzoeken, maar we moeten deze ook opslaan omdat we de verwerkte afbeeldingsgegevens naar een nieuw bestand gaan schrijven aan het einde van de testbench. Vervolgens moeten we de originele koptekst opnemen in de uitvoerafbeelding.

type header_type  is array (0 to 53) of character;

type pixel_type is record
  red : std_logic_vector(7 downto 0);
  green : std_logic_vector(7 downto 0);
  blue : std_logic_vector(7 downto 0);
end record;

type row_type is array (integer range <>) of pixel_type;
type row_pointer is access row_type;
type image_type is array (integer range <>) of row_pointer;
type image_pointer is access image_type;

De tweede instructie declareert een record met de naam pixel_type . Dit aangepaste type fungeert als een container voor de RGB-gegevens voor één pixel.

Ten slotte worden de dynamische datastructuren voor het opslaan van alle pixels gedeclareerd. Terwijl row_type is een onbeperkte array van de pixel_type , de row_pointer is een toegangstype ervoor, een VHDL-aanwijzer. Op dezelfde manier construeren we een onbeperkte image_type array om alle rijen pixels in op te slaan.

Dus de image_pointer type zal functioneren als een handvat voor de volledige afbeelding in dynamisch toegewezen geheugen.

Instantiëren van de TU Delft

Aan het einde van het declaratieve gebied declareren we de interfacesignalen voor de TU Delft, zoals hieronder weergegeven. De ingangssignalen worden gefixeerd met _in en de uitgangssignalen met _out . Hierdoor kunnen we ze zowel in de code als in de golfvorm gemakkelijk identificeren. De DUT wordt aan het begin van de architectuur geïnstantieerd met de signalen toegewezen via de poortkaart.

signal r_in : std_logic_vector(7 downto 0);
signal g_in : std_logic_vector(7 downto 0);
signal b_in : std_logic_vector(7 downto 0);
signal r_out : std_logic_vector(7 downto 0);
signal g_out : std_logic_vector(7 downto 0);
signal b_out : std_logic_vector(7 downto 0);

begin

DUT :entity work.grayscale(rtl)
port map (
  r_in => r_in,
  g_in => g_in,
  b_in => b_in,
  r_out => r_out,
  g_out => g_out,
  b_out => b_out
);

Procesvariabelen en bestandshandvatten

We zullen één enkel testbench-proces maken om al het lezen en schrijven van bestanden te bevatten. Het declaratieve gebied van het proces wordt hieronder weergegeven. We beginnen met het declareren van een nieuwe char_file type om het gegevenstype te definiëren dat we uit het invoerbeeldbestand willen lezen. Het BMP-bestand is binair gecodeerd; daarom willen we werken op bytes, de character typ VHDL in. Op de volgende twee regels gebruiken we het type om een ​​invoer- en een uitvoerbestand te openen.

process
  type char_file is file of character;
  file bmp_file : char_file open read_mode is "boeing.bmp";
  file out_file : char_file open write_mode is "out.bmp";
  variable header : header_type;
  variable image_width : integer;
  variable image_height : integer;
  variable row : row_pointer;
  variable image : image_pointer;
  variable padding : integer;
  variable char : character;
begin

Vervolgens declareren we een variabele die de headergegevens bevat, evenals twee integer-variabelen voor het vasthouden van de breedte en hoogte van de afbeelding. Daarna declareren we een row aanwijzer en een image wijzer. De laatste zal ons handvat zijn voor de volledige afbeelding zodra deze uit het bestand is gelezen.

Ten slotte declareren we twee gemaksvariabelen; padding van het type integer en char van het type character . We zullen deze gebruiken om waarden die we tijdelijk uit het bestand lezen op te slaan.

De BMP-header lezen

Aan het begin van de procestekst lezen we de volledige header van het BMP-bestand in de header variabele, zoals weergegeven in de onderstaande code. De header is 54 bytes lang, maar in plaats van de hardgecodeerde waarde te gebruiken, krijgen we het bereik om te herhalen door te verwijzen naar de header_type'range attribuut. U moet altijd attributen gebruiken wanneer u kunt om de constante waarden op zo min mogelijk plaatsen gedefinieerd te houden.

  for i in header_type'range loop
    read(bmp_file, header(i));
  end loop;

Daarna volgen een paar beweringen waarin we controleren of sommige van de koptekstvelden zijn zoals verwacht. Dit is een beveiliging tegen gebruikersfouten, omdat we de gelezen waarden nergens voor gebruiken, we controleren alleen of ze zijn zoals verwacht. De verwachte waarden staan ​​vermeld in deze tabel, eerder in het artikel weergegeven.

De onderstaande code toont de assert-statements, elk met een report statement dat de fout beschrijft en een severity failure statement om de simulatie te stoppen als de beweerde expressie false . is . We moeten een verhoogd ernstniveau gebruiken, want in ieder geval met de standaardinstellingen in ModelSim, zal het gewoon een foutmelding afdrukken en de simulatie voortzetten.

  -- Check ID field
  assert header(0) = 'B' and header(1) = 'M'
    report "First two bytes are not ""BM"". This is not a BMP file"
    severity failure;

  -- Check that the pixel array offset is as expected
  assert character'pos(header(10)) = 54 and
    character'pos(header(11)) = 0 and
    character'pos(header(12)) = 0 and
    character'pos(header(13)) = 0
    report "Pixel array offset in header is not 54 bytes"
    severity failure;

  -- Check that DIB header size is 40 bytes,
  -- meaning that the BMP is of type BITMAPINFOHEADER
  assert character'pos(header(14)) = 40 and
    character'pos(header(15)) = 0 and
    character'pos(header(16)) = 0 and
    character'pos(header(17)) = 0
    report "DIB headers size is not 40 bytes, is this a Windows BMP?"
    severity failure;

  -- Check that the number of color planes is 1
  assert character'pos(header(26)) = 1 and
    character'pos(header(27)) = 0
    report "Color planes is not 1" severity failure;

  -- Check that the number of bits per pixel is 24
  assert character'pos(header(28)) = 24 and
    character'pos(header(29)) = 0
    report "Bits per pixel is not 24" severity failure;

Vervolgens lezen we de velden voor de breedte en hoogte van de afbeelding uit de koptekst. Dit zijn de enige twee waarden die we daadwerkelijk gaan gebruiken. Daarom wijzen we ze toe aan de image_width en image_height variabelen. Zoals we aan de hand van de onderstaande code kunnen zien, moeten we de volgende bytes vermenigvuldigen met de gewogen macht van twee waarden om de vier-byte headervelden om te zetten in de juiste integerwaarden.

  -- Read image width
  image_width := character'pos(header(18)) +
    character'pos(header(19)) * 2**8 +
    character'pos(header(20)) * 2**16 +
    character'pos(header(21)) * 2**24;

  -- Read image height
  image_height := character'pos(header(22)) +
    character'pos(header(23)) * 2**8 +
    character'pos(header(24)) * 2**16 +
    character'pos(header(25)) * 2**24;

  report "image_width: " & integer'image(image_width) &
    ", image_height: " & integer'image(image_height);

Ten slotte drukken we de leeshoogte en -breedte af naar de simulatorconsole met behulp van de report verklaring.

De pixelgegevens lezen

We moeten uitzoeken hoeveel bytes aan opvulling er op elke regel zullen zijn voordat we kunnen beginnen met het inlezen van de pixelgegevens. Het BMP-formaat vereist dat elke rij pixels wordt opgevuld tot een veelvoud van vier bytes. In onderstaande code zorgen we hiervoor met een one-liner formule met de modulo operator op de beeldbreedte.

  -- Number of bytes needed to pad each row to 32 bits
  padding := (4 - image_width*3 mod 4) mod 4;

We moeten ook ruimte reserveren voor alle rijen pixelgegevens die we gaan lezen. De image variabele is een toegangstype, een VHDL-aanwijzer. Om het te laten verwijzen naar een beschrijfbare geheugenruimte gebruiken we de new trefwoord om ruimte te reserveren voor image_height aantal rijen in dynamisch geheugen, zoals hieronder weergegeven.

  -- Create a new image type in dynamic memory
  image := new image_type(0 to image_height - 1);

Nu is het tijd om de beeldgegevens te lezen. De onderstaande lijst toont de for-lus die de reeks pixels rij voor rij leest. Voor elke rij reserveren we ruimte voor een nieuwe row_type object waarnaar wordt verwezen door de row variabel. Vervolgens lezen we het verwachte aantal pixels, eerst de blauwe, dan de groene en tenslotte de rode kleur. Dit is de volgorde volgens de 24-bit BMP-standaard.

  for row_i in 0 to image_height - 1 loop

    -- Create a new row type in dynamic memory
    row := new row_type(0 to image_width - 1);

    for col_i in 0 to image_width - 1 loop

      -- Read blue pixel
      read(bmp_file, char);
      row(col_i).blue :=
        std_logic_vector(to_unsigned(character'pos(char), 8));

      -- Read green pixel
      read(bmp_file, char);
      row(col_i).green :=
        std_logic_vector(to_unsigned(character'pos(char), 8));

      -- Read red pixel
      read(bmp_file, char);
      row(col_i).red :=
        std_logic_vector(to_unsigned(character'pos(char), 8));

    end loop;

    -- Read and discard padding
    for i in 1 to padding loop
      read(bmp_file, char);
    end loop;

    -- Assign the row pointer to the image vector of rows
    image(row_i) := row;

  end loop;

Na het lezen van de payload voor elke regel lezen we de extra opvulbytes (indien aanwezig) en verwijderen ze deze. Ten slotte, aan het einde van de lus, wijzen we de nieuwe dynamische rij pixels toe aan het juiste slot van de image reeks. Wanneer de for-lus de image . beëindigt variabele moet pixelgegevens bevatten voor de hele BMP-afbeelding.

De TU Delft testen

De grijswaardenmodule gebruikt alleen combinatielogica, dus we hoeven ons geen zorgen te maken over klok- of resetsignalen. De onderstaande code gaat door elke pixel in elke rij terwijl de RGB-waarden naar de DUT-ingangen worden geschreven. Na het toewijzen van de invoerwaarden wachten we 10 nanoseconden om alle vertragingen van de deltacyclus binnen de DUT te laten afwikkelen. Elke tijdswaarde groter dan 0 werkt, of zelfs wait for 0 ns; vaak genoeg herhaald.

  for row_i in 0 to image_height - 1 loop
    row := image(row_i);

    for col_i in 0 to image_width - 1 loop

      r_in <= row(col_i).red;
      g_in <= row(col_i).green;
      b_in <= row(col_i).blue;
      wait for 10 ns;

      row(col_i).red := r_out;
      row(col_i).green := g_out;
      row(col_i).blue := b_out;

    end loop;
  end loop;

Wanneer het programma uit de wait-statement komt, moeten de DUT-uitgangen de RGB-waarden voor de grijswaardenkleur voor deze pixel bevatten. Aan het einde van de lus laten we de DUT-uitvoer de pixelwaarden vervangen die we uit het invoer-BMP-bestand lezen.

Het BMP-uitvoerbestand schrijven

Op dit punt zijn alle pixels in de image variabele had gemanipuleerd moeten zijn door de TU Delft. Het is tijd om de afbeeldingsgegevens naar de out_file . te schrijven object, dat verwijst naar een lokaal bestand met de naam "out.bmp". In de onderstaande code doorlopen we elke pixel in de headerbytes die we hebben opgeslagen uit het invoer-BMP-bestand, en schrijven ze naar het uitvoerbestand.

  for i in header_type'range loop
    write(out_file, header(i));
  end loop;

Na de header moeten we de pixels schrijven in de volgorde waarin we ze uit het invoerbestand lezen. De twee geneste for-loops in de onderstaande lijst zorgen daarvoor. Merk op dat we na elke rij de deallocate . gebruiken sleutelwoord om het dynamisch toegewezen geheugen voor elke rij vrij te maken. Garbage collection is alleen opgenomen in VHDL-2019, in eerdere versies van VHDL kun je geheugenlekken verwachten als je deze regel weglaat. Aan het einde van de for-loop schrijven we zo nodig opvulbytes om de rijlengte op een veelvoud van 4 bytes te brengen.

  for row_i in 0 to image_height - 1 loop
    row := image(row_i);

    for col_i in 0 to image_width - 1 loop

      -- Write blue pixel
      write(out_file,
        character'val(to_integer(unsigned(row(col_i).blue))));

      -- Write green pixel
      write(out_file,
        character'val(to_integer(unsigned(row(col_i).green))));

      -- Write red pixel
      write(out_file,
        character'val(to_integer(unsigned(row(col_i).red))));

    end loop;

    deallocate(row);

    -- Write padding
    for i in 1 to padding loop
      write(out_file, character'val(0));
    end loop;

  end loop;

Nadat de lus is beëindigd, maken we de geheugenruimte vrij voor de image variabel, zoals hieronder weergegeven. Daarna sluiten we de bestanden door file_close . te bellen op de dossierhandvatten. Dit is in de meeste simulatoren niet strikt noodzakelijk omdat het bestand impliciet wordt gesloten wanneer het subprogramma of proces wordt beëindigd. Toch is het nooit verkeerd om bestanden te sluiten als je er klaar mee bent.

  deallocate(image);

  file_close(bmp_file);
  file_close(out_file);

  report "Simulation done. Check ""out.bmp"" image.";
  finish;
end process;

Aan het einde van het testbench-proces printen we een bericht naar de ModelSim-console dat de simulatie voorbij is, met een hint van waar de uitvoerafbeelding kan worden gevonden. De finish zoekwoord vereist VHDL-2008, het is een elegante manier om de simulator te stoppen nadat alle tests zijn voltooid.

Het uitgevoerde BMP-beeld

De afbeelding hieronder laat zien hoe het bestand "out.bmp" eruitziet nadat de testbench is voltooid. Het daadwerkelijke bestand dat in deze blogpost wordt getoond, is een JPEG omdat BMP's niet geschikt zijn om in webpagina's in te sluiten, maar u kunt uw e-mailadres in het bovenstaande formulier achterlaten om een ​​zip te krijgen met het volledige project inclusief het bestand "boeing.bmp".

Laatste opmerkingen

Voor beeldverwerking in FPGA wordt vaak het YUV-kleurcoderingsschema gebruikt in plaats van RGB. In YUV wordt de luma (luminantie) component, Y, gescheiden gehouden van de kleurinformatie. Het YUV-formaat is beter afgestemd op de menselijke visuele waarneming. Gelukkig is het eenvoudig om te zetten tussen RGB en YUV.

Het omzetten van RGB naar CMYK is wat ingewikkelder omdat er geen één-op-één-pixelformule is.

Een ander alternatief bij het gebruik van dergelijke exotische coderingsschema's is om uw eigen beeldbestandsformaat uit te vinden. Sla de pixelarrays eenvoudig op in een aangepast bestandsformaat met het achtervoegsel ".yuv" of ".cmyk". Er is geen header nodig als je weet wat voor soort beeldformaat de pixels zullen hebben, ga je gang en lees het in je testbench.

U kunt altijd een softwareconversie in uw ontwerpstroom opnemen. Converteer bijvoorbeeld automatisch een PNG-afbeelding naar het BMP-formaat met behulp van standaard opdrachtregelsoftware voor het converteren van afbeeldingen voordat de simulatie begint. Lees het dan in je testbench met VHDL zoals je uit dit artikel hebt geleerd.


VHDL

  1. Hologram
  2. C# met behulp van
  3. C Bestandsverwerking
  4. Java-bestandsklasse
  5. Digitale handtekeningen gebruiken voor gegevensintegriteitscontrole in Linux
  6. Hoe RAM vanuit een bestand te initialiseren met TEXTIO
  7. Java BufferedReader:hoe een bestand in Java te lezen met voorbeeld
  8. Python JSON:coderen (dumps), decoderen (laden) &JSON-bestand lezen
  9. Verilog File IO-bewerkingen
  10. C - Koptekstbestanden
  11. Plenoptische camera