OpenCV実践小規模プロジェクト(1):クレジットカード番号認識

1.前に書く

今日、私はOpenCV実践の小さなプロジェクトを組織しました。数日前に、OpenCV処理画像のナレッジノートを組織しました。後で、この知識をいくつかの小さなプロジェクトを通じて実践に適用します。1つは理解を深めることであり、もう1つは統合して接続するために。全体として、これらのものを使用しないと、実際にはすぐに忘れられてしまうことがわかったからです。さらに、これらの実用的な小さなプロジェクトは非常に有用であり、一部のコードまたは画像処理スキルは後で使用できることがわかったので、これが私がそれらを整理したい理由です。

最初の実践プロジェクトは、クレジットカード番号の認識です。これは、クレジットカードを提供し、次の効果をもたらすことです。

ここに画像の説明を挿入
このプロジェクトで使用される知識は、ナンバープレート番号の認識と検出、デジタル認識など、他の多くのシナリオで実際に遭遇するため、より実用的であると感じます。しかし実際には、使用される知識は本質的に複雑ではなく、完全に以前に分類されたOpenCVの基本的な画像操作ですが、どのように行われるのでしょうか。

以下では、最初にこのプロジェクトのマクロ実装ロジック、つまり、このような小さなタスクを一般的にどのように考えるかを分析し、次に特定のプラクティスとコードの説明を示します。

2.ロジックを実装します

クレジットカードの場合、上記のカード番号を出力し、元の画像でカード番号の位置を丸で囲む必要があります。基本的に、これはテンプレートマッチングタスクです。コンピューターに数字を認識させたい場合は、次のようなテンプレートを指定する必要があります。

ここに画像の説明を挿入
このように、クレジットカードの番号領域を見つけて、番号領域の番号をテンプレートと1つずつ照合し、番号が何であるかを確認するだけで、識別できます。ただし、クレジットカードの場合は、番号の領域を見つける必要があります。特定のテンプレートでは、番号の領域はありますが、マッチング作業を実行するために1つずつ番号に分割する必要があるため、タスクは回転しました。クレジットカードの処理、テンプレートの処理、テンプレートマッチングの3つのサブ問題に分けられます。

小学校で学んだ「一歩、もう一歩」という文章を思い出します。

クレジットカードを処理する方法、デジタル領域を見つける?一般的な考え方は次のとおりです。

  1. 輪郭検出アルゴリズムを使用して、各オブジェクトのおおよその輪郭と外接長方形を見つけます。つまり、最初に各オブジェクトを見つけます。
  2. オブジェクトの輪郭を見つけたら、外接する長方形のアスペクト比に従って、中央にある長い数字の文字列を見つけます。この輪郭は比較的長くて狭いので、見つけやすくなります。
  3. この長い数字の文字列の場合、形態学的操作を使用して、それをより目立たせ、この部分をより正確にします
  4. 次に、この部分について、輪郭検出を再度実行し、4つの小さなブロックに分割します。小さなブロックごとに、輪郭検出を再度実行して、特定の番号を取得します。
  5. 番号ごとに、テンプレートを一致させて(直接使用できる関数があります)、それが何であるかがわかります。

テンプレートの扱いはどうですか?これはとても簡単です。輪郭検出では、これらの10個のオブジェクトを検出し、各オブジェクトに値を割り当てて、辞書を作成できます。

コードは以下のステップバイステップで説明されています。

3.テンプレート画像を処理します

テンプレート画像は、最初に3つのステップを実行します。读入 -> 转成灰度图 -> 二值化これは、輪郭検出機能がバイナリ画像を受信するためです。

# 读取模板图像
img = cv2.imread("images/ocr_a_reference.png")   # 读取的时候转灰度 cv2.imread("images/ocr_a_reference.png", 0)
# 转成灰度图
template = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值图像
template = cv2.threshold(template, 10, 255, cv2.THRESH_BINARY_INV)[1]

結果は次のとおりです。
ここに画像の説明を挿入

次に、cv2の輪郭検出機能を使用して、10個の数値の輪郭を取得します。

cv2.findContours()関数が受け入れるパラメータは、バイナリ画像、つまり白黒画像(グレースケール画像ではない)でcv2.RETR_EXTERNALあり、外側の輪郭のみが検出され、cv2.CHAIN_APPROX_SIMPLE終点の座標のみが保持されます。

# 最新版opencv只返回两个值了 3.2之后, 不会返回原来的二值图像了,直接返回轮廓信息和层级信息
contourss, hierarchy = cv2.findContours(template.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

len(contourss)  # 10个轮廓

効果は次のとおりです
ここに画像の説明を挿入
。このようにして、それぞれが10である各番号の外側の輪郭が見つかりますが、これらの10個の輪郭の配置順序は、必ずしも上記の0〜9の輪郭に対応するわけではないことに注意してください。 、保険のために、各等高線の左上隅の座標値に従って、小さいものから大きいものへと並べ替える必要があります。

# 下面将轮廓进行排序,这是因为必须保证轮廓的顺序是0-9的顺序排列着
def sort_contours(cnts, method='left-to-right'):
    reverse = False
    i = 0
    if method == 'right-to-left' or method == 'bottom-to-top':
        reverse = True
    if method == 'top-to-bottom' or method == 'bottom-to-top':
        i = 1
    
    boundingBoxes = [cv2.boundingRect(c) for c in cnts]  # 用一个最小矩形,把找到的形状包起来x,y,h,w
    
    # 根据每个轮廓左上角的点进行排序, 这样能保证轮廓的顺序就是0-9的数字排列顺序
    (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes), key=lambda x:x[1][i], reverse=reverse))
    
    return cnts, boundingBoxes 

refCnts = sort_contours(contourss, method='left-to-right')[0]  

このように、各等高線は0〜9に従って配置され、次のアイデアは非常に明確であり、各等高線オブジェクトをトラバースし、それに実数、つまり確立された数字->轮廓関連マップを添付します。

# 每个轮廓进行数字编号
digits2Cnt = {
    
    }
# 遍历每个轮廓
for i, c in enumerate(refCnts):
    # 计算外接矩形,并且resize成合适大小
    (x, y, w, h) = cv2.boundingRect(c)
    # 单独把每个数字框拿出来 坐标系竖着的是y, 横着的是x
    roi = template[y:y+h, x:x+w] 
    # 重新改变大小
    roi = cv2.resize(roi, (57, 88))
    
    # 框与字典对应
    digits2Cnt[i] = roi

# 把处理好的模板进行保存
pickle.dump(digits2Cnt, open('digits2Cnt.pkl', 'wb'))

ここには2つのポイントがあります。まず、輪郭ごとに、最初に外接長方形を計算します。つまり、最初にフレームを作成し、次に元のテンプレート画像(各番号)からフレームを取り出します。次に、後でクレジットカードの番号と一致させるために、ここでサイズを変更する必要があります。

このようにして、テンプレート画像が処理され、ditits2Cnt辞書が取得されます。辞書のキーは数値であり、値はテンプレート内のアウトラインオブジェクトです。

4.クレジットカードの処理と照合

クレジットカードの部分は、最初にクレジットカードの番号領域を見つけてから、いくつかの操作などによってこの領域を拡張する必要があるため、もう少し複雑です。

最初のステップは、画像を読み取り、サイズを変更して、グレースケールに変換することです。

# 读取图像
base_path = 'images'
file_name = 'credit_card_01.png'
credit_card = cv2.imread(os.path.join(base_path, file_name))
credit_card = resize(credit_card, width=300)
credit_gray = cv2.cvtColor(credit_card, cv2.COLOR_BGR2GRAY)

効果は次のとおりです。
ここに画像の説明を挿入
次に、明るい領域を強調表示するシルクハット操作と、暗い領域を強調表示するブラックハット操作を実行します。

# 顶帽操作,突出更明亮的区域

# 初始化卷积核
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))  # 自定义卷积核的大小了
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))

tophat = cv2.morphologyEx(credit_gray, cv2.MORPH_TOPHAT, rectKernel)

効果は次のとおりです。
ここに画像の説明を挿入
次に、上のオブジェクトのエッジを強調表示するためにエッジ検出が必要です。エッジ検出そこで、水平エッジ検出、垂直エッジ検出、および2つの組み合わせを学習しました。これらは多くの場合うまく機能します。しかし、ここでは、水平エッジ検出だけで十分であることがわかります。

# 水平边缘检测  
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)  # 水平边缘检测
# gradX = cv2.convertScaleAbs(gradX)    这个操作会把一些背景边缘也给检测出来,加了一些噪声

# 所以下面手动归一化操作
gradX = np.absolute(gradX)
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
gradX = (255 * ((gradX-minVal) / (maxVal-minVal)))
gradX = gradX.astype('uint8')

# 这里也可以按照之前的常规, 先水平,后垂直,然后合并,但是效果可能不如单独x的效果好

効果は次のとおりです。
ここに画像の説明を挿入
現在、エッジを見つけることができますが、数字を並べてつなぎ合わせたい場合は、形態学的に関連する操作を使用する必要があります。

# 闭操作: 先膨胀, 后腐蚀  膨胀就能连成一块了
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel)

効果は次のとおりです。
ここに画像の説明を挿入
すると、ほとんどの数字はバラバラに接続されていますが、場所によってはブラックホールがいくつかあり、色は特に順序付けられて明確ではないため、次のように2値画像に変換されます。オブジェクトを強調表示し、しきい値+閉じる操作が強化されました。

#THRESH_OTSU会自动寻找合适的阈值,适合双峰,需把阈值参数设置为0  让opencv自动的去做判断,找合适的阈值,这样就能自动找出哪些有用,哪些没用
thresh = cv2.threshold(gradX, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] 
cv_show('thresh',thresh)
#再来一个闭操作
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel) #再来一个闭操作

効果は次のとおりです。

ここに画像の説明を挿入
次に、輪郭検出アルゴリズムを使用して輪郭を簡単に見つけることができますが、数値の輪郭を取得する場合は、アスペクト比に従ってフィルタリングする必要もあります。

threshCnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = threshCnts
cur_img = credit_card.copy()

# 把轮廓画出来
cv2.drawContours(cur_img, cnts, -1, (0, 0, 255), 3)
cv_show('img', cur_img)

アルゴリズムによって検出された輪郭は次のとおりです。
ここに画像の説明を挿入
次に、各輪郭をトラバースし、中央の4つのデジタル輪郭をロックします。

# 找到包围数字的那四个大轮廓
locs = []
# 遍历轮廓
for i, c in enumerate(cnts):
    # 计算外接矩形
    (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):
            # 符合
            locs.append((x, y, w, h))

# 轮廓从左到右排序
locs = sorted(locs, key=lambda x: x[0])

ここでの操作は、最初にオブジェクトを外接する長方形でラップしてから、それを選択することです。これにより、4つの大きなアウトラインが作成されます。

次のステップは非常に簡単です。

  1. それぞれの大きな輪郭を反復します

    1. 輪郭ごとに、テンプレートと同じように実行して、番号を取得します
    2. 番号ごとに、テンプレートマッチングを実行します
    outputs = []
    
    # 遍历每一个轮廓中的的数字
    for (i, (gX, gY, gW, gH)) in enumerate(locs):
        # 初始化组
        groupOutput = []
        
        # 根据坐标提取每一组
        group = credit_gray[gY-5:gY+gH+5, gX-5:gX+gW+5]  # 有5的一个容错长度
        
        # 对于这每一组,先预处理  
        # 二值化,自动寻找合适阈值,增强对比,更突出有用的部分,即数字
        group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
        
        # 计算每一组的轮廓
        digitCnts, hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        digitCnts = sort_contours(digitCnts, method='left-to-right')[0]
        
        # 拿到每一组的每一个数字,然后进行模板匹配
        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))
            
            # 模板匹配
            scores = []
            for (digit, digitROI) in digits2Cnt.items():
                result = cv2.matchTemplate(roi, digitROI, cv2.TM_CCOEFF)
                (_, score, _, _) = cv2.minMaxLoc(result)
                scores.append(score)
            
            # 得到合适的数字
            # 这是个列表,存储的每个小组里面的数字识别结果
            groupOutput.append(str(np.argmax(scores)))
        
        # 画出来
        cv2.rectangle(credit_card, (gX - 5, gY - 5), (gX + gW + 5, gY + gH + 5), (0, 0, 255), 1)
        cv2.putText(credit_card, "".join(groupOutput), (gX, gY - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
        
        # 合并到最后的结果里面
        outputs.extend(groupOutput)
    
  2. 出力結果

    # 打印结果
    print("Credit Card Type: {}".format(FIRST_NUMBER[outputs[0]]))
    print("Credit Card #: {}".format("".join(outputs)))
    cv2.imshow("Image", credit_card)
    

5.ゼネラルマネージャー

このプロジェクトはここにあり、全体は比較的単純ですが、関連する知識ポイントの多くがより一般的に使用されています。次のように要約します。

  1. 图像的读取 ->转灰度->二值化操作する
  2. 等高線操作を見つける(cv2.findContours
  3. 基本的な形態学的操作(シルクハット、ブラックハット、開閉、膨張腐食)
  4. エッジ検出操作(Sobel演算子、Sharr演算子など)
  5. 等高線の並べ替え、見つかった等高線配列が故障している可能性があることに注意する必要があります
  6. 外接する長方形を描画してから、特定のオブジェクトを取り出します

もちろん、それほど複雑なロジックは含まれていません。Opencvの基本機能とPythonの基本操作がすべて含まれており、画像処理の小さなエントリプロジェクトと見なすことができます。

このプロジェクトのコードアドレスはhttps://github.com/zhongqiangwu960812/OpenCVLearningです。興味があれば、プレイできます。

おすすめ

転載: blog.csdn.net/wuzhongqiang/article/details/123796571