怎樣用Python識別條形碼?[譯]

# 怎樣用Python識別條形碼?[譯]

現(xiàn)在每個人都在使用條形碼,大家卻幾乎注意不到。當我們在商店買東西時,貨品的識別使用條形碼。倉庫中的貨物,郵政包裹等也同樣使用條形碼來識別。但實際上并沒有多少人知道條形碼是如何工作的。

條形碼包含了什么內容,這個圖像的編碼內容是什么?

![](http://imgs.zhuotang.cc/img/zggiwtxnuelznl9cq6u9jzcio5m.jpeg)

讓我們來弄清楚,并寫出我們自己的解碼器。

## 介紹

使用條形碼已經(jīng)有很長的歷史。首次嘗試實現(xiàn)自動化是在50年代完成的,當時一個讀碼系統(tǒng)被授予專利。在賓夕法尼亞鐵路公司工作的大衛(wèi).柯林斯(David Collins)決定簡化火車車廂分揀過程。他的方法是 —— 用不同的顏色條紋來編制車廂標識碼,然后使用光電管讀取它們。1962年這套編碼成為美國鐵路協(xié)會的標準(即KarTrak系統(tǒng))。到了1968年,為了增加識別準確率,同時減小讀碼器的尺寸,采用激光替代了氙氣燈。1973年開發(fā)出通用產(chǎn)品編碼(UPC碼),1974年第一個帶條碼的百貨商品(箭牌口香糖)開始在美國銷售。1984年條形碼已經(jīng)在全美商店使用,其他國家稍后也開始流行。

對于不同的應用,有不同的條碼類型。比如字符串“12345678”可以被編碼成下列這些條碼(不是全部喲):

![](http://imgs.zhuotang.cc/img/20190320151853.png)

讓我們開始分析。為了方便理解其原理,下面所有條形碼均使用 Code-128 碼。若想嘗試其他編碼,請使用 [在線條碼生成器](https://barcode.tec-it.com/en/Code128) 自行處理。

初看條形碼象一組隨機的數(shù)字,實際上它的結構井井有條:

![](http://imgs.zhuotang.cc/img/20190320152125.png)

1 — 空白區(qū),需要確定條碼的起始位置。

2 — 開始位 。有三種Code-128類型可供選擇(叫作A,B和C)。開始位相應分別是11010000100, 11010010000 或 11010011100 。不同類型的編碼表是不同的(詳見Code_128規(guī)范)

3 — 條碼本身,包含用戶數(shù)據(jù)。

4 — 校驗位。

5 — 停止位。對于 Code-128是 1100011101011 。

6(1) — 空白區(qū)。

現(xiàn)在讓我們來看看這些位是如何編碼的。其實很簡單——如果我們將最細的線寬設為 ?1?,那么2倍的線寬就是?11?,3倍的線寬就是 ?111?,以此類推。空白寬度按照同樣原則,分別代表 ?0?, ?00? 或 ?000?。有興趣的人可以比較上面圖片驗證規(guī)則是否有效。

現(xiàn)在我們可以開始編碼了。

## 獲得條碼序列

一般來說,這是最復雜的部分,可以通過不同的方式實現(xiàn)。 我不確定我的方法是否是最優(yōu)的,但對于我們的任務來說,這絕對是足夠的。

首先,讓我們加載圖像,拉伸其寬度,從中間裁剪一條水平線,將其轉換為黑白顏色并保存到數(shù)組中。

```

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?,所以數(shù)組中數(shù)據(jù)值需要倒置。另外我們還需要計算數(shù)組的平均值。

```

hor_data = 255 - hor_data

avg = np.average(hor_data)

plt.plot(hor_data)

plt.show()

```

讓我們運行程序來驗證條形碼被正確加載:

![](http://imgs.zhuotang.cc/img/20190320152420.png)

現(xiàn)在我們需要確定一個數(shù)位的寬度。為此我們要提取數(shù)據(jù),記錄黑白線分界點的位置

```

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?,但是對于獲取條碼數(shù)位的像素寬度足夠了。

現(xiàn)在讓我們對數(shù)據(jù)進行解碼。我們需要找到每個條碼線,并找出其間距對應的位數(shù)。位數(shù)并不能精確匹配(條碼會被拉伸或扭曲一點),所以我們需要將結果四舍五入為整數(shù)值。

```

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

```

也許有更好的方法來做到這一點,大家可以寫到評論區(qū)。

如果一切都做得很完美,我們會得到類似的序列:

```

11010010000110001010001000110100010001101110100011011101000111011011

01100110011000101000101000110001000101100011000101110110011011001111

00010101100011101011

```

## 解碼

一般來說,解碼很容易。Code-128碼是11位條碼,具有不同的編碼類型(根據(jù)編碼類型—A,B或C,可以表示字母或[00]-[99]的數(shù)字對集合。

在我們的例子中,起始位是 11010010000,對應編碼類型B。我懶得手動輸入所有代碼,所以直接從維基百科頁面上復制粘貼它。解析每行的內容也是使用Python(提示—開發(fā)產(chǎn)品可別這么干)

```

? ? 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位數(shù)據(jù)塊:

```

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 CODE128B[sym] == 'Start':

? ? ? ? continue

? ? if CODE128B[sym] == 'Stop':

? ? ? ? break

? ? str_out += CODE128B[sym]

? ? print("? ", sym, CODE128B[sym])

print("Str:", str_out)

```

我沒有在此顯示本文開頭條碼圖片的解碼結果,把它作為讀者的作業(yè)吧(使用下載的智能手機APP識別將被視為作弊:)

CRC校驗也沒有在此代碼中實現(xiàn),如有需要請自行解決。

當然,本算法并不完美,它只花了一個半小時完成。對于專業(yè)性任務可以使用現(xiàn)成的類庫,比如 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%條碼能夠被正確驗證。

第一步算法:讓我們得到數(shù)字和,且余數(shù)為0.第一個符號包含數(shù)據(jù),最后一個數(shù)字是這樣選擇的,數(shù)字和除以10。解碼后,如果數(shù)字和不能被10整除,則解碼錯誤,需要重新解碼。比如,條碼1234有效—1+2+3+4 = 10。條碼1216也有效,但1218無效。

這避免了解碼問題。但是條碼可以通過硬件鍵盤手工輸入。這個方法的另一個缺陷被發(fā)現(xiàn)——如果訂單的兩位數(shù)字被交換了,校驗位仍然正確,這太糟了。不如,代碼1234被輸入為2134,校驗位仍然是一樣的。如果人們輸入數(shù)字很快,錯誤的數(shù)字順序是常見的情況。

第二步算法:改進校驗位算法——計算奇數(shù)位兩次。這樣,如果訂單號改變了,數(shù)字和就不對了。比如代碼2364是有效的(2 + 3\*2+ 6 + 4\*2 = 22),但代碼3264是無效的(3 + 2\*2 + 6 + 4\*2 = 21)。很好,但是另一種情況又出現(xiàn)了。有些鍵盤是兩行10鍵,第一行是12345,第二行是67890.如果 ?1? 輸入成?2?,檢驗碼會出錯。但是如果 ?1? 輸入成?6?,有時校驗碼仍然正確。因為6=1+5,如果數(shù)字在奇數(shù)位,26=21+2\*5—數(shù)字和增加了10.同樣的錯誤也會發(fā)生在 ?7?代替 ?2?, ?8? 代替 ?3?, 等情況下。

第三步算法:再次計算數(shù)字和,但是讓奇數(shù)位...乘以3。比如代碼1234565 是有效的,因為1 + 2\*3 + 3 +? 4\*3 + 5 + 6\*3 +5 = 50.

這個方法略微變化成為了EAN13編碼的標準:數(shù)字位固定為13位,第13位為校驗位。奇數(shù)位數(shù)字相加3次,偶數(shù)位數(shù)字相加1次。

EAN-13條碼廣泛使用在貿(mào)易和商業(yè)領域,是人們最常見到的條碼編碼。Code-128編碼也使用同樣的校驗規(guī)則,具體條碼數(shù)據(jù)結構參見Wikipedia相關條目。

## 結論

正如我們所看到的,即使像條形碼這樣簡單的東西,也可以包含一些很酷的東西。順便給耐心地讀到這個地方的讀者一個小竅門——條形碼下的文本與條形碼數(shù)據(jù)完全相同。這是為操作員準備的,如果掃描器無法讀取,他們可以手動輸入代碼。因此很容易知道條形碼內容—— 只需閱讀條碼下面的文字。

------

原文:[How does a barcode work?](https://habr.com/en/post/439768/)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容