Dieser Thread (Bild in Bild erkennen und extra speichern) von @Kameo hatte mich inspiriert, dazu ein kleines Projekt zu machen...
Mich interessierte es, wie man verschiedene Portraits bzw. Figuren aus einem Bild im angepassten Rechteck herauspicken kann.
VORAUSSETZUNG: Diese Bilder müssen einen homogenen Hintergrund vorweisen und die Figuren (Portraits) müssen zueinander abgetrennt vorliegen.
Das Kernstück dieses Projekts ist eine Routine, die ein Rechteck um eine Figur bestimmt.
Diesen berechneten Bereich kann man dann beliebig weiter verwenden...
Es gibt 2 Richtungen zum Pixel des innenliegenden Figurrands:
Entweder man sucht außerhalb oder innerhalb der Figur zum Randpixel.
Hat man dieses Randpixel festgestellt kann man danach den ganzen Figurrand abscannen.
Es werden alle 8 Nachbarpixel des Randpixels beginnend ab 12 Uhr im Uhzeigersinn auf die Hintergrundfarbe getestet.
Dabei gilt folgende Theorie:
1) Zuerst muss ein Nachbarpixel eine Hintergrundfarbe vorweisen also unmittelbar außerhalb der Figur liegen.
2) Das nächsterste Randpixel, dass von der Hintergrundfarbe wieder abweicht, befindet sich dann wieder im direkten Randbereich innerhalb der Kontour.
3) Wenn das zutrifft, wird dieses als nächstes innenliegende Randpixel übernommen.
4) Der gesuchte Rechteckauschnitt (Top, Bottom, Width, Height) wird nach diesem Pixel neu bestimmt.
So wird die ganze Figur im Randbereich bis zum Startpunkt des Scans im Uhrzeigersinn abgescannt!
Sollte diese Kontour im Hauptbild bis zu seinem Bildrand reichen, wird der Scan abgebrochen.
scanDeteilPicture
Dieser Scan tut, was er soll - aber er ist leider nicht gerade der schnellste!
(ich werde als nächstes das mit LockBits versuchen...)
Anbei das Testprojekt und ein Beispielbild von Kameo.
Mich interessierte es, wie man verschiedene Portraits bzw. Figuren aus einem Bild im angepassten Rechteck herauspicken kann.
VORAUSSETZUNG: Diese Bilder müssen einen homogenen Hintergrund vorweisen und die Figuren (Portraits) müssen zueinander abgetrennt vorliegen.
Das Kernstück dieses Projekts ist eine Routine, die ein Rechteck um eine Figur bestimmt.
Diesen berechneten Bereich kann man dann beliebig weiter verwenden...
Es gibt 2 Richtungen zum Pixel des innenliegenden Figurrands:
Entweder man sucht außerhalb oder innerhalb der Figur zum Randpixel.
Hat man dieses Randpixel festgestellt kann man danach den ganzen Figurrand abscannen.
Es werden alle 8 Nachbarpixel des Randpixels beginnend ab 12 Uhr im Uhzeigersinn auf die Hintergrundfarbe getestet.
Dabei gilt folgende Theorie:
1) Zuerst muss ein Nachbarpixel eine Hintergrundfarbe vorweisen also unmittelbar außerhalb der Figur liegen.
2) Das nächsterste Randpixel, dass von der Hintergrundfarbe wieder abweicht, befindet sich dann wieder im direkten Randbereich innerhalb der Kontour.
3) Wenn das zutrifft, wird dieses als nächstes innenliegende Randpixel übernommen.
4) Der gesuchte Rechteckauschnitt (Top, Bottom, Width, Height) wird nach diesem Pixel neu bestimmt.
So wird die ganze Figur im Randbereich bis zum Startpunkt des Scans im Uhrzeigersinn abgescannt!
Sollte diese Kontour im Hauptbild bis zu seinem Bildrand reichen, wird der Scan abgebrochen.
VB.NET-Quellcode
- ''' <summary>
- ''' Der Scan beginnt beim ersten bekannten Randpixel innerhalb der Figur
- ''' In der Schleife wird der Figurrand Pixelweise untersucht und
- ''' das Rectangel des Bildausschnittes wird ständig bei gültigen Pixel neu berechnet
- ''' Ist der Ausgangspunkt wieder erreicht => Ausstieg aus dem Loop
- ''' Bei einer Sackgasse werden die Pixel wieder retour gemommen...
- ''' </summary>
- Private Function scanDeteil(x As Integer, y As Integer) As Rectangle
- Dim top = y
- Dim bottom = y
- Dim left = x
- Dim right = x
- Dim cntBack = 1
- savePixel(x, y)
- Dim p = debugNextPixel(x, y)
- Do
- If Not isPixelPositionOK(p.X, p.Y) Then Exit Do
- If p = listEdgePixel.First.Point Then Exit Do
- If deadEnd Then
- cntBack += 1
- Dim i = listEdgePixel.Count - cntBack : If i < 0 Then Exit Do
- p = listEdgePixel(i).Point
- deadEnd = False
- p = debugNextPixel(p.X, p.Y)
- Else
- cntBack = 1
- savePixel(p.X, p.Y)
- If top > p.Y Then top = p.Y
- If bottom < p.Y Then bottom = p.Y
- If left > p.X Then left = p.X
- If right < p.X Then right = p.X
- p = debugNextPixel(p.X, p.Y)
- End If
- Loop
- Return New Rectangle(top, left, right - left + 1, bottom - top + 1)
- End Function
- ''' <summary>
- ''' Diese Methode dient nur zum Testen und kann mit der Methode 'getNextPixel' direkt ersetzt werden!
- ''' </summary>
- Private Function debugNextPixel(w As Integer, h As Integer) As Point
- Debug.WriteLine(String.Format("{0} {1} {2}", w, h, If(deadEnd, "R", "")))
- Return getNextPixel(w, h)
- End Function
- ''' <summary>
- ''' Test aller Nachbarpixel ab 12 Uhr im Uhrzeigersinn
- ''' 1) liegt ein Pixel innerhalb oder außerhalb der Figur 'outside'?
- ''' 2) liegt ein Pixel außerhalb und ein nächstes Pixel innerhalb der Figur?
- ''' 3) ist das vorherige Pixel noch nicht aufgenommen => Übernahme dieses nächsten Pixels!
- ''' </summary>
- Private Function getNextPixel(w As Integer, h As Integer) As Point
- Dim outside = False
- Do
- h -= 1 : outside = isPixelOK(w, h) 'OBEN
- If listEdgePixel.First.Point = New Point(w, h) Then Exit Do 'WIEDER AM START
- If deadEnd Then Exit Do 'WIEDER ZURÜCK
- If w < 0 AndAlso h < 0 Then Exit Do 'AUSSERHALB
- w += 1 : If outside AndAlso isPixelOK(w, h, False) AndAlso reverseNextPixel(w, h) Then Exit Do Else w -= 1 'OBEN RECHTS
- w += 1 : outside = isPixelOK(w, h) 'OBEN RECHTS
- h += 1 : If outside AndAlso isPixelOK(w, h, False) AndAlso reverseNextPixel(w, h) Then Exit Do Else h -= 1 'RECHTS
- h += 1 : outside = isPixelOK(w, h) 'RECHTS
- h += 1 : If outside AndAlso isPixelOK(w, h, False) AndAlso reverseNextPixel(w, h) Then Exit Do Else h -= 1 'UNTEN RECHTS
- h += 1 : outside = isPixelOK(w, h) 'UNTEN RECHTS
- w -= 1 : If outside AndAlso isPixelOK(w, h, False) AndAlso reverseNextPixel(w, h) Then Exit Do Else w += 1 'UNTEN
- w -= 1 : outside = isPixelOK(w, h) 'UNTEN
- w -= 1 : If outside AndAlso isPixelOK(w, h, False) AndAlso reverseNextPixel(w, h) Then Exit Do Else w += 1 'LINKS UNTEN
- w -= 1 : outside = isPixelOK(w, h) 'LINKS UNTEN
- h -= 1 : If outside AndAlso isPixelOK(w, h, False) AndAlso reverseNextPixel(w, h) Then Exit Do Else h += 1 'LINKS
- h -= 1 : outside = isPixelOK(w, h) 'LINKS
- h -= 1 : If outside AndAlso isPixelOK(w, h, False) AndAlso reverseNextPixel(w, h) Then Exit Do Else h += 1 'LINKS OBEN
- h -= 1 : outside = isPixelOK(w, h) 'LINKS OBEN
- w += 1 : If outside AndAlso isPixelOK(w, h, False) AndAlso reverseNextPixel(w, h) Then Exit Do Else w -= 1 'OBEN
- Loop
- Return New Point(w, h) 'Übernahme des Pixels
- End Function
- ''' <summary>
- ''' Überprüfung, ob das Pixel schon einmal aufgenommen wurde.
- ''' Wenn das der Fall ist, wird mit 'deadend' (Sackgasse) zurückgeschaltet.
- ''' Das Ergebnis wird erst beim nächsten Pixel aktuell geprüft => Return NOT Found
- ''' </summary>
- Private Function reverseNextPixel(w As Integer, h As Integer) As Boolean
- Dim found = listEdgePixel.Contains(New Pixel(New Point(w, h)))
- deadEnd = found 'Sackgasse
- Return Not found
- End Function
- ''' <summary>
- ''' Überprüfung auf die korrekte Position des Pixels
- ''' Und dann Test auf Vordergrund- oder Hintergrundfarbe
- ''' </summary>
- ''' <param name="onTheEdge">Test auf außerhalb (True) oder innerhalb (False) der Figur liegendes Pixel</param>
- Private Function isPixelOK(w As Integer, h As Integer, Optional onTheEdge As Boolean = True) As Boolean
- If Not isPixelPositionOK(w, h) Then Return False
- If onTheEdge Then
- Return (bmpTarget.GetPixel(w, h) = colorBackground) 'außerhalb der Figur
- Else
- Return (bmpTarget.GetPixel(w, h) <> colorBackground) 'innerhalb der Figur
- End If
- End Function
Dieser Scan tut, was er soll - aber er ist leider nicht gerade der schnellste!
(ich werde als nächstes das mit LockBits versuchen...)
Anbei das Testprojekt und ein Beispielbild von Kameo.
Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „VB1963“ ()