c++ opencv image-processing perspective skew

c++ - Ejecutando cv:: warpPerspective para un falso enderezado en un conjunto de cv:: Point



opencv image-processing (6)

ACTUALIZACIÓN: RESUELTO

Casi tengo esto funcionando. Tan cerca de ser usable Se adapta correctamente, pero parece que tengo un problema de escala o traducción. Establecí el punto de anclaje en cero y también experimenté cambiando el modo de escala (aspectFill, scale to fit, etc ...).

Configure los puntos de alineación (el rojo los hace difíciles de ver):

Aplicar la transformada calculada:

Ahora se tuerce. Esto se ve bastante bien, excepto que no está centrado en la pantalla. Al agregar un gesto panorámico a la vista de imagen, puedo arrastrarlo y verificar que se alinea:

Esto no es tan simple como traducir por -0.5, -0.5 porque la imagen original se convierte en un polígono que se extiende muy, muy lejos (potencialmente), por lo que su rectitud es mucho mayor que el marco de la pantalla.

¿Alguien ve lo que puedo hacer para terminar esto? Me gustaría comprometerlo y compartirlo aquí. Este es un tema popular, pero no he encontrado una solución que sea tan simple como copiar / pegar.

El código fuente completo está aquí:

git clone https://github.com/zakkhoyt/Quadrilateral.git

demo de pago de git

Sin embargo, pegaré las partes relevantes aquí. Este primer método es mío y es de donde obtengo los puntos de alineación.

- (IBAction)buttonAction:(id)sender { Quadrilateral quadFrom; float scale = 1.0; quadFrom.topLeft.x = self.topLeftView.center.x / scale; quadFrom.topLeft.y = self.topLeftView.center.y / scale; quadFrom.topRight.x = self.topRightView.center.x / scale; quadFrom.topRight.y = self.topRightView.center.y / scale; quadFrom.bottomLeft.x = self.bottomLeftView.center.x / scale; quadFrom.bottomLeft.y = self.bottomLeftView.center.y / scale; quadFrom.bottomRight.x = self.bottomRightView.center.x / scale; quadFrom.bottomRight.y = self.bottomRightView.center.y / scale; Quadrilateral quadTo; quadTo.topLeft.x = self.view.bounds.origin.x; quadTo.topLeft.y = self.view.bounds.origin.y; quadTo.topRight.x = self.view.bounds.origin.x + self.view.bounds.size.width; quadTo.topRight.y = self.view.bounds.origin.y; quadTo.bottomLeft.x = self.view.bounds.origin.x; quadTo.bottomLeft.y = self.view.bounds.origin.y + self.view.bounds.size.height; quadTo.bottomRight.x = self.view.bounds.origin.x + self.view.bounds.size.width; quadTo.bottomRight.y = self.view.bounds.origin.y + self.view.bounds.size.height; CATransform3D t = [self transformQuadrilateral:quadFrom toQuadrilateral:quadTo]; // t = CATransform3DScale(t, 0.5, 0.5, 1.0); self.imageView.layer.anchorPoint = CGPointZero; [UIView animateWithDuration:1.0 animations:^{ self.imageView.layer.transform = t; }]; } #pragma mark OpenCV stuff... -(CATransform3D)transformQuadrilateral:(Quadrilateral)origin toQuadrilateral:(Quadrilateral)destination { CvPoint2D32f *cvsrc = [self openCVMatrixWithQuadrilateral:origin]; CvMat *src_mat = cvCreateMat( 4, 2, CV_32FC1 ); cvSetData(src_mat, cvsrc, sizeof(CvPoint2D32f)); CvPoint2D32f *cvdst = [self openCVMatrixWithQuadrilateral:destination]; CvMat *dst_mat = cvCreateMat( 4, 2, CV_32FC1 ); cvSetData(dst_mat, cvdst, sizeof(CvPoint2D32f)); CvMat *H = cvCreateMat(3,3,CV_32FC1); cvFindHomography(src_mat, dst_mat, H); cvReleaseMat(&src_mat); cvReleaseMat(&dst_mat); CATransform3D transform = [self transform3DWithCMatrix:H->data.fl]; cvReleaseMat(&H); return transform; } - (CvPoint2D32f*)openCVMatrixWithQuadrilateral:(Quadrilateral)origin { CvPoint2D32f *cvsrc = (CvPoint2D32f *)malloc(4*sizeof(CvPoint2D32f)); cvsrc[0].x = origin.topLeft.x; cvsrc[0].y = origin.topLeft.y; cvsrc[1].x = origin.topRight.x; cvsrc[1].y = origin.topRight.y; cvsrc[2].x = origin.bottomRight.x; cvsrc[2].y = origin.bottomRight.y; cvsrc[3].x = origin.bottomLeft.x; cvsrc[3].y = origin.bottomLeft.y; return cvsrc; } -(CATransform3D)transform3DWithCMatrix:(float *)matrix { CATransform3D transform = CATransform3DIdentity; transform.m11 = matrix[0]; transform.m21 = matrix[1]; transform.m41 = matrix[2]; transform.m12 = matrix[3]; transform.m22 = matrix[4]; transform.m42 = matrix[5]; transform.m14 = matrix[6]; transform.m24 = matrix[7]; transform.m44 = matrix[8]; return transform; }

Actualización: lo hice funcionar correctamente. Las coordenadas deben ser origen en el centro, no en la parte superior. Apliqué xOffset y yOffset y viola. Código de demostración en la ubicación mencionada anteriormente (rama "demostración")

Estoy tratando de hacer una transformación de perspectiva de un conjunto de puntos para lograr un efecto de deskewing :

http://nuigroup.com/?ACT=28&fid=27&aid=1892_H6eNAaign4Mrnn30Au8d

Estoy usando la imagen a continuación para las pruebas, y el rectángulo verde muestra el área de interés.

Me preguntaba si es posible lograr el efecto que espero usando una combinación simple de cv::getPerspectiveTransform y cv::warpPerspective . Estoy compartiendo el código fuente que he escrito hasta ahora, pero no funciona. Esta es la imagen resultante:

Entonces hay un vector<cv::Point> que define la región de interés , pero los puntos no se almacenan en ningún orden particular dentro del vector, y eso es algo que no puedo cambiar en el procedimiento de detección. De todos modos, más adelante , los puntos en el vector se usan para definir un RotatedRect , que a su vez se usa para ensamblar cv::Point2f src_vertices[4]; , una de las variables requeridas por cv::getPerspectiveTransform() .

Mi comprensión sobre los vértices y cómo están organizados podría ser uno de los problemas . También creo que usar un RotatedRect no es la mejor idea para almacenar los puntos originales del ROI, ya que las coordenadas cambiarán un poco para encajar en el rectángulo girado, y eso no es muy bueno .

#include <cv.h> #include <highgui.h> #include <iostream> using namespace std; using namespace cv; int main(int argc, char* argv[]) { cv::Mat src = cv::imread(argv[1], 1); // After some magical procedure, these are points detect that represent // the corners of the paper in the picture: // [408, 69] [72, 2186] [1584, 2426] [1912, 291] vector<Point> not_a_rect_shape; not_a_rect_shape.push_back(Point(408, 69)); not_a_rect_shape.push_back(Point(72, 2186)); not_a_rect_shape.push_back(Point(1584, 2426)); not_a_rect_shape.push_back(Point(1912, 291)); // For debugging purposes, draw green lines connecting those points // and save it on disk const Point* point = &not_a_rect_shape[0]; int n = (int)not_a_rect_shape.size(); Mat draw = src.clone(); polylines(draw, &point, &n, 1, true, Scalar(0, 255, 0), 3, CV_AA); imwrite("draw.jpg", draw); // Assemble a rotated rectangle out of that info RotatedRect box = minAreaRect(cv::Mat(not_a_rect_shape)); std::cout << "Rotated box set to (" << box.boundingRect().x << "," << box.boundingRect().y << ") " << box.size.width << "x" << box.size.height << std::endl; // Does the order of the points matter? I assume they do NOT. // But if it does, is there an easy way to identify and order // them as topLeft, topRight, bottomRight, bottomLeft? cv::Point2f src_vertices[4]; src_vertices[0] = not_a_rect_shape[0]; src_vertices[1] = not_a_rect_shape[1]; src_vertices[2] = not_a_rect_shape[2]; src_vertices[3] = not_a_rect_shape[3]; Point2f dst_vertices[4]; dst_vertices[0] = Point(0, 0); dst_vertices[1] = Point(0, box.boundingRect().width-1); dst_vertices[2] = Point(0, box.boundingRect().height-1); dst_vertices[3] = Point(box.boundingRect().width-1, box.boundingRect().height-1); Mat warpMatrix = getPerspectiveTransform(src_vertices, dst_vertices); cv::Mat rotated; warpPerspective(src, rotated, warpMatrix, rotated.size(), INTER_LINEAR, BORDER_CONSTANT); imwrite("rotated.jpg", rotated); return 0; }

¿Alguien puede ayudarme a solucionar este problema?


Cuando se trabaja con un cuadrángulo, OpenCV no es realmente tu amigo. RotatedRect le dará resultados incorrectos. También necesitará una proyección de perspectiva en lugar de una proyección afín como las mencionadas aquí.

Básicamente lo que se debe hacer es:

  • Pasa por todos los segmentos del polígono y conecta los que están casi en equel.
  • Ordenelos para que tenga los 4 segmentos de línea más grandes.
  • Intersecta esas líneas y tienes los 4 puntos de esquina más probables.
  • Transforma la matriz sobre la perspectiva obtenida de los puntos de las esquinas y la relación de aspecto del objeto conocido.

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

Vea una implementación que funciona aquí: Java OpenCV enderezar un contorno


El problema era el orden en que se declaraban los puntos dentro del vector, y luego también había otro problema relacionado con esto en la definición de dst_vertices .

El orden de los puntos es importante para getPerspectiveTransform() y debe especificarse en el siguiente orden:

1st-------2nd | | | | | | 3rd-------4th

Por lo tanto, los puntos de origen deben ser reordenados a esto:

vector<Point> not_a_rect_shape; not_a_rect_shape.push_back(Point(408, 69)); not_a_rect_shape.push_back(Point(1912, 291)); not_a_rect_shape.push_back(Point(72, 2186)); not_a_rect_shape.push_back(Point(1584, 2426));

y el destino:

Point2f dst_vertices[4]; dst_vertices[0] = Point(0, 0); dst_vertices[1] = Point(box.boundingRect().width-1, 0); // Bug was: had mistakenly switched these 2 parameters dst_vertices[2] = Point(0, box.boundingRect().height-1); dst_vertices[3] = Point(box.boundingRect().width-1, box.boundingRect().height-1);

Después de esto, es necesario hacer algunos recortes porque la imagen resultante no es solo el área dentro del rectángulo verde como pensé que sería:

No sé si esto es un error de OpenCV o si me falta algo, pero el problema principal ha sido resuelto.


Entonces, el primer problema es el orden de esquina. Deben estar en el mismo orden en ambos vectores. Entonces, si en el primer vector su orden es: (arriba-izquierda, abajo-izquierda, abajo-derecha, arriba-derecha), DEBEN estar en el mismo orden en el otro vector.

En segundo lugar, para que la imagen resultante contenga solo el objeto de interés, debe configurar su ancho y alto para que coincida con el ancho y el alto del rectángulo resultante. No se preocupe, las imágenes src y dst en warpPerspective pueden tener diferentes tamaños.

En tercer lugar, una preocupación de rendimiento. Si bien su método es absolutamente preciso, ya que solo está haciendo transformaciones afines (rotar, redimensionar, alinear) matemáticamente, puede usar el corresponsal afín de sus funciones. Ellos son mucho más rápidos .

  • getAffineTransform ()

  • warpAffine ().

Nota importante: la transformación getAffine necesita y espera SOLAMENTE 3 puntos, y la matriz de resultados es 2 por 3, en lugar de 3 por 3.

Cómo hacer que la imagen resultante tenga un tamaño diferente al de la entrada:

cv::warpPerspective(src, dst, dst.size(), ... );

utilizar

cv::Mat rotated; cv::Size size(box.boundingRect().width, box.boundingRect().height); cv::warpPerspective(src, dst, size, ... );

Entonces aquí estás, y tu tarea de programación ha terminado.

void main() { cv::Mat src = cv::imread("r8fmh.jpg", 1); // After some magical procedure, these are points detect that represent // the corners of the paper in the picture: // [408, 69] [72, 2186] [1584, 2426] [1912, 291] vector<Point> not_a_rect_shape; not_a_rect_shape.push_back(Point(408, 69)); not_a_rect_shape.push_back(Point(72, 2186)); not_a_rect_shape.push_back(Point(1584, 2426)); not_a_rect_shape.push_back(Point(1912, 291)); // For debugging purposes, draw green lines connecting those points // and save it on disk const Point* point = &not_a_rect_shape[0]; int n = (int)not_a_rect_shape.size(); Mat draw = src.clone(); polylines(draw, &point, &n, 1, true, Scalar(0, 255, 0), 3, CV_AA); imwrite("draw.jpg", draw); // Assemble a rotated rectangle out of that info RotatedRect box = minAreaRect(cv::Mat(not_a_rect_shape)); std::cout << "Rotated box set to (" << box.boundingRect().x << "," << box.boundingRect().y << ") " << box.size.width << "x" << box.size.height << std::endl; Point2f pts[4]; box.points(pts); // Does the order of the points matter? I assume they do NOT. // But if it does, is there an easy way to identify and order // them as topLeft, topRight, bottomRight, bottomLeft? cv::Point2f src_vertices[3]; src_vertices[0] = pts[0]; src_vertices[1] = pts[1]; src_vertices[2] = pts[3]; //src_vertices[3] = not_a_rect_shape[3]; Point2f dst_vertices[3]; dst_vertices[0] = Point(0, 0); dst_vertices[1] = Point(box.boundingRect().width-1, 0); dst_vertices[2] = Point(0, box.boundingRect().height-1); /* Mat warpMatrix = getPerspectiveTransform(src_vertices, dst_vertices); cv::Mat rotated; cv::Size size(box.boundingRect().width, box.boundingRect().height); warpPerspective(src, rotated, warpMatrix, size, INTER_LINEAR, BORDER_CONSTANT);*/ Mat warpAffineMatrix = getAffineTransform(src_vertices, dst_vertices); cv::Mat rotated; cv::Size size(box.boundingRect().width, box.boundingRect().height); warpAffine(src, rotated, warpAffineMatrix, size, INTER_LINEAR, BORDER_CONSTANT); imwrite("rotated.jpg", rotated); }


Muy inspirado en la respuesta de @ VaporwareWolf, implementado en C # con Xamarin MonoTouch para iOS. La principal diferencia es que estoy usando GetPerspectiveTransform en lugar de FindHomography y TopLeft en lugar de ScaleToFit para el modo de contenido:

void SetupWarpedImage(UIImage sourceImage, Quad sourceQuad, RectangleF destRectangle) { var imageContainerView = new UIView(destRectangle) { ClipsToBounds = true, ContentMode = UIViewContentMode.TopLeft }; InsertSubview(imageContainerView, 0); var imageView = new UIImageView(imageContainerView.Bounds) { ContentMode = UIViewContentMode.TopLeft, Image = sourceImage }; var offset = new PointF(-imageView.Bounds.Width / 2, -imageView.Bounds.Height / 2); var dest = imageView.Bounds; dest.Offset(offset); var destQuad = dest.ToQuad(); var transformMatrix = Quad.GeneratePerspectiveTransformMatrixFromQuad(sourceQuad, destQuad); CATransform3D transform = transformMatrix.ToCATransform3D(); imageView.Layer.AnchorPoint = new PointF(0f, 0f); imageView.Layer.Transform = transform; imageContainerView.Add(imageView); }