Detect a change in color?

Blitz3D Forums/Blitz3D Programming/Detect a change in color?

How can I detect a strong change in color on an image so that I can mark it as an edge? For example a red ball on a blue background, I need to find the edges of the ball.

Thanks.

Lock the image, read a pixel, check whether the different between that colour and previous colour using ReadPixelFast (either default or previous pixel), move on to the next pixel.

You still trying to work out this 'mean filter' thingie, CopperCircle?

As I mentioned in the other thread, I did have a quick play with an idea I had, but it proved pretty pants - as I warned it might. :)

The problem comes down to the fact that detecting if two given colours are the same (irrespective of brightness, etc), or not, is difficult.

In case you're interested, the idea I was playing with was to use a 'flood search' to identify areas of an image that have the same, or similar, 'colour'. This flood search worked on the same principle as a flood fill, so that it identified all connected pixels of the 'same colour', starting from a given x,y point in the image. I then tried setting all the pixels identified by the search to the average colour of all those found.

I had some limited success with this method, but didn't have time to develop it any further. I'm not convinced it can be made to work any better, anyway. Results obtained from using filters in standard image editing software seemed to do a better job, too. :/

To select by colour you'd need to convert to a colourspace you're happy working with, such as HSL and detecting by Hue.

http://www.cs.rit.edu/~ncs/color/t_convert.html

However there's no simple select by colour process, such as if Hue Difference is > delta. I don't know of any good algorithm for this.

Yeah, I tried using HSL, but it didn't help much. The trouble with the HSL colour model is that it doesn't have the resolution of the RGB colour model. For example, how do you find the Hue of a pure grey?

Traditional HSL pegs it at H=0, but it really needs a per case consideration depending on what you're trying to do. It's down to the user to decide if grey is a change from Hue = 100 or not.

I have tried posterising the image by anding each color with %11000000 and also comparing the brightness of each pixel to find edges. But neither give the required effect, I know the basic mean shift algorithm will break an image into seperate color sections but I have not been able to find a good source of how to write the mean shift filter.

Once the image reduced to basic color patches I could smooth it and then define the edges.

I am trying to get the look used in "A Scanner Darkly"

big10p I would love to see what you have done?

Cheers.

That Mean Shift did a good job, but I've no idea of the algorithm :(

big10p I would love to see what you have done?
Yikes! I was afraid you were going to ask that. :P The code is pretty rough and unoptimized (i.e. slow) as I just wanted to slap something together quickly, to see if the principle worked. However, I'll try and post it up here, later on.

[edit]
OK, here's the code I was playing with. In it's current state, it doesn't work too well at all, TBH, and is very slow, but you can probably work out the method I was going for, from it.
	Graphics 640,480,32,2
	SetBuffer BackBuffer()

	Type fill_pointT
		Field x%,y%
	End Type
	
	Global result_h#, result_s#, result_l#
	Global thresh = 30
	Global total_r,total_g,total_b,total_points
			
	image = LoadImage("ball_seg.jpg")
	Global image_width = ImageWidth(image)
	Global image_height = ImageHeight(image)
	new_image = CreateImage(image_width,image_height)

	; Image pixel RGB and HSL matrices.
	Dim pix_rgb(image_width-1,image_height-1,2)
	Dim pix_hsl(image_width-1,image_height-1,2)
	IB_image = ImageBuffer(image)
	LockBuffer IB_image
	For y = 0 To image_height-1
		For x = 0 To image_width-1
			pix = ReadPixelFast(x,y,IB_image)
			r = (pix And $00FF0000) Shr 16
			g = (pix And $0000FF00) Shr 8
			b = (pix And $000000FF)
			pix_rgb(x,y,0) = r
			pix_rgb(x,y,1) = g
			pix_rgb(x,y,2) = b
			
			rgb_to_hsl(r,g,b)
			pix_hsl(x,y,0) = result_h
			pix_hsl(x,y,1) = result_s
			pix_hsl(x,y,2) = result_l
		Next
	Next	
	UnlockBuffer IB_image
		

	IB_new_image = ImageBuffer(new_image)
	For y = 0 To image_height-1
Print y
		For x = 0 To image_width-1
			Dim pix_fill(image_width-1,image_height-1)
			fill(x,y)
			mean_r = Float(total_r) / total_points
			mean_g = Float(total_g) / total_points
			mean_b = Float(total_b) / total_points
			
			rgb = $FF000000 Or (mean_r Shl 16) Or (mean_g Shl 8) Or mean_b

			LockBuffer IB_new_image
			For yy = 0 To image_height-1
				For xx = 0 To image_width-1
					If pix_fill(xx,yy)
						WritePixelFast xx,yy,rgb,IB_new_image
					EndIf
				Next
			Next	
			UnlockBuffer IB_new_image
		Next
	Next

	Cls
	DrawBlock image,0,0
	DrawBlock new_image,image_width+19,0
	Flip
	WaitKey()
	End
	
	
	
Function match(h%, s%, l%, x%, y%)

	dh = Abs(h - pix_hsl(x,y,0))
	
	If (dh <= thresh) Or ((200-dh) <= thresh)
		ds = Abs(s - pix_hsl(x,y,1))
		
		If (ds <= thresh) Or ((200-ds) <= thresh)
			dl = Abs(l - pix_hsl(x,y,2))
			
			If (dl <= thresh) Or ((200-dl) <= thresh)
				Return True
			EndIf
		EndIf
	EndIf
	
End Function	
	
	
	
Function fill(x%, y%)

	p.fill_pointT = New fill_pointT
	p\x = x
	p\y = y
	
	base_h = pix_hsl(x,y,0)
	base_s = pix_hsl(x,y,1)
	base_l = pix_hsl(x,y,2)
	
	total_points = 0
	total_r = 0
	total_g = 0
	total_b = 0
	
	While p <> Null
		point_x = p\x
		point_y = p\y

		If match(base_h,base_s,base_l,point_x,point_y) And pix_fill(point_x,point_y) = 0

			; Find left-most pixel on this line to start filling from.
			While point_x > 0
				If match(base_h,base_s,base_l,point_x-1,point_y) And pix_fill(point_x-1,point_y) = 0
					point_x = point_x - 1
				Else
					Exit
				EndIf
			Wend
			
			add_above = True
			add_below = True
	
			; Start filling pixels from start point, moving rightwards...
			Repeat

				; Add pixel above to fill queue if neccessary.
				If point_y > 0
					If match(base_h,base_s,base_l,point_x,point_y-1) And pix_fill(point_x,point_y-1) = 0
						If add_above
							add_above = False
							new_p.fill_pointT = New fill_pointT
							new_p\x = point_x
							new_p\y = point_y - 1
						EndIf
					Else
						add_above = True
					EndIf
				EndIf
	
				; Add pixel below to fill queue if neccessary.
				If point_y < image_height-1
					If match(base_h,base_s,base_l,point_x,point_y+1) And pix_fill(point_x,point_y+1) = 0
						If add_below
							add_below = False
							new_p.fill_pointT = New fill_pointT
							new_p\x = point_x
							new_p\y = point_y + 1
						EndIf
					Else
						add_below = True
					EndIf
				EndIf
				
				; Fill pixel and move on to next if it's fillable.
				pix_fill(point_x,point_y) = 1
				total_r = total_r + pix_rgb(point_x,point_y,0)
				total_g = total_g + pix_rgb(point_x,point_y,1)
				total_b = total_b + pix_rgb(point_x,point_y,2)
				total_points = total_points + 1
				
				point_x = point_x + 1
				
				If point_x >= image_width Then Exit
				
				If (Not match(base_h,base_s,base_l,point_x,point_y)) Then Exit
			Forever
			
		EndIf
		
		; Remove the processed fill point from the queue.
		Delete p
		
		p.fill_pointT = First fill_pointT
	Wend

End Function


;
;
;
Function rgb_to_hsl(ir%, ig%, ib%)

	; Scale RGB down to unit range (0-1).
	r# = ir/255.0
	g# = ig/255.0
	b# = ib/255.0
	
	If r > g
		max_is = 0
		max_color# = r
		min_color# = g
	Else
		max_is = 1
		max_color# = g
		min_color# = r
	EndIf
	
	If b > max_color
		max_is = 2
		max_color = b
	ElseIf b < min_color
		min_color = b
	EndIf
	
	; Luminance.
	result_l = (max_color + min_color) / 2.0

	If max_color = min_color
		; Color is grey.
		result_s = 0
		;result_h = 0
	Else
		delta# = (max_color - min_color)

		; Saturation.
		If result_l < 0.5
			result_s = delta / (max_color + min_color)
		Else
			result_s = delta / (2.0 - max_color - min_color)
		EndIf
	
		; Hue.
		Select max_is
		Case 0
			; Red.
			result_h = (g - b) / delta
		Case 1
			; Green.
			result_h = 2.0 + (b - r) / delta
		Case 2
			; Blue.
			result_h = 4.0 + (r - g) / delta
		End Select

		result_h = result_h * 60.0
		If result_h < 0 Then result_h = result_h + 360.0
	EndIf

	; Scale back to CP_HSL_SCALE range.
	result_h = ((result_h/360.0) * 200)
	result_s = (result_s * 200)
	result_l = (result_l * 200)

End Function


It kind of works, I see what you are trying to do.

Thanks.

I was going to develop it further by first creating a greyscale version of the image. This would (I think) help to identify areas of similar colour because there are only 256 greys (even in 32bit images). Then, I was going to use this greyscale image to perform the fill search on the real image.

Still not convinced it would have worked any better, but I didn't have time to try it, anyway. :)