2021SC@SDUSC
目录
前言:本篇博客简单介绍了PDF417二维码的相关知识,以及编码步骤过程。
一、PDF417概述
1.1 简介
PDF417是美国符号科技(Symbol Technologies, Inc.)发明的二维条码,是一种堆叠式二维条码。PDF(Portable Data File)意思是“便携数据文件”,组成条码的每一个条码字符由4个条和4个空共17个模块构成,故称为PDF417条码。 PDF417条码需要有417解码功能的条码阅读器才能识别。发明人是台湾赴美学人王寅君博士,王博士于1984年毕业于国立交通大学资讯系,获得纽约州立大学石溪分校(University of New York at Stony Brook)电脑硕士和博士学位后,在1988年进入符号科技进行二维条码的研发工作,于1992年底推出PDF417,并于1989年至1992年间领导世界第一部二维条码雷射读码系统的开发。1993年3月作者正式将PDF417引进台湾,交由祥记资讯推广及研发相关套装软体(黄庆祥,1995)。
目前PDF417、Maxicode、Datamatrix同被美国国家标准协会 (American National Standards Institute, ANSI) MH10 SBC-8委员会选为二维条码国际标准制定范围,其中PDF417主要是预备应用于运输包裹与商品资料标签(Burnell, 1995)。PDF417不仅具有错误侦测能力,且可从受损的条码中读回完整的资料(Moore, 1994),亦即「错误复原能力」,其错误复原率最高可达50%。
PDF417条码是一种高密度、高信息含量的便携式数据文件,是实现证件及卡片等大容量、高可靠性信息自动存储、携带并可用机器自动识读的理想手段。
PDF417二维码可应用在:证件管理、执照年检、报表管理、机电产品的生产和组配线、银行票据管理及行包、货物的运输和邮递。PDF417条码最大的优势在于其庞大的数据容量和极强的纠错能力。
1.2 PDF417二维条码的结构
由于PDF417二维条码的容量较大,除了可将人的姓名、单位、地址、电话等基本资料进行编码外,还可将人体的特徵如指纹、视网膜扫瞄、及照片等个人纪录储存在条码中,这样不但可以实现证件资料的自动输入,而且可以防止证件的伪造,减少犯罪。PDF417已在美国、加拿大、纽西兰的交通部门的执照年审、车辆违规登记、罚款及定期检验上开始应用。美国并同时将PDF417应用在身分证、驾照、军人证上。此外墨西哥也将PDF417应用在报关单据与证件上,从而防止了仿造及犯罪。
PDF417是一个公开码,任何人皆可用其演算法而不必付费,因此是一个开放的条码系统。PDF417的PDF为可携性资料档(Portable Data File)的缩写,取其条码类似一个资料档,可储存较多资料,且可随身携带或随产品走而得名(Paclidis, 1992)。正如其名,每一个PDF码的储存量可高达1,108个文数字(Bytes),若将数字压缩则可存放至2,729Bytes。
每一个PDF417码是由3~90横列堆叠而成,而为了扫瞄方便,其四周皆有静空区,静空区分为水平静空区与垂直静空区,至少应为0.020寸,如图:
其中每一层都包括下列五个部份:
起始码。
左标区:在起始码后面,为一指示符号字元。
资料区:可容纳1~30个资料字元。
右标区: 在资料区的后面,为一指示符号字元。
结束码:在横列之最右边。
除了起始码和结束码外,左标区、资料区和右标区的组成字元皆可称为字码 (Codeword),每一个字码由17个模组(Modules)所构成,每一个字码又可分成4线条(或黑线)及4空白(或白线),每个线条至多不能超过6个模组宽。每个417码因资料大小不同,其行数及每行的资料模组数与字码数都可以从1至30不等。字码的组成如图:
1.3 PDF417二维条码的尺寸
也因为符号的组合较有弹性,每一个PDF417二维条码可因应不同的实体设备印成不同的长宽比例与密度,以适应印刷条件及扫瞄条件的要求。其中每个模组宽X是PDF417码中最重要的尺寸之一,X值的最小限制为0.0075英寸(约0.191mm),在同一个条码符号中,X的值是固定不变的。
PDF417的最小高度与长度可由下列算式算出:
W= (17C+69)X+ 2Q
H = R ×Y+ 2Q
其中:
W= 条码宽度,H=条码高度,X=条码模组宽,Y=层数
C=每层符号字元的总数(含左右标区),R=层高,Q=静空区大小
1.4 PDF417二维条码的错误纠正能力
PDF417二维条码的一个重要特性是其自动纠正错误的能力较高,不过PDF417的错误纠正能力与每个条码可存放的资料量有关,PDF417码将错误复原分为9个等级,其值从0到8,级数愈高,错误纠正能力愈强,但可存放资料量就愈少,一般建议编入至少10%的检查字码。资料存放量与错误纠正等级的关系如下表所示:
错误纠正等级 | 纠正码数 | 可存资料量(位元) |
---|---|---|
自动设定 | 64 | 1024 |
0 | 2 | 1108 |
1 | 4 | 1106 |
2 | 8 | 1101 |
3 | 16 | 1092 |
4 | 32 | 1072 |
5 | 64 | 1024 |
6 | 128 | 957 |
7 | 256 | 804 |
8 | 512 | 496 |
下表建议不同的字数所适用的错误纠正等级;
资料字码数 | 错误纠正等级 |
---|---|
1~40 | 2 |
40~160 | 3 |
161~320 | 4 |
321~863 | 5 |
如前所述,错误纠正等级涉及拒读错误(E错误)与替代错误(T错误)两种错误类型。无论使用哪一种条码机都有一定的精密度极限,造成线条和空白的宽度与理想宽度间必有偏差存在,条码扫瞄设备能够读出解码演算法所允许范围内的不精确条码符号,目前标准中规定X的值最小为0.0075英寸(约0.191mm),此一限制同时反映出目前标准设备的技术现状。
1.5 PDF417的特性
综合本节所讨论,PDF417的特性如下表所示:
项目 | 特性 |
---|---|
可编码字元集 | 8位二进制资料,多达811800种不同的字元集或解释 |
类型 | 连续型,多层 |
字元自我检查 | 有 |
尺寸 | 可变高:390层宽:130栏 |
读码方式 | 双向可读 |
错误纠正字码数 | 2~512个 |
最大资料容量 | 安全等级为0, 每个符号可表示1108个位元 |
二、PDF417Writer
与之前介绍的二维码类似,PDF417Writer类继承了父类Writer。
PDF417Writer有两个静态变量:
分别表示代码周围的默认空白(边距)和默认错误更正级别。
private static final int WHITE_SPACE = 30;
private static final int DEFAULT_ERROR_CORRECTION_LEVEL = 2;
PDF 417的值是由一个数组来保存的。
input 是一个信息字节数组,0为黑色,1为白色;
margin 是条形码周围的边距边框;
返回输入的位矩阵。
private static BitMatrix bitMatrixFromBitArray(byte[][] input, int margin) {
// 创建带有额外空格的位矩阵
BitMatrix output = new BitMatrix(input[0].length + 2 * margin, input.length + 2 * margin);
output.clear();
for (int y = 0, yOutput = output.getHeight() - margin - 1; y < input.length; y++, yOutput--) {
byte[] inputY = input[y];
for (int x = 0; x < input[0].length; x++) {
// 零在字节矩阵中为白色
if (inputY[x] == 1) {
output.set(x + margin, yOutput);
}
}
}
return output;
}
除此之外,还有函数rotateArray(),获取PDF417二维码数组并将其旋转90度。
这使得旋转屏幕时二维码在屏幕上的方向一致。
private static byte[][] rotateArray(byte[][] bitarray) {
byte[][] temp = new byte[bitarray[0].length][bitarray.length];
for (int ii = 0; ii < bitarray.length; ii++) {
int inverseii = bitarray.length - ii - 1;
for (int jj = 0; jj < bitarray[0].length; jj++) {
temp[jj][inverseii] = bitarray[ii][jj];
}
}
return temp;
}
三、class PDF417
PDF417实现的逻辑部分的顶级类。
有两个静态变量,分别表示启动模式(17位)和停止模式(18位)。
private static final int START_PATTERN = 0x1fea8;
private static final int STOP_PATTERN = 0x3fa29;
接下来介绍几种set方法:
setDimensions(int maxCols, int minCols, int maxRows, int minRows) 用来设置最大/最小行/列值,其参数分别表示允许的最大列数、允许的最小列数、允许的最大行数、允许的最小行数。
setCompaction(Compaction compaction)设置要使用的压缩模式。
setCompact(boolean compact)如果compact为真,则启用压缩。
setEncoding(Charset encoding)设置要使用的字符编码。
public void setDimensions(int maxCols, int minCols, int maxRows, int minRows) {
this.maxCols = maxCols;
this.minCols = minCols;
this.maxRows = maxRows;
this.minRows = minRows;
}
public void setCompaction(Compaction compaction) {
this.compaction = compaction;
}
public void setCompact(boolean compact) {
this.compact = compact;
}
public void setEncoding(Charset encoding) {
this.encoding = encoding;
}
determineDimensions(int sourceCodeWords, int errorCorrectionCodeWords)用来确定指定码字数的最佳列和行。
其中,sourceCodeWords 为代码字数,errorCorrectionCodeWords 为纠错码字数,该函数返回维度对象包含列作为宽度和行作为高度。
private int[] determineDimensions(int sourceCodeWords, int errorCorrectionCodeWords) throws WriterException {
float ratio = 0.0f;
int[] dimension = null;
for (int cols = minCols; cols <= maxCols; cols++) {
int rows = calculateNumberOfRows(sourceCodeWords, errorCorrectionCodeWords, cols);
if (rows < minRows) {
break;
}
if (rows > maxRows) {
continue;
}
float newRatio = ((float) (17 * cols + 69) * DEFAULT_MODULE_WIDTH) / (rows * HEIGHT);
// 如果以前的比率更接近首选比率,则忽略
if (dimension != null && Math.abs(newRatio - PREFERRED_RATIO) > Math.abs(ratio - PREFERRED_RATIO)) {
continue;
}
ratio = newRatio;
dimension = new int[] {cols, rows};
}
// 处理最小值大于必要值时的情况
if (dimension == null) {
int rows = calculateNumberOfRows(sourceCodeWords, errorCorrectionCodeWords, minCols);
if (rows < minRows) {
dimension = new int[]{minCols, minRows};
}
}
if (dimension == null) {
throw new WriterException("Unable to fit message in columns");
}
return dimension;
}