Scrolling Bg Optical Flow (Lucas-Kanade method)
/**
* Copyright Hasufel ( http://wonderfl.net/user/Hasufel )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/2nb2
*/
// forked from Saqoosha's Optical Flow (Lucas-Kanade method)
// Wanted to do a simple scrolling optical analysis proof of concept using Saqoosha's code
// We can build intelligent tracking...endless possibilities, for IA and more!
// thx Saqoosha!
// @Hasufel
package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Graphics;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Rectangle;
[SWF(width=465, height=465, backgroundColor=0,frameRate=30)]
public class OpticalFlowLK extends Sprite {
private static const WIDTH:uint = 465;
private static const HEIGHT:uint = 240;
private static const toDegree:Number = 180 / Math.PI;
private static const _imageRect:Rectangle = new Rectangle(0, 0, WIDTH, HEIGHT);
private static const _starsCols:Array = [0xffffffff,0xffffcdff,0xffbebebe,0xffc7c7c7,0xffaaaaaa];
private static const _starsNum:uint = 5000;
private var _star:Particle;
private var currImage:BitmapData = new BitmapData(WIDTH, HEIGHT, true, 0xFF000000);
private var prevImage:BitmapData = new BitmapData(WIDTH, HEIGHT, true, 0xFF000000);
private var tmp:BitmapData = new BitmapData(WIDTH, HEIGHT, true, 0xFF000000);
private var container:Sprite;
private var arrow:Shape;
private var dir:Shape;
private var g:Graphics;
private static const winSize:uint = 16;//8
private static const winStep:uint = winSize * 2 + 1;
public function OpticalFlowLK() {
var blackBg:Bitmap = new Bitmap(new BitmapData(465,465,false,0x000000));
addChild(blackBg);
addChild(new Bitmap(currImage)) as Bitmap;
container = Sprite(addChild(new Sprite()));
arrow = Shape(addChild(new Shape()));
arrow.x = arrow.y = 465 / 2;
dir = Shape(container.addChild(new Shape()));
g = dir.graphics;
prepareStars();
addEventListener(Event.ENTER_FRAME, render);
}
private function render(e:Event):void {
tmp = prevImage;
prevImage = currImage;
currImage = tmp;
starsScroll();
var curr:Vector.<uint> = currImage.getVector(_imageRect);
var prev:Vector.<uint> = prevImage.getVector(_imageRect);
var i:int, j:int, k:int, l:int;
var address:uint;
var gradX:int, gradY:int, gradT:int;
var A2:Number, A1B2:Number, B1:Number, C1:Number, C2:Number;
var u:Number, v:Number, uu:Number, vv:Number, n:int;
var wmax:int = WIDTH - winSize - 1;
var hmax:int = HEIGHT - winSize - 1;
g.clear();
uu = vv = n = 0;
for (i = winSize + 1; i < hmax; i += winStep) { // y
for (j = winSize + 1; j < wmax; j += winStep) { // x
A2 = 0;
A1B2 = 0;
B1 = 0;
C1 = 0;
C2 = 0;
for (k = -winSize; k <= winSize; ++k) { // y
for (l = -winSize; l <= winSize; ++l) { // x
address = (i + k) * WIDTH + j + l;
gradX = (curr[(address - 1)>>0] & 0xff) - (curr[(address + 1)>>0] & 0xff);
gradY = (curr[(address - WIDTH)>>0] & 0xff) - (curr[(address + WIDTH)>>0] & 0xff);
gradT = (prev[address>>0] & 0xff) - (curr[address>>0] & 0xff);
A2 += gradX * gradX;
A1B2 += gradX * gradY;
B1 += gradY * gradY;
C2 += gradX * gradT;
C1 += gradY * gradT;
}
}
var delta:Number = (A1B2 * A1B2 - A2 * B1);
if (delta) {
/* system is not singular - solving by Kramer method */
var deltaX:Number;
var deltaY:Number;
var Idelta:Number = winSize / delta;//8/delta;
deltaX = -(C1 * A1B2 - C2 * B1);
deltaY = -(A1B2 * C2 - A2 * C1);
u = deltaX * Idelta;
v = deltaY * Idelta;
} else {
/* singular system - find optical flow in gradient direction */
var Norm:Number = (A1B2 + A2) * (A1B2 + A2) + (B1 + A1B2) * (B1 + A1B2);
if (Norm) {
var IGradNorm:Number = winSize / Norm;//8/Norm;
var temp:Number = -(C1 + C2) * IGradNorm;
u = (A1B2 + A2) * temp;
v = (B1 + A1B2) * temp;
} else {
u = v = 0;
}
}
if (-winStep < u && u < winStep && -winStep < v && v < winStep) {
uu += u;
vv += v;
n++;
g.lineStyle(0, hsv(Math.atan2(v, u) * toDegree + 360,1,1));
g.moveTo(j, i);
g.lineTo(j + u * 3, i + v * 3);
}
}
}
uu /= n;
vv /= n;
var a:Number = Math.atan2(vv, uu) * toDegree + 360;
arrow.graphics.clear();
arrow.graphics.beginFill(hsv(a,1,1));
arrow.graphics.drawRect(0, -0.5, 10, 1);
arrow.graphics.moveTo(10, -2);
arrow.graphics.lineTo(13.5, 0);
arrow.graphics.lineTo(10, 2);
arrow.graphics.endFill();
arrow.scaleX = arrow.scaleY= Math.sqrt(uu * uu + vv * vv) * 10;
arrow.rotation = a;
}
private function starsScroll():void {
var p:Particle=_star;
currImage.lock();
currImage.fillRect(_imageRect,0x00000000);
while(p){
p.y+=p.vy;
currImage.setPixel32(p.x>>0,p.y>>0,p.c);
if (p.y>HEIGHT){p.y=0;p.x=randomNumber(0,WIDTH);}
p=p.next;
}
currImage.unlock();
}
private function prepareStars():void {
_star=new Particle();
setProps(_star, {x:randomNumber(0,WIDTH),y:randomNumber(0,HEIGHT),vx:0,vy:randomNumber(1,10),c:_starsCols[randomNumber(0,4)]});
var prev:Particle=_star;
for (var i:uint=0;i<_starsNum;++i){
var next:Particle=new Particle();
setProps(next, {x:randomNumber(0,WIDTH),y:randomNumber(0,HEIGHT),vx:0,vy:randomNumber(1,10),c:_starsCols[randomNumber(0,4)]});
prev.next=next;
prev=next;
}
}
private function hsv(h:Number, s:Number, v:Number):uint {
h %= 360;
var i:int = h / 60;
var f:Number = h / 60 - i;
var b:int = v * 255;
var p:int = b * (1 - s);
var q:int = b * (1 - f * s);
var t:int = b * (1 - (1 - f) * s);
switch (i) {
case 0: return b << 16 | t << 8 | p;
case 1: return q << 16 | b << 8 | p;
case 2: return p << 16 | b << 8 | t;
case 3: return p << 16 | q << 8 | b;
case 4: return t << 16 | p << 8 | b;
case 5: return b << 16 | p << 8 | q;
}
return 0;
}
private function setProps(o:*,p:Object):void {
for (var k:String in p) {o[k]=p[k];}
}
private function randomNumber(low:int, high:int):int{
return Math.round(Math.random() * (high - low) + low);
}
}
}
class Particle {
public var x:Number,y:Number,vx:Number,vy:Number,c:uint,t:uint,power:int,clock:int=0,col:uint,next:Particle;
}