Выполнение cv :: warpPerspective для фальшивого рисования на наборе cv :: Point - PullRequest
55 голосов
/ 20 октября 2011

Я пытаюсь выполнить перспективное преобразование набора точек для достижения эффекта выжатости :

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

Я использую изображение ниже для тестов, а зеленый прямоугольник отображает интересующую область.

image

Мне было интересно, возможно ли достичь эффекта, на который я надеюсь, используя простую комбинацию cv::getPerspectiveTransform и cv::warpPerspective. Я делюсь исходным кодом, который я написал до сих пор, но он не работает. Это полученное изображение:

image

Так что есть vector<cv::Point>, который определяет интересующую область , но точки не сохраняются в каком-либо определенном порядке внутри вектора, и это то, что я не могу изменение в процедуре обнаружения. В любом случае, позже , точки в векторе используются для определения RotatedRect, который, в свою очередь, используется для сборки cv::Point2f src_vertices[4];, одной из переменных, необходимых для cv::getPerspectiveTransform().

Мое понимание о вершинах и о том, как они организованы может быть одной из проблем . Я также думаю, что использование RotatedRect не самая лучшая идея для хранения исходных точек области интереса, поскольку координаты немного изменятся на , чтобы поместиться в повернутый прямоугольник, и это не очень круто .

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

Может кто-нибудь помочь мне решить эту проблему?

Ответы [ 6 ]

41 голосов
/ 25 октября 2011

Итак, первая проблема - это угловой порядок. Они должны быть в одинаковом порядке в обоих векторах. Итак, если в первом векторе ваш порядок: (вверху слева, внизу слева, внизу справа, вверху справа), они ДОЛЖНЫ быть в том же порядке в другом векторе.

Во-вторых, чтобы полученное изображение содержало только интересующий объект, вы должны установить его ширину и высоту, равные ширине и высоте получаемого прямоугольника. Не беспокойтесь, изображения 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 = &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);
}
17 голосов
/ 24 октября 2011

Проблема была в том порядке, в котором точки были объявлены внутри вектора, а затем возникла еще одна проблема, связанная с этим при определении 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);

После этого необходимо выполнить некоторое кадрирование , потому что получающееся изображение необласть внутри зеленого прямоугольника, как я и думал, будет:

image

Я не знаю, является ли это ошибкой OpenCV или я что-то упустил, но главноепроблема решена.

5 голосов
/ 04 августа 2013

При работе с четырехугольником OpenCV на самом деле не ваш друг. RotatedRect даст вам неверные результаты. Также вам понадобится перспективная проекция вместо аффинной проекции, как и другие, упомянутые здесь ..

В основном, что должно быть сделано:

  • Прокрутите все сегменты многоугольника и соедините те, которые почти равны.
  • Сортируйте их, чтобы у вас было 4 самых больших отрезка.
  • Пересечь эти линии, и у вас есть 4 наиболее вероятных угловых точки.
  • Преобразуйте матрицу по перспективе, собранной из угловых точек, и соотношению сторон известного объекта.

Я реализовал класс Quadrangle, который заботится о преобразовании контура в четырехугольник, а также преобразует его в правильной перспективе.

Смотрите рабочую реализацию здесь: Java OpenCV выравнивание контура

4 голосов
/ 24 июля 2014

ОБНОВЛЕНИЕ: РАЗРЕШЕНО

У меня почти все работает.Так близко к тому, чтобы быть пригодным для использования.Это правильно, но у меня, кажется, есть проблема с масштабированием или переводом.Я установил опорную точку на ноль, а также экспериментировал с изменением режима масштабирования (aspectFill, scale to fit и т.д. ...).

Установите точки перегиба (красный цвет затрудняет их просмотр): enter image description here

Примените вычисленное преобразование: enter image description here

Теперь это перегибы.Это выглядит довольно хорошо, за исключением того, что его не по центру на экране.Добавив жест панорамирования к представлению изображения, я могу перетащить его и убедиться, что оно выровнено: enter image description here

Это не так просто, как перевести на -0,5, -0,5, поскольку исходное изображение становится многоугольникомон простирается очень очень далеко (потенциально), поэтому его ограничивающий прямоугольник намного больше, чем рамка экрана.

Кто-нибудь видит, что я могу сделать, чтобы обернуть это?Я хотел бы сделать это и поделиться этим здесь.Это популярная тема, но я не нашел такого простого решения, как копирование / вставка.

Полный исходный код находится здесь:

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

git checkout demo

Однако я вставлю соответствующие части здесь.Этот первый метод мой, и именно здесь я получаю очки.

- (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 и альт.Демонстрационный код в указанном выше месте (филиал "демо")

3 голосов
/ 28 сентября 2012

Я получил такую ​​же проблему и исправил ее, используя функцию извлечения гомографии OpenCV.

Вы можете увидеть, как я сделал в этом вопросе: Преобразование прямоугольного изображения в четырехугольник с помощью CATransform3D

1 голос
/ 07 декабря 2014

Очень вдохновлен ответом @ 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);
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...