ios opencv uiimage avfoundation edge-detection

ios - Detección de bordes de una tarjeta con esquinas redondeadas



opencv uiimage (4)

Hola, actualmente estoy trabajando en una aplicación de lectura de OCR, donde pude capturar la imagen de la tarjeta con el framework AVFoundation.

Para el siguiente paso, necesito encontrar los bordes de la tarjeta, para poder recortar la imagen de la imagen principal capturada y luego puedo enviarla al motor de OCR para su procesamiento.

El problema principal ahora es encontrar los bordes de la tarjeta y estoy usando el código de abajo (tomado de otro proyecto de código abierto) que usa OpenCV para este propósito. Está funcionando bien si la tarjeta es una tarjeta o papel rectangular puro. Pero cuando uso una tarjeta con una esquina redondeada (por ejemplo, permiso de conducir), no se detecta. Además, no tengo mucha experiencia en OpenCV, ¿alguien me puede ayudar a resolver este problema?

- (void)detectEdges { cv::Mat original = [MAOpenCV cvMatFromUIImage:_adjustedImage]; CGSize targetSize = _sourceImageView.contentSize; cv::resize(original, original, cvSize(targetSize.width, targetSize.height)); cv::vector<cv::vector<cv::Point>>squares; cv::vector<cv::Point> largest_square; find_squares(original, squares); find_largest_square(squares, largest_square); if (largest_square.size() == 4) { // Manually sorting points, needs major improvement. Sorry. NSMutableArray *points = [NSMutableArray array]; NSMutableDictionary *sortedPoints = [NSMutableDictionary dictionary]; for (int i = 0; i < 4; i++) { NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSValue valueWithCGPoint:CGPointMake(largest_square[i].x, largest_square[i].y)], @"point" , [NSNumber numberWithInt:(largest_square[i].x + largest_square[i].y)], @"value", nil]; [points addObject:dict]; } int min = [[points valueForKeyPath:@"@min.value"] intValue]; int max = [[points valueForKeyPath:@"@max.value"] intValue]; int minIndex; int maxIndex; int missingIndexOne; int missingIndexTwo; for (int i = 0; i < 4; i++) { NSDictionary *dict = [points objectAtIndex:i]; if ([[dict objectForKey:@"value"] intValue] == min) { [sortedPoints setObject:[dict objectForKey:@"point"] forKey:@"0"]; minIndex = i; continue; } if ([[dict objectForKey:@"value"] intValue] == max) { [sortedPoints setObject:[dict objectForKey:@"point"] forKey:@"2"]; maxIndex = i; continue; } NSLog(@"MSSSING %i", i); missingIndexOne = i; } for (int i = 0; i < 4; i++) { if (missingIndexOne != i && minIndex != i && maxIndex != i) { missingIndexTwo = i; } } if (largest_square[missingIndexOne].x < largest_square[missingIndexTwo].x) { //2nd Point Found [sortedPoints setObject:[[points objectAtIndex:missingIndexOne] objectForKey:@"point"] forKey:@"3"]; [sortedPoints setObject:[[points objectAtIndex:missingIndexTwo] objectForKey:@"point"] forKey:@"1"]; } else { //4rd Point Found [sortedPoints setObject:[[points objectAtIndex:missingIndexOne] objectForKey:@"point"] forKey:@"1"]; [sortedPoints setObject:[[points objectAtIndex:missingIndexTwo] objectForKey:@"point"] forKey:@"3"]; } [_adjustRect topLeftCornerToCGPoint:[(NSValue *)[sortedPoints objectForKey:@"0"] CGPointValue]]; [_adjustRect topRightCornerToCGPoint:[(NSValue *)[sortedPoints objectForKey:@"1"] CGPointValue]]; [_adjustRect bottomRightCornerToCGPoint:[(NSValue *)[sortedPoints objectForKey:@"2"] CGPointValue]]; [_adjustRect bottomLeftCornerToCGPoint:[(NSValue *)[sortedPoints objectForKey:@"3"] CGPointValue]]; } original.release(); }


No sé si es una opción, pero podría hacer que el usuario defina los bordes del mismo en lugar de intentar hacerlo programáticamente.


en lugar de blobs rectangulares "puros", intenta ir por los rectangulares.

1- desenfoque gaussiano

2- Escala de grises y detección de bordes astutos

3- extraer todos los blobs (contornos) en su imagen y filtrar los pequeños. utilizará findcontours y contourarea funciones para ese fin.

4- usando momentos , filtra los que no son rectangulares. Primero debe verificar los momentos de objetos rectangulares. Puedes hacerlo tú mismo o googlearlo. Luego enumere esos momentos y encuentre similitud entre los objetos, cree su filtro como tal.

Ej: Después de la prueba, digamos que descubrió que el momento central m30 es similar para objetos rectangulares -> filtra objetos que tienen un m30 inexacto.


Sé que quizás sea demasiado tarde para esta publicación, pero estoy publicando esto en caso de que pueda ayudar a otra persona.

El marco de iOS Core Image ya tiene una buena herramienta para detectar funciones como rectángulos (desde iOS 5), rostros, códigos QR e incluso regiones que contienen texto en una imagen fija. Si revisa la clase CIDetector , encontrará lo que necesita. Lo estoy usando también para una aplicación de OCR, es muy fácil y muy confiable comparado con lo que puedes hacer con OpenCV (no soy bueno con OpenCV, pero el CIDetector da mejores resultados con 3-5 líneas de código).


Esta implementación ingenua se basa en algunas de las técnicas demostradas en squares.cpp , disponible en el directorio de ejemplo de OpenCV. Las siguientes publicaciones también discuten aplicaciones similares:

  • OpenCV C ++ / Obj-C: detección de una hoja de papel / detección cuadrada
  • La detección de cuadrados no encuentra cuadrados
  • Encuentra el rincón de los papeles

@John, el código siguiente se ha probado con la imagen de muestra que proporcionó y otra que creé:

La línea de procesamiento comienza con findSquares() , una simplificación de la misma función implementada por la demostración squares.cpp de OpenCV. Esta función convierte la imagen de entrada a escala de grises y aplica un desenfoque para mejorar la detección de los bordes (Canny):

La detección de bordes es buena, pero se necesita una operación morfológica (dilatación) para unir las líneas cercanas:

Después de eso tratamos de encontrar los contornos (bordes) y ensamblar cuadrados de ellos. Si tratamos de dibujar todos los cuadrados detectados en las imágenes de entrada, este sería el resultado:

Se ve bien, pero no es exactamente lo que estamos buscando, ya que hay demasiados cuadrados detectados. Sin embargo, el cuadrado más grande es en realidad la carta, así que a partir de ahora es bastante simple y solo averiguamos cuál de los cuadrados es el más grande. Eso es exactamente lo que findLargestSquare() hace.

Una vez que conocemos el cuadrado más grande, simplemente pintamos puntos rojos en las esquinas del cuadrado con fines de depuración:

Como puede ver, la detección no es perfecta, pero parece lo suficientemente buena para la mayoría de los usos. Esta no es una solución sólida y solo quería compartir un enfoque para resolver el problema. Estoy seguro de que hay otras maneras de lidiar con esto que podrían ser más interesantes para usted. ¡Buena suerte!

#include <iostream> #include <cmath> #include <vector> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/imgproc/imgproc_c.h> /* angle: finds a cosine of angle between vectors, from pt0->pt1 and from pt0->pt2 */ 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); } /* findSquares: returns sequence of squares detected on the image */ void findSquares(const cv::Mat& src, std::vector<std::vector<cv::Point> >& squares) { cv::Mat src_gray; cv::cvtColor(src, src_gray, cv::COLOR_BGR2GRAY); // Blur helps to decrease the amount of detected edges cv::Mat filtered; cv::blur(src_gray, filtered, cv::Size(3, 3)); cv::imwrite("out_blur.jpg", filtered); // Detect edges cv::Mat edges; int thresh = 128; cv::Canny(filtered, edges, thresh, thresh*2, 3); cv::imwrite("out_edges.jpg", edges); // Dilate helps to connect nearby line segments cv::Mat dilated_edges; cv::dilate(edges, dilated_edges, cv::Mat(), cv::Point(-1, -1), 2, 1, 1); // default 3x3 kernel cv::imwrite("out_dilated.jpg", dilated_edges); // Find contours and store them in a list std::vector<std::vector<cv::Point> > contours; cv::findContours(dilated_edges, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE); // Test contours and assemble squares out of them std::vector<cv::Point> approx; for (size_t i = 0; i < contours.size(); i++) { // approximate contour with accuracy proportional to the contour perimeter cv::approxPolyDP(cv::Mat(contours[i]), approx, cv::arcLength(cv::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 && std::fabs(contourArea(cv::Mat(approx))) > 1000 && cv::isContourConvex(cv::Mat(approx))) { double maxCosine = 0; for (int j = 2; j < 5; j++) { double cosine = std::fabs(angle(approx[j%4], approx[j-2], approx[j-1])); maxCosine = MAX(maxCosine, cosine); } if (maxCosine < 0.3) squares.push_back(approx); } } } /* findLargestSquare: find the largest square within a set of squares */ void findLargestSquare(const std::vector<std::vector<cv::Point> >& squares, std::vector<cv::Point>& biggest_square) { if (!squares.size()) { std::cout << "findLargestSquare !!! No squares detect, nothing to do." << std::endl; return; } int max_width = 0; int max_height = 0; int max_square_idx = 0; for (size_t i = 0; i < squares.size(); i++) { // Convert a set of 4 unordered Points into a meaningful cv::Rect structure. cv::Rect rectangle = cv::boundingRect(cv::Mat(squares[i])); //std::cout << "find_largest_square: #" << i << " rectangle x:" << rectangle.x << " y:" << rectangle.y << " " << rectangle.width << "x" << rectangle.height << endl; // Store the index position of the biggest square found if ((rectangle.width >= max_width) && (rectangle.height >= max_height)) { max_width = rectangle.width; max_height = rectangle.height; max_square_idx = i; } } biggest_square = squares[max_square_idx]; } int main() { cv::Mat src = cv::imread("cc.png"); if (src.empty()) { std::cout << "!!! Failed to open image" << std::endl; return -1; } std::vector<std::vector<cv::Point> > squares; findSquares(src, squares); // Draw all detected squares cv::Mat src_squares = src.clone(); for (size_t i = 0; i < squares.size(); i++) { const cv::Point* p = &squares[i][0]; int n = (int)squares[i].size(); cv::polylines(src_squares, &p, &n, 1, true, cv::Scalar(0, 255, 0), 2, CV_AA); } cv::imwrite("out_squares.jpg", src_squares); cv::imshow("Squares", src_squares); std::vector<cv::Point> largest_square; findLargestSquare(squares, largest_square); // Draw circles at the corners for (size_t i = 0; i < largest_square.size(); i++ ) cv::circle(src, largest_square[i], 4, cv::Scalar(0, 0, 255), cv::FILLED); cv::imwrite("out_corners.jpg", src); cv::imshow("Corners", src); cv::waitKey(0); return 0; }