Seite 1 von 1

Bildanalyse: Position von Objekten in einem Image ermitteln

Verfasst: 15.07.2007 03:27
von PureLust
Moin zusammen, ...

ich habe für einen User im englischen Forum eine kleine Routine geschrieben mit der man farbige Objekte in einem Bild (z.B. Personen oder auch Tiere vor einer WebCam) lokalisieren und deren Position im Bild bestimmen kann.
Somit ist es dann z.B. möglich ein Bewegungsprofil dieser Objekte zu erstellen.

Falls also auch jemand hier im deutschen Forum Verwendung für sowas hat, hier mal ein ScreenShot und der Source:

Bild

[Rightclick to download the Source]
[Rightclick to download the Windows-EXE]

Code: Alles auswählen

; PB 4.x - should work on all OS-Platforms (tested on Win-XP and Ubuntu-Linux).

EnableExplicit

CompilerIf #PB_Compiler_OS <> #PB_OS_Windows	; On other OS then Windows the following Structures must be defined seperately:
	Structure Point
		x.l
		y.l
	EndStructure
	Structure RGBQuad
		rgbBlue.b
		rgbGreen.b
		rgbRed.b
		rgbReserved.b
	EndStructure
CompilerEndIf


Procedure FindObjectInPicture(Image.l, ColRangeMin.l, ColRangeMax.l, *pt.Point)
	;
   ; Description: 
   ; This Routine analyses an image for pixels within an given ColorRange. 
   ; Furthermore it does a weighting of all found points. 
   ; So it is possible to identify an object within the image and get the position of its central point - even if the Image is very noisy.
   ; By calling this routine multiple times with different ColorRanges, it is possible to identify multiple objects within the same Image. 
   ; Limitations: So far it cannot identify more than one object whithin the same ColorRange. 
   ;              In this Case the result will be a weighted middle position of all objects found within this ColorRange. 
	;
	; Parameters:
	; - Image				:	#Image of the Image to be analysed
	; - ColRangeMin		:	specifies the minimum Range for the RGB-Filter
	; - ColRangeMax		:	specifies the maximum Range for the RGB-Filter
	;								Only the Colorchannels are filtered, which have a value in ColRangeMin or ColRangeMax
	;								So setting ColRangeMin=$000088 and ColRangeMax=$6600ff means, that
	;								the valid Range for blue is from $00-$66 and the valid Range for red is from $88-$ff
	;								Because there is no range set for the green-channel, green could have any value.
	;
	; - *pt.Point	[out]	:	Pointer to a POINT-Structure
	;								The x/y-coordinates (center point) of a found Object will be written to this Structure
	;
	; Results				:	The Procedure returns a #True if an Object with the specified Range was found - otherwise it returns #False.
	
	Global FindObjectInPicture_StartDrawingActive.l = #False
	Structure AnalysePicture_Weighting
		Weight.RGBQuad
		Pos.w
	EndStructure
	Protected Result.l = #False
	Protected x.l, y.l, ColInRange.l, ActCol.l, *ActCol.RGBQuad
	Protected ActRed.w, ActGreen.w, ActBlue.w, MinRed.w, MinGreen.w, CheckBlue.w
	Protected xWeight.l, yWeight.l, xMark.l, yMark.l
	Protected *ColMin.RGBQuad = @ColRangeMin
	Protected *ColMax.RGBQuad = @ColRangeMax
	Protected CheckColors.l = ColRangeMin | ColRangeMax
	Protected *ColCheck.RGBQuad = @CheckColors
	If IsImage(Image)
		Protected Dim xWeighting(ImageWidth(Image)-1)
		Protected Dim yWeighting(ImageHeight(Image)-1)
		CompilerSelect #PB_Compiler_OS
			CompilerCase #PB_OS_Windows, #PB_OS_Linux
				!extrn _PB_2DDrawing_GlobalStructure 		; This little ASM-Code (intel CPU only) just checks, if StartDrawing() is already active or not
				!PUSH eax
				!MOV eax,[_PB_2DDrawing_GlobalStructure]
				!MOV [v_FindObjectInPicture_StartDrawingActive],eax
				!POP eax
		CompilerEndSelect
		If Not FindObjectInPicture_StartDrawingActive
			If CheckColors > 0 And StartDrawing(ImageOutput(Image))
				; Image ColorRange-Analysis
				For y = 0 To ImageHeight(Image)-1
					For x = 0 To ImageWidth(Image)-1
						ActCol = Point(x,y)	; <== This could be speeded up by an API-Guru if required.  ;)
						*ActCol = @ActCol
						ColInRange = #True
						If *ColCheck\rgbRed And (*ActCol\rgbRed & 255 < *ColMin\rgbRed & 255 Or *ActCol\rgbRed & 255 > *ColMax\rgbRed& 255 )									; red channel
							ColInRange = #False
						EndIf
						If ColInRange And *ColCheck\rgbGreen And (*ActCol\rgbGreen & 255 < *ColMin\rgbGreen & 255 Or *ActCol\rgbGreen & 255 > *ColMax\rgbGreen & 255)	; green channel
							ColInRange = #False
						EndIf
						If ColInRange And *ColCheck\rgbBlue And (*ActCol\rgbBlue & 255 < *ColMin\rgbBlue & 255 Or *ActCol\rgbBlue & 255 > *ColMax\rgbBlue & 255)			; blue channel
							ColInRange = #False
						EndIf
						If ColInRange
							xWeighting(x) + 1
							yWeighting(y) + 1
; 							Debug Str(x)+","+Str(y)+"  "+Hex(ActCol)+" "+Hex(CheckColors)
						EndIf
					Next x
				Next y
				StopDrawing()
				; Calculating horizontal Weight
				xMark		= 0
				xWeight	= 0
				For x = 0 To ImageWidth(Image)-1
					xMark		= xMark		+ (x+1) * xWeighting(x)
					xWeight	= xWeight	+ xWeighting(x)
				Next x
				; Calculating vertical Weight
				yMark		= 0
				yWeight	= 0
				For y = 0 To ImageHeight(Image)-1
					yMark		= yMark		+ (y+1) * yWeighting(y)
					yWeight	= yWeight	+ yWeighting(y)
				Next y
				; Write Results to Structure
				If xWeight > 0 And yWeight > 0
					*pt\x = xMark / xWeight - 1
					*pt\y = yMark / yWeight - 1
					Result = #True
				EndIf
			EndIf
		Else
			MessageRequester("FindObjectInPicture()-Error",Chr(34)+"StopDrawing()"+Chr(34)+" must be called before using FindObjectInPicture().")
		EndIf
	EndIf
	ProcedureReturn Result
EndProcedure

Procedure GenerateNoisyTestImage(Image, Move=#False)
	Structure TestImageObject
		x.w
		y.w
		width.w
		height.w
		shape.b
		angle.f
		speed.f
		anglestep.f
		steps.b
		dots.w
	EndStructure
	Static Dim Object.TestImageObject(5)
	Protected n, x, y
	Protected x1, y1, x2, y2, angle.f
	Protected rMin, rMax, gMin, gMax, bMin, bMax
	If StartDrawing(ImageOutput(Image))
		If Not Move Or Object(0)\x = 0		; Set new random position and attributes to objects
			For n = 0 To 5
				Object(n)\x = Random(ImageWidth(Image)-57)+28
				Object(n)\y = Random(ImageHeight(Image)-57)+28
				Object(n)\width	= Random(10)+10
				Object(n)\height	= Random(10)+10
				Object(n)\shape 	= Random(1)
				Object(n)\angle		= Random(64000)/10000
				Object(n)\speed		= Random(3000)/1000 + 3
				Object(n)\anglestep	= (Random(5000)-2500)/10000
				Object(n)\steps		= Random(10)+5
				Object(n)\dots			= Random(50)+100
			Next n
		Else											; Move Objects
			For n = 0 To 5
				Object(n)\angle - Object(n)\anglestep
				Object(n)\x + Sin(Object(n)\angle) * Object(n)\speed
				Object(n)\y + Cos(Object(n)\angle) * Object(n)\speed
				If Object(n)\x < 28 Or Object(n)\x > ImageWidth(Image)-28 Or Object(n)\y < 28 Or Object(n)\y > ImageHeight(Image) - 28
					Object(n)\angle - 3.2
					Object(n)\x + Sin(Object(n)\angle) * Object(n)\speed
					Object(n)\y + Cos(Object(n)\angle) * Object(n)\speed
				EndIf
				Object(n)\steps - 1
				If Object(n)\steps < 1
					Object(n)\speed		= Random(3000)/1000 + 3
					Object(n)\anglestep	= (Random(5000)-2500)/10000
					Object(n)\steps		= Random(10)+5
				EndIf
				Object(n)\width	= Random(10)+10
				Object(n)\height	= Random(10)+10
				Object(n)\dots		= Random(50)+100
			Next n
		EndIf
		Box(0,0,ImageWidth(Image),ImageHeight(Image),0)		; blank image
		For y = 0 To ImageHeight(Image)-1						; Fill Image with dust
			For x = 0 To ImageWidth(Image)-1
				If Random(1) : Plot(x,y,RGB(Random($30),Random($30),Random($30))) : EndIf
			Next x
		Next y
		For n = 0 To 5													; draw Objects
			rMin = 0 : rMax = $30
			gMin = 0 : gMax = $30
			bMin = 0 : bMax = $30
			If n = 0 Or n = 3 Or n = 4 : rMin = $40 : rMax = $5f : EndIf
			If n = 1 Or n = 3 Or n = 5 : gMin = $40 : gMax = $5f : EndIf
			If n = 2 Or n = 4 Or n = 5 : bMin = $40 : bMax = $5f : EndIf
			If Object(n)\shape		 ; draw a colored square
				For x = 1 To Object(n)\dots
						Plot(Object(n)\x+Random(Object(n)\width)-Object(n)\width/2,Object(n)\y+Random(Object(n)\height)-Object(n)\height/2,RGB(Random(rMax-rMin)+rMin,Random(gMax-gMin)+gMin,Random(bMax-bMin)+bMin))
				Next x
			Else					; draw a round shape
				For x = 1 To Object(n)\dots
						angle = Random(6400)/1000
						Plot(Object(n)\x+Sin(angle)*Random(Object(n)\width),Object(n)\y+Cos(angle)*Random(Object(n)\height),RGB(Random(rMax-rMin)+rMin,Random(gMax-gMin)+gMin,Random(bMax-bMin)+bMin))
				Next x
			EndIf
		Next n
		StopDrawing()
	EndIf
EndProcedure

Structure MultiPoint
	pt.Point[3]
EndStructure
NewList ObjectPath.MultiPoint()
Define Event
Define tStart, tStop, n, x, y, iCount, Animate
Dim Square.Point(2)

If CreateImage(0,280,200)
	If OpenWindow(0,0,0,ImageWidth(0)+20,ImageHeight(0)+80,"Filtering red, green and cyan Objects", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
		If CreateGadgetList(WindowID(0))
			ImageGadget(0,10,10,0,0,ImageID(0))
			ButtonGadget(1,20,WindowHeight(0)-60,WindowWidth(0)-40,20,"Create and analyse single Image")
			ButtonGadget(2,20,WindowHeight(0)-30,WindowWidth(0)-40,20,"Continuously animate and trace Objects")
			Repeat
				Event = WaitWindowEvent(Animate)
				If Event = #PB_Event_Gadget Or Animate
					If EventGadget() = 2 Or Animate
						If Animate
							If Event = #PB_Event_Gadget											; stop tracing objects
								Animate = #False
								SetGadgetText(2,"Continuously animate and trace Objects")
								DisableGadget(1,#False)
							Else
								GenerateNoisyTestImage(0, #True)
								FindObjectInPicture(0,RGB($40,$00,$00),RGB($7f,$30,$30),@Square(0))	; find red     : red-Range        $40-$7f - green and blue is only valid upto a noiselevel of $30
								FindObjectInPicture(0,RGB($00,$40,$00),RGB($30,$7f,$30),@Square(1))	; find green   : green Range      $40-$7f -  red  and blue is only valid upto a noiselevel of $30
								FindObjectInPicture(0,RGB($00,$40,$40),RGB($30,$7f,$7f),@Square(2))	; find cyan    : green&blue Range $40-$7f -  red  is only valid upto a noiselevel of $30
								If  StartDrawing(ImageOutput(0))
									If CountList(ObjectPath()) > 20		; if Path is longer than 20 steps -> cut it
										FirstElement(ObjectPath())
										DeleteElement(ObjectPath())
										LastElement(ObjectPath())
									EndIf
									AddElement(ObjectPath())
									For n = 0 To 2
										Line(Square(n)\x-10,Square(n)\y,20,0,$ffffff)		; draw cross to show object center point
										Line(Square(n)\x,Square(n)\y-10,0,20,$ffffff)
										ObjectPath()\pt[n]\x = Square(n)\x						; write new center to path
										ObjectPath()\pt[n]\y = Square(n)\y
										ForEach ObjectPath()										; draw path
											x = ObjectPath()\pt[n]\x
											y = ObjectPath()\pt[n]\y
											If NextElement(ObjectPath())
												LineXY(x,y,ObjectPath()\pt[n]\x,ObjectPath()\pt[n]\y,RGB((0 Or n=0)*$5f,(0 Or n>0) *$5f,(0 Or n=2)*$5f))
												PreviousElement(ObjectPath())
											EndIf
										Next
									Next n
									StopDrawing()
									SetGadgetState(0,ImageID(0))
								EndIf
							EndIf
						Else																				; start tracing objects
							Animate = #True
							SetGadgetText(2,"Stop Object tracing")
							DisableGadget(1,#True)
						EndIf
					ElseIf EventGadget() = 1
						GenerateNoisyTestImage(0)
						SetGadgetState(0,ImageID(0))
						iCount = 0
						tStart = ElapsedMilliseconds()
						iCount + FindObjectInPicture(0,RGB($40,$00,$00),RGB($ff,$30,$30),@Square(0))	; find red     : red-Range        $40-$7f - green and blue is only valid upto a noiselevel of $30
						iCount + FindObjectInPicture(0,RGB($00,$40,$00),RGB($30,$7f,$30),@Square(1))	; find green   : green Range      $40-$7f -  red  and blue is only valid upto a noiselevel of $30
						iCount + FindObjectInPicture(0,RGB($00,$40,$40),RGB($30,$7f,$7f),@Square(2))	; find cyan    : green&blue Range $40-$7f -  red  is only valid upto a noiselevel of $30
; 						iCount + FindObjectInPicture(0,RGB($00,$00,$40),RGB($30,$30,$7f),@Square(0))	; find blue    : blue-Range       $40-$7f - green and red  is only valid upto a noiselevel of $30
; 						iCount + FindObjectInPicture(0,RGB($40,$40,$00),RGB($7f,$7f,$30),@Square(1))	; find yellow  : red&green Range  $40-$7f -  blue is only valid upto a noiselevel of $30
; 						iCount + FindObjectInPicture(0,RGB($40,$00,$40),RGB($7f,$30,$7f),@Square(2))	; find magenta : red&blue  Range  $40-$7f - green is only valid upto a noiselevel of $30
						tStop = ElapsedMilliseconds()
						StartDrawing(ImageOutput(0))
							For n = 0 To 2
								Line(Square(n)\x-10,Square(n)\y,20,0,$ffffff)				; draw cross to show object center point
								Line(Square(n)\x,Square(n)\y-10,0,20,$ffffff)
							Next n
							DrawingMode(#PB_2DDrawing_Transparent)
							DrawText(1,1,Str(iCount)+" Objects identified in "+Str(tStop - tStart)+"ms",$ffffff)
						StopDrawing()
						SetGadgetState(0,ImageID(0))
						ClearList(ObjectPath())
					EndIf
				EndIf
			Until Event = #PB_Event_CloseWindow
		EndIf
		CloseWindow(0)
	EndIf
	FreeImage(0)
EndIf
Greets, PureLust.

Verfasst: 16.07.2007 16:46
von PureLust
Hab den Demo-Teil des Codes nochmal um eine Object-Animation sowie eine Bewegungsverfolgung erweitert.
(Code, Screenshot und Downloadlink s.o.)

[Edit:]
War noch ein 'kleiner' Fehler drin, wodurch nicht korrekt gefiltert wurde wenn die Filterbereiche über $7f lagen.
(Korrigierter Code und Downloadlink s.o.)

Verfasst: 18.07.2007 10:02
von Konne
kanns noch nicht testen sdieht aber sehr geil aus :allright:

Verfasst: 18.07.2007 12:01
von Fluid Byte
Um den aktuellen Farbwert zu ermitteln solltest mit *Pointern arbeiten. Gibt genügend Beispiele hier im Forum. Ansonsten Hut ab! :D

Verfasst: 18.07.2007 14:37
von PureLust
Fluid Byte hat geschrieben:Um den aktuellen Farbwert zu ermitteln solltest mit *Pointern arbeiten. Gibt genügend Beispiele hier im Forum...
Das es und auch wie es zu beschleunigen geht ist schon klar (daher ja auch mein Kommentar im Code). ;)

Wie Du ja auch vielleicht in einigen meinen allerersten geposteten Codes gesehen hast (irgendwann 2005) mach ich das auch so, wenn's mir halt auf Geschwindigkeit ankommt.
Solche Optimierungen sind dann aber eben meist für Anfänger nicht mehr so leicht nachvollziehbar und leider auch nicht mehr unbedingt plattformübergreifend anwendbar.
Aber genau darauf arbeite ich bei meinen hier eingestellten Codes eigentlich hin.

Auf Geschwindigkeit ist die ganze Geschichte aber ohnehin nicht getrimmt (war auch nicht das angestrebte Ziel) - es ging eher um's "wie".
Wenn jemand eine solche Routine gebrauchen kann und 'ne Speedoptimierung explizit für Windows anfragt kann ich (oder halt jemand anderes) das natürlich noch gerne nachrüsten.

Was Linux und Mac angeht, so müsste man sich hier halt auf jeden Fall mit 62ms je ColorRange-Analyse (A64-3200) begnügen.
Es sei denn jemand bringt mal auf Linux und OSX eine (API-)Lösung für eine direkte Bildmanipulation im Speicher - oder eben - die PB-eigenen Plot-/Point-Befehle werden mal etwas optimiert.

Ansonsten natürlich: Vielen Dank für's Lob. :D

Verfasst: 18.07.2007 14:48
von Fluid Byte
Jaja, das mit der Plattform-Kompatibilität vergesse ich immer. :D

Aber funktioniert dieser code denn jetzt wirklich 1:1 auch auf Linux? Hab's leider nicht installiert sonst würd ich selber testen. Was Mac OS angeht, ich hab 'nen MacBook Pro zu hause und könnte es mal versuchen umzusetzen. Wobei ich sagen muss das aufm' Mac PB erst 3, 4 mal benutzt habe. :mrgreen:

Verfasst: 18.07.2007 15:21
von PureLust
Fluid Byte hat geschrieben:Jaja, das mit der Plattform-Kompatibilität vergesse ich immer. :D

Aber funktioniert dieser code denn jetzt wirklich 1:1 auch auf Linux?
Hab ihn soeben mal getestet und dabei festgestellt, dass die verwendeten Strukturen wohl unter Linux nicht existieren. (Hab's also nachgerüstet - siehe oben.)
Nun läuft's also auch definitiv unter Linux und sollte im Grunde auch auf Amiga und OSX laufen (bisher jedoch noch ungetestet).

Aber viel interessanter war, dass die Routine selbst auf meinem Ubuntu in einer Virtuellen Box weit über 3 mal so schnell lief wie unter Windows (<20ms/ColorRange-Analyse). :o
Vom möglichen Speed auf einem reinen (nicht durch eine VM emulierten) Linux Rechner mal garnicht zu reden.

Unter Linux scheinen die Plot-/Point-Befehle also wesentlich besser optimiert zu sein als unter Windows (oder eben ElapsedMilliseconds() funnzt in einer VM nicht korrekt). :roll: