pdf417条码解码(上)——图像的预处理

本文介绍PDF417条码的识别流程,包括OSTU二值化、形态学变换去除多余连通域、Hough变换提取边界、双线性变换矫正条码等关键技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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:1
    if 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 : L
    w0(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 : L
    variance(t) = w0(t)*(u-u0(t))^2/(1-w0(t));
end
% 找出最大方差时的t
t = 1;
for i = 1 : L
    if variance(t) < variance(i)
        t = i;
    end
end
%二值化
PXD = t-2+st; %阈值像素
for i = 1:Row
    for j = 1:Col
        if gray(i,j) > PXD
            gray(i,j) = 0;
        else
            gray(i,j) = 255;
        end
    end
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:H
    for n=1:W
        if 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;
        end
    end
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:H
    for n=1:W
        if Image2(m+round(BH/2)-1,n+round(BW/2)-1)~=0
            tmp=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;  %中心为255
            end
        end
    end
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*col
    h1 = 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:num
    if 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:row
    for j = 1:col
        if im(i,j) ~= 0
            k = k+1;
            lines(k,1) = i; 
            lines(k,2) = j;
        end
    end
end
% 坐标系为向下 向右
% c = y + m*x   m = (y2-y1)/(x1-x2)
for i = 1:k
    for j = i+1:k
        if 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~90
            if n > -90 && n < -45
                n = n + 180;    % 不在所需范围内需要转换到90~134
            end
            if n >= -45 && n <= 44
                c = lines(i,1) + lines(i,2)*m;
                % 保证下标为正
                sc = int16(c) + col + 1;
                sn = int16(n) + 45 + 1;
                Hr(sc, sn) = Hr(sc, sn) + 1;
            else
                if n >= 45 && n <= 134
                    c = lines(i,2) + lines(i,1)/m;
                    sc = int16(c) + row + 1;
                    sn = int16(n) - 45 + 1;
                    Hc(sc, sn) = Hc(sc, sn) + 1;
                end
            end
        end
    end
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 <= 134
    m3 = 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 <= 134
    m4 = 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);
        else
            dst = [0 0;point(2,3) 0;point(2,3) point(4,3);0 point(4,3)];
            newcol = point(4,3);
        end
    else
        if (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);
        else
            dst = [point(2,3) 0;0 0;0 point(4,3);point(2,3) point(4,3)];
            newcol = point(4,3);
        end
    end
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);
        else
            dst = [0 0;point(2,3) 0;point(2,3) point(4,3);0 point(4,3)];
            newcol = point(4,3);
        end
    else
        if (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);
        else
            dst = [point(2,3) 0;0 0;0 point(4,3);point(2,3) point(4,3)];
            newcol = point(4,3);
        end
    end
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:newrow
    for j = 1:newcol
        t = round(T*[i; j; i*j; 1]);
        if t(1) > 0 && t(1) <= row && t(2) > 0 && t(2) <= col
            newimg(i,j)=img(t(1),t(2));
        end
    end
end
%figure;imshow(newimg);title('双线性变换');
%判断左右是否颠倒
%左边第一个黑色块的长度
trow = round(newrow/2);
startlen = 0;
for j = 1:newcol
    if newimg(trow,j) == 0
        while newimg(trow,j) == 0
            j=j+1;
            startlen = startlen + 1;
        end
        break;
    end
end
%右边第一个黑色块的长度
endlen = 0;
for j = newcol:-1:1
    if newimg(trow,j) == 0
        while newimg(trow,j) == 0
            j=j-1;
            endlen = endlen + 1;
        end
        break;
    end
end
%判断是否左右颠倒,比较左边第一个黑色块的长度和右边第一个黑色块的长度,若右边的更长,说明左右颠倒
%旋转180if startlen < endlen
    newimg(:,1:newcol) = newimg(:,newcol:-1:1);
end

邻近插值

功能: 去除变换过后二维码中的大量毛刺及白点

思路: 如果该点的水平或垂直方向上的邻近点与该点的灰度值不同,则把该点的灰度值重新赋值为邻近点的值。

为去除白点做的修改:对水平或垂直方向上的较小的白色区间赋为黑色,水平阈值为2,竖直阈值为3时效果最佳。

function img=interpolate(img)
[row,col] = size(img);
%横向插值
for i=1:row
    for j=1:col
        if img(i,j) == 0
            tmp = j+1;
            %白色区间
            while tmp <= col && img(i,tmp) ~= 0
                tmp=tmp+1;
            end
            if tmp <= col && tmp-j <= 2  
                img(i,j+1:tmp-1)=0;
            end
            j = tmp - 1;
        end
    end
end
%纵向插值
for j=1:col
    for i=1:row
        if img(i,j) == 0
            tmp = i+1;
            while tmp <= row && img(tmp,j) ~= 0
                tmp=tmp+1;
            end
            if tmp <= row && tmp-i <= 3 
                img(i+1:tmp-1,j)=0;
            end
            i = tmp - 1;
        end
    end
end
end

主模块

close all;
clc;
%读取图像
img = imread('lv0 - relax.bmp');
figure;
imshow(img);title('原图');
%灰度图
if length(size(img)) == 3
    img = 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('邻近插值');

效果演示

  • 原图
    原图
  • OSTU
    • 二值化:前景像素为白色255,背景像素为黑色0
      这里写图片描述
  • 形态学变换去除多余连通域
    • 重构出的与边界相连的连通域
      这里写图片描述
    • 去除与边界相连的连通域
      这里写图片描述
    • 开运算,去除极小的连通域
      这里写图片描述
    • 闭运算,将二维码揉成一个连通域
      这里写图片描述
    • 去除小于最大连通域一半的连通域
      这里写图片描述
  • 形态学腐蚀膨胀获取边界
    这里写图片描述
  • Hough变换
    • 累加数组Hr的surf图
      这里写图片描述
    • 累加数组Hc的surf图
      这里写图片描述
    • 求出顶点并显示在原图
      这里写图片描述
  • 双线性变换
    这里写图片描述
  • 邻近插值
    这里写图片描述
1、本软件为演示软件,在性能和功能上有所限制; 2、参数说明: 条码列数 取值范围1-30,根据具体要生成条码的数据量决定; 例如20 byte左右数据量,取值范围可设3-5; 纠错等级 条码污损后的纠错能力,取值范围0-7,建议取1-3; 长宽比例 条码单位长度和宽度比例,取值范围1-5; 1----紧凑型条码,生成条码图片容量最小,适合配合数据库管理系统使用, 便于直接以最小的容量存储在数据库中,而且生成速度最快,适合大 批量生成条码。但需要通过报表控件做纵横向适当拉伸后,打印出来 才可识读,不能直接打印识读或软解码; 2--5 标准型条码,适合直接打印识读或软件解码,建议取3。 以上参数的设置将直接影响条码的可读性和条码图片的大小。 3、在文本框中输入要生成条码的信息,输入欲保存图片的名称,按“生成”即在当前目录下产生指定的bmp图片; 默认路径为执行文件同目录,默认文件名为bar.bmp; 4、若要打印,可用windows提供的“画图”工具打开bar.bmp,直接按打印就可以在打印机上输出; 建议采用激光打印机; 5、本控件能将文字信息、照片信息等生成二维条码; 6、本控件可以采用DLL方式或OCX方式提供。 7、本软件只演示将文本信息(注意要<1kByte)生成pdf417二维条码解码; 8、解码控件可以支持黑白二色、256色、24位真彩二维条码图片。 若需要其他功能演示,如图片压缩生成二维条码,二维条码与数据库连接,扫描枪与串口连接读取二维条码, 软件进行二维条码图片解码等等;
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值