【相机标定系列】单目相机,内参,外参

openCV标定:https://blog.csdn.net/weixin_44524040/article/details/103221837
matlab标定:mathwork官网

文章目录

  • 1、内参:
  • 2、外参:
  • 使用matlab工具箱标定后的结果换算

1、内参:

matlab 工具很简单,主要是需要知道各个参数的含义。这个请查询官网参数解释。十分重要

opencv 标定:查看:https://blog.csdn.net/weixin_44524040/article/details/103221837

import cv2
import numpy as np
import glob

# 标定图像
'''
标定步骤:
1)
'''
def calibration_photo(photo_path):
    # 设置要标定的角点个数(我这里使用的是11 × 8的棋盘,11×8代
    #表的是内角点,这里要注意,不懂的话可以数数我拍摄的棋盘你就知道哪个是内角点了)
    x_nums = 11  # x方向上的角点个数
    y_nums = 8
    # 设置(生成)标定图在世界坐标中的坐标
    world_point = np.zeros((x_nums * y_nums, 3), np.float32)  # 生成x_nums*y_nums个坐标,每个坐标包含x,y,z三个元素
    world_point[:, :2] =15 * np.mgrid[:x_nums, :y_nums].T.reshape(-1, 2)  # mgrid[]生成包含两个二维矩阵的矩阵,每个矩阵都有x_nums列,y_nums行,我这里用的是15mm×15mm的方格,所以乘了15,以mm代表世界坐标的计量单位
    print(world_point)#打印出来的就是某一张出图片的世界坐标了
    # .T矩阵的转置
    # reshape()重新规划矩阵,但不改变矩阵元素
    # 保存角点坐标
    world_position = [] #存放世界坐标
    image_position = [] #存放棋盘角点对应的图片像素坐标
    
    '''
    下面就是查找图片中角点的像素坐标存入image_position了
    '''
    
    # 设置角点查找限制
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
    # 获取所有标定图
    images = glob.glob(photo_path + '\\*.jpg')
    # print(images)
    for image_path in images:
        image = cv2.imread(image_path)
        gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
        # 查找角点
        ok, corners = cv2.findChessboardCorners(gray, (x_nums, y_nums), None)
        """
		如果能找得到角点:返回角点对应的像素坐标,并且将其对应到世界坐标中
		世界坐标[0,0,0],[0,1,0].....
		图像坐标[10.123123,20.123122335],[19.123123,21.123123123]....
        """
        if ok:
            # 把每一幅图像的世界坐标放到world_position中
            world_position.append(world_point)
            # 获取更精确的角点位置
            exact_corners = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
            # 把获取的角点坐标放到image_position中
            image_position.append(exact_corners)
            # 可视化角点
            image = cv2.drawChessboardCorners(image,(x_nums,y_nums),exact_corners,ok)
            cv2.imshow('image_corner',image)
            cv2.waitKey(1)
     
    """
    点对应好了,开始计算内参,畸变矩阵,外参
    """
    ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(world_position, image_position, gray.shape[::-1], None, None)
    #内参是mtx,畸变矩阵是dist,旋转向量(要得到矩阵还要进行罗德里格斯变换)rvecs,外参:平移矩阵tvecs
    # 将内参保存起来
    np.savez('D:\\ML\\Project_python\\my_code\\video_and_img\\checkerboard', mtx=mtx, dist=dist)
    
    print('内参是:\n', mtx, '\n畸变参数是:\n', dist,
       '\n外参:旋转向量(要得到矩阵还要进行罗德里格斯变换,下章讲)是:\n',rvecs, '\n外参:平移矩阵是:\n',tvecs)
          
    # 计算偏差
    mean_error = 0
    for i in range(len(world_position)):
        image_position2, _ = cv2.projectPoints(world_position[i], rvecs[i], tvecs[i], mtx, dist)
        error = cv2.norm(image_position[i], image_position2, cv2.NORM_L2) / len(image_position2)
        mean_error += error
    print("total error: ", mean_error / len(image_position))

def main():
    # 标定图像保存路径
    photo_path = "D:\\ML\\Project_python\\my_code\\video_and_img\\checkerboard"
    calibration_photo(photo_path)
if __name__ == '__main__':
    main()

2、外参:

如果你用opencv标内参,可以直接调用内参文件npz
https://blog.csdn.net/weixin_44524040/article/details/103223103

import cv2
import numpy as np

def draw(img, corners, imgpts):
    corner = tuple(corners[0].ravel())
    img = cv2.line(img, corner, tuple(imgpts[0].ravel()), (255, 0, 0), 5)
    img = cv2.line(img, corner, tuple(imgpts[1].ravel()), (0, 255, 0), 5)
    img = cv2.line(img, corner, tuple(imgpts[2].ravel()), (0, 0, 255), 5)
    return img


# 标定图像
def calibration_photo(photo_path):
    # 设置要标定的角点个数
    x_nums = 11  # x方向上的角点个数
    y_nums = 8
    # 设置(生成)标定图在世界坐标中的坐标
    world_point = np.zeros((x_nums * y_nums, 3), np.float32)  # 生成x_nums*y_nums个坐标,每个坐标包含x,y,z三个元素
    world_point[:, :2] = 15 * np.mgrid[:x_nums, :y_nums].T.reshape(-1, 2)  # mgrid[]生成包含两个二维矩阵的矩阵,每个矩阵都有x_nums列,y_nums行
    print('world point:',world_point)
    # .T矩阵的转置
    # reshape()重新规划矩阵,但不改变矩阵元素
    # 设置世界坐标的坐标
    axis = 15* np.float32([[3, 0, 0], [0, 3, 0], [0, 0, -3]]).reshape(-1, 3)
    # 设置角点查找限制
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)

    image = cv2.imread(photo_path)

    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    # 查找角点
    ok, corners = cv2.findChessboardCorners(gray, (x_nums, y_nums), )
    # print(ok)
    if ok:
        # 获取更精确的角点位置
        exact_corners = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)

        # 获取外参
        _, rvec, tvec, inliers = cv2.solvePnPRansac(world_point, exact_corners, mtx, dist)
        #获得的旋转矩阵是向量,是3×1的矩阵,想要还原回3×3的矩阵,需要罗德里格斯变换Rodrigues,
        
        rotation_m, _ = cv2.Rodrigues(rvec)#罗德里格斯变换
        # print(rotation_m)
        # print('旋转矩阵是:\n', rvec)
        # print('平移矩阵是:\n', tvec)
        rotation_t = np.hstack([rotation_m,tvec])
        rotation_t_Homogeneous_matrix = np.vstack([rotation_t,np.array([[0, 0, 0, 1]])])
        print(rotation_t_Homogeneous_matrix)
        imgpts, jac = cv2.projectPoints(axis, rvec, tvec, mtx, dist)
        # 可视化角点
        img = draw(image, corners, imgpts)
        cv2.imshow('img', img)
        return rotation_t_Homogeneous_matrix # 返回旋转矩阵和平移矩阵组成的其次矩阵


if __name__ == '__main__':
    # 读取相机内参
    with np.load('D:\\ML\\Project_python\\my_code\\video_and_img\\checkerboard.npz') as X:
        mtx, dist = [X[i] for i in ('mtx', 'dist')]
        print(mtx, '\n', dist)
    photo_path = "D:\\ML\\Project_python\\my_code\\video_and_img\\checkerboard\\WIN_20191123_11_54_24_Pro.jpg" # 标定图像保存路径
    calibration_photo(photo_path)
    cv2.waitKey()
    cv2.destroyAllWindows()

如果你是用matlab 标定内参,可以手动设置内参:

import cv2
import numpy as np

def draw(img, corners, imgpts):
    corner = tuple(corners[0].ravel())
    img = cv2.line(img, corner, tuple(imgpts[0].ravel()), (255, 0, 0), 5)
    img = cv2.line(img, corner, tuple(imgpts[1].ravel()), (0, 255, 0), 5)
    img = cv2.line(img, corner, tuple(imgpts[2].ravel()), (0, 0, 255), 5)
    return img


# 标定图像
def calibration_photo(photo_path):
    # 设置要标定的角点个数
    x_nums = 9 # x方向上的角点个数
    y_nums = 3
    # 设置(生成)标定图在世界坐标中的坐标
    world_point = np.zeros((x_nums * y_nums, 3), np.float32)+1700  # 生成x_nums*y_nums个坐标,每个坐标包含x,y,z三个元素
    world_point[:, :2] = 100 * np.mgrid[:x_nums, :y_nums].T.reshape(-1, 2)  # mgrid[]生成包含两个二维矩阵的矩阵,每个矩阵都有x_nums列,y_nums行我这里用的是100mm×100mm的方格,所以乘了100,以mm代表世界坐标的计量单位
    print(world_point)#打印出来的就是某一张出图片的世界坐标了
    world_point[:, :2]  =world_point[:, :2] +[-372,-1235]
    print('world point:',world_point)
    # .T矩阵的转置
    # reshape()重新规划矩阵,但不改变矩阵元素
    # 设置世界坐标的坐标
    axis = 100* np.float32([[1, 0, 0], [0, 1, 0], [0, 0, -1]]).reshape(-1, 3)
    print(axis)
    # 设置角点查找限制
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)

    image = cv2.imread(photo_path)

    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    # 查找角点
    ok, corners = cv2.findChessboardCorners(gray, (x_nums, y_nums), )
    
    '''
    ########################################################
    '''
    mtx=[[1.126646475614905e+03,0,6.111793261115752e+02],[0,1.087426433445202e+03,3.248166933870364e+02],[6.111793261115752e+02,3.248166933870364e+02,1]]
    mtx=np.array(mtx).astype(np.float)
    dist=[[-0.4161,0.1689,0.,0.,0.]]#数 D = (k1,k2, P1, P2, k3);
    dist=np.array(dist).astype(np.float)
    print(ok)
    if ok:
        # 获取更精确的角点位置
        exact_corners = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)

        # 获取外参
        _, rvec, tvec, inliers = cv2.solvePnPRansac(world_point, exact_corners, mtx, dist)
        #获得的旋转矩阵是向量,是3×1的矩阵,想要还原回3×3的矩阵,需要罗德里格斯变换Rodrigues,
        
        rotation_m, _ = cv2.Rodrigues(rvec)#罗德里格斯变换
        # print(rotation_m)
        print('旋转矩阵是:\n', rvec)
        print('平移矩阵是:\n', tvec)
        rotation_t = np.hstack([rotation_m,tvec])
        rotation_t_Homogeneous_matrix = np.vstack([rotation_t,np.array([[0, 0, 0, 1]])])
        print(rotation_t_Homogeneous_matrix)
        imgpts, jac = cv2.projectPoints(axis, rvec, tvec, mtx, dist)
        # 可视化角点
        img = draw(image, corners, imgpts)
        cv2.imshow('img', img)
        return rotation_t_Homogeneous_matrix # 返回旋转矩阵和平移矩阵组成的其次矩阵


if __name__ == '__main__':
    # # 读取相机内参
    # with np.load('D:\\ML\\Project_python\\my_code\\video_and_img\\checkerboard.npz') as X:
        # mtx, dist = [X[i] for i in ('mtx', 'dist')]
        # print(mtx, '\n', dist)
    photo_path = "1-1.avi(551).jpg" # 标定图像保存路径
    calibration_photo(photo_path)
    cv2.waitKey()
    cv2.destroyAllWindows()

统一计算世界坐标点的方法

import cv2
import numpy as np
x_nums = 9 # x方向上的角点个数
y_nums = 3
world_point = np.zeros((x_nums * y_nums, 3), np.float32)+1700  # 生成x_nums*y_nums个坐标,每个坐标包含x,y,z三个元素
world_point[:, :2] = 100 * np.mgrid[:x_nums, :y_nums].T.reshape(-1, 2)  # mgrid[]生成包含两个二维矩阵的矩阵,每个矩阵都有x_nums列,y_nums行
world_point[:, :2]  =world_point[:, :2] +[-372,-1235]
print('world point:',world_point)

【相机标定系列】单目相机,内参,外参_第1张图片

使用matlab工具箱标定后的结果换算

标定的相关工作,记录一下标定的结果。

我使用的是basler aca 2500-14gm相机+12mm镜头进行的标定实验,得到的标定结果大概如下,由于两台相机的位置摆放没有确定,在此只记录内参数的焦距结果。

标定的结果单位主要是像素,可以看到两台相机的焦距近似相等,之所以有两个值,原因如下:

采用matlab摄像机标定工具箱标出来的焦距值有两个,它对应于像素在x、y两个方向的尺寸大小不一致的情况。本来,从绝对尺寸来说,摄像机的焦距只应该有一个。但若用像素个数来表示时,问题会有所不同。假定焦距为fmm,若像素水平尺寸为dx mm,垂直尺寸为dy mm ,则焦距f在水平方向的像素个数就是f/dx,垂直方向像素个数就是f/dy,显然二者是不同的。

(参考博客:https://blog.csdn.net/chenxi0601041028/article/details/8234824?locationNum=14&fps=1)

也正对应了内参矩阵中的fx与fy。

由于得到的结果单位是像素,需要计算转换为mm,需要乘以对应的像素尺寸dx与dy,在购买的basler aca 2500-14gm手册可知

水平/垂直像素尺寸	2.2 µm x 2.2 µm

但是得到的结果是(以左相机x方向为例):f=1415.06504pix*2.2um/pix=3113.143088um≈3.1mm

与镜头的12mm差了好几倍,后来查了半天才发现使用像素尺寸进行计算时,图像的分辨率需要一致。

手册中的分辨率是25291944,我的图像却是640480,差了4.05倍

所以最终的结果应该是f=3.1*4.05=12.555mm

虽说仍有一定误差,但是已经接近了真实的焦距值。

其他参数弄清楚后再继续记录吧。
https://blog.csdn.net/berlinpand/article/details/81012853

你可能感兴趣的:(摄像头底层)