我使用以下解决方案稳健但不准确地解决了这个问题:
- 找到至少有 3 个绿点与该线非常匹配的线。 (图中红色细线)
- 保留边界线:从这些线中,将点仅保留在线的一侧或非常靠近线的位置。
- 过滤边界线:从边界线中,选取 4 个最好的/点数最多的边界线。 (图片中的粗白线)
- 计算剩余 4 条边界线的交点(没有一条线完全平行,因此产生 6 个交点,我们只需要其中 4 个)。
- 从交叉口中,删除距离交叉口平均位置最远的一个,直到只剩下 4 个。
- 这是 4 个蓝点。
然后您可以将这 4 个点输入 OpenCV 的 findPerspectiveTransform 函数以找到透视变换(也称为单应性):
Point2f* srcPoints = (Point2f*) malloc(4 * sizeof(Point2f));
std::vector<Point2f> detectedCorners = CheckDet::getOuterCheckerboardCorners(srcImg);
for (int i = 0; i < MIN(4, detectedCorners.size()); i++) {
srcPoints[i] = detectedCorners[i];
}
Point2f* dstPoints = (Point2f*) malloc(4 * sizeof(Point2f));
int dstImgSize = 400;
dstPoints[0] = Point2f(dstImgSize * 1/8, dstImgSize * 1/8);
dstPoints[1] = Point2f(dstImgSize * 7/8, dstImgSize * 1/8);
dstPoints[2] = Point2f(dstImgSize * 7/8, dstImgSize * 7/8);
dstPoints[3] = Point2f(dstImgSize * 1/8, dstImgSize * 7/8);
Mat m = getPerspectiveTransform(srcPoints, dstPoints);
对于我们的示例图像,findPerspectiveTranform 的输入和输出如下所示:
input
(349.1, 383.9) -> ( 50.0, 50.0)
(588.9, 243.3) -> (350.0, 50.0)
(787.9, 404.4) -> (350.0, 350.0)
(506.0, 593.1) -> ( 50.0, 350.0)
output
( 1.6 -1.1 -43.8 )
( 1.4 2.4 -1323.8 )
( 0.0 0.0 1.0 )
然后您可以将图像的透视图转换为棋盘坐标:
Mat plainBoardImg;
warpPerspective(srcImg, plainBoardImg, m, Size(dstImgSize, dstImgSize));
结果如下图:
对于我的项目,不再需要您在问题板上看到的红点,但我确信它们可以通过反转单应性然后使用逆进行反向转换轻松地计算出来点 (0, 0)、(0, dstImgSize)、(dstImgSize, dstImgSize) 和 (dstImgSize, 0)。
该算法运行起来非常可靠,但是它并没有使用所有可用信息,因为它只使用外部点(那些与白线相连的点)。它不使用内部点的任何数据来提高准确性。我仍然想找到一个更好的解决方案,它使用内部点的数据。