OpenCV warpPerspective, warpAffine без обрезки (whole image) и размер результата (destination result image size)

В общем потребовалось восстановить перспективу картинки,
на примере этой:

Как обычно — нашли 4 точки на картинке, в данном случае — углы листа, по часовой стрелке, начиная с верхнего левого:

[20, 340]
[860,110]
[1160, 650]
[200, 950]

Хотим, что бы лист располагался прямо, а для этого мы знаем, что ширина и высота листа 870 на 620 пикселей, аналогично координаты по часовой стрелке, начиная с верхнего левого:

[0, 0]
[870, 0]
[870, 620]
[0, 620]

в данном случае левый верхний угол располагается в нуле (где ещё то), а правый нижний строго по горизонтали и вертикали (мы ведь хотим расположить прямо)

ладно, пора покодить (доки нам в помощь):

/*
* Пока что всё как обычно
* открываем картинку,
* задаём найденные и целевые точки,
*/
Mat input;
input = imread("/home/pavelk/Projects/OpenCVwrapPerspective/sheet.jpg");

Point2f inputQuad[4];
inputQuad[0] = Point2f( 20, 340 );
inputQuad[1] = Point2f( 860,110 );
inputQuad[2] = Point2f( 1160, 650 );
inputQuad[3] = Point2f( 200, 950 );

Point2f outputQuad[4];
outputQuad[0] = Point2f( 0, 0 );
outputQuad[1] = Point2f( 870, 0 );
outputQuad[2] = Point2f( 870, 620 );
outputQuad[3] = Point2f( 0, 620 );

/*
* Находим матрицу трансформации
*/
Mat M = getPerspectiveTransform( inputQuad, outputQuad );

/*
* Теперь применяем трансформацию на картинке с помощью warpPerspective
*/

Mat output;
warpPerspective(input, output, M, Size(2000, 2000));

Но тут сталкиваемся с первой засадой — нужно задать итоговый размер, а его мы до трансформации не знаем =(   Ну ладно, попробуем пока что подгадать.

Посмотрим на вывод:

Как видим, перспективу то мы исправили,
но что если нам так же нужна вся плоскость картинки, а не только сам лист (например потому, что точки  мы можем найти только с помощью этого объекта, а нам нужен абсолютно другой)?
Да и с определением итогового размера можно не угадать, а заранее задавать слишком большой — тормоза для последующих обработок.

Почему так происходит?

Потому, что когда мы задали целевую точку верхнего левого угла [0, 0], то при трансформации получается (см. формулу warpPerspective), что нужно взять позицию с отрицательными координатами с картинки оригинала, а откуда на ней взяться отрицательному-то значению…  Вот и получается обрезка.  Можно, конечно, заранее подгадать смещение целевых точек, что бы не было отрицательных позиций, но мы не знаем, как может повернуться исходная картинка, да и проблему с размером результата мы не решим да и вообще гадать — не мой метод.

Что будем делать?

Как следует из написанного выше — нам нужно сместить целевые точки, поэтому будем выяснять насколько их смещать, а для этого нам нужно выяснить, где окажется левый верхний угол после трансформации. Но! Не забываем, что исходная картинка может быть как угодно повёрнута, и левый нижний окажется дальше в минусе, чем верхний левый. Поэтому выясняем положение после трансформации всех четырёх углов исходной картинки, а что бы не геммороиться с определением где какой угол — найдём их ограничительную рамку, благо для этого в OpenCV есть метод boundingRect и сместим целевые точки в обратную сторону от её позиции, а бонусом по её размеру мы знаем размер итоговой картинки=)

Покодим:

/**
* Выясняем положение углов исходной картинке,
* по ширине и высоте
*/
vector<Point2f> inputCorners(4);
inputCorners[0]=Point2f(0, 0);
inputCorners[1]=Point2f(input.cols, 0);
inputCorners[2]=Point2f(0, input.rows);
inputCorners[3]=Point2f(input.cols, input.rows);

/*
* Выясняем, где они будут - применяем трансформацию
*/
vector<Point2f> outputCorners(4);
perspectiveTransform(inputCorners, outputCorners, M);

/*
* Находим ограничительную рамку
*/
Rect br= boundingRect(outputCorners);

/*
* Сдвигаем все целевые точки в противоположное направление, 
* от того, куда ушла ограничительная рамка
*/
for(int i=0; i<4; i++) {
   outputQuad[i]+=Point2f(-br.x,-br.y);
}

/*
* Ну и заново вычисляем матрицу трансформацию
* с новыми целевыми точками
*/
 M = getPerspectiveTransform( inputQuad, outputQuad );

/*
* Применяем трансформацию к картинке
* размер - как ограничительная рамка
*/
warpPerspective(input, output, M, br.size());

/*
* Ну и показываем итоговую картинку
*/
imshow("Output2", output);

Посмотрим на результат:

По-моему, получилось идеально? =)

Ок, полный листинг на Qt 5.6 console application:

#include <QApplication>
#include <QDebug>
#include <iostream>

#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>

using namespace std;
using namespace cv;


int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
 
    Mat input = imread("/home/pavelk/Projects/OpenCVwrapPerspective/sheet.jpg");

    Point2f inputQuad[4];
    inputQuad[0] = Point2f( 20, 340 );
    inputQuad[1] = Point2f( 860,110 );
    inputQuad[2] = Point2f( 1160, 650 );
    inputQuad[3] = Point2f( 200, 950 );

    Point2f outputQuad[4];
    outputQuad[0] = Point2f( 0, 0 );
    outputQuad[1] = Point2f( 870, 0 );
    outputQuad[2] = Point2f( 870, 620 );
    outputQuad[3] = Point2f( 0, 620 );

    Mat M = getPerspectiveTransform( inputQuad, outputQuad );

    vector<Point2f> inputCorners(4);
    inputCorners[0]=Point2f(0, 0);
    inputCorners[1]=Point2f(input.cols, 0);
    inputCorners[2]=Point2f(0, input.rows);
    inputCorners[3]=Point2f(input.cols, input.rows);

    vector<Point2f> outputCorners(4);
    perspectiveTransform(inputCorners, outputCorners, M);

    Rect br= boundingRect(outputCorners);

    for(int i=0; i<4; i++) {
        outputQuad[i]+=Point2f(-br.x,-br.y);
    }

    M = getPerspectiveTransform( inputQuad, outputQuad );

    warpPerspective(input, output, M, br.size());

    resize(input, input, Size(1000,1000));
    imshow("Input", input);
    resize(output, output, Size(1000,1000));
    imshow("Output2", output);

    waitKey(5000);

    return app.exec();
}

Вот как-то так =)

Related posts

OpenCV — Rotate Text

AVFrame(AVPicture) конвертация в OpenCV::Mat

Конвертация QVideoFrame to OpenCV Mat в Qt 5.6 и OpenCV 3.1

3 комментария

Paula 8 мая 2020 - 5:24
Hi Danil. I've been looking everywhere for something like and reached you page. I just cannot find a reference for `perspectiveTransform`, line 39. Is this a custom function or should it be another call to `getPerspectiveTransform`? Thank you.
Pavelk 16 мая 2020 - 4:04
Hi. perspectiveTransform and getPerspectiveTransform it`s differental functions make inverse actions. Code must be right. See more: https://docs.opencv.org/2.4/modules/core/doc/operations_on_arrays.html#void perspectiveTransform(InputArray src, OutputArray dst, InputArray m)
Даниил 6 августа 2019 - 1:33
Большое спасибо! Искал именно это в контексте iOS-разработки. На stackoverflow нашел нечто похожее, в гугле нашел ваше решение – именно то, что мне было нужно. У Apple их CIPerspectiveCorrection обрезало изображение, в отличие от Вашего решения. Ещё раз спасибо)
Add Comment