OpenCV C ++ / Obj-C: detección de una hoja de papel / detección cuadrada

Implementé con éxito el ejemplo de detección de cuadrados de OpenCV en mi aplicación de prueba, pero ahora necesito filtrar el resultado, porque es bastante desordenado, ¿o está mal mi código?

Estoy interesado en los cuatro puntos de esquina del documento para la reducción de sesgo (así) y el procesamiento posterior …

De entrada y salida: De entrada y salida

Imagen original:

hacer clic

Código:

double angle( cv::Point pt1, cv::Point pt2, cv::Point pt0 ) { double dx1 = pt1.x - pt0.x; double dy1 = pt1.y - pt0.y; double dx2 = pt2.x - pt0.x; double dy2 = pt2.y - pt0.y; return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10); } - (std::vector<std::vector >)findSquaresInImage:(cv::Mat)_image { std::vector<std::vector > squares; cv::Mat pyr, timg, gray0(_image.size(), CV_8U), gray; int thresh = 50, N = 11; cv::pyrDown(_image, pyr, cv::Size(_image.cols/2, _image.rows/2)); cv::pyrUp(pyr, timg, _image.size()); std::vector<std::vector > contours; for( int c = 0; c < 3; c++ ) { int ch[] = {c, 0}; mixChannels(&timg, 1, &gray0, 1, ch, 1); for( int l = 0; l = (l+1)*255/N; } cv::findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); std::vector approx; for( size_t i = 0; i  1000 && cv::isContourConvex(cv::Mat(approx))) { double maxCosine = 0; for( int j = 2; j < 5; j++ ) { double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1])); maxCosine = MAX(maxCosine, cosine); } if( maxCosine < 0.3 ) { squares.push_back(approx); } } } } } return squares; } 

EDITAR 17/08/2012:

Para dibujar los cuadrados detectados en la imagen usa este código:

 cv::Mat debugSquares( std::vector<std::vector > squares, cv::Mat image ) { for ( int i = 0; i< squares.size(); i++ ) { // draw contour cv::drawContours(image, squares, i, cv::Scalar(255,0,0), 1, 8, std::vector(), 0, cv::Point()); // draw bounding rect cv::Rect rect = boundingRect(cv::Mat(squares[i])); cv::rectangle(image, rect.tl(), rect.br(), cv::Scalar(0,255,0), 2, 8, 0); // draw rotated rect cv::RotatedRect minRect = minAreaRect(cv::Mat(squares[i])); cv::Point2f rect_points[4]; minRect.points( rect_points ); for ( int j = 0; j < 4; j++ ) { cv::line( image, rect_points[j], rect_points[(j+1)%4], cv::Scalar(0,0,255), 1, 8 ); // blue } } return image; } 

Este es un tema recurrente en Stackoverflow y como no pude encontrar una implementación relevante, decidí aceptar el desafío.

Hice algunas modificaciones a la demostración de cuadrados presente en OpenCV y el siguiente código C ++ resultante es capaz de detectar una hoja de papel en la imagen:

 void find_squares(Mat& image, vector >& squares) { // blur will enhance edge detection Mat blurred(image); medianBlur(image, blurred, 9); Mat gray0(blurred.size(), CV_8U), gray; vector > contours; // find squares in every color plane of the image for (int c = 0; c < 3; c++) { int ch[] = {c, 0}; mixChannels(&blurred, 1, &gray0, 1, ch, 1); // try several threshold levels const int threshold_level = 2; for (int l = 0; l < threshold_level; l++) { // Use Canny instead of zero threshold level! // Canny helps to catch squares with gradient shading if (l == 0) { Canny(gray0, gray, 10, 20, 3); // // Dilate helps to remove potential holes between edge segments dilate(gray, gray, Mat(), Point(-1,-1)); } else { gray = gray0 >= (l+1) * 255 / threshold_level; } // Find contours and store them in a list findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); // Test contours vector approx; for (size_t i = 0; i < contours.size(); i++) { // approximate contour with accuracy proportional // to the contour perimeter approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true)*0.02, true); // Note: absolute value of an area is used because // area may be positive or negative - in accordance with the // contour orientation if (approx.size() == 4 && fabs(contourArea(Mat(approx))) > 1000 && isContourConvex(Mat(approx))) { double maxCosine = 0; for (int j = 2; j < 5; j++) { double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1])); maxCosine = MAX(maxCosine, cosine); } if (maxCosine < 0.3) squares.push_back(approx); } } } } } 

Después de que se ejecuta este procedimiento, la hoja de papel será el cuadrado más grande en el vector > :

detección de hojas de papel opencv

Te dejo escribir la función para encontrar el cuadrado más grande. 😉

A menos que haya algún otro requerimiento no especificado, simplemente convertiría su imagen en color a escala de grises y trabajaría solo con eso (no es necesario trabajar en los 3 canales, el contraste presente ya es demasiado alto). Además, a menos que haya algún problema específico con respecto al cambio de tamaño, trabajaría con una versión reducida de sus imágenes, ya que son relativamente grandes y el tamaño no agrega nada al problema que se está resolviendo. Luego, finalmente, su problema se resuelve con un filtro de mediana, algunas herramientas morfológicas básicas y estadísticas (principalmente para el umbral de Otsu, que ya está hecho para usted).

Esto es lo que obtengo con su imagen de muestra y alguna otra imagen con una hoja de papel que encontré a mi alrededor:

enter image description hereenter image description here

El filtro de la mediana se usa para eliminar detalles menores de la imagen, ahora en escala de grises. Posiblemente eliminará las líneas delgadas dentro del papel blanquecino, lo cual es bueno porque luego terminará con componentes pequeños conectados que son fáciles de descartar. Después de la mediana, aplica un gradiente morfológico (simplemente dilationerosion ) y binariza el resultado por Otsu. El gradiente morfológico es un buen método para mantener los bordes fuertes, se debe utilizar más. Luego, dado que este gradiente boostá el ancho del contorno, aplique un adelgazamiento morfológico. Ahora puedes descartar componentes pequeños.

En este punto, aquí está lo que tenemos con la imagen de la derecha de arriba (antes de dibujar el polígono azul), el de la izquierda no se muestra porque el único componente restante es el que describe el papel:

enter image description here

Dados los ejemplos, ahora el único problema que queda es distinguir entre componentes que parecen rectangularjs y otros que no. Se trata de determinar una relación entre el área del casco convexo que contiene la forma y el área de su caja delimitadora; la relación 0.7 funciona bien para estos ejemplos. Puede ser que también necesite descartar los componentes que están dentro del documento, pero no en estos ejemplos utilizando este método (sin embargo, hacer este paso debería ser muy fácil, especialmente porque se puede hacer a través de OpenCV directamente).

Como referencia, aquí hay un código de muestra en Mathematica:

 f = Import["http://thwartedglamour.files.wordpress.com/2010/06/my-coffee-table-1-sa.jpg"] f = ImageResize[f, ImageDimensions[f][[1]]/4] g = MedianFilter[ColorConvert[f, "Grayscale"], 2] h = DeleteSmallComponents[Thinning[ Binarize[ImageSubtract[Dilation[g, 1], Erosion[g, 1]]]]] convexvert = ComponentMeasurements[SelectComponents[ h, {"ConvexArea", "BoundingBoxArea"}, #1 / #2 > 0.7 &], "ConvexVertices"][[All, 2]] (* To visualize the blue polygons above: *) Show[f, Graphics[{EdgeForm[{Blue, Thick}], RGBColor[0, 0, 1, 0.5], Polygon @@ convexvert}]] 

Si hay situaciones más variadas donde el rectángulo del papel no está tan bien definido, o el enfoque lo confunde con otras formas, estas situaciones podrían ocurrir debido a varias razones, pero una causa común es la mala adquisición de imágenes, entonces intente combinar el pre -transforma pasos con el trabajo descrito en el documento “Detección de rectángulo basado en una transformación Hough Windowed”.

Bueno, llegué tarde.


En su imagen, el papel es white , mientras que el fondo es de colored . Por lo tanto, es mejor detectar que el papel es un canal de Saturation(饱和度) en el HSV color space . Tome refiriéndose a la wiki HSL_and_HSV primero. Luego copiaré la mayoría de la idea de mi respuesta en este Segmento de color detectado en una imagen .


Pasos principales:

  1. Lea en BGR
  2. Convierte la imagen de bgr a hsv space
  3. Umbral del canal S
  4. Luego encuentra el contorno externo máximo (o haz Canny , o HoughLines como quieras, elijo findContours ), aproximadamente para obtener las esquinas.

Este es mi resultado:

enter image description here


El código de Python (Python 3.5 + OpenCV 3.3):

 #!/usr/bin/python3 # 2017.12.20 10:47:28 CST # 2017.12.20 11:29:30 CST import cv2 import numpy as np ##(1) read into bgr-space img = cv2.imread("test2.jpg") ##(2) convert to hsv-space, then split the channels hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) h,s,v = cv2.split(hsv) ##(3) threshold the S channel using adaptive method(`THRESH_OTSU`) or fixed thresh th, threshed = cv2.threshold(s, 50, 255, cv2.THRESH_BINARY_INV) ##(4) find all the external contours on the threshed S _, cnts, _ = cv2.findContours(threshed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) canvas = img.copy() #cv2.drawContours(canvas, cnts, -1, (0,255,0), 1) ## sort and choose the largest contour cnts = sorted(cnts, key = cv2.contourArea) cnt = cnts[-1] ## approx the contour, so the get the corner points arclen = cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, 0.02* arclen, True) cv2.drawContours(canvas, [cnt], -1, (255,0,0), 1, cv2.LINE_AA) cv2.drawContours(canvas, [approx], -1, (0, 0, 255), 1, cv2.LINE_AA) ## Ok, you can see the result as tag(6) cv2.imwrite("detected.png", canvas) 

Respuestas relacionadas:

  1. Detectar segmento coloreado en una imagen
  2. Detección de bordes en Android OpenCV
  3. OpenCV C ++ / Obj-C: detección de una hoja de papel / detección cuadrada

Lo que necesitas es un cuadrilátero en lugar de un rectángulo girado. RotatedRect le dará resultados incorrectos. También necesitarás una proyección en perspectiva.

Básicamente lo que se debe hacer es:

  • Pasa por todos los segmentos del polígono y conecta los que están casi en equel.
  • Ordenelos para que tenga los 4 segmentos de línea más grandes.
  • Intersecta esas líneas y tienes los 4 puntos de esquina más probables.
  • Transforma la matriz sobre la perspectiva obtenida de los puntos de las esquinas y la relación de aspecto del objeto conocido.

Implementé un Quadrangle clase que se encarga de la conversión de contorno a cuadrángulo y también lo transformará en la perspectiva correcta.

Vea una implementación que funciona aquí: Java OpenCV enderezar un contorno

Detectar hojas de papel es un poco de la vieja escuela. Si desea abordar la detección de sesgo, entonces es mejor si apunta directamente a la detección de línea de texto. Con esto obtendrás los extremos a la izquierda, derecha, arriba y abajo. Deseche cualquier gráfico en la imagen si no lo desea y luego haga algunas estadísticas en los segmentos de línea de texto para encontrar el rango de ángulo más común o más bien el ángulo. Así es como se reducirá a un buen ángulo de inclinación. Ahora después de esto, coloca estos parámetros en el ángulo de inclinación y los extremos para alinear y cortar la imagen según lo requerido.

En cuanto al requisito de imagen actual, es mejor si prueba CV_RETR_EXTERNAL en lugar de CV_RETR_LIST.

Otro método para detectar bordes es entrenar un clasificador de bosques al azar en los bordes del papel y luego usar el clasificador para obtener el borde del mapa. Este es, de lejos, un método robusto pero requiere entrenamiento y tiempo.

Los bosques aleatorios funcionarán con escenarios de baja diferencia de contraste, por ejemplo, papel blanco sobre fondo blanco.