Python案例如何用OpenCV做相机标定

wen python案例 2

本文目录导读:

Python案例如何用OpenCV做相机标定

  1. 准备工作
  2. 完整相机标定代码
  3. 使用方法
  4. 关键参数说明
  5. 注意事项

我来详细说明如何使用OpenCV进行相机标定,包含完整案例:

准备工作

安装依赖

pip install opencv-python numpy

准备标定板

  • 打印棋盘格标定板(如9×6的内角点)
  • 或使用显示器显示棋盘格
  • 从不同角度拍摄15-20张照片

完整相机标定代码

import cv2
import numpy as np
import glob
import os
class CameraCalibration:
    def __init__(self, chessboard_size=(9, 6), square_size=25):
        """
        初始化相机标定器
        :param chessboard_size: 棋盘格内角点数量 (列数, 行数)
        :param square_size: 棋盘格方格大小(毫米)
        """
        self.chessboard_size = chessboard_size
        self.square_size = square_size
        # 准备对象点(3D坐标)
        self.object_points = []  # 世界坐标系中的3D点
        self.image_points = []   # 图像坐标系中的2D点
        # 棋盘格角点的世界坐标
        self.objp = np.zeros((chessboard_size[0] * chessboard_size[1], 3), np.float32)
        self.objp[:, :2] = np.mgrid[0:chessboard_size[0], 0:chessboard_size[1]].T.reshape(-1, 2)
        self.objp *= square_size
    def find_corners(self, image_paths):
        """
        查找棋盘格角点
        :param image_paths: 图像路径列表
        :return: 成功检测到角点的图像数量
        """
        successful_images = 0
        for image_path in image_paths:
            # 读取图像
            img = cv2.imread(image_path)
            if img is None:
                print(f"无法读取图像: {image_path}")
                continue
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            # 查找棋盘格角点
            ret, corners = cv2.findChessboardCorners(gray, 
                                                      self.chessboard_size, 
                                                      None)
            if ret:
                # 亚像素精确化角点位置
                criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
                corners_refined = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
                # 保存点集
                self.object_points.append(self.objp)
                self.image_points.append(corners_refined)
                # 可视化角点
                cv2.drawChessboardCorners(img, self.chessboard_size, 
                                          corners_refined, ret)
                cv2.imshow('Chessboard Corners', img)
                cv2.waitKey(100)
                successful_images += 1
                print(f"成功检测: {image_path}")
            else:
                print(f"未能检测到棋盘格: {image_path}")
        cv2.destroyAllWindows()
        return successful_images
    def calibrate(self, image_size):
        """
        执行相机标定
        :param image_size: 图像尺寸 (width, height)
        :return: 标定结果
        """
        if len(self.object_points) < 3:
            print("至少需要3张有效图像进行标定")
            return None
        # 执行标定
        ret, camera_matrix, dist_coeffs, rvecs, tvecs = cv2.calibrateCamera(
            self.object_points, 
            self.image_points, 
            image_size, 
            None, 
            None
        )
        return ret, camera_matrix, dist_coeffs, rvecs, tvecs
    def evaluate_calibration(self, image_paths):
        """
        评估标定结果(重投影误差)
        """
        total_error = 0
        for i, image_path in enumerate(image_paths):
            img = cv2.imread(image_path)
            if img is None:
                continue
            # 计算重投影错误
            imgpoints2, _ = cv2.projectPoints(
                self.object_points[i], 
                rvecs[i], 
                tvecs[i], 
                camera_matrix, 
                dist_coeffs
            )
            error = cv2.norm(self.image_points[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
            total_error += error
        return total_error / len(self.object_points)
def main():
    """
    主函数:演示相机标定流程
    """
    # 设置参数
    CHESSBOARD_SIZE = (9, 6)  # 棋盘格内角点数
    SQUARE_SIZE = 25  # 方格大小(毫米)
    # 创建标定器
    calibrator = CameraCalibration(CHESSBOARD_SIZE, SQUARE_SIZE)
    # 获取标定图像路径
    input_dir = "calibration_images"  # 存放标定图像的文件夹
    print("=== 相机标定程序 ===")
    print(f"棋盘格配置: {CHESSBOARD_SIZE[0]}x{CHESSBOARD_SIZE[1]} 内角点")
    print(f"方格大小: {SQUARE_SIZE}mm")
    print()
    # 检查目录是否存在
    if not os.path.exists(input_dir):
        print(f"错误: 找不到目录 {input_dir}")
        print("请在程序同目录下创建 'calibration_images' 文件夹")
        print("并将棋盘格照片放入该文件夹")
        return
    # 获取所有图像
    image_paths = glob.glob(os.path.join(input_dir, "*.jpg")) + \
                  glob.glob(os.path.join(input_dir, "*.png"))
    if len(image_paths) == 0:
        print("未找到图像文件,请将棋盘格照片放入文件夹")
        return
    print(f"找到 {len(image_paths)} 张图像")
    print("开始检测棋盘格角点...")
    # 查找角点
    successful = calibrator.find_corners(image_paths)
    print(f"\n成功检测: {successful}/{len(image_paths)} 张图像")
    if successful < 3:
        print("有效图像不足,请拍摄更多照片")
        return
    # 获取图像尺寸
    sample_img = cv2.imread(image_paths[0])
    image_size = (sample_img.shape[1], sample_img.shape[0])
    # 执行标定
    print("\n正在执行相机标定...")
    results = calibrator.calibrate(image_size)
    if results is None:
        return
    ret, camera_matrix, dist_coeffs, rvecs, tvecs = results
    # 输出标定结果
    print("\n=== 标定结果 ===")
    print(f"重投影误差: {ret:.6f}")
    print(f"\n相机内参矩阵 (K):")
    print(f"{camera_matrix}")
    print(f"\n畸变系数 (k1, k2, p1, p2, k3):")
    print(f"{dist_coeffs.flatten()}")
    # 评估标定质量
    mean_error = calibrator.evaluate_calibration(image_paths[:successful])
    print(f"\n平均重投影误差: {mean_error:.6f} 像素")
    # 保存标定结果
    save_calibration_results(camera_matrix, dist_coeffs)
    # 演示畸变校正
    demonstrate_undistortion(image_paths[0], camera_matrix, dist_coeffs)
    print("\n标定完成!")
def save_calibration_results(camera_matrix, dist_coeffs):
    """
    保存标定结果到文件
    """
    np.savez("calibration_results.npz",
             camera_matrix=camera_matrix,
             dist_coeffs=dist_coeffs)
    print("\n标定结果已保存到 calibration_results.npz")
def demonstrate_undistortion(image_path, camera_matrix, dist_coeffs):
    """
    演示畸变校正效果
    """
    # 读取原始图像
    img = cv2.imread(image_path)
    if img is None:
        return
    h, w = img.shape[:2]
    # 计算新的相机矩阵(优化视野)
    new_camera_matrix, roi = cv2.getOptimalNewCameraMatrix(
        camera_matrix, dist_coeffs, (w, h), 1, (w, h))
    # 畸变校正
    undistorted_img = cv2.undistort(img, camera_matrix, dist_coeffs, 
                                     None, new_camera_matrix)
    # 裁剪图像(去除黑色边缘)
    x, y, w, h = roi
    undistorted_img = undistorted_img[y:y+h, x:x+w]
    # 显示结果
    cv2.imshow('Original Image', img)
    cv2.imshow('Undistorted Image', undistorted_img)
    # 保存结果
    cv2.imwrite('original.jpg', img)
    cv2.imwrite('undistorted.jpg', undistorted_img)
    print("原图和校正后图像已保存")
    print("按任意键关闭窗口...")
    cv2.waitKey(0)
    cv2.destroyAllWindows()
def load_and_use_calibration():
    """
    加载标定结果并应用于新图像
    """
    # 加载标定结果
    with np.load('calibration_results.npz') as data:
        camera_matrix = data['camera_matrix']
        dist_coeffs = data['dist_coeffs']
    # 创建标定对象
    calibrator = CameraCalibration()
    # 测试图像校正
    test_image_path = "test_image.jpg"
    if os.path.exists(test_image_path):
        img = cv2.imread(test_image_path)
        if img is not None:
            h, w = img.shape[:2]
            new_camera_matrix, _ = cv2.getOptimalNewCameraMatrix(
                camera_matrix, dist_coeffs, (w, h), 1, (w, h))
            # 畸变校正
            dst = cv2.undistort(img, camera_matrix, dist_coeffs, None, new_camera_matrix)
            cv2.imshow('Test Image', dst)
            cv2.waitKey(0)
            cv2.destroyAllWindows()
if __name__ == "__main__":
    main()

使用方法

步骤1:准备标定图像

# 创建目录存放标定图像
mkdir calibration_images
# 将棋盘格照片(从不同角度拍摄15-20张)放入该目录

步骤2:运行标定程序

python camera_calibration.py

步骤3:使用标定结果

# 加载保存的标定结果
import numpy as np
import cv2
# 加载标定数据
with np.load('calibration_results.npz') as data:
    camera_matrix = data['camera_matrix']
    dist_coeffs = data['dist_coeffs']
# 校正新图像
def undistort_image(image, camera_matrix, dist_coeffs):
    h, w = image.shape[:2]
    new_camera_matrix, roi = cv2.getOptimalNewCameraMatrix(
        camera_matrix, dist_coeffs, (w, h), 1, (w, h))
    dst = cv2.undistort(image, camera_matrix, dist_coeffs, None, new_camera_matrix)
    return dst
# 使用示例
img = cv2.imread('new_image.jpg')
undistorted = undistort_image(img, camera_matrix, dist_coeffs)

关键参数说明

棋盘格配置

CHESSBOARD_SIZE = (9, 6)  # 内角点数量(不是方格数量)
SQUARE_SIZE = 25  # 方格实际尺寸(单位:mm)

标定质量评估指标

  • 重投影误差:< 0.5 像素表示标定质量良好
  • 焦距参数:fx, fy 应接近相机规格
  • 主点坐标:cx, cy 应在图像中心附近

注意事项

  1. 图像拍摄要求

    • 尽量从不同角度拍摄
    • 棋盘格要占据图像的30-50%
    • 保持图像清晰,避免模糊
    • 保证光照均匀
  2. 棋盘格要求

    • 使用平整的棋盘格
    • 避免棋盘格有污渍或损坏
    • 确保棋盘格在图像中完整可见
  3. 常见问题

    • 角点检测失败:调整光照或使用亚像素处理
    • 标定结果差:增加图像数量(至少15张)
    • 畸变校正不理想:检查标定参数是否保存正确

这个案例提供了完整的相机标定解决方案,可以直接使用或根据具体需求修改。

抱歉,评论功能暂时关闭!