CV學習筆記(十八):CardOCR

作者:雲時之間
來源:知乎
鏈接:https://zhuanlan.zhihu.com/p/136891320
編輯:王萌


在這篇文章中,我們將會通過使用之前學習過的OpenCV的知識,比如:高斯模糊,灰度操作,邊緣檢測,二值化操作等。如果之前沒有接觸過,可以通過之前專欄的文章瞭解。
因爲最近要做關於OCR識別的任務,有一個內容是關於卡片類的卡號格式化識別,瞭解後發現我們國內現在目前用的卡類標準爲ISO/IEC7810:2003的ID-1標準,卡片尺寸是85.60mm*53.98mm,通過計算長寬比可以得到爲0.623.

CV學習筆記(十八):CardOCR

先思考一下我們的大致思路:
因爲我們最終是要完成卡面信息的識別,我們可以分爲兩個關鍵流程:
輸入->文本檢測->文本識別->輸出
我們會使用傳統的濾波方式完成文本檢測,當然現在使用的更多的像是yolo,ctpn檢測這樣的深度學習的方法。但是在卡片類這種較爲固定的格式,個人認爲不如使用濾波類效率高,況且濾波類的效果也不差。


CV學習筆記(十八):CardOCR

大體思路如上,現在具體到代碼實現部分:
①:判斷長寬比:
首先讀取輸入圖片的尺寸(H,W)

CV學習筆記(十八):CardOCR

如果在,直接接下來正常進行。
如果不在,那我們可以認爲輸入的圖片尺寸過大或過小,會存在一些背景信息,這時候就需要去除背景,只留下卡面主體。跳轉到removeBackground()函數:
第一步:無論是否有背景,先將圖片進行尺寸reshape(550,350),方便以後操作:
CV學習筆記(十八):CardOCR
測試圖片:
CV學習筆記(十八):CardOCR
因爲採取了最近鄰插值來reshape,這也會讓像素的一些顏色發生變化:
CV學習筆記(十八):CardOCR
尺寸如下:
CV學習筆記(十八):CardOCR
接下來需要進行:
灰度->中值濾波->Sobel邊緣檢測->二值處理->去除多餘部分的背景
CV學習筆記(十八):CardOCR
在完成sobel檢測以後,我們輸出一下結果,可以發現已經比較清晰的看起給出卡片的輪廓。
接下來自適應二值化,檢測輪廓部分,輪廓之間有一片連通區域,我們要讓系統認爲這是我們要檢測的卡面部分。
CV學習筆記(十八):CardOCR














CV學習筆記(十八):CardOCR
CV學習筆記(十八):CardOCR
得到二值圖後,我們需要把連通部分摘取出來,這裏用的boundingRect:

CV學習筆記(十八):CardOCR
得到去除背景後的圖片:
CV學習筆記(十八):CardOCR
現在我們獲取到較爲完整的卡面後,可以去識別卡片上的號碼了,首先要找到號碼的位置:
CV學習筆記(十八):CardOCR
操作與上邊去除背景的時候基本類似,只不過會多一個浮雕化處理(embossment):
這裏簡單的說一下浮雕化處理:
根據像素與周圍像素的差值確定像素值,差別較大的像素(邊緣點通常像素差別較大)像素值較大,
在灰度圖中表現爲較亮,邊緣凸顯,形成浮雕狀,然後加上一個灰度偏移值,作爲圖片的整體底色。

實現公式:newP = gray0-gray1+150
CV學習筆記(十八):CardOCR
經過浮雕化處理後,顯示一下:
CV學習筆記(十八):CardOCR
我們可以比較清晰的看見圖片中卡號等信息,這時候需要二值化處理,對圖像的黑色部分進行豎直投影,圖像水平方向的黑色像素進行統計,
CV學習筆記(十八):CardOCR
除了頂部位置印有銀行名稱部分,剩下黑色像素少的區 域就是所要找的銀行卡卡號區域。驗證結果查看其他測試圖發現都是如此。
根據經驗來看,剩下黑色像素少的區域就是所要找的銀行卡卡號區域。














CV學習筆記(十八):CardOCR
CV學習筆記(十八):CardOCR

CV學習筆記(十八):CardOCR

CV學習筆記(十八):CardOCR

輸出函數結果,發現可以很精準的裁到卡號所在的位置:
CV學習筆記(十八):CardOCR
但是這結果長度還是有些長,圖片越緊湊,識別的精度越高,速度也會越快,爲了方便以後的操作,我們再將這個結果進行處理一下:使用的操作與上述類似,只不過調整了一些尺寸
就這樣,我們完成了銀行卡號碼的定位檢測,學以致用,多多實踐。
接下來我們將繼續拓展,使用現在實現的結果去識別出數字。
代碼如下:




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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章