thresh retr_tree python3 how findcontours drawcontours contours contourarea contornos chain_approx_simple centroide opencv image-processing opencv-contour

retr_tree - how to use contours in opencv



OpenCV afila los bordes(bordes sin agujeros) (2)

Gracias por tus comentarios y sugerencias. El código provisto por @NEJC funciona perfectamente y cubre el 80% de mi caso de uso.

Sin embargo, no funciona con casos similares como este caso no resuelto por el código actual y no sé por qué.

Tal vez alguien tiene una idea / pista / solución?

Continúo mejorando el código y trato de encontrar una solución más genérica que pueda abarcar más casos. Lo publicaré si alguna vez lo encuentro.

En cualquier caso, a continuación se muestra el código de trabajo basado en la solución y las notas de @NEJC.

public static Mat process(Mat original){ Mat src = original.clone(); Mat hsvMat = new Mat(); Mat saturation = new Mat(); Mat sobx = new Mat(); Mat soby = new Mat(); Mat grad_abs_val_approx = new Mat(); Imgproc.cvtColor(src, hsvMat, Imgproc.COLOR_BGR2HSV); List<Mat> hsv_channels = new ArrayList<Mat>(3); Core.split(hsvMat, hsv_channels); Mat hue = hsv_channels.get( 0 ); Mat sat = hsv_channels.get( 1 ); Mat val = hsv_channels.get( 2 ); Imgproc.GaussianBlur(sat, saturation, new Size(9, 9), 2, 2); Mat imf = new Mat(); saturation.convertTo(imf, CV_32FC1, 0.5f, 0.5f); Imgproc.Sobel(imf, sobx, -1, 1, 0); Imgproc.Sobel(imf, soby, -1, 0, 1); sobx = sobx.mul(sobx); soby = soby.mul(soby); Mat sumxy = new Mat(); Core.add(sobx,soby, sumxy); Core.pow(sumxy, 0.5, grad_abs_val_approx); sobx.release(); soby.release(); sumxy.release();; Mat filtered = new Mat(); Imgproc.GaussianBlur(grad_abs_val_approx, filtered, new Size(9, 9), 2, 2); final MatOfDouble mean = new MatOfDouble(); final MatOfDouble stdev = new MatOfDouble(); Core.meanStdDev(filtered, mean, stdev); Mat thresholded = new Mat(); Imgproc.threshold(filtered, thresholded, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_TOZERO); /* Mat thresholded_bin = new Mat(); Imgproc.threshold(filtered, thresholded_bin, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_BINARY_INV); Mat converted = new Mat(); thresholded_bin.convertTo(converted, CV_8UC1); */ Mat converted = new Mat(); thresholded.convertTo(converted, CV_8UC1); return converted; }

Intento detectar la forma rectangular más grande / más grande y dibujar el cuadro delimitador en el área detectada. En mi caso de uso, muy a menudo (y no siempre) el objeto que representa la forma del rectángulo es de color blanco y el fondo también tiene un color muy similar al blanco.

Antes de detectar contornos, preprocesé la imagen para detectar el borde perfecto. Mi problema es que no puedo detectar los bordes perfectamente y tengo mucho ruido incluso después de difuminar y usar ''umbral adaptativo'' o ''umbral''.

La imagen original que he usado para la detección de contornos

He intentado una manera diferente de detectar el borde perfecto en diferentes condiciones de iluminación sin éxito.

¿Cómo puedo procesar la imagen para detectar bordes perfectos (bordes sin agujeros) para la detección del contorno?

A continuación está el código que estoy usando

public static Mat findRectangleX(Mat original) { Mat src = original.clone(); Mat gray = new Mat(); Mat binary = new Mat(); MatOfPoint2f approxCurve; List<MatOfPoint> contours = new ArrayList<MatOfPoint>(); if (original.type() != CvType.CV_8U) { Imgproc.cvtColor(original, gray, Imgproc.COLOR_BGR2GRAY); } else { original.copyTo(gray); } Imgproc.GaussianBlur(gray, gray, new Size(5,5),0); Imgproc.adaptiveThreshold(gray, binary, 255,Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,Imgproc.THRESH_BINARY_INV,11, 1); //Imgproc.threshold(gray, binary,0,255,Imgproc.THRESH_BINARY_INV | Imgproc.THRESH_OTSU); double maxArea = 0; Imgproc.findContours(binary, contours, new Mat(),Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE); for (int i = 0; i<contours.size();i++) { MatOfPoint contour = contours.get(i); MatOfPoint2f temp = new MatOfPoint2f(contour.toArray()); double area = Imgproc.contourArea(contour); approxCurve = new MatOfPoint2f(); Imgproc.approxPolyDP(temp, approxCurve, Imgproc.arcLength(temp, true) * 0.03, true); if (approxCurve.total() == 4 ) { Rect rect = Imgproc.boundingRect(contours.get(i)); Imgproc.rectangle(src, rect.tl(), rect.br(), new Scalar(255, 0, 0, .8), 4); if(maxArea < area) maxArea = area; } } Log.v(TAG, "Total contours found : " + contours.size()); Log.v(TAG, "Max area :" + maxArea); return src; }

He buscado problemas similares en stackoverflow y he intentado con la muestra del código, pero cualquiera de ellos funcionó para mí. La dificultad creo que es el objeto blanco sobre fondo blanco.

¿Cómo puedo procesar la imagen para agudizar los bordes para la detección del contorno?

¿Cómo puedo detectar la forma de rectángulo más grande / más grande y dibujar una línea rectangular en la forma detectada?

// Actualizado en: 20/02/2017

He intentado con la solución sugerida por @Nejc en la publicación a continuación. La segmentación es mejor, pero todavía tengo agujeros en el contorno y encuentro que los contactos no logran detectar el contorno más grande. A continuación se muestra el código proporcionado por @Nejc y traducido a java.

public static Mat process(Mat original){ Mat src = original.clone(); Mat hsvMat = new Mat(); Mat saturation = new Mat(); Mat sobx = new Mat(); Mat soby = new Mat(); Mat grad_abs_val_approx = new Mat(); Imgproc.cvtColor(src, hsvMat, Imgproc.COLOR_BGR2HSV); List<Mat> hsv_channels = new ArrayList<Mat>(3); Core.split(hsvMat, hsv_channels); Mat hue = hsv_channels.get( 0 ); Mat sat = hsv_channels.get( 1 ); Mat val = hsv_channels.get( 2 ); Imgproc.GaussianBlur(sat, saturation, new Size(9, 9), 2, 2); Mat imf = new Mat(); saturation.convertTo(imf, CV_32FC1, 0.5f, 0.5f); Imgproc.Sobel(imf, sobx, -1, 1, 0); Imgproc.Sobel(imf, soby, -1, 0, 1); sobx = sobx.mul(sobx); soby = soby.mul(soby); Mat abs_x = new Mat(); Core.convertScaleAbs(sobx,abs_x); Mat abs_y = new Mat(); Core.convertScaleAbs(soby,abs_y); Core.addWeighted(abs_x, 1, abs_y, 1, 0, grad_abs_val_approx); sobx.release(); soby.release(); Mat filtered = new Mat(); Imgproc.GaussianBlur(grad_abs_val_approx, filtered, new Size(9, 9), 2, 2); final MatOfDouble mean = new MatOfDouble(); final MatOfDouble stdev = new MatOfDouble(); Core.meanStdDev(filtered, mean, stdev); Mat thresholded = new Mat(); Imgproc.threshold(filtered, thresholded, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_TOZERO); /* Mat thresholded_bin = new Mat(); Imgproc.threshold(filtered, thresholded_bin, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_BINARY); Mat converted = new Mat(); thresholded_bin.convertTo(converted, CV_8UC1); */ return thresholded; }

Aquí está la imagen que tengo después de ejecutar el código anterior

Imagen después de usar la solución @Nejc

1) ¿Por qué mi código traducido no muestra la misma imagen que @Nejc? El mismo código aplicado a la misma imagen debería producir el mismo resultado?

2) ¿extrañé algo cuando traduje?

3) Para mi comprensión, ¿por qué multiplicamos la imagen por sí mismo en esta instrucción? Sobx = sobx.mul (sobx); ?


Logré obtener una imagen muy bonita del borde al calcular una aproximación del valor absoluto del gradiente de la imagen de entrada.

EDITAR: Antes de empezar a trabajar, cambié el tamaño de la imagen de entrada a un tamaño 5x más pequeño. ¡Haz click aquí para verlo! . Si usa mi código en esa imagen, los resultados serán buenos. Si desea que mi código funcione bien con la imagen del tamaño original, entonces:

  • multiplicar los tamaños y sigmas del kernel gaussiano por 5, o
  • Resume la imagen por el factor 5, ejecuta el algoritmo y luego muestra el resultado por el factor 5 (esto debería funcionar mucho más rápido que la primera opción)

Este es el resultado que obtuve:

Mi procedimiento se basa en dos características clave. El primero es una conversión al espacio de color apropiado. Como dijo Jeru Luke en su respuesta , el canal de saturación en el espacio de color HSV es la mejor opción aquí. La segunda cosa importante es la estimación del valor absoluto del gradiente. Usé operadores de sobel y algunos aritméticos para ese propósito. Puedo proporcionar explicaciones adicionales si alguien las solicita.

Este es el código que utilicé para obtener la primera imagen.

using namespace std; using namespace cv; Mat img_rgb = imread("letter.jpg"); Mat img_hsv; cvtColor(img_rgb, img_hsv, CV_BGR2HSV); vector<Mat> channels_hsv; split(img_hsv, channels_hsv); Mat channel_s = channels_hsv[1]; GaussianBlur(channel_s, channel_s, Size(9, 9), 2, 2); Mat imf; channel_s.convertTo(imf, CV_32FC1, 0.5f, 0.5f); Mat sobx, soby; Sobel(imf, sobx, -1, 1, 0); Sobel(imf, soby, -1, 0, 1); sobx = sobx.mul(sobx); soby = soby.mul(soby); Mat grad_abs_val_approx; cv::pow(sobx + soby, 0.5, grad_abs_val_approx); Mat filtered; GaussianBlur(grad_abs_val_approx, filtered, Size(9, 9), 2, 2); Scalar mean, stdev; meanStdDev(filtered, mean, stdev); Mat thresholded; cv::threshold(filtered, thresholded, mean.val[0] + stdev.val[0], 1.0, CV_THRESH_TOZERO); // I scale the image at this point so that it is displayed properly imshow("image", thresholded/50);

Y así es como calculé la segunda imagen:

Mat thresholded_bin; cv::threshold(filtered, thresholded_bin, mean.val[0] + stdev.val[0], 1.0, CV_THRESH_BINARY); Mat converted; thresholded_bin.convertTo(converted, CV_8UC1); vector<vector<Point>> contours; findContours(converted, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE); Mat contour_img = Mat::zeros(converted.size(), CV_8UC1); drawContours(contour_img, contours, -1, 255); imshow("contours", contour_img);