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:
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:
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:
- 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
- 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.
- Para esta longa sequência de números, use operações morfológicas para torná-la mais proeminente e tornar esta parte mais precisa
- 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.
- 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:
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_EXTERNAL
apenas o contorno externo é detectado ecv2.CHAIN_APPROX_SIMPLE
apenas 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:
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 ditits2Cnt
dicioná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:
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:
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:
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:
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:
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:
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:
-
iterar sobre cada contorno grande
- Para cada contorno, faça o mesmo que com o modelo para obter o número
- 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)
-
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:
图像的读取 ->转灰度->二值化
operar- encontrar operação de contorno (
cv2.findContours
) - Operações morfológicas básicas (carteira, chapéu preto, abertura e fechamento, corrosão por dilatação)
- Operações de detecção de borda (operador Sobel, operador Sharr, etc.)
- Classificação de contorno, deve-se notar que a matriz de contorno encontrada pode estar fora de ordem
- 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.