Obter a localização de todo o texto presente na imagem usando o opencv

11

Eu tenho esta imagem que contém texto (números e alfabetos) nela. Quero obter a localização de todo o texto e números presentes nesta imagem. Também quero extrair todo o texto também.

insira a descrição da imagem aqui

Como obtenho os cordinatos e todo o texto (números e alfabetos) na minha imagem. Por exemplo, 10B, 44, 16, 38, 22B, etc

Pulkit Bhatnagar
fonte
qual é a sua versão do tensorflow? Se a sua versão for 2.1, tente instalar o 2.0
gellezzz
11
Jogar recompensas em perguntas ruins não é uma boa prática. Você não demonstrou conhecimento sobre como fazer isso, portanto parece que você está simplesmente tentando atrair desenvolvedores a codificar uma solução completa em troca de alguns pontos de repetição. Não espero encontrar respostas perfeitas por esse motivo, mas acredito que você pode obter melhores soluções em sites freelancers se pagar às pessoas pelo tempo gasto.
karlphillip 15/02
@karlphillip desculpe, mas sou iniciante e preciso de algo para começar, certo? Você pode me ajudar com este
Pulkit Bhatnagar

Respostas:

13

Aqui está uma abordagem potencial usando operações morfológicas para filtrar contornos não textuais. A ideia é:

  1. Obter imagem binária. Carregar imagem, escala de cinza e o limite do Otsu

  2. Remova as linhas horizontais e verticais. Crie kernels horizontais e verticais usando cv2.getStructuringElemente remova linhas comcv2.drawContours

  3. Remova linhas diagonais, objetos em círculo e contornos curvos. Filtre usando a área do contorno cv2.contourArea e a aproximação do contorno cv2.approxPolyDP para isolar os contornos que não são de texto

  4. Extrair ROIs e OCR de texto. Encontre contornos e filtre ROIs e OCR usando o Pytesseract .


Linhas horizontais removidas destacadas em verde

insira a descrição da imagem aqui

Linhas verticais removidas

insira a descrição da imagem aqui

Removidos vários contornos não textuais (linhas diagonais, objetos circulares e curvas)

insira a descrição da imagem aqui

Regiões de texto detectadas

insira a descrição da imagem aqui

import cv2
import numpy as np
import pytesseract

pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"

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

# Remove horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15,1))
detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(clean, [c], -1, 0, 3)

# Remove vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,30))
detect_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(detect_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(clean, [c], -1, 0, 3)

cnts = cv2.findContours(clean, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    # Remove diagonal lines
    area = cv2.contourArea(c)
    if area < 100:
        cv2.drawContours(clean, [c], -1, 0, 3)
    # Remove circle objects
    elif area > 1000:
        cv2.drawContours(clean, [c], -1, 0, -1)
    # Remove curve stuff
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.02 * peri, True)
    x,y,w,h = cv2.boundingRect(c)
    if len(approx) == 4:
        cv2.rectangle(clean, (x, y), (x + w, y + h), 0, -1)

open_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
opening = cv2.morphologyEx(clean, cv2.MORPH_OPEN, open_kernel, iterations=2)
close_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,2))
close = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, close_kernel, iterations=4)
cnts = cv2.findContours(close, 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 = cv2.contourArea(c)
    if area > 500:
        ROI = image[y:y+h, x:x+w]
        ROI = cv2.GaussianBlur(ROI, (3,3), 0)
        data = pytesseract.image_to_string(ROI, lang='eng',config='--psm 6')
        if data.isalnum():
            cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
            print(data)

cv2.imwrite('image.png', image)
cv2.imwrite('clean.png', clean)
cv2.imwrite('close.png', close)
cv2.imwrite('opening.png', opening)
cv2.waitKey()
nathancy
fonte
Boa idéia para remover essas linhas primeiro.
karlphillip 15/02
má idéia para também remover partes das letras ...
Walter Tross
8

Tudo bem, aqui está outra solução possível. Eu sei que você trabalha com Python - trabalho com C ++. Vou lhe dar algumas idéias e, se desejar, você poderá implementar esta resposta.

A idéia principal é não usar pré-processamento (pelo menos não no estágio inicial) e, em vez disso, focar em cada caractere de destino, obter algumas propriedades e filtrar cada blob de acordo com essas propriedades.

Estou tentando não usar o pré-processamento porque: 1) Os filtros e os estágios morfológicos podem degradar a qualidade dos blobs e 2) os blobs de destino parecem exibir algumas características que poderíamos explorar, principalmente: proporção e área .

Confira, todos os números e letras parecem mais altos que largos ... além disso, eles parecem variar dentro de um determinado valor da área. Por exemplo, você deseja descartar objetos "muito grandes" ou "muito grandes" .

A ideia é filtrar tudo o que não se enquadra nos valores pré-calculados. Examinei os caracteres (números e letras) e vim com valores mínimos e máximos de área e uma proporção mínima (aqui, a proporção entre altura e largura).

Vamos trabalhar no algoritmo. Comece lendo a imagem e redimensionando-a para metade das dimensões. Sua imagem é muito grande. Converta em escala de cinza e obtenha uma imagem binária via otsu, aqui está no pseudo-código:

//Read input:
inputImage = imread( "diagram.png" );

//Resize Image;
resizeScale = 0.5;

inputResized = imresize( inputImage, resizeScale );

//Convert to grayscale;
inputGray = rgb2gray( inputResized );

//Get binary image via otsu:
binaryImage = imbinarize( inputGray, "Otsu" );

Legal. Vamos trabalhar com esta imagem. Você precisa examinar todos os blobs brancos e aplicar um "filtro de propriedades" . Estou usando componentes conectados com estatísticas para percorrer cada blob e obter sua área e proporção, em C ++, isso é feito da seguinte maneira:

//Prepare the output matrices:
cv::Mat outputLabels, stats, centroids;
int connectivity = 8;

//Run the binary image through connected components:
int numberofComponents = cv::connectedComponentsWithStats( binaryImage, outputLabels, stats, centroids, connectivity );

//Prepare a vector of colors  color the filtered blobs in black
std::vector<cv::Vec3b> colors(numberofComponents+1);
colors[0] = cv::Vec3b( 0, 0, 0 ); // Element 0 is the background, which remains black.

//loop through the detected blobs:
for( int i = 1; i <= numberofComponents; i++ ) {

    //get area:
    auto blobArea = stats.at<int>(i, cv::CC_STAT_AREA);

    //get height, width and compute aspect ratio:
    auto blobWidth = stats.at<int>(i, cv::CC_STAT_WIDTH);
    auto blobHeight = stats.at<int>(i, cv::CC_STAT_HEIGHT);
    float blobAspectRatio = (float)blobHeight/(float)blobWidth;

    //Filter your blobs

};

Agora, aplicaremos o filtro de propriedades. Esta é apenas uma comparação com os limites pré-calculados. Eu usei os seguintes valores:

Minimum Area: 40  Maximum Area:400
MinimumAspectRatio:  1

Dentro do seu forloop, compare as propriedades atuais do blob com esses valores. Se os testes forem positivos, você "pinta" o blob de preto. Continuando dentro do forloop:

    //Filter your blobs

    //Test the current properties against the thresholds:
    bool areaTest =  (blobArea > maxArea)||(blobArea < minArea);
    bool aspectRatioTest = !(blobAspectRatio > minAspectRatio); //notice we are looking for TALL elements!

    //Paint the blob black:
    if( areaTest || aspectRatioTest ){
        //filtered blobs are colored in black:
        colors[i] = cv::Vec3b( 0, 0, 0 );
    }else{
        //unfiltered blobs are colored in white:
        colors[i] = cv::Vec3b( 255, 255, 255 );
    }

Após o loop, construa a imagem filtrada:

cv::Mat filteredMat = cv::Mat::zeros( binaryImage.size(), CV_8UC3 );
for( int y = 0; y < filteredMat.rows; y++ ){
    for( int x = 0; x < filteredMat.cols; x++ )
    {
        int label = outputLabels.at<int>(y, x);
        filteredMat.at<cv::Vec3b>(y, x) = colors[label];
    }
}

E ... é praticamente isso. Você filtrou todos os elementos que não são semelhantes ao que você está procurando. Executando o algoritmo, você obtém este resultado:

insira a descrição da imagem aqui

Além disso, encontrei as caixas delimitadoras dos blobs para visualizar melhor os resultados:

insira a descrição da imagem aqui

Como você vê, alguns elementos são detectados erroneamente. Você pode refinar o "filtro de propriedades" para identificar melhor os caracteres que está procurando. Uma solução mais profunda, envolvendo um pouco de aprendizado de máquina, requer a construção de um "vetor de recurso ideal", extraindo recursos dos blobs e comparando os dois vetores por meio de uma medida de similaridade. Você também pode aplicar algum pós- processamento para melhorar os resultados ...

Seja como for, cara, seu problema não é trivial nem fácil de ser dimensionado, e só estou lhe dando idéias. Felizmente, você poderá implementar sua solução.

eldesgraciado
fonte
Qualquer chance de converter o mesmo programa em python
Pulkit Bhatnagar
@PulkitBhatnagar Sim, é claro. Apenas aguente firme, eu terei um porto perfeito pronto em alguns minutos.
eldesgraciado 15/02
?? você fez isso, para que eu possa lhe conceder a recompensa
Pulkit Bhatnagar 17/02
Ah sim. Sinto muito, meu senhor, tive alguns problemas, mas a conversão está ocorrendo muito bem. Apenas espere. Valeu.
eldesgraciado 17/02
nunca imaginei que pudesse ser um sarcasmo.
Pulkit Bhatnagar
4

Um método é usar a janela deslizante (é caro).

Determine o tamanho dos caracteres na imagem (todos os caracteres têm o mesmo tamanho da imagem) e defina o tamanho da janela. Experimente o tesseract para a detecção (a imagem de entrada requer pré-processamento). Se uma janela detectar caracteres consecutivamente, armazene as coordenadas da janela. Mesclar as coordenadas e obter a região nos caracteres.

Salve a terra
fonte
Eu acho que 100bounty é a resposta
Himanshu Poddar