Выполнение ЧВ::warpPerspective за подделку выравнивания на ЧВ::точка
Я пытаюсь сделать преобразование из набора точек для достижения выравнивания эффект:
http://nuigroup.com/?ACT=28&fid=27&aid=1892_H6eNAaign4Mrnn30Au8d
я использую изображение ниже для тестов, и зеленое прямоугольник отображает область интереса.
мне было интересно, можно ли достичь эффекта, который я надеюсь использовать простая комбинация cv::getPerspectiveTransform
и cv::warpPerspective
. Я делюсь исходным кодом, который я написал до сих пор, но это не работает. Это результирующее изображение:
так и есть vector<cv::Point>
это определяет область интересов, но точки не хранится в определенном порядке внутри вектор, а это то, что я не могу изменить в процедуре обнаружения. Во всяком случае,позже, точки в векторе используются для определения a RotatedRect
, который в свою очередь используется, чтобы собрать cv::Point2f src_vertices[4];
, одна из переменных, необходимых cv::getPerspectiveTransform()
.
мое понимание о вершин а как они организованы может быть одним из вопросов. Я также думаю, что с помощью 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 = ¬_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;
}
может кто-нибудь помочь мне решить эту проблему?
6 ответов:
Итак, первая проблема-угловой порядок. Они должны быть в одинаковом порядке в обоих векторах. Итак, если в первом векторе ваш порядок:(верхний левый, нижний левый, нижний правый , верхний правый), они должны быть в том же порядке в другом векторе.
во-вторых, чтобы полученное изображение содержало только интересующий объект, вы должны установить его ширину и высоту такими же, как ширина и высота результирующего прямоугольника. Не волнуйтесь, изображения src и dst в warpPerspective могут быть разными размеры.
В-третьих, проблема производительности. Хотя ваш метод абсолютно точен, потому что вы делаете только аффинные преобразования (поворот, изменение размера, deskew), математически вы можете использовать аффинный corespondent ваших функций. Они намного быстрее.
getAffineTransform()
warpAffine().
важное примечание: getAffine transform нуждается и ожидает только 3 очка, а матрица результатов-2 на 3, а не 3 на 3.
как сделать результат изображения имеют другой размер, чем вход:
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 = ¬_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
поэтому точки происхождения должны быть переупорядочены следующим образом:
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
даст вам неверные результаты. Также вам понадобится перспективная проекция вместо аффинной проекции, как и другие, упомянутые здесь..в основном то, что должно быть сделано:
- пройдите через все сегменты полигона и соедините те, которые почти равны.
- сортировать их, так что у вас есть 4 самых крупных отрезков.
- пересеките эти линии и у вас есть 4 наиболее вероятные угловые точки.
- преобразуйте матрицу по перспективе, собранной из угловых точек и соотношения сторон известного объекта.
я реализовал класс
Quadrangle
который заботится о преобразовании контура в четырехугольник, а также преобразует его в правильной перспективе.посмотреть рабочую реализацию здесь: Java с использованием OpenCV выравнивания контура
ОБНОВЛЕНИЕ: РАЗРЕШЕНО
У меня почти это работает. Так близко к тому, чтобы быть полезным. Это deskews правильно, но я, кажется, есть масштаб или перевести вопрос. Я установил точку привязки к нулю, а также экспериментировал с изменением режима масштабирования (aspectFill, scale to fit и т. д...).
настройки точки выравнивания (красный цвет делает их трудно увидеть):
применить вычисленное преобразование:
теперь это deskews. Это выглядит довольно хорошо, за исключением того, что его не по центру экрана. Добавив жест панорамирования в представление изображения, я могу перетащить его и проверить, что он выравнивается:
это не так просто, как перевести на -0.5, -0.5, потому что исходное изображение становится многоугольником, который простирается очень далеко (потенциально), поэтому он ограничивает прямоугольник намного больше, чем рамка экрана.
кто-нибудь видит, что я могу сделать, чтобы получить это завернутый? Я бы хотел, чтобы это было совершено и поделиться им здесь. Это популярная тема, но я не нашли решение, которое так же просто, как копировать/вставить.
полный исходный код здесь:
git clone https://github.com/zakkhoyt/Quadrilateral.git
git checkout demo
однако, я вставлю соответствующие части здесь. Этот первый метод является моим и где я получаю очки deskew.
- (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; }
обновление: я получил его работает должным образом. Координаты должны быть исходными в центре, а не в верхнем левом углу. Я подал заявление xOffset и yOffset и Виола. Демо-код по указанному выше адресу (ветка" демо")
Я получил такую же проблему и исправил ее с помощью функции извлечения гомографии OpenCV.
вы можете видеть, как я сделал в этом вопросе:преобразование изображения прямоугольника в четырехугольник с помощью CATransform3D
очень вдохновлен ответом @VaporwareWolf, реализованным в C# с использованием Xamarin MonoTouch для 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); }