【立体标定】圆形标定板标定python实现

1. 非对称圆形标定板

标定板生成器:calib.io

如上所示row = 实际行数/2 ,col = 实际列数

2. 代码

完整代码

python 复制代码
"""
Stereo calibration (asymmetric circles grid)
"""

import glob, os, cv2
import numpy as np
from tqdm import tqdm

# ---------- 参数 ----------
LEFT_DIR   = r'D:\Document\Camera1'
RIGHT_DIR  = r'D:\Document\Camera2'
ROWS       = 4          # 圆点行数(短边)
COLS       = 11         # 圆点列数(长边)
SQUARE     = 15.0       # 圆心间距,单位 mm


# ---------- 1. 生成物理坐标 ----------
def generate_board_points(rows, cols, dist, type_flag=1):
    """生成非对称圆点标定板 3D 坐标"""
    pts = []
    if type_flag == 1:
        offset_x, offset_y = dist / 2.0, dist / 2.0
        i = k = 0
        for row in range(cols):
            if row != 0:
                if row % 2 == 0:
                    k += 1
                else:
                    i += 1
            for col in range(rows):
                x = col * dist
                y = (row - (k if row % 2 == 0 else i)) * dist
                if row % 2 != 0:
                    x += offset_x
                    y += offset_y
                pts.append((x, y, 0.0))
    return np.array(pts, dtype=np.float32)

# ---------- 2. 查找圆点 ----------
def find_circles(images, pattern_size):
    """返回 (ok, corners) 列表"""
    blob_params = cv2.SimpleBlobDetector.Params()
    blob_params.filterByArea = False
    blob_params.minArea = 20
    blob_params.maxArea = 1024
    blob_params.filterByCircularity = True
    blob_params.minCircularity = 0.7
    blob_params.filterByConvexity = True
    blob_params.minConvexity = 0.8
    blob_params.filterByInertia = True
    blob_params.minInertiaRatio = 0.5
    detector = cv2.SimpleBlobDetector.create(blob_params)

    flags = cv2.CALIB_CB_ASYMMETRIC_GRID + cv2.CALIB_CB_CLUSTERING
    all_corners, ok_idx = [], []

    for im_path in tqdm(images, desc='Detect circles'):
        img = cv2.imread(im_path, cv2.IMREAD_GRAYSCALE)
        if img is None:  # 读图失败
            continue
        ok, corners = cv2.findCirclesGrid(img, pattern_size, flags=flags,
                                          blobDetector=detector)
        ok_idx.append(ok)
        if ok:
            corners = cv2.cornerSubPix(img, corners, (5, 5), (-1, -1),
                                       criteria=(cv2.TERM_CRITERIA_EPS +
                                                 cv2.TERM_CRITERIA_MAX_ITER, 10, 0.1))
            all_corners.append(corners)

    return all_corners, ok_idx

# ---------- 3. 主流程 ----------
def stereo_calibrate_main():
    left_imgs  = sorted(glob.glob(os.path.join(LEFT_DIR,  '*.*')))
    right_imgs = sorted(glob.glob(os.path.join(RIGHT_DIR, '*.*')))
    assert len(left_imgs) == len(right_imgs), '左右图像数量必须一致'

    # 3.1 准备 3D 点
    pattern_size = (ROWS, COLS)
    objp = generate_board_points(ROWS, COLS, SQUARE)
    objpoints = [objp] * len(left_imgs)   # 每幅图对应同一组 3D 点

    # 3.2 检测圆点
    left_corners,  ok_L = find_circles(left_imgs, pattern_size)
    right_corners, ok_R = find_circles(right_imgs, pattern_size)
    ok = np.array(ok_L) & np.array(ok_R)   # 只保留左右都检测成功的
    # 取最小数量,保证左右一一对应
    n_pairs = min(len(left_corners), len(right_corners))
    left_corners = left_corners[:n_pairs]
    right_corners = right_corners[:n_pairs]
    objpoints = [objp] * n_pairs
    print(f'有效图像对:{n_pairs}')

    if len(objpoints) < 3:
        raise RuntimeError('有效图像对不足,无法标定')

    # 3.3 单目标定
    img_size = cv2.imread(left_imgs[0], cv2.IMREAD_GRAYSCALE).shape[::-1]
    retL, K1, D1, rvecsL, tvecsL = cv2.calibrateCamera(
        objpoints, left_corners, img_size, None, None)
    retR, K2, D2, rvecsR, tvecsR = cv2.calibrateCamera(
        objpoints, right_corners, img_size, None, None)

    # 3.4 双目标定
    flags = (cv2.CALIB_FIX_INTRINSIC |
             cv2.CALIB_USE_INTRINSIC_GUESS |
             cv2.CALIB_RATIONAL_MODEL)
    retS, K1, D1, K2, D2, R, T, E, F = cv2.stereoCalibrate(
        objpoints, left_corners, right_corners,
        K1, D1, K2, D2, img_size,
        flags=flags,
        criteria=(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-6))

    # ---------- 4. 保存结果 ----------
    np.savez('stereo_calib.npz',
             K1=K1, D1=D1, K2=K2, D2=D2,
             R=R, T=T, E=E, F=F,
             img_size=img_size)

    # ---------- 5. 打印信息 ----------
    print('\n========== 双目标定结果 ==========')
    print(f'左重投影误差:{retL:.4f} 像素')
    print(f'右重投影误差:{retR:.4f} 像素')
    print(f'立体标定误差:{retS:.4f} 像素')
    print('\n左相机内参 K1:\n', K1)
    print('\n右相机内参 K2:\n', K2)
    print('\n旋转向量 R:\n', R)
    print('\n平移向量 T:\n', T)

if __name__ == '__main__':
    stereo_calibrate_main()

对称圆形标定板同理,替换cv2.findCirclesGrid 的参数flags=cv2.CALIB_CB_SYMMETRIC_GRID即可。

若是检测不到,可以在第一步添加下面代码观察圆形点是否被检出。

python 复制代码
img = cv2.resize(img, (640, 480))
# 检测斑点
keypoints = detector.detect(img)
# 在图像上绘制检测到的斑点
image_with_keypoints = cv2.drawKeypoints(img, keypoints, None, color=(0, 255, 0),
                                                 flags=cv2.DrawMatchesFlags_DRAW_RICH_KEYPOINTS)
# 显示结果
cv2.imshow("Blob Detection", image_with_keypoints)
cv2.waitKey(0)
相关推荐
我的xiaodoujiao7 小时前
使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 38--Allure 测试报告
python·学习·测试工具·pytest
Boilermaker199213 小时前
[Java 并发编程] Synchronized 锁升级
java·开发语言
沈浩(种子思维作者)14 小时前
真的能精准医疗吗?癌症能提前发现吗?
人工智能·python·网络安全·健康医疗·量子计算
saoys14 小时前
Opencv 学习笔记:图像掩膜操作(精准提取指定区域像素)
笔记·opencv·学习
MM_MS14 小时前
Halcon变量控制类型、数据类型转换、字符串格式化、元组操作
开发语言·人工智能·深度学习·算法·目标检测·计算机视觉·视觉检测
꧁Q༒ོγ꧂14 小时前
LaTeX 语法入门指南
开发语言·latex
njsgcs14 小时前
ue python二次开发启动教程+ 导入fbx到指定文件夹
开发语言·python·unreal engine·ue
alonewolf_9914 小时前
JDK17新特性全面解析:从语法革新到模块化革命
java·开发语言·jvm·jdk
io_T_T14 小时前
迭代器 iteration、iter 与 多线程 concurrent 交叉实践(详细)
python
古城小栈15 小时前
Rust 迭代器产出的引用层数——分水岭
开发语言·rust