Algoritmo para detectar las esquinas de la hoja de papel en la foto

97

¿Cuál es la mejor manera de detectar las esquinas de una factura / recibo / hoja de papel en una foto? Esto se utilizará para la corrección de perspectiva posterior, antes del OCR.

Mi enfoque actual ha sido:

RGB> Gray> Canny Edge Detection con umbralización> Dilate (1)> Elimina objetos pequeños (6)> clear boarder objects> elige un blog grande basado en el área convexa. > [detección de esquinas - No implementado]

No puedo evitar pensar que debe haber un enfoque "inteligente" / estadístico más sólido para manejar este tipo de segmentación. No tengo muchos ejemplos de entrenamiento, pero probablemente podría juntar 100 imágenes.

Contexto mas amplio:

Estoy usando matlab para crear prototipos y planeo implementar el sistema en OpenCV y Tesserect-OCR. Este es el primero de una serie de problemas de procesamiento de imágenes que necesito resolver para esta aplicación específica. Así que estoy buscando desarrollar mi propia solución y volver a familiarizarme con los algoritmos de procesamiento de imágenes.

Aquí hay una imagen de muestra que me gustaría que manejara el algoritmo: Si desea aceptar el desafío, las imágenes grandes están en http://madteckhead.com/tmp

caso 1
(fuente: madteckhead.com )

caso 2
(fuente: madteckhead.com )

caso 3
(fuente: madteckhead.com )

caso 4
(fuente: madteckhead.com )

En el mejor de los casos, esto da:

caso 1 - astuto
(fuente: madteckhead.com )

caso 1 - post astuto
(fuente: madteckhead.com )

caso 1 - blog más grande
(fuente: madteckhead.com )

Sin embargo, falla fácilmente en otros casos:

caso 2 - astuto
(fuente: madteckhead.com )

caso 2 - post astuto
(fuente: madteckhead.com )

caso 2 - blog más grande
(fuente: madteckhead.com )

¡Gracias de antemano por todas las grandes ideas! ¡Me gusta tanto!

EDITAR: Hough Transform Progress

P: ¿Qué algoritmo agruparía las suficientes líneas para encontrar esquinas? Siguiendo los consejos de las respuestas, pude usar la transformación de Hough, seleccionar líneas y filtrarlas. Mi enfoque actual es bastante tosco. Supuse que la factura siempre estará a menos de 15 grados fuera de alineación con la imagen. Termino con resultados razonables para las líneas si este es el caso (ver más abajo). Pero no estoy completamente seguro de un algoritmo adecuado para agrupar las líneas (o votar) para extrapolar las esquinas. Las líneas de Hough no son continuas. Y en las imágenes ruidosas, puede haber líneas paralelas, por lo que se requiere alguna forma o distancia de las métricas del origen de la línea. ¿Algunas ideas?

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

Nathan Keller
fuente
1
Sí, lo hice funcionar en aproximadamente el 95% de los casos. Desde entonces tuve que archivar el código debido a la escasez de tiempo. Publicaré un seguimiento en algún momento, no dude en comisionarme si necesita ayuda urgente. Perdón por la falta de un buen seguimiento. Me encantaría volver a trabajar en esta función.
Nathan Keller
Nathan, ¿podrías publicar un seguimiento de cómo terminaste haciéndolo? Me he quedado atascado en el mismo punto reconociendo las esquinas / el contorno exterior de una hoja de papel. Me encuentro exactamente con los mismos problemas que tú, así que estaría muy interesado en una solución.
Tim
6
Todas las imágenes de esta publicación ahora son 404.
ChrisF

Respuestas:

28

Soy el amigo de Martin que estaba trabajando en esto a principios de este año. Este fue mi primer proyecto de codificación, y terminó con un poco de prisa, por lo que el código necesita algo errr ... decodificación ... Daré algunos consejos de lo que ya te he visto hacer, y luego ordenar mi código en mi día libre mañana.

El primer consejo, OpenCVy pythones genial, muévete hacia ellos lo antes posible. :RE

En lugar de eliminar objetos pequeños o ruido, baje las astutas restricciones, para que acepte más bordes, y luego encuentre el contorno cerrado más grande (en OpenCV use findcontour()con algunos parámetros simples, creo que usé CV_RETR_LIST). aún podría tener problemas cuando está en una hoja de papel blanco, pero definitivamente estaba brindando mejores resultados.

Para la Houghline2()Transformación, intente con el CV_HOUGH_STANDARDen lugar de CV_HOUGH_PROBABILISTIC, le dará valores rho y theta , definiendo la línea en coordenadas polares, y luego puede agrupar las líneas dentro de una cierta tolerancia a esos.

Mi agrupación funcionó como una tabla de búsqueda, para cada línea generada por la transformación hough daría un par rho y theta. Si estos valores estaban dentro de, digamos, el 5% de un par de valores en la tabla, se descartaron, si estaban fuera de ese 5%, se agregó una nueva entrada a la tabla.

A continuación, puede analizar las líneas paralelas o la distancia entre líneas con mucha más facilidad.

Espero que esto ayude.

Daniel Crowley
fuente
Hola Daniel, gracias por involucrarte. Me gusta que te acerques. En realidad, es la ruta con la que estoy obteniendo buenos resultados en este momento. Incluso hubo un ejemplo de OpenCV que detectó los rectángulos. Solo tuve que filtrar un poco los resultados. como decía, el blanco sobre blanco es difícil de detectar con este método. Pero fue un enfoque simple y menos costoso que el anterior. De hecho, dejé el enfoque de hough fuera de mi algoritmo y hice una aproximación de poli, eche un vistazo al ejemplo de cuadrados en opencv. Me gustaría ver su implementación de la votación suficiente. Gracias de antemano, Nathan
Nathan Keller
Tenía problemas con este enfoque, publicaré una solución si puedo idear algo mejor para referencia futura
Anshuman Kumar
@AnshumanKumar Realmente necesito ayuda con esta pregunta, ¿pueden ayudarme, por favor? stackoverflow.com/questions/61216402/…
Carlos Diego
19

Un grupo de estudiantes de mi universidad demostró recientemente una aplicación para iPhone (y una aplicación Python OpenCV) que habían escrito para hacer exactamente esto. Según recuerdo, los pasos eran algo así:

  • Filtro de mediana para eliminar completamente el texto en el papel (este era texto escrito a mano en papel blanco con bastante buena iluminación y puede que no funcione con texto impreso, funcionó muy bien). La razón es que facilita mucho la detección de curvas.
  • Transformación de Hough para líneas
  • Encuentre los picos en el espacio del acumulador Hough Transform y dibuje cada línea a lo largo de toda la imagen.
  • Analice las líneas y elimine las que estén muy cerca entre sí y que formen un ángulo similar (agrupe las líneas en una). Esto es necesario porque la transformación de Hough no es perfecta, ya que funciona en un espacio de muestra discreto.
  • Encuentra pares de líneas que sean aproximadamente paralelas y que se crucen con otros pares para ver qué líneas forman cuadrículas.

Esto pareció funcionar bastante bien y pudieron tomar una foto de una hoja de papel o libro, realizar la detección de esquinas y luego mapear el documento en la imagen en un plano casi en tiempo real (había una sola función OpenCV para realizar el mapeo). No había OCR cuando lo vi funcionar.

Pie de Martin
fuente
Gracias por las grandes ideas Martin. He seguido su consejo e implementé el enfoque de transformación de Hough. (Ver resultados arriba). Estoy luchando por determinar un algoritmo robusto que extrapolará las líneas para encontrar las intersecciones. No hay muchas líneas y algunos falsos positivos. ¿Tiene algún consejo sobre la mejor manera de fusionar y descartar líneas? Si sus estudiantes están interesados, anímelos a ponerse en contacto. Me encantaría escuchar sus experiencias para hacer que los algoritmos se ejecuten en una plataforma móvil. (Ese es mi próximo objetivo). Muchas gracias por tus ideas.
Nathan Keller
1
Parece que el HT para líneas ha funcionado bien en todas las imágenes excepto en la segunda, pero ¿está definiendo una tolerancia de umbral para sus valores de inicio y fin en el acumulador? El HT realmente no define las posiciones inicial y final, sino los valores myc en y = mx + c. Vea aquí : tenga en cuenta que esto está utilizando coordenadas polares en el acumulador en lugar de cartesiano. De esta manera, puede agrupar las líneas por cy luego por m para adelgazarlas e imaginando que las líneas se extienden por toda la imagen, encontrará intersecciones más útiles.
Martin Foot
@MartinFoot Realmente necesito ayuda con esta pregunta, ¿pueden ayudarme, por favor? stackoverflow.com/questions/61216402/…
Carlos Diego
16

Esto es lo que se me ocurrió después de un poco de experimentación:

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()

No es perfecto, pero al menos funciona para todas las muestras:

1 2 3 4

Vanuan
fuente
4
Estoy trabajando en un proyecto similar. Ejecuto sobre el código y me da el error "Ningún módulo llamado cv". Instalé la versión Open CV 2.4 e importar cv2 funciona perfectamente para mí.
Navneet Singh
¿Sería tan amable de actualizar este código para que funcione? pastebin.com/PMH5Y0M8 solo me da una página negra.
the7erm
¿Tiene alguna idea sobre cómo transformar el siguiente código a 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, realmente necesito ayuda con esta pregunta, ¿pueden ayudarme, por favor? stackoverflow.com/questions/61216402/…
Carlos Diego
9

En lugar de comenzar desde la detección de bordes, puede utilizar la detección de esquinas.

Marvin Framework proporciona una implementación del algoritmo Moravec para este propósito. Podrías encontrar las esquinas de los papeles como punto de partida. Debajo de la salida del algoritmo de Moravec:

ingrese la descripción de la imagen aquí

Gabriel Ambrósio Archanjo
fuente
4

También puede utilizar MSER (regiones extremas máximamente estables) sobre el resultado del operador Sobel para encontrar las regiones estables de la imagen. Para cada región devuelta por MSER, puede aplicar un casco convexo y una aproximación de poli para obtener algunos como este:

Pero este tipo de detección es útil para la detección en vivo más que una sola imagen que no siempre arroja el mejor resultado.

resultado

Flayn
fuente
1
¿Puede compartir algunos detalles más para este código, quizás un montón de gracias de antemano?
Lunes
Recibo un error en cv2.CHAIN_APPROX_SIMPLE que dice demasiados valores para descomprimir. ¿Alguna idea? Estoy usando una imagen de 1024 * 1024 como muestra
Praveen
1
Gracias a todos, acabo de descubrir el cambio de sintaxis en la rama actual de Opencv answers.opencv.org/question/40329/…
Praveen
¿No está destinado el MSER a extraer blobs? Lo probé y solo detecta la mayor parte del texto
Anshuman Kumar
3

Después de la detección de bordes, use Hough Transform. Luego, coloque esos puntos en una SVM (máquina de vectores de soporte) con sus etiquetas, si los ejemplos tienen líneas suaves, SVM no tendrá ninguna dificultad para dividir las partes necesarias del ejemplo y otras partes. Mi consejo sobre SVM, pon un parámetro como conectividad y longitud. Es decir, si los puntos están conectados y son largos, es probable que sean una línea del recibo. Luego, puede eliminar todos los demás puntos.

Hefesto
fuente
Hola Ares, gracias por tus ideas! Implementé la transformación de Hough (ver arriba). No puedo encontrar una forma sólida de encontrar las esquinas dados los falsos positivos y las líneas no continuas. ¿Tienes más ideas? Ha pasado un tiempo desde que miré las técnicas de SVM. ¿Es este un enfoque supervisado? No tengo datos de entrenamiento, pero podría generar algunos. Me interesaría explorar el enfoque ya que tengo muchas ganas de aprender más sobre SVM. ¿Puede recomendar algún recurso? Saludos cordiales. Nathan
Nathan Keller
3

Aquí tienes el 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
fuente
¿Dónde está la definición de variables de líneas? Debe ser std :: vector <cv :: Vec4i> lines;
Can Ürek
@ CanÜrek Tienes razón. std::vector<cv::Vec4i> lines;se declara en un alcance global en mi proyecto.
GBF_Gabriel
1
  1. Convertir en espacio de laboratorio

  2. Utilice el clúster del segmento 2 de kmeans

  3. Luego use contornos o hough en uno de los grupos (intenral)
user3452134
fuente