解码须知
- 符号字符的结构
- 符号字符的簇
三种译码模式
文本
- 大写字母型模式
- 小写字母型模式
- 混合型子模式
- 标点型子模式
子模式的设置是为了更有效的表示数据,每组子模式选择了文件中出现频率较高的一组字符集。在子模式中,每一个字符对应一个基为30的值(0~29),因此一个码字可以表示一个字符对。
码字=30*H+L任何模式到文本压缩模式的锁定都是到大写字母型子模式的锁定。
在一个子模式转移符后不接另一个子模式转移或锁定。
- 字节
- 数字
分组:将每15个码字从左到右分为一组(每15个码字可转换成44个数字位),其最后一组码字可小于15个。
对于每一组码字:先执行基900到基10的转换,再去掉前导位1.
转移和锁定
- 模式 锁定: 码字用于将当前模式切换为指定的目标模式,该模式在下一个切换前一直有效。
- 模式 转移: 码字用于将文本压缩模式暂时切换切换为字节压缩,这种切换仅对切换后的第一个码字有效,随后的码字又返回文本压缩的当前子模式。
步骤
码字提取
功能描述:
提取图像边缘,从水平边缘可以得到层数,从竖直边缘可以得到条空长度;通过查对应簇的码字表得到码字
设计思路:
- 选用边缘检测算子sobel,使用其水平方向掩码和竖直方向掩码对图像滤波可分别得到水平边缘和竖直边缘。
- 图像中层与层之间的边界都会在水平投影图上形成明显的峰值,峰值的中点就是层的中心
- 竖直边缘包含了每个条空的长度信息,即每一层每两条竖线间的距离,记录到code中
- 起始符的长是17个模块,最前面黑色块长是8个模块,矩阵code中第一列的像素点数除以8即为一个模块的长度aunit
- Code数组点除以aunit,四舍五入后就能等得到期望的符号码字
- Pdf417每一行的簇按0,3,6依次排列,查对应簇的码字表得到码字
function str=mydecode(filepath)
%读取图像
img = imread(filepath);
if(length(size(img)))==3
img = rgb2gray(img);
end
[row,col] = size(img);
img = double(img);
%figure; imshow(img);title('原图');
%边缘检测
topso = [-1 -2 -1;0 0 0;1 2 1];
lefso = [-1 0 1;-2 0 2;-1 0 1];
bx = abs(imfilter(img,topso));
by = abs(imfilter(img,lefso));
horizontal=(bx~=0)*255; %二值化 值不为0说明为边缘,设为255.
vertical=(by~=0)*255;
%figure; imshow(horizontal);title('水平边缘');
%figure; imshow(vertical);title('竖直边缘');
%寻找峰值
line(1) = 1;
k = 1;
for i=3:row-2
%由于边缘的宽度为2个像素,所以不能判断该行i与邻行i-1、i+1的大小
if sum(horizontal(i,:)) > sum(horizontal(i-2,:)) && sum(horizontal(i,:)) > sum(horizontal(i-3,:))...
&& sum(horizontal(i,:)) > sum(horizontal(i+2,:)) && sum(horizontal(i,:)) > sum(horizontal(i+3,:))
k = k+1;
line(k) = i;
%且需要去除邻近行的干扰,将其设为0
horizontal(i-1:i+1,:) = 0; %排除干扰
end
end
k = k+1;
line(k) = row;
%每层中心
Layers = floor( (line(1:k-1)+line(2:k))/2 );
%遍历竖直边缘图vertical每一层中心行,遍历到竖线时记录此像素和上一个记录点的距离,即一个模块的长度
%记录每个条空长度
codes = zeros(k-1, col);
len = 0;
for i=1:k-1
last = 1;
m = 0;
for j=2:col-1
if vertical(Layers(i),j)==255
m = m+1;
codes(i,m) = j-last;
last = j;
%由于边缘的宽度为2个像素,需要去除邻近列的干扰,将其设为0
vertical(Layers(i),j-1:j+1)=0; %排除干扰
end
end
m = m+1;
codes(i,m) = col-last;
if len < m
len = m; %各行最大的有效点长度
end
end
%只取有效长度1到len,去除零向量
codes = codes(:,1:len);
%计算矩阵code中第一列的平均值,除以8,四舍五入后即为一个模块的长度aunit
auint = sum(codes(:,1))/length(codes(:,1))/8;
%code数组点除以aunit,四舍五入后就能得到期望的符号码字
codes = round(codes/auint);
%矩阵code中,前17列和后17列不携带码字,可以除去
codes = codes(:,17:len-17);
%遍历矩阵code,每从(i,j)处取8个数,使用find由8位数字查对应簇(i-1)%3+1的码字表得到码字
[r,c] = size(codes);
l = ceil(c/8);
decodes = zeros(1,r*l);
load symcodes.mat -ASCII;
for i = 1:r
for j =1:8:c
temp = sum(codes(i,j:j+7).*(10.^(8-(1:8))));
decodes((i-1)*l+ceil(j/8)) = find(symcodes(mod((i-1),3)+1,:) == temp) - 1;
end
end
文本、数字压缩模式译码
功能描述:根据码字判断文本、数字压缩模式的锁定和转移,在对应模式下完成码字的译码。
设计思路:
- 数据区中的第一个码字是符号长度值。
- 根据码字判断文本、数字、压缩模式的锁定和转移,使用mode记录当前的解码模式 ,数字型:2,字节型:3, 文本大写模式:11,文本小写:12,文本混合:13,文本标点:14
- 使用premode用于转移模式时记录模式值,第一个表示当前是否为转移模式,第二个表示要返回的模式值
- 根据码字表,建立文本模式下各子模式下的对应字符表,tcbyte记录文本模式时的高低位数据,第一个值表示高位,第二个表示低位。
- 数字压缩模式将每15个码字从左到右分为一组,其最后一组码字可小于15个。对于每一组码字,先执行基900到基10的转换,再去掉前导位。用valueindex记录字节模式和数字模式的缓存序列,其中第一个值表示序列是否有效及何种模式 ,无效 0,数字模式1, 字节模式 2, 第二个值表示序列起始位置。
%字节模式未实现
tc_uc = [65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,32,201,202,204];%大写字母模式
tc_lc = [97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,32,205,202,204];%小写字母模式
tc_mi = [48,49,50,51,52,53,54,55,56,57,38,13,09,44,58,35,45,46,36,47,43,37,42,61,94,203,32,201,200,204];%混合型模式
tc_do = [59,60,62,64,91,92,93,95,96,126,33,13,09,44,58,10,45,46,36,47,34,124,42,40,41,63,123,125,39,200];%标点型模式
codelen = decodes(1); %待解码码字长度
mode = 11; %2->数字型、3->字节型、11->文本模式大写型、12->文本模式小写型、13->文本模式混合型、14->文本模式标点型
premode = [0,0]; %用于转移模式时记录模式值,第一个值表示当前是否为转移模式,第二个值表示要返回的模式值
tcbyte = zeros(1,2); %用于记录文本模式时的高低位数据,第一个值表示高位数据,第二个值表示低位数据
valueindex = [0,0]; %用于记录字节模式和数字模式的缓存序列,其中第一个值表示序列是否有效及何种模式0->无效、1->数字模式、2->字节模式,第二个值表示序列起始位置
str = ''; %用来存放译码结果
for i=2:codelen
if decodes(i)>=900 %三种模式的锁定
if decodes(i)==900
mode = 11;
elseif decodes(i)==902
mode = 2;
elseif decodes(i)==901 || decodes(i)==924 || decodes(i)==913
mode = 3;
end
if valueindex(1)==1 %数字模式
caches = decodes(valueindex(2):(i-1)); %数字模式下的待解码码字
len = size(caches,2);
for k=1:15:len
curvalues = zeros(1,15); %用来存储15个码字
if (k+14)<len
curvalues = caches(k:k+14);
else %不足15个码字
curvalues(k+15-len:15) = caches(k:len);
end
longnum=0;
for j=1:15
%使用sym以便存储更多位
longnum = longnum+curvalues(j)*sym(9^(15-j))*(100^(15-j));
end
tempstr = char(longnum); %转为字符串
str = strcat(str, tempstr(2:end)); %去除第一位1
%disp(['数字模式',str]);
end
end
valueindex = [0,0];
else
if mode>10 %文本模式解码
tcbyte(1) = floor(decodes(i)/30); %高位
tcbyte(2) = mod(decodes(i),30); %低位
for j=1:2
if mode==11 %大写字母模式
if premode(1)==1 %转移模式
mode = premode(2);
premode(1) = 0;
end
if tc_uc(tcbyte(j)+1)==201 %ll=201,锁定为小写字母模式
mode = 12;
elseif tc_uc(tcbyte(j)+1)==202 %ml=202,锁定为混合模式
mode = 13;
elseif tc_uc(tcbyte(j)+1)==204 %ps=204,转移为标点模式
premode(1) = 1;
premode(2) = mode;
mode = 14;
else
str = strcat(str,char(tc_uc(tcbyte(j)+1)));
%disp(['大写字母模式',str]);
end
elseif mode==12 %小写字母模式
if premode(1)==1
mode = premode(2);
premode(1) = 0;
end
if tc_lc(tcbyte(j)+1)==205 %as=205,转移为大写字母模式
premode(1) = 1;
premode(2) = mode;
mode = 11;
elseif tc_lc(tcbyte(j)+1)==202 %ml=202,锁定为混合模式
mode = 13;
elseif tc_lc(tcbyte(j)+1)==204 %ps=204,转移为标点模式
premode(1) = 1;
premode(2) = mode;
mode = 14;
else
str = strcat(str,char(tc_lc(tcbyte(j)+1)));
%disp(['小写字母模式',str]);
end
elseif mode==13 %混合型
if premode(1)==1
mode = premode(2);
premode(1) = 0;
end
if tc_mi(tcbyte(j)+1)==200 %al=200,锁定为大写字母模式
mode = 11;
elseif tc_mi(tcbyte(j)+1)==201 %ll=201,锁定为小写字母模式
mode = 12;
elseif tc_mi(tcbyte(j)+1)==203 %pl=203,锁定为标点模式
mode = 14;
elseif tc_mi(tcbyte(j)+1)==204 %ps=204,转移为标点模式
premode(1) = 1;
premode(2) = mode;
mode = 14;
else
str = strcat(str,char(tc_mi(tcbyte(j)+1)));
%disp(['混合型',str]);
end
elseif mode==14 %标点
if premode(1)==1
mode = premode(2);
premode(1) = 0;
end
if tc_do(tcbyte(j)+1)==200 %al=200,锁定为大写字母模式
mode = 11;
else
str = strcat(str,char(tc_do(tcbyte(j)+1)));
%disp(['标点',str]);
end
end
end
elseif mode==2 %数字模式
if valueindex(1)==0
valueindex(1)=1;
valueindex(2)=i;
end
elseif mode==3 %字节模式
if valueindex(1)==0
valueindex(1)=2;
valueindex(2)=i;
end
end
end
end
%disp(['解码后的数据为:',str])
测试结果
原图
sobel算子边缘提取
水平方向边缘
竖直方向边缘
找出每层中心
边界
共12层,每层中心如下
- 获取符号码字codes
截取出的数据块的符号码字,层数为12,列数为8*3=24
- 查表找出codes对应码字,12层,3个数据块,一共36个
- 最终译码结果