$1 Unistroke Recognizer( lua )

  最近在做一个游戏,里面涉及到了手势识别(比较复杂的手势),由于自身数学并不好。。去网上搜了一番,找到了这个$1识别算法,http://depts.washington.edu/aimgroup/proj/dollar/ 这是官网,发现这是目前网上比较好的一种手势识别算法,而且可以添加自定义手势模板。然后发现源码下载里面没有Lua的版本T_T,,,无奈之下,把js版本的大概翻译了一下,现在发上来好了。
  

local Recognizer = class("Recognizer")

---
-- Point 
--
local function Point(x,y)
--    local pt = { X=0,Y=0 }
--    pt.X = x
--    pt.Y = y
    return { X=x,Y=y }
end

---
-- Rectangle
--
local function Rectangle(x,y,width,height)

    local rectangle = {}
    rectangle.X = x
    rectangle.Y = y
    rectangle.Width = width
    rectangle.Height = height

    return rectangle 
end

---
-- deg
local function Deg2Rad(d)
    return (d * math.pi / 180.0)
end


local NumUnistrokes = 16;
local NumPoints = 64;
local SquareSize = 250.0;
local Origin = Point(0,0);
local Diagonal = math.sqrt(SquareSize * SquareSize + SquareSize * SquareSize);
local HalfDiagonal = 0.5 * Diagonal;
local AngleRange = Deg2Rad(45.0);
local AnglePrecision = Deg2Rad(2.0);
local Phi = 0.5 * (-1.0 + math.sqrt(5.0)) -- Golden Ratio

---
-- distance
local function Distance(p1, p2)
    local dx = p2.X - p1.X;
    local dy = p2.Y - p1.Y;
    return math.sqrt(dx * dx + dy * dy);
end

---
-- pathLength
local function PathLength(points)
    local d = 0.0;
    for i = 2 ,#points,1 do
        d = d + Distance(points[i - 1], points[i])
    end
    return d
end

---
-- pathDistance
local function PathDistance(pts1, pts2)
    local d = 0.0
    for i = 1,#pts1,1 do -- assumes pts1.length == pts2.length
        d = d + Distance(pts1[i], pts2[i])
    end
    return d / #pts1;
end

---
-- boundingbox
local function BoundingBox(points)
    local minX = math.huge
    local maxX = -math.huge
    local minY = math.huge
    local maxY = -math.huge
    for i = 1,#points, 1 do
        minX = math.min(minX, points[i].X)
        minY = math.min(minY, points[i].Y)
        maxX = math.max(maxX, points[i].X)
        maxY = math.max(maxY, points[i].Y)
    end
    return Rectangle(minX, minY, maxX - minX, maxY - minY)
end

---
-- centroid 矩心❤
local function Centroid(points)
    local x = 0.0
    local y = 0.0
    for i = 1,#points,1 do
        x = x + points[i].X
        y = y + points[i].Y
    end
    x = x / #points
    y = y / #points
    return Point(x, y)
end

local function RotateBy(points, radians) -- rotates points around centroid
    local c = Centroid(points)
    local cos = math.cos(radians)
    local sin = math.sin(radians)
    local newpoints = {}
    for i = 1,#points,1 do
        local qx = (points[i].X - c.X) * cos - (points[i].Y - c.Y) * sin + c.X
        local qy = (points[i].X - c.X) * sin + (points[i].Y - c.Y) * cos + c.Y;
        newpoints[#newpoints+1] = Point(qx, qy);
    end
    return newpoints;
end

---
--distanceAtangle
local function DistanceAtAngle(points, T, radians)
    local newpoints = RotateBy(points, radians)
    return PathDistance(newpoints, T.Points)
end
---
--DistanceAtBestAngle
local function DistanceAtBestAngle(points, T, a, b, threshold)
    local x1 = Phi * a + (1.0 - Phi) * b;
    local f1 = DistanceAtAngle(points, T, x1);
    local x2 = (1.0 - Phi) * a + Phi * b;
    local f2 = DistanceAtAngle(points, T, x2);
    while math.abs(b - a) > threshold do
        if (f1 < f2) then
            b = x2;
            x2 = x1;
            f2 = f1;
            x1 = Phi * a + (1.0 - Phi) * b;
            f1 = DistanceAtAngle(points, T, x1);
        else
            a = x1;
            x1 = x2;
            f1 = f2;
            x2 = (1.0 - Phi) * a + Phi * b;
            f2 = DistanceAtAngle(points, T, x2);
        end
    end
    return math.min(f1, f2);
end

---
--OptimalCosineDistance
local function OptimalCosineDistance(v1, v2) -- for Protractor
    local a = 0.0;
    local b = 0.0;
    for i = 1,#v1,2 do
--        print("           "..i.."              "..#v1.."     "..#v2)
        a = a + v1[i] * v2[i] + v1[i + 1] * v2[i + 1]
        b = b + v1[i] * v2[i + 1] - v1[i + 1] * v2[i]
    end
    local angle = math.atan(b / a)
    return math.acos(a * math.cos(angle) + b * math.sin(angle));
end

---
--Vectorize
local function Vectorize(points) -- for Protractor
    local sum = 0.0;
    local vector ={}
    for i = 1,#points,1 do
        vector[#vector+1] = points[i].X;
        vector[#vector+1] = points[i].Y;
        sum = sum + points[i].X * points[i].X + points[i].Y * points[i].Y;
    end
    local magnitude = math.sqrt(sum);
    for i = 1,#vector,1 do
        vector[i] = vector[i] / magnitude;
    end
    return vector;
end

---
--TranslateTo
local function TranslateTo(points, pt) -- translates points' centroid
    local c = Centroid(points)
    local newpoints = {}
    for i = 1,#points,1 do
        local qx = points[i].X + pt.X - c.X;
        local qy = points[i].Y + pt.Y - c.Y;
        newpoints[#newpoints+1] = Point(qx, qy);
    end
    return newpoints;
end

---
--ScaleTo
local function ScaleTo(points, size) -- non-uniform scale; assumes 2D gestures (i.e., no lines)
    local B = BoundingBox(points)
    local newpoints = {}
    for i = 1,#points,1 do
        local qx = points[i].X * (size / B.Width)
        local qy = points[i].Y * (size / B.Height)
        newpoints[#newpoints+1] = Point(qx, qy);
    end
    return newpoints;
end

---
--RotateBy
local function RotateBy(points, radians) -- rotates points around centroid
    local c = Centroid(points);
    local cos = math.cos(radians);
    local sin = math.sin(radians);
    local newpoints = {}
    for i = 1,#points,1 do
        local qx = (points[i].X - c.X) * cos - (points[i].Y - c.Y) * sin + c.X
        local qy = (points[i].X - c.X) * sin + (points[i].Y - c.Y) * cos + c.Y
        newpoints[#newpoints+1] = Point(qx, qy)
    end
    return newpoints;
end

---
--IndicativeAngle
local function IndicativeAngle(points)
    local c = Centroid(points)
    return math.atan2(c.Y - points[1].Y, c.X - points[1].X)
end

---
-- Private helper functions from this point down
--
local function Resample(points, n)
    local I = PathLength(points) / (n - 1) -- interval length
    local D = 0.0
    local newpoints = { points[1] }
--    print("           ijjhhhhhhh
    local k = 2
    while k<= #points do
        local d = Distance(points[k - 1], points[k])
--        print("         "..I)
        if D + d >= I then
--            print("         "..points[k - 1].X + ((I - D) / d) * (points[k].X - points[k - 1].X)) 
            local qx = points[k - 1].X + ((I - D) / d) * (points[k].X - points[k - 1].X);
            local qy = points[k - 1].Y + ((I - D) / d) * (points[k].Y - points[k - 1].Y);
--            print("   "..points[k - 1].X.."   "..I.."   "..D.."   "..d.."   "..points[k].X.."  "..points[k - 1].X) 
            local q = Point(qx, qy)
--            print("       "..q.X.."            "..q.Y.."            "..k-1) 
            newpoints[#newpoints+1] = q; -- append new point 'q'
--            print("              "..#newpoints.."             "..k.."           "..#points)
            table.insert(points,k,q)  --            points.splice(k, 0, q) -- insert 'q' at position k in points s.t. 'q' will be the next k
            D = 0.0;
        else
            D = D + d;
--            print("              "..#newpoints.."             "..k.."           "..#points)
        end
        k = k + 1
    end
    if #newpoints == n - 1 then -- somtimes we fall a rounding-error short of adding the last point, so add it if so
        newpoints[#newpoints+1] = Point(points[#points].X, points[#points].Y)
    end
    return newpoints;
end
---
-- Unistroke class: a unistroke template
--
local function Unistroke(name, points) -- constructor
    local uni = {}
    uni.Name = name;
    uni.Points = Resample(points, NumPoints);
    local radians = IndicativeAngle(uni.Points);
    uni.Points = RotateBy(uni.Points, -radians);
    uni.Points = ScaleTo(uni.Points, SquareSize);
    uni.Points = TranslateTo(uni.Points, Origin);
    uni.Vector = Vectorize(uni.Points); -- for Protractor

    return uni
end
---
-- Result class
--
local function Result(name, score) --constructor
    local re = {}
    re.Name = name;
    re.Score = score;
    return re
end

local tbSign = { "Heng" } --  手势的名称

---
-- DollarRecognizer class
--
function Recognizer:DollarRecognizer() -- constructor

    local mUnis = { Unistrokes={} }
    --
    -- one built-in unistroke per gesture type
    --
    mUnis.Unistrokes = {}
    --模板
    mUnis.Unistrokes[1] = Unistroke(tbSign[3],{ Point(80,304)
,Point(82,297)
,Point(87,281)
,Point(95,261)
,Point(102,236)
,Point(117,197)
,Point(128,173)
,Point(137,152)
,Point(140,146)
,Point(144,138)
,Point(145,134)
,Point(148,128)
,Point(149,126)
,Point(150,123)
,Point(151,121)
,Point(152,121)
,Point(153,117)
,Point(157,112)
,Point(159,109)
,Point(162,106)
,Point(162,105)
,Point(165,114)
,Point(173,131)
,Point(184,143)
,Point(197,164)
,Point(205,180)
,Point(213,200)
,Point(223,220)
,Point(228,242)
,Point(234,258)
,Point(238,271)
,Point(240,278)
,Point(243,285)
,Point(247,292)
,Point(251,297)
,Point(251,298)
,Point(251,298)});    

    --
    -- The $1 Gesture Recognizer API begins here -- 3 methods: Recognize(), AddGesture(), and DeleteUserGestures()
    --
--    mUnis.Recognize = function(points, useProtractor)
    function mUnis:Recognize(points,useProtractor)
        local tbResult = {}
        points = Resample(points, NumPoints);
        local radians = IndicativeAngle(points);
        points = RotateBy(points, -radians);
        points = ScaleTo(points, SquareSize);
        points = TranslateTo(points, Origin);
        local vector = Vectorize(points); -- for Protractor

        local b = math.huge
        local u = -1
        for i = 1,#mUnis.Unistrokes,1 do -- for each unistroke
            local d;
            if (useProtractor) then -- for Protractor
                d = OptimalCosineDistance(mUnis.Unistrokes[i].Vector, vector);
            else -- Golden Section Search (original $1)
                d = DistanceAtBestAngle(points, mUnis.Unistrokes[i], -AngleRange, AngleRange, AnglePrecision)
            end

            if (d < b) then
                b = d; -- best (least) distance
                u = i; -- unistroke
            end
        end
        if u == -1 then
            return Result("No match.",0.0)
        else
            if useProtractor == true then
                return Result( mUnis.Unistrokes[u].Name,1.0 / b )
            else
                return Result( mUnis.Unistrokes[u].Name,1.0 - b / HalfDiagonal )
            end
        end
--        return (u == -1) ? new Result("No match.", 0.0) : new Result(this.Unistrokes[u].Name, useProtractor ? 1.0 / b : 1.0 - b / HalfDiagonal);
    end

----    this.AddGesture = function(name, points)
--    function mUnis:AddGesture(name,points)
--        mUnis.Unistrokes[#mUnis.Unistrokes+1] = Unistroke(name, points) -- append new unistroke
--        local num = 0;
--        for i = 1,#mUnis.Unistrokes,1 do
--            if mUnis.Unistrokes[i].Name == name then
--                num = num + 1
--            end
--        end
--        return num;
--    end
----    this.DeleteUserGestures = function()
--    function mUnis:DeleteUserGestures()
----        #mUnis.Unistrokes = NumUnistrokes; -- clear any beyond the original set
--        for i=#mUnis.Unistrokes ,NumUnistrokes,-1 do
--            table.remove(mUnis.Unistrokes,i)
--        end
--        return NumUnistrokes;
--    end

    return mUnis
end
return Recognizer

ps:模板可以自定义,按照上面的格式添加进数组就可以,亲测可用。但是对于某些简单的手势识别概率不是太大,而且模板太多的话执行效率貌似也不高,求来个大大给优化一下。。

你可能感兴趣的:(游戏开发)