使用 OpenCV 和 Python 識別信用卡號
在之前的博文中,我們學習了如何安裝 Tesseract 二進制檔案并將其用于 OCR, 然后我們學習了如何使用基本的影像處理技術來清理影像以提高 Tesseract OCR 的輸出,
但是,不應將 Tesseract 視為能夠獲得高精度的光學字符識別的通用、現成的解決方案, 在某些情況下,它會作業得很好——而在其他情況下,它會失敗得很慘, 這種用例的一個很好的例子是信用卡識別,給定輸入影像, 我們希望:
本地化四組四位數字,與信用卡上的十六位數字有關, 應用 OCR 識別信用卡上的十六位數字, 識別信用卡型別(即 Visa、MasterCard、American Express 等),
在這些情況下,Tesseract 庫無法正確識別數字(這可能是因為 Tesseract 沒有接受信用卡示例字體的培訓), 因此,我們需要為 OCR 信用卡設計我們自己的定制解決方案, 在今天的博客文章中,我將演示如何使用模板匹配作為 OCR 的一種形式來幫助我們創建一個解決方案來自動識別信用卡并從影像中提取相關的信用卡數字,
今天的博文分為三個部分, 在第一部分中,我們將討論 OCR-A 字體,這是一種專為輔助光學字符識別演算法而創建的字體, 然后我們將設計一種計算機視覺和影像處理演算法,它可以:
- 本地化信用卡上的四組四位數字,
- 提取這四個分組中的每一個,然后單獨分割 16 個數字中的每一個,
- 使用模板匹配和 OCR-A 字體識別 16 個信用卡數字中的每一個,
最后,我們將看一些將信用卡 OCR 演算法應用于實際影像的示例,
通過與 OpenCV 模板匹配的 OCR
在本節中,我們將使用 Python + OpenCV 實作我們的模板匹配演算法來自動識別信用卡數字,
為了實作這一點,我們需要應用許多影像處理操作,包括閾值、計算梯度幅度表示、形態學操作和輪廓提取,這些技術已在其他博客文章中用于檢測影像中的條形碼并識別護照影像中的機器可讀區域,
由于將應用許多影像處理操作來幫助我們檢測和提取信用卡數字,因此我在輸入影像通過我們的影像處理管道時包含了許多中間螢屏截圖,
這些額外的螢屏截圖將讓您更深入地了解我們如何能夠將基本影像處理技術鏈接在一起以構建計算機視覺專案的解決方案, 讓我們開始吧,
打開一個新檔案,命名為 ocr_template_match.py ,我們將開始作業:
# import the necessary packages
from imutils import contours
import numpy as np
import argparse
import imutils
import cv2
要安裝/升級 imutils ,只需使用 pip :
pip install --upgrade imutils
注意:如果您使用 Python 虛擬環境(就像我所有的 OpenCV 安裝教程一樣),請確保首先使用 workon 命令訪問您的虛擬環境,然后安裝/升級 imutils ,
現在我們已經安裝并匯入了包,我們可以決議我們的命令列引數:
# construct the argument parser and parse the arguments
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 OCR-A image")
args = vars(ap.parse_args())
建立了一個引數決議器,添加兩個引數,然后決議它們,將它們存盤為變數 args , 兩個必需的命令列引數是:
–image :要進行 OCR 處理的影像的路徑,
–reference :參考 OCR-A 影像的路徑, 該影像包含 OCR-A 字體中的數字 0-9,從而允許我們稍后在管道中執行模板匹配,
接下來讓我們定義信用卡型別:
# define a dictionary that maps the first digit of a credit card
# number to the credit card type
FIRST_NUMBER = {
"3": "American Express",
"4": "Visa",
"5": "MasterCard",
"6": "Discover Card"
}
信用卡型別,例如美國運通、Visa 等,可以通過檢查 16 位信用卡號中的第一位數字來識別,我們定義了一個字典 FIRST_NUMBER ,它將第一個數字映射到相應的信用卡型別, 讓我們通過加載參考 OCR-A 影像來啟動我們的影像處理管道:
# load the reference OCR-A image from disk, convert it to grayscale,
# and threshold it, such that the digits appear as *white* on a
# *black* background
# and invert it, such that the digits appear as *white* on a *black*
ref = cv2.imread(args["reference"])
ref = cv2.cvtColor(ref, cv2.COLOR_BGR2GRAY)
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]
首先,我們加載參考 OCR-A 影像,然后將其轉換為灰度和閾值 + 反轉, 在這些操作中的每一個中,我們存盤或覆寫 ref ,我們的參考影像,

現在讓我們在 OCR-A 字體影像上定位輪廓:
# find contours in the OCR-A image (i.e,. the outlines of the digits)
# sort them from left to right, and initialize a dictionary to map
# digit name to the ROI
refCnts = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
refCnts = imutils.grab_contours(refCnts)
refCnts = contours.sort_contours(refCnts, method="left-to-right")[0]
digits = {}
找到了參考影像中的輪廓, 然后,由于 OpenCV 2.4、3 和 4 版本如何不同地存盤回傳的輪廓資訊,我們檢查版本并對 refCnts 進行適當更改, 接下來,我們從左到右對輪廓進行排序,并初始化一個字典,digits,它將數字名稱映射到感興趣的區域,
此時,我們應該遍歷輪廓,提取ROI并將其與其對應的數字相關聯:
# loop over the OCR-A reference contours
for (i, c) in enumerate(refCnts):
# compute the bounding box for the digit, extract it, and resize
# it to a fixed size
(x, y, w, h) = cv2.boundingRect(c)
roi = ref[y:y + h, x:x + w]
roi = cv2.resize(roi, (57, 88))
# update the digits dictionary, mapping the digit name to the ROI
digits[i] = roi
遍歷參考影像輪廓,
在回圈中, i 保存數字名稱/編號, c 保存輪廓, 我們圍繞每個輪廓 c 計算一個邊界框,用于存盤矩形的 (x, y) 坐標和寬度/高度,使用邊界矩形引數從 ref(參考影像)中提取 roi, 該 ROI 包含數字,
我們將每個 ROI 大小調整為 57×88 像素的固定大小, 我們需要確保每個數字都調整為固定大小,以便在本教程后面的數字識別中應用模板匹配,
我們將每個數字 0-9(字典鍵)與每個 roi 影像(字典值)相關聯,
在這一點上,我們完成了從參考影像中提取數字并將它們與相應的數字名稱相關聯的作業,
我們的下一個目標是隔離輸入 --image 中的 16 位信用卡號, 我們需要先找到并隔離數字,然后才能啟動模板匹配以識別每個數字, 這些影像處理步驟非常有趣且有見地,特別是如果您之前從未開發過影像處理管道,請務必密切關注,
讓我們繼續初始化幾個結構化內核:
# initialize a rectangular (wider than it is tall) and square
# structuring kernel
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
您可以將內核視為我們在影像上滑動的小矩陣,以執行(卷積)操作,例如模糊、銳化、邊緣檢測或其他影像處理操作,
構造了兩個這樣的內核——一個矩形和一個正方形, 我們將使用矩形作為 Top-hat 形態算子,使用方形作為閉運算, 我們很快就會看到這些, 現在讓我們準備要進行 OCR 的影像:
# load the input image, resize it, and convert it to grayscale
image = cv2.imread(args["image"])
image = imutils.resize(image, width=300)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
加載了包含信用卡照片的命令列引數影像, 然后,我們將其調整為 width=300 ,保持縱橫比,然后將其轉換為灰度, 讓我們看看我們的輸入影像:

接下來是我們的調整大小和灰度操作:

現在我們的影像已經灰度化并且大小一致,讓我們進行形態學操作:
# apply a tophat (whitehat) morphological operator to find light
# regions against a dark background (i.e., the credit card numbers)
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)
使用我們的 rectKernel 和我們的灰度影像,我們執行 Top-hat 形態學操作,將結果存盤為 tophat,
Top-hat操作在深色背景(即信用卡號)下顯示淺色區域,如下圖所示:

給定我們的高帽影像,讓我們計算沿 x 方向的梯度:
# compute the Scharr gradient of the tophat image, then scale
# the rest back into the range [0, 255]
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0,
ksize=-1)
gradX = np.absolute(gradX)
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
gradX = (255 * ((gradX - minVal) / (maxVal - minVal)))
gradX = gradX.astype("uint8")
我們努力隔離數字的下一步是計算 x 方向上高帽影像的 Scharr 梯度,完成計算,將結果存盤為 gradX ,
在計算 gradX 陣列中每個元素的絕對值后,我們采取一些步驟將值縮放到 [0-255] 范圍內(因為影像當前是浮點資料型別), 為此,我們計算 gradX 的 minVal 和 maxVal,然后計算第 73 行所示的縮放方程(即最小/最大歸一化), 最后一步是將 gradX 轉換為范圍為 [0-255] 的 uint8, 結果如下圖所示:

讓我們繼續改進信用卡數字查找演算法:
# apply a closing operation using the rectangular kernel to help
# cloes gaps in between credit card number digits, then apply
# Otsu's thresholding method to binarize the image
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel)
thresh = cv2.threshold(gradX, 0, 255,
cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# apply a second closing operation to the binary image, again
# to help close gaps between credit card number regions
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)
為了縮小差距,我們執行了一個關閉操作,請注意,我們再次使用了 rectKernel, 隨后我們對 gradX 影像執行 Otsu 和二進制閾值,然后是另一個關閉操作, 這些步驟的結果如下所示:

接下來讓我們找到輪廓并初始化數字分組位置串列,
# find contours in the thresholded image, then initialize the
# list of digit locations
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
locs = []
我們找到了輪廓并將它們存盤在一個串列 cnts 中 , 然后,我們初始化一個串列來保存數字組位置,
現在讓我們遍歷輪廓,同時根據每個輪廓的縱橫比進行過濾,允許我們從信用卡的其他不相關區域中修剪數字組位置:
# loop over the contours
for (i, c) in enumerate(cnts):
# compute the bounding box of the contour, then use the
# bounding box coordinates to derive the aspect ratio
(x, y, w, h) = cv2.boundingRect(c)
ar = w / float(h)
# since credit cards used a fixed size fonts with 4 groups
# of 4 digits, we can prune potential contours based on the
# aspect ratio
if ar > 2.5 and ar < 4.0:
# contours can further be pruned on minimum/maximum width
# and height
if (w > 40 and w < 55) and (h > 10 and h < 20):
# append the bounding box region of the digits group
# to our locations list
locs.append((x, y, w, h))
我們以與參考影像相同的方式回圈遍歷輪廓, 在計算每個輪廓的邊界矩形 c之后,我們通過將寬度除以高度來計算縱橫比 ar , 使用縱橫比,我們分析每個輪廓的形狀, 如果 ar 介于 2.5 和 4.0 之間(寬大于高),以及 40 到 55 像素之間的 w 和 10 到 20 像素之間的 h,我們將一個方便的元組中的邊界矩形引數附加到 locs,
下圖顯示了我們找到的分組——出于演示目的,我讓 OpenCV 在每個組周圍繪制了一個邊界框:

接下來,我們將從左到右對分組進行排序并初始化信用卡數字串列:
# sort the digit locations from left-to-right, then initialize the
# list of classified digits
locs = sorted(locs, key=lambda x:x[0])
output = []
我們根據 x 值對 locs 進行排序,因此它們將從左到右排序, 我們初始化一個串列 output ,它將保存影像的信用卡號, 現在我們知道每組四位數字的位置,讓我們回圈遍歷四個排序的組并確定其中的數字,
這個回圈相當長,分為三個代碼塊——這是第一個塊:
# loop over the 4 groupings of 4 digits
for (i, (gX, gY, gW, gH)) in enumerate(locs):
# initialize the list of group digits
groupOutput = []
# extract the group ROI of 4 digits from the grayscale image,
# then apply thresholding to segment the digits from the
# background of the credit card
group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
group = cv2.threshold(group, 0, 255,
cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# detect the contours of each individual digit in the group,
# then sort the digit contours from left to right
digitCnts = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
digitCnts = imutils.grab_contours(digitCnts)
digitCnts = contours.sort_contours(digitCnts,
method="left-to-right")[0]
在此回圈的第一個塊中,我們提取并在每側填充組 5 個像素,應用閾值處理,并查找和排序輪廓, 詳情請務必參考代碼, 下面顯示的是已提取的單個組:

讓我們用嵌套回圈繼續回圈以進行模板匹配和相似度得分提取:
# loop over the digit contours
for c in digitCnts:
# compute the bounding box of the individual digit, extract
# the digit, and resize it to have the same fixed size as
# the reference OCR-A images
(x, y, w, h) = cv2.boundingRect(c)
roi = group[y:y + h, x:x + w]
roi = cv2.resize(roi, (57, 88))
# initialize a list of template matching scores
scores = []
# loop over the reference digit name and digit ROI
for (digit, digitROI) in digits.items():
# apply correlation-based template matching, take the
# score, and update the scores list
result = cv2.matchTemplate(roi, digitROI,
cv2.TM_CCOEFF)
(_, score, _, _) = cv2.minMaxLoc(result)
scores.append(score)
# the classification for the digit ROI will be the reference
# digit name with the *largest* template matching score
groupOutput.append(str(np.argmax(scores)))
使用 cv2.boundingRect 我們獲得提取包含每個數字的 ROI 所需的引數,為了使模板匹配以某種程度的精度作業,我們將 roi 的大小調整為與我們在第 144 行上的參考 OCR-A 字體數字影像(57×88 像素)相同的大小,
我們初始化了一個分數串列,將其視為我們的置信度分數——它越高,它就越有可能是正確的模板,
現在,讓我們通過每個參考數字回圈(第三個嵌套回圈)并執行模板匹配,這是為這個腳本完成繁重作業的地方,
OpenCV 有一個名為 cv2.matchTemplate 的方便函式,您可以在其中提供兩個影像:一個是模板,另一個是輸入影像,將 cv2.matchTemplate 應用于這兩個影像的目的是確定它們的相似程度,
在這種情況下,我們提供參考 digitROI 影像和包含候選數字的信用卡的 roi,使用這兩個影像,我們呼叫模板匹配函式并存盤結果, 接下來,我們從結果中提取分數并將其附加到我們的分數串列中,這樣就完成了最內部的回圈,
使用分數(每個數字 0-9 一個),我們取最大分數——最大分數應該是我們正確識別的數字,我們找到得分最高的數字,通過 np.argmax 獲取特定索引,該索引的整數名稱表示基于與每個模板的比較最可能的數字(再次記住,索引已經預先排序為 0-9),
最后,讓我們在每組周圍畫一個矩形,并以紅色文本查看影像上的信用卡號:
# draw the digit classifications around the group
cv2.rectangle(image, (gX - 5, gY - 5),
(gX + gW + 5, gY + gH + 5), (0, 0, 255), 2)
cv2.putText(image, "".join(groupOutput), (gX, gY - 15),
cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
# update the output digits list
output.extend(groupOutput)
對于此回圈的第三個也是最后一個塊,我們在組周圍繪制一個 5 像素的填充矩形,然后在螢屏上繪制文本,
最后一步是將數字附加到輸出串列中, Pythonic 方法是使用擴展函式將可迭代物件(在本例中為串列)的每個元素附加到串列的末尾,
要查看腳本的執行情況,讓我們將結果輸出到終端并在螢屏上顯示我們的影像,
# display the output credit card information to the screen
print("Credit Card Type: {}".format(FIRST_NUMBER[output[0]]))
print("Credit Card #: {}".format("".join(output)))
cv2.imshow("Image", image)
cv2.waitKey(0)
將信用卡型別列印到控制臺,然后在隨后的第 173 行列印信用卡號,
在最后幾行,我們在螢屏上顯示影像并等待任何鍵被按下,然后退出腳本第 174 和 175 行,
花點時間祝賀自己——你做到了, 回顧一下(在高層次上),這個腳本:
- 將信用卡型別存盤在字典中,
- 獲取參考影像并提取數字,
- 將數字模板存盤在字典中,
- 本地化四個信用卡號碼組,每個組包含四位數字(總共 16 位數字),
- 提取要“匹配”的數字,
- 對每個數字執行模板匹配,將每個單獨的 ROI 與每個數字模板 0-9 進行比較,同時存盤每個嘗試匹配的分數,
- 找到每個候選數字的最高分,并構建一個名為 output 的串列,其中包含信用卡號,
- 將信用卡號和信用卡型別輸出到我們的終端,并將輸出影像顯示到我們的螢屏上,
現在是時候查看運行中的腳本并檢查我們的結果了,
信用卡 OCR 結果
現在我們已經對信用卡 OCR 系統進行了編碼,讓我們試一試,
我們顯然不能在這個例子中使用真實的信用卡號,所以我使用谷歌收集了一些信用卡示例影像,
這些信用卡顯然是假的,僅用于演示目的, 但是,您可以應用此博客文章中的相同技術來識別實際信用卡上的數字,
要查看我們的信用卡 OCR 系統的運行情況,請打開一個終端并執行以下命令:
$ python ocr_template_match.py --reference ocr_a_reference.png \
--image images/credit_card_05.png
Credit Card Type: MasterCard
Credit Card #: 5476767898765432
我們的第一個結果影像,100% 正確:

請注意我們如何能夠正確地將信用卡標記為萬事達卡,只需檢查信用卡號中的第一位數字即可, 讓我們嘗試第二張圖片,這次是一張簽證:
$ python ocr_template_match.py --reference ocr_a_reference.png \
--image images/credit_card_01.png
Credit Card Type: Visa
Credit Card #: 4000123456789010

再一次,我們能夠使用模板匹配正確地對信用卡進行 OCR,
$ python ocr_template_match.py --reference ocr_a_reference.png \
--image images/credit_card_02.png
Credit Card Type: Visa
Credit Card #: 4020340002345678

總結
在本教程中,我們學習了如何通過 OpenCV 和 Python 使用模板匹配來執行光學字符識別 (OCR),
具體來說,我們應用我們的模板匹配 OCR 方法來識別信用卡型別以及 16 位信用卡數字,
為了實作這一點,我們將影像處理管道分為 4 個步驟:
- 通過各種影像處理技術檢測信用卡上的四組四個數字,包括形態學操作、閾值和輪廓提取,
- 從四個分組中提取每個單獨的數字,導致需要分類的 16 個數字,
- 通過將每個數字與 OCR-A 字體進行比較,將模板匹配應用于每個數字以獲得我們的數字分類,
- 檢查信用卡號的第一位數字以確定發行公司,
在評估了我們的信用卡 OCR 系統后,我們發現它是 100% 準確的,前提是發行信用卡公司使用 OCR-A 字體作為數字,
要擴展此應用程式,您可能希望在野外收集信用卡的真實影像,并可能訓練機器學習模型(通過標準特征提取或訓練或卷積神經網路)以進一步提高該系統的準確性,
希望您喜歡這篇關于 OCR 的博文,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/355428.html
標籤:其他
上一篇:DASH標準&ABR演算法介紹
下一篇:OpenCV 影像模糊原理
