相机标定原理与实战【python-opencv】

这篇具有很好参考价值的文章主要介绍了相机标定原理与实战【python-opencv】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

为什么要进行相机标定?

相机的功能就是将真实的三维世界拍摄形成二维的图片。所以可以将相机成像的过程看做一个函数,输入是一个三维的场景,输出是二维的图片。但是,当我们想将二维的图片反映射成三维场景时,很明显,我们无法仅通过一张二维图来得到真实的三维场景。也就是说,上述的函数是不可逆的。

相机标定的目标是用一个具体的数学模型来模拟复杂的成像过程,并且求解出该数学模型中的一些参数,包括相机的内参,畸变系数和外参。这样我们便能够近似这个三维到二维的过程,进而找到这个函数的反函数,便可以从获取二维重建出三维。

相机标定原理与实战【python-opencv】

一旦标定得到这些参数,我们就得到了一个完整的相机成像的数学模型,其在在机器视觉、图像测量、摄影测量、三维重建等应用普遍应用。如

  • (1)对相机拍摄的图片进行畸变校正;
  • (2)确定空间物体表面某点的三维几何位置与其在图像中对应点之间的相互关系;
  • (3)确定物理尺寸和像素间的换算关系;
  • (4)用多个相机拍摄图片来进行三维重建;
  • (5)以及其他的计算机视觉的应用。

1. 成像几何

1.1 坐标系统

相机标定原理与实战【python-opencv】

  • 世界坐标系( O W − X W Y W Z W O_W-X_WY_WZ_W OWXWYWZW):用户定义的三维世界的坐标系,为了描述目标物在真实世界里的位置而被引入。世界坐标系的原点位置可以根据需要调整。单位 m m m;
  • 相机坐标系( O C − X C Y C Z C O_C-X_CY_CZ_C OCXCYCZC):以摄像机光心为原点(在针孔模型中也就是针孔为光心), Z Z Z轴与光轴重合(与成像平面垂直),X 轴和Y 轴分别平行于图像物理坐标系(CCD)的X轴和 Y 轴, f f f为摄像机的焦距。单位 m m m;
  • 图像物理坐标系( o − x y o-xy oxy):用物理单位表示像素的位置,坐标原点为摄像机光轴与图像物理坐标系的交点位置。单位是 m m mm mm。单位毫米的原因是此时由于相机内部的CCD传感器是很小的,比如8mm x 6mm。
  • 像素坐标系( u v uv uv):以像素为单位,坐标原点在左上角。举个例子,CCD传感上上面的8mm x 6mm,转换到像素大小是640x480,此时dx表示像素坐标系中每个像素的物理大小就是1/80mm,也就是说毫米与像素点的之间关系是piexl/mm.

1.2 坐标转换

1.2.1 世界坐标系转换到相机坐标系

相机标定原理与实战【python-opencv】

从世界坐标系变换到相机坐标系属于刚体变换,即物体不会发生形变,只需要进行旋转和平移。

[ X C Y C Z C ] = R [ X W Y W Z W ] + T \begin{bmatrix} X_C \\ Y_C \\ Z_C \end{bmatrix} = R \begin{bmatrix} X_W \\ Y_W \\ Z_W \end{bmatrix} + T XCYCZC=RXWYWZW+T

R ∈ R 3 × 3 R \in R^{3 \times3} RR3×3: 旋转矩阵;
T ∈ R 3 × 1 T \in R^{3 \times 1} TR3×1: 平移向量;

齐次表达:
[ X C Y C Z C 1 ] = [ R T 0 1 ] [ X W Y W Z W 1 ] \begin{bmatrix} X_C \\ Y_C \\ Z_C \\ 1 \end{bmatrix} = \begin{bmatrix} R & T \\ 0 & 1 \\ \end{bmatrix} \begin{bmatrix} X_W \\ Y_W \\ Z_W \\ 1 \end{bmatrix} XCYCZC1=[R0T1]XWYWZW1

这里相机标定原理与实战【python-opencv】就是相机的外参矩阵,一旦世界坐标系发生变化,外参矩阵也要随之变化。

1.1.3 相机坐标系到图像坐标系

相机标定原理与实战【python-opencv】

从相机坐标系到图像坐标系,属于透视投影关系,从3D转换到2D。根据图中的相似三角形可以得出以下对应关系:
Δ A B O C ∼ Δ o C O C Δ P B O C ∼ Δ p C O C \Delta ABO_C \sim \Delta oCO_C \\ \Delta PBO_C \sim \Delta pCO_C \\ ΔABOCΔoCOCΔPBOCΔpCOC
A B o C = A O C o O C = P B p C = X C x = Z C f = Y C y \frac{AB}{oC} = \frac{AO_C}{oO_C} = \frac{PB}{pC}=\frac{X_C}{x} = \frac{Z_C}{f} = \frac{Y_C}{y} oCAB=oOCAOC=pCPB=xXC=fZC=yYC

此处的 f f f对应相机焦距,最终,图像物理坐标系中的一点和相机坐标系中的对应点的转换关系便可以表示如下:

{ x = f X C Z C y = f Y C Z C \left\{ \begin{gathered} x = f \frac{X_C}{Z_C} \\ y = f \frac{Y_C}{Z_C} \end{gathered} \right. x=fZCXCy=fZCYC

其矩阵表示如下:

Z C [ x y 1 ] = [ f 0 0 0 0 f 0 0 0 0 1 0 ] [ X C Y C Z C 1 ] Z_C \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} = \begin{bmatrix} f & 0 & 0 & 0 \\ 0 & f & 0 & 0 \\ 0 & 0 & 1 & 0 \end{bmatrix} \begin{bmatrix} X_C \\ Y_C \\ Z_C \\ 1 \end{bmatrix} ZCxy1=f000f0001000XCYCZC1

1.1.4 图像物理坐标系到像素坐标系

图像物理坐标系与像素坐标系只是坐标原点位置不一致,且单位长度(mm和pixel)不一致,因此,只需要进行伸缩变换及平移变换。
相机标定原理与实战【python-opencv】

{ u = x d x + u 0 v = y d y + v 0 \left\{ \begin{gathered} u=\frac{x}{dx}+u_0 \\ v=\frac{y}{dy} + v_0 \end{gathered} \right. u=dxx+u0v=dyy+v0
表达为矩阵形式如下:
[ u v 1 ] = [ 1 d x 0 u 0 0 1 d y v 0 0 0 1 ] [ x y 1 ] \begin{bmatrix} u \\ v \\ 1 \end{bmatrix} = \begin{bmatrix} \frac{1}{dx} & 0 & u_0 \\ 0 & \frac{1}{dy} & v_0 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} uv1=dx1000dy10u0v01xy1

1.1.5 世界坐标系到像素坐标系

相机标定原理与实战【python-opencv】

根据以上四个坐标系之间的转换关系,在不考虑相机畸变的情况下,物体从世界坐标系投影到像素坐标系的过程如下:
相机标定原理与实战【python-opencv】

1.2 相机畸变

畸变(distortion)是对直线投影(rectilinear projection)的一种偏移。简单来说直线投影是场景内的一条直线投影到图片上也保持为一条直线。畸变简单来说就是一条直线投影到图片上不能保持为一条直线了,这是一种光学畸变(optical aberration),由于镜头不规整和镜头与感光片不平行导致的

畸变一般可以分为:径向畸变、切向畸变

  • 径向畸变来自于透镜形状;
  • 切向畸变来自于整个相机的组装过程;
    相机标定原理与实战【python-opencv】

d r dr dr: 径向畸变
d t dt dt: 切向畸变

1.2.1 径向畸变

径向畸变是由于镜头不规整造成的。图像径向畸变是图像像素点以畸变中心为中心点,沿着径向产生的位置偏差,从而导致图像中所成的像发生形变。径向畸变分为桶形畸变和枕形畸变。

  • 桶形畸变,对应于负径向位移, 多见于变焦镜头的广角端或者鱼眼镜头;
  • 枕形畸变,对应于正径向位移,多见于长焦镜头;
    相机标定原理与实战【python-opencv】

1.2.2 切向畸变

切向畸变是由于透镜本身与相机传感器平面(感光面)不平行而产生的,这种情况多是由于透镜被粘贴到镜头模组上的安装偏差导致。

1.2.3 径向畸变模型

相机主光轴中心的畸变为0,沿着镜头半径方向向边缘移动,畸变越来越严重。畸变的数学模型可以用主点(principle point)周围的泰勒级数展开式的前几项进行描述,通常使用前两项,即k1和k2,对于畸变很大的镜头,如鱼眼镜头,可以增加使用第三项k3来进行描述,相机上某点根据其在径向方向上的分布位置,校正公式为:
相机标定原理与实战【python-opencv】
公式里 ( x 0 , y 0 ) (x_0, y_0) (x0,y0)是畸变点在相机上的原始位置, ( x , y ) (x, y) (x,y)是畸变较正后新的位置。

1.2.4 切向畸变模型

畸变模型可以用两个额外的参数p1和p2来描述:
相机标定原理与实战【python-opencv】

1.2.5 畸变系数

  • k 1 , k 2 , k 3 k_1,k_2,k_3 k1,k2,k3径向畸变系数, p 1 , p 2 p_1,p_2 p1,p2是切向畸变系数。
  • 在Opencv中他们被排列成一个5*1的矩阵,依次包含 [ k 1 、 k 2 、 p 1 、 p 2 、 k 3 ] [k_1、k_2、p_1、p_2、k_3] [k1k2p1p2k3]

2. 相机标定

2.1 标定参数

2.1.1 相机内参

相机内参: f , u 0 , v 0 , 1 / d x , 1 / d y f, u_0, v_0, 1/dx, 1/dy f,u0,v0,1/dx,1/dy

f f f: 焦距
u 0 , v 0 u_0, v_0 u0,v0: 主点在像素坐标系下的偏移
d x , d y dx, dy dx,dy: 表示x方向和y方向的一个像素分别占多少个单位

在OpenCV及Matlab标定工具箱中,标定直接得到的是内参矩阵相机标定原理与实战【python-opencv】。因此在标定过程是得不到物理焦距 f f f的 。

2.1.2 相机外参

指相机相对于某个世界坐标系的方向(旋转和平移)。

相机的外参是6个;
R: 三个轴的旋转参数分别是 ( w 、 δ 、 θ ) (w、δ、θ) wδθ,把每个轴的3x3旋转矩阵进行组合(矩阵之间相乘),得到集合三个轴旋转信息的R,其大小还是3x3;
T: 三个轴的平移参数 ( T x 、 T y 、 T z ) (Tx、Ty、Tz) TxTyTz

opencv中标定得到直接是 ( w 、 δ 、 θ ) (w、δ、θ) wδθ ( T x 、 T y 、 T z ) (Tx、Ty、Tz) TxTyTz,进而通过下面的转换得到外参矩阵。

mR, _ = cv2.Rodrigues([w、δ、θ])  # 通过罗德里格斯公式将旋转向量转换为旋转矩阵
mT = [Tx、Ty、Tz]  # 平移矩阵
exmat = np.concatenate([mR, mT], axis=1)
exmat = np.vstack([exmat, np.array([0, 0, 0, 1])])  # 外参矩阵

2.1.3 畸变系数

k 1 , k 2 , k 3 k_1,k_2,k_3 k1,k2,k3径向畸变系数, p 1 , p 2 p_1,p_2 p1,p2是切向畸变系数。

在Opencv中他们被排列成一个5*1的矩阵,依次包含 [ k 1 、 k 2 、 p 1 、 p 2 、 k 3 ] [k_1、k_2、p_1、p_2、k_3] [k1k2p1p2k3]

2.2 标定流程

相机标定就是为了标定出上述的内参,外参和畸变系数。所以一旦相机结构固定,包括镜头结构固定,对焦距离固定,相机的内参就是固定的。但是一旦相机的位置发生移动,外参就会变化。因此,我们现在的任务就是找出一些点的像素坐标和对应的世界坐标,来求解出内参,外参和畸变系数。

目前最常用的获取这些点对的方式是采用标定板,其标定流程如下:

  • (1)首先从calib.io生成棋盘格pdf。棋盘格大小尽可能与拍摄主体大小接近,以确保最终获取的拍摄主体上某点映射得到的世界坐标尽可能准确。
    相机标定原理与实战【python-opencv】
  • (2) 打印棋盘格,把它贴在一个平面上,作为标定物,世界坐标系原点固定在棋盘格左上角点处,则棋盘格每个角点的XY世界坐标可以通过格子大小得到,Z坐标始终为0。注意,棋盘格的高度与待测物体保持在同一高度。
    相机标定原理与实战【python-opencv】
  • (3) 通过调整标定物或相机的方向,为标定物拍摄一些不同方向的照片。
  • (4) 利用cv2.findChessboardCorners从照片中提取棋盘格角点的像素坐标。
  • (5) 利用cv2.cornerSubPix进行角点坐标亚像素优化。
  • (6) 已知棋盘格角点的世界坐标和对应的像素坐标,利用cv2.calibrateCamera求解相机内参,外参和畸变系数;

2.3 opencv 相机标定代码

https://github.com/dyfcalid/CameraCalibration
上述代码包含了普通相机和鱼眼相机的内参和外参标定代码。

import argparse
import cv2
import numpy as np
import os

parser = argparse.ArgumentParser(description="Camera Intrinsic Calibration")
parser.add_argument('-fw','--FRAME_WIDTH', default=1280, type=int, help='Camera Frame Width')
parser.add_argument('-fh','--FRAME_HEIGHT', default=1024, type=int, help='Camera Frame Height')
parser.add_argument('-bw','--BORAD_WIDTH', default=6, type=int, help='Chess Board Width (corners number)')
parser.add_argument('-bh','--BORAD_HEIGHT', default=7, type=int, help='Chess Board Height (corners number)')
parser.add_argument('-size','--SQUARE_SIZE', default=100, type=int, help='Chess Board Square Size (mm)')
parser.add_argument('-num','--CALIB_NUMBER', default=5, type=int, help='Least Required Calibration Frame Number')
parser.add_argument('-subpix','--SUBPIX_REGION', default=5, type=int, help='Corners Subpix Optimization Region')
parser.add_argument('-fs', '--FOCAL_SCALE', default=1, type=float, help='Camera Undistort Focal Scale')
parser.add_argument('-ss', '--SIZE_SCALE', default=1, type=float, help='Camera Undistort Size Scale')
args = parser.parse_args([])                 # Jupyter Notebook中直接运行时要加[], py文件则去掉


class CalibData:                             # 标定数据类
    def __init__(self):
        self.camera_mat = None               # 相机内参
        self.dist_coeff = None               # 畸变参数
        self.rvecs = None                    # 旋转向量
        self.tvecs = None                    # 平移向量
        self.map1 = None                     # 映射矩阵1
        self.map2 = None                     # 映射矩阵2
        self.reproj_err = None               # 重投影误差
        self.ok = False                      # 数据采集完成标志
        self.camera_mat_dst = None           # 无畸变图相机内参

class Normal:           # 平面相机
    def __init__(self):
        self.data = CalibData()
        self.inited = False
        self.BOARD = np.array([ [(j * args.SQUARE_SIZE, i * args.SQUARE_SIZE, 0.)]
                               for i in range(args.BORAD_HEIGHT) 
                               for j in range(args.BORAD_WIDTH) ],dtype=np.float32)

    # 多图的2D3D点对标定相机参数
    def update(self, corners, frame_size):
        board = [self.BOARD] * len(corners)
        if not self.inited:
            self._update_init(board, corners, frame_size)
            self.inited = True
        else:
            self._update_refine(board, corners, frame_size)
        self._calc_reproj_err(corners)
        self._get_undistort_maps()

    # 首图的2D3D点对进行标定初始化
    def _update_init(self, board, corners, frame_size):
        data = self.data
        data.camera_mat = np.eye(3, 3)
        data.dist_coeff = np.zeros((5, 1))     # 畸变向量的尺寸根据使用模型修改
        data.ok, data.camera_mat, data.dist_coeff, data.rvecs, data.tvecs = cv2.calibrateCamera(
            board, corners, frame_size, data.camera_mat, data.dist_coeff, 
            criteria=(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_COUNT, 30, 1e-6))
        data.ok = data.ok and cv2.checkRange(data.camera_mat) and cv2.checkRange(data.dist_coeff)
        
    # 其余图的2D3D点对进行初始化标定结果的优化
    def _update_refine(self, board, corners, frame_size):
        data = self.data
        data.ok, data.camera_mat, data.dist_coeff, data.rvecs, data.tvecs = cv2.calibrateCamera(
            board, corners, frame_size, data.camera_mat, data.dist_coeff,  
            flags = cv2.CALIB_USE_INTRINSIC_GUESS,
            criteria=(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_COUNT, 10, 1e-6))
        data.ok = data.ok and cv2.checkRange(data.camera_mat) and cv2.checkRange(data.dist_coeff)
        
    # 计算重投影误差
    def _calc_reproj_err(self, corners):
        if not self.inited: return
        data = self.data
        data.reproj_err = []
        for i in range(len(corners)):
            corners_reproj, _ = cv2.projectPoints(self.BOARD, data.rvecs[i], data.tvecs[i], data.camera_mat, data.dist_coeff)
            err = cv2.norm(corners_reproj, corners[i], cv2.NORM_L2) / len(corners_reproj)
            data.reproj_err.append(err)
            
    # 获取新的相机内参矩阵
    def _get_camera_mat_dst(self, camera_mat):
        camera_mat_dst = camera_mat.copy()
        camera_mat_dst[0][0] *= args.FOCAL_SCALE     # FOCAL_SCALE < 1, 则畸变校正后的图会放大,否则图会缩小
        camera_mat_dst[1][1] *= args.FOCAL_SCALE
        camera_mat_dst[0][2] = args.FRAME_WIDTH / 2 * args.SIZE_SCALE
        camera_mat_dst[1][2] = args.FRAME_HEIGHT / 2 * args.SIZE_SCALE
        return camera_mat_dst

    # 获取畸变校正映射
    def _get_undistort_maps(self):
        data = self.data
        data.camera_mat_dst = self._get_camera_mat_dst(data.camera_mat)
        data.map1, data.map2 = cv2.initUndistortRectifyMap(
                                 data.camera_mat, data.dist_coeff, np.eye(3, 3), data.camera_mat_dst,
                                 (int(args.FRAME_WIDTH * args.SIZE_SCALE), int(args.FRAME_HEIGHT * args.SIZE_SCALE)), cv2.CV_16SC2)


class InCalibrator:                  # 内参标定器
    def __init__(self):
        self.camera = Normal()   # 普通相机类
        self.corners = []
    
    # 获取args参数,供外部调用修改参数
    @staticmethod
    def get_args():
        return args
    
    # 获取棋盘格角点坐标
    def get_corners(self, img):
        ok, corners = cv2.findChessboardCorners(img, (args.BORAD_WIDTH, args.BORAD_HEIGHT),
                      flags = cv2.CALIB_CB_ADAPTIVE_THRESH|cv2.CALIB_CB_NORMALIZE_IMAGE|cv2.CALIB_CB_FAST_CHECK)
        if ok: 
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            # 角点坐标亚像素优化
            corners = cv2.cornerSubPix(gray, corners, (args.SUBPIX_REGION, args.SUBPIX_REGION), (-1, -1),
                                       (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.01))
        return ok, corners
    
    # 在图上绘制棋盘格角点
    def draw_corners(self, img):
        ok, corners = self.get_corners(img)
        cv2.drawChessboardCorners(img, (args.BORAD_WIDTH, args.BORAD_HEIGHT), corners, ok)
        return img
    
    # 图像去畸变
    def undistort(self, img):
        data = self.camera.data
        return cv2.remap(img, data.map1, data.map2, cv2.INTER_LINEAR)
    
    # 使用现有角点坐标标定
    def calibrate(self, img):
        if len(self.corners) >= args.CALIB_NUMBER:
            self.camera.update(self.corners, img.shape[1::-1])  # 更新标定数据
        return self.camera.data
    
    def __call__(self, raw_frame):
        ok, corners = self.get_corners(raw_frame)
        result = self.camera.data
        if ok:
            self.corners.append(corners)          # 加入新的角点坐标
            result = self.calibrate(raw_frame)    # 得到标定结果
        return result

2.3.1 相机标定(内参,畸变系数,以及这些图像对应的外参)

from glob import glob
from tqdm import tqdm
import matplotlib.pyplot as plt

calibrator = InCalibrator()
image_list = glob(os.path.join('/Users/nickccnie/Desktop/能力沉淀/10.代码库/CameraCalibration-master/IntrinsicCalibration/data', '*.jpg'))
for img_path in tqdm(image_list):
    image = cv2.imread(img_path)
    result = calibrator(image)
    # img = calibrator.draw_corners(image)
    # plt.figure(figsize=(10, 10))
    # plt.imshow(img)
    # plt.show()

print('相机内参:')
print(calibrator.camera.data.camera_mat)
print('畸变系数:')
print(calibrator.camera.data.dist_coeff)
print('重投影误差:')
print(calibrator.camera.data.reproj_err)
相机内参:
[[429.01201174   0.         567.41969891]
 [  0.         419.76447848 467.46234827]
 [  0.           0.           1.        ]]
畸变系数:
[[-0.28372365]
 [ 0.06597315]
 [ 0.01174763]
 [ 0.00297211]
 [-0.0063206 ]]
重投影误差:
[0.27869147314646936, 0.4886522637692187, 0.17598982232576374, 0.48923469676289055, 0.514708116953903, 0.5678517808250879, 0.4997095543126862, 0.48520367988999785, 0.5357485071819145, 0.429865668423967]

2.3.2 整图畸变校正

img = cv2.imread(image_list[0])
undistort_img = calibrator.undistort(img)
plt.figure(figsize=(20, 20))
plt.subplot(121)
plt.imshow(img)
plt.subplot(122)
plt.imshow(undistort_img)
plt.show()

相机标定原理与实战【python-opencv】

2.3.3 2D点转3D点

(1) 获取相关参数
inmat = calibrator.camera.data.camera_mat
distCoeffs = calibrator.camera.data.dist_coeff
inmat_dst = calibrator.camera.data.camera_mat_dst

rvec = calibrator.camera.data.rvecs[0]
tvec = calibrator.camera.data.tvecs[0]
mR, _ = cv2.Rodrigues(rvec)
mT = tvec
exmat = np.concatenate([mR, mT], axis=1)
exmat = np.vstack([exmat, np.array([0, 0, 0, 1])])

print('第一张图对应的相机外参--旋转:')
print(calibrator.camera.data.rvecs[-1])
print('第一张图对应的相机外参--平移:')
print(calibrator.camera.data.tvecs[-1])
print('第一张图对应的相机外参:')
print(exmat)
第一张图对应的相机外参--旋转:
[[-1.23794909]
 [ 0.0842647 ]
 [ 0.01222559]]
第一张图对应的相机外参--平移:
[[-317.82200853]
 [ 355.12147718]
 [1161.07445248]]
第一张图对应的相机外参:
[[ 9.97197679e-01 -1.29569903e-02  7.36811127e-02 -1.54700207e+02]
 [-2.47796673e-02  8.72085682e-01  4.88725415e-01 -1.87299319e+02]
 [-7.05886538e-02 -4.89181643e-01  8.69320747e-01  1.05852036e+03]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  1.00000000e+00]]
(2) 构造测试用2D点
box_tl = [510, 398]
box_tr = [704, 394]
box_dl = [489, 548]
box_dr = [735, 541]

pts = np.array([box_tl, box_tr, box_dr, box_dl], np.int32)  # 每个点都是(x, y)
pts = pts.reshape((-1,1,2))
draw_img = cv2.polylines(img.copy(),[pts],True,(0,255,0), 2)

plt.figure(figsize=(20, 20))
plt.imshow(draw_img)
plt.show()

相机标定原理与实战【python-opencv】

(3) 测试点畸变校正
undistort_pts = cv2.undistortPoints(pts.astype(np.float32), inmat, distCoeffs, P=inmat_dst)
print(undistort_pts)
draw_undistort_img = cv2.polylines(undistort_img.copy(),[undistort_pts.astype(np.int32)],True,(0,255,0), 2)
plt.figure(figsize=(20, 20))
plt.imshow(draw_undistort_img)
plt.show()
[[[581.46466 441.02515]]
 [[782.36176 434.583  ]]
 [[815.69165 588.1761 ]]
 [[560.1518  593.54535]]]

相机标定原理与实战【python-opencv】

(4) 2D转3D
def camera2world(point2D, rVec, tVec, cameraMat, height):
    """
       Function used to convert given 2D points back to real-world 3D points
       point2D  : An array of 2D points
       rVec     : Rotation vector
       tVec     : Translation vector
       cameraMat: Camera Matrix used in solvePnP
       height   : Height in real-world 3D space
       Return   : output_array: Output array of 3D points

    """
    point3D = []
    point2D = (np.array(point2D, dtype='float32')).reshape(-1, 2)
    numPts = point2D.shape[0]
    point2D_op = np.hstack((point2D, np.ones((numPts, 1))))
    rMat = cv2.Rodrigues(rVec)[0]
    rMat_inv = np.linalg.inv(rMat)
    kMat_inv = np.linalg.inv(cameraMat)
    for point in range(numPts):
        uvPoint = point2D_op[point, :].reshape(3, 1)
        tempMat = np.matmul(rMat_inv, kMat_inv)
        tempMat1 = np.matmul(tempMat, uvPoint)
        tempMat2 = np.matmul(rMat_inv, tVec)
        s = (height + tempMat2[2]) / tempMat1[2]
        p = tempMat1 * s - tempMat2
        point3D.append(p)

    point3D = (np.array(point3D, dtype='float32')).reshape([-1, 1, 3])
    return point3D
pts_world = camera2world(undistort_pts, rvec, tvec, inmat_dst, height=0).reshape(-1, 3)
print(pts_world)

x = pts_world[:, 0]  #构造横坐标数据
y = pts_world[:, 1]  #构造纵坐标数据
plt.plot(x, y, 'bo')
plt.fill(x, y, 'r')
plt.show()  # 可以看出,世界坐标系中的box基本是个矩形
[[ 1.1295952e+01  1.1067908e+01 -1.1368684e-13]
 [ 4.9372278e+02  1.3734733e+01  0.0000000e+00]
 [ 4.9998770e+02  4.0107578e+02  0.0000000e+00]
 [-5.7722647e-02  4.0629202e+02  0.0000000e+00]]

相机标定原理与实战【python-opencv】

2.3.4 3D点转2D点

def world2camera(points):
        if points.size == 0:
            return np.empty((0, 3), dtype=np.int)

        # 组织为齐次坐标
        points = np.concatenate([points, np.ones((points.shape[0], 1))], axis=1)

        # 坐标变换
        points = np.dot(inmat_dst, np.dot(exmat[:-1], points.T)).T
        points = points / points[:, -1].reshape(-1, 1)
        points = points[:, :2]

        return points
point_camera_reprj = world2camera(pts_world)
print(point_camera_reprj)

draw_undistort_img = cv2.polylines(undistort_img.copy(),[point_camera_reprj[:, None, :].astype(np.int32)],True,(255,0,0), 2)
plt.figure(figsize=(20, 20))
plt.imshow(draw_undistort_img)
plt.show()
[[581.46466074 441.02514653]
 [782.36175752 434.58300772]
 [815.69165941 588.17609256]
 [560.15179407 593.54535244]]

相机标定原理与实战【python-opencv】

2.4 标定相关函数

2.4.1 相机标定

cv2.findChessboardCorners ( image,         # 棋盘图像
                            patternSize,   # 棋盘格行和列的【内角点】数量
                            corners,       # 输出数组
                            flags          # 操作标志
                            )
flags:
    CV_CALIB_CB_ADAPTIVE_THRESH            # 使用自适应阈值处理将图像转换为黑白图像
    CV_CALIB_CB_NORMALIZE_IMAGE            # 对图像进行归一化。
    CV_CALIB_CB_FILTER_QUADS               # 过滤在轮廓检索阶段提取的假四边形。
    CALIB_CB_FAST_CHECK                    # 对查找棋盘角的图像进行快速检查
cv2.cornerSubPix (image,                        # 棋盘图像
                  corners,                      # 棋盘角点
                  winSize,                      # 搜索窗口边长的一半
                  zeroZone,                     # 搜索区域死区大小的一半, (-1,-1)代表无
                  criteria                      # 迭代停止标准
                 )
cv2.calibrateCamera (objectPoints,         # 角点在棋盘中的空间坐标向量        
                     imagePoints,          # 角点在图像中的坐标向量
                     image_size,           # 图片大小
                     K,                    # 相机内参矩阵
                     D,                    # 畸变参数向量
                     rvecs,                # 旋转向量
                     tvecs,                # 平移向量
                     flags,                # 操作标志
                     criteria              # 迭代优化算法的停止标准
                    )

flags:
    cv2.CALIB_USE_INTRINSIC_GUESS          # 当相机内参矩阵包含有效的fx,fy,cx,cy初始值时,这些值会进一步进行优化
                                           # 否则,(cx,cy)初始化设置为图像中心(使用imageSize),并且以最小二乘法计算焦距
    cv2.CALIB_FIX_PRINCIPAL_POINT          # 固定光轴点(当设置CALIB_USE_INTRINSIC_GUESS时可以使用)
    cv2.CALIB_FIX_ASPECT_RATIO             # 固定fx/fy的值,函数仅将fy视为自由参数
    cv2.CALIB_ZERO_TANGENT_DIST            # 切向畸变系数(p1,p2) 设置为零并保持为零
    cv2.CALIB_FIX_FOCAL_LENGTH             # 如果设置了CALIB_USE_INTRINSIC_GUESS,则在全局优化过程中不会更改焦距
    cv2.CALIB_FIX_K1 (K1-K6)               # 固定相应的径向畸变系数为0或给定的初始值
    cv2.CALIB_RATIONAL_MODEL               # 理想模型:启用系数k4,k5和k6。此时返回8个或更多的系数
    cv2.CALIB_THIN_PRISM_MODEL             # 薄棱镜模型:启用系数s1,s2,s3和s4。此时返回12个或更多的系数
    cv2.CALIB_FIX_S1_S2_S3_S4              # 固定薄棱镜畸变系数为0或给定的初始值
    cv2.CALIB_TILTED_MODEL                 # 倾斜模型:启用系数tauX和tauY。此时返回14个系数
    cv2.CALIB_FIX_TAUX_TAUY                # 固定倾斜传感器模型的系数为0或给定的初始值
  • drawChessboardCorners:绘制棋盘格检测结果
  • projectPoints: 计算重投影误差

2.4.2 畸变校正

OpenCV 针对不同的使用场景提供了几个不同用法的畸变校正函数。https://docs.opencv.org/3.4.6/da/d54/group__imgproc__transform.html#ga55c716492470bfe86b0ee9bf3a1f0f7e

主要有以下几种:

initUndistortRectifyMap() remap()组合
undistort()
undistortPoints()

(1) initUndistortRectifyMap() undistort()组合

  • 通过映射的方式逐个找出理想点在有畸变原图的位置。initUndistortRectifyMap()用于产生映射表,remap()用于执行映射。
  • 适用场景:当要进行多次畸变校正时,使用initUndistortRectifyMap() remap()组合比较有效率,只需要执行一次initUndistortRectifyMap(),后面畸变校正只需要执行remap()即可。

(2)undistort()

  • 本质是initUndistortRectifyMap() remap()组合,写在了一个函数里,方便调用。
  • 适用场景:当只需要执行一次畸变校正时,用undistort()比用组合形式更方便一些。

(3)undistortPoints()

  • 适用场景:当只需要找出有畸变原图中的少数几个点经过畸变校正后的理想位置时,使用undistortPoints()可达到目的。如对目标检测直接在有畸变图像进行box检测,进而将box左上角和右下角进行畸变校正,避免了对全图进行畸变校正(计算量大一点)后在进行目标检测。
cv2.initUndistortRectifyMap (K,         # 相机内参矩阵
                             D,         # 畸变向量
                             R,         # 旋转矩阵
                             P,         # 新的相机矩阵
                             size,      # 输出图像大小
                             m1type,    # 映射矩阵类型
                             map1,      # 输出映射矩阵1
                             map2       # 输出映射矩阵2
                            )
def remap(src,                 # 源图像数据
          map1,                # 用于插值的X坐标
          map2,                # 用于插值的Y坐标
          interpolation,       # 插值算法
          dst=None,
          borderMode=None,     # 边界模式,有默认值BORDER_CONSTANT,表示目标图像中“离群点(outliers)”的像素值不会被此函数修改。
          borderValue=None     # 当有常数边界时使用的值,其有默认值Scalar( ),即默认值为0。
          )    
cv2.undistort(src,                      # 输入原图
              dst,                      # 输出矫正后的图像
              cameraMatrix,             # 内参矩阵
              distCoeffs,               # 畸变系数
              newCameraMatrix           # 默认情况下,它与 cameraMatrix 相同
              )
cv.undistortPoints(src,             # 待校正像素点坐标,1xN 或 Nx1 2channel
                   cameraMatrix,    # 内参矩阵
                   distCoeffs,      # 畸变系数
                   R,               # R参数是用在双目里的,单目里置为空矩阵;
                   P                # P矩阵值为空时,得到的点坐标是归一化坐标,这时候数值就会明显很小;
                                    # 通常使用时是想得到在同一个相机下的真实像素,所以P设置为内参就可以了
                   )

2.4.3 外参求解

  • cvFindExtrinsicCameraParams2: 已知内参求外参
  • solvePnP

2.4.4 2D-3D映射

  • cvProjectPoints2: 2D点映射到3D点
  • cvProjectPoints:3D点映射到2D点
  • 像素坐标转到世界坐标时相机坐标系中的Zc值求解

`文章来源地址https://www.toymoban.com/news/detail-485087.html

参考

  • 相机标定——张正友棋盘格标定法
  • 相机标定之张正友标定法数学原理详解(含python源码)
  • 相机标定(Camera calibration)原理、步骤
  • 【相机标定】四个坐标系之间的变换关系
  • python利用opencv进行相机标定(完全版)

到了这里,关于相机标定原理与实战【python-opencv】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【相机标定】opencv python 标定相机内参时不计算 k3 畸变参数

    畸变参数 k3 通常用于描述径向畸变的更高阶效应,即在需要高精度的应用中可以用到,一般的应用中 k1, k2 足矣。 常见的应用中, orbslam3 中是否传入 k3 是可选的,而 kalibr 标定中则只需要传入 k1, k2 。但计算 k3 时的 k1, k2 不等于不计算 k3 时的 k1, k2 ,因此需要学会两种场景下

    2024年02月09日
    浏览(41)
  • python opencv实现相机内参标定

    使用python opencv 标定相机内参。 (1)从网络上下载一张棋盘格图片,粘贴到word文档上,设定尺寸大小为合适值,作为标定板。 (2)在不同距离,不同角度下用手机相机拍摄棋盘图片。 (3)调用 opencv findChessboardCorners 和 cornerSubPix 函数提取棋盘的角点。 (4)调用 opencv cal

    2024年02月13日
    浏览(55)
  • Python OpenCV 单目相机标定、坐标转换相关代码(包括鱼眼相机)

      本文不讲原理,只关注代码,有很多博客是讲原理的,但是代码最多到畸变矫正就结束了,实际上就是到 OpenCV 官方示例涉及的部分。   在官方示例中使用黑白棋盘格求解了相机的内外参和畸变系数,并对图像做了畸变矫正,但在实际使用时还缺少很多功能,以下是本

    2024年02月02日
    浏览(36)
  • 【OpenCv】相机标定介绍及python/c++实现

    之前有一个项目需要公司标内参,之前对这方面没有接触过,网上找了很多资料,记录下相机标定的基础知识。文章是个人浅显理解。如有错误还请指正,非常感谢! 参考链接: 坐标系转换:相机参数标定(camera calibration)及标定结果如何使用_Aoulun的博客-CSDN博客 标定ope

    2024年02月15日
    浏览(40)
  • OpenCV-Python相机标定:Camera Calibration

    在使用相机拍照片时,大多数人会考虑拍的好不好看,关注相机中物体坐标的并不多,但是对于地信学科来说,如果能从照片中获取物体的真实位置,对地理信息获取大有帮助,在这里面,十分关键的一步就是相机标定。 相机标定的基本原理也是相对简单的,看官网中的一个

    2024年02月09日
    浏览(59)
  • 使用opencv-python(cv2)库进行相机标定

    2023年09月11日
    浏览(56)
  • 为什么需要对相机标定?

    以下内容来自系统教程如何搞定单目/鱼眼/双目/阵列 相机标定? 点击领取相机标定资料和代码 为什么需要对相机标定? 我们所处的世界是三维的,而相机拍摄的照片却是二维的,丢失了其中距离/深度的信息。从数学上可以简单理解为,相机本身类似一个映射函数,其将输

    2024年02月06日
    浏览(55)
  • python-opencv之形态学操作(腐蚀和膨胀)原理详解

    Removing noise. Isolation of individual elements and joining disparate elements in an image. Finding of intensity bumps or holes in an image. 最基本的形态操作是侵蚀和扩张。让我们更详细地了解这些操作。 原理 它会侵蚀前景物体的边界,并从图像中移除小规模的细节,但同时会减少感兴趣区域的大小。

    2024年02月05日
    浏览(54)
  • python利用opencv进行相机标定获取参数,并根据畸变参数修正图像附有全部代码(流畅无痛版)

    今天的低价单孔摄像机(照相机)会给图像带来很多畸变。畸变主要有两 种:径向畸变和切想畸变。如下图所示,用红色直线将棋盘的两个边标注出来, 但是你会发现棋盘的边界并不和红线重合。所有我们认为应该是直线的也都凸 出来了。 在 3D 相关应用中,必须要先校正这些畸变

    2024年02月06日
    浏览(50)
  • 相机标定 - (02) - 相机标定步骤与原理

    目录 2 相机标定步骤 2.1 张正有标定操作步骤 2.2 张正有标定原理 参考文章: 三步骤详解张正友标定法_谜之_摄影爱好者的博客-CSDN博客         1998年,张正友提出了基于二维平面靶标的标定方法,使用相机在不同角度下拍摄多幅平面靶标的图像,比如棋盘格的图像,然

    2024年02月11日
    浏览(70)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包