网格标定板角点检测失败,求单应性映射实现方案
棋盘格角点检测失败与精准筛选解决方案
一、修复cv2.findChessboardCorners检测失败问题
先从图像预处理和参数调整入手,这是解决检测失败的最直接手段:
- 自适应阈值二值化:光照不均是棋盘格检测失败的常见原因,用自适应阈值能更好保留棋盘格边缘:
binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2) - 形态学强化:用闭运算消除小噪点,强化网格线条:
kernel = np.ones((3,3), np.uint8) binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) - 调整检测参数:添加鲁棒性flags,覆盖光照不均、图像模糊等场景:
ret, corners = cv2.findChessboardCorners(binary, (3,4), flags=cv2.CALIB_CB_ADAPTIVE_THRESH | cv2.CALIB_CB_NORMALIZE_IMAGE | cv2.CALIB_CB_FAST_CHECK) - 核对网格尺寸:确认
(3,4)是内角点的行列数(比如4列3行的格子,对应内角点3x4),不要搞反格子数和内角点数。
如果以上方法仍失败,再用Harris角点的精准筛选方案。
二、Harris角点的精准筛选(无固定位置场景)
利用棋盘格角点的规则网格拓扑特性筛选,不需要区域限制:
- 提取强Harris角点:
dst = cv2.cornerHarris(gray, 2, 3, 0.04) dst = cv2.dilate(dst, None) threshold = 0.01 * dst.max() candidate_points = np.argwhere(dst > threshold)[:, [1,0]].astype(np.float32) # 转换为(x,y)坐标 - 筛选规则网格点:
- 计算所有候选点的水平/垂直间距,找到最频繁出现的间距(即相邻角点的像素间距);
- 按x坐标分组为列,筛选出点间距均匀的列;
- 对每列按y坐标排序,筛选出行间距均匀的行;
- 最终保留符合3行4列(或对应你的网格尺寸)的角点集,并按从左到右、从上到下的顺序排列,确保和后续映射坐标对应。
三、生成映射坐标与计算单应性
假设筛选出3行4列的内角点,相邻角点间距1cm:
- 生成目标映射坐标:以左上角角点为原点,每个角点的目标坐标为
(col*1.0, row*1.0)(单位:cm); - 计算单应性矩阵:
# 检测到的图像角点(已排序) src_points = np.array(chess_corners, dtype=np.float32) # 目标映射坐标 dst_points = np.array([[col*1.0, row*1.0] for row in range(3) for col in range(4)], dtype=np.float32) # 计算单应性矩阵 H, mask = cv2.findHomography(src_points, dst_points, cv2.RANSAC, 5.0)
完整示例代码
import cv2 import numpy as np img = cv2.imread('chessboard.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 先尝试findChessboardCorners binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2) kernel = np.ones((3,3), np.uint8) binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) ret, corners = cv2.findChessboardCorners(binary, (3,4), flags=cv2.CALIB_CB_ADAPTIVE_THRESH | cv2.CALIB_CB_NORMALIZE_IMAGE | cv2.CALIB_CB_FAST_CHECK) if ret: # 亚像素优化 corners = cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria=(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)) cv2.drawChessboardCorners(img, (3,4), corners, ret) cv2.imshow('Detected Chessboard', img) cv2.waitKey(0) # 生成目标坐标并计算单应性 dst_points = np.array([[col*1.0, row*1.0] for row in range(3) for col in range(4)], dtype=np.float32) H, mask = cv2.findHomography(corners, dst_points, cv2.RANSAC, 5.0) print("单应性矩阵:\n", H) else: # 改用Harris角点筛选 dst = cv2.cornerHarris(gray, 2, 3, 0.04) dst = cv2.dilate(dst, None) threshold = 0.01 * dst.max() candidate_points = np.argwhere(dst > threshold)[:, [1,0]].astype(np.float32) # 计算候选点的水平/垂直间距,找最频繁的间距 dx = [] dy = [] for i in range(len(candidate_points)): for j in range(i+1, len(candidate_points)): dx_abs = abs(candidate_points[i][0] - candidate_points[j][0]) dy_abs = abs(candidate_points[i][1] - candidate_points[j][1]) if 10 < dx_abs < 100: # 过滤过小/过大的距离 dx.append(dx_abs) if 10 < dy_abs < 100: dy.append(dy_abs) if dx and dy: dx_mode = np.argmax(np.bincount(np.round(dx).astype(int))) dy_mode = np.argmax(np.bincount(np.round(dy).astype(int))) # 按x坐标分组为列 sorted_x = sorted(candidate_points, key=lambda p: p[0]) cols = [] current_col = [sorted_x[0]] for p in sorted_x[1:]: if abs(p[0] - current_col[-1][0]) < dx_mode * 0.3: current_col.append(p) else: cols.append(current_col) current_col = [p] cols.append(current_col) # 筛选有足够点数且行间距均匀的列 valid_cols = [] for col in cols: if len(col) < 3: continue col_sorted = sorted(col, key=lambda p: p[1]) gaps = [col_sorted[i+1][1] - col_sorted[i][1] for i in range(len(col_sorted)-1)] if all(abs(gap - dy_mode) < dy_mode * 0.3 for gap in gaps): valid_cols.append(col_sorted) if len(valid_cols) >=4: # 提取排序后的棋盘格角点 chess_corners = [] for row in range(3): for col in valid_cols[:4]: chess_corners.append(col[row]) chess_corners = np.array(chess_corners, dtype=np.float32) # 生成目标坐标并计算单应性 dst_points = np.array([[col*1.0, row*1.0] for row in range(3) for col in range(4)], dtype=np.float32) H, mask = cv2.findHomography(chess_corners, dst_points, cv2.RANSAC, 5.0) print("筛选出的棋盘格角点:\n", chess_corners) print("单应性矩阵:\n", H) # 绘制筛选后的角点 for p in chess_corners: cv2.circle(img, (int(p[0]), int(p[1])), 3, (0,255,0), -1) cv2.imshow('Filtered Corners', img) cv2.waitKey(0) else: print("未找到足够的候选角点") cv2.destroyAllWindows()
内容的提问来源于stack exchange,提问作者AlgoManiac
相关产品推荐
相关产品推荐

