Выполнение cv:: warpPerspective для подделки на наборе cv:: Point
Я пытаюсь сделать перспективное преобразование набора точек, чтобы достичь deskewing эффект:
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;
}
Может кто-нибудь помочь мне решить эту проблему?
Ответы
Ответ 1
Итак, первая проблема - угловой порядок. Они должны быть в одном порядке в обоих векторах.
Итак, если в первом векторе ваш заказ: (вверху слева, внизу слева, внизу справа, сверху справа), они ДОЛЖНЫ быть в том же порядке в другом векторе.
Во-вторых, чтобы получившееся изображение содержало только объект, представляющий интерес, вы должны установить его ширину и высоту так же, как результирующая ширина и высота прямоугольника. Не волнуйтесь, изображения 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);
}
Ответ 2
Проблема заключалась в том, что порядок, в котором точки были объявлены внутри вектора, а затем возникла еще одна проблема, связанная с этим в определении 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 или я что-то упустил, но основная проблема была решена.
Ответ 3
При работе с четырехугольником OpenCV на самом деле не ваш друг. RotatedRect
даст неверные результаты. Также вам понадобится перспектива проекции вместо аффинной проекции, как и другие, упомянутые здесь.
В основном, что нужно сделать:
- Прокрутите все сегменты многоугольника и соедините те, которые почти равны.
- Сортируйте их так, чтобы у вас было 4 самых больших сегмента строки.
- Пересеките эти строки, и у вас есть 4 наиболее вероятных угловых точки.
- Преобразовать матрицу по перспективе, собранной из угловых точек и соотношению сторон известного объекта.
Я реализовал класс Quadrangle
, который занимается преобразованием контура в четырехугольник, а также преобразует его в правильной перспективе.
См. рабочую реализацию здесь:
Java OpenCV сопоставляет контур
Ответ 4
UPDATE: RESOLVED
У меня почти такая работа. Так близко к использованию. Он правильно работает, но у меня, кажется, есть масштаб или перевод. Я установил опорную точку в ноль, а также экспериментировал с изменением режима масштабирования (aspectFill, шкала для соответствия и т.д.).
Настройте опорные точки (красный цвет их трудно увидеть):
Применить вычисленное преобразование:
Теперь это deskews. Это выглядит довольно хорошо, за исключением того, что его не центрируют на экране. Добавляя жест панорамы к представлению изображения, я могу перетащить его и проверить, что он выравнивается:
Это не так просто, как переводить на -0.5, -0.5, потому что исходное изображение становится многоугольником, который простирается очень далеко (потенциально), поэтому ограничивающий прямоугольник намного больше, чем рамка экрана.
Кто-нибудь видит, что я могу сделать, чтобы обернуть это? Я хотел бы получить его и поделиться им здесь. Это популярная тема, но я не нашел решение, которое так же просто, как copy/paste.
Полный исходный код находится здесь:
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;
}
Обновление: я получил его правильно. Координаты должны происходить в центре, а не в верхнем левом углу. Я применил xOffset и yOffset и альта. Демо-код в указанном выше месте (ветка "demo" )
Ответ 5
Я получил такую же проблему и исправил ее с помощью функции извлечения геометрии OpenCV.
Вы можете видеть, как я это сделал в этом вопросе: Преобразование изображения прямоугольника в четырехугольник с использованием CATransform3D
Ответ 6
Очень вдохновлен ответом @VaporwareWolf, реализованным на С# с использованием 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);
}
Ответ 7
Верхний ответ хорош.
Для пользователя Python я написал блог о поиске объекта в сцене, используя SIFT на китайском языке. И the last step is deskewing
, вы можете проверить код в эту ссылку.