Here is a little project that analyze the pixels of an image to track an object of a specific color:
-- Video Tracking
-- INSTRUCTION :
-- 1. Take an object of the same colour as the Target Colour.
-- 2. Move the object in front of the camera to draw on the screen.
-- 3. You can adjust the Target Colour and other parameters in the parameters field.
-- Avoid using the provided restart button, can cause Camera glitches.
function setup()
-- We use backingMode RETAINED to have a better result
backingMode(RETAINED)
-- We set the Camera Source to the Front Camera
cameraSource(CAMERA_FRONT)
-- We show the FrameRate
parameter.watch( "math.floor(1/DeltaTime)" )
-- This parameter controle the background
parameter.boolean("Camera", true)
-- This parameter controle the Camera source
parameter.action("Front Camera", function() cameraSource(CAMERA_FRONT) end)
parameter.action("Back Camera", function() cameraSource(CAMERA_BACK) end)
-- This parameter controle the Sensibility
parameter.integer("Sensibility", 0, 200, 170)
-- This parameter controle the Target Colour
parameter.color("Target",color(0, 255, 0, 255))
-- This parameter controle the Drawing Color
parameter.color("DrawingColor",color(255, 187, 0, 255))
-- This parameter controle the Pen Size
parameter.integer("PenSize", 1, 100, 30)
-- This parameter reset the canvas
parameter.action("Reset", function() canvas = image(WIDTH,HEIGHT) end)
-- Because Codea is running on Ipad, we are not looking through all pixels
-- We are looking for one pixel out of 7
-- If your Ipad is more powerful, you can change this number
Scale = 7
-- We reset our canvas.
canvas = image(WIDTH,HEIGHT)
-- We print the Instructions.
print([[INSTRUCTION :
1. Take an object of the same colour as the Target Colour.
2. Move the object in front of the camera to draw on the screen.
3. You can adjust the Target Colour and other parameters in the parameters field.
Avoid using the provided restart button, can cause Camera glitches.
]])
end
function draw()
-- If the Camera variable is set to true, we use the Camera image as a background.
if Camera then
sprite(CAMERA, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
end
-- We draw a background with a low alpha to have the Camera image less showy.
fill(41, 41, 41, 193)
rect(-5, -5, WIDTH+10, HEIGHT+10)
-- We convert the Camera input into an image to habe acces to the pixels.
img = image(CAMERA)
-- Because the Camera is not always accessible, we check for an image before processing it.
if img then
-- We obtaint the Camera Width and Height.
imgWidth,imgHeight = spriteSize(img)
-- We reset our averages values.
avgX = 0
avgY = 0
-- We reset our count.
count = 0
-- We look through every pixels.
for x = 1,imgWidth/Scale do
for y = 1,imgHeight/Scale do
-- We check if the colour distance of the current pixel is under the Sensibility variable.
if (colorDist(color(img:get(x*Scale, y*Scale)), Target)) < Sensibility then
-- If the Camera variable is set to true, we draw circles at location of the pixels of the Target colour
if Camera then
fill(Target.r, Target.g, Target.b, 217)
noStroke()
ellipse(map(x*Scale, 1, imgWidth, 0, WIDTH), map(y*Scale, 1, imgHeight, 0, HEIGHT), Scale)
end
-- We add the position of our pixel to the average valule
avgX = avgX + (x*Scale)
avgY = avgY + (y*Scale)
-- We increment our count
count = count + 1
end
end
end
-- We draw our canvas
sprite(canvas,WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
-- We check if we have some pixels of the Target Colour.
if count > 0 then
-- We calculate our average value to determine the middle point.
avgX = avgX / count
avgY = avgY / count
-- If we don't have the value of the precedent point, we set it to the actual point.
if not aX and not aY then
aX = map(avgX, 1, imgWidth, 0, WIDTH)
aY = map(avgY, 1, imgHeight, 0, HEIGHT)
end
-- We draw a circle of the Target Colour at the avergage point.
strokeWidth(2)
stroke(14, 14, 14, 255)
fill(Target)
ellipse(map(avgX, 1, imgWidth, 0, WIDTH), map(avgY, 1, imgHeight, 0, HEIGHT), PenSize)
-- We obtaint the canvas Width and Height.
canvasWidth, canvasHeight = spriteSize(canvas)
-- We edit our canvas.
setContext(canvas)
-- We draw a circle of the drawing colour at the avergage point.
fill(DrawingColor)
ellipse(map(avgX, 1, imgWidth, 0, canvasWidth), map(avgY, 1, imgHeight, 0, canvasHeight), PenSize)
-- We draw a line between the avergage point and the ancien average point.
stroke(DrawingColor)
strokeWidth(PenSize)
line(map(avgX, 1, imgWidth, 0, canvasWidth), map(avgY, 1, imgHeight, 0, canvasHeight), aX, aY)
noStroke()
setContext()
-- We set the value of the ancien average point to the current average point.
aX = map(avgX, 1, imgWidth, 0, canvasWidth)
aY = map(avgY, 1, imgHeight, 0, canvasHeight)
else
-- If we don't have some pixels of the Target Colour, we reset the value of the ancien average point.
aX = nil
aY = nil
end
else
-- If the Camera is not avaible, we draw a little message
fill(255, 255, 255, 97)
textAlign(CENTER)
text("Camera not available \
please wait", WIDTH/2, HEIGHT/2)
end
-- We indicate the FrameRate.
fill(255, 255, 255, 97)
w,h = textSize("FrameRate "..math.floor(1/DeltaTime))
text("FrameRate "..math.floor(1/DeltaTime),w/2, HEIGHT-(h/2))
-- If we have some pixels, we indicate the number of pixels we have.
if count then
w,h = textSize("Pixels "..count)
text("Pixels "..count,w/2, HEIGHT-h-10)
end
-- We draw a little message to indicate to the user that he can double tap the screen to reset.
w,h = textSize("Double tap to reset")
text("Double tap to reset",w/2, HEIGHT-50)
end
function touched(t)
-- If we have a Camera input and if the user double tap, we reset our canvas.
if img and t.tapCount == 2 then
canvas = image(WIDTH,HEIGHT)
end
end
function colorDist(c1,c2)
-- This function calculate the distance between two colours using the Euclidian Distance
return math.sqrt(((c2.r-c1.r)^2)+((c2.g-c1.g)^2)+((c2.b-c1.b)^2))
end
function map( value, start1, stop1, start2, stop2 )
-- This function re-maps a number from one range to another.
local norm = (value - start1) / (stop1 - start1)
norm = norm * (stop2 - start2) + start2
return norm
end