image processing - guia - Algoritmo para detectar las esquinas de la hoja de papel en la foto
qgis español (8)
¿Cuál es la mejor manera de detectar las esquinas de una factura / recibo / hoja de papel en una foto? Esto se usará para corregir la perspectiva posterior, antes de OCR.
Mi enfoque actual ha sido:
RGB> Gris> Canny Edge Detection con umbraling> Dilate (1)> Eliminar objetos pequeños (6)> clear boarder objects> pick larges blog basado en Convex Area. > [detección de esquina - No implementado]
No puedo evitar pensar que debe haber un enfoque ''inteligente'' / estadístico más robusto para manejar este tipo de segmentación. No tengo muchos ejemplos de entrenamiento, pero probablemente podría obtener 100 imágenes juntas.
Contexto mas amplio:
Estoy usando matlab para prototipos y estoy planeando implementar el sistema en OpenCV y Tesserect-OCR. Este es el primero de una serie de problemas de procesamiento de imágenes que debo resolver para esta aplicación específica. Así que estoy buscando implementar 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 quieres aceptar el desafío, las imágenes grandes están en http://madteckhead.com/tmp
caso 1 http://madteckhead.com/tmp/IMG_0773_sml.jpg caso 2 http://madteckhead.com/tmp/IMG_0774_sml.jpg caso 3 http://madteckhead.com/tmp/IMG_0775_sml.jpg caso 4 http: / /madteckhead.com/tmp/IMG_0776_sml.jpg
En el mejor de los casos esto da:
caso 1 - canny http://madteckhead.com/tmp/IMG_0773_canny.jpg caso 1 - post canny http://madteckhead.com/tmp/IMG_0773_postcanny.jpg caso 1 - blog más grande http://madteckhead.com/tmp/ IMG_0773_blob.jpg
Sin embargo, falla fácilmente en otros casos:
caso 2 - canny http://madteckhead.com/tmp/IMG_0774_canny.jpg caso 2 - post canny http://madteckhead.com/tmp/IMG_0774_postcanny.jpg caso 2 - blog más grande http://madteckhead.com/tmp/ IMG_0774_blob.jpg
Gracias de antemano por todas las grandes ideas! ¡Me gusta tanto!
EDITAR: Hough Transform Progress
P: ¿Qué algoritmo agruparía las líneas de trazado para encontrar las esquinas? Siguiendo el consejo de las respuestas, pude usar la Transformada de Hough, seleccionar líneas y filtrarlas. Mi enfoque actual es bastante crudo. He asumido que la factura siempre estará 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 a continuación). Pero no estoy del todo seguro de un algoritmo adecuado para agrupar las líneas (o votar) para extrapolar las esquinas. Las líneas 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 de origen de línea. ¿Algunas ideas?
caso 1 http://madteckhead.com/tmp/IMG_0773_hough.jpg caso 2 http://madteckhead.com/tmp/IMG_0774_hough.jpg caso 3 http://madteckhead.com/tmp/IMG_0775_hough.jpg caso 4 http: / /madteckhead.com/tmp/IMG_0776_hough.jpg
Convertir a espacio de laboratorio
Utilice el clúster kmeans del segmento 2
- Luego usa contornos o masa en uno de los clusters (intenral)
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);
Después de la detección de bordes, use Hough Transform. Luego, ponga esos puntos en una SVM (máquina de vectores de soporte) con sus etiquetas, si los ejemplos tienen líneas suaves en ellos, SVM no tendrá ninguna dificultad para dividir las partes necesarias del ejemplo y otras partes. Mi consejo sobre SVM, poner un parámetro como la conectividad y la longitud. Es decir, si los puntos están conectados y son largos, es probable que sean una línea del recibo. Entonces, puedes eliminar todos los otros puntos.
En lugar de comenzar desde la detección de bordes, puede usar la detección de Esquina.
Marvin Framework proporciona una implementación del algoritmo Moravec para este propósito. Puede encontrar las esquinas de los documentos como punto de partida. Debajo de la salida del algoritmo de Moravec:
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:
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ó en un poco de prisa, por lo que el código necesita un poco de ... descifrado ... Daré algunos consejos de lo que te he visto hacer, y luego ordenar mi código en mi día libre de mañana.
Primer consejo, OpenCV
y python
son increíbles, muévete lo más pronto posible. :RE
En lugar de eliminar pequeños objetos y / o ruido, baje las restricciones astutas, por lo que acepta más bordes, y luego encuentre el contorno cerrado más grande (en OpenCV use findcontour()
con algunos parámetros simples, creo que utilicé CV_RETR_LIST
). todavía podría tener problemas cuando está en una hoja de papel blanca, pero definitivamente está dando los mejores resultados.
Para la Transformada Houghline2()
, intente con CV_HOUGH_STANDARD
en lugar de CV_HOUGH_PROBABILISTIC
, le dará los valores rho y theta , definirá la línea en coordenadas polares, y luego podrá agrupar las líneas dentro de una cierta tolerancia a esas.
Mi agrupación funcionaba como una tabla de búsqueda, para cada línea producida a partir de la transformada de hough daría un par de rho y theta. Si estos valores estaban dentro, digamos 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 hacer análisis de líneas paralelas o distancia entre líneas mucho más fácilmente.
Espero que esto ayude.
También puede usar 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 una aproximación convexa de casco y polietileno para obtener algunas como esta:
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.
Un grupo de estudiantes de mi universidad recientemente demostró una aplicación para iPhone (y la aplicación Python OpenCV) que habían escrito para hacer exactamente esto. Según recuerdo, los pasos fueron algo así:
- Mediana de filtro para eliminar por completo el texto en el papel (este fue el texto escrito a mano en papel blanco con bastante buena iluminación y puede no funcionar con texto impreso, funcionó muy bien). La razón es que hace que la detección de esquina sea mucho más fácil.
- Hough Transform para líneas
- Encuentra los picos en el espacio del acumulador Hough Transform y dibuja cada línea en toda la imagen.
- Analice las líneas y elimine las que están muy cerca unas de otras y se encuentran en un ángulo similar (agrupe las líneas en una). Esto es necesario porque la Transformada Hough no es perfecta ya que está funcionando en un espacio de muestra discreto.
- Encuentra pares de líneas que son aproximadamente paralelas y que se cruzan con otros pares para ver qué líneas forman quads.
Esto pareció funcionar bastante bien y pudieron tomar una foto de un pedazo de papel o un libro, realizar la detección de esquina y luego mapear el documento en la imagen en un plano plano casi en tiempo real (había una sola función OpenCV para realizar el mapeo). No había OCR cuando lo vi funcionar.