一维码:EAN-13码的识别

Posted dnbc66

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一维码:EAN-13码的识别相关的知识,希望对你有一定的参考价值。

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个宽度)的宽度相加后取平均值。相关代码如下:

 1 [m,n]=size(A);
 2  
 3 number=0;
 4  
 5 for i=1:m
 6  
 7     pos_cnt=1;width_id=1;
 8  
 9     for j=1:n-1
10  
11         if A(i,j) ~= A(i,j+1)
12  
13             pos(i,pos_cnt)=j;
14  
15             if pos_cnt>1
16  
17                 width(i,width_id)=pos(i,pos_cnt)-pos(i,pos_cnt-1);
18  
19                 width_id=width_id+1;
20  
21             end
22  
23             pos_cnt=pos_cnt+1;
24  
25         end
26  
27     end 
28  
29     if width_id==60
30  
31             number=number+1;
32  
33             for k=1:59
34  
35         %将所有条/空的宽度都存储在total_len这个二维数组里
36  
37                   total_len(number,k)=width(i,k);
38  
39             end
40  
41         end
42  
43     end
44  
45 end
46 [mm,nn]=size(total_len);
47 for i=1:nn
48     tmp=0;
49     for j=1:mm
50         tmp=tmp+total_len(j,i); %该宽度的所有值求和
51     end
52     final_width(1,i)=tmp/mm;    %求均值
53 end

 

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

 

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

 1 index=1
 2  
 3 for i=1:59
 4  
 5     if mod(i,2)==1
 6  
 7         for j=1:1:round(proposition(1,i))
 8  
 9             mat95(1,index)=1;
10  
11             index=index+1;
12  
13         end
14  
15     else
16  
17         for j=1:1:round(proposition(1,i))
18  
19             mat95(1,index)=0;
20  
21             index=index+1;
22  
23         end
24  
25     end
26  
27 end
28  
29 isCheck=0;
30  
31 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)
32  
33   isCheck=1;
34  
35 end
36  
37  if isCheck==0
38  
39     msgbox(不满足EAN-13码的条件!);  %不满足则弹出msg框,同时终止程序
40  
41     return
42  
43 end

 

 2.2.4 查表译码:

 1 j=1;
 2  
 3 for i=4:7:39
 4  
 5     left(1,j)=bin2dec(num2str(mat95(1:1,i:i+6)));
 6  
 7     j=j+1;
 8  
 9 end
10  
11 k=1;
12  
13 for i=51:7:86
14  
15     right(1,k)=bin2dec(num2str(mat95(1:1,i:i+6)));
16  
17     k=k+1;
18  
19 end

 

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

 1 checkLeft=[13,25,19,61,35,49,47,59,55,11,39,51,27,33,29,57,5,17,9,23];
 2  
 3 num_bar=‘‘;
 4  
 5 AB_check=‘‘;
 6  
 7 %以下求得左边序列以及AB序列
 8  
 9 for i=1:6
10  
11     for j=0:19
12  
13         if left(i)==checkLeft(j+1)
14  
15             if j>9
16  
17                 AB_check=strcat(AB_check,B);
18  
19             else
20  
21                 AB_check=strcat(AB_check,A);
22  
23             end
24  
25             num_bar=strcat(num_bar,num2str(mod(j,10)));
26  
27         end
28  
29     end
30  
31 end
32  
33 %以下根据Map得到对应的前置位
34  
35 preMap = containers.Map({AAAAAA,AABABB,AABBAB,AABBBA,ABAABB,ABBAAB,ABBBAA,ABABAB,ABABBA,ABBABA},...
36  
37     {0,1,2,3,4,5,6,7,8,9});
38  
39 pre=preMap(AB_check);
40  
41 num_bar=strcat(pre,num_bar);

 

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

 1 oddSum=0;evenSum=0;
 2  
 3 for i=1:12
 4  
 5     if mod(i,2)==1
 6  
 7         oddSum=oddSum+str2num(num_bar(i));
 8  
 9     else
10  
11         evenSum=evenSum+str2num(num_bar(i));
12  
13     end
14  
15 end
16  
17  c=oddSum+3*evenSum;
18  
19 if mod(c,10)==0
20  
21     checkBit=0;
22  
23 else
24  
25     checkBit=10-mod(c,10);
26  
27 end
28  
29 %如果checkBit和13位的最后一位相等,则识别正确,否则错误。弹出相应信息
30  
31 if num2str(checkBit)==num_bar(13)
32  
33     msgbox(Okay)
34  
35 else
36  
37     msgbox(Failed);
38  
39 end

 

偶数位相加得到 evenSum,令 c=oddSum+3*evenSum,若 c的个位数为 0,则校验位为 0;否则校验位为 10-c%10.这里判断两个数是否相等时稍微注意一下是否是同一类型的。对应上文中的那张一维码图,检验结果如下:

技术分享

 

3.所做的额外工作;

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

技术分享

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

    所以,先将图片进行边缘检测,然后对图像上每一个非零像素点在参数坐标下变换为一条直线,然后根据统计方法找到聚集点即可。边缘检测可以使用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()找到偏离角angle。imrotate()默认逆时针旋转,所以最后的结果是将原二值图像逆时针转90-angle。图一是线段标识图,图二是校正后的图:

技术分享

图一

技术分享

图二

 

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

 1 %以下是针对校正的图像的
 2 if angle~=90
 3         if width_id==62
 4             number=number+1;
 5             for k=1:59
 6                   total_len(number,k)=width(i,k+1);
 7             end
 8         end
 9     %以下是针对未校正的图像的
10     else
11         if width_id==60
12             number=number+1;
13             for k=1:59
14                   total_len(number,k)=width(i,k);
15             end
16         end
17     end

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

 

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

技术分享

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

 1 A=imread(5.jpg);
 2  
 3 figure(1),imshow(A);
 4  
 5 A=rgb2gray(A);
 6  
 7 A=im2bw(A,graythresh(A));
 8  
 9 A=double(A);
10  
11 K = medfilt2(A,[2,2]);
12  
13 figure(2),imshow(K);

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

技术分享

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

技术分享

 

好了,就说这么多了~~

 

以上是关于一维码:EAN-13码的识别的主要内容,如果未能解决你的问题,请参考以下文章

在JavaScript中创建SVG矢量图EAN13条码

在JavaScript中创建SVG矢量图EAN13条码

在JavaScript中创建SVG矢量图EAN13条码

在JavaScript中创建SVG矢量图EAN13条码

Halcon图像的一维码二维码识别

ean13码的生成,python读取csv中数据并处理返回并写入到另一个csv文件中