pdf417条码的符号结构
预处理步骤
OSTU二值化
假定该图像根据双模直方图包含两类像素:前景像素和背景像素。计算能将两类分开的最佳阈值,要使得它们的类内方差最小;由于两两平方距离恒定,即它们的类间方差最大。
类间方差:前景像素占比w0,期望为u0,背景像素占比w1,期望为u1,整个图像期望为u=w0*u0+w1*u1,分割像素点为t,类间方差为g(t) = w0*(u0-u)^2+w1*(u1-u)^2,当类间方差最大时,t即为最佳阈值。
由于w1=1-w0,可得到g(t)=w0 (t)*(u – u0 (t))^2/(1 – w0 (t))
function imageBW = Ostu(gray)
%统计各灰度级像素在整幅图像中的个数
Hist = imhist(gray);
%计算每个灰度级在图像中所占的比例
[Row,Col]=size(gray);
dHist=Hist /(Row*Col);
%去除两边不存在的灰度级
L=256;
st = 0; nd = 0;
for i=1:L if dHist(i) ~= 0 st = i;break;end
end
for i=L:-1:1if dHist(i) ~= 0 nd = i;break;end
end
dHist = dHist(st:nd);
L = size(dHist,1);
% 计算前t个像素的累加概率w0 (t)和像素期望值u0 (t)
w0 = zeros(L,1); u0 = zeros(L,1);
for t = 1 : Lw0(t) = sum(dHist(1:t));u0(t) = sum((st-1:st+t-2)*dHist(1:t)) / w0(t);
end
u = u0(L); %总期望
% 方差
variance = zeros(L,1);
for t = 1 : Lvariance(t) = w0(t)*(u-u0(t))^2/(1-w0(t));
end
% 找出最大方差时的t
t = 1;
for i = 1 : Lif variance(t) < variance(i)t = i;end
end
%二值化
PXD = t-2+st; %阈值像素
for i = 1:Rowfor j = 1:Colif gray(i,j) > PXDgray(i,j) = 0;elsegray(i,j) = 255;endend
end
%显示二值化图像
imageBW = gray;
%figure;
%imshow(imageBW); title('Ostu二值化');
end
膨胀腐蚀
- 膨胀:结构元素B在图像A上的前景像素上平移后的集合
function transImage=mdilate(Image,B)
[H,W]=size(Image);
[BH,BW]=size(B);
%边界扩展
H2 = H+BH-1; W2 = W+BW-1;
Image2=zeros(H2,W2);
Image2(round(BH/2):round(BH/2)+H-1,round(BW/2):round(BW/2)+W-1)=Image;
transImage = zeros(H2,W2);
for m=1:Hfor n=1:Wif Image2(m+round(BH/2)-1,n+round(BW/2)-1)~=0 %取原点为中心transImage(m:m+BH-1,n:n+BW-1) = (Image2(m:m+BH-1,n:n+BW-1) | B(1:BH,1:BW))*255;endend
end
%去除扩展
transImage = transImage(round(BH/2):round(BH/2)+H-1,round(BW/2):round(BW/2)+W-1);
end
- 腐蚀:结构元素B与图像A上前景像素完全匹配的原点位置的集合
function transImage=merode(Image,B)
[H,W]=size(Image);
[BH,BW]=size(B);
H2 = H+BH-1; W2 = W+BW-1;
Image2=zeros(H2,W2);
Image2(round(BH/2):round(BH/2)+H-1,round(BW/2):round(BW/2)+W-1)=Image;
transImage = zeros(H2,W2);
for m=1:Hfor n=1:Wif Image2(m+round(BH/2)-1,n+round(BW/2)-1)~=0tmp=Image2(m:m+BH-1,n:n+BW-1); %窗口内所有点if sum(sum(tmp&B)) == sum(sum(B&1)) %完全匹配transImage(m+round(BH/2)-1,n+round(BW/2)-1) = 255; %中心为255endendend
end
transImage = transImage(round(BH/2):round(BH/2)+H-1,round(BW/2):round(BW/2)+W-1);
end
形态学变换去除多余连通域
-
利用重构取出与边界相连的连通域
形态学重构:是一种涉及到两幅图像和一个结构元素的形态学变换。一副图像,即标记(marker),是变换的开始点,用来约束变换经过的过程。结构元素用于定义连接性。
去除边界连通域:标记(marker)为原图像的边界值,是变换Hk的开始点,每次变换都膨胀后与掩膜(原图像)取交集,直到H不在改变,此时H即为与边界相连的连通域
-
进行开运算和闭运算,去除细小的连通域,再把二维码揉成团
标记各个连通域,面积小于阈值(最大连通域面积的一半)的去除。
function I = connectdomin(im)
[row,col] = size(im);
%figure;imshow(im);
%去除边界连通域
marker = im;
marker(2:row-1,2:col-1) = 0;
g = im;
h1 = zeros(row,col);
h2 = marker;
se = ones(3,3);
%figure;
while sum(sum(h2 == h1)) ~= row*colh1 = h2;h2 = (mdilate(h1, se)&g)*255; %imshow(h2),title('边界连通域');
end
%class(im)
%class(h2)
im = im - uint8(h2);
figure;imshow(im),title('去除边界连通域');%开运算
se = ones(3,3);
I = merode(im, se);
%figure;imshow(I);
I = mdilate(I, se);
%figure;imshow(I);title('开运算');
%闭运算
se=ones(20,20);
I = mdilate(I, se);
%figure;imshow(I);
I = merode(I, se);
%figure;imshow(I);title('闭运算');
%去除多余连通域
[L,num] = bwlabel(I);
count = zeros(1,num);
%统计各个连通域面积
for i = 1:num[r,c] = find(L==i);count(i) = size(r,1);
end
%阈值
T = round(max(count)/2);
%去除
for i = 1:numif count(i) < T[r,c] = find(L==i);I(r,c) = 0;end
end
figure;imshow(I),title('去除多余连通域');
end
Hough变换(直角坐标系)
功能: 查找二维码边缘直线
思路:
-
按照笛卡尔坐标下的参数化,一副图像中通过坐标(x,y)的共线像素点,是通过斜率为m截距为c的像素连接的,即满足:
c=-m*x+y
-
根据这条公式,建立一个累加器矩阵H[c][m]。然后以一定的精度统计经过前景像素点的每一条直线的参数c,m然后H[c][m]+1,直到到遍历所有的前景像素。
-
注意若以图像左上角顶点为原点,对图像坐标(row,col)而言row为竖直的y,col为水平的x,且正方向为向右和向下,故可建立向右向下的坐标系,则直线方程为c = y + m*x ,其中m = (y2-y1)/(x1-x2),便于理解。
-
为方便记录c,分为两个累加数组,Hr记录-45到45度,Hc记录45-135度,可以算出c的范围分别为-col到row+col、-row到row+col。
-
注意矩阵下标为正
代码:
function [x,y]=Hough(im)
[row,col] = size(im);
%找到经过每两个前景像素的直线,记录斜率m对应的角度n和截距c
%累加矩阵 -45—45和45-90、90-135(-45—0)
Hr = zeros(2*col+row+1, 90);
Hc = zeros(2*row+col+1, 90);
%记录前景像素位置
lines = zeros(row*col,2);
k = 0;
for i = 1:rowfor j = 1:colif im(i,j) ~= 0k = k+1;lines(k,1) = i; lines(k,2) = j;endend
end
% 坐标系为向下 向右
% c = y + m*x m = (y2-y1)/(x1-x2)
for i = 1:kfor j = i+1:kif lines(i,2) ~= lines(j,2)m = (lines(i,1)-lines(j,1)) / (lines(j,2)-lines(i,2)); n = round(atan(m)*180/pi); % 范围为-90~90if n > -90 && n < -45n = n + 180; % 不在所需范围内需要转换到90~134endif n >= -45 && n <= 44c = lines(i,1) + lines(i,2)*m;% 保证下标为正sc = int16(c) + col + 1;sn = int16(n) + 45 + 1;Hr(sc, sn) = Hr(sc, sn) + 1;elseif n >= 45 && n <= 134c = lines(i,2) + lines(i,1)/m;sc = int16(c) + row + 1;sn = int16(n) - 45 + 1;Hc(sc, sn) = Hc(sc, sn) + 1;endendendend
end
figure;
surf(Hr,'EdgeColor','None');title('Hr的surf');
figure;
surf(Hc,'EdgeColor','None');title('Hc的surf');
%寻找四个峰值即为四条边所在直线
Hr2 = zeros(2*col+row+1+16, 90+16);
Hr2(9:2*col+row+1+8,9:90+8) = Hr;
[c1, n1] = find(Hr2==max(max(Hr2)));
Hr2(c1-8:c1+8, n1-8:n1+8) = 0;
[c2, n2] = find(Hr2==max(max(Hr2)));
Hc2 = zeros(2*row+col+1+16, 90+16);
Hc2(9:2*row+col+1+8,9:90+8) = Hc;
[c3, n3] = find(Hc2==max(max(Hc2)));
Hc2(c3-8:c3+8, n3-8:n3+8) = 0;
[c4, n4] = find(Hc2==max(max(Hc2)));
%还原斜率k和截距b
m1 = (n1-8-45-1)*pi/180;
k1 = tan(m1);
b1 = c1-8-col-1;
m2 = (n2-8-45-1)*pi/180;
k2 = tan(m2);
b2 = c2-8-col-1;
m3 = (n3-8+45-1)*pi/180;
if m3 > 90 && m3 <= 134m3 = m3 - 180; %90~135
end
k3 = tan(m3);
b3 = k3*(c3-8-row-1);
m4 = (n4-8+45-1)*pi/180;
if m4 > 90 && m4 <= 134m4 = m4 - 180; %90~135
end
k4 = tan(m4);
b4 = k4*(c4-8-row-1);
%求四个交点
x = [(b3-b1)/(k3-k1) (b3-b2)/(k3-k2) (b4-b1)/(k4-k1) (b4-b2)/(k4-k2)];
y = [b1-k1*x(1) b2-k2*x(2) b1-k1*x(3) b2-k2*x(4)];
双线性变换
功能: 矫正二维码
双线性变换可以用以下矩阵表示:
提供一个思路:使用相对坐标,取相对于P1的距离L,进行排序,那么L12必然就是短边,由于图像拍摄角度,对角线可能会比长边短,故L13不一定是长边。之后只需判断P1、P2哪个对应左上角坐标(0,0),P1、P2确定后,P3、P4可以通过P12和P34的方向是否一致,判断那个对应右上角坐标,同时确定L13和L14哪个为长边。
为方便计算,将原图像看作是输出图像变换而成的,使用四个顶点及对应点求出变换矩阵
function newimg = bilinear_transform(img, x, y)
%figure; imshow(img);title('原图');
[row,col] = size(img);
%对原图的四个顶点P1、P2、P3、P4按到P1的距离排序
%原图点
point = zeros(4,3);
point(:,2) = x; %横
point(:,1) = y; %纵
point(:,3) = sqrt((point(1,1)-point(:,1)).^2 + (point(1,2)-point(:,2)).^2); %各点到P1的距离
%按到点1距离排序
point = round(sortrows(point,3));
%对应目标图点
%当P1、P2在P3、P4上方时,若p1在p2右边,则P1对应左上角顶点,否则P2对应左上角顶点
%当P12与P34方向一致,说明P3对应右上角顶点,L13为长边,否则P4对应右上角顶点,L14对应长边
%而当P1、P2在P3、P4下方时,若p1在p2右边,则P2对应左上角顶点,否则P1对应左上角顶点。
if max(point(1,1),point(2,1)) < max(point(3,1),point(4,1)) %p12在上方if point(1,2)-point(2,2) > 0 %p1在p2右边if (point(1,1)-point(2,1))/(point(3,1)-point(4,1)) > 0 %p12与p34方向一致dst = [0 0;point(2,3) 0;0 point(3,3);point(2,3) point(3,3)];newcol = point(3,3);elsedst = [0 0;point(2,3) 0;point(2,3) point(4,3);0 point(4,3)];newcol = point(4,3);endelseif (point(1,1)-point(2,1))/(point(3,1)-point(4,1)) > 0 %p12与p34方向一致dst = [point(2,3) 0;0 0;point(2,3) point(3,3);0 point(3,3)];newcol = point(3,3);elsedst = [point(2,3) 0;0 0;0 point(4,3);point(2,3) point(4,3)];newcol = point(4,3);endend
else %p1在下方if point(1,2)-point(2,2) < 0 %p1在p2左边if (point(1,1)-point(2,1))/(point(3,1)-point(4,1)) > 0 %p12与p34方向一致dst = [0 0;point(2,3) 0;0 point(3,3);point(2,3) point(3,3)];newcol = point(3,3);elsedst = [0 0;point(2,3) 0;point(2,3) point(4,3);0 point(4,3)];newcol = point(4,3);endelseif (point(1,1)-point(2,1))/(point(3,1)-point(4,1)) > 0 %p12与p34方向一致dst = [point(2,3) 0;0 0;point(2,3) point(3,3);0 point(3,3)];newcol = point(3,3);elsedst = [point(2,3) 0;0 0;0 point(4,3);point(2,3) point(4,3)];newcol = point(4,3);endend
end
%求变换矩阵
a = [dst(1,1) dst(2,1) dst(3,1) dst(4,1); dst(1,2) dst(2,2) dst(3,2) dst(4,2); ...dst(1,1)*dst(1,2) dst(2,1)*dst(2,2) dst(3,1)*dst(3,2) dst(4,1)*dst(4,2); 1 1 1 1];
b = [point(1,1) point(2,1) point(3,1) point(4,1); point(1,2) point(2,2) point(3,2) point(4,2)];
T = b*inv(a);
%变换
newrow = point(2,3);
newimg = ones(newrow,newcol)*255;
for i = 1:newrowfor j = 1:newcolt = round(T*[i; j; i*j; 1]);if t(1) > 0 && t(1) <= row && t(2) > 0 && t(2) <= colnewimg(i,j)=img(t(1),t(2));endend
end
%figure;imshow(newimg);title('双线性变换');
%判断左右是否颠倒
%左边第一个黑色块的长度
trow = round(newrow/2);
startlen = 0;
for j = 1:newcolif newimg(trow,j) == 0while newimg(trow,j) == 0j=j+1;startlen = startlen + 1;endbreak;end
end
%右边第一个黑色块的长度
endlen = 0;
for j = newcol:-1:1if newimg(trow,j) == 0while newimg(trow,j) == 0j=j-1;endlen = endlen + 1;endbreak;end
end
%判断是否左右颠倒,比较左边第一个黑色块的长度和右边第一个黑色块的长度,若右边的更长,说明左右颠倒
%旋转180度
if startlen < endlennewimg(:,1:newcol) = newimg(:,newcol:-1:1);
end
邻近插值
功能: 去除变换过后二维码中的大量毛刺及白点
思路: 如果该点的水平或垂直方向上的邻近点与该点的灰度值不同,则把该点的灰度值重新赋值为邻近点的值。
为去除白点做的修改:对水平或垂直方向上的较小的白色区间赋为黑色,水平阈值为2,竖直阈值为3时效果最佳。
function img=interpolate(img)
[row,col] = size(img);
%横向插值
for i=1:rowfor j=1:colif img(i,j) == 0tmp = j+1;%白色区间while tmp <= col && img(i,tmp) ~= 0tmp=tmp+1;endif tmp <= col && tmp-j <= 2 img(i,j+1:tmp-1)=0;endj = tmp - 1;endend
end
%纵向插值
for j=1:colfor i=1:rowif img(i,j) == 0tmp = i+1;while tmp <= row && img(tmp,j) ~= 0tmp=tmp+1;endif tmp <= row && tmp-i <= 3 img(i+1:tmp-1,j)=0;endi = tmp - 1;endend
end
end
主模块
close all;
clc;
%读取图像
img = imread('lv0 - relax.bmp');
figure;
imshow(img);title('原图');
%灰度图
if length(size(img)) == 3img = rgb2gray(img);
end
%二值化 形态学变换
img=Ostu(img);
figure;imshow(img),title('二值化');
I = connectdomin(img); %去除多余连通域%膨胀
se=ones(3,3);
I1 = mdilate(I, se);
%截取二维码区域
mask = I1;
imb = (img&mask)*255;
imb = 255-imb;
figure;imshow(imb); title('截取');
%提取二维码边缘
im = I1-I;
figure;
imshow(im);title('边缘');
%通过边缘获取四个顶点
[x,y] = Hough(im);
%显示顶点
figure;
imshow(img);hold on;
plot(x(1),y(1),'Marker','o','Color','red'); hold on;
plot(x(2),y(2),'Marker','o','Color','green'); hold on;
plot(x(3),y(3),'Marker','o','Color','blue'); hold on;
plot(x(4),y(4),'Marker','o','Color','yellow'); hold on;
%双线性变换
newimg = bilinear_transform(imb, x, y);
figure;
imshow(newimg);title('双线性变换');
%邻近插值
newimg = interpolate(newimg);
figure;
imshow(newimg);title('邻近插值');