双目相机标定结果内参及外参验证

双目相机标定结果(内参,外参)验证–在OpenGL中重投影目标模型到图像平面,与左右视角图像中的模型同时重合

相机标定是计算机视觉任务中比较基础且重要的前提步骤。单目相机的标定可以直接用来对图像进行反扭曲处理。多目相机标定稍微复杂一些,笔者这里在做多目姿态估计的任务,因此接触到了多目相机标定。

我这里用到的方法也很简单,用的是比较经典的棋盘格标定法。两个相机设置一定的拍摄角度,同时拍摄若干组标定板的图片,保证相机至少有几张是同时看到标定板的(方便之后的标定验证)。标定工具用的是GML C++ Camera Calibration Toolbox,也可以用Camera Calibration Toolbox for Matlab。两个工具标定后都会给出一个反投影误差,用来评估标定的准确性。

如果标定结果足够精确,那么在一个视角下使目标模型与图片模型重合,其他视角下也会重合。基于这个前提,做下面的任务。项目地址
1 拍摄图片
2 单独标定左右相机图片
3 验证标定结果
1 拍摄图片 (图片中的手部模型为3D打印,OpenGL中会绘制相同的模型)

简易做了一个标定版,A4纸打印,8*5 size的pattern,格子边长为27.5mm。分别拍摄两个视角下的图片若干张。我这里保证left 和right 视角下各有两张是同时拍到标定板的。
在这里插入图片描述

2 单独标定左右相机图片

笔者用GML标定工具,并且分别导出了内参矩阵和外参矩阵。具体的标定结果可在这里下载。

3 验证标定结果

接下来才是这篇博客的重点。这里我贴出几个自己参考的blog。1:3D Projection 2:Augmented Reality on libQGLViewer and OpenCV-OpenGL tips [w/code] 3:Camera Models and Augmented Reality

关于标定的原理,大家可以查询相关知识,这里不再赘述。我们的主要目的就是验证内参和外参。如下公式所示。

OpenCV中相机坐标系为右手坐标系,转换到OpenGL相机坐标系要进行z轴的反转。
相机内参矩阵对应OpenGL的投影矩阵,外参矩阵对应OpenGL的ModelView矩阵。费话不多说,直接来看代码,读取GML标定结果,并且构造Projection 和 View 矩阵。

以下为初始化变量的声明,其中用到了自己写的简单的OpenGL库,可参考项目地址查看

from OpenGL.GL import *
from OpenGL.GLUT import *
import numpy as np
from GEngine.shader import ShaderProgram
from GEngine.model import Model, ModelFromExport, generate_grid_mesh
import glm
from GEngine.camera3D import Camera3D
from GEngine.input_process import InputProcess, keys
from GEngine.window import GWindow
import cv2

camera = Camera3D(glm.vec3(0.0, 5.0, 30.0))
window = GWindow(b"demo", SCR_WIDTH, SCR_HEIGHT, InputProcess(camera, SCR_WIDTH, SCR_HEIGHT), keep_mouse_stay=False)

image_size = (1920, 1080)
light_color = (1.0, 1.0, 1.0)
hand_color = (0.9, 0.9, 0.9)
light_position = (-1000, -700, 1000)

model_position = [
    glm.vec3(1.0, 1.0, 1.0),
    [[0, glm.vec3(1.0, 0.0, 0.0)], [0, glm.vec3(0.0, 1.0, 0.0)], [0, glm.vec3(0.0, 0.0, 1.0)]],
    glm.vec3(500, 500.0, 0.0)
]
image_path = "/media/shuai/SHUAI_AHUT/calibration_same_camera/"

left_camera_intrinsic = np.loadtxt(image_path + "left/Intrinsic.txt")
right_camera_intrinsic = np.loadtxt(image_path + "/right/Intrinsic.txt")

left_camera_extrinsic = np.loadtxt(image_path + "left/ExtrinsicCameraPars.txt")
right_camera_extrinsic = np.loadtxt(image_path + "right/ExtrinsicCameraPars.txt")
# 构造projection矩阵
def build_projection_matrix(camera_intrinsic_matrix, width, height):
    d_near = 0.1  # Near clipping distance
    d_far = 1000.0  # Far clipping distance

    # Camera parameters
    fx = camera_intrinsic_matrix[0, 0]  # Focal length in x axis
    fy = camera_intrinsic_matrix[1, 1]  # Focal length in y axis (usually the same?)
    cx = camera_intrinsic_matrix[0, 2]  # Camera primary point x
    cy = camera_intrinsic_matrix[1, 2]  # Camera primary point y

    projection_matrix = np.array([[fx / cx, 0.0, 0.0, 0.0],
                                  [0.0, fy / cy, 0.0, 0.0],
                                  [0, 0, -(d_far + d_near) / (d_far - d_near), -1.0],
                                  [0.0, 0.0, -2.0 * d_far * d_near / (d_far - d_near), 0.0]], dtype=np.float32)


    return projection_matrix

# 构造view矩阵
def build_model_view_matrix(camera_extrinsic_matrix):
    r, t = camera_extrinsic_matrix[3:], camera_extrinsic_matrix[:3]
    R = cv2.Rodrigues(r)[0]
	
    inverse = np.array([[1, 0, 0],
                        [0, -1, 0],
                        [0, 0, -1]])
	# 奇异值分解,消除标定误差导致的旋转矩阵不合格的情况。
    u, _, v = np.linalg.svd(R)
    R = u @ v
	
	# OpenGL相机坐标系下z轴负向为正方向
    rotation = inverse @ R
    translation = inverse @ t
    
    model_view_matrix = np.identity(4, dtype=np.float32)
    model_view_matrix[:3, :3] = rotation
    model_view_matrix[:3, 3] = translation
	
	# OpengGL中矩阵为列优先存储
    model_view_matrix = model_view_matrix.T
    return model_view_matrix

接下来是绘制背景图像,这个就很简单了。

def render_background_image(bg_model):
    glDisable(GL_DEPTH_TEST)
    bg_model.draw(bg_shader_program, draw_type=GL_TRIANGLES)
    glEnable(GL_DEPTH_TEST)

整个OpenGL视图分成两块,分别对应left和right。

def render():
    glClearColor(1.0, 1.0, 1.0, 0.0)
    glClearDepth(1.0)
    glPointSize(5)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    glViewport(0, 0, int(SCR_WIDTH / 2), SCR_HEIGHT)
    render_background_image(bg_model_left)
    render_side_view(left_camera_intrinsic, left_camera_extrinsic, SCR_WIDTH / 2, SCR_HEIGHT, stereo_left_index)

    glViewport(int(SCR_WIDTH / 2), 0, int(SCR_WIDTH / 2), SCR_HEIGHT)
    render_background_image(bg_model_right)
    render_side_view(right_camera_intrinsic, right_camera_extrinsic, SCR_WIDTH / 2, SCR_HEIGHT, stereo_right_index)

最后就是效果图(不小心上镜了???)
在这里插入图片描述

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值