我假设的问题很简单,但由于我刚才在线性代数方面的经验,我仍然无法解决这个问题。我阅读了几所大学发表的演讲稿,但我似乎无法遵循一些非标准化的符号。如果有人有更好的例子,那将非常感激......
问题: 相机朝向地板倾斜。给定像素坐标,我希望能够在地板平面上获得相应的3D世界坐标。
已知:
未知
我正在研究的内容 找到了一个非凡的例子here.本质上它是完全相同的问题,但有一些跟进问题:
SolvePnP看起来是我的朋友,但我不太清楚如何处理Camera Matrix或distCoefficients。有没有什么方法可以避免相机矩阵和dist系数校准步骤,我认为这是通过棋盘处理(可能以一些准确性为代价)完成的?还是有一些更简单的方法来做到这一点?
非常感谢您的投入!
答案 0 :(得分:1)
尝试这种方法:
从4点对应计算单应性,为您提供在图像平面和地平面坐标之间转换的所有信息。
这种方法的局限性在于它假设一个均匀参数化的图像平面(针孔相机),因此镜头失真会给你带来错误,如我的例子所示。如果你能够消除镜头失真效果,我猜你会很好地采用这种方法。 另外,如果你提供更多的对应关系,你会得到一些误差,给出稍微错误的像素坐标,如果你提供更多的对应关系,你可以获得更稳定的值。
使用此输入图像
我已经从图像处理软件中读取了一个国际象棋领域的4个角,这与您在图像中知道4个点的事实相对应。我选择了那些点(标记为绿色):
现在我做了两件事:首先将棋盘图案坐标转换为图像(0,0),(0,1)等,这给出了映射质量的良好视觉印象。第二,我从图像转变为世界。读取图像位置中的最左角位置(87,291),其对应于棋盘坐标中的(0,0)。如果我将那个像素位置转换为你想要的结果(0,0)。
cv::Point2f transformPoint(cv::Point2f current, cv::Mat transformation)
{
cv::Point2f transformedPoint;
transformedPoint.x = current.x * transformation.at<double>(0,0) + current.y * transformation.at<double>(0,1) + transformation.at<double>(0,2);
transformedPoint.y = current.x * transformation.at<double>(1,0) + current.y * transformation.at<double>(1,1) + transformation.at<double>(1,2);
float z = current.x * transformation.at<double>(2,0) + current.y * transformation.at<double>(2,1) + transformation.at<double>(2,2);
transformedPoint.x /= z;
transformedPoint.y /= z;
return transformedPoint;
}
int main()
{
// image from http://d20uzhn5szfhj2.cloudfront.net/media/catalog/product/cache/1/image/9df78eab33525d08d6e5fb8d27136e95/5/2/52440-chess-board.jpg
cv::Mat chessboard = cv::imread("../inputData/52440-chess-board.jpg");
// known input:
// image locations / read pixel values
// 478,358
// 570, 325
// 615,382
// 522,417
std::vector<cv::Point2f> imageLocs;
imageLocs.push_back(cv::Point2f(478,358));
imageLocs.push_back(cv::Point2f(570, 325));
imageLocs.push_back(cv::Point2f(615,382));
imageLocs.push_back(cv::Point2f(522,417));
for(unsigned int i=0; i<imageLocs.size(); ++i)
{
cv::circle(chessboard, imageLocs[i], 5, cv::Scalar(0,0,255));
}
cv::imwrite("../outputData/chessboard_4points.png", chessboard);
// known input: this is one field of the chessboard. you could enter any (corresponding) real world coordinates of the ground plane here.
// world location:
// 3,3
// 3,4
// 4,4
// 4,3
std::vector<cv::Point2f> worldLocs;
worldLocs.push_back(cv::Point2f(3,3));
worldLocs.push_back(cv::Point2f(3,4));
worldLocs.push_back(cv::Point2f(4,4));
worldLocs.push_back(cv::Point2f(4,3));
// for exactly 4 correspondences. for more you can use cv::findHomography
// this is the transformation from image coordinates to world coordinates:
cv::Mat image2World = cv::getPerspectiveTransform(imageLocs, worldLocs);
// the inverse is the transformation from world to image.
cv::Mat world2Image = image2World.inv();
// create all known locations of the chessboard (0,0) (0,1) etc we will transform them and test how good the transformation is.
std::vector<cv::Point2f> worldLocations;
for(unsigned int i=0; i<9; ++i)
for(unsigned int j=0; j<9; ++j)
{
worldLocations.push_back(cv::Point2f(i,j));
}
std::vector<cv::Point2f> imageLocations;
for(unsigned int i=0; i<worldLocations.size(); ++i)
{
// transform the point
cv::Point2f tpoint = transformPoint(worldLocations[i], world2Image);
// draw the transformed point
cv::circle(chessboard, tpoint, 5, cv::Scalar(255,255,0));
}
// now test the other way: image => world
cv::Point2f imageOrigin = cv::Point2f(87,291);
// draw it to show which origin i mean
cv::circle(chessboard, imageOrigin, 10, cv::Scalar(255,255,255));
// transform point and print result. expected result is "(0,0)"
std::cout << transformPoint(imageOrigin, image2World) << std::endl;
cv::imshow("chessboard", chessboard);
cv::imwrite("../outputData/chessboard.png", chessboard);
cv::waitKey(-1);
}
生成的图像是:
正如您所看到的,数据中存在大量错误。正如我所说,这是因为像素坐标稍微错误,因为对应物(在一个小区域内!),并且由于镜头失真导致地平面在图像上显示为真实平面。
转换(87,291)到世界坐标的结果是:
[0.174595, 0.144853]
预期/完美的结果将是[0,0]
希望这会有所帮助。
答案 1 :(得分:0)
当然,您可以将cameraMatrix
设置为Identity矩阵(eye(3)
)并将distCoefficients
设置为NULL,solvePNP将假设您拥有完美的相机。正如你所说,这将引入一些额外的不准确性,但你仍然会得到答案。
如果您发现结果不够准确,那么相机校准确实不是那么重要。