背景:项目有用到条形码扫码的需求,一开始用的zxing,后因为识别率实在不尽人意后,改用QuaggaJs,发现网上教程不多,遂作此分享。
官方文档地址:github.com/serratus/qu…
一、基本使用
a.安装
npm install quagga --save
b.导入
import Quagga from 'quagga'
c.定义html容器
<div id="videoContainer">
<video id="videoElement"></video>
<!-- 这里的video也可以不加,初始化时会在videoContainer中自动生成一个video元素,但加了可以方便自定义视频css属性,包括后面的canvas同理 -->
</div>
d.定义css样式
#videoContainer {
position: relative;
height: 400px;
background-color: black;
}
#videoElement {
position: absolute;
left: 50%;
transform: translate(-50%, 0);
width: 400px;
height: 100%;
z-index: 1;
}
e.容器初始化
Quagga.init(
{
inputStream: {
name: "Live",
type: "LiveStream",
target: document.querySelector("#videoContainer"),
constraints: {
facingMode: "environment",
},
},
locator: {
patchSize: "medium",
halfSample: true,
},
numOfWorkers: 4,
decoder: {
readers: ["code_128_reader"],
},
},
function (err) {
if (err) {
console.error(err);
return;
}
console.log("初始化完成");
Quagga.start();//打开摄像头
}
);
目前由于浏览器安全问题,插件最好在https环境下使用,http环境可能无法使用
二、核心api
详细api说明可参考官方文档
Quagga.init(config,callback):第一个参数是传入config对象,用于传入相机初始化的配置项。第二个参数是完成配置后的回调函数,注意只是配置完成,并不是相机启动完成,该函数返回会返回err作为结果。
Quagga.start():启动视频流并开始定位和解码图像。
Quagga.stop():停止视频流处理,断开相机连接。
Quagga.onProcessed(callback):注册一个回调函数callback(data),在每帧图像处理完后触发,data参数将返回识别的详细信息。
Quagga.offProcessed(handler):移除onProcessed绑定的处理函数。
Quagga.onDetected(callback):注册一个回调函数callback(data),在成功解码出条形码后触发,data参数将返回解码的详细信息,解码出的二维码在data.codeResult.code内。
Quagga.offDetected(handler):移除onDetected绑定的处理函数。
当多次重复初始化组件时,需要使用offDetected注销掉上一个通过onDetedted注册的函数,不然会因没有正确销毁函数而重复触发事件
Quagga.decodeSingle(config, callback):解析单张图片并识别里面的条形码,该用法类似quagga.init的用法,不同的是,这里需要传入src配置项指定图像的路径。
Quagga.decodeSingle({
decoder: {
readers: ["code_128_reader"]
},
locate: true,
src: 'path/to/image.jpg' // 图像源路径
}, function(result) {
if (result.codeResult) {
console.log("Detected code: " + result.codeResult.code);
} else {
console.log("No code detected.");
}
});
三、相机初始化config配置项
inputStream
配置视频或图像源选项,部分配置样例如下
inputStream:{
name: "Live",
type: "LiveStream",
target: document.querySelector("#videoContainer"),
constraints: {
width: 640,
height: 480,
facingMode: "environment"
},
singleChannel: false
}
type:图像源类型。可选项分别为*ImageStream(静态图像),VideoStream(导入视频流),LiveStream(实时视频流)*。
target:指定一个 DOM 元素作为视频流的容器。
constraints:约束条件(适用于LiveStream)。
- facingMode: 指定摄像头的方向,常见值包括
environment和user,分别为后置摄像头和前置摄像头。 - width: 图像宽度,通常为
{ ideal: 1280 }。 - height: 图像高度,通常为
{ ideal: 720 }。 - aspectRatio: 例如
{ ideal: 1.7777777777777777 },指定图像的宽高比。 - deviceId: 特定设备的 ID,指定使用哪个摄像头。
size :图像尺寸(适用于ImageStream)。
- width: 图像宽度,通常为
{ ideal: 640 }。 - height: 图像高度,通常为
{ ideal: 480 }。
area:限制图像的处理区域。有两种使用方法,
//第一种用法
area:{
top: "0%",//顶部偏移量
right: "0%",
left: "0%",
bottom: "0%"
},
//第二种用法
area:{
x:0,//图像左上角x坐标
y:0,//图像左上角y坐标
width:640,
height:480
}
singleChannel:是否仅开启图像单一通道。开启后将读取输入图像的红色通道而不再读取灰色通道,可以结合后面的ResultCollector对结果进行保存,保存错误识别图像的灰度表示。
numOfWorkers
指定用于图像处理的线程数量。默认为 4,更多的线程可以提高性能但增加内存消耗。
locate
是否启用定位条形码功能。默认为true,该属性会大大增加性能开销,且当设备缺乏自动对焦功能时,图像会模糊并使得定位功能不稳定,此时可选择关闭此属性。
locator
对条形码定位功能进行相应设置。官方样例如下:
{
halfSample: true,
patchSize: "medium",
debug: {
showCanvas: false, // 是否显示用于条形码定位的画布
showPatches: false,// 是否显示用于定位条形码的网格
showFoundPatches: false, // 是否显示已识别的网格
showSkeleton: false,// 是否显示条形码的“骨架”结构
showLabels: false,// 是否显示条形码的标签
showPatchLabels: false,// 是否显示网格的标签
showRemainingPatchLabels: false,// 是否显示剩余的网格标签
boxFromPatches: {
showTransformed: false,// 是否显示从网格中变换过来的框
showTransformedBox: false,// 是否显示变换后的条形码框
showBB: false// 是否显示边界框(Bounding Box)
}
}
}
只有halfSample与patchSize属性与使用相关,其余属性只用于调试用途。
- halfSample:是否对图像进行半采样。开启此选项可以显著减少处理时间,并使识别过程更加丝滑。但是当条形码非常小时应该关闭此选项避免误识别,官方建议此选项打开。
- patchSize:设置识别网格的密度。可选项有
x-small,small,medium,large,x-large。当设备没有自动对焦时或条形码远离摄像头时,可以适当减小设置的密度。
frequency
设置视频流的扫描频率,单位是毫秒。例如frequency: 500代表500毫秒进行一次扫描处理。
decoder
配置解码项。实际条形码识别过程一般分为定位和解码两个过程(locate设置为true时),官方案例如下:
{
readers: [
'code_128_reader'
],
debug: {
drawBoundingBox: false,//控制是否绘制条形码的边界框
showFrequency: false,//控制是否显示频率
drawScanline: false,//控制是否绘制扫描线
showPattern: false//控制是否显示条形码识别模式
}
multiple: false
}
只有readers与multiple属性与使用相关,其余属性只用于调试用途。
- readers:需要识别的条形码类型,默认
code_128_reader。可配置多个,但多个类型同时配置时可能会造成识别冲突,尽量按所需识别的条形码类型按需设置。目前已支持以下条码识别:
- code_128_reader :Code 128 条形码,能够解码所有 128 个 ASCII 字符
- ean_reader:EAN-13 条形码,常用于零售商品,包含 13 位数字。
- ean_8_reader: EAN-8 条形码,用于小包装商品,包含 8 位数字。
- code_39_reader:Code 39 条形码,包含字母和数字。
- code_39_vin_reader:用于解码车辆识别码 (VIN) 的 Code 39 条形码。
- codabar_reader:Codabar 条形码,通常用于图书馆和医院系统。
- upc_reader:UPC-A 条形码,主要用于美国和加拿大的零售商品,包含 12 位数字。
- upc_e_reader:UPC-E 条形码,UPC-A 的压缩版本,包含 6 位数字。
- i2of5_reader:Interleaved 2 of 5 (I2of5) 条形码,常用于物流和仓储。
- 2of5_reader:2 of 5 条形码,通常用于工业和商业环境。
- code_93_reader:Code 93 条形码,能表示更多字符集的高密度条形码。
另外,如果要开启ean_reader的拓展,比如EAN-2和EAN-5,要按照如下配置另外设置:
decoder: {
readers: [{
format: "ean_reader",
config: {
supplements: [
'ean_5_reader', 'ean_2_reader'
]
}
}]
}
- multiple:设置解码器在解码成功后是否继续解码,当设置为true时,结果数据将按数组返回,数组中每个对象都具有自己的box,并且可能因为单个条形码解码成功即返回结果。
按照官方说法,当启用supplements拓展时,EAN-13识别不能正常识别,需要向ean-reader配置添加另一个识别器。
四、结果过滤
使用过滤器对扫码结果进行过滤,减少错误率。
1.使用Quagga.ResultCollector.create生成过滤器
var resultCollector = Quagga.ResultCollector.create({
capture: true, // 保留产生此结果的图像
capacity: 20, // 最大存储结果的数量
blacklist: [
{code: "3574660239843", format: "ean_13"}],// 不应被记录的条形码列表
filter: function(codeResult) {
//过滤只符合该过滤条件的条码
return true;
}
});
2.使用Quagga.registerResultCollector(resultCollector)挂载过滤器
3.(可选)调用resultCollector.getResults()获取识别结果,结果包含如下
{
codeResult: {}, //返回的条码识别结果
frame: "data:image/png;base64,iVBOR..." // 灰度图像的dataURL
}
- codeResult:跟之前
Quagga.onDetected返回的结果一致,以及跟上面过滤器中filter回调函数返回的结果一致。 - frame:帧属性是图像的内部表示,因此仅在灰度级中可用。dataURL表示允许保存图像,可以配合之前的
Quagga.decodeSingle作为图像源做识别处理,但记得设置singleChannel为true。
五、绘制识别框
原理:生成视频流时会生成一个video元素以及一个canvas元素,可以通过该canvas为基准绘制识别框,理论上也可自己另外生成一个canvas元素实现自己另外的需求。以下代码参考他人博客,
Quagga.onProcessed(function(result) {
const drawingCtx = Quagga.canvas.ctx.overlay;
const drawingCanvas = Quagga.canvas.dom.overlay;
if (result) {
if (result.boxes) {
drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
result.boxes.filter(function (box) {
return box !== result.box;
}).forEach(function (box) {
//实时绘制识别过程中的识别框
Quagga.ImageDebug.drawPath(box, {x: 0, y: 1}, drawingCtx, {color: "green", lineWidth: 2});
});
}
if (result.box) {
//当有识别结果时绘制另外的结果框
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, drawingCtx, {color: "#00F", lineWidth: 2});
}
if (result.codeResult && result.codeResult.code) {
Quagga.ImageDebug.drawPath(result.line, {x: 'x', y: 'y'}, drawingCtx, {color: 'red', lineWidth: 3});
}
}
});