文档章节

一维码:EAN-13码的识别

dnbc66
 dnbc66
发布于 2016/01/15 16:36
字数 2936
阅读 92
收藏 0

1.一维码简述;

    一维条码是一种能用于信息编码和信息自动识别的标准符号,是由一组宽度不同的黑白符号按一定规则交替排列编码组成的图形符号,用于表示一定的信息。

    码制指条码符号的类型,不同的类型有不同的编码规则。我们本次实验是基于EAN-13码制。EAN-13码主要由起始符(3)、左侧数据符(42)、中间分割符(5)、右侧数据符(42)、校验符、终止符(3)组成,一共95个模块,表示13个字符。条表示1,空表示0;只能表示0-9这十个数字;每个字符的宽度为7个模块,交替由两个条和两个空组成,每个条或者空的宽度不超过4个模块。起始符101,中间分割符01010,终止符101.

    我完成的这个识别程序能解析的条码类型包括标准、受噪声污染以及倾斜的一维码图像。 

2.解码方法(分为图像处理和译码两个部分)

2.1 图像处理

2.1.1 用imread()方法载入需要验证的一维码图像;

2.1.2 将载入的RGB三通道图像转化为灰度图像,每个像素点取值范围为0-255,共有256个灰度级别。用rgb2gray()函数可得到灰度图:

2.1.3 用大津法求阀值进而从灰度图像得到二值图,二值图的像素点取值范围不是0就是1,利于我们后续的译码操作.求阀值用graythresh()函数,求二值图用im2bw()函数:

2.1.4 接下来可以对图像进行滤波去噪以及图像校正,这一部分将在后文详细描述。这里先只讨论标准一维码的图像。 

2.2 译码

 2.2.1 获取条和空的宽度:这里的思路是遍历图像的每一个像素点,在一行中,当遇到像素值与其后一个点像素值不等的时候,记录其位置;后面的位置减去前面的位置,既可以得到条或空的宽度。对于一张标准的一维码图像,边界区域有60个,所以每一行应该有59个条/空的宽度值,当某一行的宽度值不等于59时,忽略该行。同时在这一步做了一个优化操作:由于得到的二值图中的条码的边界可能会出现锯齿和毛刺等现象,这就导致每次计算的宽度可能不一样,减少这个误差的方法是将所有有效行(59个宽度)的宽度相加后取平均值。相关代码如下:

[m,n]=size(A);

number=0;

for i=1:m

    pos_cnt=1;width_id=1;

    for j=1:n-1

        if A(i,j) ~= A(i,j+1)

            pos(i,pos_cnt)=j;

            if pos_cnt>1

                width(i,width_id)=pos(i,pos_cnt)-pos(i,pos_cnt-1);

                width_id=width_id+1;

            end

            pos_cnt=pos_cnt+1;

        end

    end 

    if width_id==60

            number=number+1;

            for k=1:59

        %将所有条/空的宽度都存储在total_len这个二维数组里

                  total_len(number,k)=width(i,k);

            end

        end

    end

end
[mm,nn]=size(total_len);
for i=1:nn
    tmp=0;
    for j=1:mm
        tmp=tmp+total_len(j,i);	%该宽度的所有值求和
    end
    final_width(1,i)=tmp/mm;  	%求均值
end


 2.2.2 获取单位模块宽度以及条空比例:前文已经提到,一维码图像包括95个图像,将上一步得到的全部宽度求和,除以95即可得到单位模块长度。然后将每个条/空的宽度除以单位模块宽度,即可得到条/空比例。这一步比较简单就不贴代码了。 

 2.2.3 对条和空区域进行 0/1标注:将条码区标注成 1,空白区标注成 0;这里需要注意的是,一个单位模块只能标注一种符号,条码和空白区域可能占据三四个单位模块。标注完成后,检查起始符( 101)、中间分割符( 01010)、终止符( 101)是否符合 EAN-13的条件,不符合则输入相应的判断信息,否则进行下一步:

index=1

for i=1:59

    if mod(i,2)==1

        for j=1:1:round(proposition(1,i))

            mat95(1,index)=1;

            index=index+1;

        end

    else

        for j=1:1:round(proposition(1,i))

            mat95(1,index)=0;

            index=index+1;

        end

    end

end

isCheck=0;

if(mat95(1,1)==1&&mat95(1,2)==0&&mat95(1,3)==1&&mat95(1,46)==0&&mat95(1,47)==1&&mat95(1,48)==0&&mat95(1,49)==1&&mat95(1,50)==0&&mat95(1,93)==1&&mat95(1,94)==0&&mat95(1,95)==1)

  isCheck=1;

end

 if isCheck==0

    msgbox('不满足EAN-13码的条件!');  %不满足则弹出msg框,同时终止程序

    return

end


 2.2.4 查表译码:

j=1;

for i=4:7:39

    left(1,j)=bin2dec(num2str(mat95(1:1,i:i+6)));

    j=j+1;

end

k=1;

for i=51:7:86

    right(1,k)=bin2dec(num2str(mat95(1:1,i:i+6)));

    k=k+1;

end




查表得到左边和右边各 6个字符对应的 0-9字符,同时根据表格创建一个 Map:根据左边数据用 AB字符集序列得到前置位 .部分代码如下 :



checkLeft=[13,25,19,61,35,49,47,59,55,11,39,51,27,33,29,57,5,17,9,23];

num_bar='';

AB_check='';

%以下求得左边序列以及AB序列

for i=1:6

    for j=0:19

        if left(i)==checkLeft(j+1)

            if j>9

                AB_check=strcat(AB_check,'B');

            else

                AB_check=strcat(AB_check,'A');

            end

            num_bar=strcat(num_bar,num2str(mod(j,10)));

        end

    end

end

%以下根据Map得到对应的前置位

preMap = containers.Map({'AAAAAA','AABABB','AABBAB','AABBBA','ABAABB','ABBAAB','ABBBAA','ABABAB','ABABBA','ABBABA'},...

    {'0','1','2','3','4','5','6','7','8','9'});

pre=preMap(AB_check);

num_bar=strcat(pre,num_bar);





接下来就是检查校验位是否正确:将前面的12个数字的奇数位相加,得到一个数oddSum


oddSum=0;evenSum=0;

for i=1:12

    if mod(i,2)==1

        oddSum=oddSum+str2num(num_bar(i));

    else

        evenSum=evenSum+str2num(num_bar(i));

    end

end

 c=oddSum+3*evenSum;

if mod(c,10)==0

    checkBit=0;

else

    checkBit=10-mod(c,10);

end

%如果checkBit和13位的最后一位相等,则识别正确,否则错误。弹出相应信息

if num2str(checkBit)==num_bar(13)

    msgbox('Okay')

else

    msgbox('Failed');

end




偶数位相加得到 evenSum,令 c=oddSum+3*evenSum,若 c的个位数为 0,则校验位为 0;否则校验位为 10-c%10.这里判断两个数是否相等时稍微注意一下是否是同一类型的。相关代码如下 :

对应上文中的那张一维码图,检验结果如下:


3.所做的额外工作;

 3.1 对于倾斜一维码图像的校正:

对于像上图这样的一维码图像,我们在遍历一行试图求条/空的宽度时,是无论如何也得不到正确结果的,因为图像倾斜后宽度都变长了。所以较好的做法是将这个图像摆正,摆正的关键是找到偏离角度。这里选用的hough直线检测方法。hough变换的主要思想是将该方程的参数和变量交换,对于直线y=kx+b,即用xy作为参数,kb作为变量,所以在直角坐标系中的直线y=kx+b在参数坐标上表示为点(kb),而直角坐标上的点(x1y1)则在参数坐标下表示为一条直线。此外,为了计算方便,将参数控件的坐标转换成极坐标进行运算。

    所以,先将图片进行边缘检测,然后对图像上每一个非零像素点在参数坐标下变换为一条直线,然后根据统计方法找到聚集点即可。边缘检测可以使用edge()方法,这里使用的是canny边缘检测:

以上算法,matlab都帮我们封装好了.这里还有一个小技巧:因为我们需要验证的是一维码图像,一维码图像的特点是所有条/空都是两两平行的,所以我们根本没有必要找出所有的直线,而仅仅需要找出最长的那一条直线(其实无论哪一条都无所谓,对结果没什么影响)即可:

       [H,T,R]=hough(BW);

       P=houghpeaks(H,4,'threshold',ceil(0.3*max(H(:))));

    这一句选取了4个峰值,即聚集点,所以对应到参数坐标上是四条直线;H对应的是theta和ρ的关系矩阵,两个参数分别代表极坐标中的夹角和到原点的距离。 

       lines=houghlines(BW,T,R,P,'FillGap',50,'MinLength',10);

    这里就是利用hough()函数返回的参数值选取线段;参数50是一个正的标量,指定了与相同的hough变换相关的两条线段的距离,小于该距离则将线段合并;参数10是一个正的标量,指定合并的线是丢弃还是保留。lines里的成员是一个结构体,包含了线段端点的坐标等信息。 

       [L1,Index1]=max(Len(:)); 

x1=[lines(Index1).point1(1) lines(Index1).point2(1)];

y1=[lines(Index1).point1(2) lines(Index1).point2(2)];

K1=-(lines(Index1).point1(2)-…

lines(Index1).point2(2))/(lines(Index1).point1(1)-lines(Index1).point2(1))

       angle=atan(K1)*180/pi

       A = imrotate(I,90-angle,'bilinear');

    先找到最长线段L1以及索引Index1,根据端点求出斜率K1,然后用反正切函数atan()找到偏离角angleimrotate()默认逆时针旋转,所以最后的结果是将原二值图像逆时针转90-angle。图一是线段标识图,图二是校正后的图:

图一

图二

     这里还有一个很蛋疼的地方,可以发现经过旋转后的图比原图更大,而且四周出现了四个角,关键这四个角还是黑色的,这就会引起一个很严重的问题:在遍历图像某一行时,条/空的数量会比原来多(单单考虑图二的话,确切的说是所有有效行多了两条),画条线看的更清楚。所以在程序中这部分加了一个特判:对于旋转过的图像,计算宽度值的方法要和未旋转的图像区分开来,相关代码如下:


%以下是针对校正的图像的
if angle~=90
        if width_id==62
            number=number+1;
            for k=1:59
                  total_len(number,k)=width(i,k+1);
            end
        end
    %以下是针对未校正的图像的
    else
        if width_id==60
            number=number+1;
            for k=1:59
                  total_len(number,k)=width(i,k);
            end
        end
    end




    当然,上述代码只是针对特定的图而言的,更一般的做法是:如果某一行的条/空总数大于59,如61,则要去掉第一个值和第61个值,保留中间的59个值作为有效值;如果总数等于59则满足要求;小于59则直接忽略改行。代码很容易,在这里不再赘述。

3.3 处理含有椒盐噪声的图像:对于一张含有椒盐噪声的图像,我们做识别处理肯定是会增大误差的。下面是一张例图:


 我们滤波的对象是二值图,所以先需要用前文中提及的方法来将这张RGB图转化成二值图,再做滤波处理。相关代码如下:


A=imread('5.jpg');

figure(1),imshow(A);

A=rgb2gray(A);

A=im2bw(A,graythresh(A));

A=double(A);

K = medfilt2(A,[2,2]);

figure(2),imshow(K);


这是直接使用中值滤波函数medfilt2()的例子,滤波后的图像如下:

这里值得注意的一点是medfilt2()函数的第二个参数,是一个[N,M]大小的滑动窗口,对于某一个像素点(xy),仅处理它邻域的响应。窗口越大,就有越多的像素点对中心像素点有影响。一般而言当图像比较小时,选取的滑动窗口也应该相应的小。对于上面那段代码,如果将滑动窗口改成3×3的话,就会牺牲更多的清晰度,效果很差。图像如下所示:

 


© 著作权归作者所有

dnbc66
粉丝 0
博文 1
码字总数 2936
作品 0
私信 提问
常用的条形码类型以及如何选择条码类型、条形码字体和条形码控件

条形码是什么? 条形码是由一组按一定编码规则排列的条,空符号,用以表示一定的字符、数字及符号组成的信息。 条形码分为一维码和二维码两种。一维码比较常用,如日常商品外包装上的条形码就...

flyingsnail
2014/05/04
10.1K
0
一维码Code 128简介及其解码实现(zxing-cpp)

一维码Code 128:1981年推出,是一种长度可变、连续性的字母数字条码。与其他一维条码比较起来,相对较为复杂,支持的字元也相对较多,又有不同的编码方式可供交互运用,因此其应用弹性也较大...

fengbingchun
2017/01/18
0
0
一个基于web的标签设计,打印工具,超diao

Bartender 标签设计工具,也许你用过,很多终端用户也在用,但如果你是做客户的项目,客户要求在系统运行时,自己能够增加标签,可视化设计标签,并从自己的数据库里,提取数据进行打印,用 ...

joe123
2014/02/26
5.6K
4
Flex:条形码的生成与读取

barcodelib 条形码类库介绍 Flex下的条形码类库,支持一维码EAN8,EAN13,UPCA, CODE39, CODE128, ITF,二维码QR_CODE的生成和读取。 本类库基于ZXing条形码库修改而来,主要的改动: * 取消支...

彭博
2012/03/09
910
0
专业的条形码读写控件VintaSoftBarcode.NET SDK

http://www.fanganwang.com/product/1473 VintaSoftBarcode.NET SDK是专业的.NET条形码阅读器和条码生成器控件,可以嵌入到你开发的软件中。它可以读写数字图形和PDF中的的一维和二维条码。 ...

baobiaokongjian
2014/06/09
90
0

没有更多内容

加载失败,请刷新页面

加载更多

java通过ServerSocket与Socket实现通信

首先说一下ServerSocket与Socket. 1.ServerSocket ServerSocket是用来监听客户端Socket连接的类,如果没有连接会一直处于等待状态. ServetSocket有三个构造方法: (1) ServerSocket(int port);...

Blueeeeeee
今天
6
0
用 Sphinx 搭建博客时,如何自定义插件?

之前有不少同学看过我的个人博客(http://python-online.cn),也根据我写的教程完成了自己个人站点的搭建。 点此:使用 Python 30分钟 教你快速搭建一个博客 为防有的同学不清楚 Sphinx ,这...

王炳明
昨天
5
0
黑客之道-40本书籍助你快速入门黑客技术免费下载

场景 黑客是一个中文词语,皆源自英文hacker,随着灰鸽子的出现,灰鸽子成为了很多假借黑客名义控制他人电脑的黑客技术,于是出现了“骇客”与"黑客"分家。2012年电影频道节目中心出品的电影...

badaoliumang
昨天
15
0
很遗憾,没有一篇文章能讲清楚线程的生命周期!

(手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本。 简介 大家都知道线程是有生命周期,但是彤哥可以认真负责地告诉你网上几乎没有一篇文章讲得是完全正确的。 ...

彤哥读源码
昨天
17
0
jquery--DOM操作基础

本文转载于:专业的前端网站➭jquery--DOM操作基础 元素的访问 元素属性操作 获取:attr(name);$("#my").attr("src"); 设置:attr(name,value);$("#myImg").attr("src","images/1.jpg"); ......

前端老手
昨天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部