I created an application for a target hit evaluation using a webcam. The camera is never vertical to the target so there is a significant perspective distortion and I need a bird’s eye view. Fixing this with getPerspectiveTransform()
and cv::warpPerspective()
is easy but cv::warpPerspective()
is very slow (at least for my purpose).
Faster perspective correction can be done using cv::remap()
if you don’t mind that the maps creation takes a long time. Since the camera has a fixed focus then the maps can be created only once and then just run the remapping. The speedup of cv::remap()
compared to cv::
is almost 5x.warpPerspective
()
Maps creation
The main problem is how to efficiently create those maps. My first try was to calculate the points one by one:
void perspective_to_maps(const cv::Mat &perspective_mat, const cv::Size &img_size,
cv::Mat &map1, cv::Mat &map2)
{
// invert the matrix because the transformation maps must be
// bird's view -> original
cv::Mat inv_perspective(perspective_mat.inv());
cv::Mat map_x(img_size, CV_32FC1);
cv::Mat map_y(img_size, CV_32FC1);
// bird's view point coordinates
cv::Mat point = (cv::Mat_(3, 1)<double> 0, 0, 1);
double *ppoint = point.ptr<double>(0);
// original point coordinates
cv::Mat orig_point(cv::Size(3, 1), CV_64FC1);
double *porig_point = orig_point.ptr<double>(0);
for (int y = 0; y < img_size.height; y++)
{
ppoint[1] = y;
float *pmap_x = map_x.ptr<float>(y);
float *pmap_y = map_y.ptr<float>(y);
for (int x = 0; x < img_size.width; x++)
{
ppoint[0] = x;
// perspective transformation by matrix multiplication
orig_point = inv_perspective * point;
pmap_x[x] = porig_point[0] / porig_point[2];
pmap_y[x] = porig_point[1] / porig_point[2];
}
}
// remap() with integer maps is faster
cv::convertMaps(map_x, map_y, map1, map2, CV_16SC2);
}
Faster
Previous solution works well but takes a considerable time to calculate the maps even with maximum compiler optimizations. Lately I found function cv::perspectiveTransform()
which can do the heavy lifting. Prepare simple XY matrix and apply transformation at once.
void perspective_to_maps(const cv::Mat &perspective_mat, const cv::Size &img_size,
cv::Mat &map1, cv::Mat &map2)
{
// invert the matrix because the transformation maps must be
// bird's view -> original
cv::Mat inv_perspective(perspective_mat.inv());
inv_perspective.convertTo(inv_perspective, CV_32FC1);
// create XY 2D array
// (((0, 0), (1, 0), (2, 0), ...),
// ((0, 1), (1, 1), (2, 1), ...),
// ...)
cv::Mat xy(img_size, CV_32FC2);
float *pxy = (float*)xy.data;
for (int y = 0; y < img_size.height; y++)
for (int x = 0; x < img_size.width; x++)
{
*pxy++ = x;
*pxy++ = y;
}
// perspective transformation of the points
cv::Mat xy_transformed;
cv::perspectiveTransform(xy, xy_transformed, inv_perspective);
// split x/y to extra maps
assert(xy_transformed.channels() == 2);
cv::Mat maps[2]; // map_x, map_y
cv::split(xy_transformed, maps);
// remap() with integer maps is faster
cv::convertMaps(maps[0], maps[1], map1, map2, CV_16SC2);
}
This generator is 5x-10x faster depending on architecture (ARM, x64, …).
Question: can be the XY matrix prepared faster? Any comments are welcome.
Hi
Thank you for sharing you code ! I have the same issue with remap which is faster than warpPerspective. I was quite surprise that OpenCV don’t have it in the box …
Your code is what I was looking for ! (Thus, in my case, I don’t need to invert the perspective mat)
Cheers,
mhtrinh
Comment by mhtrinh — 2014-03-13 @ 21:51