В общем потребовалось восстановить перспективу картинки,
на примере этой:
Как обычно — нашли 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();
}
Вот как-то так =)
3 комментария