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的話,就會犧牲更多的清晰度,效果很差。圖像如下所示:
好了,就說這么多了~~