Выполнение 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 — не лучшая идея для хранения исходных точек области интереса, поскольку координаты будут немного изменяться, чтобы соответствовать повернутому прямоугольнику, и это не очень круто.

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

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


person karlphillip    schedule 20.10.2011    source источник
comment
можете ли вы поделиться своей волшебной процедурой... для обнаружения точек, которые представляют собой углы бумаги на картинке, может быть, это поможет мне или другим?   -  person QueueOverFlow    schedule 23.11.2012
comment
Я сделал это здесь, не забудьте проголосовать.   -  person karlphillip    schedule 23.11.2012
comment
Здравствуйте, подскажите, пожалуйста, как мне сделать вашу магическую процедуру пригодной для белой бумаги, лежащей на столе белого цвета... (я имею в виду, как обнаружить объект, который имеет несколько похожий фон, не точный, но контрастный.... он wud b помощь полная, если вы поделитесь чем-нибудь по этой проблеме ...   -  person user1140237    schedule 17.09.2013
comment
Здравствуйте, я не знаю. Чтобы обнаружение работало, должен быть некоторый контраст между бумагой и фоном. Но вы можете исследовать обнаружение квадратов судоку и посмотреть, не принесет ли это вам какие-либо идеи. Удачи!   -  person karlphillip    schedule 17.09.2013
comment
спасибо за помощь gr8 :)   -  person user1140237    schedule 17.09.2013
comment
должно быть в dst_vertices[2] = Point(0, box.boundingRect().height-1); высота будет шириной? (Нижний правый)   -  person mnl    schedule 02.12.2013


Ответы (6)


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

Во-вторых, чтобы результирующее изображение содержало только интересующий объект, вы должны установить его ширину и высоту такими же, как ширина и высота результирующего прямоугольника. Не волнуйтесь, изображения src и dst в warpPerspective могут быть разных размеров.

В-третьих, проблема производительности. Хотя ваш метод абсолютно точен, поскольку вы выполняете только аффинные преобразования (поворот, изменение размера, выравнивание), математически вы можете использовать аффинный корреспондент ваших функций. Они намного быстрее.

  • получитьAffineTransform()

  • деформация аффинного().

Важное примечание: преобразование 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);
}
person Sam    schedule 25.10.2011
comment
Код автоматически переупорядочивает точки, что является мешаниной, но вы увидите, что изображение перевернуто. Решением является правильная комбинация точек здесь `src_vertices[0] = pts[0]; src_vertices[1] = pts[1]; src_vertices[2] = pts[3];` - person Sam; 27.10.2011
comment
Одно из свойств аффинного преобразования: параллельные прямые остаются параллельными. В данном случае это не так. AD параллелен BC только в конце. Так это не аффинное преобразование, не так ли? - person mnl; 21.11.2013
comment
Это перспективное преобразование, а не аффинное. Аффинные преобразования являются подмножеством перспективных преобразований и имеют ряд специальных свойств, одно из которых упоминается вами. - person Sam; 21.11.2013
comment
@sammy: Разве это не приводит к тому, что соотношение сторон изолированного объекта не сохраняется при исправлении? Я вижу, вы используете ширину/высоту ограничивающей рамки в качестве места назначения для преобразования. Таким образом, более перекошенное изображение изолированного объекта приведет к искажению соотношения сторон в результирующем изображении, да? - person Zoran Pavlovic; 14.07.2015

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

person karlphillip    schedule 24.10.2011
comment
как мне найти угол бумаги? (Я уже рисую линии вокруг прямоугольника... уже следуя вашему ответу в переполнении стека) Я изо всех сил пытаюсь найти угол бумаги и обрезать его. Теперь у меня есть прямоугольник с координатами x: 507 y: 418 1776x1372, когда я использую cout‹‹ Согласно вашему ответу. я правильно делаю? Поэтому, пожалуйста, предоставьте способ, как определить углы бумаги. - person QueueOverFlow; 22.10.2012
comment
Хотел бы я проголосовать за это более одного раза! После нескольких часов боли мой документ прекрасно сканируется. Спасибо - person Bijington; 12.01.2017
comment
Я только что вернулся, и мне пришлось повторно использовать это для нового проекта Xamarin! - person Bijington; 04.09.2018
comment
@Bijington Рад слышать! - person karlphillip; 04.09.2018

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

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

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

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

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

person Tim    schedule 04.08.2013

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

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

Настройте точки выравнивания (красный цвет делает их плохо видимыми): введите здесь описание изображения

Примените рассчитанное преобразование: введите здесь описание изображения

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

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

person VaporwareWolf    schedule 24.07.2014
comment
Спасибо за пример кода! Помогли мне решить аналогичную проблему. В итоге я использовал getPerspectiveTransform вместо FindHomography и TopLeft, а не ScaleToFit для режима содержимого. - person Bjørn Egil; 02.12.2014
comment
Я рад, что смог тебе помочь. Я хотел бы увидеть ваш код. Если он открыт, пожалуйста, поделитесь. - person VaporwareWolf; 02.12.2014
comment
Пожалуйста, найдите упрощенный пример кода в отдельном ответе. - person Bjørn Egil; 07.12.2014

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

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

person MonsieurDart    schedule 28.09.2012

Очень вдохновлен ответом @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);
    }
person Bjørn Egil    schedule 07.12.2014
comment
Где определяется ToCATransform3D? - person Geoff; 28.05.2015
comment
@Geoff, это только из моей реализации: public static CATransform3D ToCATransform3D (эта матрица DoubleMatrix) { CATransform3D transform = CATransform3D.Identity; transform.m11 = (float)matrix[0,0]; transform.m21 = (float)matrix[0,1]; transform.m41 = (float)matrix[0,2]; transform.m12 = (float)matrix[1,0]; transform.m22 = (float)matrix[1,1]; transform.m42 = (float)matrix[1,2]; transform.m14 = (float)matrix[2,0]; transform.m24 = (float)matrix[2,1]; transform.m44 = (float)matrix[2,2]; возвратное преобразование; } // Извините за плохое форматирование комментария - person Bjørn Egil; 28.05.2015