Opencv相机标定(1):内外参、畸变系数标定与三维点到二维图像投影

0. 内容

可用于相机标定图片筛选

相机标定

三维点重投影会二维图像

绘制Z平面以及连接线

注意:该标定方法适用于畸变较小得普通相机,鱼眼相机标定如下:Opencv相机标定(2):鱼眼相机校正_m0_58772523的博客-CSDN博客...

1. 图片筛选

利用opencv中findChessboardCorners判断图片是否可以检测到对应角点,若检测到角点ret值为True,当检测不到角点时删除对应图片。

import os
import cv2
import glob


def delete_pic(inter_corner_shape, img_dir, img_type):
    images = glob.glob(img_dir + os.sep + '**.' + img_type)
    for fname in images:
        img = cv2.imread(fname)
        gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        ret, cp_img = cv2.findChessboardCorners(gray_img,
            (inter_corner_shape[0], inter_corner_shape[1]), None)
        if not ret:
            os.remove(fname)


if __name__ == '__main__':
    inter_corner_shape = (9, 6)
    img_dir = "./data"
    img_type = "jpg"
    delete_pic(inter_corner_shape, img_dir, img_type)

2. 线条和平面绘制辅助函数(含详细注释)

# 二维图片, 图片角点坐标, 所需投影的三维点在图像中的投影点坐标
def draw(img, corners, imgpts):
    # 获取第一个图像角点信息
    corner = tuple(corners[0].ravel().astype(np.int64))
    # 利用第一个图像角点坐标和投影点的坐标绘制想要的直线和平面
    # np.ravel()函数为多维数组的维度降为一维
    # 新版本的opencv在绘制直线和平面时需手动将坐标信息转为整型,老版本绘制直线时不需要(测试发现的)
    img = cv2.drawContours(img, [np.array([list(corners[0].ravel()), list(imgpts[1].ravel()),
                                           list(imgpts[2].ravel()), list(imgpts[3].ravel())]).astype(np.int64)], -1,
                           (175, 0, 175), -3)
    img = cv2.line(img, corner, tuple(imgpts[0].ravel().astype(np.int64)), (255, 0, 0), 5)
    img = cv2.line(img, corner, tuple(imgpts[1].ravel().astype(np.int64)), (0, 255, 0), 5)
    # img = cv2.line(img, corner, tuple(imgpts[2].ravel().astype(np.int64)), (0, 0, 255), 5)
    img = cv2.line(img, corner, tuple(imgpts[3].ravel().astype(np.int64)), (0, 0, 255), 5)
    return img

3. 主函数(相机标定:获取内参、畸变系数,外参<标定阶段没太大作用>)

# 定义角点数量, 棋盘格尺寸(应该可以随意写,不影响参数标定),图像路径, 图像类型
def calib(inter_corner_shape, size_per_grid, img_dir, img_type):
    # 生成角点的世界坐标系坐标
    # np.mgrid() 返回多维结构,具体功能建议自行尝试
    w, h = inter_corner_shape
    cp_int = np.zeros((w * h, 3), np.float32)
    cp_int[:, :2] = np.mgrid[0:w, 0:h].T.reshape(-1, 2)
    cp_world = cp_int * size_per_grid
    obj_points = []
    img_points = []
    # 角点提取
    images = glob.glob(img_dir + os.sep + '**.' + img_type)
    for fname in images:
        img = cv2.imread(fname)
        gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        # 得到是否有角点以及角点
        ret, cp_img = cv2.findChessboardCorners(gray_img, (w, h), None)
        if ret:
            # 得到优化后的角点
            cp_img = cv2.cornerSubPix(gray_img, cp_img, (11, 11), (-1, -1), criteria)
            obj_points.append(cp_world)
            img_points.append(cp_img)
            # 绘制角点,可以检查角点提取是否准确
            cv2.drawChessboardCorners(img, (w, h), cp_img, ret)
            cv2.imshow('FoundCorners', img)
            cv2.waitKey(100)
    cv2.destroyAllWindows()
    # 相机标定
    # 参数:世界坐标系坐标,角点坐标,图像,None,None(标定方法,默认)
    # 需要注意:opencv中图像为宽、高,与numpy的读取方式不同
    # 传参数应依次传numpy的1, 0两个维度
    _, mat_inter, coff_dis, v_rot, v_trans = cv2.calibrateCamera(obj_points,
                    img_points, gray_img.shape[::-1], None, None)
    # 内参
    print("internal matrix:\n", mat_inter)
    # 畸变系数
    print("distortion cofficients:\n", coff_dis)
    # 旋转向量
    # print("rotation vectors:\n", v_rot)
    # 平移向量
    # print("translation vectors:\n", v_trans)
    # 将世界坐标系的点投影回二维平面,计算投影点与角点的距离误差
    total_error = 0
    for i in range(len(obj_points)):
        img_points_repro, _ = cv2.projectPoints(obj_points[i], v_rot[i], v_trans[i], mat_inter, coff_dis)
        error = cv2.norm(img_points[i], img_points_repro, cv2.NORM_L2) / len(img_points_repro)
        total_error += error
    print(("Average Error of Reproject: "), total_error / len(obj_points))
    return mat_inter, coff_dis, cp_world

4. 实现(含详细注释)

if __name__ == '__main__':
    # 定义角点数量
    inter_corner_shape = (9, 6)
    # 定义棋盘格尺寸
    size_per_grid = 0.02
    # 定义图片路径
    img_dir = "./data"
    # 定义图片格式
    img_type = "jpg"
    # 相机标定
    mat_inter, coff_dis, cp_world = calib(inter_corner_shape, size_per_grid, img_dir, img_type)
    # 读取图片测试标定效果
    img = cv2.imread("./data/test01.jpg")
    h, w = img.shape[:2]
    # new为优化参数矩阵,roi为opencv认为的感兴趣区域,可用于去畸变后的黑色边缘裁剪
    # 当参数1改为0时,opencv会对图像自动裁剪
    # 注意w和h
    new, roi = cv2.getOptimalNewCameraMatrix(mat_inter, coff_dis, (w, h), 1, (w, h))
    x, y, w, h = roi
    print(roi)
    # 图像去畸变,方法一
    dst = cv2.undistort(img, mat_inter, coff_dis, None, new)
    # 图像去畸变,方法二,有点点问题,可以自己尝试
    # mapx, mapy = cv2.initUndistortRectifyMap(mat_inter, coff_dis, None, new, (w, h), 5)
    # dst = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)
    cv2.namedWindow('origin', cv2.WINDOW_NORMAL)
    cv2.namedWindow('no_cut', cv2.WINDOW_NORMAL)
    cv2.namedWindow('cut', cv2.WINDOW_NORMAL)
    cv2.namedWindow('img_with_line', cv2.WINDOW_NORMAL)
    cv2.imshow('origin', img)
    cv2.imshow('cut', dst[y: y+h, x: x+w])
    cv2.imshow('no_cut', dst)
    # 可视化连线,流程如下
    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    # 找角点 -> 优化角点 -> 获取外参 -> 外参矩阵变换 -> 选择要绘制的世界坐标系点 -> 得到世界坐标系点在图像上的投影点 -> 绘制连线
    ok, corners = cv2.findChessboardCorners(gray, (inter_corner_shape[0], inter_corner_shape[1]), )
    if ok:
        exact_corners = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
        _, rvec, tvec, inliers = cv2.solvePnPRansac(cp_world, exact_corners, mat_inter, coff_dis)
        axis = 0.02 * np.float32([[0, 0, -8], [8, 0, 0], [8, 5, 0], [0, 5, 0]]).reshape(-1, 3)
        imgpts, _ = cv2.projectPoints(axis, rvec, tvec, mat_inter, coff_dis)
        img = draw(img, corners, imgpts)
        cv2.imshow('img_with_line', img)
    cv2.waitKey(0)

5. 结果展示

Opencv相机标定(1):内外参、畸变系数标定与三维点到二维图像投影_第1张图片

 6. 代码链接

360sorround/calibration at main · liuweixue001/360sorround (github.com)

7. 后续更新

1. 图像去畸变、鸟瞰图生成、图像拼接、图像融合:liuweixue001/360sorround: 360环视--相机校正->畸变处理->俯视变换->图像拼接 (github.com)

2. C++版本相关工作:

liuweixue001/360surround-2.0-: 一个简单案例,包含:图像去畸变、俯视变换、图像拼接和融合 (github.com)

你可能感兴趣的:(360环视,计算机视觉,opencv)