Implementé con éxito el ejemplo de detección cuadrada de OpenCV en mi aplicación de prueba, pero ahora necesito filtrar la salida, porque es bastante desordenado, ¿o está mal mi código?
Estoy interesado en las cuatro esquinas del papel para la reducción del sesgo (como que ) y su posterior procesamiento ...
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;
}
Respuestas:
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 código C ++ resultante a continuación es capaz de detectar una hoja de papel en la imagen:
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 cuadrado más grande. ;)
fuente
for (int c = 0; c < 3; c++)
, que es responsable de iterar en cada canal de la imagen. Por ejemplo, puede configurarlo para que itere solo en un canal :) No olvide votar.angle()
es una función auxiliar . Como se indica en la respuesta, este código se basa en samples / cpp / squares.cpp presente en OpenCV.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. 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é alrededor:
El filtro de mediana se utiliza para eliminar detalles menores de la imagen, ahora en escala de grises. Posiblemente eliminará líneas finas dentro del papel blanquecino, lo cual es bueno porque luego terminará con pequeños componentes conectados que son fáciles de descartar. Después de la mediana, aplique un gradiente morfológico (simplemente
dilation
-erosion
) y binarice el resultado por Otsu. El gradiente morfológico es un buen método para mantener bordes fuertes, debe usarse 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 arriba (antes de dibujar el polígono azul), el izquierdo no se muestra porque el único componente restante es el que describe el papel:
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 cuadro delimitador; la relación 0.7 funciona bien para estos ejemplos. Es posible que también deba descartar los componentes que están dentro del papel, pero no en estos ejemplos al usar 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:
Si hay situaciones más variadas en las que 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 -procesos de procesamiento con el trabajo descrito en el documento "Detección de rectángulos basada en una transformación de ventana de Hough".
fuente
Concept is the same
. (Nunca usé Mathematica, por lo que no puedo entender el código). Y las diferencias que mencionó son diferencias, pero no un enfoque diferente o uno importante. Si aún no lo hizo, por ejemplo, verifique esto:Bueno, llego tarde.
En su imagen, el papel es
white
, mientras que el fondo escolored
. Por lo tanto, es mejor detectar que el papel estáSaturation(饱和度)
canalizadoHSV color space
. Tome referirse a wiki HSL_and_HSV primero. Luego, copiaré la mayoría de las ideas de mi respuesta en este Detectar segmento de color en una imagen .Pasos principales:
BGR
bgr
dehsv
espacioCanny
, oHoughLines
como quiera, elijofindContours
), aproximadamente para obtener las esquinas.Este es mi resultado:
El código de Python (Python 3.5 + OpenCV 3.3):
Respuestas relacionadas:
fuente
Lo que necesita es un cuadrángulo en lugar de un rectángulo girado.
RotatedRect
te dará resultados incorrectos. También necesitará una proyección en perspectiva.Básicamente lo que se debe hacer es:
Implementé una clase
Quadrangle
que se encarga de la conversión de contorno a cuadrilátero y también la transformará en la perspectiva correcta.Vea una implementación funcional aquí: Java OpenCV que diseña un contorno
fuente
Una vez que haya detectado el cuadro delimitador del documento, puede realizar una transformación de perspectiva de cuatro puntos para obtener una vista de arriba hacia abajo de la imagen. Esto solucionará la inclinación y aislará solo el objeto deseado.
Imagen de entrada:
Objeto de texto detectado
Vista de arriba hacia abajo del documento de texto
Código
fuente
Detectar una hoja de papel es algo de la vieja escuela. Si desea abordar la detección de sesgo, es mejor que apunte inmediatamente a la detección de líneas de texto. Con esto obtendrás los extremos a la izquierda, derecha, arriba y abajo. Descarte 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 ángulo. Así es como se reducirá a un buen ángulo de inclinación. Ahora, después de esto, coloque estos parámetros, el ángulo de inclinación y los extremos para enderezar y cortar la imagen a lo que se requiere.
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 aleatorio de bosques 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 capacitación y tiempo.
Los bosques aleatorios funcionarán con escenarios de diferencia de bajo contraste, por ejemplo, papel blanco sobre fondo más o menos blanco.
fuente