Simple character recognition input demo

A demo of character recognition. Currently recognises 26 lower case letters, a full stop and a hyphen, but more letters could be added.
Draw above the line for lower case otherwise the upper case letter will be displayed. Each letter is comprised of a single stroke so no dotting the i’s or crossing the t’s.

I’ve also written a " learner" module to allow new letters to be learned based on the user inputting 20 examples. If there’s interest I could post it as well.

In 2 halves due to size.


--Simple character recognition
--Written by West
--July 2012
--Draw lower case letters with only a single continuous line.
--no dotting the i's or crossing the t's!
--Draw any part of the letter below the line to return an UPPPER CASE letter.
function setup()
    version = 1.0
   saveProjectInfo("Description", "Simple character recognition input routine")
   saveProjectInfo("Author", "West")
   saveProjectInfo("Date", "July 2012")
   saveProjectInfo("Version", version)
    displayMode(FULLSCREEN)
    xpoints={}
    ypoints={}
    i=0
    d=1
chars={" "," "," "," "," "}
    gx=10
    gy=10
    gridxmax=5
    gridymax=5
    input={}
    for a=0,4 do
        input[a] = {}
        for b=0,4 do
            input[a][b]=-1                
        end
    end
    backingMode(STANDARD)

end

function draw()
    noSmooth()
    background(0, 0, 0, 0)
    noStroke()
    if CurrentTouch.state == BEGAN then
        fill(16, 178, 197, 255)
        xmax=CurrentTouch.x
        xmin=CurrentTouch.x
        ymax=CurrentTouch.y
        ymin=CurrentTouch.y
        xpoints={}
        ypoints={}
        i=0
        d=0
        specialCase=""
    elseif CurrentTouch.state == MOVING then
        xpoints[i]=CurrentTouch.x
        ypoints[i]=CurrentTouch.y
        i = i + 1
        fill(255, 0, 0, 255)
    elseif CurrentTouch.state == ENDED and d==0 then
        fill(210, 218, 16, 255)
        if i>2 then
        --find the extents of the drawn letter
            for c=0,i-1 do
                if xpoints[c]>xmax then
                    xmax=xpoints[c]
                    xmaxpos=c
                end
                if ypoints[c]>ymax then
                    ymax=ypoints[c]
                end
                if xpoints[c]<xmin then
                    xmin=xpoints[c]
                    xminpos=c
                end
                if ypoints[c]<ymin then
                    ymin=ypoints[c]
                end   
            end  
        end
        xrange=xmax-xmin
        yrange=ymax-ymin
        --deal with some special cases
        if xrange==0 then
            -- vertical line so set as i
            specialCase="i"
        elseif yrange/xrange>8 then
            -- thin vertical line so set as i
               specialCase="i"
        
        elseif yrange==0 then
            -- horizontal line so set as -
            specialCase="-"
        elseif xrange/yrange>8 then
            specialCase="-"
        end
        if i<3 then
        --short tap so set as full stop
            specialCase="."
        end
        input={}
        fill(24, 37, 226, 255)
        --cycle through the points in the gesture and set the 5x5 grid location 
        --to 1 if the point is in that location
        for gridx=0,gridxmax-1 do
            input[gridx]={}
            for gridy=0,gridymax-1 do
                input[gridx][gridy]=0
                for c=0,i-1 do
                    if (xpoints[c]-xmin)/xrange>(gridx/gridxmax) and (xpoints[c]-xmin)/xrange<((gridx+1)/gridxmax) and (ypoints[c]-ymin)/yrange>(gridy/gridymax) and (ypoints[c]-ymin)/yrange<((gridy+1)/gridymax) then
                        input[gridx][gridy]=1
                    end
                end
            end
        end
    end
    if d==0 then
        --draw a marker for the gesture
        ellipse(CurrentTouch.x, CurrentTouch.y, 30,30)
        --comment in to leave a trail
        -- for c=1,i-2 do
        --    line(xpoints[c],ypoints[c],xpoints[c+1],ypoints[c+1])
        --end
    end

And the second half


letter={}
    sc={}
    charac={"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"}
    letter[1]={{-1,-1,-1,-1,-1},{1,-1,1,-1,-1},{-1,0,-1,1,-1},{1,0,0,1,-1},{1,1,1,1,1}}
    letter[2]={{1,1,-1,1,1},{1,-1,-1,-1,1},{1,-1,-1,-1,-1},{1,0,0,0,0},{1,-1,0,0,0}}
    letter[3]={{1,1,-1,1,-1},{1,0,0,0,-1},{-1,0,0,0,0},{1,0,0,0,0},{-1,1,-1,1,-1}}
    letter[4]={{1,1,1,-1,-1},{1,0,-1,-1,-1},{-1,1,1,1,1},{0,0,0,-1,-1},{0,0,0,-1,-1}}
    letter[5]={{1,1,-1,1,-1},{-1,-1,-1,-1,0},{1,-1,-1,-1,-1},{1,-1,0,0,1},{0,-1,1,-1,1}}
    letter[6]={{1,1,-1,0,0},{0,-1,-1,0,0},{0,-1,-1,0,0},{0,-1,-1,0,1},{0,-1,1,1,1}}
    letter[7]={{1,1,-1,-1,1},{0,0,0,0,1},{-1,1,1,-1,1},{1,-1,-1,1,-1},{-1,1,1,1,-1}}
    letter[8]={{1,0,0,0,1},{1,-1,0,-1,1},{1,-1,1,1,-1},{1,0,0,0,0},{-1,0,0,0,0}}
    letter[9]={{1,-1,0,0,0},{-1,-1,0,0,0},{-1,-1,-1,0,0},{0,-1,-1,-1,0},{0,0,-1,-1,-1}}
    letter[10]={{1,1,-1,1,-1},{-1,0,0,0,1},{0,0,0,0,-1},{0,0,0,0,-1},{0,0,0,0,1}}
    letter[11]={{1,-1,-1,1,1},{1,1,-1,0,0},{1,-1,1,-1,0},{-1,-1,0,-1,0},{-1,-1,0,0,0}}
    letter[12]={{1,1,1,1,1},{-1,0,0,-1,-1},{1,0,0,0,0},{1,-1,0,0,0},{-1,-1,-1,0,0}}
    letter[13]={{1,-1,0,0,-1},{1,-1,-1,0,-1},{1,-1,1,0,-1},{1,-1,1,-1,1},{1,1,-1,1,1}}
    letter[14]={{1,0,0,0,-1},{1,0,0,0,-1},{1,-1,0,0,1},{1,-1,-1,-1,1},{1,-1,-1,-1,-1}}
    letter[15]={{1,1,1,1,1},{1,0,0,0,1},{1,0,0,0,1},{1,0,0,0,1},{1,1,1,1,1}} 
    letter[16]={{1,-1,0,0,0},{-1,-1,0,0,0},{-1,1,-1,-1,0},{1,-1,-1,-1,1},{-1,1,1,-1,1}}
    letter[17]={{0,-1,1,-1,0},{0,-1,1,-1,-1},{-1,-1,1,-1,-1},{1,-1,1,-1,0},{1,1,1,-1,-1}}
    letter[18]={{1,0,0,0,0},{1,0,0,0,0},{1,-1,0,0,-1},{1,-1,-1,-1,-1},{-1,-1,-1,-1,-1}}    
    letter[19]={{1,-1,1,1,1},{0,0,0,0,1},{-1,1,1,1,-1},{1,0,0,0,0},{1,1,-1,-1,-1}}
    letter[20]={{0,-1,-1,0,0},{0,-1,-1,0,0},{0,-1,-1,0,0},{0,-1,1,-1,0},{1,1,1,1,1}}
    letter[21]={{-1,1,1,0,1},{-1,0,-1,-1,1},{-1,0,0,1,1},{-1,0,0,-1,1},{1,0,0,0,-1}}
    letter[22]={{0,-1,1,0,0},{0,1,1,-1,0},{-1,-1,-1,1,0},{1,-1,0,-1,-1},{1,0,0,0,1}}    
    letter[23]={{1,-1,-1,1,-1},{1,1,1,-1,-1},{1,-1,1,0,1},{-1,-1,-1,0,1},{-1,0,0,0,1}}
    letter[24]={{1,1,1,1,-1},{-1,1,-1,-1,-1},{0,-1,1,-1,-1},{0,-1,-1,1,-1},{-1,-1,-1,-1,1}}
    letter[25]={{1,-1,-1,1,1},{0,-1,-1,-1,-1},{-1,-1,-1,1,1},{-1,-1,0,-1,1},{-1,-1,0,-1,1}}
    letter[26]={{1,1,1,1,1},{-1,1,0,0,0},{0,-1,1,-1,0},{0,0,-1,1,-1},{1,1,1,1,-1}}    
    defo={}
    for t=1,#charac do
        --count the number of -1's ignoring the 1's and 0's
        defototal=0
        sc[t]=0
        for a=1,5 do
            for b=1,5 do
                if letter[t][a][b]==-1 then
                defototal=defototal+1
                end
            end
        end
        --normalise
        defo[t]=25-defototal
    end
 
    if d==0 and CurrentTouch.state==ENDED then
        --shift characters for printing along
        for charpos=1,4 do
            chars[charpos]=chars[(charpos+1)]
        end
        if specialCase=="" then        
            for a=0,gridxmax-1 do
                for b=0,gridymax-1 do
                    for co=1,#sc do
                        if input[a][b]==letter[co][b+1][a+1] then
                            sc[co] = sc[co] + 1
                        end
                    end
                end
            end
            scmax=sc[1]/defo[1]        
            chars[5]=charac[1]
            for scount=1,#sc do
                if sc[scount]/defo[scount]>scmax then
                    scmax=sc[scount]/defo[scount]
                    chars[5]=charac[scount]
                end
            end
        --overwrite with special cases
         else
            chars[5]=specialCase
         end
        --test to see if the letter crosses the uppercase boundary
        if(ymin<HEIGHT/3) then
            chars[5]=string.upper(chars[5])
        end
 --change flag to say this has been done so no need to repeat every draw()       
        d=1
    end
    --draw the uppercase boundary line
    line(0,HEIGHT/3,WIDTH,HEIGHT/3)
    font("ArialRoundedMTBold")    
    for charpos=1,5 do
    fill(245, 103, 3, ((51*charpos)))
    fontSize(120)
    text(""..chars[charpos],(100*charpos)+WIDTH/5,HEIGHT-100)
        end
 
--display the activated segments
    if  CurrentTouch.state==ENDED then
        gridxmax=5
        gridymax=5
        for a=0,gridxmax-1 do
            for b=0,gridymax-1 do
                if input[a][b]==1 then 
                    fill(235, 215, 5, 255)
                else
                    fill(65, 46, 46, 255)
                end
                rect(a*gx,(b*gy)+50,10,10)
            end
        end
    end
end

Wow, this is fun, @West! I was wondering how you got the example templates. I’d love to see the learning version.

Here’s the letter learner. You’ll need to manually note down the output and enter it to the first program. It’s very rough and ready and the formatting could be better but should work. Hopefully it’s straightforward but give us a shout if you need anything explained.

--Learner function to train a specific letter
--Draw the letter 20 times to get the grid representation.
--grid is printed in the sequence needed for the other program, 
--aaaaa
--bbbbb
--ccccc
--ddddd
--eeeee
--letter[]={{aaaaa},{bbbbb},{ccccc},{ddddd},{eeeee}}
--Confusingly aaaaa refers to the bottom row
function setup()
    xpoints={}
    ypoints={}
    i=0
    d=1
    overall={}
    counter=0
         for a=0,4 do
             overall[a] = {}
            for b=0,4 do
                 overall[a][b]=0                
                end               
            end
        gx=10
        gy=10
        gridxmax=5
        gridymax=5
    --backingMode(RETAINED)
     backingMode(STANDARD)
    print("Recognising letters")
end

function draw()
    noSmooth()
    background(0, 0, 0, 0)
    noStroke()

    if CurrentTouch.state == BEGAN then
        fill(16, 178, 197, 255)
        xmax=CurrentTouch.x
        xmin=CurrentTouch.x
        ymax=CurrentTouch.y
        ymin=CurrentTouch.y
               
    xpoints={}
    ypoints={}
    i=0
    d=0
    winner=" "
    elseif CurrentTouch.state == MOVING then
        xpoints[i]=CurrentTouch.x
        ypoints[i]=CurrentTouch.y
        i = i + 1
        
        fill(255, 0, 0, 255)
    elseif CurrentTouch.state == ENDED and d==0 then
        fill(210, 218, 16, 255)
        --process the completed touch
        --get the max and min x and y then rescale to normalise
        -- ignore single taps
             if i>2 then
        for c=0,i-1 do
            if xpoints[c]>xmax then
                xmax=xpoints[c]
                xmaxpos=c
            end
            if ypoints[c]>ymax then
                ymax=ypoints[c]
            end
            if xpoints[c]<xmin then
                xmin=xpoints[c]
                xminpos=c
            end
            if ypoints[c]<ymin then
                ymin=ypoints[c]
            end   

        end  
        end
    
        xrange=xmax-xmin
        yrange=ymax-ymin
   
input={}
  fill(24, 37, 226, 255)
        
        for gridx=0,gridxmax-1 do
            input[gridx]={}
            for gridy=0,gridymax-1 do
                 input[gridx][gridy]=0
                for c=0,i-1 do
                
                    if (xpoints[c]-xmin)/xrange>(gridx/gridxmax) and (xpoints[c]-xmin)/xrange<((gridx+1)/gridxmax) and (ypoints[c]-ymin)/yrange>(gridy/gridymax) and (ypoints[c]-ymin)/yrange<((gridy+1)/gridymax) then
                        fill(255, 196, 0, 255)
                    input[gridx][gridy]=1
                    end
                end           
                rect(gridx*gx,gridy*gy,10,10)
                fill(24, 37, 226, 255)
            end
        end

    if d==0 and CurrentTouch.state==ENDED then
        for a=0,gridxmax-1 do
            for b=0,gridymax-1 do
                if input[a][b]==1 then
                  overall[a][b] = overall[a][b] + 1
                end            
            end
        end
end
   end

    ellipse(CurrentTouch.x, CurrentTouch.y, 30,30)

    --run once for each new letter
    if d==0 and CurrentTouch.state==ENDED then
    counter = counter + 1
          print(""..counter)
        if counter==20 then
              temp={}
            for a=0,4 do
                temp[a]={}
               for b=0,4 do
                if overall[a][b]>17 then
                temp[a][b]=1
                elseif overall[a][b]<3 then
                    temp[a][b]=0
                    else
                        temp[a][b]=-1
                end
                end           
                end               
                for a=0,4 do
                    print(temp[0][a],temp[1][a],temp[2][a],temp[3][a],temp[4][a])
                    --prints out from bottom to top.
                    -- in the other prog the first line printed should be the first in the curly brackets
                end          
            end     
        d=1
        end
        fill(245, 103, 3, 255)
          fontSize(300)
        font("ArialRoundedMTBold")

    if  CurrentTouch.state==ENDED then
        gridxmax=5
        gridymax=5    
        for a=0,gridxmax-1 do
            for b=0,gridymax-1 do               
                  fill(255,(overall[a][b]*10),(overall[a][b]*20),255)   
                 rect(a*gx,b*gy,10,10)               
            end
        end          
        end
end

Thanks @West, I had fun seeing how that worked!

That is way cool @West. Thanks for sharing demo.

Thanks @west very helpful