作者:雲時之間
來源:知乎
鏈接:https://zhuanlan.zhihu.com/p/136891320
編輯:王萌
在這篇文章中,我們將會通過使用之前學習過的OpenCV的知識,比如:高斯模糊,灰度操作,邊緣檢測,二值化操作等。如果之前沒有接觸過,可以通過之前專欄的文章瞭解。
因爲最近要做關於OCR識別的任務,有一個內容是關於卡片類的卡號格式化識別,瞭解後發現我們國內現在目前用的卡類標準爲ISO/IEC7810:2003的ID-1標準,卡片尺寸是85.60mm*53.98mm,通過計算長寬比可以得到爲0.623.
先思考一下我們的大致思路:
因爲我們最終是要完成卡面信息的識別,我們可以分爲兩個關鍵流程:
輸入->文本檢測->文本識別->輸出
我們會使用傳統的濾波方式完成文本檢測,當然現在使用的更多的像是yolo,ctpn檢測這樣的深度學習的方法。但是在卡片類這種較爲固定的格式,個人認爲不如使用濾波類效率高,況且濾波類的效果也不差。
大體思路如上,現在具體到代碼實現部分:
①:判斷長寬比:
首先讀取輸入圖片的尺寸(H,W)
如果在,直接接下來正常進行。
如果不在,那我們可以認爲輸入的圖片尺寸過大或過小,會存在一些背景信息,這時候就需要去除背景,只留下卡面主體。跳轉到removeBackground()函數:
第一步:無論是否有背景,先將圖片進行尺寸reshape(550,350),方便以後操作:
測試圖片:
因爲採取了最近鄰插值來reshape,這也會讓像素的一些顏色發生變化:
尺寸如下:
接下來需要進行:
灰度->中值濾波->Sobel邊緣檢測->二值處理->去除多餘部分的背景
在完成sobel檢測以後,我們輸出一下結果,可以發現已經比較清晰的看起給出卡片的輪廓。
接下來自適應二值化,檢測輪廓部分,輪廓之間有一片連通區域,我們要讓系統認爲這是我們要檢測的卡面部分。
得到二值圖後,我們需要把連通部分摘取出來,這裏用的boundingRect:
得到去除背景後的圖片:
現在我們獲取到較爲完整的卡面後,可以去識別卡片上的號碼了,首先要找到號碼的位置:
操作與上邊去除背景的時候基本類似,只不過會多一個浮雕化處理(embossment):
這裏簡單的說一下浮雕化處理:
根據像素與周圍像素的差值確定像素值,差別較大的像素(邊緣點通常像素差別較大)像素值較大,
在灰度圖中表現爲較亮,邊緣凸顯,形成浮雕狀,然後加上一個灰度偏移值,作爲圖片的整體底色。
實現公式:newP = gray0-gray1+150
經過浮雕化處理後,顯示一下:
我們可以比較清晰的看見圖片中卡號等信息,這時候需要二值化處理,對圖像的黑色部分進行豎直投影,圖像水平方向的黑色像素進行統計,
除了頂部位置印有銀行名稱部分,剩下黑色像素少的區 域就是所要找的銀行卡卡號區域。驗證結果查看其他測試圖發現都是如此。
根據經驗來看,剩下黑色像素少的區域就是所要找的銀行卡卡號區域。
輸出函數結果,發現可以很精準的裁到卡號所在的位置:
但是這結果長度還是有些長,圖片越緊湊,識別的精度越高,速度也會越快,爲了方便以後的操作,我們再將這個結果進行處理一下:使用的操作與上述類似,只不過調整了一些尺寸
就這樣,我們完成了銀行卡號碼的定位檢測,學以致用,多多實踐。
接下來我們將繼續拓展,使用現在實現的結果去識別出數字。
代碼如下:
import cv2
import numpy as np
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
# 定義銀行卡的卡號位置,主要使用opencv對卡號進行處理
'''
銀行卡長寬85.60毫米、53.98毫米,長寬比0.623
首先判斷銀行卡的長寬比是否在0.60-0.690之間
如果在:
reshape(550,350)模擬比例0.623
如果不在:
高斯模糊->灰度化->邊緣檢測->二值化->找輪廓->輪廓判斷
這一部分是銀行卡的圖像處理部分
'''
class Image:
HEIGHT = 550
WIDTH = 350
img = 0
remove_back_img = 0
number_area = 0
def __init__(self, img):
self.img = img
H, W, _ = img.shape
if H / W > 0.6 and H / W < 0.69:
# 如果在這個區間,圖片reshape(550.350)
self.remove_back_img = cv2.resize(self.img, (self.HEIGHT, self.WIDTH))
else:
# 如果不在,處理iamge的背景
self.remove_back_img = self.removeBackground()
# 計算銀行卡數字的位置
self.number_area = self.position(self.remove_back_img)
# 獲取銀行卡號碼
self.pos_img = self.getNumberArea()
def getNumberArea(self):
num_img = self.number_area
h, w, _ = num_img.shape
# 灰度化處理
gray_img = cv2.cvtColor(num_img, cv2.COLOR_BGR2GRAY)
# 浮雕化處理
dilate_img = self.embossment(gray_img)
# 中值濾波
embo_img = cv2.medianBlur(dilate_img, 3)
# 二值化處理
_, thresh_img = cv2.threshold(embo_img, 155, 255, cv2.THRESH_BINARY)
# 中值濾波,高斯濾波,侵蝕
thresh_img = cv2.medianBlur(thresh_img, 3)
thresh_img = cv2.GaussianBlur(thresh_img, (3, 3), 0)
thresh_img = cv2.dilate(thresh_img, None, iterations=10)
# 二值化處理
_, thresh_img = cv2.threshold(thresh_img, 220, 255, cv2.THRESH_BINARY)
a = np.zeros(w, np.uint8)
for j in range(0, w): # 計算水平方向上的黑色像素點數目,
for i in range(0, h):
if thresh_img[i, j] == 0:
a[j] += 1
a = a[::-1]
length = int(0.75 * w)
min_ = sum(a)
start = 0
for i in range(len(a)):
if a[i] < 15:
a[i] = 0
else:
a[i] = 35
for i in range(w - length):
end = i + length
mean_ = a[i:end].mean()
if (min_ > mean_ and i < 50):
min_ = mean_
start = i
end = w - start
a = a[::-1]
min_ = sum(a)
start = 0
for i in range(130):
mean_ = a[i:end].mean()
if min_ > mean_:
min_ = mean_
start = i
# print(start,end)
self.W_end = end + 20
self.W_start = start + 25
cv2.imshow('aaa', num_img[:, start:end + 10])
cv2.waitKeyEx()
return num_img[:, start + 5:end]
def removeBackground(self):
# 先將圖像處理成550,350的大小區間
resize_img = cv2.resize(self.img, (self.HEIGHT, self.WIDTH), 0, 0, cv2.INTER_NEAREST) # 調整圖片大小
self.img = resize_img
# print(resize_img.shape)
# cv2.imshow('resize_img', resize_img)
'''
灰度->中值濾波->Sobel邊緣檢測->二值處理->去除多餘部分的背景。
'''
gray_img = cv2.cvtColor(resize_img, cv2.COLOR_BGR2GRAY)# 灰度處理
blur_img = cv2.medianBlur(gray_img, 9) # 中值濾波去除噪聲# Sobel邊緣檢測
x = cv2.Sobel(blur_img, cv2.CV_32F, 1, 0, 3)
y = cv2.Sobel(blur_img, cv2.CV_32F, 0, 1, 3)
absX = cv2.convertScaleAbs(x)
absY = cv2.convertScaleAbs(y)
sobel_img = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
# 自適應二值化
thresh_img = cv2.adaptiveThreshold(sobel_img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 3, 0)
# 檢測輪廓,找到連通區域,也就是銀行卡號區域
cnts, _ = cv2.findContours(thresh_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
'''
img是一個二值圖,boundingRect返回四個值,
分別是x,y,w,h;
x,y是矩陣左上點的座標
w,h是矩陣的寬和高
判斷圖像的寬高是否都在
如果在:
reshape(x+w,y+h)
對面目標圖像像素:(x, y)其值等於原圖像(x * w_ration, y * h_ration)處的值
返回圖像
'''
temp = 0
W = 0
H = 0
X = 0
Y = 0
for i in range(0, len(cnts)):
x, y, w, h = cv2.boundingRect(cnts[i])
if (temp < w + h):
temp = w + h
W = w
H = h
X = x
Y = y
remove_back_img = resize_img[Y:Y + H, X:X + W]
cv2.imshow('remove_back', remove_back_img)
return cv2.resize(remove_back_img, (self.HEIGHT, self.WIDTH), cv2.INTER_NEAREST)
# 圖片浮雕處理
'''
根據像素與周圍像素的差值確定像素值,差別較大的像素(邊緣點通常像素差別較大)像素值較大,
在灰度圖中表現爲較亮,邊緣凸顯,形成浮雕狀,然後加上一個灰度偏移值,作爲圖片的整體底色。
實現公式:newP = gray0-gray1+150
'''
def embossment(self, img):
H, W = img.shape
dst = np.zeros((H, W), np.uint8)
for i in range(0, H):
for j in range(0, W - 1):
grayP0 = int(img[i, j])
grayP1 = int(img[i, j + 1])
newP = grayP0 - grayP1 + 150
if newP > 255:
newP = 255
if newP < 0:
newP = 0
dst[i, j] = newP
return dst
'''
圖像水平方向的黑色像素進行統計
剩下黑色像素少的區域就是所要找的銀行卡卡號區域
'''
def horizontal(self, img):
H, W = img.shape
# test_img = np.ones((H,W)) * 255
hor_array = np.zeros(H, np.int32)
for j in range(0, H):
for i in range(0, W):
if img[j, i] == 0:
hor_array[j] += 1
return hor_array
def getArea(self, array):
'''
除去頂部,計算平均值小的區域就是銀行卡卡號位置所在
選取圖片高的1/10爲一個區間用於計算不同區域的平均值。
'''
H = len(array)
label_H = int(H / 10)
min_ = sum(array)
ans = 0
for i in range(int(1 / 2 * H) - label_H): # 從圖像高2/5位置處開始進行平均值計算。
a = int(2 / 5 * H) + i
b = int(2 / 5 * H) + i + label_H
mean = array[a:b].mean()
if mean < min_:
ans = a
min_ = mean
if a > 0.6 * H:
return ans, ans + label_H
def position(self, img):
'''灰度->膨脹->腐蝕'''
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray_img = cv2.dilate(gray_img, None, iterations=2)
gray_img = cv2.erode(gray_img, None, iterations=2)
# 浮雕化處理,突出圖像的邊緣部分
emboss_img = self.embossment(gray_img)
sobel_x = cv2.Sobel(emboss_img, cv2.CV_32F, 1, 0, 3) # 邊緣檢測
sobel_y = cv2.Sobel(emboss_img, cv2.CV_32F, 0, 1, 3)
absX = cv2.convertScaleAbs(sobel_x)
absY = cv2.convertScaleAbs(sobel_y)
sobel_img = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
sobel_img = cv2.medianBlur(sobel_img, 11) # 中值模糊
sobel_img = cv2.dilate(sobel_img, None, iterations=2) # 膨脹
_, threshold = cv2.threshold(sobel_img, 10, 255, cv2.THRESH_BINARY) # 二值化
threshold = cv2.GaussianBlur(threshold, (9, 9), 0) # 高斯模糊
# pixel_array,test_img = self.horizontal(threshold)
pixel_array = self.horizontal(threshold) # 對圖形黑色像素進行豎直投影
start, end = self.getArea(pixel_array)
self.H_start = start
self.H_end = end
res = img[start:end, 20:]
cv2.imshow('res', res)
return res
if __name__ == '__main__':
lena = mpimg.imread('2.jpeg')
A1 = Image(lena)