使用QuaggaJs实现条形码扫描

1,033 阅读8分钟

背景:项目有用到条形码扫码的需求,一开始用的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: 指定摄像头的方向,常见值包括 environmentuser,分别为后置摄像头和前置摄像头。
  • 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-smallsmallmediumlargex-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。可配置多个,但多个类型同时配置时可能会造成识别冲突,尽量按所需识别的条形码类型按需设置。目前已支持以下条码识别:
  1. code_128_reader :Code 128 条形码,能够解码所有 128 个 ASCII 字符
  2. ean_reader:EAN-13 条形码,常用于零售商品,包含 13 位数字。
  3. ean_8_reader: EAN-8 条形码,用于小包装商品,包含 8 位数字。
  4. code_39_reader:Code 39 条形码,包含字母和数字。
  5. code_39_vin_reader:用于解码车辆识别码 (VIN) 的 Code 39 条形码。
  6. codabar_reader:Codabar 条形码,通常用于图书馆和医院系统。
  7. upc_reader:UPC-A 条形码,主要用于美国和加拿大的零售商品,包含 12 位数字。
  8. upc_e_reader:UPC-E 条形码,UPC-A 的压缩版本,包含 6 位数字。
  9. i2of5_reader:Interleaved 2 of 5 (I2of5) 条形码,常用于物流和仓储。
  10. 2of5_reader:2 of 5 条形码,通常用于工业和商业环境。
  11. 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});
      }
    }
  });