识别EAN-13条形码(JavaScript)

上篇文章写了如何用JavaScript生成EAN-13条形码,这次讲下如何不用库,用原生JavaScript识别EAN-13条形码。

老师给的实验方法是:
面向一维条码的图像识别,是模式识别与机器智能的一种最简单的应用。假设之间你生成的一维码图像(不含人识别的数字),假设你的图像扫描设备完全平行于一维码(即图像没有旋转),则图像可简单理解为在一特定矩形区域内的黑白相间的直方图。对于一个特定的像素点,如果是一个字节表示某个像素点的灰度值,则理论上黑的灰度很高,白的灰度很低。识别是取一个中间值,大于此值的为1(黑),小于此值的为零(白)。
识别是,可以在二维的维度进行,也可以把你的二维图像映射到一维,即把矩形区域的直方图全部投影到X轴,简单判别X轴每个像素点的灰度值,就能识别各个黑白模块的宽度,和宽度相对值,即可识别。

原本小组的另一个同学是已经利用Java的条形码库做好识别了,但是按照这个要求,就必须得自己写识别算法了,所以我查了一下如何用JavaScript做图像识别,参照https://blog.csdn.net/sunny_xsc1994/article/details/78355033
根据这篇博客我获取了上传的图片的像素矩阵,获取矩阵以后我发现,这个矩阵好大啊,输出这个像素矩阵还卡了,因为我的图片宽高还有点大,再加上这个矩阵里包含的值有该像素的red、green、blue、alpha四个值,所以这个像素矩阵的大小为宽 X高 X 4,不过得到这个矩阵后,就有了大致思路了
获取的像素矩阵

  1. 取条码的第一行像素点的值
  2. 确定条码的单位模块宽度
  3. 根据单位模块宽度确定剩余条和空的宽度并确定是条或空
  4. 查询条码的数值对应表确定后12位的值以及版本
  5. 根据得到的12位码的前六位判断第一位

知道逻辑以后就开始正式的识别条码了。

取条码的第一行像素点的值
我这里采用了偷懒的方式,已经像素矩阵大小 = 图像宽度 * 图像高度 * 4
所以这个矩阵可以利用for循环遍历,其中i代表了行,j就代表了第j个像素点的red值
为什么说偷懒,因为我这里直接取了red值,并没有将rbga的四个值结合,所以这里也导致了我后面出现了识别率的问题。

这个for循环是得到非空白区的第一行的下标以及这一行的起始码的第一个像素点下标,记为startI,startJ。

for(var i=0;i<array.length/(4*image.width);i++){
    for(var j=0;j<array.length/(4*image.height);j++){
         if(data[4*i*image.width+j*4-4]===255&&data[4*i*image.width+j*4]===0){
             startI=i;
             startJ=j;
             break;
         }
     }
 }

这个for循环是取出第一行条码的最后一位终止码的最后一个像素点的下标,记为endJ。

for(var i=image.width*4-4;i>=0;i--){
   for(var j=0;j<array.length/(4*image.height);j++){
        if(data[4*i*image.width+j*4+4]===255&&data[4*i*image.width+j*4]===0){
            endJ=j;
            break;
        }

    }
}

然后把这一行的条码的所有像素点的red值放入数组作为之后处理的数据数组

for(var i=startJ;i<endJ;i++){
    arrayPX.push(data[startI*4*image.width+i*4]);
}
console.log(arrayPX);

这里输出的arrayPX显示为
在这里插入图片描述
这里的输出其实能看出一些问题,就是我的结尾像素取得似乎出问题,但是起始像素点取得没有问题,所以暂时先这样,如果大家有好的算法可以自己再优化一下。

确定条码的单位模块宽度
因为EAN-13码的编码规则是有起始码并且为101,而一个“1”和一个“0”都是占了一个单位模块宽度,所以我就从第一个像素点开始进行遍历,来获取单位宽度。
basicLen代表单位模块宽度。

var temp=arrayPX[0];
var j;
var basicLen;
for(j=1;j<arrayPX.length;j++){
    if(Math.abs(arrayPX[j]-temp)>150){
        basicLen=j;
        oneLindeCode.push(1);
        break;
    }
}

然后就是利用这个单位宽度,判断后面的条和空的宽度了。

根据单位模块宽度确定剩余条和空的宽度并确定是条或空

这里用tempOneCode储存除起始位的条空的二进制编码。
因为前面偷懒的原因,导致这里就要纠结精确度的问题了,同时因为将截图得到的条形码上传以后用Canvas加载图片出现了失真问题,得到的像素点的red值有了波动,所以我这里做了大约的处理,在判断某个条和空时,如果遍历碰到某个像素点的red值和此次循环的起始像素点的red值差值的绝对值在150以上,说明已经到了下一个空和条,需要进行下一次遍历了,而它的宽度也由于失真出现了波动,如果这个像素的下标减去起始像素点的下标除去单位模块宽度在一定范围内就将它归于一个宽度,参照代码可以知道怎么处理。

var temponeCode=new Array();
for(var i=basicLen;i<arrayPX.length;i++){
    var flag=1;
    var temp = arrayPX[i];
    var j;
    for(j=i+1;j<arrayPX.length;j++){
        if(Math.abs(arrayPX[j]-temp)>150){
            flag=0;
            break;
        }
    }
    var length = j-i;
    var dif = length/basicLen;
    var thisLen;
    if(dif<1.5&&dif>0.5){
        thisLen=1;
    }
    else if(dif<2.5&&dif>1.5){
        thisLen=2;
    }
    else if(dif<3.5&&dif>2.5){
        thisLen=3;
    }
    else {
        thisLen=4;
    }
    var bin;
    if(Math.abs(temp-255)<=100)
        bin=0;
    else
        bin=1;
    for(var k=0;k<thisLen;k++){
        temponeCode.push(bin);
    }
    i=j-1;
}

前面提到了结束像素点取得有点问题,所以我们直接取得到的前95个二进制码(一个EAN-13码共有95个模块)。

oneLindeCode.push(1);
for(var i=0;i<94;i++){
    oneLindeCode.push(temponeCode[i]);
}

查询条码的数值对应表确定后12位的值以及版本

这里就遍历数组就行,我先定义了ABC三个版本不同数值对应的二进制编码的值组成的数组,以及不同起始符对应的前六位数值的版本所组成的数组

var PresentNumber=
    [   "0001101","0100111","1110010",
        "0011001","0110011","1100110",
        "0010011","0011011","1101100",
        "0111101","0100001","1000010",
        "0100011","0011101","1011100",
        "0110001","0111001","1001110",
        "0101111","0000101","1010000",
        "0111011","0010001","1000100",
        "0110111","0001001","1001000",
        "0001011","0010111","1110100"
    ]


var version=[
    ["A","A","A","A","A","A"],
    ["A","A","B","A","B","B"],
    ["A","A","B","B","A","B"],
    ["A","A","B","B","B","A"],
    ["A","B","A","A","B","B"],
    ["A","B","B","A","A","B"],
    ["A","B","B","B","A","A"],
    ["A","B","A","B","A","B"],
    ["A","B","A","B","B","A"],
    ["A","B","B","A","B","A"]
];

因为前六位数值和后六位数值中间隔着中间分隔符,所以我将循环分开了,先判断前六位
还有起始码,所以从点3(编号为0-94)开始遍历。

for(var i=3;i<45;i+=7){
 str="";
 for(var j=0;j<7;j++){
     str+=oneLindeCode[i+j].toString();
 }
 console.log(str);
 var index;
 for(var k=0;k<30;k++){
     if(str===PresentNumber[k]){
         index=k;
         break;
     }
 }
 codeBar.push(parseInt((index+1)/3));
 if(index%3===0){
     tempVersion.push("A");
 }
 else if(index%3===1){
     tempVersion.push("B");
 }
 else
     tempVersion.push("C");
}

然后确定后六位

for(var i=50;i<92;i+=7){
    str="";
    for(var j=0;j<7;j++){
        str+=oneLindeCode[i+j].toString();
    }
    console.log(str);
    var index;
    for(var k=0;k<30;k++){
        if(str===PresentNumber[k]){
            index=k;
            break;
        }
    }
    codeBar.push(parseInt((index)/3));
}

最后输出结果如下
在这里插入图片描述
和上传的图片的码一致了,说明处理成功了,然后就是判断起始符。

根据得到的12位码的前六位判断第一位

js部分

for(var i=0;i<10;i++){
    if(version[i].toString()==tempVersion.toString()){
        startCode=i;
        break;
    }
}

然后就将得到的这十三位的条码在网页上显示了。

源码

var PresentNumber=
    [   "0001101","0100111","1110010",
        "0011001","0110011","1100110",
        "0010011","0011011","1101100",
        "0111101","0100001","1000010",
        "0100011","0011101","1011100",
        "0110001","0111001","1001110",
        "0101111","0000101","1010000",
        "0111011","0010001","1000100",
        "0110111","0001001","1001000",
        "0001011","0010111","1110100"
    ]


var version=[
    ["A","A","A","A","A","A"],
    ["A","A","B","A","B","B"],
    ["A","A","B","B","A","B"],
    ["A","A","B","B","B","A"],
    ["A","B","A","A","B","B"],
    ["A","B","B","A","A","B"],
    ["A","B","B","B","A","A"],
    ["A","B","A","B","A","B"],
    ["A","B","A","B","B","A"],
    ["A","B","B","A","B","A"]
];

var oneLindeCode = new Array();

//识别条形码
function readCodeBar() {

    //创建画布
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext('2d');

    //读取图片
    var myCodeBar = document.getElementById("input_image").files[0];
    var reader = new FileReader();
    reader.readAsDataURL(myCodeBar);

    reader.onload = function (ev) {
        var image = new Image();
        image.src = ev.target.result;
        image.onload = function () {

            console.log("宽度"+this.width+"高度"+this.height);
            ctx.drawImage(image,0,0,image.height,image.width);
            var data = ctx.getImageData(0,0,image.width,image.height).data;
            // console.log(data.toString());
            var array = data;
            var startI ,startJ,endJ;
            var arrayPX = new Array();
            for(var i=0;i<array.length/(4*image.width);i++){
                for(var j=0;j<array.length/(4*image.height);j++){
                    if(data[4*i*image.width+j*4-4]===255&&data[4*i*image.width+j*4]===0){
                        startI=i;
                        startJ=j;
                        break;
                    }

                }
            }
            for(var i=image.width*4-4;i>=0;i--){
                for(var j=0;j<array.length/(4*image.height);j++){
                    if(data[4*i*image.width+j*4+4]===255&&data[4*i*image.width+j*4]===0){
                        endJ=j;
                        break;
                    }

                }
            }
            for(var i=startJ;i<endJ;i++){
                arrayPX.push(data[startI*4*image.width+i*4]);
            }
            // console.log(arrayPX.toString());
            // arrayPX.push(data[4*i*image.width+j*4]);
            var temp=arrayPX[0];
            var j;
            var basicLen;
            for(j=1;j<arrayPX.length;j++){
                if(Math.abs(arrayPX[j]-temp)>150){
                    basicLen=j;
                    oneLindeCode.push(1);
                    break;
                }
            }
            var temponeCode=new Array();
            for(var i=basicLen;i<arrayPX.length;i++){
                var flag=1;
                var temp = arrayPX[i];
                var j;
                for(j=i+1;j<arrayPX.length;j++){
                    if(Math.abs(arrayPX[j]-temp)>150){
                        flag=0;
                        break;
                    }
                }
                var length = j-i;
                var dif = length/basicLen;
                var thisLen;
                if(dif<1.5&&dif>0.5){
                    thisLen=1;
                }
                else if(dif<2.5&&dif>1.5){
                    thisLen=2;
                }
                else if(dif<3.5&&dif>2.5){
                    thisLen=3;
                }
                else {
                    thisLen=4;
                }
                var bin;
                if(Math.abs(temp-255)<=100)
                    bin=0;
                else
                    bin=1;
                for(var k=0;k<thisLen;k++){
                    temponeCode.push(bin);
                }
                i=j-1;
            }
            //生成正式的二进制代码
            for(var i=0;i<94;i++){
                oneLindeCode.push(temponeCode[i]);
            }
            console.log(oneLindeCode)
            var codeBar=new Array();
            var str = new String();
            var tempVersion = new Array();
            for(var i=3;i<45;i+=7){
                str="";
                for(var j=0;j<7;j++){
                    str+=oneLindeCode[i+j].toString();
                }
                console.log(str);
                var index;
                for(var k=0;k<30;k++){
                    if(str===PresentNumber[k]){
                        index=k;
                        break;
                    }
                }
                codeBar.push(parseInt((index+1)/3));
                if(index%3===0){
                    tempVersion.push("A");
                }
                else if(index%3===1){
                    tempVersion.push("B");
                }
                else
                    tempVersion.push("C");
            }
            for(var i=50;i<92;i+=7){
                str="";
                for(var j=0;j<7;j++){
                    str+=oneLindeCode[i+j].toString();
                }
                console.log(str);
                var index;
                for(var k=0;k<30;k++){
                    if(str===PresentNumber[k]){
                        index=k;
                        break;
                    }
                }
                codeBar.push(parseInt((index)/3));
            }
            console.log(codeBar);
            console.log(tempVersion);
            var startCode;
            for(var i=0;i<10;i++){
                if(version[i].toString()==tempVersion.toString()){
                    startCode=i;
                    break;
                }
            }
            var tempStr=new String();
            tempStr+=startCode;
            for(var i=0;i<12;i++){
                tempStr+=codeBar[i];
            }
            $("#codeBar_content").val(tempStr)
            // var data = ctxt.getImageData(0,0,this.width,this.height).data;
            // console.log(data,data.toString());
        }
    }
}

html部分

<div id="body_read">
    <input id="input_image" type="file" placeholder="请上传条码图片">
    <button id="read_EAN" "readCodeBar()">识别条形码</button>
</div>

<canvas id="canvas">对不起,你的浏览器不支持Canvas</canvas>
<textarea id="codeBar_content"></textarea>

总结
虽然这个代码可以识别出一些条形码,但是还有挺大的失误的,比如上传的图片上方空白过多就会出错,以及在同一个页面里暂时只能做一次处理,好像是因为图像缓存的问题,大家都可以再优化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值