Source: detection/ViolaJones.js

(function() {
 /**
 * ViolaJones utility.
 * @static
 * @constructor
 */
 tracking.ViolaJones = {};
 /**
 * Holds the minimum area of intersection that defines when a rectangle is
 * from the same group. Often when a face is matched multiple rectangles are
 * classified as possible rectangles to represent the face, when they
 * intersects they are grouped as one face.
 * @type {number}
 * @default 0.5
 * @static
 */
 tracking.ViolaJones.REGIONS_OVERLAP = 0.5;
 /**
 * Holds the HAAR cascade classifiers converted from OpenCV training.
 * @type {array}
 * @static
 */
 tracking.ViolaJones.classifiers = {};
 /**
 * Detects through the HAAR cascade data rectangles matches.
 * @param {pixels} pixels The pixels in a linear [r,g,b,a,...] array.
 * @param {number} width The image width.
 * @param {number} height The image height.
 * @param {number} initialScale The initial scale to start the block
 * scaling.
 * @param {number} scaleFactor The scale factor to scale the feature block.
 * @param {number} stepSize The block step size.
 * @param {number} edgesDensity Percentage density edges inside the
 * classifier block. Value from [0.0, 1.0], defaults to 0.2. If specified
 * edge detection will be applied to the image to prune dead areas of the
 * image, this can improve significantly performance.
 * @param {number} data The HAAR cascade data.
 * @return {array} Found rectangles.
 * @static
 */
 tracking.ViolaJones.detect = function(pixels, width, height, initialScale, scaleFactor, stepSize, edgesDensity, data) {
 var total = 0;
 var rects = [];
 var integralImage = new Int32Array(width * height);
 var integralImageSquare = new Int32Array(width * height);
 var tiltedIntegralImage = new Int32Array(width * height);
 var integralImageSobel;
 if (edgesDensity> 0) {
 integralImageSobel = new Int32Array(width * height);
 }
 tracking.Image.computeIntegralImage(pixels, width, height, integralImage, integralImageSquare, tiltedIntegralImage, integralImageSobel);
 var minWidth = data[0];
 var minHeight = data[1];
 var scale = initialScale * scaleFactor;
 var blockWidth = (scale * minWidth) | 0;
 var blockHeight = (scale * minHeight) | 0;
 while (blockWidth < width && blockHeight < height) {
 var step = (scale * stepSize + 0.5) | 0;
 for (var i = 0; i < (height - blockHeight); i += step) {
 for (var j = 0; j < (width - blockWidth); j += step) {
 if (edgesDensity> 0) {
 if (this.isTriviallyExcluded(edgesDensity, integralImageSobel, i, j, width, blockWidth, blockHeight)) {
 continue;
 }
 }
 if (this.evalStages_(data, integralImage, integralImageSquare, tiltedIntegralImage, i, j, width, blockWidth, blockHeight, scale)) {
 rects[total++] = {
 width: blockWidth,
 height: blockHeight,
 x: j,
 y: i
 };
 }
 }
 }
 scale *= scaleFactor;
 blockWidth = (scale * minWidth) | 0;
 blockHeight = (scale * minHeight) | 0;
 }
 return this.mergeRectangles_(rects);
 };
 /**
 * Fast check to test whether the edges density inside the block is greater
 * than a threshold, if true it tests the stages. This can improve
 * significantly performance.
 * @param {number} edgesDensity Percentage density edges inside the
 * classifier block.
 * @param {array} integralImageSobel The integral image of a sobel image.
 * @param {number} i Vertical position of the pixel to be evaluated.
 * @param {number} j Horizontal position of the pixel to be evaluated.
 * @param {number} width The image width.
 * @return {boolean} True whether the block at position i,j can be skipped,
 * false otherwise.
 * @static
 * @protected
 */
 tracking.ViolaJones.isTriviallyExcluded = function(edgesDensity, integralImageSobel, i, j, width, blockWidth, blockHeight) {
 var wbA = i * width + j;
 var wbB = wbA + blockWidth;
 var wbD = wbA + blockHeight * width;
 var wbC = wbD + blockWidth;
 var blockEdgesDensity = (integralImageSobel[wbA] - integralImageSobel[wbB] - integralImageSobel[wbD] + integralImageSobel[wbC]) / (blockWidth * blockHeight * 255);
 if (blockEdgesDensity < edgesDensity) {
 return true;
 }
 return false;
 };
 /**
 * Evaluates if the block size on i,j position is a valid HAAR cascade
 * stage.
 * @param {number} data The HAAR cascade data.
 * @param {number} i Vertical position of the pixel to be evaluated.
 * @param {number} j Horizontal position of the pixel to be evaluated.
 * @param {number} width The image width.
 * @param {number} blockSize The block size.
 * @param {number} scale The scale factor of the block size and its original
 * size.
 * @param {number} inverseArea The inverse area of the block size.
 * @return {boolean} Whether the region passes all the stage tests.
 * @private
 * @static
 */
 tracking.ViolaJones.evalStages_ = function(data, integralImage, integralImageSquare, tiltedIntegralImage, i, j, width, blockWidth, blockHeight, scale) {
 var inverseArea = 1.0 / (blockWidth * blockHeight);
 var wbA = i * width + j;
 var wbB = wbA + blockWidth;
 var wbD = wbA + blockHeight * width;
 var wbC = wbD + blockWidth;
 var mean = (integralImage[wbA] - integralImage[wbB] - integralImage[wbD] + integralImage[wbC]) * inverseArea;
 var variance = (integralImageSquare[wbA] - integralImageSquare[wbB] - integralImageSquare[wbD] + integralImageSquare[wbC]) * inverseArea - mean * mean;
 var standardDeviation = 1;
 if (variance> 0) {
 standardDeviation = Math.sqrt(variance);
 }
 var length = data.length;
 for (var w = 2; w < length; ) {
 var stageSum = 0;
 var stageThreshold = data[w++];
 var nodeLength = data[w++];
 while (nodeLength--) {
 var rectsSum = 0;
 var tilted = data[w++];
 var rectsLength = data[w++];
 for (var r = 0; r < rectsLength; r++) {
 var rectLeft = (j + data[w++] * scale + 0.5) | 0;
 var rectTop = (i + data[w++] * scale + 0.5) | 0;
 var rectWidth = (data[w++] * scale + 0.5) | 0;
 var rectHeight = (data[w++] * scale + 0.5) | 0;
 var rectWeight = data[w++];
 var w1;
 var w2;
 var w3;
 var w4;
 if (tilted) {
 // RectSum(r) = RSAT(x-h+w, y+w+h-1) + RSAT(x, y-1) - RSAT(x-h, y+h-1) - RSAT(x+w, y+w-1)
 w1 = (rectLeft - rectHeight + rectWidth) + (rectTop + rectWidth + rectHeight - 1) * width;
 w2 = rectLeft + (rectTop - 1) * width;
 w3 = (rectLeft - rectHeight) + (rectTop + rectHeight - 1) * width;
 w4 = (rectLeft + rectWidth) + (rectTop + rectWidth - 1) * width;
 rectsSum += (tiltedIntegralImage[w1] + tiltedIntegralImage[w2] - tiltedIntegralImage[w3] - tiltedIntegralImage[w4]) * rectWeight;
 } else {
 // RectSum(r) = SAT(x-1, y-1) + SAT(x+w-1, y+h-1) - SAT(x-1, y+h-1) - SAT(x+w-1, y-1)
 w1 = rectTop * width + rectLeft;
 w2 = w1 + rectWidth;
 w3 = w1 + rectHeight * width;
 w4 = w3 + rectWidth;
 rectsSum += (integralImage[w1] - integralImage[w2] - integralImage[w3] + integralImage[w4]) * rectWeight;
 // TODO: Review the code below to analyze performance when using it instead.
 // w1 = (rectLeft - 1) + (rectTop - 1) * width;
 // w2 = (rectLeft + rectWidth - 1) + (rectTop + rectHeight - 1) * width;
 // w3 = (rectLeft - 1) + (rectTop + rectHeight - 1) * width;
 // w4 = (rectLeft + rectWidth - 1) + (rectTop - 1) * width;
 // rectsSum += (integralImage[w1] + integralImage[w2] - integralImage[w3] - integralImage[w4]) * rectWeight;
 }
 }
 var nodeThreshold = data[w++];
 var nodeLeft = data[w++];
 var nodeRight = data[w++];
 if (rectsSum * inverseArea < nodeThreshold * standardDeviation) {
 stageSum += nodeLeft;
 } else {
 stageSum += nodeRight;
 }
 }
 if (stageSum < stageThreshold) {
 return false;
 }
 }
 return true;
 };
 /**
 * Postprocess the detected sub-windows in order to combine overlapping
 * detections into a single detection.
 * @param {array} rects
 * @return {array}
 * @private
 * @static
 */
 tracking.ViolaJones.mergeRectangles_ = function(rects) {
 var disjointSet = new tracking.DisjointSet(rects.length);
 for (var i = 0; i < rects.length; i++) {
 var r1 = rects[i];
 for (var j = 0; j < rects.length; j++) {
 var r2 = rects[j];
 if (tracking.Math.intersectRect(r1.x, r1.y, r1.x + r1.width, r1.y + r1.height, r2.x, r2.y, r2.x + r2.width, r2.y + r2.height)) {
 var x1 = Math.max(r1.x, r2.x);
 var y1 = Math.max(r1.y, r2.y);
 var x2 = Math.min(r1.x + r1.width, r2.x + r2.width);
 var y2 = Math.min(r1.y + r1.height, r2.y + r2.height);
 var overlap = (x1 - x2) * (y1 - y2);
 var area1 = (r1.width * r1.height);
 var area2 = (r2.width * r2.height);
 if ((overlap / (area1 * (area1 / area2))>= this.REGIONS_OVERLAP) &&
 (overlap / (area2 * (area1 / area2))>= this.REGIONS_OVERLAP)) {
 disjointSet.union(i, j);
 }
 }
 }
 }
 var map = {};
 for (var k = 0; k < disjointSet.length; k++) {
 var rep = disjointSet.find(k);
 if (!map[rep]) {
 map[rep] = {
 total: 1,
 width: rects[k].width,
 height: rects[k].height,
 x: rects[k].x,
 y: rects[k].y
 };
 continue;
 }
 map[rep].total++;
 map[rep].width += rects[k].width;
 map[rep].height += rects[k].height;
 map[rep].x += rects[k].x;
 map[rep].y += rects[k].y;
 }
 var result = [];
 Object.keys(map).forEach(function(key) {
 var rect = map[key];
 result.push({
 total: rect.total,
 width: (rect.width / rect.total + 0.5) | 0,
 height: (rect.height / rect.total + 0.5) | 0,
 x: (rect.x / rect.total + 0.5) | 0,
 y: (rect.y / rect.total + 0.5) | 0
 });
 });
 return result;
 };
}());

AltStyle によって変換されたページ (->オリジナル) /