With the last step we know where the bottom edges of the pawn are located on the image, we just need to find a way to transform the coordinates of those pixels into game field coordinates.
Given a point from the Image plane, we’d like to transform it into from the Game field plane. We can write:
We observe that straight lines are kept straight, thus H is called the homography matrix which can be computed if at least 4 different matching points are given for both planes.
It’s worth noticing that both and points are given in homogeneus coordinates.
Algorithm and code
1. In order to compute H, the homography matrix. We use the ready to use openCV’s function: findHomography.
// Create a column vector with the coordinates of each point (on the field plane) cv::Mat xField; xField.create(4, 1, CV_32FC2); xField.at<Point2f>(0) = ( cv::Point2f(x1, y1) ); xField.at<Point2f>(1) = ( cv::Point2f(x2, y2) ); xField.at<Point2f>(2) = ( cv::Point2f(x3, y3) ); xField.at<Point2f>(3) = ( cv::Point2f(x4, y4) ); // same thing for xImage but with the pixel coordinates instead of the field coordinates, same order as in xField cv::Mat xImage; xImage.at<Point2f>(0) = ( cv::Point2f(x1_bis, y1_bis) ); ... // Compute the homography matrix cv::Mat H = cv::findHomography( xImage, xField );
2. Whenever we want to find the coordinates of a point on the game field, given a pixel on an image. We only need to transform into :
// pImage = p'(x,y) // pImage is in the projective plane cv::Mat pImage = (cv::Mat_(3,1) << x, y, 1); cv::Mat pField = H * pImage; // pField is in the projective plane (homogeneous coordinates): (X, Y, W). Transform it back to the euclidean plane: (X', Y', 1) pField /= pField.at(2); // p(xField, yField) represent the same point as p'(x, y) but in different planes. double xField = pField.at(0); double yField = pField.at(1);
Because (2) is used really often we can avoid doing matrix products during run time by pre-calculating all possible transformations of the image. All game field points corresponding to every pixel of the image are computed in advance and saved into an bi-dimensional array for efficient access. To correlate to we do:
p = pixelsToMeters.at(p')
Just for fun
You guessed right we now have the full ‘tool-kit’ to precisely link pawns on the image to their positions on the game field plane. How to do it is the subject of the next post.