最近在做一个游戏,里面涉及到了手势识别(比较复杂的手势),由于自身数学并不好。。去网上搜了一番,找到了这个$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:模板可以自定义,按照上面的格式添加进数组就可以,亲测可用。但是对于某些简单的手势识别概率不是太大,而且模板太多的话执行效率貌似也不高,求来个大大给优化一下。。