Spawning multiple workers for entire pipeline; API change for Quagga.init; Added async-library

pull/12/head
Christoph Oberhofer 11 years ago
parent b7b4711a7e
commit 2d5fa058d5

@ -48,13 +48,18 @@ module.exports = function(grunt) {
"glMatrixAddon" : { "glMatrixAddon" : {
"deps" : ["glMatrix"], "deps" : ["glMatrix"],
"exports" : "glMatrixAddon" "exports" : "glMatrixAddon"
},
"async": {
"deps": [],
"exports": "async"
} }
}, },
"paths" : { "paths" : {
"typedefs" : "typedefs", "typedefs" : "typedefs",
"glMatrix" : "vendor/glMatrix", "glMatrix" : "vendor/glMatrix",
"glMatrixAddon" : "glMatrixAddon" "glMatrixAddon" : "glMatrixAddon",
"async": "vendor/async"
} }
} }
} }

1671
dist/quagga.js vendored

File diff suppressed because it is too large Load Diff

@ -8,11 +8,10 @@ $(function() {
}, },
decoder : { decoder : {
readers : ["code_128_reader"] readers : ["code_128_reader"]
}, }
readyFunc : function() { }, function() {
App.attachListeners(); App.attachListeners();
Quagga.start(); Quagga.start();
}
}); });
}, },
attachListeners : function() { attachListeners : function() {

@ -9,11 +9,10 @@ $(function() {
}, },
decoder : { decoder : {
readers : [App.config.reader + "_reader"] readers : [App.config.reader + "_reader"]
}, }
readyFunc : function() { }, function() {
App.attachListeners(); App.attachListeners();
Quagga.start(); Quagga.start();
}
}); });
}, },
config: { config: {

@ -30,7 +30,7 @@ define(["bresenham", "image_debug", 'code_128_reader', 'ean_reader'], function(B
initConfig(); initConfig();
function initCanvas() { function initCanvas() {
var $debug = document.querySelector("#debug.detection"); /* var $debug = document.querySelector("#debug.detection");
_canvas.dom.frequency = document.querySelector("canvas.frequency"); _canvas.dom.frequency = document.querySelector("canvas.frequency");
if (!_canvas.dom.frequency) { if (!_canvas.dom.frequency) {
_canvas.dom.frequency = document.createElement("canvas"); _canvas.dom.frequency = document.createElement("canvas");
@ -54,7 +54,7 @@ define(["bresenham", "image_debug", 'code_128_reader', 'ean_reader'], function(B
_canvas.dom.overlay = document.querySelector("canvas.drawingBuffer"); _canvas.dom.overlay = document.querySelector("canvas.drawingBuffer");
if (_canvas.dom.overlay) { if (_canvas.dom.overlay) {
_canvas.ctx.overlay = _canvas.dom.overlay.getContext("2d"); _canvas.ctx.overlay = _canvas.dom.overlay.getContext("2d");
} } */
} }
function initReaders() { function initReaders() {
@ -66,7 +66,7 @@ define(["bresenham", "image_debug", 'code_128_reader', 'ean_reader'], function(B
} }
function initConfig() { function initConfig() {
var i, /* var i,
vis = [{ vis = [{
node : _canvas.dom.frequency, node : _canvas.dom.frequency,
prop : config.showFrequency prop : config.showFrequency
@ -81,7 +81,7 @@ define(["bresenham", "image_debug", 'code_128_reader', 'ean_reader'], function(B
} else { } else {
vis[i].node.style.display = "none"; vis[i].node.style.display = "none";
} }
} } */
} }
/** /**

@ -26,10 +26,7 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
_numPatches = {x: 0, y: 0}, _numPatches = {x: 0, y: 0},
_inputImageWrapper, _inputImageWrapper,
_skeletonizer, _skeletonizer,
self = this, self = this;
_worker,
_locatedCb,
_initialized;
function initBuffers() { function initBuffers() {
var skeletonImageData; var skeletonImageData;
@ -477,96 +474,19 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
return label; return label;
} }
function initWorker(cb) {
var tmpData,
blobURL;
blobURL = generateWorkerBlob();
_worker = new Worker(blobURL);
URL.revokeObjectURL(blobURL);
tmpData = _inputImageWrapper.data;
_inputImageWrapper.data = null; // do not send the data along
_worker.postMessage({cmd: 'init', inputImageWrapper: _inputImageWrapper, config: _config});
_inputImageWrapper.data = tmpData;
_worker.onmessage = function(e) {
if (e.data.event === 'initialized') {
_initialized = true;
cb();
} else if (e.data.event === 'located') {
_inputImageWrapper.data = new Uint8Array(e.data.buffer);
_locatedCb(e.data.result);
}
};
}
function generateWorkerBlob() {
var blob,
quaggaAbsoluteUrl,
scripts = document.getElementsByTagName('script'),
regex = new RegExp('\/' + _config.scriptName + '$');
quaggaAbsoluteUrl = Array.prototype.slice.apply(scripts).filter(function(script) {
return script.src && script.src.match(regex);
}).map(function(script) {
return script.src;
})[0];
/* jshint ignore:start */
blob = new Blob(['(' +
(function(scriptUrl){
importScripts(scriptUrl);
var inputImageWrapper,
config,
Locator = Quagga.Locator;
self.onmessage = function(e) {
if (e.data.cmd === 'init') {
inputImageWrapper = e.data.inputImageWrapper;
config = e.data.config;
config.useWorker = false;
Locator.init(inputImageWrapper, config, function() {
self.postMessage({'event': 'initialized'});
});
} else if (e.data.cmd === 'locate') {
inputImageWrapper.data = new Uint8Array(e.data.buffer);
Locator.locate(function(result) {
self.postMessage({'event': 'located', result: result, buffer : inputImageWrapper.data}, [inputImageWrapper.data.buffer]);
});
}
};
}).toString() + ')("' + quaggaAbsoluteUrl + '");'
], {type : 'text/javascript'});
/* jshint ignore:end */
return window.URL.createObjectURL(blob);
}
return { return {
init : function(inputImageWrapper, config, cb) { init : function(inputImageWrapper, config) {
_config = config; _config = config;
_inputImageWrapper = inputImageWrapper; _inputImageWrapper = inputImageWrapper;
// 1. check config for web-worker
if (_config.useWorker) {
initWorker(cb);
} else {
initBuffers(); initBuffers();
initCanvas(); initCanvas();
cb();
}
}, },
locate : function(cb) { locate : function() {
var patchesFound, var patchesFound,
topLabels = [], topLabels = [],
boxes = []; boxes = [];
if (_config.useWorker) {
_locatedCb = cb;
_worker.postMessage({cmd: 'locate', buffer: _inputImageWrapper.data}, [_inputImageWrapper.data.buffer]);
} else {
if (_config.halfSample) { if (_config.halfSample) {
CVUtils.halfSample(_inputImageWrapper, _currentImageWrapper); CVUtils.halfSample(_inputImageWrapper, _currentImageWrapper);
} }
@ -575,24 +495,23 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
patchesFound = findPatches(); patchesFound = findPatches();
// return unless 5% or more patches are found // return unless 5% or more patches are found
if (patchesFound.length < _numPatches.x * _numPatches.y * 0.05) { if (patchesFound.length < _numPatches.x * _numPatches.y * 0.05) {
return cb(null); return null;
} }
// rasterrize area by comparing angular similarity; // rasterrize area by comparing angular similarity;
var maxLabel = rasterizeAngularSimilarity(patchesFound); var maxLabel = rasterizeAngularSimilarity(patchesFound);
if (maxLabel <= 1) { if (maxLabel <= 1) {
return cb(null); return null;
} }
// search for area with the most patches (biggest connected area) // search for area with the most patches (biggest connected area)
topLabels = findBiggestConnectedAreas(maxLabel); topLabels = findBiggestConnectedAreas(maxLabel);
if (topLabels.length === 0) { if (topLabels.length === 0) {
return cb(null); return null;
} }
boxes = findBoxes(topLabels, maxLabel); boxes = findBoxes(topLabels, maxLabel);
cb(boxes); return boxes;
}
} }
}; };
}); });

@ -11,13 +11,15 @@ define(function(){
debug: false, debug: false,
controls: false, controls: false,
locate: true, locate: true,
numOfWorkers: 4,
scriptName: 'quagga.js',
visual: { visual: {
show: true show: true
}, },
decoder:{ decoder:{
drawBoundingBox: true, drawBoundingBox: false,
showFrequency: false, showFrequency: false,
drawScanline: true, drawScanline: false,
showPattern: false, showPattern: false,
readers: [ readers: [
'code_128_reader' 'code_128_reader'
@ -25,8 +27,6 @@ define(function(){
}, },
locator: { locator: {
halfSample: true, halfSample: true,
useWorker: true,
scriptName: 'quagga.js',
showCanvas: false, showCanvas: false,
showPatches: false, showPatches: false,
showFoundPatches: false, showFoundPatches: false,

@ -1,8 +1,8 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ /* jshint undef: true, unused: true, browser:true, devel: true */
/* global define, vec2 */ /* global define, vec2, importScripts */
define(["code_128_reader", "ean_reader", "input_stream", "image_wrapper", "barcode_locator", "barcode_decoder", "frame_grabber", "html_utils", "config", "events", "camera_access"], define(["code_128_reader", "ean_reader", "input_stream", "image_wrapper", "barcode_locator", "barcode_decoder", "frame_grabber", "html_utils", "config", "events", "camera_access", "async"],
function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, BarcodeDecoder, FrameGrabber, HtmlUtils, _config, Events, CameraAccess) { function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, BarcodeDecoder, FrameGrabber, HtmlUtils, _config, Events, CameraAccess, async) {
"use strict"; "use strict";
var _inputStream, var _inputStream,
@ -21,11 +21,12 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
_inputImageWrapper, _inputImageWrapper,
_boxSize, _boxSize,
_decoder, _decoder,
_initialized = false; _workerPool,
_onUIThread = true;
function initialize(config) { function initializeData(imageWrapper) {
_config = HtmlUtils.mergeObjects(_config, config); initBuffers(imageWrapper);
initInputStream(); _decoder = BarcodeDecoder.create(_config.decoder, _inputImageWrapper);
} }
function initConfig() { function initConfig() {
@ -48,7 +49,7 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
} }
} }
function initInputStream() { function initInputStream(cb) {
var video; var video;
if (_config.inputStream.type == "VideoStream") { if (_config.inputStream.type == "VideoStream") {
video = document.createElement("video"); video = document.createElement("video");
@ -74,23 +75,31 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
_inputStream.setAttribute("preload", "auto"); _inputStream.setAttribute("preload", "auto");
_inputStream.setAttribute("autoplay", true); _inputStream.setAttribute("autoplay", true);
_inputStream.setInputStream(_config.inputStream); _inputStream.setInputStream(_config.inputStream);
_inputStream.addEventListener("canrecord", canRecord); _inputStream.addEventListener("canrecord", canRecord.bind(undefined, cb));
} }
function canRecord() { function canRecord(cb) {
initBuffers(function() {
initCanvas(); initCanvas();
_decoder = BarcodeDecoder.create(_config.decoder, _inputImageWrapper);
_framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image); _framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image);
_framegrabber.attachData(_inputImageWrapper.data);
initConfig(); initConfig();
_inputStream.play();
_initialized = true; if (_config.numOfWorkers > 0) {
if (_config.readyFunc) { initWorkers(function() {
_config.readyFunc.apply(); console.log("Workers created");
} _workerPool.forEach(function(workerThread) {
console.log(workerThread.busy);
});
ready(cb);
}); });
} else {
initializeData();
ready(cb);
}
}
function ready(cb){
_inputStream.play();
cb();
} }
function initCanvas() { function initCanvas() {
@ -104,8 +113,8 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
} }
} }
_canvasContainer.ctx.image = _canvasContainer.dom.image.getContext("2d"); _canvasContainer.ctx.image = _canvasContainer.dom.image.getContext("2d");
_canvasContainer.dom.image.width = _inputImageWrapper.size.x; _canvasContainer.dom.image.width = _inputStream.getWidth();
_canvasContainer.dom.image.height = _inputImageWrapper.size.y; _canvasContainer.dom.image.height = _inputStream.getHeight();
_canvasContainer.dom.overlay = document.querySelector("canvas.drawingBuffer"); _canvasContainer.dom.overlay = document.querySelector("canvas.drawingBuffer");
if (!_canvasContainer.dom.overlay) { if (!_canvasContainer.dom.overlay) {
@ -121,50 +130,85 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
} }
} }
_canvasContainer.ctx.overlay = _canvasContainer.dom.overlay.getContext("2d"); _canvasContainer.ctx.overlay = _canvasContainer.dom.overlay.getContext("2d");
_canvasContainer.dom.overlay.width = _inputImageWrapper.size.x; _canvasContainer.dom.overlay.width = _inputStream.getWidth();
_canvasContainer.dom.overlay.height = _inputImageWrapper.size.y; _canvasContainer.dom.overlay.height = _inputStream.getHeight();
} }
function initBuffers(cb) { function initBuffers(imageWrapper) {
if (imageWrapper) {
_inputImageWrapper = imageWrapper;
} else {
_inputImageWrapper = new ImageWrapper({ _inputImageWrapper = new ImageWrapper({
x : _inputStream.getWidth(), x : _inputStream.getWidth(),
y : _inputStream.getHeight() y : _inputStream.getHeight()
}); });
console.log(_inputStream.getWidth()); }
console.log(_inputStream.getHeight());
console.log(_inputImageWrapper.size);
_boxSize = [ _boxSize = [
vec2.create([20, _inputStream.getHeight() / 2 - 100]), vec2.create([20, _inputImageWrapper.size.y / 2 - 100]),
vec2.create([20, _inputStream.getHeight() / 2 + 100]), vec2.create([20, _inputImageWrapper.size.y / 2 + 100]),
vec2.create([_inputStream.getWidth() - 20, _inputStream.getHeight() / 2 + 100]), vec2.create([_inputImageWrapper.size.x - 20, _inputImageWrapper.size.y / 2 + 100]),
vec2.create([_inputStream.getWidth() - 20, _inputStream.getHeight() / 2 - 100]) vec2.create([_inputImageWrapper.size.x - 20, _inputImageWrapper.size.y / 2 - 100])
]; ];
BarcodeLocator.init(_inputImageWrapper, _config.locator, cb); BarcodeLocator.init(_inputImageWrapper, _config.locator);
} }
function getBoundingBoxes(cb) { function getBoundingBoxes() {
if (_config.locate) { if (_config.locate) {
BarcodeLocator.locate(cb); return BarcodeLocator.locate();
} else { } else {
cb([_boxSize]); return [_boxSize];
} }
} }
function update(cb) { function locateAndDecode() {
var result; var result,
boxes;
if (_framegrabber.grab()) { boxes = getBoundingBoxes();
_canvasContainer.ctx.overlay.clearRect(0, 0, _inputImageWrapper.size.x, _inputImageWrapper.size.y);
getBoundingBoxes(function(boxes) {
// attach data back to grabber
_framegrabber.attachData(_inputImageWrapper.data);
if (boxes) { if (boxes) {
result = _decoder.decodeFromBoundingBoxes(boxes); result = _decoder.decodeFromBoundingBoxes(boxes);
Events.publish("processed", result);
if (result && result.codeResult) { if (result && result.codeResult) {
Events.publish("detected", result.codeResult.code); Events.publish("detected", result.codeResult.code);
} }
} else {
Events.publish("processed");
} }
return cb();
}); }
function update() {
var availableWorker;
if (_onUIThread) {
if (_workerPool.length > 0) {
availableWorker = _workerPool.filter(function(workerThread) {
return !workerThread.busy;
})[0];
if (availableWorker) {
_framegrabber.attachData(availableWorker.imageData);
} else {
return; // all workers are busy
}
} else {
_framegrabber.attachData(_inputImageWrapper.data);
}
if (_framegrabber.grab()) {
_canvasContainer.ctx.overlay.clearRect(0, 0, _inputStream.getWidth(), _inputStream.getHeight());
if (availableWorker) {
availableWorker.busy = true;
availableWorker.worker.postMessage({
cmd: 'process',
imageData: availableWorker.imageData
}, [availableWorker.imageData.buffer]);
} else {
locateAndDecode();
}
}
} else {
locateAndDecode();
} }
} }
@ -172,21 +216,123 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
_stopped = false; _stopped = false;
( function frame() { ( function frame() {
if (!_stopped) { if (!_stopped) {
update(function() { update();
if (_config.inputStream.type == "LiveStream") { if (_onUIThread && _config.inputStream.type == "LiveStream") {
window.requestAnimFrame(frame); window.requestAnimFrame(frame);
} }
});
} }
}()); }());
} }
function initWorkers(cb) {
_workerPool = [];
async.times(_config.numOfWorkers, function(n, next) {
initWorker(function(workerThread) {
_workerPool.push(workerThread);
next(null);
});
}, cb);
}
function initWorker(cb) {
var blobURL,
workerThread = {
worker: null,
imageData: new Uint8Array(_inputStream.getWidth() * _inputStream.getHeight()),
busy: true
};
blobURL = generateWorkerBlob();
workerThread.worker = new Worker(blobURL);
URL.revokeObjectURL(blobURL);
workerThread.worker.onmessage = function(e) {
if (e.data.event === 'initialized') {
workerThread.busy = false;
workerThread.imageData = new Uint8Array(e.data.imageData);
console.log("Worker initialized");
return cb(workerThread);
} else if (e.data.event === 'processed') {
workerThread.imageData = new Uint8Array(e.data.imageData);
workerThread.busy = false;
if (e.data.result && e.data.result.codeResult) {
Events.publish("detected", e.data.result.codeResult.code);
}
}
};
workerThread.worker.postMessage({
cmd: 'init',
size: {x: _inputStream.getWidth(), y: _inputStream.getHeight()},
imageData: workerThread.imageData,
config: _config
}, [workerThread.imageData.buffer]);
}
function workerInterface(scriptUrl) {
importScripts(scriptUrl);
/* jshint ignore:start */
var imageWrapper;
self.onmessage = function(e) {
if (e.data.cmd === 'init') {
var config = e.data.config;
config.numOfWorkers = 0;
imageWrapper = new Quagga.ImageWrapper({
x : e.data.size.x,
y : e.data.size.y
}, new Uint8Array(e.data.imageData));
Quagga.init(config, ready, imageWrapper);
Quagga.onProcessed(onProcessed);
} else if (e.data.cmd === 'process') {
imageWrapper.data = new Uint8Array(e.data.imageData);
Quagga.start();
}
};
function onProcessed(result) {
self.postMessage({'event': 'processed', imageData: imageWrapper.data, result: result}, [imageWrapper.data.buffer]);
}
function ready() {
self.postMessage({'event': 'initialized', imageData: imageWrapper.data}, [imageWrapper.data.buffer]);
}
/* jshint ignore:end */
}
function generateWorkerBlob() {
var blob,
quaggaAbsoluteUrl,
scripts = document.getElementsByTagName('script'),
regex = new RegExp('\/' + _config.scriptName + '$');
quaggaAbsoluteUrl = Array.prototype.slice.apply(scripts).filter(function(script) {
return script.src && script.src.match(regex);
}).map(function(script) {
return script.src;
})[0];
blob = new Blob(['(' + workerInterface.toString() + ')("' + quaggaAbsoluteUrl + '");'],
{type : 'text/javascript'});
return window.URL.createObjectURL(blob);
}
return { return {
init : function(config) { init : function(config, cb, imageWrapper) {
initialize(config); _config = HtmlUtils.mergeObjects(_config, config);
if (imageWrapper) {
_onUIThread = false;
initializeData(imageWrapper);
return cb();
} else {
initInputStream(cb);
}
}, },
start : function() { start : function() {
console.log("Start!");
start(); start();
}, },
stop : function() { stop : function() {
@ -198,8 +344,8 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
onDetected : function(callback) { onDetected : function(callback) {
Events.subscribe("detected", callback); Events.subscribe("detected", callback);
}, },
isInitialized : function() { onProcessed: function(callback) {
return _initialized; Events.subscribe("processed", callback);
}, },
setReaders: function(readers) { setReaders: function(readers) {
_decoder.setReaders(readers); _decoder.setReaders(readers);
@ -212,19 +358,19 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
sequence : false, sequence : false,
size: 800 size: 800
}; };
config.readyFunc = function() { config.numOfWorkers = 1;
this.init(config, function() {
Events.once("detected", function(result) { Events.once("detected", function(result) {
_stopped = true; _stopped = true;
resultCallback.call(null, result); resultCallback.call(null, result);
}, true); }, true);
start(); start();
}; });
initialize(config);
}, },
Reader: { Reader: {
EANReader : EANReader, EANReader : EANReader,
Code128Reader : Code128Reader Code128Reader : Code128Reader
}, },
Locator: BarcodeLocator ImageWrapper: ImageWrapper
}; };
}); });

1123
src/vendor/async.js vendored

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save