I've been trying a few methods to do headtracking with a webcam, and I'm more and more convinced it's possible in flash with items you have at home.
Attached is the begining of my experiments, using the same on-screen demo environment type as shown on the youtube videos for WiiFlash and PS3Eye. It doesn't have the same amount of freedom yet, no rotation around the y-axis and rotation around the x-axis is auto-focused on the horizon. A single light source develops z-depth issues when the detection area fluctuates, but that can be smoothed out with multi-sampling. Once I mount a couple of lights on a hat, I'm sure the increased width will be easier to work with.
I think for full freedom (or close to it), I'll need a third light source either at the brim or top of the hat. This will allow for z-depth dertimination by height and y-axis rotation by width and the distance between the points. I don't know how to do rotation with two points without constraining right->left and left->right (ie you couldn't look left if you are standing on the left), which is fine for windowing but isn't full freedom.
Any ideas on better methods?
Here's some ugly code I'm working on:
PHP Code:
// import flash.display.BitmapData, flash.geom.Rectangle, flash.geom.Point, and flash.geom.Matrix
// set up rendering variables
var Ox = Stage.width/2;
var Oy = Stage.height/2;
var focalLength = 100;
var cam = new Object();
cam.x = 0;
cam.y = 0;
cam.z = 100;
// light source width multiplier
// for a single point of light try values between 5 and 20
// for two points of light, or a light bar, try values between 0.1 and 5
var scaler = 10;
// attach the webcam video to a video object
my_cam = Camera.get();
webcam_video.attachVideo(my_cam);
// create a mirror image of the webcam video, scaled 2* for help with precision later
createEmptyMovieClip('holder', getNextHighestDepth());
now = new flashBitmapData(webcam_video._width * 2, webcam_video._height * 2);
holder.attachBitmap(now, holder.getNextHighestDepth());
with(holder){
_x = webcam_video._width;
_y = webcam_video._height + 10;
_xscale = -50;
_yscale = 50;
}
// create a box to place around the isolated light source for debugging
createEmptyMovieClip('box',getNextHighestDepth());
with(box){
lineStyle(1, 0xFFFFFF); lineTo(100, 0); lineTo(100, 100); lineTo(0, 100); lineTo(0, 0);
}
// create some target objects
createEmptyMovieClip('targets', getNextHighestDepth());
targetsObject = new Object();
targetsObject.names = new Array('target1','target2','target3','target4','target5','target6');
targetsObject.x = new Array(0,200,-300,-100,-300,300);
targetsObject.y = new Array(0,200,-100,-100,-300,-300);
targetsObject.z = new Array(100,200,300,500,700,700);
targetsObject.colors = new Array('0xff0000','0x0000ff','0x00b000','0xff00ff','0xffff00','0xff6666');
targets.makeTargets(targetsObject);
// work the magic
this.onEnterFrame=function(){
// draw the current webcam image to a bitmapdata object
// scaling up will help with accuracy a bit
matrix = new flashGeomMatrix();
matrix.scale(2,2);
now.draw(webcam_video, matrix);
// eleminate all but the brightest colors
now.threshold(now, now.rectangle, new flashGeomPoint(0, 0), '<=', 0xFF666666, 0xFF000000, 0xFF0000FF, false);
// find the bounding box of the brightest color
redBox=now.getColorBoundsRect(0x00FF0000,0x00FFFFFF,false);
// TO-DO -- use multiple lights, subdivide the bounding box and repeat to isolate each light
// if a light source is detected, track it
if(redBox.width>0){
// align and resize box indicator for debugging
box._x = redBox.x/2-redBox.width/4+webcam_video._x;
box._y = redBox.y/2-redBox.height/4+webcam_video._y;
box._width = redBox.width/2;
box._height = redBox.height/2;
// set z based on bounding box width and scaler value
cam.z = redBox.width*scaler;
// determine the head position in 3d space (remember that holder has been scaled to 2*)
var ratio = focalLength / (focalLength + cam.z);
cam.x = (holder._width / 2 - redBox.x) * ratio * 4;
cam.y = (redBox.y - holder._height / 2) * ratio * 4;
cam.rotY = 1-(redBox.y/holder._height); // keeps the camera pointed at horizon
// TO-DO -- decide which freedoms are important, rotation vs position in x/y planes
// perhaps 3 points of light can be used to triangulate all freedoms using x,y,width,height ratios
// 3d perspective rendering
function adjust3d(obj, cam){
var TminusC = (obj.z-cam.z == 0) ? 1 : obj.z - cam.z;
if(focalLength + TminusC == 0){
var ratio = 0.00000001;
}else{
var ratio = focalLength / (focalLength + TminusC);
}
obj._x = Ox + (obj.x - cam.x) * ratio;
obj._y = Oy + (obj.y - cam.y) * ratio;
obj._xscale = obj._yscale = ratio * 100;
if(obj.z < cam.z - focalLength){ obj._visible = false; }else{ obj._visible = true; }
obj.swapDepths(Math.round(10000-obj.z-cam.z));
}
// render prespective lines
function renderLines(cam){
with(_root){
clear();
for(var x = -Stage.width; x <= Stage.width; x+=200){
var y = -Stage.height;
var z = 0;
var ratio = focalLength / (focalLength + 200-cam.z);
var sx = Ox + (x - cam.x) * ratio;
var sy = Oy + (y - cam.y) * ratio;
ratio = focalLength / (focalLength + -cam.z+100000);
var ex = Ox + (x - cam.x) * ratio;
var ey = Oy + (y - cam.y) * ratio;
lineStyle(1,0xffffff);
moveTo(sx,sy);
lineTo(ex,ey);
y = Stage.height;
z = 0;
ratio = focalLength / (focalLength + 200-cam.z);
sx = Ox + (x - cam.x) * ratio;
sy = Oy + (y - cam.y) * ratio;
ratio = focalLength / (focalLength + -cam.z+100000);
ex = Ox + (x - cam.x) * ratio;
ey = Oy + (y - cam.y) * ratio;
lineStyle(1,0xffffff);
moveTo(sx,sy);
lineTo(ex,ey);
}
for(var v = -Stage.height; v <= Stage.height; v+=200){
var x = -Stage.width;
var y = v;
var z = 0;
var ratio = focalLength / (focalLength + 200-cam.z);
var sx = Ox + (x - cam.x) * ratio;
var sy = Oy + (y - cam.y) * ratio;
ratio = focalLength / (focalLength + -cam.z+100000);
var ex = Ox + (x - cam.x) * ratio;
var ey = Oy + (y - cam.y) * ratio;
lineStyle(1,0xffffff);
moveTo(sx,sy);
lineTo(ex,ey);
x = Stage.width;
y = v;
z = 0;
ratio = focalLength / (focalLength + 200-cam.z);
sx = Ox + (x - cam.x) * ratio;
sy = Oy + (y - cam.y) * ratio;
ratio = focalLength / (focalLength + -cam.z+100000);
ex = Ox + (x - cam.x) * ratio;
ey = Oy + (y - cam.y) * ratio;
lineStyle(1,0xffffff);
moveTo(sx,sy);
lineTo(ex,ey);
}
for(var z = 200; z <= 100000; z*=1.5){
var x = Stage.width;
var y = Stage.height;
var ratio = focalLength / (focalLength + z-cam.z);
var sx = Ox + (x - cam.x) * ratio;
var sy = Oy + (y - cam.y) * ratio;
ratio = focalLength / (focalLength + z-cam.z);
y = -Stage.height;
var ex = Ox + (x - cam.x) * ratio;
var ey = Oy + (y - cam.y) * ratio;
lineStyle(1,0xffffff);
moveTo(sx,sy);
lineTo(ex,ey);
x = -Stage.width;
y = Stage.height;
ratio = focalLength / (focalLength + z-cam.z);
sx = Ox + (x - cam.x) * ratio;
sy = Oy + (y - cam.y) * ratio;
ratio = focalLength / (focalLength + z-cam.z);
y = -Stage.height;
ex = Ox + (x - cam.x) * ratio;
ey = Oy + (y - cam.y) * ratio;
lineStyle(1,0xffffff);
moveTo(sx,sy);
lineTo(ex,ey);
x = Stage.width;
y = Stage.height;
ratio = focalLength / (focalLength + z-cam.z);
sx = Ox + (x - cam.x) * ratio;
sy = Oy + (y - cam.y) * ratio;
ratio = focalLength / (focalLength + z-cam.z);
x = -Stage.width;
ex = Ox + (x - cam.x) * ratio;
ey = Oy + (y - cam.y) * ratio;
lineStyle(1,0xffffff);
moveTo(sx,sy);
lineTo(ex,ey);
x = Stage.width;
y = -Stage.height;
ratio = focalLength / (focalLength + z-cam.z);
sx = Ox + (x - cam.x) * ratio;
sy = Oy + (y - cam.y) * ratio;
ratio = focalLength / (focalLength + z-cam.z);
x = -Stage.width;
ex = Ox + (x - cam.x) * ratio;
ey = Oy + (y - cam.y) * ratio;
lineStyle(1,0xffffff);
moveTo(sx,sy);
lineTo(ex,ey);
}
}
}
stop();
Note- For this to work, you need either a dark room, or a piece of film over the webcam lens to filter out all light other than your lightsource
Last edited by JerryScript; 02-20-2008 at 07:19 PM.
Reason: forgot to add attachment