Bitweise aus einem BYTE Array lesen



  • Hallo,

    ich bin gerade auf der suche nach einer Möglichkeit bitweise auf ein BYTE Array zuzugreifen.
    Folgendes Ausgangsszenario:
    Ich habe eine Kamera die 10, 12, oder 14Bit Bilder produziert. Sprich die Pixel haben einen Wertebereich von 2^10, 2^12 oder 2^14. Das "Bild" bzw. der Speicher wird mittels eine Netzwerkklasse an einen Klienten verschickt. Auf der gegenüberliegenden Seite möchte ich jetzt gerne das Bild für die Ausgabe (nicht für die weitere Verarbeitung!) herunterrechnen. Ich weis an dieser Stelle welche Auflösung mein Bild hat und welche BPP Einstellung vorlag.
    Demnach sollte es möglich sein das Bild auf 8 Bit runter zu skalieren.

    Bsp.: Mein Bild hat eine Auflösung von 320x240 und wurde mit 12BPP aufgezeichnet. Daraus ergibt sich mein BYTE Array mit 320*240*12/8 = 115200 Bytes.
    Ich würde jetzt gerne immer 12 Bits auslesen, diesen Wert auf den Wertebreich von 8Bit skalieren, speichern und die nächsten 12Bits auslesen bis ich die 115200 Bytes durchhabe.

    Und am ende sollte dann ein 8Bit Bild entstanden sein mit der Größe 76800.

    Soweit, so einfach. Dachte ich jedenfalls 🙂
    Wie kann ich auf meine Bits zugreifen? Ich habe google und auch dieses Forum bemüht und so richtig erkenne ich nicht wie es funktioniert. Ich las auch was von BitMasken, aber auch damit kam ich nicht so recht klar.

    Bin über jeden denkanstoß dankbar!
    gruß teddds


  • Mod

    Du kannst nicht auf Bits zugreifen. Bits sind nur virtuell. Ein Byte ist per Definition die kleinste adressierbare Einheit im Computer. Daher muss man sich ausrechnen, wie die Bits wohl aussehen würden. Da sind Bitmasken schon das richtige Stichwort. Wenn du 12 Bits lesen möchtest, kannst du beispielsweise 3 Bytes = 24 Bits = 2 x 12 Bits lesen. Dann nimmst du von dieser 24-Bit Zahl einmal die ersten 12 und einmal die letzten 12 Bit. Das machst du, indem du die Zahl einmal mit der Zahl 0xFFF (das sind im Binärsystem 12 Einsen) verUNDest (bitweises UND, also &, nicht das logische UND &&) und ein zweites Mal mit der Zahl 0xFFF000 (das sind Binär 12 Einsen gefolgt von 12 Nullen). Bei letzterem musst du das Ergebnis anschließend noch mit dem Shift-Operator (>>) 12 Stellen nach rechts schieben. Die Zahlen 0xFFF und 0xFFF000 nennt man die Bitmasken.

    Warum funktioniert das? Denk dir ein Byte b10011101 (=0x9D) und du möchtest die ersten 4 und die letzten 4 Bits wissen. Damit errechnet man sich die Masken b00001111 = 0xF und b11110000 = 0xF0. Jetzt die VerUNDung:

    b10011101
    &b00001111
    ----------
     b00001101 = 0xD
    

    Das ist schon das Ergebnis für die letzten 4 Bit. Man muss das Ergebnis also nicht mehr shiften. Oder allgemeiner formuliert, man braucht nur um 0 Stellen zu shiften.
    Die ersten 4 Bit gehen so:

    b10011101
    &b11110000
    ----------
     b10010000 = 0x90
    

    Da wollen wir aber b1001 haben. Also noch 4 Stellen nach rechts shiften, dann stimmt es.



  • Vielen Dank für die schnelle und ausführliche Antwort.

    Bleiben wir aber mal beim konkreten Beispiel (12BPP 320x240):

    BYTE* CamClient::convertTo8Bpp(BYTE* image, int imageSize, BPP currentBPP)
    {
    	if(currentBPP == _8)
    	{
    		return image;
    	}
    
        int bpp = toInt(currentBPP);
    	int _8bppSize = imageSize /  bpp * 8;
    	BYTE* image8bpp = new BYTE[_8bppSize];
    
    	//Masken für bit lesen 
    	//(10 Bit -> Lesen 80Bytes = 640Bit = 64*10Bit
    	// to do ->Bitmaske mit 64 Einträge
    
    	//(12 Bit -> Lesen 3Bytes = 24Bit = 2*12Bit)
    	unsigned char mask12[] = { 0xFFF, 0xFFF000 };
    
    	//(14 Bit -> Lesen 112Bytes = 896Bit = 64*14Bit)
    	//to do ->Bitmaske mit 64 Einträge :(
    
    	//mögliche Zustände
    	int total = 256;
    	if(currentBPP == _14)
    	{ total = 16384; }
    	else if(currentBPP == _12)
    	{ total = 4096; }
    	else if(currentBPP == _10)
    	{ total = 1024; }
    
    	//total = 100% = 256 bei 8bpp
    	int itImage8bpp = 0;
    	for(int i=0; i<imageSize; i+=3)
    	{
    		// 12 Bit Szenario -> Lesen von 3 Bytes 
    		int firstByte = image[i] + image[i+1];
    		int secondByte = image[i+1] + image[i+2];
    
    		//Maske daruf anwenden
    		int firstByteVal = (firstByte & mask12[0]) << 12;
    		int secondByteVal = secondByte & mask12[1];
    
    		//Skalieren auf 8 Bit
    		image8bpp[itImage8bpp] = firstByteVal/total * 256;
    		image8bpp[itImage8bpp+1] = secondByteVal/total * 256;
    		itImage8bpp +=2;
    	}
    
    	delete [] image;
    	return image8bpp;
    }
    

    Ich kann es leider nicht direkt testen. Ist aber der allgemeine Ansatz richtig? Auch das Shifting? Bin mir total unsicher ob da wirklich das raus kommt was ich haben will. Auch das Lesen von 3Bytes und dem anwenden der 12 Bit Maske verwirrt mich ein wenig. Ist die Addition zweier 8Bit Werte überhaupt ein 24Bit wert und damit legitim, also

    int firstByte = image[i] + image[i+1];
    

    ?


  • Mod

    Nein, int firstByte = image[i] + image[i+1]; ist doch nur die Addition. Du musst die Werte vorher schon an die richtige Stelle shiften und dann erst addieren:

    int alles_zusammen = (image[i+0] << 16) + (image[i+1] << 8) + (image[i+2] << 0);
    

    Darauf wendest du dann die Masken an.

    Noch ein paar Anmerkungen:

    • new[]i st doof. new[] ist std::vector, aber mit vielen Nachteilen.
    • Deine Masken passen nicht in einen unsigned char.
    • Pass auf in welche Richtung du shiftest. Nach dem anbringen der Maske willst du das Ergebnis wohl eher nach rechts geshiftet haben.
    • Du brauchst auch nicht alle Fälle einzeln zu programmieren. Ich war in meinem Beispiel so konkret, damit du es verstehst. Aber man kann die Prozedur auch ganz allgemein aufschreiben. Mach dir mal Gedanken, wie ich wohl zu den konkreten Zahlen in meinem Beispiel komme und wie diese mit den Vorgaben (im Beispiel 12 Bit Eingabe, ein Byte hat 8 Bit, 8 Bit Ausgabe erwünscht) zusammenhängen. Dann verstehst du auch viel besser den Algorithmus, ich habe nämlich den Eindruck, so ganz hat es noch nicht Klick gemacht.


  • Vielen Dank nochmal für deine Ratschläge.

    Ich hoffe ich habe es nun verstanden und richtig umgesetz.

    BYTE* CamClientStart::convertTo8Bpp(BYTE* image, int imageSize, BPP currentBPP)
    {
    	if(currentBPP == _8)
    	{
    		return image;
    	}
    
    	int bpp = toInt(currentBPP);
    	int _8bppSize = imageSize /  bpp * 8;
    	BYTE* image8bpp = new BYTE[_8bppSize];
    
    	//Masken für bit lesen 
    	vector<int> mask;
    
    	//mögliche Zustände
    	int total = 256;
    	int readBytes = 0;
    	int shiftVal = bpp;
    	int maskSize = 0;
    	if(currentBPP == _14)
    	{ 
    		total = 16384;
    		readBytes = 7;
    		mask.push_back( 0x3FFF);				// 1 x 14 Bit -> 14
    		mask.push_back( 0xFFC000);				// 2 x 14 Bit -> 28
    		mask.push_back( 0x3FFF0000000 );		// 3 x 14 Bit -> 42
    		mask.push_back( 0xFFFC0000000000 );	// 4 x 14 Bit -> 56 -> == 7 Bytes!!!!
    	}
    	else if(currentBPP == _12)
    	{ 
    		total = 4096;
    		readBytes = 3;
    		mask.push_back( 0xFFF );				// 1 x 12 Bit -> 12
    		mask.push_back( 0xFFF000 );				// 2 x 12 Bit -> 24 -> == 2 Bytes!!!!
    	}
    	else if(currentBPP == _10)
    	{ 
    		total = 1024; 
    		readBytes = 5;
    		mask.push_back( 0x3FF );				// 1 x 10 Bit -> 10
    		mask.push_back( 0xFFC00 );				// 2 x 10 Bit -> 20
    		mask.push_back( 0x3FF00000 );   		// 3 x 10 Bit -> 30
    		mask.push_back( 0xFFC0000000 );			// 4 x 10 Bit -> 40 -> == 5 Bytes
    	}
    
    	int alle_zusammen = 0;
    
    	//total = 100% = 256 bei 8bpp
    	int itImage8bpp = 0;
    	int newVal = 0;
    	for(int i=0; i<imageSize; i+=readBytes)
    	{
    		// Die X (== 5 || 3 || 7) Bytes miteinander verknüpfen
    		for(int j=readBytes; j>0; j--)
    		{
    			alle_zusammen += (image[i+( readBytes-j )] << (j-1)*8);
    		}
    
    		//Maske darauf anwenden
    		for(int j=0; j<(int)mask.size(); j++)
    		{
    			newVal = (alle_zusammen & mask[j]) >> shiftVal*((int)mask.size()-j-1);
    
    			//Skalieren auf 8 Bit
    			image8bpp[itImage8bpp+j] = newVal/total * 256;
    		}
    
    		itImage8bpp +=maskSize;
    	}
    
    	delete [] image;
    	return image8bpp;
    }
    

    Allerdings habe ich jetzt noch das Problem das meine Werte bei den Masken von 10 und 14 Bit nicht mehr in den Wertebereich von Int hineinpassen. Ich könnte jetzt zwar aus dem 32 Bit int eine 64Bit Zahl machen. Aber ich weiß nicht inwiefern das Einfluss auf den Und-Operator bei (alle_zusammen & mask[j]) hat?! Sind die Masken denn überhaupt richtig? Kann man nicht auch einfach eine Schleife schreiben die die Masken automatisch füllt?

    EDIT: Was ist an

    BYTE* image = new BYTE[Size]; 
    delete [] image
    

    falsch? Ich dachte so wird der Speicher reserviert und wieder freigegeben und laut Task Manager passiert das auch. Ich bitte um Nachhilfe für mich 😉


  • Mod

    Berechnen der Masken: Das ist jeweils (2 hoch N) - 1 und dann um 0*N, 1*N, 2*N, usw. Stellen nach links geshiftet.

    Zu 14 und 10: 64 Bits würden da auch nichts helfen. Was du tun kannst (Beispiel 10 Bit): Du liest wie gehabt 32 Bit ein. Dann holst du beim ersten Durchgang 3 10-Bit Werte daraus und merkst dir noch die letzten 2 Bit die übrig bleiben. Beim nächsten Durchgang liest du zuerst noch 8 Bit, verrechnest diese mit den 2 und dann noch 2*10 Bit, merkst dir die 4 Bit. Beim nächsten Durchgang, ... den Rest kannst du dir sicherlich denken.

    Das kannst du auch für 12 machen, dann kannst du für alle Fälle bei 32 Bit bleiben.

    Wie schon gehabt: Versuch das obige konkrete Beispiel mal allgemein zu formulieren, damit du es richtig verstehst. Dann musst du deinen dabei entstandenen Pseudocode nur noch nach C++ übersetzen und hast schon das fertige Programm.

    Deinen neuen Code habe ich bloß überflogen, aber mir sind keine Fehler aufgefallen.

    Warum new[] falsch ist? Weil man es falsch machen kann. Bei vector kann gar nichts schief gehen und es hat keine Nachteile.



  • Habe das Problem dank der super Hilfe von SeppJ lösen können.

    Falls also mal jemand ein ähnliches Problem hat. Hier der funktionierenden Code für 10, 12 oder 14Bit Bilder die auf 8Bit per Pixel skaliert werden sollen. Statt dynamisch neue Masken zu erstellen, damit die auf mein 32 Bit breites Int passen, arbeite ich hier mit festen Masken zum "verunden" und feste Masken für die Restbits. Danach verschiebe ich nur noch die Bits innerhalb der 32Bit Int. Man kann das natürlich auch anderesherum lösen. Aber für meinen Fall war das die schnellere, weil einfachere, Methode.

    Ich möchte mich noch mal ausdrücklich bei SeppJ bedanken. Und kann Ihm versichern, das ich jetzt das Problem und die Logik dahinter 100%ig verstanden habe. Auch dank der Hilfe einiger A4 Seiten händischer Schreibarbeit 😉

    int d2i(double d)
    {
      return d<0?d-.5:d+.5;
    }
    
    BYTE* convertTo8Bpp(BYTE* image, int imageSize, int currentBPP, int& resultSize)
    {
    	if(currentBPP == 8)
    	{
    		return image;
    	}
    
    	int bpp = currentBPP;
    	int _8bppSize = resultSize = (int)((float)imageSize /  (float)bpp * (float)8);
    	BYTE* image8bpp = new BYTE[_8bppSize];
    
    	//Masken für bit lesen 
    	vector<unsigned int> mask;
    	vector<int> rest;
    
    	//mögliche Zustände
    	int total = 256;
    	if(currentBPP == 14)
    	{  
    		total = 16384;
    		rest.push_back(0);
    		rest.push_back(2);
    		rest.push_back(4);
    		rest.push_back(6);
    		mask.push_back( 0xFFFC0000 );			// die ersten 14 Bit
    		mask.push_back( 0x3FFFF );				// die restlichen 18 Bit
    	}
    	else if(currentBPP == 12)
    	{ 
    		total = 4096;
    		rest.push_back(0);
    		rest.push_back(4);
    		mask.push_back( 0xFFF00000 );			// die ersten 12 Bit
    		mask.push_back( 0xFFFFF );				// die restlichen 20 Bit
    	}
    	else if(currentBPP == 10)
    	{ 
    		total = 1024; 
    		rest.push_back(0);
    		rest.push_back(6);
    		rest.push_back(4);
    		rest.push_back(2);
    		mask.push_back( 0xFFC00000 );			// die ersten 10 Bit
    		mask.push_back( 0x3FFFFF );				// die restlichen 22 Bit
    	}
    
    	int pixel = 0;
    	unsigned int bytes_zusammen = 0;
    	int itRest = 0;
    	int itImage8bpp = 0;
    	for(int i=0; i<imageSize; i++)
    	{
    		if(rest[itRest] + 8 < bpp)
    		{
    			//Lesen von zwei Bytes
    			bytes_zusammen += (image[i] << (24 - rest[itRest])) + (image[i+1] << (16 - rest[itRest]));
    			i++;
    		}
    		else
    		{
    			//Lesen von einem Byte
    			bytes_zusammen += (image[i] << (24 - rest[itRest]));
    		}
    
    		//Pixel mittels Maske[0] ermitteln und nach Rechts verschieben
    		pixel = (bytes_zusammen & mask[0]) >> (32-bpp);
    		image8bpp[itImage8bpp] = d2i((double)pixel/(double)total * (double)256);
    
    		//RestBits mittels Maske[1] ermitteln und an den Anfang verschieben
    		bytes_zusammen = (bytes_zusammen & mask[1]) << bpp;
    
    		itImage8bpp ++;
    		itRest++;
    
    		if(itRest == (int)rest.size())
    		{ itRest = 0; bytes_zusammen = 0;}
    	}
    
    	delete [] image;
    	return image8bpp;
    }
    

Anmelden zum Antworten