These days I was interesting in motion detection on Codea, we all know that iPad have very powerful GPU, so I want to try to use it to do some interesting thing, for examples, to do the real-time motion detection woth shader, and after a few tests, the baseline is OK.
Here is the demo(a video 37MB):
http://v.youku.com/v_show/id_XMjY0ODEwMDYzNg==.html
Here is the code:
-- MotionDetect V1.00 20170318 Baseline
-- GrabMotion
-- ??????
function setup()
mo = Motion
-- ????????????????
mo.init()
end
function draw()
background(40, 40, 50)
-- ???? starting detect
mo.detect()
-- ??????????????????? add rect on the motion area
mo.addRect()
end
-- ???????
Motion = {}
local mo = Motion
function Motion.init()
displayMode(OVERLAY)
spriteMode(CORNER)
memory = 0
-- ?????????? adjust the sub of two colors
parameter.number("deltaColor",0,0.5,0.2)
-- parameter.number("dScaler",2,20,10)
parameter.watch("memory")
parameter.watch("1/DeltaTime")
parameter.watch("ElapsedTime")
parameter.watch("os.clock()")
-- parameter.watch("period1")
cameraSource(CAMERA_FRONT)
-- cameraSource(CAMERA_BACK)
img = image(CAMERA)
print(img)
-- ??????????????? set some parameter in the rect function
scaler = 10
threshold = 20
-- ?????????? keep the coordinate of the motion area
cdx,cdy = {},{}
-- ?? mesh????? GPU create mesh, prepare using GPU to do the detect job
m = mesh()
mp = mesh()
ma = mesh()
tex = image(WIDTH,HEIGHT)
period1 = ElapsedTime
-- period1 = os.clock()
-- ????????????
delayNextFrame = 10/60
-- ????????? delayNextFrame
period2 = period1 + delayNextFrame
img0 = image(WIDTH/1,HEIGHT/1)
img1 = image(WIDTH/1,HEIGHT/1)
img2 = image(WIDTH/1,HEIGHT/1)
img3 = image(WIDTH/1,HEIGHT/1)
img4 = image(WIDTH/scaler,HEIGHT/scaler)
local w,h = img1.width, img1.height
-- ?????? detect motion
m:addRect(WIDTH/2,HEIGHT/2,WIDTH/1,HEIGHT/1)
m.shader = shader(myShader.vs,myShader.fs)
-- m.texture = img1
m.shader.tex0 = img0
m.shader.tex1 = img1
m.shader.tex2 = img2
-- ???????????? add rect to show where it action
ma:addRect(WIDTH/2,HEIGHT/2,WIDTH/1,HEIGHT/1)
ma.shader = shader(myShader.vs,myShader.fs1)
ma.shader.resolution = vec2(WIDTH,HEIGHT)
ma.shader.tex0 = img3
-- ??????
mp:addRect(WIDTH/2+200,HEIGHT/2,WIDTH/3,HEIGHT/3)
mp.texture = img2
end
function Motion:detect()
pushStyle()
spriteMode(CORNER)
-- ????????
memory = string.format("%.3f Mb",collectgarbage("count")/1024)
-- ????????
m.shader.deltaColor = deltaColor
-- ????????? img0
setContext(img0)
sprite(CAMERA,0,0,WIDTH,HEIGHT)
setContext()
-- ??? draw ?????? DeltaTime
-- currentT = os.clock()
currentT = ElapsedTime
-- ?? draw ????????????? draw ????? 2 ?
local dTPerDraw = DeltaTime
-- ????????????????????? dTPerDraw ???????????
if currentT - period1 > dTPerDraw then
period1 = currentT
setContext(img1)
-- sprite(CAMERA,WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
sprite(CAMERA,0,0,WIDTH,HEIGHT)
-- print("p1: ",period1)
setContext()
end
-- ???????????????
if currentT - period2 > dTPerDraw then
period2 = currentT + delayNextFrame
setContext(img2)
-- sprite(CAMERA,WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
sprite(CAMERA,0,0,WIDTH,HEIGHT)
-- print("p2: ",period2)
setContext()
-- cdx,cdy = {},{}
end
-- ???? m:draw ?? shader ????????????????? img3
setContext(img3)
m:draw()
setContext()
-- ?????????????? img3
sprite(img3,0,0,WIDTH,HEIGHT)
-- ???????
sprite(CAMERA,0,0,WIDTH,HEIGHT)
popStyle()
end
function Motion:addRect()
--[[ ????? ma ?? shader ???????????????????
ma.shader.tex0 = img3
ma:draw()
--]]
-- ??? Codea ?? CPU ???????
-- ???? img4 ??????(?????)?????????
local sw,sh = WIDTH/scaler,HEIGHT/scaler
setContext(img4)
pushMatrix()
sprite(img3,0,0,sw,sh)
popMatrix()
setContext()
-- ??????????
sprite(img4,WIDTH-sw-5,HEIGHT-sh-5,sw,sh)
---[[ ???????????????????????????????
local w,h = img4.width,img4.height
local k= 0
-- ???????????
local sx,sy = 2,2
for x= 1,w,sx do
for y = 1,h,sy do
local r,g,b,a =img4:get(x,y)
if (r==255 and g == 255 and b == 0) then
k = k + 1
if k > threshold then
table.insert(cdx,x)
table.insert(cdy,y)
-- print(cdx[1])
speech.say("act")
-- speech.say("?")
-- sound("Game Sounds One:Blaster")
speech.stop()
k=0
end
end
end
end
-- ??????? cdx,cdy ??????????????????????????
if #cdx ~= 0 and #cdy ~= 0 then
pushStyle()
-- print("#cdx,cdx[1]: ",#cdx,cdx[1])
-- print("#cdy,cdy[1]: ",#cdy,cdy[1])
-- local cx,cy =
table.sort(cdx)
table.sort(cdy)
-- print(cx,#cx)
-- ????????????
local lb,rt= vec2(cdx[1],cdy[1]),vec2(cdx[#cdx],cdy[#cdy])
-- ?????
lb,rt=lb*scaler,rt*scaler
-- print(lb,rt)
-- fill(10, 255, 0, 255)
noFill()
stroke(0, 221, 255, 255)
strokeWidth(5)
-- rectMode(CENTER)
-- ??????????????
local x,y,w,h = lb.x,lb.y,rt.x-lb.x,rt.y-lb.y
rect(x,y,w,h)
sprite("Tyrian Remastered:Flame 1",x,y,w,h)
-- ???????????? cdx,cdy ??????
cdx,cdy = {},{}
popStyle()
end
--]]
end
myShader = {
vs =[[
uniform mat4 modelViewProjection;
//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
//This is an output variable that will be passed to the fragment shader
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
void main()
{
gl_Position = modelViewProjection * position;
vColor = color;
vTexCoord = texCoord;
}
]],
-- ?????????? shader
fs =[[
uniform highp sampler2D tex0;
uniform highp sampler2D tex1;
uniform highp sampler2D tex2;
uniform highp float deltaColor;
//The interpolated vertex color for this fragment
varying lowp vec4 vColor;
//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;
uniform highp float time;
highp float grey(lowp vec4 col)
{
highp float grey = 0.2126*col.r + 0.7152* col.g + 0.0722*col.b;
return grey;
}
void main()
{
highp vec2 uv = vTexCoord;
highp vec4 col,col0,col1,col2;
col0 = texture2D(tex0,uv);
col1 = texture2D(tex1,uv);
col2 = texture2D(tex2,uv);
// ????????????????????????
highp vec4 dCol;
highp float g;
dCol = abs(col1-col2);
g = grey(dCol);
if (g <= deltaColor) { col = col0;
// ?????????????
//col = vec4(0.,0.,0.,1.);
} else {
col = vec4(1.,1.,0.0,1.);
}
gl_FragColor = col;
}
]],
-- ?????????? shader
fs1 =[[
varying highp vec2 vTexCoord;
highp vec4 col,col0,col1,col2;
uniform highp sampler2D tex0;
uniform highp sampler2D tex1;
uniform highp vec2 resolution;
void main() {
highp vec2 uv = vTexCoord;
col0 = texture2D(tex0,uv);
// ???
highp vec2 dl,dr,db,dt;
if (col0.r ==1. && col0.g == 1. && col0.b == 0.) {
highp vec2 step;
step = uv.xy/resolution.xy;
//
bool p =true;
highp float k = 1.;
while (p && k <200.) {
//if (k < 2000.) {
dl = uv-vec2(step.x,0.)*k;
dr = uv+vec2(step.x,0.)*k;
db = uv-vec2(0.,step.y)*k;
dt = uv+vec2(0.,step.y)*k;
highp vec4 l,r,b,t;
l = texture2D(tex0,dl);
r = texture2D(tex0,dr);
b = texture2D(tex0,db);
t = texture2D(tex0,dt);
if ((l.r == 1. && l.g ==1.) || (r.r == 1. && r.g ==1.) || (b.r == 1. && b.g ==1.) || (t.r == 1. && t.g ==1.)){
// ??????
k = k+10.;
} else {k = 1.; p = false;}
}
} else {col = col0;}
// ???
highp float d=10.;
if ((uv.x>=dl.x && uv.x <= dr.x) && (uv.y>=db.y && uv.y <= dt.y)) {col = vec4(1.,0.,0.,1.);}
if ((uv.x>=dl.x+d && uv.x <= dr.x-d) && (uv.y>=db.y+d && uv.y <= dt.y-d)) {col = col0;}
gl_FragColor = col;
}
]]
}
My goal is to let the GPU to do all the heavy job, but it is hard to add the real-time rect with shader, so I have to use the CPU, but I will go on to think how to solve it, if you are interesting of these, please join me, thanks!
The code is published on github:
https://github.com/FreeBlues/CodeaMotionDetect
Here is the screenshot:
The left hand action:
The left hand stop, the right hand action:
BTW, I only have a iPad Pro now, so the code have not tested on iPad, iPad Air, iPad Mini etc. So you can tell me the best parameters on your devices.