【OpenCV】 ⚠️實戰⚠️ 銀行卡卡号讀取
- 概述
- 預處理
- 代碼
- 模闆預處理
- 銀行卡預處理
- 計算輪廓
- 代碼
- 模闆輪廓
- 銀行卡輪廓
- 其他程式
- 主函數
- 代碼
- 數字分割
- 最終結果
概述
今天帶大家使用我們之前學會的知識來實作銀行卡卡号讀取. 代碼分為四個部分: 主函程式, 預處理, 計算輪廓, 其他程式.
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiYWan5yM4kTM4QDZjVzNygTZiZTYyYzX0ETNzITMzEzLcZDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.gif)
預處理
通過灰階轉換, 二值化, 膨脹, 腐蝕, 邊緣檢測等方法, 去除圖檔噪聲, 突出我們想要得到的結果.
代碼
import numpy as np
import cv2
from matplotlib import pyplot as plt
from my_functions import resize
def read_template(image_path, visualize=False):
"""
讀取模闆
:param image_path: 圖檔路徑
:param visualize: 可視化, 預設為False
:return: 傳回模闆, 二值化後的模闆
"""
# 讀取模闆
template = cv2.imread(image_path)
# 轉換成灰階圖
template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
# 二值化
ret, template_thresh = cv2.threshold(template_gray, 10, 255, cv2.THRESH_BINARY_INV)
# 如果展示為真
if visualize:
"""圖檔展示"""
# 繪制子圖
f, ax = plt.subplots(3, 1, figsize=(10, 8))
ax[0].imshow(template)
ax[1].imshow(template_gray, "gray")
ax[2].imshow(template_thresh, "gray")
# 标題
ax[0].set_title("template")
ax[1].set_title("template gray")
ax[2].set_title("template binary inverse")
plt.show()
# 傳回
return template, template_thresh
def read_image(image_path, visualize=False):
"""
讀取銀行卡圖檔
:param image_path: 圖檔路徑
:param visualize: 可視化, 預設為False
:return: 傳回裁剪後的圖檔, 灰階圖, 處理後的圖
"""
# 初始化卷積核
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
# 讀取圖檔
image = cv2.imread(image_path)
# 更改尺寸
image_resize = resize(image, width=300)
# 轉換成灰階圖
image_gray = cv2.cvtColor(image_resize, cv2.COLOR_BGR2GRAY)
# 禮帽操作, 突出明亮區域
tophat = cv2.morphologyEx(image_gray, cv2.MORPH_TOPHAT, rectKernel)
# Sobel邊緣檢測
edge = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
edge = np.absolute(edge)
# 标準化
edge = 255 * cv2.normalize(edge, None, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_32F)
edge = edge.astype("uint8")
# 通過閉操作(先膨脹,再腐蝕)将數字連在一起
edge_close = cv2.morphologyEx(edge, cv2.MORPH_CLOSE, rectKernel)
# THRESH_OTSU會自動尋找合适的門檻值,适合雙峰,需把門檻值參數設定為0
ret, thresh = cv2.threshold(edge_close, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
# 再來一個閉操作
thresh_close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel) # 再來一個閉操作
# 如果展示為真
if visualize:
"""圖檔展示"""
# 繪制子圖
f, ax = plt.subplots(2, 2, figsize=(12, 8))
ax[0, 0].imshow(cv2.cvtColor(image_resize, cv2.COLOR_BGR2RGB))
ax[0, 1].imshow(image_gray, "gray")
ax[1, 0].imshow(tophat, "gray")
ax[1, 1].imshow(edge, "gray")
# 标題
ax[0, 0].set_title("image resize")
ax[0, 1].set_title("image gray")
ax[1, 0].set_title("image tophat")
ax[1, 1].set_title("image edge")
plt.show()
# 繪制子圖
f, ax = plt.subplots(2, 2, figsize=(12, 8))
ax[0, 0].imshow(edge, "gray")
ax[0, 1].imshow(edge_close, "gray")
ax[1, 0].imshow(thresh, "gray")
ax[1, 1].imshow(thresh_close, "gray")
# 标題
ax[0, 0].set_title("image edge")
ax[0, 1].set_title("image close")
ax[1, 0].set_title("image binary")
ax[1, 1].set_title("image binary close")
plt.show()
# 傳回
return image_resize, image_gray, thresh
模闆預處理
銀行卡預處理
計算輪廓
代碼
import cv2
from matplotlib import pyplot as plt, gridspec
from my_functions import sort_contours
def template_calculate_contours(template, template_binary, visualize=False):
"""
計算模闆輪廓
:param template: 模闆
:param template_binary: 二值化的模闆
:return: 輪廓
"""
# 擷取輪廓
contours, hierarchy = cv2.findContours(template_binary.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 輪廓排序, 從左到右, 從上到下
contours = sort_contours(contours)
digits = {}
# 周遊每一個輪廓
for (i, c) in enumerate(contours):
# 計算矩陣
(x, y, w, h) = cv2.boundingRect(c)
# 擷取roi
roi = template_binary[y:y + h, x:x + w]
roi = cv2.resize(roi, (57, 88))
# 每一個數字對應每一個模闆
digits[i] = roi
if visualize:
"""圖檔展示"""
# 繪制子圖
plt.figure(figsize=(12, 6))
gs = gridspec.GridSpec(2, 10)
# 軸1
plt.subplot(gs[0, :10])
plt.imshow(template, "gray")
plt.xticks([])
plt.yticks([])
plt.title("original")
# 軸2
for (number, image) in digits.items():
plt.subplot(gs[1, number])
plt.xticks([])
plt.yticks([])
plt.imshow(image, "gray")
plt.title("number: {}".format(number))
plt.show()
print(digits)
return digits
def image_calculate_contours(image, thresh, visualize=False):
"""
計算輪廓
:param image: 圖檔
:param thresh: 處理後的圖檔
:param visualize: 可視化, 預設為False
:return: 輪廓
"""
# 擷取輪廓
contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 繪制輪廓
cur_img = image.copy()
image_with_contour = cv2.drawContours(cur_img, contours, -1, (0, 0, 255), 3)
# 位置
locations = []
# 周遊輪廓
for (i, c) in enumerate(contours):
# 計算矩形
(x, y, w, h) = cv2.boundingRect(c)
ar = w / float(h)
# 選擇合适的區域,根據實際任務來,這裡的基本都是四個數字一組
if ar > 2.5 and ar < 4.0:
if (w > 40 and w < 55) and (h > 10 and h < 20):
# 符合的留下來
locations.append((x, y, w, h))
# 将符合的輪廓從左到右排序
locs = sorted(locations, key=lambda x: x[0])
if visualize:
"""圖檔展示"""
# 繪制子圖
f, ax = plt.subplots(2, 1, figsize=(12, 8))
ax[0].imshow(cv2.cvtColor(image_with_contour, cv2.COLOR_BGR2RGB))
for r in locs:
(x, y, w, h) = r
rectangle = cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
ax[1].imshow(cv2.cvtColor(rectangle, cv2.COLOR_BGR2RGB))
# 标題
ax[0].set_title("original")
ax[1].set_title("detect")
plt.show()
return locs
模闆輪廓
銀行卡輪廓
其他程式
import cv2
def img_show(name, img):
"""圖檔展示"""
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
def sort_contours(contours):
"""
輪廓排序 (從左到右)
:param contours: 輪廓
:return: 傳回排序好的輪廓
"""
boundingBoxes = [cv2.boundingRect(c) for c in contours] # 用一個最小的矩形,把找到的形狀包起來x,y,h,w
(contours, boundingBoxes) = zip(*sorted(zip(contours, boundingBoxes), key=lambda b: b[1][0]))
return contours
def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
"""
修改圖檔大小
:param image: 原圖
:param width: 寬
:param height: 高
:param inter: 模式
:return: 修改好的圖檔
"""
dim = None
(h, w) = image.shape[:2]
if width is None and height is None:
return image
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
主函數
代碼
import numpy as np
import argparse
import cv2
from matplotlib import pyplot as plt
from my_functions import sort_contours
from pre_process import read_template
from pre_process import read_image
from calculate_contours import template_calculate_contours
from calculate_contours import image_calculate_contours
def extract_number(image_gray, locations, digits, visualize=False):
"""
提取數字
:param image_gray: 灰階圖
:param locations: 圖檔輪廓
:param digits: 模闆輪廓
:param visualize: 可視化, 預設為False
:return: 讀取完數字的圖檔
"""
# 輸出
output = []
# 圖檔
total_img = []
# 周遊每一個輪廓中的數字
for (i, (gX, gY, gW, gH)) in enumerate(locations):
# 組輸出
groupOutput = []
group_img = []
# 根據坐标提取每一個組
group = image_gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
# 預處理
group_binary = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# 計算每一組的輪廓
digitCnts, hierarchy = cv2.findContours(group_binary.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
digitCnts = sort_contours(digitCnts)
# 計算每一組中的每一個數值
for c in digitCnts:
# 找到目前數值的輪廓,resize成合适的的大小
(x, y, w, h) = cv2.boundingRect(c)
roi = group[y:y + h, x:x + w]
roi = cv2.resize(roi, (57, 88))
group_img.append(roi)
# 計算比對得分
scores = []
# 在模闆中計算每一個得分
for (digit, digitROI) in digits.items():
# 模闆比對
result = cv2.matchTemplate(roi, digitROI, cv2.TM_CCOEFF)
(_, score, _, _) = cv2.minMaxLoc(result)
scores.append(score)
# 得到最合适的數字
groupOutput.append(str(np.argmax(scores)))
# 畫出來
cv2.rectangle(image, (gX - 5, gY - 5), (gX + gW + 5, gY + gH + 5), (0, 0, 255), 1)
cv2.putText(image, "".join(groupOutput), (gX, gY - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
# 得到結果
output.extend(groupOutput)
# 添加圖檔
total_img.append(group_img)
if visualize:
"""圖檔展示"""
# 繪制子圖
f, ax = plt.subplots(4, 4, figsize=(8, 8))
for i, group_im in enumerate(total_img):
for j, im in enumerate(group_im):
ax[i, j].imshow(im, "gray")
ax[i, j].set_xticks([])
ax[i, j].set_yticks([])
ax[i, j].set_title("group: {}".format(i + 1))
plt.show()
# 展示最終圖檔
plt.figure(figsize=(10, 8))
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
plt.title("Final Result")
plt.show()
return image
def parse_opt():
"""設定參數"""
parser = argparse.ArgumentParser()
parser.add_argument("--image_path", type=str, default="images/credit_card_01.png", help="輸入圖檔路徑")
parser.add_argument("--template_path", type=str, default="template/template.png", help="模闆圖檔路徑")
args = parser.parse_args()
return args
if __name__ == "__main__":
args = parse_opt()
# 讀取模闆
template, template_binary = read_template(args.template_path, True)
# 計算模闆輪廓
digits = template_calculate_contours(template=template, template_binary=template_binary, visualize=True)
# 讀取圖檔
image, image_gray, thresh = read_image(args.image_path, visualize=True)
# 計算圖檔輪廓
locations = image_calculate_contours(image, thresh, visualize=True)
# 提取數字
result = extract_number(image_gray=image_gray, locations=locations, digits=digits, visualize=True)
# 儲存最終結果
cv2.imwrite("Final_result.png", result)