OpenCV C++ / Obj-C: Detección de una hoja de papel / Detección de cuadrados


Implementé con éxito el ejemplo de detección de cuadrados OpenCV en mi aplicación de prueba, pero ahora necesito filtrar la salida, porque es bastante desordenada, ¿o mi código es incorrecto?

Estoy interesado en los cuatro puntos de esquina del papel para la reducción de sesgo (como que ) y el procesamiento posterior {

Entrada y salida: Entrada y salida

Imagen original:

Haga clic en

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<cv::Point> >)findSquaresInImage:(cv::Mat)_image
{
    std::vector<std::vector<cv::Point> > 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<cv::Point> > contours;
    for( int c = 0; c < 3; c++ ) {
        int ch[] = {c, 0};
        mixChannels(&timg, 1, &gray0, 1, ch, 1);
        for( int l = 0; l < N; l++ ) {
            if( l == 0 ) {
                cv::Canny(gray0, gray, 0, thresh, 5);
                cv::dilate(gray, gray, cv::Mat(), cv::Point(-1,-1));
            }
            else {
                gray = gray0 >= (l+1)*255/N;
            }
            cv::findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
            std::vector<cv::Point> approx;
            for( size_t i = 0; i < contours.size(); i++ )
            {
                cv::approxPolyDP(cv::Mat(contours[i]), approx, arcLength(cv::Mat(contours[i]), true)*0.02, true);
                if( approx.size() == 4 && fabs(contourArea(cv::Mat(approx))) > 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 use este código:

cv::Mat debugSquares( std::vector<std::vector<cv::Point> > 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<cv::Vec4i>(), 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;
}
Author: Eric Platon, 2011-12-29

5 answers

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 demo de cuadrados presente en OpenCV y el código C++ resultante a continuación es capaz de detectar una hoja de papel en la imagen:

void find_squares(Mat& image, vector<vector<Point> >& squares)
{
    // blur will enhance edge detection
    Mat blurred(image);
    medianBlur(image, blurred, 9);

    Mat gray0(blurred.size(), CV_8U), gray;
    vector<vector<Point> > 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<Point> 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 ejecutar este procedimiento, la hoja de papel será el cuadrado más grande en vector<vector<Point> >:

detección de hojas de papel opencv

Te dejo escribir la función para encontrar el mayor Plaza. ;)

 144
Author: karlphillip,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-14 15:10:09

A menos que haya algún otro requisito 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. Entonces, finalmente, su problema se resuelve con un filtro mediano, algunos básicos herramientas morfológicas y estadísticas (principalmente para el umbral 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é alrededor:

introduzca la descripción de la imagen aquíintroduzca la descripción de la imagen aquí

El filtro mediana se usa para eliminar detalles menores de la imagen, ahora en escala de grises. Posiblemente eliminará las líneas finas dentro del papel blanquecino, lo cual es bueno porque entonces terminará con pequeños componentes conectados que son fáciles de descartar. Despues la mediana, aplicar un gradiente morfológico (simplemente dilation - erosion) y binarizar el resultado por Otsu. El gradiente morfológico es un buen método para mantener bordes fuertes, se debe usar más. Luego, dado que este gradiente aumentará el ancho del contorno, aplique un adelgazamiento morfológico. Ahora puede descartar componentes pequeños.

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

introduzca la descripción de la imagen aquí

Dados los ejemplos, ahora el único problema que queda es distinguir entre componentes que parecen rectángulos 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. Podría ser el caso de que también necesite descartar los componentes que están dentro del papel, pero no en estos ejemplos mediante el uso de este método (sin embargo, hacer este paso debe ser muy fácil, especialmente porque se puede hacer a través de OpenCV directamente).

Para referencia, aquí hay un código de ejemplo 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 these estas situaciones podrían ocurrir debido a varias razones, pero una causa común es la mala adquisición de imágenes try entonces intente combinar los pasos de preprocesamiento con el trabajo descrito en el artículo "Detección de rectángulos basada en una Transformada de Hough con ventana".

 37
Author: mmgp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-16 04:20:21

Bueno, llego tarde.


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


Pasos principales:

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

Este es mi resultado:

introduzca la descripción de la imagen aquí


El código 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 opencv android
  3. OpenCV C++ / Obj-C: Detección de una hoja de papel / Cuadrado Detección
 9
Author: Silencer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-20 12:19:36

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

Básicamente lo que se debe hacer es:

  • Recorre todos los segmentos de polígonos y conecta aquellos que son casi equel.
  • Ordénelos para que tenga los 4 segmentos de línea más grandes.
  • Interseca esas líneas y tienes los 4 puntos de esquina más probables.
  • Transformar la matriz sobre la perspectiva recogidos de los puntos de esquina y la relación de aspecto del objeto conocido.

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

Vea una implementación de trabajo aquí: Java OpenCV deskewing a contour

 2
Author: Tim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:31:16

Detectar una hoja de papel es algo de la vieja escuela. Si desea abordar la detección de sesgo, entonces es mejor si apunta directamente a la detección de líneas de texto. Con esto obtendrás las extremas izquierda, derecha, superior e inferior. 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 frecuente o más bien el ángulo. Así es como se reducirá a un buen ángulo de inclinación. Ahora después de esto pones estos parámetros el ángulo de inclinación y las extremas para desquitarse y picar la imagen a lo que se requiere.

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

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

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

 -1
Author: Anubhav Rohatgi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-14 12:23:53