执行cv :: warpPerspective在一组cv :: Point上伪造的校验

我正在尝试对一组点进行透视转换 ,以达到去扭曲效果:

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

我使用下面的图片进行testing, 绿色的矩形显示感兴趣的区域。

我想知道是否有可能实现我希望使用cv::getPerspectiveTransformcv::warpPerspective的简单组合的cv::warpPerspective 。 我分享了迄今为止我写的源代码,但是它不起作用。 这是由此产生的图像:

所以有一个vector<cv::Point> 定义了感兴趣的区域 ,但是这些点并没有以任何特定的顺序存储在vector中,而这在检测过程中是不能改变的。 无论如何, 后来 ,向量中的点用来定义一个RotatedRect ,它依次被用来组装cv::Point2f src_vertices[4];cv::getPerspectiveTransform()所需的variables之一。

我对顶点及其组织方式的理解可能是其中一个问题 。 我也认为使用RotatedRect并不是存储ROI原始点的最佳方法 ,因为坐标会稍微改变以适应旋转的矩形, 这并不是很酷

 #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; } 

有人可以帮我解决这个问题吗?

所以,第一个问题是angular落顺序。 它们必须在两个向量中以相同的顺序。 因此,如果在第一个向量中,您的顺序是:(左上angular,左下angular,右下angular,右上angular),它们必须在另一个vector中的顺序相同。

其次,要生成的图像只包含感兴趣的对象,则必须将其宽度和高度设置为与所得的矩形宽度和高度相同。 不要担心,warpPerspective中的src和dst图像可以是不同的大小。

第三,性能问题。 虽然你的方法是绝对准确的,因为你只是做仿射变换(旋转,resize,抗扭斜),在math上,你可以使用函数的仿射函数。 他们快得多

  • getAffineTransform()

  • warpAffine()。

重要提示:getAffine变换只需要3分,结果matrix是2×3,而不是3×3。

如何使结果图像具有与input不同的大小:

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

使用

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

所以在这里,你的编程任务结束了。

 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); } 

问题是在向量中声明点的顺序,然后在dst_vertices的定义中还有另外一个与此有关的问题。

点的顺序getPerspectiveTransform() ,必须按以下顺序指定:

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

因此,原点需要重新sorting:

 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)); 

和目的地:

 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); 

在此之后,需要进行一些裁剪,因为所产生的图像不仅仅是我认为的绿色矩形区域内的区域:

我不知道这是OpenCV的错误还是我错过了一些东西,但主要问题已经解决了。

在使用四边形时,OpenCV不是真正的朋友。 RotatedRect会给你不正确的结果。 此外,您将需要一个透视投影,而不是像这里提到的其他仿射投影。

基本上必须做的是:

  • 循环遍历所有的多边形分段,并连接那些几乎是平等的。
  • 对它们进行sorting,以便拥有最大的4条线段。
  • 相交这些线,你有4个最有可能的angular点。
  • 在从angular点聚集的angular度和已知物体的纵横比上变换matrix。

我实现了一个类Quadrangle ,它将轮廓转换为四边形转换,并将其转换为正确的视angular。

在这里看到一个工作实现: Java OpenCV去除一个轮廓

更新:解决

我几乎有这个工作。 如此接近可用。 它歪斜正确,但我似乎有一个规模或翻译问题。 我已经将定位点设置为零,并尝试更改缩放模式(aspectFill,缩放到适合等等)。

设置相差点(红色让他们难以看清): 在这里输入图像描述

应用计算的变换: 在这里输入图像描述

现在它歪斜了。 这看起来不错,只是它不在屏幕上居中。 通过向图像视图添加平移手势,我可以将其拖动并validation它是否排齐: 在这里输入图像描述

这并不像由-0.5,-0.5这样简单的转换,因为原始图像变成了非常非常远(可能)的多边形,所以它的边界矩比屏幕边框大得多。

有没有人看到我能做些什么来得到这个包裹? 我希望得到它的承诺,并在这里分享。 这是一个很受欢迎的话题,但我还没有find像复制/粘贴一样简单的解决scheme。

完整的源代码在这里:

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

git结帐演示

不过,我会在这里粘贴相关部分。 这第一种方法是我的,我得到的去偏点。

 - (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; } 

更新:我得到它正常工作。 坐标需要在中心,而不是左上angular。 我申请了xOffset和yOffset和中提琴。 在上面提到的位置(“demo”分支)

我得到了同样的问题,并使用OpenCV的单应性提取function进行修复。

你可以看到我在这个问题上做了什么: 使用CATransform3D将矩形图像转换成四边形

非常受@ VaporwareWolf的答复启发,在C#中使用Xamarin MonoTouch for iOS实现。 主要区别在于我使用的是GetPerspectiveTransform而不是FindHomography和TopLeft,而不是ScaleToFit的内容模式:

  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); } 
    Interesting Posts