| 1 |
// |
|---|
| 2 |
// Project Marilena |
|---|
| 3 |
// Object Detection in Actionscript3 |
|---|
| 4 |
// based on OpenCV (Open Computer Vision Library) Object Detection |
|---|
| 5 |
// |
|---|
| 6 |
// Copyright (C) 2008, Masakazu OHTSUKA (mash), all rights reserved. |
|---|
| 7 |
// contact o.masakazu(at)gmail.com |
|---|
| 8 |
// |
|---|
| 9 |
// Redistribution and use in source and binary forms, with or without modification, |
|---|
| 10 |
// are permitted provided that the following conditions are met: |
|---|
| 11 |
// |
|---|
| 12 |
// * Redistribution's of source code must retain the above copyright notice, |
|---|
| 13 |
// this list of conditions and the following disclaimer. |
|---|
| 14 |
// |
|---|
| 15 |
// * Redistribution's in binary form must reproduce the above copyright notice, |
|---|
| 16 |
// this list of conditions and the following disclaimer in the documentation |
|---|
| 17 |
// and/or other materials provided with the distribution. |
|---|
| 18 |
// |
|---|
| 19 |
// This software is provided by the copyright holders and contributors "as is" and |
|---|
| 20 |
// any express or implied warranties, including, but not limited to, the implied |
|---|
| 21 |
// warranties of merchantability and fitness for a particular purpose are disclaimed. |
|---|
| 22 |
// In no event shall the Intel Corporation or contributors be liable for any direct, |
|---|
| 23 |
// indirect, incidental, special, exemplary, or consequential damages |
|---|
| 24 |
// (including, but not limited to, procurement of substitute goods or services; |
|---|
| 25 |
// loss of use, data, or profits; or business interruption) however caused |
|---|
| 26 |
// and on any theory of liability, whether in contract, strict liability, |
|---|
| 27 |
// or tort (including negligence or otherwise) arising in any way out of |
|---|
| 28 |
// the use of this software, even if advised of the possibility of such damage. |
|---|
| 29 |
// |
|---|
| 30 |
package jp.maaash.ObjectDetection |
|---|
| 31 |
{ |
|---|
| 32 |
import flash.events.Event; |
|---|
| 33 |
import flash.events.EventDispatcher; |
|---|
| 34 |
import flash.display.Bitmap; |
|---|
| 35 |
import flash.geom.Rectangle; |
|---|
| 36 |
import flash.utils.setTimeout; |
|---|
| 37 |
public class ObjectDetector extends EventDispatcher{ |
|---|
| 38 |
private var debug :Boolean = false; |
|---|
| 39 |
private var tgt :TargetImage; |
|---|
| 40 |
public var detected :Array; // of Rectangles |
|---|
| 41 |
public var cascade :HaarCascade; |
|---|
| 42 |
private var _options :ObjectDetectorOptions; |
|---|
| 43 |
private var xmlloader :HaarCascadeLoader; |
|---|
| 44 |
|
|---|
| 45 |
private var waiting :Boolean = false; |
|---|
| 46 |
private var loaded :Boolean = false; |
|---|
| 47 |
|
|---|
| 48 |
public function ObjectDetector() { |
|---|
| 49 |
tgt = new TargetImage; |
|---|
| 50 |
} |
|---|
| 51 |
|
|---|
| 52 |
public function detect( bmp :Bitmap = null ) :void{ |
|---|
| 53 |
logger("[detect]"); |
|---|
| 54 |
if ( bmp && bmp.bitmapData ) { |
|---|
| 55 |
tgt.bitmapData = bmp.bitmapData; |
|---|
| 56 |
} |
|---|
| 57 |
|
|---|
| 58 |
if ( !loaded ) { |
|---|
| 59 |
waiting = true; |
|---|
| 60 |
return; |
|---|
| 61 |
} |
|---|
| 62 |
dispatchEvent( new ObjectDetectorEvent(ObjectDetectorEvent.DETECTION_START) ); |
|---|
| 63 |
_detect(); |
|---|
| 64 |
} |
|---|
| 65 |
|
|---|
| 66 |
private function _detect() :void { |
|---|
| 67 |
|
|---|
| 68 |
detected = new Array; |
|---|
| 69 |
var imgw :int = tgt.width, imgh :int = tgt.height; |
|---|
| 70 |
var scaledw :int, scaledh :int, limitx :int, limity :int, stepx :int, stepy :int, result :int, factor:Number = 1; |
|---|
| 71 |
for( factor = 1; |
|---|
| 72 |
factor*cascade.base_window_w < imgw && factor*cascade.base_window_h < imgh; |
|---|
| 73 |
factor *= _options.scale_factor ) |
|---|
| 74 |
{ |
|---|
| 75 |
scaledw = int( cascade.base_window_w * factor ); |
|---|
| 76 |
scaledh = int( cascade.base_window_h * factor ); |
|---|
| 77 |
if( scaledw < _options.min_size || scaledh < _options.min_size ){ |
|---|
| 78 |
continue; |
|---|
| 79 |
} |
|---|
| 80 |
limitx = tgt.width - scaledw; |
|---|
| 81 |
limity = tgt.height - scaledh; |
|---|
| 82 |
if( _options.endx != ObjectDetectorOptions.INVALID_POS && _options.endy != ObjectDetectorOptions.INVALID_POS ){ |
|---|
| 83 |
limitx = Math.min( _options.endx, limitx ); |
|---|
| 84 |
limity = Math.min( _options.endy, limity ); |
|---|
| 85 |
} |
|---|
| 86 |
logger("[detect]limitx,y: "+limitx+","+limity); |
|---|
| 87 |
|
|---|
| 88 |
//stepx = Math.max(_options.MIN_MARGIN_SEARCH,factor); |
|---|
| 89 |
stepx = scaledw>>3; |
|---|
| 90 |
stepy = stepx; |
|---|
| 91 |
logger("[detect] w,h,step: "+scaledw+","+scaledh+","+stepx); |
|---|
| 92 |
|
|---|
| 93 |
var ix:int=0, iy:int=0, startx:int=0, starty:int=0; |
|---|
| 94 |
if( _options.startx != ObjectDetectorOptions.INVALID_POS && _options.starty != ObjectDetectorOptions.INVALID_POS ){ |
|---|
| 95 |
startx = Math.max( ix, _options.startx ); |
|---|
| 96 |
starty = Math.max( iy, _options.starty ); |
|---|
| 97 |
} |
|---|
| 98 |
logger("[detect]startx,y: "+startx+","+starty); |
|---|
| 99 |
|
|---|
| 100 |
for( iy = starty; iy < limity; iy += stepy ){ |
|---|
| 101 |
for( ix = startx; ix < limitx; ix += stepx ){ |
|---|
| 102 |
if( _options.search_mode & ObjectDetectorOptions.SEARCH_MODE_NO_OVERLAP && |
|---|
| 103 |
overlaps(ix,iy,scaledw,scaledh) ){ |
|---|
| 104 |
// do nothing |
|---|
| 105 |
}else{ |
|---|
| 106 |
//logger("[checkAndRun]ix,iy,scaledw,scaledh: "+ix+","+iy+","+scaledw+","+scaledh); |
|---|
| 107 |
cascade.scale = factor; |
|---|
| 108 |
result = runHaarClassifierCascade(cascade,ix,iy,scaledw,scaledh); |
|---|
| 109 |
if ( result > 0 ) { |
|---|
| 110 |
var faceArea :Rectangle = new Rectangle(ix,iy,scaledw,scaledh); |
|---|
| 111 |
detected.push( faceArea ); |
|---|
| 112 |
logger("[createCheckAndRun]found!: "+ix+","+iy+","+scaledw+","+scaledh); |
|---|
| 113 |
|
|---|
| 114 |
// doesnt mean anything cause detection is not time-divided (now) |
|---|
| 115 |
var ev1 :ObjectDetectorEvent = new ObjectDetectorEvent( ObjectDetectorEvent.FACE_FOUND ); |
|---|
| 116 |
ev1.rect = faceArea; |
|---|
| 117 |
dispatchEvent( ev1 ); |
|---|
| 118 |
} |
|---|
| 119 |
} |
|---|
| 120 |
} |
|---|
| 121 |
} |
|---|
| 122 |
} |
|---|
| 123 |
|
|---|
| 124 |
// integrate redundant candidates ... |
|---|
| 125 |
|
|---|
| 126 |
var ev2 :ObjectDetectorEvent = new ObjectDetectorEvent( ObjectDetectorEvent.DETECTION_COMPLETE ); |
|---|
| 127 |
ev2.rects = detected; |
|---|
| 128 |
dispatchEvent( ev2 ); |
|---|
| 129 |
} |
|---|
| 130 |
|
|---|
| 131 |
private function runHaarClassifierCascade(c:HaarCascade,x:int,y:int,w:int,h:int):int{ |
|---|
| 132 |
//logger("[runHaarClassifierCascade] c:",c,x,y,w,h); |
|---|
| 133 |
var mean :Number = tgt.getSum(x,y,w,h) * c.inv_window_area; |
|---|
| 134 |
var variance_norm_factor :Number = tgt.getSum2(x,y,w,h)* c.inv_window_area - mean*mean; |
|---|
| 135 |
if( variance_norm_factor >= 0 ){ |
|---|
| 136 |
variance_norm_factor = Math.sqrt(variance_norm_factor); |
|---|
| 137 |
}else{ |
|---|
| 138 |
variance_norm_factor = 1; |
|---|
| 139 |
} |
|---|
| 140 |
|
|---|
| 141 |
var trees :Array = c.trees, treenums :int = trees.length, tree: FeatureTree, features :Array, featurenums :int, val :Number = 0, sum :Number = 0, feature :FeatureBase, i :int=0, j :int=0, st_th:Number = 0; |
|---|
| 142 |
for( i=0; i<treenums; i++ ){ |
|---|
| 143 |
tree = trees[i]; |
|---|
| 144 |
features = tree.features; |
|---|
| 145 |
featurenums = features.length; |
|---|
| 146 |
val = 0; |
|---|
| 147 |
st_th = tree.stage_threshold; |
|---|
| 148 |
for( j=0; j<featurenums; j++ ){ |
|---|
| 149 |
feature = features[j]; |
|---|
| 150 |
sum = feature.getSum( tgt, x, y ); |
|---|
| 151 |
|
|---|
| 152 |
// val += (sum < feature.threshold * variance_norm_factor) ? |
|---|
| 153 |
// feature.left_val : feature.right_val; |
|---|
| 154 |
// |
|---|
| 155 |
// * Ternary operation causes coersion and makes slower. |
|---|
| 156 |
|
|---|
| 157 |
if (sum < feature.threshold * variance_norm_factor) |
|---|
| 158 |
val += feature.left_val; |
|---|
| 159 |
else |
|---|
| 160 |
val += feature.right_val; |
|---|
| 161 |
|
|---|
| 162 |
if( val > st_th ){ |
|---|
| 163 |
// left_val, right_val are always plus |
|---|
| 164 |
break; |
|---|
| 165 |
} |
|---|
| 166 |
} |
|---|
| 167 |
if( val < st_th ){ |
|---|
| 168 |
return 0; |
|---|
| 169 |
} |
|---|
| 170 |
} |
|---|
| 171 |
return 1; |
|---|
| 172 |
} |
|---|
| 173 |
|
|---|
| 174 |
private function overlaps(_x:int,_y:int,_w:int,_h:int):Boolean{ |
|---|
| 175 |
// if the area we're going to check contains, or overlaps the square which is already picked up, ignore it |
|---|
| 176 |
var i:int=0; |
|---|
| 177 |
var l:int=detected.length; |
|---|
| 178 |
var tg: Rectangle; |
|---|
| 179 |
var x:int = _x, y:int = _y, w:int = _w, h:int = _h, tx1:int, tx2:int, ty1:int, ty2:int; |
|---|
| 180 |
for( i=0; i<l; i++ ){ |
|---|
| 181 |
tg = detected[i]; |
|---|
| 182 |
tx1 = tg.x; |
|---|
| 183 |
tx2 = tg.x + tg.width; |
|---|
| 184 |
ty1 = tg.y; |
|---|
| 185 |
ty2 = tg.y + tg.height; |
|---|
| 186 |
if( ( ( x <= tx1 && tx1 < x+w ) |
|---|
| 187 |
||( x <= tx2 && tx2 < x+w ) ) |
|---|
| 188 |
&& ( ( y <= ty1 && ty1 < y+h ) |
|---|
| 189 |
||( y <= ty2 && ty2 < y+h ) ) ) |
|---|
| 190 |
{ |
|---|
| 191 |
return true; |
|---|
| 192 |
} |
|---|
| 193 |
} |
|---|
| 194 |
return false; |
|---|
| 195 |
} |
|---|
| 196 |
|
|---|
| 197 |
public function loadHaarCascades( url :String ) :void { |
|---|
| 198 |
xmlloader = new HaarCascadeLoader( url ); |
|---|
| 199 |
xmlloader.addEventListener(Event.COMPLETE,function(e:Event):void{ |
|---|
| 200 |
xmlloader.removeEventListener(Event.COMPLETE,arguments.callee); |
|---|
| 201 |
dispatchEvent( new ObjectDetectorEvent(ObjectDetectorEvent.HAARCASCADES_LOAD_COMPLETE) ); |
|---|
| 202 |
cascade = xmlloader.cascade; |
|---|
| 203 |
|
|---|
| 204 |
loaded = true; |
|---|
| 205 |
if( waiting ){ |
|---|
| 206 |
waiting = false; |
|---|
| 207 |
detect(); |
|---|
| 208 |
} |
|---|
| 209 |
}); |
|---|
| 210 |
loaded = false; |
|---|
| 211 |
dispatchEvent( new ObjectDetectorEvent(ObjectDetectorEvent.HAARCASCADES_LOADING) ); |
|---|
| 212 |
xmlloader.load(); // kick it! |
|---|
| 213 |
} |
|---|
| 214 |
|
|---|
| 215 |
public function set bitmap( bmp :Bitmap ) :void { |
|---|
| 216 |
tgt.bitmapData = bmp.bitmapData; |
|---|
| 217 |
} |
|---|
| 218 |
public function set options( opt :ObjectDetectorOptions ) :void { |
|---|
| 219 |
_options = opt; |
|---|
| 220 |
} |
|---|
| 221 |
|
|---|
| 222 |
private function logger(... args):void{ |
|---|
| 223 |
if(!debug){ return; } |
|---|
| 224 |
log(["[ObjectDetector]"+args.shift()].concat(args)); |
|---|
| 225 |
} |
|---|
| 226 |
} |
|---|
| 227 |
} |
|---|