From a8b2d3ed9a4fc73a34f63d7064963119402e02d2 Mon Sep 17 00:00:00 2001 From: Christoph Oberhofer Date: Fri, 8 Apr 2016 07:52:37 +0200 Subject: [PATCH] Preparing pdf417 decoding --- example/css/styles.css | 2 - example/file_input.js | 9 ++- src/common/geometric.js | 43 +++++++++++ src/common/image_debug.js | 17 ++++- src/decoder/barcode_decoder.js | 66 +++++----------- src/detector/pdf_147_detector.js | 48 ++++++++++++ src/reader/common.js | 126 +++++++++++++++++++++++++++++++ src/reader/pdf_417_reader.js | 30 ++++++++ 8 files changed, 287 insertions(+), 54 deletions(-) create mode 100644 src/common/geometric.js create mode 100644 src/detector/pdf_147_detector.js create mode 100644 src/reader/common.js create mode 100644 src/reader/pdf_417_reader.js diff --git a/example/css/styles.css b/example/css/styles.css index a39a361..3c88d65 100644 --- a/example/css/styles.css +++ b/example/css/styles.css @@ -19,14 +19,12 @@ /* line 1, ../sass/_viewport.scss */ #interactive.viewport { width: 640px; - height: 480px; } /* line 6, ../sass/_viewport.scss */ #interactive.viewport canvas, video { float: left; width: 640px; - height: 480px; } /* line 10, ../sass/_viewport.scss */ #interactive.viewport canvas.drawingBuffer, video.drawingBuffer { diff --git a/example/file_input.js b/example/file_input.js index cccf288..28946b3 100644 --- a/example/file_input.js +++ b/example/file_input.js @@ -88,16 +88,17 @@ $(function() { }, state: { inputStream: { - size: 640, + size: 800, singleChannel: false }, locator: { patchSize: "large", - halfSample: false + halfSample: true }, decoder: { - readers: ["code_128_reader"] + readers: ["pdf_417_reader"] }, + debug: true, locate: true, src: null } @@ -131,7 +132,7 @@ $(function() { drawingCanvas = Quagga.canvas.dom.overlay, area; - if (result) { + if (false) { if (result.boxes) { drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height"))); result.boxes.filter(function (box) { diff --git a/src/common/geometric.js b/src/common/geometric.js new file mode 100644 index 0000000..336acd2 --- /dev/null +++ b/src/common/geometric.js @@ -0,0 +1,43 @@ + +export function getCenterLineFromBox(box) { + return [{ + x: (box[1][0] - box[0][0]) / 2 + box[0][0], + y: (box[1][1] - box[0][1]) / 2 + box[0][1] + }, { + x: (box[3][0] - box[2][0]) / 2 + box[2][0], + y: (box[3][1] - box[2][1]) / 2 + box[2][1] + }]; +} + +export function getLineLength(line) { + return Math.sqrt( + Math.pow(Math.abs(line[1].y - line[0].y), 2) + + Math.pow(Math.abs(line[1].x - line[0].x), 2)); +} + +export function getLineAngle(line) { + return Math.atan2(line[1].y - line[0].y, line[1].x - line[0].x); +} + +function extendLine(line, angle, ext) { + const extension = { + y: ext * Math.sin(angle), + x: ext * Math.cos(angle) + }; + + line[0].y -= extension.y; + line[0].x -= extension.x; + line[1].y += extension.y; + line[1].x += extension.x; + return line; +} + +export function getExtendedLine(inputImageWrapper, line, angle, ext) { + line = extendLine(line, angle, ext); + while (ext > 1 && (!inputImageWrapper.inImageWithBorder(line[0], 0) + || !inputImageWrapper.inImageWithBorder(line[1], 0))) { + ext -= Math.ceil(ext / 2); + line = extendLine(line, angle, -ext); + } + return line; +} diff --git a/src/common/image_debug.js b/src/common/image_debug.js index 77f08a6..5f72456 100644 --- a/src/common/image_debug.js +++ b/src/common/image_debug.js @@ -15,9 +15,24 @@ export default { for (var j = 1; j < path.length; j++) { ctx.lineTo(path[j][def.x], path[j][def.y]); } - ctx.closePath(); ctx.stroke(); }, + drawVertex: function(vertex, def, ctx) { + ctx.beginPath(); + ctx.moveTo(vertex[def.x]-2, vertex[def.y]-2); + ctx.lineTo(vertex[def.x]+2, vertex[def.y]+2); + ctx.moveTo(vertex[def.x]-2, vertex[def.y]+2); + ctx.lineTo(vertex[def.x]+2, vertex[def.y]-2); + ctx.stroke(); + }, + drawVertices: function(vertices, def, ctx, style) { + ctx.strokeStyle = style.color; + ctx.fillStyle = style.color; + ctx.lineWidth = style.lineWidth; + vertices.forEach((vertex) => { + this.drawVertex(vertex, def, ctx); + }); + }, drawImage: function(imageData, size, ctx) { var canvasData = ctx.getImageData(0, 0, size.x, size.y), data = canvasData.data, diff --git a/src/decoder/barcode_decoder.js b/src/decoder/barcode_decoder.js index 11a32a2..387d491 100644 --- a/src/decoder/barcode_decoder.js +++ b/src/decoder/barcode_decoder.js @@ -9,6 +9,13 @@ import UPCReader from '../reader/upc_reader'; import EAN8Reader from '../reader/ean_8_reader'; import UPCEReader from '../reader/upc_e_reader'; import I2of5Reader from '../reader/i2of5_reader'; +import Pdf417Reader from '../reader/pdf_417_reader'; +import { + getCenterLineFromBox as getLine, + getLineLength, + getLineAngle, + getExtendedLine +} from '../common/geometric'; const READERS = { code_128_reader: Code128Reader, @@ -19,7 +26,8 @@ const READERS = { codabar_reader: CodabarReader, upc_reader: UPCReader, upc_e_reader: UPCEReader, - i2of5_reader: I2of5Reader + i2of5_reader: I2of5Reader, + pdf_417_reader: Pdf417Reader }; export default { create: function(config, inputImageWrapper) { @@ -115,44 +123,6 @@ export default { } } - /** - * extend the line on both ends - * @param {Array} line - * @param {Number} angle - */ - function getExtendedLine(line, angle, ext) { - function extendLine(amount) { - var extension = { - y: amount * Math.sin(angle), - x: amount * Math.cos(angle) - }; - - line[0].y -= extension.y; - line[0].x -= extension.x; - line[1].y += extension.y; - line[1].x += extension.x; - } - - // check if inside image - extendLine(ext); - while (ext > 1 && (!inputImageWrapper.inImageWithBorder(line[0], 0) - || !inputImageWrapper.inImageWithBorder(line[1], 0))) { - ext -= Math.ceil(ext / 2); - extendLine(-ext); - } - return line; - } - - function getLine(box) { - return [{ - x: (box[1][0] - box[0][0]) / 2 + box[0][0], - y: (box[1][1] - box[0][1]) / 2 + box[0][1] - }, { - x: (box[3][0] - box[2][0]) / 2 + box[2][0], - y: (box[3][1] - box[2][1]) / 2 + box[2][1] - }]; - } - function tryDecode(line) { var result = null, i, @@ -215,12 +185,6 @@ export default { return result; } - function getLineLength(line) { - return Math.sqrt( - Math.pow(Math.abs(line[1].y - line[0].y), 2) + - Math.pow(Math.abs(line[1].x - line[0].x), 2)); - } - /** * With the help of the configured readers (Code128 or EAN) this function tries to detect a * valid barcode pattern within the given area. @@ -240,10 +204,17 @@ export default { } } + if (_barcodeReaders.some((reader) => (reader.FORMAT === 'pdf147'))) { + const reader = _barcodeReaders + .filter((reader) => (reader.FORMAT === 'pdf147'))[0]; + reader.decode(inputImageWrapper, box, ctx); + return null; + } + line = getLine(box); lineLength = getLineLength(line); - lineAngle = Math.atan2(line[1].y - line[0].y, line[1].x - line[0].x); - line = getExtendedLine(line, lineAngle, Math.floor(lineLength * 0.1)); + lineAngle = getLineAngle(line); + line = getExtendedLine(inputImageWrapper, line, lineAngle, Math.floor(lineLength * 0.1)); if (line === null){ return null; } @@ -279,6 +250,7 @@ export default { barcodes = [], multiple = config.multiple; + console.log(boxes); for ( i = 0; i < boxes.length; i++) { const box = boxes[i]; result = decodeFromBoundingBox(box) || {}; diff --git a/src/detector/pdf_147_detector.js b/src/detector/pdf_147_detector.js new file mode 100644 index 0000000..3bf043a --- /dev/null +++ b/src/detector/pdf_147_detector.js @@ -0,0 +1,48 @@ +import { + getCenterLineFromBox as getLine, + getLineLength, + getLineAngle, + getExtendedLine +} from '../common/geometric'; +import ImageDebug from '../common/image_debug'; +import Bresenham from '../decoder/bresenham'; +import {findPattern} from '../reader/common'; + +const vec2 = { + clone: require('gl-vec2/clone'), + dot: require('gl-vec2/dot') +} + +const START_PATTERN = [8, 1, 1, 1, 1, 1, 1, 3], + STOP_PATTERN = [7, 1, 1, 3, 1, 1, 1, 2, 1], + MODULO = START_PATTERN.reduce((sum, i) => (sum + i),0); + +export default function detect(inputImageWrapper, box, ctx) { + let line = getLine(box), + lineLength = getLineLength(line), + lineAngle = getLineAngle(line), + extendedLine = getExtendedLine(inputImageWrapper, line, lineAngle, Math.floor(lineLength * 0.15)), + barcodeLine = Bresenham.getBarcodeLine(inputImageWrapper, extendedLine[0], extendedLine[1]); + + if (ENV.development) { + if (ctx) { + ImageDebug.drawPath(extendedLine, {x: 'x', y: 'y'}, ctx, {color: 'red', lineWidth: 1}); + } + } + + Bresenham.toBinaryLine(barcodeLine); + + const match = findPattern(barcodeLine.line, START_PATTERN, {modulo: MODULO}); + console.log(match); + + return [ + vec2.clone([199, 84]), + vec2.clone([210, 303]), + vec2.clone([634, 88]), + vec2.clone([660, 271]), + vec2.clone([295, 83]), + vec2.clone([310, 295]), + vec2.clone([554, 83]), + vec2.clone([580, 277]) + ] +}; diff --git a/src/reader/common.js b/src/reader/common.js new file mode 100644 index 0000000..5f21e42 --- /dev/null +++ b/src/reader/common.js @@ -0,0 +1,126 @@ +export function nextSet(line, offset) { + var i; + + offset = offset || 0; + for (i = offset; i < line.length; i++) { + if (line[i]) { + return i; + } + } + return line.length; +}; + +export function normalize(counter, modulo) { + var i, + sum = 0, + ratio, + numOnes = 0, + normalized = [], + norm = 0; + + for (i = 0; i < counter.length; i++) { + if (counter[i] === 1) { + numOnes++; + } else { + sum += counter[i]; + } + } + ratio = sum / (modulo - numOnes); + if (ratio > 1.0) { + for (i = 0; i < counter.length; i++) { + norm = counter[i] === 1 ? counter[i] : counter[i] / ratio; + normalized.push(norm); + } + } else { + ratio = (sum + numOnes) / modulo; + for (i = 0; i < counter.length; i++) { + norm = counter[i] / ratio; + normalized.push(norm); + } + } + return normalized; +} + +export function matchPattern(counter, code, modulo, maxSingleError=1) { + var i, + error = 0, + singleError = 0; + + for (i = 0; i < counter.length; i++) { + singleError = Math.abs(code[i] - counter[i]); + if (singleError > maxSingleError) { + return Number.MAX_VALUE; + } + error += singleError; + } + return error / modulo; +} + +export function findPattern(row, pattern, { + offset, + isWhite=false, + tryHarder=true, + epsilon=0.5, + maxSingleError=0.5, + modulo}) { + var counter = [], + i, + counterPos = 0, + bestMatch = { + error: Number.MAX_VALUE, + code: -1, + start: 0, + end: 0 + }, + error, + j, + sum, + normalized; + + if (!offset) { + offset = nextSet(row); + } + + for ( i = 0; i < pattern.length; i++) { + counter[i] = 0; + } + + for ( i = offset; i < row.length; i++) { + if (row[i] ^ isWhite) { + counter[counterPos]++; + } else { + if (counterPos === counter.length - 1) { + sum = 0; + for ( j = 0; j < counter.length; j++) { + sum += counter[j]; + } + normalized = normalize(counter, modulo); + if (normalized) { + error = matchPattern(normalized, pattern, modulo, maxSingleError); + + if (error < epsilon) { + bestMatch.error = error; + bestMatch.start = i - sum; + bestMatch.end = i; + return bestMatch; + } + } + if (tryHarder) { + for ( j = 0; j < counter.length - 2; j++) { + counter[j] = counter[j + 2]; + } + counter[counter.length - 2] = 0; + counter[counter.length - 1] = 0; + counterPos--; + } else { + return null; + } + } else { + counterPos++; + } + counter[counterPos] = 1; + isWhite = !isWhite; + } + } + return null; +} diff --git a/src/reader/pdf_417_reader.js b/src/reader/pdf_417_reader.js new file mode 100644 index 0000000..4b24f95 --- /dev/null +++ b/src/reader/pdf_417_reader.js @@ -0,0 +1,30 @@ +import BarcodeReader from './barcode_reader'; +import detect from '../detector/pdf_147_detector'; +import ImageDebug from '../common/image_debug'; + +function Pdf147Reader() { + BarcodeReader.call(this); +} + +const properties = { + SINGLE_CODE_ERROR: {value: 1}, + AVG_CODE_ERROR: {value: 0.5}, + FORMAT: {value: "pdf147", writeable: false} +}; + +Pdf147Reader.prototype = Object.create(BarcodeReader.prototype, properties); +Pdf147Reader.prototype.constructor = Pdf147Reader; + +Pdf147Reader.prototype.decode = function(inputImageWrapper, box, ctx) { + console.log("Pdf147Reader..."); + const detectionInfo = detect(inputImageWrapper, box, ctx); + console.log(detectionInfo); + if (ENV.development) { + if (ctx) { + ImageDebug.drawVertices(detectionInfo, {x: 0, y: 1}, ctx, {color: "red", lineWidth: 1}); + } + } + console.log("Pdf147Reader... END") +}; + +export default Pdf147Reader;