导言:条形码技术实验,基于Java的EAN-13条码识别
实验原理
EAN-13简介
European Article Number (欧洲物品编码的缩写),其中共计13位代码的EAN-13是比较通用的一般终端产品的条形码协议和标准,主要应用于超级市场和其它零售业,因此这种是我们比较常见的,随便拿起身边的一个从超市买来的商品都可以从包装上看得到。
条码结构
EAN-13商品条码是表示EAN/UCC-13商品标识代码的条码符号,由左侧空白区、起始符、左侧数据符、中间分隔符、右侧数据符、校验符、终止符、右侧空白区及供人识别字符组成。
- 左侧空白区:位于条码符号最左侧与空的反射率相同的区域,其最小宽度为11个模块宽。
- 起始符:位于条码符号左侧空白区的右侧,表示信息开始的特殊符号,由3个模块组成。
- 左侧数据符:位于起始符右侧,表示6位数字信息的一组条码字符,由42个模块组成。
- 中间分隔符:位于左侧数据符的右侧,是平分条码字符的特殊符号,由5个模块组成。
- 右侧数据符:位于中间分隔符右侧,表示5位数字信息的一组条码字符,由35个模块组成。
- 校验符:位于右侧数据符的右侧,表示校验码的条码字符,由7个模块组成。
- 终止符:位于条码符号校验符的右侧,表示信息结束的特殊符号,由3个模块组成。
- 右侧空白区:位于条码符号最右侧的与空的反射率相同的区域,其最小宽度为7个模块宽。为保护右侧空白区的宽度,可在条码符号右下角加“>”符号。
- 供人识读字符:位于条码符号的下方,是与条码字符相对应的供人识别的13位数字,最左边一位称前置码。供人识别字符优先选用OCR-B字符集,字符顶部和条码底部的最小距离为0.5个模块宽。标准版商品条码中的前置码印制在条码符号起始符的左侧。
字符集
包括EAN-13在内的商品条码,每一条码数据字符由 2个条和 2个空 构成,每一条或空由1~4个模块组成,每一条码字符的总模块数为7。
用二进制“1”表示条的模块,即黑色;用二进制“0”表示空的模块,即白色。
条码的二进制表示方法有三个子集:A、B 和 C
数字字符 | A 子集 | B 子集 | C 子集 |
---|---|---|---|
0 | 0001101 | 0100111 | 1110010 |
1 | 0011001 | 0110011 | 1100110 |
2 | 0010011 | 0011011 | 1101100 |
3 | 0111101 | 0100001 | 1000010 |
4 | 0100011 | 0011101 | 1011100 |
5 | 0110001 | 0111001 | 1001110 |
6 | 0101111 | 0000101 | 1010000 |
7 | 0111011 | 0010001 | 1000100 |
8 | 0110111 | 0001001 | 1001000 |
9 | 0001011 | 0010111 | 1110100 |
A 子集中条码字符所包含的条的模块的个数为奇数,称为奇排列;
B、C 子集中条码字符所包含的条的模块的个数为偶数,称为偶排列。
前置码
EAN-13商品条码中的前置码不用条码字符表示,不包括在左侧数据符内。
右侧数据符及校验符均用字符集中的 C 子集表示;
左侧数据符使用的子集取决于前置码的数值。
下表中列出了左侧数据符的字符集的选择规则:
前置码 | 左1 | 左2 | 左3 | 左4 | 左5 | 左6 |
---|---|---|---|---|---|---|
0 | A | A | A | A | A | A |
1 | A | A | B | A | B | B |
2 | A | A | B | B | A | B |
3 | A | A | B | B | B | A |
4 | A | B | A | A | B | B |
5 | A | B | B | A | A | B |
6 | A | B | B | B | A | A |
7 | A | B | A | B | A | B |
8 | A | B | A | B | B | A |
9 | A | B | B | A | B | A |
校验符
EAN-13商品条码中的校验符用字符集中的 C 子集表示,校验符的作用是检验前面12个数字是否正确,在条码机每次读入数据时,都会计算一次数据符的校验并与校验符进行比对。
校验符的计算方法非常简单,将12个数据符从左起将所有的奇数位相加得出一个数a,将所有的偶数位相加得出一个数b,然后将数b乘以3再与a相加得到数c,用10减去数c的个位数,如果结果不为10则检验符为结果本身,如果为10则检验符为0。
校验符的计算与本次实验无关,暂且不过多赘述。
识别原理
该识别主要应用于简单场景下的 EAN-13 条码识别,需要标准的 EAN-13 图片。
首先选取图片中间部分的一行像素点,由于起始符 “101” 是1个标准模块宽,于是先识别1个标准模块宽对应的像素点个数,随后依次识别左右数据区,获得编码数据后,对比 A、B、C 字符集得到解码后的数据(不包括前置位),再根据前6位数据所属的字符集,推出前置位,从而实现解码。
实验内容
定义全局变量
首先声明需要的全局变量
/** 自定义类 用于存储像素点的RGB值 */
class Color{
int r;
int g;
int b;
}
/** 文件选择器 */
JFileChooser jfc = new JFileChooser();
/** 待识别的图片 */
BufferedImage image;
/** 图片长度 */
static int length;
/** 图片宽度 */
static int width;
/** 单位宽度 */
static int perLength = 0;
/** 字符集 */
String[] a = {"0001101","0011001","0010011","0111101","0100011","0110001","0101111","0111011","0110111","0001011"};
String[] b = {"0100111","0110011","0011011","0100001","0011101","0111001","0000101","0010001","0001001","0010111"};
String[] c = {"1110010","1100110","1101100","1000010","1011100","1001110","1010000","1000100","1001000","1110100"};
/** 前置码集 */
String[] front = {"AAAAAA", "AABABB", "AABBAB", "AABBBA", "ABAABB", "ABBAAB", "ABBBAA","ABABAB","ABABBA","ABBABA"};
获得图片
获得需要识别的图片
/** 获取图片 */
public void loadImage(){
jfc.setFileSelectionMode(JFileChooser.FILES_ONLY );
jfc.setMultiSelectionEnabled(false);
jfc.setFileFilter(new FileNameExtensionFilter("图片文件(*.png, *.jpg, *.jpeg)",
"png","jpg","jpeg"));
File directory = new File("");
jfc.setCurrentDirectory(new File(directory.getAbsolutePath()));
int status = jfc.showOpenDialog(null);
// 打开图片
if(status==JFileChooser.APPROVE_OPTION) {
File sourceImage = jfc.getSelectedFile();
try {
image = ImageIO.read(sourceImage);
} catch (IOException e) {
e.printStackTrace();
}
}
}
处理图片
对图片进行处理
public Color[] proc(){
length = image.getHeight();
width = image.getWidth();
// 第一行条码
Color[] color = new Color[width];
// true 是白 false 是黑
boolean last = true;
boolean measure = true;
// 获取第一行的像素值,并获取标准宽度
for (int i = 0; i < width; i++){
Color temp = new Color();
int pixel = image.getRGB(i, length/2);
temp.r = ((pixel >> 16) & 0xFF);
temp.g = ((pixel >> 8) & 0xFF);
temp.b = ((pixel & 0xFF));
color[i] = temp;
// 是否在获取标准宽度
if (measure){
if (temp.r == 255 || temp.g == 255 || temp.b == 255) {
if (!last){
measure = false;
}
last = true;
} else if (temp.r == 0 || temp.g == 0 || temp.b == 0){
perLength++;
last = false;
}
}
}
return color;
}
使用 BufferedImage 的 getRGB() 函数得到的值是 sRGB 色彩空间的值,想要得到我们日常生活中常见的 R、G、B 值,需要进行对位与的操作
temp.r = ((pixel >> 16) & 0xFF);
temp.g = ((pixel >> 8) & 0xFF);
temp.b = ((pixel & 0xFF));
该部分代码为获取标准模块宽
if (measure){
if (temp.r == 255 || temp.g == 255 || temp.b == 255) {
if (!last){
measure = false;
}
last = true;
} else if (temp.r == 0 || temp.g == 0 || temp.b == 0){
perLength++;
last = false;
}
}
解码处理
EAN-13 共有 95个模块,除去起始符各 3 个模块、中间分隔符 5 个模块,剩余数据区共 84 个模块
// EN-13 共 84 块数据区
int[] data = new int[84 * perLength];
int[] pureData = new int[84];
于是我们在左右两侧数据区采集数据,范围为:
左侧数据区:count > 3 * perLength - 1 && count < 45 * perLength - 1
右侧数据区:count > 50 * perLength - 1 && count < 85 * perLength - 1
perLength 为上一步获取的标准模块宽
for (int i = 0; i < width; i++){
if (!measure){
if(color[i].r == 0 || color[i].g == 0 || color[i].b == 0){
measure = true;
}
}
if (measure){
boolean range = count > 3 * perLength - 1 && count < 45 * perLength - 1
|| count > 50 * perLength - 1 && count < 85 * perLength - 1;
if (range){
if (color[i].r == 255 || color[i].g == 255 || color[i].b == 255) {
data[start] = 0;
start++;
} else if (color[i].r == 0 || color[i].g == 0 || color[i].b == 0){
data[start] = 1;
start++;
}
}
count++;
}
}
在识别中,采集数据时,相同的数据会重复采集 perLength 次,所以使用下列代码来获取纯净的数据
// 获取纯净数据
for (int i = 0; i < pureData.length; i++) {
pureData[i] = data[i * perLength];
}
接下来需要对纯净的数据 pureData 进行分析,由于每一条码数据字符由 2个条和 2个空 构成,每一条码字符的总模块数为7。所以以 7 个一组对数据进行分析,通过对比 A、B、C 字符集获得解码数据,存入code中,并用 sort 记录左侧数据区分别所属的字符集
int[] result = new int[12];
count = 1;
int size = 7;
StringBuilder code = new StringBuilder();
StringBuilder sort = new StringBuilder();
for (int i = 0; i < pureData.length; i++) {
code.append(pureData[i]);
if (((i+1) % size) == 0){
for (int j = 0; j < a.length; j++){
if (code.toString().equals(a[j])){
if (count < 7){
sort.append("A");
}
result[count] = j;
count++;
} else if (code.toString().equals(b[j])){
if (count < 7){
sort.append("B");
}
result[count] = j;
count++;
} else if (code.toString().equals(c[j])){
if (count < 7){
sort.append("C");
}
result[count] = j;
count++;
}
}
code = new StringBuilder();
}
}
通过对比前置码,获得前置码,并填充至 code 的首位
for (int i = 0; i < front.length; i++){
if (sort.toString().equals(front[i])){
result[0] = i;
}
}
实验结果
原条码如下
解码结果
解码成功!👍