Color Tracking - Project

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

@dadaroulin That’s a really interesting program. Works great.

@dadaroulin just FYI it works great on the iPhone in landscape mode but in portrait mode the tracking is a little off.