这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 企业专区 » OpenVINO生态社区 » 【原创】项目实战—CreditCard数字识别(十二)

共2条 1/1 1 跳转至

【原创】项目实战—CreditCard数字识别(十二)

高工
2020-09-02 19:26:23     打赏

项目实战—Credit Card 数字识别续

现在我们需要进行第二阶段的工作。

准备工作(opencv

cv2.getStructuringElement()

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(11,11))

这个函数的第一个参数表示内核的形状,有三种形状可以选择。

矩形:MORPH_RECT;

交叉形:MORPH_CROSS;

椭圆形:MORPH_ELLIPSE;

第二和第三个参数分别是内核的尺寸以及锚点的位置。一般在调用erode以及dilate函数之前,先定义一个Mat类型的变量来获得

getStructuringElement函数的返回值:
对于锚点的位置,有默认值Point-1,-1),表示锚点位于中心点。element形状唯一依赖锚点位置,其他情况下,锚点只是影响了形态学运算结果的偏移。

cv2.resize()

cv2.resize(InputArray src, OutputArray dst, Size, fx, fy, interpolation)

InputArray src 输入图片

OutputArray dst 输出图片 (可省)

Size 输出图片尺寸 w, h)宽和高

fx, fy 沿x轴,y轴的缩放系数 interpolation 插入方式

interpolation 选项所用的5种插值方法:

cv2.INTER_NEAREST 最近邻插值

cv2.INTER_LINEAR 双线性插值(默认设置)

cv2.INTER_AREA 使用像素区域关系进行重采样。

cv2.INTER_CUBIC 4x4像素邻域的双三次插值

cv2.INTER_LANCZOS4 8x8像素邻域的Lanczos插值

cv2.findContours() & cv2.drawContours()

binary, contours, hierarchy = cv2.findContours(src_bi, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

NONE输出所有点,SIMPLE只保留他们的终点。

binary是返回的二值图

contours返回一个listlist中每个元素都是图像中的一个轮廓,用numpy中的ndarray表示。

hierarchy结果,这是一个ndarray,其中的元素个数和轮廓个数相同,每个轮廓contours[i]对应4hierarchy元素hierarchy[i][0] ~hierarchy[i][3],分别表示后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号,如果没有对应项,则该值为负数

cv2.drawContours(image, contours, contourIdx, color[, thickness[,
lineType[, hierarchy[, maxLevel[, offset ]]]]])

第一个参数是指明在哪幅图像上绘制轮廓; 第二个参数是轮廓本身,在Python中是一个list

第三个参数指定绘制轮廓list中的哪条轮廓,如果是-1,则绘制其中的所有轮廓。后面的参数很简单。其中thickness表明轮廓线的宽度,如果是-1cv2.FILLED),则为填充模式。绘制参数将在以后独立详细介绍。

现在我们即将进行最终的代码实现,具体过程不讲述,代码种有详细注释,一步一步的调试

代码实现

我们将使用实验图片:

                                              image.png

image.png

image.png

image.png

image.png

模板图片:

image.png

来看代码:

ocr_template_match.py

# 导入工具包
from imutils import contours
import numpy as np
import argparse
import cv2
import myutils
 
# 设置参数
# 在pycharm中,Edit Configurations中配置下面参数
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")
ar = ap.parse_args()
args = vars(ap.parse_args())  # print(args["image"])
# "第一种方式:"
# args = vars(ap.parse_args())
# args为字典{'image': 'images/credit_card_01.png', 'template': 'images/ocr_a_reference.png'}
# 调用方式:print(args["image"])
# "第二种方式:"
# args = ap.parse_args()
# Namespace(image='images/credit_card_01.png', template='images/ocr_a_reference.png')
# 调用方式:print(args.image)
 
 
# 指定****类型
FIRST_NUMBER = {
      "3": "American Express",
      "4": "Visa",
      "5": "MasterCard",
      "6": "Discover Card"
}
 
 
# 绘图展示
def cv_show(name, img):
      cv2.imshow(name, img)
      cv2.waitKey(0)
      cv2.destroyAllWindows()
 
 
# 读取一个模板图像
img = cv2.imread(args["template"])
cv_show('img', img)
# 灰度图
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv_show('ref', ref)
# 二值图像
# ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]  # 法1c
ret, ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)  # 法2, 10值越小,保留的黑越黑,INV后变成白色
# ref要么是0要么是255
# ret返回的是阈值;ref返回的是二值化后的图像矩阵,是二维矩阵。
cv_show('ref', ref)
 
# 计算轮廓
#cv2.findContours()函数接受的参数为二值图,即黑白的(不是灰度图),cv2.RETR_EXTERNAL只检测外轮廓,cv2.CHAIN_APPROX_SIMPLE只保留终点坐标
#返回的list中每个元素都是图像中的一个轮廓
 
# ref_, refCnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
ref_, refCnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# ref_二值图, refCnts是边缘轮廓,返回值形状为(10,),表示10个形状,每个里面是一个列表,列表里是具体每一个轮廓的值。
# cv2.RETR_EXTERNAL获取外轮廓, cv2.RETR_TREE获取所有轮廓
cv2.drawContours(img, refCnts, -1, (0, 0, 255), 3)  # -1表示画出所有轮廓
cv_show('img', img)
print(np.array(refCnts).shape)
refCnts = myutils.sort_contours(refCnts, method="left-to-right")[0]  # 排序,指定 从左到右 还是 从上到下
# refCnts找到的轮廓不一定就是,从左往右123,现在找到的10个轮廓顺序乱序,需要将轮廓从左往右对应拍成0,1,2..等等
digits = {}
 
# 遍历每一个轮廓
for (i, c) in enumerate(refCnts):  # (i, c)加不加括号都是一样的
      # 计算外接矩形并且resize成合适大小
      (x, y, w, h) = cv2.boundingRect(c)
      roi = ref[y:y + h, x:x + w]
      roi = cv2.resize(roi, (57, 88), cv2.INTER_LINEAR)
      cv_show("roi", roi)
      # 每一个数字对应每一个模板
      digits[i] = roi
print(digits)
 
# 初始化卷积核
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
# (9, 3)是指的矩阵的宽w和高h, 9是因为想让横向的信息多保留?
# rectKernel为:
# [[1 1 1 1 1 1 1 1 1]
#  [1 1 1 1 1 1 1 1 1]
#  [1 1 1 1 1 1 1 1 1]]
# 形状(3, 9)
 
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
 
# 读取输入图像,预处理
image = cv2.imread(args["image"])
cv_show('image', image)
image = myutils.resize(image, width=300)  # resize前(368, 583, 3),resize后(189, 300, 3)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# cv_show('gray', gray)
cv_show('gray', gray)
 
# gradX = cv2.morphologyEx(tophat, cv2.MORPH_CLOSE, rectKernel)
# # 此处的卷积核为(9, 3),全1
# cv_show('gradX', gradX)  # 此处做闭操作也行
# 闭操作就是把字连接起来
 
# 顶帽操作,突出更明亮的区域;为了突出字体,所以核(9,3)和字体部分差不多比例
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)   # 可以对灰度图进行操作
cv_show('tophat', tophat)
 
# gradX = cv2.morphologyEx(tophat, cv2.MORPH_CLOSE, rectKernel)
# # 此处的卷积核为(9, 3),全1
# cv_show('gradX', gradX)  # 此处做闭操作也行
 
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)  # ksize=-1相当于用3*3的
cv_show('gradX1', gradX)
gradX = np.absolute(gradX)  # 这个是(0,3040)之间的浮点数
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
gradX = (255 * ((gradX - minVal) / (maxVal - minVal)))  # 这个是(0,255)之间的浮点数
gradX = gradX.astype("uint8")
cv_show('gradX', gradX)
 
# 通过闭操作(先膨胀,再腐蚀)将数字连在一起
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel)
# 此处的卷积核为(9, 3),全1
cv_show('gradX', gradX)
 
# THRESH_OTSU会自动寻找合适的阈值,适合双峰,需把阈值参数设置为0
thresh = cv2.threshold(gradX, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
# cv_show('thresh', thresh)
cv2.imshow('thresh2', thresh)
 
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)  # 再来一个闭操作
cv_show('thresh', thresh)
 
# 计算轮廓
 
thresh_, threshCnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
      cv2.CHAIN_APPROX_SIMPLE)
 
cnts = threshCnts
cur_img = image.copy()
cv2.drawContours(cur_img, cnts, -1, (0, 0, 255), 3)
cv_show('img', cur_img)
cv2.imwrite("1.jpg", cur_img)  # 把图片保存下来,然后用画图软件打开查看大小。
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])
output = []
 
# 遍历每一个轮廓中的数字
for (i, (gX, gY, gW, gH)) in enumerate(locs):
      # initialize the list of group digits
      groupOutput = []
      # 根据坐标提取每一个组
      group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
      cv_show('group', group)
      # 预处理
      group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
      cv_show('group', group)
      # 计算每一组的轮廓
      group_, digitCnts, hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,
             cv2.CHAIN_APPROX_SIMPLE)
      digitCnts = contours.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))
             cv_show('roi', roi)
 
             # 计算匹配得分
             scores = []
 
             # 在模板中计算每一个得分
             for (digit, digitROI) in digits.items():
                    # 模板匹配
                    result = cv2.matchTemplate(roi, digitROI, cv2.TM_CCOEFF)
                    (_, score, _, _) = cv2.minMaxLoc(result)  # 由于使用的是相关性,所以是去最大值即可
                    # min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
                    # # 返回的res 中min_loc代表每一个滑动窗口左上角坐标的值,min_val代表损失
                    # print(score)
                    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)
      # cv2.FONT_HERSHEY_SIMPLEX为字体,常用还有cv2.FONT_HERSHEY_COMPLEX
 
      # 得到结果
      print(groupOutput)
      output.extend(groupOutput)
      print(output)
 
# 打印结果
print("Credit Card Type: {}".format(FIRST_NUMBER[output[0]]))
print("Credit Card #: {}".format("".join(output)))
cv2.imshow("Image", image)
cv2.waitKey(0)

myutils.py

import cv2
 
 
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
    # print(cv2.boundingRect(cnts[0]))  # (730, 20, 54, 85)
    print(list(zip(cnts, boundingBoxes)))
    print(zip(cnts, boundingBoxes))
    (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
                                        key=lambda b: b[1][i], reverse=reverse))
 
    return cnts, boundingBoxes
 
 
def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
    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

最终效果:

image.png

image.png

image.png

image.png

image.png

可以看到,效果非常不错。

当然了,我们本次所用的数字识别,其本质来讲属于模板匹配,并非真正意义上的数字识别,不过接下来我们将使用更加强大的OCR识别。

 


对计算机视觉感兴趣?这个社区推荐给你~

>>点击了解OpenVINO生态开发社区



工程师
2020-09-02 21:10:46     打赏
2楼

十分感谢您的分享


共2条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]