Algoritmo para detectar cantos da folha de papel na foto

97

Qual é a melhor maneira de detectar os cantos de uma fatura / recibo / folha de papel em uma foto? Deve ser usado para correção de perspectiva subsequente, antes do OCR.

Minha abordagem atual tem sido:

RGB> Cinza> Canny Edge Detection with thresholding> Dilate (1)> Remove small objects (6)> Clear boarder objects> pick grande blog based on Convex Area. > [detecção de canto - não implementado]

Não posso deixar de pensar que deve haver uma abordagem "inteligente" / estatística mais robusta para lidar com esse tipo de segmentação. Não tenho muitos exemplos de treinamento, mas provavelmente poderia obter 100 imagens juntas.

Contexto mais amplo:

Estou usando matlab para prototipar e planejando implementar o sistema em OpenCV e Tesserect-OCR. Este é o primeiro de uma série de problemas de processamento de imagem que preciso resolver para este aplicativo específico. Portanto, estou procurando lançar minha própria solução e me familiarizar novamente com algoritmos de processamento de imagem.

Aqui estão alguns exemplos de imagem que eu gostaria que o algoritmo manipulasse: Se você gostaria de aceitar o desafio, as imagens grandes estão em http://madteckhead.com/tmp

caso 1
(fonte: madteckhead.com )

caso 2
(fonte: madteckhead.com )

caso 3
(fonte: madteckhead.com )

caso 4
(fonte: madteckhead.com )

Na melhor das hipóteses, isso dá:

caso 1 - astuto
(fonte: madteckhead.com )

caso 1 - postar esperto
(fonte: madteckhead.com )

caso 1 - maior blog
(fonte: madteckhead.com )

No entanto, ele falha facilmente em outros casos:

caso 2 - astuto
(fonte: madteckhead.com )

caso 2 - postar esperto
(fonte: madteckhead.com )

caso 2 - maior blog
(fonte: madteckhead.com )

Agradecemos antecipadamente por todas as ótimas ideias! Eu amo então!

EDIT: Progresso da Transformação de Hough

P: Qual algoritmo agruparia as linhas de altura para encontrar cantos? Seguindo os conselhos das respostas, consegui usar a transformada de Hough, escolher as linhas e filtrá-las. Minha abordagem atual é bastante rude. Presumi que a fatura sempre estará menos de 15 graus fora do alinhamento com a imagem. Acabo com resultados razoáveis ​​para as linhas, se for esse o caso (veja abaixo). Mas não estou totalmente certo de um algoritmo adequado para agrupar as linhas (ou votar) para extrapolar para os cantos. As linhas de Hough não são contínuas. E nas imagens com ruído, pode haver linhas paralelas, portanto, alguma forma ou distância das métricas de origem da linha são necessárias. Alguma ideia?

caso 1 caso 2 caso 3 caso 4
(fonte: madteckhead.com )

Nathan Keller
fonte
1
Sim, consegui funcionar em cerca de 95% dos casos. Desde então, tive que arquivar o código devido à falta de tempo. Vou postar um acompanhamento em algum momento, fique à vontade para me comissionar se precisar de ajuda urgente. Desculpe pela falta de um bom acompanhamento. Eu adoraria voltar a trabalhar nesse recurso.
Nathan Keller
Nathan, você poderia postar um acompanhamento de como você acabou fazendo isso? Eu estive no mesmo ponto ao reconhecer cantos / contorno externo de folhas de papel. Encontrei exatamente os mesmos problemas que você, por isso estou muito interessado em uma solução.
tim
6
Todas as imagens neste post agora 404.
ChrisF

Respostas:

28

Sou amigo de Martin que estava trabalhando nisso no início deste ano. Este foi meu primeiro projeto de codificação e meio que terminou com um pouco de pressa, então o código precisa de algum erro ... decodificação ... Vou dar algumas dicas do que eu já vi você fazer, e então classificar meu código no meu dia de folga amanhã.

Primeira dica, OpenCVepython são fantásticos, mude para eles o mais rápido possível. : D

Em vez de remover pequenos objetos e / ou ruído, reduza as restrições astutas, de modo que aceite mais arestas, e então encontre o maior contorno fechado (em OpenCV use findcontour()com alguns parâmetros simples, acho que useiCV_RETR_LIST ). ainda pode ter dificuldades quando está em um pedaço de papel branco, mas definitivamente estava fornecendo os melhores resultados.

Para a Houghline2()transformação, tente com o CV_HOUGH_STANDARDem oposição a CV_HOUGH_PROBABILISTIC, ele fornecerá os valores rho e theta , definindo a linha em coordenadas polares, e então você pode agrupar as linhas dentro de uma certa tolerância a essas.

Meu agrupamento funcionou como uma tabela de consulta, para cada linha gerada da transformação hough, ela forneceria um par rho e theta. Se esses valores estivessem dentro de, digamos 5% de um par de valores na tabela, eles seriam descartados; se estivessem fora desses 5%, uma nova entrada era adicionada à tabela.

Você pode então fazer análises de linhas paralelas ou distâncias entre linhas com muito mais facilidade.

Espero que isto ajude.

Daniel Crowley
fonte
Olá Daniel, obrigado por se envolver. Eu gosto de sua abordagem. é realmente a rota com a qual estou obtendo bons resultados no momento. Houve mesmo um exemplo do OpenCV que detectou os retângulos. Só tive que fazer alguma filtragem nos resultados. como você dizia, o branco sobre branco é difícil de detectar com esse método. Mas era uma abordagem simples e menos custosa do que o hough. Na verdade, deixei a abordagem hough de fora do meu algo e fiz uma poli-aproximação, dê uma olhada no exemplo dos quadrados em opencv. Eu gostaria de ver sua implementação da votação em Hough. Agradecemos antecipadamente, Nathan
Nathan Keller
Eu estava tendo problemas com essa abordagem, postarei uma solução se puder criar algo melhor para referência futura
Anshuman Kumar
@AnshumanKumar estou realmente precisando de ajuda com essa questão, você pode me ajudar, por favor? stackoverflow.com/questions/61216402/…
Carlos Diego
19

Um grupo de estudantes na minha universidade demonstrou recentemente um aplicativo para iPhone (e um aplicativo Python OpenCV) que eles escreveram para fazer exatamente isso. Pelo que me lembro, as etapas eram mais ou menos assim:

  • Filtro de mediana para remover completamente o texto no papel (este era um texto manuscrito em papel branco com iluminação bastante boa e pode não funcionar com texto impresso, funcionou muito bem). A razão é que torna a detecção de cantos muito mais fácil.
  • Transformada de Hough para linhas
  • Encontre os picos no espaço do acumulador Hough Transform e desenhe cada linha em toda a imagem.
  • Analise as linhas e remova aquelas que estão muito próximas umas das outras e em um ângulo semelhante (agrupe as linhas em uma). Isso é necessário porque a transformada de Hough não é perfeita, pois está funcionando em um espaço de amostra discreto.
  • Encontre pares de linhas que são aproximadamente paralelas e que cruzam outros pares para ver quais linhas formam quads.

Isso pareceu funcionar muito bem e eles conseguiram tirar uma foto de um pedaço de papel ou livro, realizar a detecção de cantos e, em seguida, mapear o documento na imagem em um plano quase em tempo real (havia uma única função OpenCV para executar o mapeamento). Não havia OCR quando o vi funcionando.

Martin Foot
fonte
Obrigado pelas ótimas ideias Martin. Segui seu conselho e implementei a abordagem de transformação de Hough. (Veja os resultados acima). Estou lutando para determinar um algoritmo robusto que extrapole as linhas para encontrar as interseções. Não há muitas linhas e alguns falsos positivos. Você tem algum conselho sobre a melhor maneira de mesclar e descartar linhas? Se seus alunos estiverem interessados, incentive-os a entrar em contato. Eu adoraria ouvir suas experiências em fazer os algoritmos rodarem em uma plataforma móvel. (Esse é meu próximo objetivo). Muito obrigado por suas idéias.
Nathan Keller
1
Parece que o HT para linhas funcionou bem em todas as imagens, exceto na segunda, mas você está definindo um limite de tolerância para os valores inicial e final no acumulador? O HT realmente não define as posições inicial e final, em vez dos valores me c em y = mx + c. Veja aqui - observe que isso está usando coordenadas polares no acumulador em vez de cartesianas. Desta forma, você pode agrupar as linhas por ce depois por m para afiná-las e, imaginando as linhas se estendendo por toda a imagem, você encontrará interseções mais úteis.
Martin Foot
@MartinFoot, estou realmente precisando de ajuda com essa questão, você pode me ajudar, por favor? stackoverflow.com/questions/61216402/…
Carlos Diego
16

Aqui está o que eu descobri depois de um pouco de experimentação:

import cv, cv2, numpy as np
import sys

def get_new(old):
    new = np.ones(old.shape, np.uint8)
    cv2.bitwise_not(new,new)
    return new

if __name__ == '__main__':
    orig = cv2.imread(sys.argv[1])

    # these constants are carefully picked
    MORPH = 9
    CANNY = 84
    HOUGH = 25

    img = cv2.cvtColor(orig, cv2.COLOR_BGR2GRAY)
    cv2.GaussianBlur(img, (3,3), 0, img)


    # this is to recognize white on white
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(MORPH,MORPH))
    dilated = cv2.dilate(img, kernel)

    edges = cv2.Canny(dilated, 0, CANNY, apertureSize=3)

    lines = cv2.HoughLinesP(edges, 1,  3.14/180, HOUGH)
    for line in lines[0]:
         cv2.line(edges, (line[0], line[1]), (line[2], line[3]),
                         (255,0,0), 2, 8)

    # finding contours
    contours, _ = cv2.findContours(edges.copy(), cv.CV_RETR_EXTERNAL,
                                   cv.CV_CHAIN_APPROX_TC89_KCOS)
    contours = filter(lambda cont: cv2.arcLength(cont, False) > 100, contours)
    contours = filter(lambda cont: cv2.contourArea(cont) > 10000, contours)

    # simplify contours down to polygons
    rects = []
    for cont in contours:
        rect = cv2.approxPolyDP(cont, 40, True).copy().reshape(-1, 2)
        rects.append(rect)

    # that's basically it
    cv2.drawContours(orig, rects,-1,(0,255,0),1)

    # show only contours
    new = get_new(img)
    cv2.drawContours(new, rects,-1,(0,255,0),1)
    cv2.GaussianBlur(new, (9,9), 0, new)
    new = cv2.Canny(new, 0, CANNY, apertureSize=3)

    cv2.namedWindow('result', cv2.WINDOW_NORMAL)
    cv2.imshow('result', orig)
    cv2.waitKey(0)
    cv2.imshow('result', dilated)
    cv2.waitKey(0)
    cv2.imshow('result', edges)
    cv2.waitKey(0)
    cv2.imshow('result', new)
    cv2.waitKey(0)

    cv2.destroyAllWindows()

Não é perfeito, mas pelo menos funciona para todas as amostras:

1 2 3 4

Vanuan
fonte
4
Estou trabalhando em um projeto semelhante. Eu corro acima do código e me dá o erro "Nenhum módulo chamado cv". Instalei a versão Open CV 2.4 e o import cv2 está funcionando perfeitamente para mim.
Navneet Singh
Você faria a gentileza de atualizar este código para que funcione? pastebin.com/PMH5Y0M8 apenas me mostra uma página preta.
the7erm
Você tem alguma ideia de como transformar o seguinte código para java: for line in lines[0]: cv2.line(edges, (line[0], line[1]), (line[2], line[3]), (255,0,0), 2, 8) # finding contours contours, _ = cv2.findContours(edges.copy(), cv.CV_RETR_EXTERNAL, cv.CV_CHAIN_APPROX_TC89_KCOS) contours = filter(lambda cont: cv2.arcLength(cont, False) > 100, contours) contours = filter(lambda cont: cv2.contourArea(cont) > 10000, contours)
aurelianr
Vanuan, eu realmente preciso de ajuda com essa questão, você pode me ajudar, por favor? stackoverflow.com/questions/61216402/…
Carlos Diego
9

Em vez de começar a partir da detecção de bordas, você pode usar a detecção de cantos.

O Marvin Framework fornece uma implementação do algoritmo Moravec para esse propósito. Você pode encontrar os cantos dos papéis como ponto de partida. Abaixo a saída do algoritmo de Moravec:

insira a descrição da imagem aqui

Gabriel Ambrósio Archanjo
fonte
4

Você também pode usar MSER (regiões extremais estáveis ​​ao máximo) sobre o resultado do operador Sobel para encontrar as regiões estáveis ​​da imagem. Para cada região retornada pelo MSER, você pode aplicar o casco convexo e a aproximação poli para obter alguns como este:

Mas este tipo de detecção é útil para detecção ao vivo mais do que uma única imagem que nem sempre retorna o melhor resultado.

resultado

Flayn
fonte
1
Você pode compartilhar mais alguns detalhes para este talvez algum código, muito obrigado antecipadamente
Monty
Estou recebendo um erro em cv2.CHAIN_APPROX_SIMPLE dizendo muitos valores para desempacotar. Qualquer ideia? Estou usando uma imagem 1024 * 1024 como minha amostra
Praveen
1
Obrigado a todos, acabei de descobrir a mudança de sintaxe no ramo Opencv atual . Answers.opencv.org/question/40329/…
Praveen
MSER não foi feito para extrair blobs? Eu tentei e ele detecta a maior parte do texto apenas
Anshuman Kumar
3

Após a detecção da borda, use a Transformação de Hough. Em seguida, coloque esses pontos em uma SVM (máquina de suporte vetorial) com seus rótulos, se os exemplos tiverem linhas suaves sobre eles, o SVM não terá dificuldade em dividir as partes necessárias do exemplo e outras partes. Meu conselho sobre SVM, coloque um parâmetro como conectividade e comprimento. Ou seja, se os pontos forem conectados e longos, eles provavelmente serão uma linha do recibo. Então, você pode eliminar todos os outros pontos.

Hefesto
fonte
Oi Ares, obrigado por suas ideias! Eu implementei a transformação de Hough (veja acima). Não consigo descobrir uma maneira robusta de encontrar os cantos devido aos falsos positivos e linhas não contínuas. Você tem mais ideias? Já faz algum tempo desde que olhei para as técnicas de SVM. Esta é uma abordagem supervisionada? Não tenho nenhum dado de treinamento, mas poderia gerar alguns. Eu estaria interessado em explorar a abordagem, pois gostaria de aprender mais sobre SVM. Você pode recomendar algum recurso. Atenciosamente. Nathan
Nathan Keller
3

Aqui você tem o código de @Vanuan usando C ++:

cv::cvtColor(mat, mat, CV_BGR2GRAY);
cv::GaussianBlur(mat, mat, cv::Size(3,3), 0);
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Point(9,9));
cv::Mat dilated;
cv::dilate(mat, dilated, kernel);

cv::Mat edges;
cv::Canny(dilated, edges, 84, 3);

std::vector<cv::Vec4i> lines;
lines.clear();
cv::HoughLinesP(edges, lines, 1, CV_PI/180, 25);
std::vector<cv::Vec4i>::iterator it = lines.begin();
for(; it!=lines.end(); ++it) {
    cv::Vec4i l = *it;
    cv::line(edges, cv::Point(l[0], l[1]), cv::Point(l[2], l[3]), cv::Scalar(255,0,0), 2, 8);
}
std::vector< std::vector<cv::Point> > contours;
cv::findContours(edges, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_TC89_KCOS);
std::vector< std::vector<cv::Point> > contoursCleaned;
for (int i=0; i < contours.size(); i++) {
    if (cv::arcLength(contours[i], false) > 100)
        contoursCleaned.push_back(contours[i]);
}
std::vector<std::vector<cv::Point> > contoursArea;

for (int i=0; i < contoursCleaned.size(); i++) {
    if (cv::contourArea(contoursCleaned[i]) > 10000){
        contoursArea.push_back(contoursCleaned[i]);
    }
}
std::vector<std::vector<cv::Point> > contoursDraw (contoursCleaned.size());
for (int i=0; i < contoursArea.size(); i++){
    cv::approxPolyDP(Mat(contoursArea[i]), contoursDraw[i], 40, true);
}
Mat drawing = Mat::zeros( mat.size(), CV_8UC3 );
cv::drawContours(drawing, contoursDraw, -1, cv::Scalar(0,255,0),1);
GBF_Gabriel
fonte
Onde está a definição da variável de linhas? Deve ser std :: vector <cv :: Vec4i> linhas;
Can Ürek de
@ CanÜrek Você está certo. std::vector<cv::Vec4i> lines;é declarado em um escopo global em meu projeto.
GBF_Gabriel de
1
  1. Converter para espaço de laboratório

  2. Use kmeans segmento 2 cluster

  3. Em seguida, use contornos ou hough em um dos clusters (intenral)
user3452134
fonte