Выполнение cv::warpPerspective для фальшивых переписок на множестве cv::Point
Я пытаюсь выполнить перспективное преобразование набора точек, чтобы добиться эффекта вытеснения:
http://nuigroup.com/?ACT=28&fid=27&aid=1892_H6eNAaign4Mrnn30Au8d
Я использую изображение ниже для тестов, и зеленый прямоугольник отображает область интереса.
Мне было интересно, возможно ли достичь эффекта, на который я надеюсь, используя простую комбинацию cv::getPerspectiveTransform
а также cv::warpPerspective
, Я делюсь исходным кодом, который я написал до сих пор, но он не работает. Это результирующее изображение:
Так что есть vector<cv::Point>
это определяет область интереса, но точки не сохраняются в каком-либо определенном порядке внутри вектора, и это то, что я не могу изменить в процедуре обнаружения. Во всяком случае, позже, точки в векторе используются для определения 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 могут быть разных размеров.
В-третьих, проблема производительности. Хотя ваш метод абсолютно точен, поскольку вы выполняете только аффинные преобразования (поворот, изменение размера, выравнивание), математически вы можете использовать аффинный основной компонент ваших функций. Они намного быстрее.
getAffineTransform ()
warpAffine ().
Важное примечание: преобразование getAffine требует и ожидает ТОЛЬКО 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 или я что-то упустил, но основная проблема была решена.
ОБНОВЛЕНИЕ: РАЗРЕШЕНО
У меня почти это работает. Так близко к тому, чтобы быть пригодным для использования. Это правильно, но у меня, кажется, есть проблема с масштабированием или переводом. Я установил опорную точку на ноль, а также экспериментировал с изменением режима масштабирования (aspectFill, scale to fit и т. Д....).
Настройте точки наклона (красный делает их плохо различимыми):
Примените вычисленное преобразование:
Теперь это настольные игры. Это выглядит довольно хорошо, за исключением того, что его не по центру на экране. Добавив жест панорамирования к представлению изображения, я могу перетащить его и убедиться, что оно выровнено:
Это не так просто, как перевести на -0,5, -0,5, потому что исходное изображение становится многоугольником, который растягивается очень-очень далеко (потенциально), поэтому его ограничивающий прямоугольник намного больше, чем рамка экрана.
Кто-нибудь видит, что я могу сделать, чтобы это завернуть? Я хотел бы сделать это и поделиться этим здесь. Это популярная тема, но я не нашел такого простого решения, как копирование / вставка.
Полный исходный код здесь:
git clone https://github.com/zakkhoyt/Quadrilateral.git
Git Checkout демо
Тем не менее, я вставлю соответствующие части здесь. Этот первый метод мой, и именно здесь я получаю очки.
- (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 на самом деле не ваш друг. RotatedRect
даст вам неверные результаты. Также вам понадобится перспективная проекция вместо аффинной проекции, как и другие, упомянутые здесь.
В основном, что должно быть сделано:
- Переберите все сегменты многоугольника и соедините те, которые почти равны.
- Сортируйте их так, чтобы у вас было 4 самых больших отрезка.
- Пересечь эти линии, и у вас есть 4 наиболее вероятных угловых точек.
- Преобразуйте матрицу по перспективе, собранной из угловых точек и соотношению сторон известного объекта.
Я реализовал класс Quadrangle
который заботится о преобразовании контура в четырехугольник, а также преобразует его в правильной перспективе.
Смотрите рабочую реализацию здесь: Java OpenCV выравнивание контура
Я получил такую же проблему и исправил ее с помощью функции извлечения гомографии 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);
}