Extraindo texto OpenCV

148

Estou tentando encontrar as caixas delimitadoras de texto em uma imagem e atualmente estou usando esta abordagem:

// calculate the local variances of the grayscale image
Mat t_mean, t_mean_2;
Mat grayF;
outImg_gray.convertTo(grayF, CV_32F);
int winSize = 35;
blur(grayF, t_mean, cv::Size(winSize,winSize));
blur(grayF.mul(grayF), t_mean_2, cv::Size(winSize,winSize));
Mat varMat = t_mean_2 - t_mean.mul(t_mean);
varMat.convertTo(varMat, CV_8U);

// threshold the high variance regions
Mat varMatRegions = varMat > 100;

Quando recebe uma imagem como esta:

insira a descrição da imagem aqui

Então, quando eu mostro varMatRegions, recebo esta imagem:

insira a descrição da imagem aqui

Como você pode ver, combina um pouco o bloco de texto esquerdo com o cabeçalho do cartão, para a maioria dos cartões esse método funciona muito bem, mas em cartões mais ocupados, pode causar problemas.

A razão pela qual é ruim conectar esses contornos é que faz com que a caixa delimitadora do contorno ocupe quase todo o cartão.

Alguém pode sugerir uma maneira diferente de encontrar o texto para garantir a detecção adequada do texto?

200 pontos para quem encontrar o texto no cartão acima dos dois.

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

Grampo
fonte
1
A maneira mais fácil que eu vejo aqui é aumentar o contraste antes de começar as regiões ...
Paweł Stawarz
3
Pergunta legal. Obrigado por publicá-lo e hospedar a recompensa para garantir respostas tão interessantes.
Geoff
Novo na programação. O mesmo material pode ser feito para textos em scripts diferentes do inglês, como o sânscrito?
Vamshi Krishna 12/08

Respostas:

127

Você pode detectar texto localizando elementos de borda estreita (inspirados em um LPD):

#include "opencv2/opencv.hpp"

std::vector<cv::Rect> detectLetters(cv::Mat img)
{
    std::vector<cv::Rect> boundRect;
    cv::Mat img_gray, img_sobel, img_threshold, element;
    cvtColor(img, img_gray, CV_BGR2GRAY);
    cv::Sobel(img_gray, img_sobel, CV_8U, 1, 0, 3, 1, 0, cv::BORDER_DEFAULT);
    cv::threshold(img_sobel, img_threshold, 0, 255, CV_THRESH_OTSU+CV_THRESH_BINARY);
    element = getStructuringElement(cv::MORPH_RECT, cv::Size(17, 3) );
    cv::morphologyEx(img_threshold, img_threshold, CV_MOP_CLOSE, element); //Does the trick
    std::vector< std::vector< cv::Point> > contours;
    cv::findContours(img_threshold, contours, 0, 1); 
    std::vector<std::vector<cv::Point> > contours_poly( contours.size() );
    for( int i = 0; i < contours.size(); i++ )
        if (contours[i].size()>100)
        { 
            cv::approxPolyDP( cv::Mat(contours[i]), contours_poly[i], 3, true );
            cv::Rect appRect( boundingRect( cv::Mat(contours_poly[i]) ));
            if (appRect.width>appRect.height) 
                boundRect.push_back(appRect);
        }
    return boundRect;
}

Uso:

int main(int argc,char** argv)
{
    //Read
    cv::Mat img1=cv::imread("side_1.jpg");
    cv::Mat img2=cv::imread("side_2.jpg");
    //Detect
    std::vector<cv::Rect> letterBBoxes1=detectLetters(img1);
    std::vector<cv::Rect> letterBBoxes2=detectLetters(img2);
    //Display
    for(int i=0; i< letterBBoxes1.size(); i++)
        cv::rectangle(img1,letterBBoxes1[i],cv::Scalar(0,255,0),3,8,0);
    cv::imwrite( "imgOut1.jpg", img1);  
    for(int i=0; i< letterBBoxes2.size(); i++)
        cv::rectangle(img2,letterBBoxes2[i],cv::Scalar(0,255,0),3,8,0);
    cv::imwrite( "imgOut2.jpg", img2);  
    return 0;
}

Resultados:

uma. elemento = getStructuringElement (cv :: MORPH_RECT, cv :: Size (17, 3)); imgOut1 imgOut2

b. elemento = getStructuringElement (cv :: MORPH_RECT, cv :: Size (30, 30)); imgOut1 imgOut2

Os resultados são semelhantes para a outra imagem mencionada.

LovaBill
fonte
6
Detector de placa.
LovaBill
2
Para alguns cartões, a caixa delimitadora não inclui todo o texto, como meia letra sendo cortada. Como este cartão: i.imgur.com/tX3XrwH.jpg Como posso estender cada altura e largura de cada caixa delimitadora n? Obrigado pela solução que funciona muito bem!
Clip
4
Diga cv::Rect a;. Ampliado por n: a.x-=n/2;a.y-=n/2;a.width+=n;a.height+=n;.
LovaBill
2
Oi, como faço para obter o mesmo resultado com python cv2?
dnth
3
Livro . Código .
LovaBill
128

Eu usei um método baseado em gradiente no programa abaixo. Adicionadas as imagens resultantes. Observe que estou usando uma versão reduzida da imagem para processamento.

versão c ++

The MIT License (MIT)

Copyright (c) 2014 Dhanushka Dangampola

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

#include "stdafx.h"

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>

using namespace cv;
using namespace std;

#define INPUT_FILE              "1.jpg"
#define OUTPUT_FOLDER_PATH      string("")

int _tmain(int argc, _TCHAR* argv[])
{
    Mat large = imread(INPUT_FILE);
    Mat rgb;
    // downsample and use it for processing
    pyrDown(large, rgb);
    Mat small;
    cvtColor(rgb, small, CV_BGR2GRAY);
    // morphological gradient
    Mat grad;
    Mat morphKernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
    morphologyEx(small, grad, MORPH_GRADIENT, morphKernel);
    // binarize
    Mat bw;
    threshold(grad, bw, 0.0, 255.0, THRESH_BINARY | THRESH_OTSU);
    // connect horizontally oriented regions
    Mat connected;
    morphKernel = getStructuringElement(MORPH_RECT, Size(9, 1));
    morphologyEx(bw, connected, MORPH_CLOSE, morphKernel);
    // find contours
    Mat mask = Mat::zeros(bw.size(), CV_8UC1);
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(connected, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
    // filter contours
    for(int idx = 0; idx >= 0; idx = hierarchy[idx][0])
    {
        Rect rect = boundingRect(contours[idx]);
        Mat maskROI(mask, rect);
        maskROI = Scalar(0, 0, 0);
        // fill the contour
        drawContours(mask, contours, idx, Scalar(255, 255, 255), CV_FILLED);
        // ratio of non-zero pixels in the filled region
        double r = (double)countNonZero(maskROI)/(rect.width*rect.height);

        if (r > .45 /* assume at least 45% of the area is filled if it contains text */
            && 
            (rect.height > 8 && rect.width > 8) /* constraints on region size */
            /* these two conditions alone are not very robust. better to use something 
            like the number of significant peaks in a horizontal projection as a third condition */
            )
        {
            rectangle(rgb, rect, Scalar(0, 255, 0), 2);
        }
    }
    imwrite(OUTPUT_FOLDER_PATH + string("rgb.jpg"), rgb);

    return 0;
}

versão python

The MIT License (MIT)

Copyright (c) 2017 Dhanushka Dangampola

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

import cv2
import numpy as np

large = cv2.imread('1.jpg')
rgb = cv2.pyrDown(large)
small = cv2.cvtColor(rgb, cv2.COLOR_BGR2GRAY)

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
grad = cv2.morphologyEx(small, cv2.MORPH_GRADIENT, kernel)

_, bw = cv2.threshold(grad, 0.0, 255.0, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 1))
connected = cv2.morphologyEx(bw, cv2.MORPH_CLOSE, kernel)
# using RETR_EXTERNAL instead of RETR_CCOMP
contours, hierarchy = cv2.findContours(connected.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
#For opencv 3+ comment the previous line and uncomment the following line
#_, contours, hierarchy = cv2.findContours(connected.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

mask = np.zeros(bw.shape, dtype=np.uint8)

for idx in range(len(contours)):
    x, y, w, h = cv2.boundingRect(contours[idx])
    mask[y:y+h, x:x+w] = 0
    cv2.drawContours(mask, contours, idx, (255, 255, 255), -1)
    r = float(cv2.countNonZero(mask[y:y+h, x:x+w])) / (w * h)

    if r > 0.45 and w > 8 and h > 8:
        cv2.rectangle(rgb, (x, y), (x+w-1, y+h-1), (0, 255, 0), 2)

cv2.imshow('rects', rgb)

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

dhanushka
fonte
3
Eu só dei uma olhada na abordagem dele. A principal diferença que vejo é que ele está usando um filtro Sobel, enquanto eu estou usando um filtro gradiente morfológico. Acho que o filtro morfológico e a redução da amostra achatam muitas das bordas não tão fortes. Sobel pode captar mais barulho.
Dhanushka
1
@ascenator Quando você combina o OTSU com o tipo de limite, ele usa o limite do Otsu em vez do valor limite especificado. Veja aqui .
Dhanushka 29/07
1
@VishnuJayanand Você só precisa aplicar uma escala ao rect. Há um pyrdown, então multiplicam x, y, width, heightda rectpor 4.
dhanushka
1
Você poderia nos fornecer a terceira condição: o número de picos significativos em uma projeção horizontal ou pelo menos algum chumbo.
ISlimani
2
@DforTye Faça a projeção horizontal do contorno preenchido (cv :: reduzir) e limpe-o (digamos, usando a altura média ou mediana). Se você visualizar esse resultado, ele parecerá um código de barras. Eu acho que na época estava pensando em contar o número de barras e impor um limite. Agora, acho que, se a região estiver limpa o suficiente, também poderá ajudar se pudermos alimentá-la com um OCR e obter um nível de confiança para cada caractere detectado para garantir que a região contenha texto.
dhanushka
51

Aqui está uma abordagem alternativa que eu usei para detectar os blocos de texto:

  1. Convertida a imagem em escala de cinza
  2. Limiar aplicado ( binário simples, com um valor escolhido a dedo de 150 como o valor limite)
  3. Dilatação aplicada a linhas mais espessas na imagem, levando a objetos mais compactos e menos fragmentos de espaço em branco. Utilizou um valor alto para o número de iterações, portanto a dilatação é muito pesada (13 iterações, também escolhidas a dedo para obter melhores resultados).
  4. Contornos identificados de objetos na imagem resultante usando opencv findContours função .
  5. Desenhou uma caixa delimitadora (retângulo) que circunscreve cada objeto contornado - cada um deles enquadra um bloco de texto.
  6. Áreas descartadas opcionalmente que dificilmente serão o objeto que você está procurando (por exemplo, blocos de texto) devido ao seu tamanho, pois o algoritmo acima também pode encontrar objetos cruzados ou aninhados (como toda a área superior da primeira placa), alguns dos quais podem ser desinteressante para seus propósitos.

Abaixo está o código escrito em python com pyopencv; deve ser fácil portar para C ++.

import cv2

image = cv2.imread("card.png")
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) # grayscale
_,thresh = cv2.threshold(gray,150,255,cv2.THRESH_BINARY_INV) # threshold
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS,(3,3))
dilated = cv2.dilate(thresh,kernel,iterations = 13) # dilate
_, contours, hierarchy = cv2.findContours(dilated,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE) # get contours

# for each contour found, draw a rectangle around it on original image
for contour in contours:
    # get rectangle bounding contour
    [x,y,w,h] = cv2.boundingRect(contour)

    # discard areas that are too large
    if h>300 and w>300:
        continue

    # discard areas that are too small
    if h<40 or w<40:
        continue

    # draw rectangle around contour on original image
    cv2.rectangle(image,(x,y),(x+w,y+h),(255,0,255),2)

# write original image with added contours to disk  
cv2.imwrite("contoured.jpg", image) 

A imagem original é a primeira imagem em sua postagem.

Após o pré-processamento (escala de cinza, limiar e dilatação - portanto, após a etapa 3), a imagem ficou assim:

Imagem dilatada

Abaixo está a imagem resultante ("contoured.jpg" na última linha); as caixas delimitadoras finais dos objetos na imagem ficam assim:

insira a descrição da imagem aqui

Você pode ver o bloco de texto à esquerda ser detectado como um bloco separado, delimitado de seus arredores.

Usando o mesmo script com os mesmos parâmetros (exceto para o tipo de limiar que foi alterado para a segunda imagem como descrito abaixo), aqui estão os resultados para os outros 2 cartões:

insira a descrição da imagem aqui

insira a descrição da imagem aqui

Ajustando os Parâmetros

Os parâmetros (valor limite, parâmetros de dilatação) foram otimizados para esta imagem e para esta tarefa (localização de blocos de texto) e podem ser ajustados, se necessário, para outras imagens de cartões ou outros tipos de objetos a serem encontrados.

Para limiar (etapa 2), usei um limite preto. Para imagens em que o texto é mais claro que o plano de fundo, como a segunda imagem em sua postagem, um limite de branco deve ser usado; portanto, substitua o tipo de limite por cv2.THRESH_BINARY). Para a segunda imagem, também usei um valor ligeiramente mais alto para o limiar (180). A variação dos parâmetros para o valor limite e o número de iterações para dilatação resultará em diferentes graus de sensibilidade na delimitação de objetos na imagem.

Localizando outros tipos de objetos:

Por exemplo, diminuir a dilatação para 5 iterações na primeira imagem nos fornece uma delimitação mais fina dos objetos na imagem, localizando aproximadamente todas as palavras na imagem (em vez de blocos de texto):

insira a descrição da imagem aqui

Conhecendo o tamanho aproximado de uma palavra, descartei aqui áreas muito pequenas (abaixo de 20 pixels de largura ou altura) ou muito grandes (acima de 100 pixels de largura ou altura) para ignorar objetos que provavelmente não são palavras, para obter os resultados em a imagem acima.

anana
fonte
2
Você é incrível! Vou tentar isso de manhã.
Clip
Eu adicionei outra etapa para descartar objetos desinteressantes; também acrescentou exemplo para a identificação de palavras ou outros tipos de objetos (de blocos de texto)
anana
Obrigado pela resposta detalhada, no entanto, estou recebendo um erro cv2.findContours. Diz ValueError: too many values to unpack.
Abhijith 22/03
1
A questão é a função cv2.findContoursretorna 3 argumentos, e as capturas de código originais única 2.
abhijith
O @Abhijith cv2 na versão dois retornou dois argumentos, mas agora, na versão três, ele retorna 3
Tomasz Giba
27

A abordagem do @ dhanushka mostrou a maior promessa, mas eu queria brincar em Python, então fui em frente e traduzi-o por diversão:

import cv2
import numpy as np
from cv2 import boundingRect, countNonZero, cvtColor, drawContours, findContours, getStructuringElement, imread, morphologyEx, pyrDown, rectangle, threshold

large = imread(image_path)
# downsample and use it for processing
rgb = pyrDown(large)
# apply grayscale
small = cvtColor(rgb, cv2.COLOR_BGR2GRAY)
# morphological gradient
morph_kernel = getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
grad = morphologyEx(small, cv2.MORPH_GRADIENT, morph_kernel)
# binarize
_, bw = threshold(src=grad, thresh=0, maxval=255, type=cv2.THRESH_BINARY+cv2.THRESH_OTSU)
morph_kernel = getStructuringElement(cv2.MORPH_RECT, (9, 1))
# connect horizontally oriented regions
connected = morphologyEx(bw, cv2.MORPH_CLOSE, morph_kernel)
mask = np.zeros(bw.shape, np.uint8)
# find contours
im2, contours, hierarchy = findContours(connected, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
# filter contours
for idx in range(0, len(hierarchy[0])):
    rect = x, y, rect_width, rect_height = boundingRect(contours[idx])
    # fill the contour
    mask = drawContours(mask, contours, idx, (255, 255, 2555), cv2.FILLED)
    # ratio of non-zero pixels in the filled region
    r = float(countNonZero(mask)) / (rect_width * rect_height)
    if r > 0.45 and rect_height > 8 and rect_width > 8:
        rgb = rectangle(rgb, (x, y+rect_height), (x+rect_width, y), (0,255,0),3)

Agora, para exibir a imagem:

from PIL import Image
Image.fromarray(rgb).show()

Não é o mais Python dos scripts, mas tentei lembrar o código C ++ original o mais próximo possível dos leitores.

Funciona quase tão bem quanto o original. Ficarei feliz em ler as sugestões de como ele pode ser melhorado / corrigido para se parecer com os resultados originais.

insira a descrição da imagem aqui

insira a descrição da imagem aqui

insira a descrição da imagem aqui

rtkaleta
fonte
3
Obrigado por fornecer uma versão python. Muitas pessoas acharão isso útil. 1
dhanushka
qual é a diferença entre preencher o contorno e desenhá-lo? Eu encontrei um código sem a fase de enchimento aqui: stackoverflow.com/a/23556997/6837132
SarahData
@ Sarahh Não sei se você está perguntando sobre a diferença genérica entre desenho e preenchimento (bastante óbvio, eu acho?) Ou a API OpenCV especificamente? Se este último, consulte os documentos para drawContoursesse estado "A função desenha contornos de contorno na imagem se espessura> 0 ou preenche a área delimitada pelos contornos se espessura <0". Isso é feito para que possamos verificar a proporção de pixels diferentes de zero para decidir se a caixa provavelmente contém texto.
Rtkaleta 12/09
15

Você pode tentar esse método desenvolvido por Chucai Yi e Yingli Tian.

Eles também compartilham um software (que é baseado no Opencv-1.0 e deve ser executado na plataforma Windows.) Que você pode usar (embora não haja código fonte disponível). Ele irá gerar todas as caixas delimitadoras de texto (mostradas em sombras de cores) na imagem. Ao aplicar às imagens de amostra, você obterá os seguintes resultados:

Nota: para tornar o resultado mais robusto, você pode mesclar ainda mais as caixas adjacentes.


Atualização: se seu objetivo final é reconhecer os textos na imagem, você pode conferir o gttext , que é um software livre de OCR e a ferramenta Ground Truthing para imagens coloridas com texto. O código fonte também está disponível.

Com isso, você pode obter textos reconhecidos como:

herohuyongtao
fonte
O gttext é para janelas. Qualquer sugestão para usuários Mac / Linux
Saghir A. Khatri
5

Código acima da versão JAVA: Obrigado @William

public static List<Rect> detectLetters(Mat img){    
    List<Rect> boundRect=new ArrayList<>();

    Mat img_gray =new Mat(), img_sobel=new Mat(), img_threshold=new Mat(), element=new Mat();
    Imgproc.cvtColor(img, img_gray, Imgproc.COLOR_RGB2GRAY);
    Imgproc.Sobel(img_gray, img_sobel, CvType.CV_8U, 1, 0, 3, 1, 0, Core.BORDER_DEFAULT);
    //at src, Mat dst, double thresh, double maxval, int type
    Imgproc.threshold(img_sobel, img_threshold, 0, 255, 8);
    element=Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(15,5));
    Imgproc.morphologyEx(img_threshold, img_threshold, Imgproc.MORPH_CLOSE, element);
    List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
    Mat hierarchy = new Mat();
    Imgproc.findContours(img_threshold, contours,hierarchy, 0, 1);

    List<MatOfPoint> contours_poly = new ArrayList<MatOfPoint>(contours.size());

     for( int i = 0; i < contours.size(); i++ ){             

         MatOfPoint2f  mMOP2f1=new MatOfPoint2f();
         MatOfPoint2f  mMOP2f2=new MatOfPoint2f();

         contours.get(i).convertTo(mMOP2f1, CvType.CV_32FC2);
         Imgproc.approxPolyDP(mMOP2f1, mMOP2f2, 2, true); 
         mMOP2f2.convertTo(contours.get(i), CvType.CV_32S);


            Rect appRect = Imgproc.boundingRect(contours.get(i));
            if (appRect.width>appRect.height) {
                boundRect.add(appRect);
            }
     }

    return boundRect;
}

E use este código na prática:

        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        Mat img1=Imgcodecs.imread("abc.png");
        List<Rect> letterBBoxes1=Utils.detectLetters(img1);

        for(int i=0; i< letterBBoxes1.size(); i++)
            Imgproc.rectangle(img1,letterBBoxes1.get(i).br(), letterBBoxes1.get(i).tl(),new Scalar(0,255,0),3,8,0);         
        Imgcodecs.imwrite("abc1.png", img1);
Fariz Agayev
fonte
2

Implementação Python para a solução da @ dhanushka:

def process_rgb(rgb):
    hasText = False
    gray = cv2.cvtColor(rgb, cv2.COLOR_BGR2GRAY)
    morphKernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
    grad = cv2.morphologyEx(gray, cv2.MORPH_GRADIENT, morphKernel)
    # binarize
    _, bw = cv2.threshold(grad, 0.0, 255.0, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
    # connect horizontally oriented regions
    morphKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 1))
    connected = cv2.morphologyEx(bw, cv2.MORPH_CLOSE, morphKernel)
    # find contours
    mask = np.zeros(bw.shape[:2], dtype="uint8")
    _,contours, hierarchy = cv2.findContours(connected, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
    # filter contours
    idx = 0
    while idx >= 0:
        x,y,w,h = cv2.boundingRect(contours[idx])
        # fill the contour
        cv2.drawContours(mask, contours, idx, (255, 255, 255), cv2.FILLED)
        # ratio of non-zero pixels in the filled region
        r = cv2.contourArea(contours[idx])/(w*h)
        if(r > 0.45 and h > 5 and w > 5 and w > h):
            cv2.rectangle(rgb, (x,y), (x+w,y+h), (0, 255, 0), 2)
            hasText = True
        idx = hierarchy[0][idx][0]
    return hasText, rgb
Akash Budhia
fonte
por que usou a máscara?
SarahData
1
Resposta duplicada. Seria mais útil se você contribuísse para a conversa em stackoverflow.com/a/43283990/6809909 .
Rtkaleta 12/09
2

Esta é uma versão em C # da resposta de dhanushka usando OpenCVSharp

        Mat large = new Mat(INPUT_FILE);
        Mat rgb = new Mat(), small = new Mat(), grad = new Mat(), bw = new Mat(), connected = new Mat();

        // downsample and use it for processing
        Cv2.PyrDown(large, rgb);
        Cv2.CvtColor(rgb, small, ColorConversionCodes.BGR2GRAY);

        // morphological gradient
        var morphKernel = Cv2.GetStructuringElement(MorphShapes.Ellipse, new OpenCvSharp.Size(3, 3));
        Cv2.MorphologyEx(small, grad, MorphTypes.Gradient, morphKernel);

        // binarize
        Cv2.Threshold(grad, bw, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);

        // connect horizontally oriented regions
        morphKernel = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(9, 1));
        Cv2.MorphologyEx(bw, connected, MorphTypes.Close, morphKernel);

        // find contours
        var mask = new Mat(Mat.Zeros(bw.Size(), MatType.CV_8UC1), Range.All);
        Cv2.FindContours(connected, out OpenCvSharp.Point[][] contours, out HierarchyIndex[] hierarchy, RetrievalModes.CComp, ContourApproximationModes.ApproxSimple, new OpenCvSharp.Point(0, 0));

        // filter contours
        var idx = 0;
        foreach (var hierarchyItem in hierarchy)
        {
            idx = hierarchyItem.Next;
            if (idx < 0)
                break;
            OpenCvSharp.Rect rect = Cv2.BoundingRect(contours[idx]);
            var maskROI = new Mat(mask, rect);
            maskROI.SetTo(new Scalar(0, 0, 0));

            // fill the contour
            Cv2.DrawContours(mask, contours, idx, Scalar.White, -1);

            // ratio of non-zero pixels in the filled region
            double r = (double)Cv2.CountNonZero(maskROI) / (rect.Width * rect.Height);
            if (r > .45 /* assume at least 45% of the area is filled if it contains text */
                 &&
            (rect.Height > 8 && rect.Width > 8) /* constraints on region size */
            /* these two conditions alone are not very robust. better to use something 
            like the number of significant peaks in a horizontal projection as a third condition */
            )
            {
                Cv2.Rectangle(rgb, rect, new Scalar(0, 255, 0), 2);
            }
        }

        rgb.SaveImage(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "rgb.jpg"));
Diomedes Domínguez
fonte
0

esta é uma versão VB.NET da resposta de dhanushka usando o EmguCV .

Algumas funções e estruturas no EmguCV precisam de consideração diferente da versão C # do OpenCVSharp

Imports Emgu.CV
Imports Emgu.CV.Structure
Imports Emgu.CV.CvEnum
Imports Emgu.CV.Util

        Dim input_file As String = "C:\your_input_image.png"
        Dim large As Mat = New Mat(input_file)
        Dim rgb As New Mat
        Dim small As New Mat
        Dim grad As New Mat
        Dim bw As New Mat
        Dim connected As New Mat
        Dim morphanchor As New Point(0, 0)

        '//downsample and use it for processing
        CvInvoke.PyrDown(large, rgb)
        CvInvoke.CvtColor(rgb, small, ColorConversion.Bgr2Gray)

        '//morphological gradient
        Dim morphKernel As Mat = CvInvoke.GetStructuringElement(ElementShape.Ellipse, New Size(3, 3), morphanchor)
        CvInvoke.MorphologyEx(small, grad, MorphOp.Gradient, morphKernel, New Point(0, 0), 1, BorderType.Isolated, New MCvScalar(0))

        '// binarize
        CvInvoke.Threshold(grad, bw, 0, 255, ThresholdType.Binary Or ThresholdType.Otsu)

        '// connect horizontally oriented regions
        morphKernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, New Size(9, 1), morphanchor)
        CvInvoke.MorphologyEx(bw, connected, MorphOp.Close, morphKernel, morphanchor, 1, BorderType.Isolated, New MCvScalar(0))

        '// find contours
        Dim mask As Mat = Mat.Zeros(bw.Size.Height, bw.Size.Width, DepthType.Cv8U, 1)  '' MatType.CV_8UC1
        Dim contours As New VectorOfVectorOfPoint
        Dim hierarchy As New Mat

        CvInvoke.FindContours(connected, contours, hierarchy, RetrType.Ccomp, ChainApproxMethod.ChainApproxSimple, Nothing)

        '// filter contours
        Dim idx As Integer
        Dim rect As Rectangle
        Dim maskROI As Mat
        Dim r As Double
        For Each hierarchyItem In hierarchy.GetData
            rect = CvInvoke.BoundingRectangle(contours(idx))
            maskROI = New Mat(mask, rect)
            maskROI.SetTo(New MCvScalar(0, 0, 0))

            '// fill the contour
            CvInvoke.DrawContours(mask, contours, idx, New MCvScalar(255), -1)

            '// ratio of non-zero pixels in the filled region
            r = CvInvoke.CountNonZero(maskROI) / (rect.Width * rect.Height)

            '/* assume at least 45% of the area Is filled if it contains text */
            '/* constraints on region size */
            '/* these two conditions alone are Not very robust. better to use something 
            'Like the number of significant peaks in a horizontal projection as a third condition */
            If r > 0.45 AndAlso rect.Height > 8 AndAlso rect.Width > 8 Then
                'draw green rectangle
                CvInvoke.Rectangle(rgb, rect, New MCvScalar(0, 255, 0), 2)
            End If
            idx += 1
        Next
        rgb.Save(IO.Path.Combine(Application.StartupPath, "rgb.jpg"))
Hakan Usakli
fonte