619
魔獸
(OpenCV/Python)實現OCR銀行票據
更多深度文章,請關注:https://yq.aliyun.com/cloud
今天我們來介紹一下如何使用(opencv/python)來實現OCR處理銀行票據。文末有代碼和相關文檔下載!
在第一部分中,我們將討論兩個主題:
1. 首先,我們將了解MICR E-13B字體,美國,英國,加拿大等國家用於支票上都是使用的這種字體。
2. 其次,我們將討論如何從MICR E-13B參考圖像中提取數字和符號。這將使我們能夠提取每個字符的ROI,然後將其用於OCR銀行支票。
MICR E-13B 字體:
MICR(磁墨字符識別)是處理文件的金融工業技術。
MICR的E-13B變體包含14個字符:
· 數字:數字0-9。
· ⑆過境:銀行分行分隔。
· ⑇金額:交易金額分隔符。
· ⑈在我們:客戶帳號分隔符。
· ⑉dash:數字分隔符(例如路由和帳號之間)。
銀行支票字符識別看起來更難:
在銀行支票上使用的MICR E-13B字體中,數字有一個輪廓。但是,控製符號對於每個角色具有三個輪廓,使任務稍微更具挑戰性。我們不能使用簡單的輪廓和邊框方法。相反,我們需要設計自己的方法來可靠地提取數字和符號。
用OpenCV提取MICR數字和符號:
創建一個新的文件,命名為bank_check_ocr.py,並插入以下代碼:
# import the necessary packages
from skimage.segmentation import clear_border
from imutils import contours
import numpy as np
import argparse
import imutils
import cv2
首先我們先導入各種我們需要的包,以確保程序的正確運行
· OpenCV:從此頁麵選擇適合您係統的安裝版本。
· scikit-image :這是通過pip安裝的, pip install -U scikit-image
· numpy :通過 pip install numpy。
· imutils :這是可以通過 pip 安裝: pip install --upgrade imutils。
接著我們來構建一個從MICR字體中提取字符的函數:
def extract_digits_and_symbols(image, charCnts, minW=5, minH=15):
charIter = charCnts.__iter__()
rois = []
locs = []
對於初學者,我們的功能需要4個參數:
· image:MICR E-13B字體圖像(代碼下載中提供)。
· charCnts:包含參考圖像中的字符輪廓的列表。
· minW :表示最小字符寬度的可選參數。默認值為5像素寬度。
· minH :最小字符高度。默認值為15像素。
接著我們初始化我們的charCnts列表的迭代器。列表對象本質上是“可迭代的”,意味著__iter__方法是由生成器完成的。
最後初始化空列表以保存我們的rois(感興趣的區域)和loc(ROI位置)。我們將在函數結尾的一個元組中返回這些列表。
我們開始循環,看一下迭代器的工作原理:
while True:
try:
c = next(charIter)
(cX, cY, cW, cH) = cv2.boundingRect(c)
roi = None
在我們的函數中,我們開始一個無限循環,我們的退出條件是當我們捕獲 StopIterator異常時。為了捕獲這個異常,我們需要設置try-catch塊。對於循環的每次迭代,我們通過調用next(charIter)來獲取下一個字符輪廓。從這個函數調用,我們可以提取矩形的(x,y)坐標和寬度/高度。接著我們初始化一個roi,我們將在短時間內存儲字符圖像。
接下來,我們將檢查我們的邊框寬度和高度的大小,並采取相應的措施:
if cW >= minW and cH >= minH:
roi = image[cY:cY + cH, cX:cX + cW]
rois.append(roi)
locs.append((cX, cY, cX + cW, cY + cH))
如果字符計數器的尺寸分別大於或等於最小寬度和高度,我們采取以下措施:
1. 使用我們的邊界矩形調用的坐標和width / height從圖像中提取roi。將roi添加到rois 。
3. 向locs附加一個元組。該元組由矩形的兩個角的(x,y)坐標組成。稍後我們將返回這個位置列表。
否則,我們假設我們正在使用MICRE-13B字符符號,需要應用更高級的一組處理操作:
else:
parts = [c, next(charIter), next(charIter)]
(sXA, sYA, sXB, sYB) = (np.inf, np.inf, -np.inf,-np.inf)
for p in parts:
(pX, pY, pW, pH) = cv2.boundingRect(p)
sXA = min(sXA, pX)
sYA = min(sYA, pY)
sXB = max(sXB, pX + pW)
sYB = max(sYB, pY + pH)
roi = image[sYA:sYB, sXA:sXB]
rois.append(roi)
locs.append((sXA, sYA, sXB, sYB))
if-else的else塊具有分析包含在MICR E-13B字體中的多個輪廓的特殊符號的邏輯。我們做的第一件事就是建立符號的部分。正如我們需要知道一個具有一個輪廓角色的邊框,我們需要知道包含三個輪廓的角色的邊框。為了實現這一點,通過sYB初始化四個邊界框參數sXA。
現在我們將循環遍曆表示一個字符/符號的部分列表。使用邊界矩形參數,我們比較和計算與先前值相關的最小值和最大值。這是我們首先通過 sYB 將sXA初始化為正/負無限值的原因。
現在我們已經找到了圍繞符號的框的坐標,我們從圖像中提取出roi,將roi附加到rois,並將框坐標元組附加到locs
我們函數的剩餘代碼塊處理我們的while循環退出條件和return語句。
Except StopIteration:
break
return (rois, locs)
如果在charIter(我們的iterator對象)上調用next來拋出一個StopIteration異常,那麼說明我們已經到達了最後一個輪廓。
現在我們已經準備好解析命令行參數並繼續執行腳本:
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
help="path to input image")
ap.add_argument("-r", "--reference", required=True,
help="path to reference MICR E-13B font")
args = vars(ap.parse_args())
在上麵的代碼中,我們建立了兩個必需的命令行參數:
· - image:我們的查詢圖像。
· - reference :我們的參考MICR E-13B字體圖像。
接下來,我們將為每個符號/字符創建“名稱”,並將其存儲在列表中。
charNames = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "T", "U", "A", "D"]
上麵的代碼很簡單,我們隻是在參考圖像中從左到右建立了我們遇到的符號的名稱。
注意:由於OpenCV不支持unicode中的繪圖字符,因此需要定義“T”表示transit,“U”表示“on-us”,“A”表示amount,“D”表示dash。
接下來,我們將參考圖像加載到內存中,並執行一些預處理:
ref = cv2.imread(args["reference"])
ref = cv2.cvtColor(ref, cv2.COLOR_BGR2GRAY)
ref = imutils.resize(ref, width=400)
ref = cv2.threshold(ref, 0, 255, cv2.THRESH_BINARY_INV |cv2.THRESH_OTSU)[1]
在上麵的代碼中,我們完成了四個任務:
1. 加載 圖像到存儲器中作為 參考。
2. 轉換為灰度級。
3. 調整 width=400 。
. 使用Otsu方法的二進製逆閾值。
這些簡單操作的結果可以在下圖中看到:
其餘的代碼遍曆分為兩部分。首先,我將向您展示一個邏輯和簡單的輪廓方法以及生成的圖像。
然後,我們將繼續使用更高級的方法,利用我們在腳本頂部寫的函數extract_digits_and_symbols 。
對於這兩個部分,我們將使用一些常見的數據,包括ref(參考圖像,我們剛剛預處理的)和refCnt(參考輪廓,我們即將提取)。
refCnts = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
refCnts = refCnts[0] if imutils.is_cv2() else refCnts[1]
refCnts = contours.sort_contours(refCnts, method="left-to-right")[0]
clone = np.dstack([ref.copy()] * 3)
# loop over the (sorted) contours
for c in refCnts:
(x, y, w, h) = cv2.boundingRect(c)
cv2.rectangle(clone, (x, y), (x + w, y + h), (0, 255, 0), 2)
# show the output of applying the simple contour method
cv2.imshow("Simple Method", clone)
cv2.waitKey(0)
要從參考圖像中提取輪廓,我們利用OpenCV的cv2 .findContours函數
接下來,我們要繪製圖像,所以我們將所有頻道複製到上稱為克隆的圖像。在顯示結果之前,簡單輪廓方法的最後一步是循環遍曆排序輪廓。在這個循環中,我們計算每個輪廓的邊界框,然後在其周圍繪製一個矩形。通過顯示圖像顯示結果並在此處暫停直到按下一個鍵參見圖4
你看到這種方法的問題嗎?問題是我們有22個邊框,而不是所需的14個邊界輪廓(每個角色一個)。顯然,這個問題可以通過更先進的方法來解決。
更先進的方法如下所示和描述
(refROIs, refLocs) = extract_digits_and_symbols(ref, refCnts,
minW=10, minH=20)
chars = {}
clone = np.dstack([ref.copy()] * 3)
for (name, roi, loc) in zip(charNames, refROIs, refLocs):
# draw a bounding box surrounding the character on the output
# image
(xA, yA, xB, yB) = loc
cv2.rectangle(clone, (xA, yA), (xB, yB), (0, 255, 0), 2)
# resize the ROI to a fixed size, then update the characters
# dictionary, mapping the character name to the ROI
roi = cv2.resize(roi, (36, 36))
chars[name] = roi
# display the character ROI to our screen
cv2.imshow("Char", roi)
cv2.waitKey(0)
# show the output of our better method
cv2.imshow("Better Method", clone)
cv2.waitKey(0)
我們初始化一個空字典,chars,它將保存每個符號的name和roi。我們通過用新的ref副本覆蓋clone圖像來執行此操作(以擺脫剛剛繪製的矩形)。
最後在for循環的主體中,首先我們為clone圖像中的每個角色繪製一個矩形 。
其次,我們調整roi到36像素和從詞典的roi和name 的鍵值對,更新我們的chars。最後一步(主要用於調試/開發目的)是在屏幕上顯示每個 roi,直到按下一個鍵。所產生的“更好的方法”圖像顯示在屏幕,直到按下一個鍵,並且結束了我們的腳本。
數字和符號提取結果:
現在我們已經編碼了我們的MICR E-13B數字和符號提取器,讓我們試一試。
從那裏執行以下腳本:
$ python bank_check_ocr.py --image example_check.png \
--reference micr_e13b_reference.png
總結
我們可以看到OCR的銀行支票比OCR的信用卡更難識別,主要是由於銀行支票符號由多個部分組成。我們不能假設我們的引用字體圖像中的每個輪廓映射到一個單獨的角色。
相反,我們需要插入額外的邏輯來檢查每個輪廓的尺寸,並確定我們正在檢查數字或符號。在我們找到一個符號的情況下,我們需要抓住下兩個輪廓來構建我們的邊界框(因為銀行檢查控製字符由三個不同的部分組成)。
希望上述的介紹能夠幫助到你!
本文由北郵@愛可可-愛生活老師推薦,@阿裏雲雲棲社區組織翻譯。
文章原標題《Bank check OCR with OpenCV and Python | PyImageSearch》
作者:Adrian Rosebrock審閱:
文章為簡譯,更為詳細的內容,請查看原文
最後更新:2017-08-13 22:40:50