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 {
Imagen original:
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;
}
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> >
:
Te dejo escribir la función para encontrar el mayor Plaza. ;)
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:
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:
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".
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:
- Leer en
BGR
- Convierte la imagen de
bgr
ahsv
espacio - Umbral del canal S
- Entonces encuentra el contorno externo máximo (o haz
Canny
, oHoughLines
como quieras, elijofindContours
), aprox para obtener las esquinas.
Este es mi resultado:
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:
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
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.
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