QuaggaJS是条形码扫描器完全用JavaScript编写支持实时的本地化和各种类型的条形码,如
EAN, CODE 128,CODE 39,EAN 8,UPC-A ,UPC-C ,I2of5, 2of5,CODE 93和CODABAR.该类库还能够 getUserMedia直接访问用户的相机流,使得我们的开发变得更加简单。
注意:
getUserMedia在绝大多数浏览器中都需要使用安全站点进行访问也就是https的模式,如果你想使用http协议进行访问,那么你就需要使用本地连接localhost(127.0.0.1)来进行访问测试,所有其他的主机访问协议必须都是https的方式。当然通过个人在开发中的测试发现火狐浏览器,无论是手机端还是电脑端,对这种协议的约束完全降低,完全可以直接访问,所以说个人建议在开发过程中可以尝试火狐,方便调试。
当然其他的浏览器对getUSerMedia对象是支持的,以下是常见浏览器的支持情况
浏览器 |
结果 |
Edge |
true |
Chrome |
true |
Firefox |
true |
IE 11 |
false |
Safari iOS |
false |
以下为项目的地址,我们可以下载项目在此基础上进项部署开发
项目地址:
https://github.com/serratus/quaggaJS#browser-support
以下介绍开发过程中常用的API
Quagga.init(config,callback)
该方法初始化给定配置的库config(见下文),并调用callback(err)Quagga完成引导阶段之后。如果配置了实时检测,则初始化过程还要求摄像机访问。如果发生错误,err 则设置参数并包含有关原因的信息。可能的原因可能inputStream.type是设置LiveStream,但浏览器不支持此API,或者简单地说,如果用户拒绝使用相机的权限。
如果没有指定目标,QuaggaJS会查找与CSS选择器匹配的元素#interactive.viewport(为了向后兼容)。 target可以是一个字符串(与CSS元素匹配的DOM节点)或DOM节点。
例子: |
Quagga.init({ inputStream : { name : "Live", type : "LiveStream", target: document.querySelector(‘#yourElement‘) // Or ‘#yourElement‘ (optional) }, decoder : { readers : ["code_128_reader"] } }, function(err) { if (err) { console.log(err); return console.log("Initialization finished. Ready to start"); |
Quagga.start()
当库被初始化时,该start()方法启动视频流并开始定位和解码图像。
Quagga.stop()
如果解码器当前正在运行,则在调用stop()解码器之后不再处理任何图像。另外,如果在初始化时请求相机流,则此操作也会断开相机的连接。
Quagga.onProcessed(callback)
该方法注册callback(data)处理完成后为每个帧调用的函数。该data对象包含有关操作成功/失败的详细信息。取决于检测和/或解码是否成功,输出会有所不同
Quagga.onDetected(callback)
注册一个callback(data)功能,每当条形码图案被定位和解码成功时触发。传递的data对象包含关于解码处理的信息,包括可以通过调用获得的检测到的代码data.codeResult.code。
Quagga.offProcessed(handler)
如果onDetected事件不再是相关的,请从事件队列中offProcessed删除给定handler的事件
Quagga.offDetected(handler)
如果onDetected事件不再相关,请从事件队列中offDetected删除给定handler的事件。
结果对象:
回调通入onProcessed,onDetected并decodeSingle 接收data在执行对象。该data对象包含以下信息。取决于成功,某些领域可能是undefined或只是空的。
代码 |
{ " codeResult ": { "代码": "格式": "启动": " end ": " codeset ": " startInfo ": { " error ": " code ": " start ": " end ": " decodeCodes ": [{ " code ": " start ": " end ": //为了简洁而剥离 "错误": "代码": "启动": "端": " endInfo ": { " error ": " code ": " start ": " end ": "方向": - 1 " line ": [{ " x ": " y ": " x ": " y ": "角度": - 0.6565217179979483, "图案": [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, / * ...* / 1 ], " box ": [ " box ": [ |
Configuration
QuaggaJS附带的配置涵盖了默认用例,可根据具体要求进行微调。
该配置由config定义以下高级属性的对象管理:
代码: |
{ numOfWorkers: 4, locate: true, inputStream: {...}, frequency: 10, decoder:{...}, locator: {...}, debug: false, } |
numOfWorkers
QuaggaJS支持线程,并在4线程的默认配置下运行。该数字应与目标设备中可用的核心数量一致。
如果您不知道前面的数字,或者设备种类太多,您可以使用navigator.hardwareConcurrency(参见 这里)可用或使用核心估计器。
Locate
QuaggaJS的主要特征之一就是能够在给定的图像中定位条形码。该locate属性控制是否启用此功能(默认)或关闭。
为什么有人会关闭此功能?本地化条形码是一项计算上昂贵的操作,可能无法在某些设备上正常工作。另一个原因是缺乏自动对焦产生模糊的图像,这使得本地化功能非常不稳定。
然而,即使没有上述情况适用,还有一种情况可能会禁用locate:如果条形码的方向和/或大致位置已知,或者如果要引导用户通过矩形大纲。这可以同时提高性能和鲁棒性。
inputStream
该inputStream属性定义了QuaggaJS中的图像/视频的来源。
代码 |
{ name: "Live", type: "LiveStream", constraints: { width: 640, height: 480, facingMode: "environment", deviceId: "7832475934759384534" }, area: { // defines rectangle of the detection/localization area top: "0%", // top offset right: "0%", // right offset left: "0%", // left offset bottom: "0%" // bottom offset }, singleChannel: false // true: only the red color-channel is read } |
首先,type属性可以被设置为三个不同的值: ImageStream,VideoStream,或LiveStream(默认值),并应根据使用情况进行选择。很可能,默认值是足够的。 第二,该constraint键定义输入图像的物理尺寸和附加属性,例如facingMode在多个连接的设备的情况下设置用户相机的源。另外,如果需要,deviceId如果给用户选择相机,则可以设置。这可以通过MediaDevices.enumerateDevices()轻松实现 |
frequency:
此顶级属性控制视频流的扫描频率。它是可选的,并定义每秒扫描的最大数量。这对于扫描会话长时间运行的情况以及诸如CPU功耗等资源非常有用。
decoder:
QuaggaJS通常以两级方式运行(locate设置为true),在条形码位于之后,解码过程开始。解码是将条形转换成其真实含义的过程。大多数配置选项decoder仅用于调试/可视化目的。
代码 |
{ readers: [ ‘code_128_reader‘ |
最重要的属性是readers需要在会话期间解码的条形码类型的数组。可能的值是:
- code_128_reader(默认)
- ean_reader
- ean_8_reader
- code_39_reader
- code_39_vin_reader
- codabar_reader
- upc_reader
- upc_e_reader
- i2of5_reader
- 2of5_reader
- code_93_reader
为什么默认情况下不会激活所有类型?只是因为应该为用例明确定义一组条形码。更多的解码器意味着更多的可能的冲突,或者是错误的。应该照顾读者的命令,因为有些人可能会返回一个值,即使它不是正确的类型(EAN-13与UPC-A)。
该multiple属性告诉解码器,在找到有效的条形码后是否应该继续解码。如果设置为多个true,结果将作为结果对象的数组返回。数组中的每个对象将具有一个 box,并且可能具有codeResult取决于解码单个框的成功。
其余的性质drawBoundingBox,showFrequency,drawScanline和 showPattern大多的调试和可视化过程中的利益。
启用扩展EAN:
默认设置ean_reader不能读取扩展名,如EAN-2或EAN-5。为了激活这些补充,您必须按照以下配置提供它们:
代码: |
decoder: { readers: [{ format: "ean_reader", config: { supplements: [ ‘ean_5_reader‘, ‘ean_2_reader‘ |
请注意,supplements当发现第一次补充时,读者停止解码的事宜的顺序。因此,如果您对EAN-2和EAN-5扩展感兴趣,请使用上面描述的顺序。 重要的是要提及,如果提供补充,常规的EAN-13代码不能再读取同一个读者。如果您想阅读带有和不带扩展名的EAN-13,则必须向ean_reader配置添加另一个阅读器。 |
Locator
locator如果locate标志设置为只有相关的配置true。它控制本地化过程的行为,需要针对每个具体的用例进行调整。默认设置只是在开发过程中最有效的值的组合。
只有两个属性与Quagga(halfSample和 patchSize)中的使用相关,而其余属性仅用于开发和调试
代码 |
{ halfSample: true, patchSize: "medium", // x-small, small, medium, large, x-large debug: { showCanvas: false, showPatches: false, showFoundPatches: false, showSkeleton: false, showLabels: false, showPatchLabels: false, showRemainingPatchLabels: false, boxFromPatches: { showTransformed: false, showTransformedBox: false, showBB: false } } } |
该halfSample标志告诉定位器处理是否应该对缩小的图像(半宽/高,四分之一像素计数)进行操作。打开 halfSample显着减少处理时间,并且还有助于由于隐式平滑而找到条形码图案。在条形码真的很小并且需要全分辨率来找到位置的情况下应该关闭它。如果需要,建议保持打开并使用更高分辨率的视频图像。 第二个属性patchSize定义搜索网格的密度。该属性接受的值的字符串x-small,small,medium,large和 x-large。的patchSize是正比于扫描条形码的大小。如果你有很大的条形码,可以读取特写,那么使用 large或x-large推荐。在条形码远离相机镜头(缺少自动对焦或小条形码)的情况下,建议将尺寸设置为small甚至均匀x-small。对于后者,还建议您调整分辨率以找到条形码。 |
ResultCollector
对结果进行筛选减少错误率
代码 |
var resultCollector = Quagga.ResultCollector.create({ capture: true, //跟踪生成结果图像 capacity: 20, //存储结果的最大数量 blacklist: [{ //不应该被记录的code的黑名单 code: "WIWV8ETQZ1", format: "code_93" }, { code: "EH3C-%GU23RK3", format: "code_93" }, { code: "O308SIHQOXN5SA/PJ", format: "code_93" }, { code: "DG7Q$TV8JQ/EN", format: "code_93" }, { code: "VOFD1DB5A.1F6QU", format: "code_93" }, { code: "4SO64P4X8 U4YUU1T-", format: "code_93" }], filter: function(codeResult) { //过滤器,严格定义存出结果 returns true/false // only store results which match this constraint // e.g.: return codeResult.format === "ean_13"; return codeResult.format === "code_128_reader"; } }); |
开发过程中源码简单的修改
2017年8月7日
9:24
* Iterate over the entire image 遍历整个图像
* extract patches
*/
function findPatches() {
var i,
j,
x,
y,
moments,
patchesFound = [],
rasterizer,
rasterResult,
patch;
//二维遍历
for (i = 0; i < _numPatches.x; i++) {
for (j = _numPatches.y * 0.4; j < _numPatches.y * 0.53; j++) { //根据业务需求修改扫描范围 ——殷瑜泰2017年08月03日 星期四 下午 8点07分08秒.
x = _subImageWrapper.size.x * i;
y = _subImageWrapper.size.y * j;
// seperate parts
skeletonize(x, y);
// Rasterize, find individual bars
//点阵化查找条形码
_skelImageWrapper.zeroBorder();
__WEBPACK_IMPORTED_MODULE_2__common_array_helper__["a" /* default */].init(_labelImageWrapper.data, 0);
rasterizer = __WEBPACK_IMPORTED_MODULE_4__rasterizer__["a" /* default */].create(_skelImageWrapper, _labelImageWrapper);
rasterResult = rasterizer.rasterize(0);
if (true && _config.debug.showLabels) {
_labelImageWrapper.overlay(_canvasContainer.dom.binary, Math.floor(360 / rasterResult.count), { x: x, y: y });
}
// calculate moments from the skeletonized patch
moments = _labelImageWrapper.moments(rasterResult.count);
// extract eligible patches,提取符合规范的片段
patchesFound = patchesFound.concat(describePatch(moments, [i, j], x, y));
}
}
经过简单的查看源码可以的得知,该框架此处运行的功能是循环遍历摄像头的视频部分,对该部分图像遍历成对应的图片然后对图片进行识别从而返回结果扫描识别后的结果集。目前由于业务需只需要遍历指定的矩形框的内容,所以对源码进行了简单的修改增加了乘积,从而固定扫描区域。
经过个人的测试发现,该x,y轴的遍历布局如下所示
x,y轴之前有一定的比例关系,目前我这里的摄像头呈现图像比例是1比上1.3333333如上图所示的关系。
function findBiggestConnectedAreas(maxLabel) {
var i,
sum,
labelHist = [],
topLabels = [];
for (i = 0; i < maxLabel; i++) {
labelHist.push(0);
}
sum = _patchLabelGrid.data.length;
while (sum--) {
if (_patchLabelGrid.data[sum] > 0) {
labelHist[_patchLabelGrid.data[sum] - 1]++;
}
}
labelHist = labelHist.map(function (val, idx) {
return {
val: val,
label: idx + 1
};
});
labelHist.sort(function (a, b) {
return b.val - a.val;
});
// extract top areas with at least 6 patches present
topLabels = labelHist.filter(function (el) {
return el.val >= 6;//原来参数el.val >= 6 ——殷瑜泰2017年08月02日 星期三 上午 10点11分52秒.
});
return topLabels;
}
该方法是对合格的区域块进行筛选。
applySetting: function(setting, value) {
var track = Quagga.CameraAccess.getActiveTrack();
if (track && typeof track.getCapabilities === ‘function‘) {//typeof track.getCapabilities === ‘function‘
switch (setting) {
case ‘zoom‘:
//return track.applyConstraints({advanced: [{zoom: 1.0}]});
/*——殷瑜泰2017年08月03日 星期四 下午 2点17分39秒.
源代码 track.getCapabilities().zoom.max}
*/
return track.applyConstraints({advanced: [{zoom: parseFloat(value)}]});
case ‘torch‘:
return track.applyConstraints({advanced: [{torch: !!value}]});
}
}
}
闪光灯和焦距的配置方法
〖。