Pequeno projeto de prática OpenCV (1): reconhecimento do número do cartão de crédito

1. Escreva na frente

Hoje, organizei um pequeno projeto de prática OpenCV. Alguns dias atrás, organizei uma nota de conhecimento de processamento de imagens OpenCV . Mais tarde, aplicarei esse conhecimento à prática através de alguns pequenos projetos. Um é aprofundar o entendimento e o outro é para integrar e conectar como um todo, porque descobri que se essas coisas não são usadas, na verdade são esquecidas muito rapidamente. Além disso, descobri que esses pequenos projetos práticos são muito úteis, e algumas habilidades de processamento de código ou imagem podem ser usadas posteriormente, então é por isso que quero organizá-los.

O primeiro projeto prático é o reconhecimento do número do cartão de crédito, que é dar um cartão de crédito e fazer os seguintes efeitos:

insira a descrição da imagem aqui
O conhecimento usado neste projeto é realmente encontrado em muitos outros cenários, como reconhecimento e detecção de número de placa, reconhecimento digital etc., por isso parece mais prático. Mas, na verdade, o conhecimento usado não é complicado em essência, é completamente a operação básica de imagem do OpenCV resolvida anteriormente, então como isso é feito?

A seguir, primeiro analisa a lógica de macro implementação deste projeto, ou seja, como pensar em uma tarefa tão pequena em geral, e depois fornece práticas específicas e explicações de código.

2. Implemente a lógica

Dado um cartão de crédito, o número do cartão acima precisa ser impresso e a posição do número do cartão precisa ser circulada na imagem original. Essencialmente, esta é uma tarefa de correspondência de modelos . Se quisermos que o computador reconheça números, precisamos fornecer um modelo, como o seguinte:

insira a descrição da imagem aqui
Dessa forma, precisamos apenas encontrar a área numérica no cartão de crédito e, em seguida, pegar os números na área numérica para corresponder ao modelo um por um, ver qual é o número e, então, podemos identificá-lo. No entanto, para um cartão de crédito, precisamos encontrar sua área numérica. Para um determinado modelo, embora tenhamos sua área numérica, temos que dividi-la em números um a um para realizar o trabalho de correspondência, então a tarefa, apenas Turned em três subproblemas de processamento de cartões de crédito, processamento de modelos e correspondência de modelos. ,

Me lembra um texto que aprendi na escola primária, "Um passo, mais um passo".

Como processar um cartão de crédito, encontrar a área digital? A ideia geral é a seguinte:

  1. Use o algoritmo de detecção de contorno para encontrar o contorno aproximado e o retângulo circunscrito de cada objeto, ou seja, localize cada objeto primeiro
  2. Depois de encontrar o contorno do objeto, de acordo com a proporção do retângulo circunscrito, encontre a longa sequência de números no meio. Como esse contorno é relativamente longo e estreito, é mais fácil encontrá-lo.
  3. Para esta longa sequência de números, use operações morfológicas para torná-la mais proeminente e tornar esta parte mais precisa
  4. Em seguida, para esta parte, execute a detecção de contorno novamente e divida-o em quatro blocos pequenos. Para cada bloco pequeno, execute a detecção de contorno novamente para obter cada número específico.
  5. Para cada número, combine o modelo (há uma função disponível diretamente) e você saberá o que é.

Que tal lidar com modelos? Este é muito simples. A detecção de contorno pode localizar esses 10 objetos e, em seguida, atribuir um valor a cada objeto e, em seguida, criar um dicionário.

O código é explicado passo a passo abaixo.

3. Processe a imagem do modelo

A imagem de modelo primeiro executa três etapas: 读入 -> 转成灰度图 -> 二值化, porque a função de detecção de contorno recebe uma imagem binária.

# 读取模板图像
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]

O resultado é o seguinte:
insira a descrição da imagem aqui

Em seguida, use a função de detecção de contorno de cv2 para obter os contornos de 10 números

cv2.findContours()O parâmetro aceito pela função é uma imagem binária, ou seja, uma imagem em preto e branco (não uma imagem em tons de cinza), cv2.RETR_EXTERNALapenas o contorno externo é detectado e cv2.CHAIN_APPROX_SIMPLEapenas as coordenadas do ponto final são mantidas

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

len(contourss)  # 10个轮廓

O efeito é o seguinte:
insira a descrição da imagem aqui
Desta forma, o contorno externo de cada número é encontrado, cada um dos quais é 10, mas deve-se notar que a ordem de disposição desses 10 contornos não corresponde necessariamente ao contorno de 0-9 acima , portanto, por segurança, precisamos classificar de pequeno a grande de acordo com o valor da coordenada do canto superior esquerdo de cada contorno.

# 下面将轮廓进行排序,这是因为必须保证轮廓的顺序是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]  

Desta forma, cada contorno é organizado de acordo com 0-9, então a seguinte ideia é muito clara, percorra cada objeto de contorno e anexe o número real a ele, ou seja, o 数字->轮廓mapa de associação estabelecido.

# 每个轮廓进行数字编号
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'))

Há dois pontos aqui: primeiro, para cada contorno, primeiro calcule seu retângulo circunscrito, ou seja, enquadre-o primeiro, e depois retire o quadro da imagem do modelo original, que é cada número. Então, para combinar os números no cartão de crédito mais tarde, você precisa redimensionar aqui.

Desta forma, a imagem do template é processada e um ditits2Cntdicionário é obtido, a chave do dicionário é o valor numérico e o valor é o objeto de contorno no template.

4. Processar cartões de crédito e combinar

A parte do cartão de crédito é um pouco mais complicada, pois temos que localizar primeiro a área do número no cartão de crédito e depois aprimorar essa área através de algumas operações, etc.

O primeiro passo é ler a imagem, redimensioná-la e convertê-la em escala de cinza.

# 读取图像
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)

O efeito é o seguinte:
insira a descrição da imagem aqui
Em seguida, execute uma operação de cartola, que destaca as áreas mais claras, e uma operação de chapéu preto, que destaca as áreas mais escuras.

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

# 初始化卷积核
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)

O efeito é o seguinte:
insira a descrição da imagem aqui
Em seguida, a detecção de bordas é necessária para destacar as bordas dos objetos acima. Detecção de bordas Aprendemos detecção de borda horizontal, detecção de borda vertical e a combinação das duas, que geralmente funciona bem. Mas aqui verifica-se que a detecção de borda horizontal por si só é suficiente.

# 水平边缘检测  
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的效果好

O efeito é o seguinte:
insira a descrição da imagem aqui
atualmente, a borda pode ser encontrada, mas se você quiser conectar os números próximos uns aos outros em pedaços, precisará usar operações relacionadas à morfologia.

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

O efeito é o seguinte:
insira a descrição da imagem aqui
então você descobrirá que, embora a maioria dos números esteja conectada em pedaços, existem alguns buracos negros em alguns lugares, e a cor não é particularmente ordenada e óbvia, então o seguinte é convertido em uma imagem binária, destacando o objeto, e a operação de limiar + fechamento é aprimorada.

#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) #再来一个闭操作

O efeito é o seguinte:

insira a descrição da imagem aqui
Em seguida, você pode encontrar facilmente o contorno através do algoritmo de detecção de contorno, mas se quiser obter o contorno do número, também precisará filtrar de acordo com a proporção.

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)

Os contornos encontrados pelo algoritmo são os seguintes:
insira a descrição da imagem aqui
Em seguida, percorra cada contorno e bloqueie os quatro contornos digitais no meio:

# 找到包围数字的那四个大轮廓
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])

A operação aqui ainda é envolver o objeto com o retângulo delimitador primeiro e depois selecioná-lo. Isso resulta em quatro grandes contornos.

O próximo passo é muito simples:

  1. iterar sobre cada contorno grande

    1. Para cada contorno, faça o mesmo que com o modelo para obter o número
    2. Para cada número, faça a correspondência de modelos
    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. resultado de saída

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

5. Gerente Geral

Este projeto está por aqui, o geral é relativamente simples, mas muitos dos pontos de conhecimento envolvidos são mais comumente usados. Resumido da seguinte forma:

  1. 图像的读取 ->转灰度->二值化operar
  2. encontrar operação de contorno ( cv2.findContours)
  3. Operações morfológicas básicas (carteira, chapéu preto, abertura e fechamento, corrosão por dilatação)
  4. Operações de detecção de borda (operador Sobel, operador Sharr, etc.)
  5. Classificação de contorno, deve-se notar que a matriz de contorno encontrada pode estar fora de ordem
  6. Desenhe um retângulo delimitador e, em seguida, retire um objeto específico

Claro que não envolve lógica muito complicada, são todas as funções básicas do Opencv e operações básicas do python, podendo ser considerado como um pequeno projeto de entrada para processamento de imagens.

O endereço do código deste projeto é https://github.com/zhongqiangwu960812/OpenCVLearning , se você estiver interessado, pode jogá-lo.

Acho que você gosta

Origin blog.csdn.net/wuzhongqiang/article/details/123796571
Recomendado
Clasificación