大家好!
如今,每个人都在使用条形码,而大多数情况下并未注意到这一点。 当我们在商店购买杂货时,它们的标识符是从条形码中获取的。 它也与仓库,邮政包裹等中的货物相同。 但实际上没有多少人知道它是如何工作的。
条形码的“内部”是什么,以及在此图像上编码的是什么?

让我们弄清楚,还可以编写我们自己的条码解码器。
引言
使用条形码已有很长的历史。 在50年代进行了自动化的首次尝试,并获得了代码读取系统的专利。 在宾夕法尼亚铁路工作的戴维·柯林斯(David Collins)决定简化汽车的分类过程。 这个想法很明显-对带有不同颜色条纹的汽车标识符进行编码,然后使用光电管读取它们。 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或C,它可以是00到99之间的字母或数字)。
在我们的情况下,序列的开头是11010010000,对应于“代码B”。 我太懒了,无法手动输入所有代码,所以我只是从Wikipedia页面复制粘贴了它。 这行代码的解析也是在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 。 要解码图像,只需四行代码即可:
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。如果用户输入“ 2”而不是“ 1”,则校验和检查将失败。 但是,如果用户输入“ 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中的相同,其数据结构可在
Wikipedia文章中找到。
结论
如我们所见,即使是诸如条形码之类的简单物品,也可能包含一些很酷的东西。 顺便说一句,对于读者来说,这是另一个小小的救生库,他们足够耐心阅读直到这个地方-条形码下的文本与条形码数据完全相同。 它是为操作员制作的,如果扫描仪无法读取,则可以手动输入代码。 因此,它很容易知道条形码的内容-只需阅读下面的文字。
感谢您的阅读。