条形码如何排列?

哈Ha!

一个现代人每天都在不考虑条形码的情况下遇到条形码。 当我们在超市购买产品时,借助条形码可以准确读取其代码。 还有包裹,仓库中的货物等等。 但是,很少有人知道它是如何工作的。

条形码如何排列,图片中的编码是什么?



让我们尝试弄清楚,与此同时,我们将编写此类代码的解码器。

引言


条形码的使用历史悠久。 第一次自动化尝试始于1950年代; 1952年获得了代码读取器的专利。 参与铁路车辆分类的工程师希望简化此过程。 这个想法很明显-使用条带对数字进行编码,并使用光电管读取它们。 1962年,代码开始正式用于识别美国铁路( KarTrak系统)上的汽车,1968年,探照灯被激光束取代,从而提高了准确性并减小了阅读器的尺寸。 1973年,出现了“通用产品代码”格式,并在1974年使用代码扫描仪(箭牌的口香糖是美国)在一家超市出售了第一款产品。 1984年,三分之一的商店使用彩带,但在俄罗斯,它们开始在90年代左右使用。

大量不同的代码用于不同的任务,例如,序列“ 12345678”可以用以下方式表示(并非全部):



让我们开始按位分析。 此外,下面描述的所有内容都将引用“ Code-128”格式-仅因为其格式非常简单明了。 那些想尝试其他物种的人可以打开在线生成器,并亲自看看。

乍一看,条形码似乎只是行的随机序列,实际上,其结构显然是固定的:



1-清楚识别代码开头所需的空白空间
2-起始符号。 对于Code-128,可以使用3个选项(称为A,B和C):11010000100、11010010000或11010011100,它们对应于不同的代码表(有关更多详细信息,请参阅Wikipedia )。
3-实际上,包含我们所需数据的代码
4-校验和
5-停止符号。 对于Code-128,这是1100011101011。
6(1)-空白。

现在介绍如何对位进行编码。 此处的一切都很简单-如果将最细的线的宽度设为“ 1”,则双倍宽度的线将给出代码“ 11”,三倍的代码为“ 111”,依此类推。 根据相同的原理,空白空间将为“ 0”或“ 00”或“ 000”。 那些希望的人可以比较图片中的起始代码以确保符合规则。

现在您可以开始编程了。

获取位序列


原则上,这是最困难的部分,当然,从算法上讲,它可以以不同的方式实现。 我不确定以下算法是否是最佳算法,但对于案例研究来说已经足够了。

首先,加载图像,拉伸图像的宽度,从图像中间取一条水平线,将其转换为b / w,然后将其作为数组加载。

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”对应于0,因此必须反转阵列。 同时,我们计算平均值。

 hor_data = 255 - hor_data avg = np.average(hor_data) plt.plot(hor_data) plt.show() 

我们启动程序以确保正确加载条形码:



现在,您需要确定一个“位”的宽度。 为此,我们突出显示起始序列“ 1101”的开始,记录图表通过中间线的过渡时刻。

 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位代码进行编码,该代码有3种类型(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 。 使用这种库的代码仅需4行:

 from pyzbar.pyzbar import decode img = Image.open(image_path) decode = decode(img) print(decode) 

(首先,您需要通过输入命令pip install pyzbar来安装库)

另外vinograd19在有关CRC计数的注释中写道:

校验位的历史很有趣。 它是进化产生的。
为了避免错误解码,需要校验位。 如果条形码是1234,并且被识别为7234,则您需要进行验证以防止将1替换为7。验证可能不准确,因此至少要确定90%的无效数字。

第一种方法:让我们以金额为准。 因此,除以10的余数为0。好吧,也就是说,前12个字符承载信息负载,然后选择最后一个数字,以使数字的总和除以10。解码序列,如果总和不能被10整除,则意味着需要进行错误解码,并且需要这一次。 例如,代码1234有效。 1 + 2 + 3 + 4 =10。代码1216也有效,但1218无效。

这样可以避免自动化问题。 但是,在创建条形码时,存在回退形式,即在键上键入数字。 还有一个不好的情况:如果您更改两位数的顺序,则校验和不会改变,这是不好的。 也就是说,如果条形码1234被锤打为2134,则校验和将收敛,但是我们输入了错误的数字。 事实证明,如果您快速敲键,数字顺序错误是很常见的情况。

第二种方法。 好吧,让我们稍微增加一点。 这样,偶数位置上的数字就会被考虑两次。 然后,当更改订单时,金额肯定不会收敛到所需的金额。 例如,代码2364是有效的(2 + 3 + 3 + 6 + 4 + 4 = 20),而代码3264是无效的(3+ 2 + 2 + 6 + 4 + 4 = 19)。 但是这是另一个不好的例子。 某些键盘会将十位数字排列成两行。 第一行是12345,第二行以下是67890。如果不是按“ 1”键,而是向右按“ 2”键,则校验和将防止输入错误。 但是如果不是按“ 1”键,而是按下面的“ 6”键,则可能不会发出警告。 毕竟6 = 1 + 5,如果在计算校验和时该数字位于偶数位置,则我们有2 * 6 = 2 * 1 + 2 * 5。 也就是说,校验和正好增加了10,因此其最后一位没有改变。 例如,代码2134和2634中的校验和相同。 如果我们按7而不是2,而不是3则按8,将发生相同的错误,依此类推。

第三种方法。 好的,让我们再次求和,只考虑偶数位置的数字……三遍。 也就是说,代码1234565是有效的,因为1 + 2 * 3 + 3 + 4 * 3 + 5 + 6 * 3 +5 = 50。

所描述的方法经过一些更正已成为计算EAN13校验和的标准:位数固定且等于13,其中第13个是相同的校验和。 奇数位上的数字被计算三次,偶数上-一次。

结论


如您所见,即使是条形码之类的简单物品也包含很多有趣的东西。 顺便说一句,对于那些读到这里的人来说,另一个生活技巧-条形码下的文本(如果有)完全复制了其内容。 这样做是为了在代码不可读的情况下,操作员可以手动输入。 因此找出条形码的内容通常很简单-只需看一下其下方的文字即可。

正如评论中所建议的,交易中最流行的是EAN-13码,那里的位编码是相同的,希望的人可以自己看到字符结构。

如果读者没有兴趣,可以单独考虑QR码。

谢谢您的关注。

Source: https://habr.com/ru/post/zh-CN439604/


All Articles