条形码如何工作?

大家好!

现在每个人都在使用条形码,大多数情况下都没有注意到这一点。当我们在商店购买杂货时,他们的标识符来自条形码。它也与仓库中的货物,邮政包裹等相同。但实际上并没有那么多人知道它是如何运作的。

条形码的“内部”是什么,以及在此图像上编码的是什么?



让我们弄清楚,还可以编写我们自己的条码解码器。
 

介绍


使用条形码有很长的历史。自动化的第一次尝试是在50年代完成的,授予了代码阅读系统的专利。在宾夕法尼亚铁路公司工作的大卫柯林斯决定让汽车分拣过程更加容易。这个想法很明显 - 用不同的颜色条纹编码汽车标识符,并使用照片单元格读取它们。1962年,这些规范成为美国铁路协会的标准。(KarTrak系统)。1968年,灯被激光取代,它可以提高精度并减小读卡器尺寸。1973年开发了通用产品代码,并于1974年出售了第一种杂货产品(瑞格利的口香糖 - 显然在美国;)。1984年,所有商店的第三部分都使用了条形码,在其他国家,它后来变得流行。

对于不同的应用程序有许多不同的条形码类型,例如,«12345678»字符串可以这种方式编码(而不是所有这些):



让我们开始分析。以下所有信息都是关于«Code-128»类型的 - 只是因为它易于理解原理。那些想要测试其他模式的人可以使用在线条码生成器并自己测试其他类型。

乍一看,条形码看起来像一组随机的数字,但事实上它的结构组织得很好:



1 - 空间,是确定代码起始位置所必需的。
2 - 开始符号。有三种Code-128可用(称为A,B和C),起始符号分别为11010000100,11010010000或11010011100。对于此类型,编码表是不同的(有关详细信息,请参阅Code_128说明)。
3 - 代码本身,包含用户数据。
4 - 检查总和。
5 - 停止符号,对于Code-128,其1100011101011. 
6(1) - 空白区域。

现在让我们来看看这些位是如何编码的。它真的很容易 - 如果我们将最细的线宽设置为«1»,那么双宽度线将是«11»,三倍宽度线是«111»,依此类推。根据相同的原理,空的空间将分别为“0”,“00”或“000”。有兴趣的人可以比较上面图片的开始顺序,看看规则是否得到尊重。

现在我们可以开始编码。
 

得到比特序列


一般来说,它是最复杂的部分,它可以通过不同的方式完成。我不确定我的方法是否是最优的,但对于我们的任务来说,这绝对是足够的。

首先,让我们加载图像,拉伸它的宽度,从中间裁剪一条水平线,将其转换为黑白颜色并将其保存为数组。
 

from PIL import Image
import numpy as np
import matplotlib.pyplot as plt

image_path = "barcode.jpg"
img = Image.open(image_path)
width, height = img.size
basewidth = 4*width
img = img.resize((basewidth, height), Image.ANTIALIAS)
hor_line_bw = img.crop((0, int(height/2), basewidth, int(height/2) + 1)).convert('L')
hor_data = np.asarray(hor_line_bw, dtype="int32")[0]


在条形码黑线对应于«1»,但在RGB中黑色相反,0,所以阵列需要反转。我们还将计算平均值。
 

hor_data = 255 - hor_data
avg = np.average(hor_data)

plt.plot(hor_data)
plt.show()


让我们运行程序来验证条形码是否正确加载:



现在我们需要确定一个“位”的宽度。为此,我们将提取序列,保存平均线交叉的位置。
 

pos1, pos2 = -1, -1
bits = ""
for p in range(basewidth - 2):
    if hor_data[p] < avg and hor_data[p + 1] > avg:
        bits += "1"
        if pos1 == -1:
            pos1 = p
        if bits == "101":
            pos2 = p
            break
    if hor_data[p] > avg and hor_data[p + 1] < avg:
        bits += "0"

bit_width = int((pos2 - pos1)/3)


我们只保存平均线交叉,因此代码«1101»将保存为«101»,位足以获得其像素宽度。

现在让我们自己进行解码。我们需要找到每个平均线交叉,并找到最后一个间隔中的位数。数字不匹配完美(代码可以拉伸或弯曲一点),因此,我们需要将值四舍五入为整数。
 

bits = ""
for p in range(basewidth - 2):
    if hor_data[p] > avg and hor_data[p + 1] < avg:
        interval = p - pos1
        cnt = interval/bit_width
        bits += "1"*int(round(cnt))
        pos1 = p
    if hor_data[p] < avg and hor_data[p + 1] > avg:
        interval = p - pos1
        cnt = interval/bit_width
        bits += "0"*int(round(cnt))
        pos1 = p


也许有更好的方法可以做到这一点,读者可以写评论。

如果一切都完美,我们将得到这样的序列:

11010010000110001010001000110100010001101110100011011101000111011011
01100110011000101000101000110001000101100011000101110110011011001111
00010101100011101011

 

解码


一般来说,它很容易。Code-128中的符号使用11位代码进行编码,可以使用不同的编码(根据此编码 - A,B或С,它可以是00到99之间的字母或数字)。

在我们的例子中,序列的开头是11010010000,对应于«Code B»。我懒得手动输入所有代码,所以我只是从维基百科页面上复制粘贴它。在Python上也解析了这一行(提示 - 在生产时不做这样的事情)。
 

    CODE128_CHART = """
        0	_	_	00	32	S	11011001100	212222
        1	!	!	01	33	!	11001101100	222122
        2	"	"	02	34	"	11001100110	222221
        3	#	#	03	35	#	10010011000	121223
        ...
        93	GS	}	93	125	}	10100011110	111341
        94	RS	~	94	126	~	10001011110	131141
        103	Start Start A	208	SCA	11010000100	211412
        104	Start Start B	209	SCB	11010010000	211214
        105	Start Start C	210	SCC	11010011100	211232
        106	Stop Stop	-	- -	11000111010	233111""".split()
    SYMBOLS = [value for value in CODE128_CHART[6::8]]
    VALUESB = [value for value in CODE128_CHART[2::8]]
    CODE128B = dict(zip(SYMBOLS, VALUESB))


最后的部分很简单。首先,让我们将序列拆分为11位块:
 

sym_len = 11
symbols = [bits[i:i+sym_len] for i in range(0, len(bits), sym_len)]


最后,让我们生成输出字符串并显示它:
 

str_out = ""
for sym in symbols:
    if CODE128A[sym] == 'Start':
        continue
    if CODE128A[sym] == 'Stop':
        break
    str_out += CODE128A[sym]
    print("  ", sym, CODE128A[sym])

print("Str:", str_out)


我不会在这里显示顶部图像的解码结果,让它成为读者的作业(使用下载的智能手机应用程序将被视为作弊:)。

CRC检查未在此代码中实现,那些想要的人可以自己完成。

当然,这个算法并不完美,它是在半小时内完成的。对于专业任务,有即用型库,例如pyzbar。对于解码图像,4行代码就足够了:
 

from pyzbar.pyzbar import decode

img = Image.open(image_path)
decode = decode(img)
print(decode)


(首先必须使用命令«pip install pyzbar»安装库)

添加:网站用户vinograd19发送了一个关于条形码校验和计算历史的有趣评论。

支票号计算很有意思,它起源于进化。
显然需要校验和来避免错误的解码。如果条形码是1234,并被解码为7234,我们需要一种方法来拒绝替换1到7.验证可能不完美,但至少90%的代码应该被正确验证。

第一种方法:让我们只取总和,得到0作为除法的余数。第一个符号包含数据,最后一个数字被选择,所有数字的总和除以10.解码后,如果数量不能被10整除 - 解码不正确,需要重复。例如,代码1234有效 - 1 + 2 + 3 + 4 = 10.代码1216 - 也有效,但1218不有效。

它有助于避免解码问题。但也可以使用硬件键盘手动输入代码。使用这个,发现另一个坏的情况 - 如果两位数的顺序将被改变,校验和将仍然是正确的,它肯定是坏的。例如,如果条形码1234输入为2134,则校验和将是相同的。结果发现,如果一个人试图快速输入数字,则错误的数字顺序是常见的情况。

第二种方法。让我们改进校验和算法 - 让我们计算两次奇数。然后,如果订单将被更改,总和将是不正确的。例如,代码2364有效(2 + 3 * 2 + 6 + 4 * 2 = 20),但代码3264不是(3 + 2 * 2 + 6 + 4 * 2 = 19)。它更好,但另一个案例已经出现。有一些键盘,两行有10个键,第一行是12345,第二行是67890.如果不是«1»,用户将输入«2»,校验和检查将失败。但如果用户输入«6»而不是«1» - 检查总和有时可能是正确的。因为6 = 1 + 5,如果数字有奇数位,我们得到2 * 6 = 2 * 1 + 2 * 5 - 总和增加了10.如果用户输入«7,则会发生同样的错误»而不是«2»,«8»而不是«3»,依此类推。

第三种方法。让我们再拿一笔钱,但让我们得到奇数... 3次。例如,代码1234565 - 有效,因为1 + 2 * 3 + 3 + 4 * 3 + 5 + 6 * 3 +5 = 50. 

此方法成为EAN13代码的标准,有一些更改:位数是固定并等于13,其中第13位 - 是校验和。奇数地方的数字计算三次,偶数地点一次。


顺便说一下,EAN-13代码是交易和购物中心中使用最广泛的代码,因此人们比其他代码类型更常见。它的位编码与Code-128相同,数据结构可以在维基百科文章中找到。
 

结论


正如我们所看到的,即使像条形码这样简单的东西,也可以包含一些很酷的东西。顺便说一句,读者的另一个小生命,他们耐心地读到这个地方 - 条形码下的文字与条形码数据完全相同。它是为操作员制作的,如果扫描器无法读取,他们可以手动输入代码。因此很容易知道条形码内容 - 只需阅读下面的文字。

谢谢阅读。

 

https://habr.com/en/post/439768/

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值