图像识别算法通常都要把彩图,转成灰度图,再转成二值图。如果把图像处理部分放到GPU上,就可以减少CPU的时间消耗。这篇文章分享下用WebGL把摄像头传入的彩图转成灰度图,以此提高Web条形码SDK的解码性能。
Web条形码识别
使用Dynamsoft JavaScript Barcode SDK(https://www.npmjs.com/package/dynamsoft-javascript-barcode) 可以快速实现一个Web条码扫描App。比如下面的代码,可以直接创建一个带webcam的扫码app:
let scanner = null;
(async()=>{
scanner = await Dynamsoft.BarcodeScanner.createInstance();
scanner.onFrameRead = results => {console.log(results);};
scanner.onUnduplicatedRead = (txt, result) => {alert(txt);};
await scanner.show();
})();
这里的BarcodeScanner类对camera做了封装,用户不需要手写摄像头相关代码,传入接口的图像是彩图。如果是自己通过canvas来实现,就需要使用BarcodeReader类,然后调用decodeBuffer()接口:
var barcodereader = null;
(async()=>{
barcodereader = await Dynamsoft.BarcodeReader.createInstance();
await barcodereader.updateRuntimeSettings('speed');
let settings = await barcodereader.getRuntimeSettings();
settings.deblurLevel = 0;
barcodereader.updateRuntimeSettings(settings);
})();
let canvas2d = document.createElement('canvas');
canvas2d.width = width;
canvas2d.height = height;
var ctx2d = canvas2d.getContext('2d');
ctx2d.drawImage(videoElement, 0, 0, width, height);
buffer = ctx2d.getImageData(0, 0, width, height).data;
if (barcodereader){
barcodereader
.decodeBuffer(
buffer,
width,
height,
width * 4,
Dynamsoft.EnumImagePixelFormat.IPF_ARGB_8888
)
.then((results) => {
showResults(results);
});
}
WebGL彩图转灰度图
灰度图是单通道,而彩图是四通道,所以理论上传入灰度图的解码速度要比彩图快。根据上面的解码代码,如果是灰度图,可以改成:
barcodereader
.decodeBuffer(
gray,
width,
height,
width,
Dynamsoft.EnumImagePixelFormat.IPF_GrayScaled
)
.then((results) => {
showResults(results);
});
根据网站的示例代码,修改shader来实现灰度转换:
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_texture;
uniform float u_colorFactor;
void main() {
vec4 sample = texture2D(u_texture, v_texcoord);
float grey = 0.21 * sample.r + 0.71 * sample.g + 0.07 * sample.b;
gl_FragColor = vec4(sample.r * u_colorFactor + grey * (1.0 - u_colorFactor), sample.g * u_colorFactor + grey * (1.0 - u_colorFactor), sample.b * u_colorFactor + grey * (1.0 - u_colorFactor), 1.0);
}
接下来就是通过视频元素来创建绘制纹理:
var drawInfo = {
x: 0,
y: 0,
dx: 1,
dy: 1,
textureInfo: loadImageAndCreateTextureInfo(videoElement)
};
draw(drawInfo);
通过纹理显示,会发现图像是颠倒的,所以需要调用pixelStorei做Y轴翻转:
function drawImage(tex, texWidth, texHeight, dstX, dstY) {
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
从纹理中获取灰度图的数据:
buffer = new Uint8Array(width * height * 4);
gl.readPixels(
0,
0,
gl.drawingBufferWidth,
gl.drawingBufferHeight,
gl.RGBA,
gl.UNSIGNED_BYTE,
buffer
);
这里创建的依然是4通道数据,因为不支持单通道数据读取。获取数据之后,要把灰度数据从数组中提取出来:
gray = new Uint8Array(width * height);
let gray_index = 0;
for (i = 0; i < width * height * 4; i += 4) {
gray[gray_index++] = buffer[i];
}
最后运行程序,可以看到事实的转换效果:
性能比较
通过console观察发现,使用WebGL之后,数据获取的时间5ms左右,而从canvas获取数据的时间只有1ms。WebGL的数据传输会比较耗时。但是灰度图的解码时间和彩图比要快上不少。所以总时间消耗是减少的。
视频
源码