BMP Farbtiefe reduzieren vor Verarbeitung?
-
Hallo,
ich arbeite mich gerade in das Thema Bildverarbeitung und Mustererkennung ein und habe eine grundsaetzliche Frage. Da ich ja meistens ein 32-Bit bmp habe, aber kein Farbbild brauche, war meine Idee nun dieses im ersten Schritt in ein 8-Bit Graustufenbild umzuwandeln, da die Verarbeitung des Graustufenbildes sicherlich effizienter ist. Man muss schliesslich nur 1 Byte pro Pixel verarbeiten anstatt 3 Byte bzw. 4 Byte (RGB / ARGB). Um in etwa zu zeigen was ich vorhabe:
1. Konvertierung des 32-Bit oder 24-Bit Bitmaps in 8-Bit Bitmap
2. diverse Filteroperationen zu Bildeverbesserung
3. binaerisierung mithilfe Schwellwertverfahren
4. Konvertierung in 1-Bit Bitmap
5. evtl. mehrfaches Opening & Closing
6. Connected Component Labeling
7. Berechnung von Merkmalen fuer KlassifikationHaltet auch Ihr es fuer richtig bei einer solchen Verarbeitungskette diese Farbtiefenreduzierungen vorzunehmen? Oder werden in der gaengigen BV-Praxis immer durchgaengig 32-Bit Bitmaps verarbeitet, also selbst bei Binaeroperationen?
Sollte die Antwort der ersten Frage nun "ja" lauten, dann wuerde ich meine eigentliche Frage stellen.
Danke schon mal
Tarsius
-
Tarsius schrieb:
ich arbeite mich gerade in das Thema Bildverarbeitung und Mustererkennung ein und habe eine grundsaetzliche Frage. Da ich ja meistens ein 32-Bit bmp habe, aber kein Farbbild brauche, war meine Idee nun dieses im ersten Schritt in ein 8-Bit Graustufenbild umzuwandeln, da die Verarbeitung des Graustufenbildes sicherlich effizienter ist. Man muss schliesslich nur 1 Byte pro Pixel verarbeiten anstatt 3 Byte bzw. 4 Byte (RGB / ARGB).
Um mal einfach meine Meinung kund zu tun, (obs dir hilft weiss ich net), aber es kommt doch drauf an wie du dein Bild weiter verarbeitest, wenn du das mit GetPixel und SetPixel machen willst, dann wird dir auch runterrechnen nicht viel helfen. Also meine meinung alles was Bildverarbeitung is, solltest du mit assembler und den 32 bit registern der CPU lösen oder dir mal MMX anschauen. Also wenn bei mir die Frage stände runterrechnen oder nicht würde es immer drauf ankommen was als entprodukt ebend rauskommen soll, aber soweit möglich würde ich immer mit der 32 bit quelle arbeiten (wobei ich mich jetzt bestimmt wieder zu weit raus gelehnt habe und es kritik der anderen hackelt)
Gruß
-
Danke fuer die Antwort!
Also ich wollte direkt ueber ein Zeiger und Zeigerarithmetik auf die Bytes zugreifen. Leider kann ich noch kein Assembler aber ich kann mir auch vorstellen, dass man enorm an Geschwindigkeit gewinnt. Jetzt werden sicherlich viele denken, wer sich mit Bildverarbeitung ernsthaft beschaefitgen will, der muss Assembler koennen. Sehe ich ein :(. Da es sich ja bei den BV-Algorithmen meist um mathematische Berechnungen handelt, kann man doch den rechenintensiven mathematischen Code in Assembler realisieren und den Rest in c++ MFC. Also erstmal nur die wichtigsten Operationen lernen.
Aber nun zu meinem Problem. Bei dem folgenden Code ist das Bild, das gezeichnet werden soll, schwarz. Ich wollte eigendlich ein 8-Bit Bitmap mit dem Grauwert 130 pro Pixel belegen :void Ctest1View::OnDraw(CDC* pDC) { Ctest1Doc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: Code zum Zeichnen der systemeigenen Daten hinzufügen BITMAPINFOHEADER bmpih; bmpih.biSize=40; // Breite des Bitmaps bmpih.biWidth=64; // Höhe des Bitmaps bmpih.biHeight=64; bmpih.biPlanes=1; bmpih.biBitCount=8; bmpih.biCompression=BI_RGB; bmpih.biSizeImage=0; bmpih.biXPelsPerMeter=0; bmpih.biYPelsPerMeter=0; bmpih.biClrUsed=0; bmpih.biClrImportant=0; BITMAPINFO bmpi; bmpi.bmiHeader=bmpih; BYTE* pBuffer; BYTE buf; HBITMAP hbmp; hbmp = CreateDIBSection((HDC)pDC, &bmpi, DIB_RGB_COLORS,(void **)&pBuffer,NULL,NULL); for (int i=0;i<bmpih.biWidth*bmpih.biHeight;i++) { *(pBuffer+i) = 130; } // Bitmap anzeigen CDC memDC; memDC.CreateCompatibleDC(pDC); memDC.SelectObject(hbmp); RECT rect; GetClientRect(&rect); pDC->StretchBlt(0, 0, rect.right-rect.left, rect.bottom-rect.top, &memDC, 0, 0, bmpih.biWidth, bmpih.biHeight, SRCCOPY); int iColorDepth = GetDeviceCaps(HDC(memDC),BITSPIXEL); }
iColorDepth am Ende des Codes betraegt 32 obwohl ich in der BITMAPINFOHEADER struct biBitCount auf 8 gesetzt habe. Das verstehe ich nicht. Eine andere Moeglichkeit, die ich ausprobiert habe ist folgende:
void Ctest1View::OnDraw(CDC* pDC) { Ctest1Doc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: Code zum Zeichnen der systemeigenen Daten hinzufügen CBitmap bitmap; CDC memDC; memDC.CreateCompatibleDC(pDC); int Breite = 4; int Hoehe = 2; BYTE bits8[8]={ 0, 249, 250, 252, 253, 254, 251, 255 }; bitmap.CreateCompatibleBitmap(pDC,Breite,Hoehe); bitmap.SetBitmapBits(8,&bits8); // Bitmap anzeigen CBitmap* pAltesBitmap = (CBitmap*)memDC.SelectObject(bitmap); RECT rect; GetClientRect(&rect); pDC->StretchBlt(0, 0, rect.right-rect.left, rect.bottom-rect.top, &memDC, 0, 0, Breite, Hoehe, SRCCOPY); int iColorDepth = GetDeviceCaps(HDC(memDC),BITSPIXEL); }
Bei dieser Variante wird zwar was gezeichnet, aber nicht richtig, weil das Bild auch hier 32-Bit tief ist.
Den Code habe ich von http://www.nikis.de/181/vcnet.htm
Was mache ich nur falsch?
-
wenn ich mal da schnell drüber schau, stell ich fest, das du dir ne Bitmap nach deinem ScreenDC erstellst (im 2. Beispiel). Jetzt geh ich mal davon aus, das dein rechner net schon 10 jahre alt is und du mit 32 BpP auf deiner Anzeige arbeitest, also auch deine erstellte Bitmap 32 bit is. Des weiteren kann das net wirklich gehen, da du keine Palette festlegst, also die benutzte Palette rein zufällig ist.
-
das mit der Palette war mir neu *schaem* und ich habs bis jetzt auch noch nicht so recht verstanden. Ich bin bis jetzt davon ausgegangen es gaebe einen einfachen Weg, ein Bitmap direkt im 8-Bit Format zu erstellen und anschliessend meine errechneten Grauwerte aus dem 32-Bit Bitmap zu uebertragen (erechnet ueber die Formel: Grauwert = 0,299 * Rot + 0,587 * Grün + 0,114 * Blau).
Ich hab ein bisschen gegoogelt und finde in vielen Beispielen irgendwelche Sachen mit RGBQUAD. Nur ist der Groschen irgendwie noch nicht gefallen. Was nutzt mir diese RGB-Struktur wenn ich das Bild in ein Grauwert-Format konvertieren will ?Ich will ja eigentlich nur ein Bild von 32-Bit auf 8-Bit konvertieren.
Die Palette in meinem Codebeispiel ist nur ein ueberbleibsel aus dem Beispiel von http://www.nikis.de/181/vcnet.htm, also bitte nicht wundern.Ewig her, dass ich MFC programmiert habe...
Achso, nochmal dazu dass man immer nur mit der 32-Bit Quelle arbeiten sollte. Mein Programm soll spaeter Objekte anhand formbasierender Merkmale wie Hu-Momente, Fourierdeskriptoren usw. klassifizieren. Das heisst, 90% der ganzen Berechnungen findet auf binaerer Ebene also 1-Bit statt. Die 8-Bit sind erstmal nur ein Zwischenschritt. Nur wenn ich da schon scheitere, na denn gute Nacht.
Hat jemand vielleicht ein gutes Codebeispiel?
-
8 bit bitmaps sind palettenorientiert, natürlich kannst du auch eine 32 bit farbbitmap in 32 Bit Graustufen umwandeln. Wobei sich die Frage stellt, ob du mit deiner konvertiererei wirklich besser kommst.
die RGBQUAD-Structure is nur ne hilfsstrucktur um aus dem 32 bit pixel einfach auf die farben rot, grün und blau zugreigen zu können (der 4. wert is entweder frei oder als alpha-wert genutzt)
Da ich mich mit Objekte anhand formbasierender Merkmale wie Hu-Momente, Fourierdeskriptoren usw. klassifizieren, noch net beschäftigt habe kann ich dir net weiter helfen, nur versteh ich net welcher zusammenhang bei dir binär und 1 bit hat, also 1 bit bitmaps sind doch nur schwarz/weiss. Vielleicht fehlt mir nur der zusammenhang, zu den sachen die du machen willst
-
danke fuer die schnelle Antwort!
Mit binaer meinte ich schon 1-bit also schwarz/weiss. Schau mal hier z. B.: http://et.fh-duesseldorf.de/g_fachgeb/sprach_bildver/vorlesung/SB_V_K08.pdf
Alles grau bzw. schwarz/weiss. Ich will ja keine Bilderverbesserung, Restauration, BildBEarbeitung oder aehnliches programmieren sondern Mustererkennung. Da kommt es schon vor, dass man das Bild einmal in ein schwarz/weiss Bild umwandelt und anschliessend dieses Bild noch mehrmals durchlaufen muss.
Da ich bei EINER Konvertierung eines Bildes die Berechnung Grauwert = 0,299 * Rot + 0,587 * Grün + 0,114 * Blau nur ein mal PRO Pixel durchfuehren muss (1 Zuweisung, 3 Multiplikationen, 2 Additionen) habe ich bei 1000x1000 Pixel = 6 Millionen Operationen. Das muss ich dann ja zum glueck nur ein mal machen und das Ergebnis in einem neuen Grauwert-Bmp abspeichern.
Stell dir nun den 32-Bit-Fall vor. Ich muesste bei einer bsplw. 3 maligen Anwendung eines 3x3-Filters (9 Operationen pro Pixel) auch noch jedes Pixel in ein Grauwert umwandeln, hiesse das 9x6x1000x1000 x 3 = 162 Mio.
Wenn ich aber nur ein mal nach 8-Bit konvertiere (6 Millionen Operationen) und anschliessend das Filter 3 mal anwende, hiesse das 6x1x1000x1000 x3 + 6 Mio = 24 Mio. Klingt immer noch besser als 162 Mio. Eine solche Filterung waere aber nur Teil der Vorverarbeitung und ich habe es hier nur zur Veranschaulichung erwaehnt. Da ich erst mit BV anfange wuerde mich interessieren, wie die Profis das machen.Nun aber zurueck zu deiner Antwort.
8 bit bitmaps sind palettenorientiert
Wie erzeuge ich ein 8-Bit bitmap? Wozu braucht man CPalette?
Wenn ich es ueber CreateDIBSection erzeuge und die Eigenschaft bmpih.biBitCount=8 gesetzt habe, erhalte ich trozdem ein 32-Bit bmp (zumindest ist GetDeviceCaps(HDC(memDC),BITSPIXEL) == 32). Was ist das los?
-
ich versuch mal nicht direkt auf deine fragen einzugehen, da es für mich net so leicht zu durchblicken ist, wie du was hin und her gerechnet hast(ehrlich gesagt ich nehm deine werte hin, da ich keine lust hab nachzurechnen ;-)). aber wenn du sagst das es mit compiler langwierig wird geb ich dir recht. Deswegen hat ja so ein Prozessorhersteller mal MMX entwickelt mit 128 bit also kannste gleich 4 x 32bit auf einmal bearbeiten.
Wobei jetzt eine 32bit farbbitmap in 32 bit graustufen zu konvertieren is nicht das problem. in einem anderen thread hatte einer das problem das sein bild auf dem kopf stand und er es gedreht haben wollte, und sein versuch mit getpixel udn setpixel scheiterte, aber mit der richtigen Assembler-Funktion läuft das in "echtzeit".
Oder anders gesagt das problem is weniger die verwendete bitbreite deiner daten als eher die optimierung deiner schleifen (des codes). Also die schleife die die du 9 mal für dein 3x3 array aufrufst.
Umd jetzt um auf die Palettenproblematik zurück zu kommen, 8bit per Pixel is indexbasierend, also an der stelle des Pixels, steht der index der palette die anzeigt welche farbe dein pixel hat, also ob das dann schneller geht wegen dem zusätzlichen zugriff auf die indextabelle (CPalette), weiss ich net.
Jetzt zum erstellen warum das mit bmpih.biBitCount=8 aus deiner sicht net geht, steht im zusammenhang mit dem nächsten. Also du kannst die tiefe der Bitmap nicht über dein devicekontext abfragen, die haben erstmal beide nix miteinander zu tun.
GetDeviceCaps(HDC(memDC),BITSPIXEL) == 32)
warum das 32 anzeigt is klar, weil du den DC kompatibel zu deinem Bildschirm erstellt hast und der wird bestimmt auf 32 bit laufen dein Bildschirm also ScreenDC.
-
Deswegen hat ja so ein Prozessorhersteller mal MMX entwickelt mit 128 bit also kannste gleich 4 x 32bit auf einmal bearbeiten.
Ist MMX schwer zu erlernen? Im Vergleich zu Assembler?
Oder anders gesagt das problem is weniger die verwendete bitbreite deiner daten als eher die optimierung deiner schleifen (des codes). Also die schleife die die du 9 mal für dein 3x3 array aufrufst.
Diese 9-Operationen in der SChleife sind leider notwendig. Siehe auf Seite 2 des folgenden Links bevor ich rauf und runter erklaere: http://www.vms.ei.tum.de/lehre/asb-java/download/Diskrete_Faltung.pdf
Da gibt es keine Optimierungsmoeglichkeiten.Umd jetzt um auf die Palettenproblematik zurück zu kommen, 8bit per Pixel is indexbasierend, also an der stelle des Pixels, steht der index der palette die anzeigt welche farbe dein pixel hat, also ob das dann schneller geht wegen dem zusätzlichen zugriff auf die indextabelle (CPalette), weiss ich net.
Ok, mit CPalette habe ich noch nicht gearbeitet. Hab im Netz leider nicht so viel gefunden wie ich eigentlich erwartet hatte. Hat jemand zufaellig ein Beispiel parat das er unbedingt posten moechte?
Jetzt zum erstellen warum das mit bmpih.biBitCount=8 aus deiner sicht net geht, steht im zusammenhang mit dem nächsten. Also du kannst die tiefe der Bitmap nicht über dein devicekontext abfragen, die haben erstmal beide nix miteinander zu tun. GetDeviceCaps(HDC(memDC),BITSPIXEL) == 32)
warum das 32 anzeigt is klar, weil du den DC kompatibel zu deinem Bildschirm erstellt hast und der wird bestimmt auf 32 bit laufen dein Bildschirm also ScreenDC.Klingt logisch
Eigentlich koennte ich doch auch die Grauwerte in einem 2d-Byte-Array speichern und verarbeiten. Am Ende dann fuer die Anzeige die Rueckkonvertierung nach 32-Bit :p
-
Ist MMX schwer zu erlernen? Im Vergleich zu Assembler?
Na ja, is so ähnlich wie assembler und bei intel gabs mal so ein kleines programm wo die paar befehle die es gibt, erklärt werden, aber ob es das bei denen noch gibt weiss ich net
Diese 9-Operationen in der SChleife sind leider notwendig. Siehe auf Seite 2 des folgenden Links bevor ich rauf und runter erklaere: http://www.vms.ei.tum.de/lehre/asb-java/download/Diskrete_Faltung.pdf
Da gibt es keine Optimierungsmoeglichkeiten.na ich denke da eher an Befehlsoptimierung.
Ok, mit CPalette habe ich noch nicht gearbeitet. Hab im Netz leider nicht so viel gefunden wie ich eigentlich erwartet hatte. Hat jemand zufaellig ein Beispiel parat das er unbedingt posten moechte?
Bekommst du is aber so net anwendbar weil ich das aus ner klasse von mir rausgerissen habe, aber da wird aus net 32 bit Farbgrafik ne 32 bit graustufen grafik gemacht
void CPicture::MakeGrayPicture() { if(!this->m_hObject) { TRACE( "CPicture::MakeGrayPicture Class not Initialised or not Picture Loaded\n"); return; } CPalette pal; UINT nSize = sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * 256); LOGPALETTE *pLP = (LOGPALETTE *) new BYTE[nSize]; pLP->palVersion = 0x300; pLP->palNumEntries = 256; for( int i=0; i < 256; i++) { pLP->palPalEntry[i].peRed = i; pLP->palPalEntry[i].peGreen = i; pLP->palPalEntry[i].peBlue = i; pLP->palPalEntry[i].peFlags = 0; } pal.CreatePalette( pLP ); BITMAP bm; CBitmap::GetObject(sizeof(BITMAP), (LPSTR)&bm); BYTE *bmpBuffer = new BYTE[ bm.bmWidthBytes*bm.bmHeight ];//allocate memory for image //byte buffer ULONG dwValue=CBitmap::GetBitmapBits(bm.bmWidthBytes*bm.bmHeight, bmpBuffer);//Get the bitmap bits //into a structure*/ DWORD* buffer = (DWORD*)bmpBuffer; for(ULONG Count = 0;Count < dwValue/4;Count++) { UINT Index = pal.GetNearestPaletteIndex(buffer[Count]); buffer[Count] = RGB(pLP->palPalEntry[Index].peRed, pLP->palPalEntry[Index].peGreen, pLP->palPalEntry[Index].peBlue); } delete[] pLP; dwValue = CBitmap::SetBitmapBits(bm.bmWidthBytes*bm.bmHeight,bmpBuffer); delete[] bmpBuffer; }
Eigentlich koennte ich doch auch die Grauwerte in einem 2d-Byte-Array speichern und verarbeiten. Am Ende dann fuer die Anzeige die Rueckkonvertierung nach 32-Bit :p
na wie du das verarbeitest is doch dein problem, also wer sagt dir denn das unbedingt ein bild in einer bitmap mit allen headern liegen muß
-
Na denn vielen besten Dank fuer deine Bemuehungen! WErde deinen Code morgen gleich mal ausprobieren
-
Wie schon geschrieben is das aus ner klasse von mir und der läuft so allein nicht, das is eigentlich nur mal zur demo da also um zu sehen wie CPalette läuft. Also net fragen warum das so net läuft und so viele fehler drin sind.