目錄
一、項目原理與核心技術
二、環境準備與工具包導入
1. 環境依賴
2. 工具包導入
三、自定義工具類 myutils.py 實現
四、主程序核心流程(銀行卡識別.py)
1. 命令行參數設置
2. 銀行卡類型映射
3. 輔助函數:圖像展示
五、步驟 1:模板圖像預處理與數字提取
1. 讀取模板并預處理
2. 檢測模板數字輪廓并排序
六、步驟 2:銀行卡圖像預處理與卡號識別
1. 讀取銀行卡圖像并預處理
2. 數字區域定位(閉運算 + 二值化 + 輪廓檢測)
3. 篩選有效數字區域
4. 模板匹配識別卡號
七、結果展示與輸出
1. 打印識別結果
2. 顯示最終結果圖
八、運行示例與效果驗證
1. 準備測試文件
2. 運行命令
3. 預期結果
九、常見問題與優化建議
1. 識別準確率低的原因與解決方法
2. 功能擴展建議
在金融場景中,自動識別銀行卡號能極大提升業務處理效率,避免人工錄入的誤差。本文將詳細介紹如何使用 Python+OpenCV 構建一套智能卡號識別系統,通過模板匹配的方式,從銀行卡圖片中精準提取卡號并判斷卡類型,適合 OpenCV 初學者和計算機視覺入門學習者參考。
一、項目原理與核心技術
本系統核心采用模板匹配思想,通過預先制作的數字模板(OCR-A 字體,銀行卡常用字體),與待識別銀行卡圖片中的數字區域進行匹配,計算相似度后確定對應數字。整體流程分為兩大模塊:模板圖像預處理與數字提取、銀行卡圖像預處理與卡號識別。
涉及的關鍵技術如下:
- 圖像預處理:灰度化、二值化、形態學操作(頂帽、閉運算),用于突出數字區域、抑制背景干擾。
- 輪廓檢測與排序:通過
cv2.findContours
檢測數字輪廓,并按 “從左到右” 順序排序,確保數字識別順序正確。 - 模板匹配:使用
cv2.matchTemplate
計算待識別數字與模板的相似度,選擇得分最高的模板作為識別結果。
二、環境準備與工具包導入
1. 環境依賴
- Python 3.6+
- OpenCV-Python(
pip install opencv-python
) - NumPy(
pip install numpy
)
2. 工具包導入
代碼中需導入 OpenCV(核心圖像處理)、NumPy(數值計算)、argparse(命令行參數解析),以及自定義工具類myutils
(封裝輪廓排序和圖像縮放功能)。
import numpy as np
import argparse
import cv2
import myutils # 自定義工具類,下文會提供完整代碼
三、自定義工具類 myutils.py 實現
myutils.py
封裝了兩個核心函數:輪廓排序和圖像縮放,是整個系統的基礎工具,代碼如下:
import cv2def sort_contours(cnts, method='left-to-right'):"""對輪廓進行排序(支持4種排序方式):param cnts: 輸入的輪廓列表:param method: 排序方式,默認左到右(left-to-right),可選:right-to-left、top-to-bottom、bottom-to-top:return: 排序后的輪廓列表、對應的邊界框列表"""reverse = False # 是否逆序,默認正序i = 0 # 排序依據:0=x坐標(左右排序),1=y坐標(上下排序)# 若為右到左或下到上,設置逆序if method in ['right-to-left', 'bottom-to-top']:reverse = True# 若為上下排序,按y坐標排序if method in ['top-to-bottom', 'bottom-to-top']:i = 1# 計算每個輪廓的外接矩形,按指定維度排序bounding_boxes = [cv2.boundingRect(c) for c in cnts]# 同時對輪廓和邊界框排序(確保一一對應)cnts, bounding_boxes = zip(*sorted(zip(cnts, bounding_boxes),key=lambda item: item[1][i], reverse=reverse))return list(cnts), list(bounding_boxes) # 返回列表格式,兼容后續操作def resize(image, width=None, height=None, inter=cv2.INTER_AREA):"""圖像縮放(保持寬高比,避免拉伸變形):param image: 輸入圖像:param width: 目標寬度(若為None,按高度縮放):param height: 目標高度(若為None,按寬度縮放):param inter: 插值方式,默認INTER_AREA(適合縮小圖像,畫質更清晰):return: 縮放后的圖像"""dim = None # 目標尺寸(寬,高)h, w = image.shape[:2] # 獲取原始圖像高度和寬度# 若寬高均為None,返回原始圖像if width is None and height is None:return image# 按高度縮放(寬度為None時)if width is None:r = height / float(h) # 縮放比例dim = (int(w * r), height) # 計算目標尺寸# 按寬度縮放(默認邏輯)else:r = width / float(w)dim = (width, int(h * r))# 執行縮放resized = cv2.resize(image, dim, interpolation=inter)return resized
四、主程序核心流程(銀行卡識別.py)
主程序分為模板處理和銀行卡處理兩大步驟,下面逐模塊詳細講解。
1. 命令行參數設置
通過argparse
設置兩個必填參數:待識別銀行卡圖片路徑(-i/--image
)和數字模板圖片路徑(-t/--template
),方便運行時靈活指定輸入文件。
# 創建參數解析器
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True, help="path to input image(銀行卡圖片路徑)")
ap.add_argument("-t", "--template", required=True, help="path to template OCR-A image(數字模板路徑)")
args = vars(ap.parse_args()) # 解析參數,轉為字典格式
2. 銀行卡類型映射
根據銀行卡號第一位數字,映射對應的卡類型(符合國際標準):
FIRST_NUMBER = {"3": "American Express(美國運通卡)","4": "Visa(維薩卡)","5": "MasterCard(萬事達卡)","6": "Discover Card(發現卡)"
}
3. 輔助函數:圖像展示
封裝cv_show
函數,方便在處理過程中查看每一步的圖像結果:
def cv_show(name, img):"""顯示圖像(按任意鍵關閉窗口):param name: 窗口名稱:param img: 待顯示圖像"""cv2.imshow(name, img)cv2.waitKey(0) # 等待按鍵(0表示無限等待)cv2.destroyWindow(name) # 關閉窗口
五、步驟 1:模板圖像預處理與數字提取
模板圖片(如kahao.png
)包含 0-9 的 OCR-A 字體數字,需先提取每個數字的輪廓,作為后續匹配的 “模板庫”。
1. 讀取模板并預處理
- 讀取模板圖像 → 轉為灰度圖 → 二值化(
THRESH_BINARY_INV
:黑底白字,突出數字)。
# 讀取模板圖像
template_img = cv2.imread(args["template"])
cv_show("模板原始圖", template_img)# 1. 轉為灰度圖
template_gray = cv2.cvtColor(template_img, cv2.COLOR_BGR2GRAY)
cv_show("模板灰度圖", template_gray)# 2. 二值化(黑底白字,便于輪廓檢測)
# THRESH_BINARY_INV:反轉二值化(原白色區域變黑色,原黑色變白色)
template_binary = cv2.threshold(template_gray, 10, 255, cv2.THRESH_BINARY_INV)[1]
cv_show("模板二值化圖", template_binary)
2. 檢測模板數字輪廓并排序
- 用
cv2.findContours
檢測數字外輪廓 → 按 “從左到右” 排序(確保數字順序為 0-9)→ 提取每個數字的 ROI(感興趣區域)并縮放為統一尺寸(57×88),存入digits
字典(鍵:0-9,值:對應數字的 ROI 模板)。
# 檢測模板中的輪廓(只檢測外輪廓,簡化輪廓坐標)
_, template_cnts, _ = cv2.findContours(template_binary.copy(), cv2.RETR_EXTERNAL, # 只檢測外輪廓cv2.CHAIN_APPROX_SIMPLE # 簡化輪廓(只保留頂點)
)# 在模板圖上繪制輪廓(紅色,線寬3),驗證輪廓檢測結果
cv2.drawContours(template_img, template_cnts, -1, (0, 0, 255), 3)
cv_show("模板輪廓圖", template_img)# 按“從左到右”排序輪廓(確保數字順序為0-9)
sorted_cnts, _ = myutils.sort_contours(template_cnts, method="left-to-right")# 提取每個數字的ROI,構建模板庫
digits = {}
for i, cnt in enumerate(sorted_cnts):# 計算輪廓的外接矩形(x:左上角x坐標,y:左上角y坐標,w:寬度,h:高度)x, y, w, h = cv2.boundingRect(cnt)# 提取ROI(數字區域)digit_roi = template_binary[y:y+h, x:x+w]# 縮放ROI為統一尺寸(57×88),便于后續匹配digit_roi = cv2.resize(digit_roi, (57, 88))cv_show(f"數字{i}模板", digit_roi)# 存入字典(鍵:數字0-9,值:對應ROI模板)digits[i] = digit_roiprint("模板庫構建完成,包含數字0-9的ROI模板")
六、步驟 2:銀行卡圖像預處理與卡號識別
銀行卡圖片(如card1.png
、card2.png
)需經過多步預處理,突出數字區域,再通過模板匹配識別卡號。
1. 讀取銀行卡圖像并預處理
- 讀取圖像 → 縮放(統一寬度為 300,避免尺寸差異影響后續處理)→ 轉為灰度圖 → 頂帽操作(突出亮細節,抑制暗背景,增強數字與背景的對比度)。
# 讀取銀行卡圖像
card_img = cv2.imread(args["image"])
cv_show("銀行卡原始圖", card_img)# 縮放圖像(寬度300,保持寬高比)
card_img_resized = myutils.resize(card_img, width=300)
cv_show("銀行卡縮放圖", card_img_resized)# 轉為灰度圖
card_gray = cv2.cvtColor(card_img_resized, cv2.COLOR_BGR2GRAY)
cv_show("銀行卡灰度圖", card_gray)# 頂帽操作(突出亮細節,消除亮背景干擾)
# 定義矩形卷積核(9×3,適合橫向數字區域)
rect_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
# 頂帽 = 原始圖像 - 開運算結果(開運算:先腐蝕后膨脹,用于去除小亮點)
card_tophat = cv2.morphologyEx(card_gray, cv2.MORPH_TOPHAT, rect_kernel)
cv_show("銀行卡頂帽圖(突出數字)", card_tophat)
2. 數字區域定位(閉運算 + 二值化 + 輪廓檢測)
- 閉運算(先膨脹后腐蝕):將數字區域連接成一個整體,便于定位 → 二值化(
THRESH_OTSU
自動找閾值,適合雙峰圖像)→ 再次閉運算(填補數字內部的小空隙)→ 輪廓檢測(找到所有可能的數字區域)。
# 1. 第一次閉運算:連接數字區域(橫向)
card_close1 = cv2.morphologyEx(card_tophat, cv2.MORPH_CLOSE, rect_kernel)
cv_show("第一次閉運算(連接數字)", card_close1)# 2. 二值化(OTSU自動閾值,適合數字與背景對比明顯的圖像)
_, card_thresh = cv2.threshold(card_close1, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU # 二值化+自動閾值
)
cv_show("銀行卡二值化圖", card_thresh)# 3. 第二次閉運算:填補數字內部小空隙(使用5×5正方形核)
sq_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
card_close2 = cv2.morphologyEx(card_thresh, cv2.MORPH_CLOSE, sq_kernel)
cv_show("第二次閉運算(填補空隙)", card_close2)# 4. 檢測數字區域輪廓
_, card_cnts, _ = cv2.findContours(card_close2.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)# 在銀行卡圖上繪制所有輪廓(紅色,線寬3),驗證輪廓檢測結果
card_cnts_img = card_img_resized.copy()
cv2.drawContours(card_cnts_img, card_cnts, -1, (0, 0, 255), 3)
cv_show("銀行卡輪廓檢測圖", card_cnts_img)
3. 篩選有效數字區域
通過寬高比(AR)?和尺寸范圍篩選出真正的數字區域(排除無關輪廓,如卡面 logo、文字等):
# 存儲有效數字區域的坐標(x, y, w, h)
valid_locs = []for cnt in card_cnts:x, y, w, h = cv2.boundingRect(cnt)ar = w / float(h) # 寬高比(數字區域通常為橫向,AR>2.5)# 篩選條件:寬高比2.5~4.0,寬度40~55,高度10~20(需根據實際圖片調整)if 2.5 < ar < 4.0 and 40 < w < 55 and 10 < h < 20:valid_locs.append((x, y, w, h))# 按“從左到右”排序有效區域(確保卡號順序正確)
valid_locs = sorted(valid_locs, key=lambda x: x[0])
print(f"找到{len(valid_locs)}個數字區域")
4. 模板匹配識別卡號
遍歷每個有效數字區域,提取 ROI → 預處理(二值化)→ 檢測單個數字輪廓 → 模板匹配(計算與每個模板的相似度)→ 選擇得分最高的模板作為識別結果,最終拼接成完整卡號。
# 存儲最終識別結果
card_number = []# 遍歷每個有效數字區域
for i, (gx, gy, gw, gh) in enumerate(valid_locs):# 提取數字區域ROI(適當擴大邊界5個像素,避免裁剪到數字)group_roi = card_gray[gy-5:gy+gh+5, gx-5:gx+gw+5]cv_show(f"數字區域{i}", group_roi)# 預處理:二值化(突出單個數字)_, group_binary = cv2.threshold(group_roi, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)cv_show(f"數字區域{i}二值化", group_binary)# 檢測單個數字輪廓(每個數字區域包含4個數字,如“4000”)_, digit_cnts, _ = cv2.findContours(group_binary.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 按“從左到右”排序單個數字輪廓sorted_digit_cnts, _ = myutils.sort_contours(digit_cnts, method="left-to-right")# 識別當前區域的4個數字group_result = []for cnt in sorted_digit_cnts:# 提取單個數字的ROI并縮放為模板尺寸(57×88)x, y, w, h = cv2.boundingRect(cnt)digit_roi = group_binary[y:y+h, x:x+w]digit_roi = cv2.resize(digit_roi, (57, 88))cv_show("單個數字ROI", digit_roi)# 模板匹配:計算與每個模板的相似度(使用TM_CCOEFF方法,值越大相似度越高)match_scores = []for digit, digit_template in digits.items():# 模板匹配result = cv2.matchTemplate(digit_roi, digit_template, cv2.TM_CCOEFF)_, score, _, _ = cv2.minMaxLoc(result) # 獲取最大相似度得分match_scores.append(score)# 選擇得分最高的模板對應的數字(np.argmax返回最大得分的索引,即數字0-9)best_digit = str(np.argmax(match_scores))group_result.append(best_digit)# 在銀行卡圖上繪制矩形框和識別結果(紅色框,紅色文字)cv2.rectangle(card_img_resized, (gx-5, gy-5), # 左上角坐標(gx+gw+5, gy+gh+5), # 右下角坐標(0, 0, 255), 1 # 紅色,線寬1)# 繪制文字(位置在矩形框上方,字體大小0.65,紅色,線寬2)cv2.putText(card_img_resized, "".join(group_result), # 識別的數字(如“4000”)(gx, gy-15), # 文字左上角坐標cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)# 將當前區域的識別結果添加到總卡號中card_number.extend(group_result)
七、結果展示與輸出
1. 打印識別結果
輸出銀行卡類型(通過卡號第一位數字映射)和完整卡號:
# 打印銀行卡類型和卡號
card_type = FIRST_NUMBER.get(card_number[0], "Unknown Card Type(未知卡類型)")
print(f"\n銀行卡類型:{card_type}")
print(f"銀行卡號:{' '.join(card_number[i:i+4] for i in range(0, len(card_number), 4))}")
2. 顯示最終結果圖
cv_show("銀行卡號識別結果", card_img_resized)
cv2.destroyAllWindows() # 關閉所有窗口
八、運行示例與效果驗證
1. 準備測試文件
- 模板圖片:
kahao.png
(包含 0-9 的 OCR-A 字體數字)。
- 銀行卡圖片:
card1.png
(卡號 4000 1234 5678 9010,)、card2.png
(萬事達卡,卡號 5412 7512 3456 7890)等。
2. 運行命令
在終端中執行以下命令(需替換為實際文件路徑):
python 銀行卡識別.py -i card1.png -t kahao.png
3. 預期結果
- 依次彈出每一步的圖像處理窗口(模板輪廓、銀行卡頂帽圖、數字區域 ROI 等)。
- 最終輸出:
銀行卡類型:Visa(維薩卡) 銀行卡號:4000 1234 5678 9010
- 顯示標注了卡號的銀行卡圖片,紅色矩形框包圍數字區域,上方顯示識別的數字。
九、常見問題與優化建議
1. 識別準確率低的原因與解決方法
- 問題 1:數字區域未被正確檢測(輪廓篩選條件不合適)。
解決:調整valid_locs
篩選時的寬高比(AR)和尺寸范圍(如寬度 40~60,高度 8~22),根據實際銀行卡圖片尺寸優化。 - 問題 2:模板匹配得分低(數字 ROI 與模板尺寸不匹配)。
解決:確保數字 ROI 縮放后的尺寸與模板尺寸一致(本文為 57×88),若模板字體不同,需重新制作 OCR-A 字體模板。 - 問題 3:圖像噪聲干擾(如卡面反光、污漬)。
解決:增加高斯模糊(cv2.GaussianBlur
)預處理步驟,減少噪聲影響,例如:card_gray = cv2.GaussianBlur(card_gray, (3, 3), 0) # 在灰度圖后添加高斯模糊
2. 功能擴展建議
- 支持更多卡類型:在
FIRST_NUMBER
字典中添加更多卡類型映射(如 “2” 對應 MasterCard Maestro)。 - 批量識別:修改代碼支持批量讀取文件夾中的銀行卡圖片,自動輸出所有識別結果到文檔(如 Excel、TXT)。
- 實時識別:結合攝像頭(
cv2.VideoCapture
),實現實時拍攝銀行卡并識別卡號。