Forum: FPGA, VHDL & Co. YCrCB zu RGB, Problem mit VHDL-Mathematik


von GS (chromosoma)


Lesenswert?

Hallo,
Ich will  ein Bild aus YCrCb in RGB Format konvertieren.
Natürlich in VHDL.Ich bin mit den Datentypen einwenig durcheinander 
geraten. Der compiler spukt ein Fehler aus:
Error (10476): VHDL error at packages.vhd(28): type of identifier 
"R_int" does not agree with its usage as "natural" type

Könnt ihr bitte schauen, was ich falsch gemacht habe?

Danke
1
library ieee;
2
use ieee.std_logic_1164.all;
3
use ieee.numeric_std.all;
4
use ieee.math_real.all;
5
6
7
8
PACKAGE BODY MY IS
9
PROCEDURE YCRCB_to_RGB  (Cr,Cb,Y: IN STD_LOGIC_VECTOR(7 downto 0);SIGNAL R,G,B: OUT STD_LOGIC_VECTOR(7 downto 0))IS
10
VARIABLE R_int,G_int,B_int: REAL RANGE 0.0 to 255.0 ;
11
BEGIN
12
13
R_int:= ROUND (REAL(to_integer(unsigned(Y))) +1.4* REAL(to_integer(unsigned(Cr))-128));
14
G_int:= ROUND (REAL(to_integer(unsigned(Y)))-0.34* REAL(to_integer(unsigned(Cb))-128)-0.71*REAL(to_integer(unsigned(Cr))-128));
15
B_int:= ROUND (REAL(to_integer(unsigned(Y)))+1.7* REAL(to_integer(unsigned(Cb))-128));
16
17
R<=STD_LOGIC_VECTOR(to_unsigned(R_int,8));
18
G<=STD_LOGIC_VECTOR(to_unsigned(G_int,8));
19
B<=STD_LOGIC_VECTOR(to_unsigned(B_int,8));
20
21
22
END YCRCB_to_RGB;
23
END MY;

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Welche ist die Zeile 28?

Ich glaube nicht, dass das "to_unsigned()" mit einem real als Parameter 
zurecht kommt. Ein real muss erst noch einen Umweg über "integer()" 
machen...
So wie dort unten: 
http://www.lothar-miller.de/s9y/archives/37-DDFS-mit-BROM.html
Siehe auch dort: 
http://www.lothar-miller.de/s9y/archives/14-Numeric_Std.html

BTW: soll das mal auf Hardware laufen?

: Bearbeitet durch Moderator
von GS (chromosoma)


Lesenswert?

Danke, ich versuche mal deine Lösung.
Ja, das muss auf Hardware auch laufen. Vllt muss ich so eine 
Multiplikation lieber mit freundlichere Division ersetzen.


Zeile28 ist die, mi R_int:=......

von Fitzebutze (Gast)


Lesenswert?

Hi,

ah, ein alter Klassiker zum Haareraufen :-)
Also erst mal würde ich mir die Fixpoint-Arithmetik "roh" hinschreiben, 
also so wie sie nachher auch umgesetzt wird (im Hinblick auf die HW).
Bei YCrCb reicht typischerweise eine signed-2.14-Darstellung (1.0 ist 
0x4000).
Dann musst Du dir überlegen, wie Deine Pipeline ausschauen soll, damit 
auch alles in einem Taktzyklus per Pixel durchläuft.
Zudem sollte dein FGPA ein paar DSP-Slices haben, sonst wird's ev. noch 
kniffliger.
Und der Teil mit dem Haareraufen: Die richtigen Faktoren zu finden, wird 
extrem "lustig", je nachdem, welcher YCrCb-'Standard' verwendet wird. 
Meistens kriegt man dann auch trotz korrekter Rundung von den 24 Bit 
(siehe auch unten) einen Overflow und eine Falschfarbe rein, man baut zu 
guter Letzt also auch noch eine "Clamp"-Stage mit rein.

Noch zur Arithmetik: Multiplizierst du 16 (2.14 fractional) mit 8 bit, 
kommt 24 bit bei raus, die addierst du in der nächsten Stage irgendwann 
und schneidest schlussendlich dein Ergebnis raus, um das 
8-Bit-RGB-Resultat zu erhalten. Entweder schaffst Du da die richtige 
Wahl von Koeffizienten / Rundung in Abhängigkeit der umgekehrten 
Konversion (RGB->YUV), oder du musst eben eine Begrenzung gegen Overflow 
("Clamp") einführen. Meistens gehts ohne letzteres nicht, da so einige 
Chips kein Dithering bei den YUV-Werten machen.

Viel Glück!

von GS (chromosoma)


Lesenswert?

Hallo noch einmal,
Da REAL nur für Simulation geeignet ist, habe ich das Problem so gelöst:
1
PROCEDURE YCRCB_to_RGB  (Cr,Cb,Y: IN STD_LOGIC_VECTOR(7 downto 0);SIGNAL R,G,B: OUT STD_LOGIC_VECTOR(7 downto 0))IS
2
VARIABLE Rv,Gv,Bv: INTEGER RANGE 0 to 255:=0;
3
BEGIN
4
5
6
Rv:=to_integer(unsigned(Y)+(unsigned(Cr)-128)*(11/8));
7
Gv:=to_integer(unsigned(Y)-(11/32)*(unsigned(Cb)-128)-(23/32)*(unsigned(Cr)-128));
8
Bv:=to_integer(unsigned(Y)+(27/16)*(unsigned(Cb)-128));
9
IF(Rv<255)THEN
10
R<=STD_LOGIC_VECTOR(to_unsigned(Rv,8));
11
ELSE
12
R<=(others=>'1');
13
END IF;
14
15
IF(Gv<255)THEN
16
G<=STD_LOGIC_VECTOR(to_unsigned(Gv,8));
17
ELSE
18
G<=(others=>'1');
19
END IF;
20
21
IF(Bv<255)THEN
22
B<=STD_LOGIC_VECTOR(to_unsigned(Bv,8));
23
ELSE
24
B<=(others=>'1');
25
END IF;
26
END YCRCB_to_RGB;
Ich weiß noch nicht genau, ob es  genau das tut, was ich brauche, aber 
das lässt sich zumindest synthetisieren.

PS
Ich benutze Cyclone V SoC. Der hat so um 80 DSP-Blocks, also sollte es 
auch möglich mit floating point zu rechnen, oder? Wie genau ich diese 
DSP Block verwende habe ich aber noch nicht herausgefunden...

: Bearbeitet durch User
von Mike (Gast)


Lesenswert?

Böser Kommunist schrieb:
> PS
> Ich benutze Cyclone V SoC. Der hat so um 80 DSP-Blocks, also sollte es
> auch möglich mit floating point zu rechnen, oder?

Die Cyclone V DSP Blöcke haben keine Floating Point Fähigkeiten. Das 
wäre für diesen Anwendungsfall aber auch Overkill.

> Wie genau ich diese
> DSP Block verwende habe ich aber noch nicht herausgefunden...

So wie du das beschrieben hast könnte es schon sein das Quartus 
automatisch die Multiplier benutzt. Einfach mal in einen getakteten 
Prozess einbauen und im Synthese-Report nachschauen ob das der Fall ist. 
Mit der Division könnte es aber Probleme geben (schon wegen der 
Rundung). Ich würde das so beschreiben das sie erst am Ende durchgeführt 
wird.
1
Rv:=to_integer(unsigned(Y)+(unsigned(Cr)-128)* 11/8 );

11/8 ergibt ansonsten einfach nur 1 und aus 11/32 wird 0...

Beispiele für die Instanziierung von Multipliern findest unter 
"Inferring Multipliers and DSP Functions" in der Anleitung für Quartus.

Alternativ kannst du ja auch einmal mit dem neuen Fixed Point Package 
(für VHDL-2008) spielen:

http://www.vhdl.org/fphdl/Fixed_ug.pdf
http://www.eda-stds.org/fphdl/

Allerdings war bei meinen Versuchen das generierte Design größer und 
langsamer als das mit dem selbst programmierten Code. Hat das hier schon 
jemand einmal ernsthaft verwendet?

von Fitzebutze (Gast)


Lesenswert?

Deine oben hingeschriebene Routine mag zwar synthetisieren, aber ob 
dabei das richtige rauskommt? Das Tool kann ev. eine 
1-Latency-Matrizenmultiplikation synthetisieren, aber mit Sicherheit 
leidet darunter deine Verarbeitungsgeschwindigkeit und Du benötigst mehr 
Logik. Du solltest die Rechnung manuell in eine Pipeline aufsplitten, 
z.B.

Schritt 1: Multiplikation
Schritt 2: Addition
Schritt 3: Sättigung/Rundung
           (Slicing für RGB-Ergebnis)

Ev. musst Du noch weitere Delay-Stages einführen, wenn du die Daten 
nicht im 4:4:4-Format bekommst. Teils ist für höhere 
Verarbeitungsgeschwindigkeit auch schon so ein Delay nötig, nur um die 
lokale Logikspaghetti etwas zu entbündeln.


Böser Kommunist schrieb:
> Ich benutze Cyclone V SoC. Der hat so um 80 DSP-Blocks, also sollte es
> auch möglich mit floating point zu rechnen, oder? Wie genau ich diese
> DSP Block verwende habe ich aber noch nicht herausgefunden...

Du musst nur eine Ganzzahl-Multiplikation hinschreiben, dann 
instanziieren die Tools typischerweise einen Multiplier. Wenn die 
Bitbreite nicht reicht, werden sie einfach kaskadiert (nicht der Fall 
bei bloss 8 bit RGB). Float-Support geschieht ebenfalls über die 
Kaskadierung, aber wie oben genannt: Overkill, und bringt eine Menge 
Probleme. Besser clever runden/sättigen.

Die Matrizenwerte für die Farbraumkonvertierung würde ich gleich als 
16-Bit-Ganzzahlregister implementieren, damit du sie mit dem SoC setzen 
kannst. Auf Anhieb kriegt man nämlich in den seltensten Fällen die 
richtigen Farben..
Und den 2.14 oder 1.15-Wert (siehe auch 
http://phobos.iet.unipi.it/~pieri/EdT2/2181_Manual/Appendix_C.pdf) 
kannst Du schnell in Software berechnen.

Für 2.14 z.B. (0x4000 entspr. 1.0) ergibt sich (C oder python style)

fact = (0x4000 * f) & 0xffff)

von GS (chromosoma)


Lesenswert?

Das Bild kommt in 4:2:2 Format.

Das ganze Projekt sieht so aus, in etwa:

Beitrag "Konzept für CCD Kamera<=>VGA interfacing mit FPGA"


Die Sache mit dem Pipeline werde ich noch anschauen, noch nie so was 
gemacht. Sieht aber einfach aus:

Z.B. wie in diesem Bsp:

PS. Die Farbkonvertierung ist eigentlich egal, da meine Kamera sowieso 
S/W ist. Ich denke hier kann  einfach der Y Wert genommen werden.
Aber wenn ich schon so ein Projekt mache, dann mal richtig:)
1
 
2
PROCESS(Clk)
3
BEGIN
4
    if(rising_edge(Clk)) then
5
    --Implement the pipeline stages using a for loop and case statement.
6
    --'i' is the stage number here.
7
    --The multiplication is done in 3 stages here.
8
    --See the output waveform of both the modules and compare them.
9
        for i in 0 to 2 loop
10
            case i is
11
                when 0 => temp1 <= a*data;
12
                when 1 => temp2 <= temp1*b;
13
                when 2 => result <= temp2*c;
14
                when others => null;
15
            end case;
16
        end loop;
17
    end if;
18
END PROCESS;

: Bearbeitet durch User
von Duke Scarring (Gast)


Lesenswert?

Die foor-loop und das case-Konstrukt kannst Du weglassen:
1
PROCESS(Clk)
2
BEGIN
3
    if rising_edge(Clk) then
4
        -- stage 1
5
        temp1 <= a*data;
6
7
        -- stage 2
8
        temp2 <= temp1*b;
9
10
        -- stage 3
11
        result <= temp2*c;
12
    end if;
13
END PROCESS;

Duke

von Fitzebutze (Gast)


Lesenswert?

Hi,

sag' doch gleich, dass es nur SW ist :-)
Dann kannst du auch auf die Schnelle die '2:2'-Sache überspringen und 
die Y-Werte direkt auf je R, G, B zuweisen.

Zur Pipeline noch: hat sich für mich bewährt, die Stages in 
unterschiedliche Prozesse zu klatschen und entsprechend zu kennzeichnen. 
Solche Loop-Konstrukte würde ich eher weglassen. Meistens führt man ja 
noch ein "data valid" signal mit, was jeweils verzögert wird, dann 
wird's übersichtlich, und es wird gleich klar, wann welche Werte gültig 
sind.
1
-- Erste Stage:
2
3
s1_mul:
4
    process (clk)
5
    begin
6
        if rising_edge(clk) then
7
            s1_valid <= ce;
8
            s1_result <= signed(a) * signed(b);
9
        end if;
10
    end process;
11
12
-- Zweite Stage:
13
14
s2_acc:
15
    process (clk)
16
    begin
17
        if rising_edge(clk) then
18
            s2_valid <= s1_valid;
19
            s2_acc <= s2_acc + s1_result;
20
        end if;
21
    end process;

Bei den DSP-Units gibt es recht unterschiedliche Architekturen, die 
einen addieren recht gut und schnell mit 1-Latenz, bei manchen brauchst 
du etwas Delay, um eine Matrizenmultiplikation optimal umzusetzen. 
Entweder musst du dann mit der Pipeline "spielen", oder nimmst gleich 
die IP-Core-Generatoren, die den optimalen Code ausspucken.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.