Imagem de limpeza para OCR

9

Eu tenho tentado limpar imagens para OCR: (as linhas)

insira a descrição da imagem aqui

Preciso remover essas linhas para, às vezes, processar ainda mais a imagem e estou chegando bem perto, mas na maioria das vezes o limite retira muito do texto:

    copy = img.copy()
    blur = cv2.GaussianBlur(copy, (9,9), 0)
    thresh = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV,11,30)

    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9))
    dilate = cv2.dilate(thresh, kernel, iterations=2)

    cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]

    for c in cnts:
        area = cv2.contourArea(c)
        if area > 300:
            x,y,w,h = cv2.boundingRect(c)
            cv2.rectangle(copy, (x, y), (x + w, y + h), (36,255,12), 3)

Editar: Além disso, o uso de números constantes não funcionará caso a fonte seja alterada. Existe uma maneira genérica de fazer isso?

K41F4r
fonte
2
Algumas dessas linhas, ou fragmentos delas, têm as mesmas características do texto legal e será difícil se livrar delas sem estragar o texto válido. Se isso se aplica, você pode se concentrar nos fatos de que eles são mais longos do que caracteres e um pouco isolados. Portanto, um primeiro passo seria estimar o tamanho e a proximidade dos caracteres.
Yves Daoust
@YvesDaoust Como alguém poderia encontrar a proximidade dos personagens? (desde filtragem puramente em tamanho fica-se misturado com os personagens de uma grande parte do tempo)
K41F4r
11
Você pode encontrar, para cada bolha, a distância até o vizinho mais próximo. Então, pela análise histograma das distâncias, você encontraria um limite entre "fechar" e "separar" (algo como o modo da distribuição), ou entre "cercado" e "isolado".
Yves Daoust
No caso de várias pequenas linhas próximas uma da outra, o vizinho mais próximo não seria a outra pequena linha? O cálculo da distância média a todos os outros blobs seria muito caro?
K41F4r
"o vizinho mais próximo não seria a outra linha pequena?": boa objeção, Meritíssimo. De fato, vários segmentos curtos e próximos não diferem do texto legítimo, embora em um arranjo completamente improvável. Pode ser necessário reagrupar os fragmentos de linhas quebradas. Não tenho certeza de que a distância média de todos os resgataria.
Yves Daoust

Respostas:

14

Aqui está uma ideia. Dividimos esse problema em várias etapas:

  1. Determine a área retangular média do contorno. Limitamos, então, os contornos e filtramos usando a área retangular do contorno. A razão pela qual fazemos isso é por causa da observação de que qualquer caractere típico será apenas tão grande, enquanto um ruído grande abrangerá uma área retangular maior. Em seguida, determinamos a área média.

  2. Remova contornos grandes e extremos. Repetimos os contornos e removemos os contornos grandes, se forem 5xmaiores que a área média do contorno, preenchendo o contorno. Em vez de usar uma área de limite fixo, usamos esse limite dinâmico para obter mais robustez.

  3. Dilate com um kernel vertical para conectar caracteres . A idéia é aproveitar a observação de que os caracteres estão alinhados em colunas. Ao dilatar com um núcleo vertical, conectamos o texto para que o ruído não seja incluído nesse contorno combinado.

  4. Remova pequenos ruídos . Agora que o texto a ser mantido está conectado, encontramos contornos e removemos os contornos menores que 4xa área média do contorno.

  5. Bit a bit - e para reconstruir a imagem . Como nós apenas desejamos contornos para manter nossa máscara, nós bit a bit - e para preservar o texto e obter nosso resultado.


Aqui está uma visualização do processo:

Nós limiar de Otsu para obter uma imagem binária, em seguida, encontrar contornos para determinar a área retangular média do contorno. A partir daqui, removemos os grandes contornos extremos destacados em verde preenchendo os contornos

insira a descrição da imagem aqui insira a descrição da imagem aqui

Em seguida, construímos um núcleo vertical e dilatamos para conectar os caracteres. Esta etapa conecta todo o texto desejado para manter e isolar o ruído em blobs individuais.

insira a descrição da imagem aqui

Agora encontramos contornos e filtramos usando a área de contorno para remover o pequeno ruído

insira a descrição da imagem aqui

Aqui estão todas as partículas de ruído removidas destacadas em verde

insira a descrição da imagem aqui

Resultado

insira a descrição da imagem aqui

Código

import cv2

# Load image, grayscale, and Otsu's threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Determine average contour area
average_area = [] 
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    x,y,w,h = cv2.boundingRect(c)
    area = w * h
    average_area.append(area)

average = sum(average_area) / len(average_area)

# Remove large lines if contour area is 5x bigger then average contour area
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    x,y,w,h = cv2.boundingRect(c)
    area = w * h
    if area > average * 5:  
        cv2.drawContours(thresh, [c], -1, (0,0,0), -1)

# Dilate with vertical kernel to connect characters
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,5))
dilate = cv2.dilate(thresh, kernel, iterations=3)

# Remove small noise if contour area is smaller than 4x average
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    area = cv2.contourArea(c)
    if area < average * 4:
        cv2.drawContours(dilate, [c], -1, (0,0,0), -1)

# Bitwise mask with input image
result = cv2.bitwise_and(image, image, mask=dilate)
result[dilate==0] = (255,255,255)

cv2.imshow('result', result)
cv2.imshow('dilate', dilate)
cv2.imshow('thresh', thresh)
cv2.waitKey()

Nota: O processamento tradicional da imagem é limitado a operações de limiar, morfológicas e filtragem de contorno (aproximação de contorno, área, proporção ou detecção de blob). Como as imagens de entrada podem variar com base no tamanho do texto dos caracteres, é bastante difícil encontrar uma solução singular. Você pode querer treinar seu próprio classificador com aprendizado profundo / máquina para obter uma solução dinâmica.

nathancy
fonte
11
No caso de uma fonte maior, isso também não excluiria o texto?
K41F4r
Sim, pode ser, então você teria que ajustar o valor da área limite. Para uma abordagem mais dinâmica, uma idéia é determinar a área média de caracteres e usá-la como limite
nathancy
Parece ser muito específico para o exemplo, usar a área média ainda excluirá o texto muitas vezes, o que piora o resultado para OCR
K41F4r
Você tem outro exemplo de imagem de entrada que pode adicionar à postagem?
Nathancy 5/12/19
11
Encontrar uma solução que funcione em todas as situações usando técnicas tradicionais de processamento de imagem é bastante difícil. Você pode querer treinar seu próprio classificador usando aprendizado profundo. Boa sorte!
Nathancy 6/12/19