0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

张正友教授相机标定法原理与实现

新机器视觉 来源:金凯博自动化 作者:金凯博自动化 2020-12-31 10:06 次阅读

张正友相机标定法是张正友教授1998年提出的单平面棋盘格的相机标定方法。传统标定法的标定板是需要三维的,需要非常精确,这很难制作,而张正友教授提出的方法介于传统标定法和自标定法之间,但克服了传统标定法需要的高精度标定物的缺点,而仅需使用一个打印出来的棋盘格就可以。同时也相对于自标定而言,提高了精度,便于操作。因此张氏标定法被广泛应用于计算机视觉方面。

张正友标定法的标定板

今天,我们就来讲解一下张氏标定法的原理和实现,学会之后,我们就可以自己去制作一个棋盘标定板,然后拍照,标定自己手机相机的参数啦!

一、相机标定介绍

二、算法原理

1.整体流程

2.模型假设

3.模型求解

(1)内外参数求解

(2)畸变系数求解

(3)精度优化

三、算法实现

1.main.py

2.homography.py

4.extrinsics.py

5.distortion.py

6.refine_all.py

7.结果

一、相机标定介绍

相机标定指建立相机图像像素位置与场景点位置之间的关系,根据相机成像模型,由特征点在图像中坐标与世界坐标的对应关系,求解相机模型的参数。相机需要标定的模型参数包括内部参数和外部参数。

针孔相机成像原理其实就是利用投影将真实的三维世界坐标转换到二维的相机坐标上去,其模型示意图如下图所示:

cfa988f6-4a98-11eb-8b86-12bb97331649.png

从图中我们可以看出,在世界坐标中的一条直线上的点在相机上只呈现出了一个点,其中发生了非常大的变化,同时也损失和很多重要的信息,这正是我们3D重建、目标检测与识别领域的重点和难点。实际中,镜头并非理想的透视成像,带有不同程度的畸变。理论上镜头的畸变包括径向畸变和切向畸变,切向畸变影响较小,通常只考虑径向畸变。

径向畸变:径向畸变主要由镜头径向曲率产生(光线在远离透镜中心的地方比靠近中心的地方更加弯曲)。导致真实成像点向内或向外偏离理想成像点。其中畸变像点相对于理想像点沿径向向外偏移,远离中心的,称为枕形畸变;径向畸点相对于理想点沿径向向中心靠拢,称为桶状畸变。

用数学公式来表示:

d20a7290-4a98-11eb-8b86-12bb97331649.png

其中,X为相机中的坐标;X为真实世界坐标;K为内参矩阵;RT为外参矩阵 K为内参矩阵,是相机内部参数组成的一个3*3的矩阵,其中,代表焦距;S为畸变参数为d25315c2-4a98-11eb-8b86-12bb97331649.jpg中心点坐标,a为纵横比例参数,我们可以默认设为1,所以   RT为外参矩阵,R是描述照相机方向的旋转矩阵,T是描述照相机中心位置的三维平移向量。

二、算法原理

1.整体流程

d28117d8-4a98-11eb-8b86-12bb97331649.png

2.模型假设

d30fc032-4a98-11eb-8b86-12bb97331649.png

d35df748-4a98-11eb-8b86-12bb97331649.png

d3a5b600-4a98-11eb-8b86-12bb97331649.png

d3dc5598-4a98-11eb-8b86-12bb97331649.png

3.模型求解

(1)内外参数求解

我们令d4ca5f18-4a98-11eb-8b86-12bb97331649.png,则d4f53f80-4a98-11eb-8b86-12bb97331649.png

其中,H为一个3*3的矩阵,并且有一个元素作为齐次坐标。因此,H有8个自由度。

现在有8个自由度需要求解,所以需要四个对应点。也就是四个点就可以求出图像平面到世界平面的单应性矩阵H。

我想,张氏标定法选用的棋盘格作为标定板的原因除了角点方便检测的另外一个原因可能就是这个吧。

通过4个点,我们就可以可以获得单应性矩阵H。但是,H是内参阵和外参阵的合体。我们想要最终分别获得内参和外参。所以需要想个办法,先把内参求出来。然后外参也就随之解出了。观察一下这个式子:

d4f53f80-4a98-11eb-8b86-12bb97331649.png

我们可以知道以下约束条件:

①,R1R2 正交,也就是说 R1 R2=0。其实这个不难理解,因为 R1 R2 是分别绕x轴和y轴得到的,而x轴和y轴均垂直z轴。

②旋转向量的模为1,也就是说R1=R2=1,这是因为旋转不改变尺度。

根据这两个约束条件,经过数学变换,我们可以得到:

d54265e4-4a98-11eb-8b86-12bb97331649.png

观察上面的两个式子,我们可以看出,由于H1和H2是通过单应性求解出来的,所以我们要求解的参数就变成A矩阵中未知的5个参数。我们可以通过三个单应性矩阵来求解这5个参数,利用三个单应性矩阵在两个约束下可以生成6个方程。其中,三个单应性矩阵可以通过三张对同一标定板不同角度和高度的照片获得。

用数学公式来表达如下:

d5c0531e-4a98-11eb-8b86-12bb97331649.png

我们很容易发现B是一个对称阵,所以B的有效元素就剩下6个,即

d5f67a84-4a98-11eb-8b86-12bb97331649.png

进一步化简:

d647f828-4a98-11eb-8b86-12bb97331649.png

通过计算,我们可以得到

d69d2f1e-4a98-11eb-8b86-12bb97331649.png

利用上面提到的两个约束条件,我们可以得到下面的方程组:

d6fca32c-4a98-11eb-8b86-12bb97331649.png

这个方程组的本质和前面那两个用h和A组成的约束条件方程组是一样的。

通过至少含一个棋盘格的三幅图像,应用上述公式我们就可以估算出B了。得到B后,我们通过cholesky分解,就可以得到摄相机机的内参阵A的六个自由度,即:

d7571a32-4a98-11eb-8b86-12bb97331649.png

再根据d4f53f80-4a98-11eb-8b86-12bb97331649.png化简可得外部参数,即:

d7ba260e-4a98-11eb-8b86-12bb97331649.png

(2)畸变系数求解

在文章的开始,我们就讲到真实的镜头并非理想的透视成像,而是带有不同程度的畸变。理论上镜头的畸变包括径向畸变和切向畸变,切向畸变影响较小,通常只考虑径向畸变,而且在径向畸变的求解中,仅考虑了起主导的二元泰勒级数展开的前两个系数。

具体推导,参考https://blog.csdn.net/onthewaysuccess/article/details/40736177

(3)精度优化

在张正友标定法中,使用了两次极大似然估计策略,第一次是在不考虑畸变的情况下求解内参和外参,第二次是求解实际的畸变系数。

极大似然参数估计,参考https://blog.csdn.net/onthewaysuccess/article/details/40717213

三、算法实现

1.main.py

#!usr/bin/env/ python

# _*_ coding:utf-8 _*_

import cv2 as cv

import numpy as np

import os

from step.homography import get_homography

from step.intrinsics import get_intrinsics_param

from step.extrinsics import get_extrinsics_param

from step.distortion import get_distortion

from step.refine_all import refinall_all_param

def calibrate():

#求单应矩阵

H = get_homography(pic_points, real_points_x_y)

#求内参

intrinsics_param = get_intrinsics_param(H)

#求对应每幅图外参

extrinsics_param = get_extrinsics_param(H, intrinsics_param)

#畸变矫正

k = get_distortion(intrinsics_param, extrinsics_param, pic_points, real_points_x_y)

#微调所有参数

[new_intrinsics_param, new_k, new_extrinsics_param] = refinall_all_param(intrinsics_param,

k, extrinsics_param, real_points, pic_points)

print("intrinsics_parm: ", new_intrinsics_param)

print("distortionk: ", new_k)

print("extrinsics_parm: ", new_extrinsics_param)

if __name__ == "__main__":

file_dir = r'..pic'

# 标定所用图像

pic_name = os.listdir(file_dir)

# 由于棋盘为二维平面,设定世界坐标系在棋盘上,一个单位代表一个棋盘宽度,产生世界坐标系三维坐标

cross_corners = [9, 6] #棋盘方块交界点排列

real_coor = np.zeros((cross_corners[0] * cross_corners[1], 3), np.float32)

real_coor[:, :2] = np.mgrid[0:9, 0:6].T.reshape(-1, 2)

real_points = []

real_points_x_y = []

pic_points = []

for pic in pic_name:

pic_path = os.path.join(file_dir, pic)

pic_data = cv.imread(pic_path)

# 寻找到棋盘角点

succ, pic_coor = cv.findChessboardCorners(pic_data, (cross_corners[0], cross_corners[1]), None)

if succ:

# 添加每幅图的对应3D-2D坐标

pic_coor = pic_coor.reshape(-1, 2)

pic_points.append(pic_coor)

real_points.append(real_coor)

real_points_x_y.append(real_coor[:, :2])

calibrate()

2.homography.py

这是用于求解单应性矩阵的文件

#!usr/bin/env/ python

# _*_ coding:utf-8 _*_

import numpy as np

from scipy import optimize as opt

#求输入数据的归一化矩阵

def normalizing_input_data(coor_data):

x_avg = np.mean(coor_data[:, 0])

y_avg = np.mean(coor_data[:, 1])

sx = np.sqrt(2) / np.std(coor_data[:, 0])

sy = np.sqrt(2) / np.std(coor_data[:, 1])

norm_matrix = np.matrix([[sx, 0, -sx * x_avg],

[0, sy, -sy * y_avg],

[0, 0, 1]])

return norm_matrix

#求取初始估计的单应矩阵

def get_initial_H(pic_coor, real_coor):

# 获得归一化矩阵

pic_norm_mat = normalizing_input_data(pic_coor)

real_norm_mat = normalizing_input_data(real_coor)

M = []

for i in range(len(pic_coor)):

#转换为齐次坐标

single_pic_coor = np.array([pic_coor[i][0], pic_coor[i][1], 1])

single_real_coor = np.array([real_coor[i][0], real_coor[i][1], 1])

#坐标归一化

pic_norm = np.dot(pic_norm_mat, single_pic_coor)

real_norm = np.dot(real_norm_mat, single_real_coor)

#构造M矩阵

M.append(np.array([-real_norm.item(0), -real_norm.item(1), -1,

0, 0, 0,

pic_norm.item(0) * real_norm.item(0), pic_norm.item(0) * real_norm.item(1), pic_norm.item(0)]))

M.append(np.array([0, 0, 0,

-real_norm.item(0), -real_norm.item(1), -1,

pic_norm.item(1) * real_norm.item(0), pic_norm.item(1) * real_norm.item(1), pic_norm.item(1)]))

#利用SVD求解M * h = 0中h的解

U, S, VT = np.linalg.svd((np.array(M, dtype='float')).reshape((-1, 9)))

# 最小的奇异值对应的奇异向量,S求出来按大小排列的,最后的最小

H = VT[-1].reshape((3, 3))

H = np.dot(np.dot(np.linalg.inv(pic_norm_mat), H), real_norm_mat)

H /= H[-1, -1]

return H

#返回估计坐标与真实坐标偏差

def value(H, pic_coor, real_coor):

Y = np.array([])

for i in range(len(real_coor)):

single_real_coor = np.array([real_coor[i, 0], real_coor[i, 1], 1])

U = np.dot(H.reshape(3, 3), single_real_coor)

U /= U[-1]

Y = np.append(Y, U[:2])

Y_NEW = (pic_coor.reshape(-1) - Y)

return Y_NEW

#返回对应jacobian矩阵

def jacobian(H, pic_coor, real_coor):

J = []

for i in range(len(real_coor)):

sx = H[0]*real_coor[i][0] + H[1]*real_coor[i][1] +H[2]

sy = H[3]*real_coor[i][0] + H[4]*real_coor[i][1] +H[5]

w = H[6]*real_coor[i][0] + H[7]*real_coor[i][1] +H[8]

w2 = w * w

J.append(np.array([real_coor[i][0]/w, real_coor[i][1]/w, 1/w,

0, 0, 0,

-sx*real_coor[i][0]/w2, -sx*real_coor[i][1]/w2, -sx/w2]))

J.append(np.array([0, 0, 0,

real_coor[i][0]/w, real_coor[i][1]/w, 1/w,

-sy*real_coor[i][0]/w2, -sy*real_coor[i][1]/w2, -sy/w2]))

return np.array(J)

#利用Levenberg Marquart算法微调H

def refine_H(pic_coor, real_coor, initial_H):

initial_H = np.array(initial_H)

final_H = opt.leastsq(value,

initial_H,

Dfun=jacobian,

args=(pic_coor, real_coor))[0]

final_H /= np.array(final_H[-1])

return final_H

#返回微调后的H

def get_homography(pic_coor, real_coor):

refined_homographies =[]

error = []

for i in range(len(pic_coor)):

initial_H = get_initial_H(pic_coor[i], real_coor[i])

final_H = refine_H(pic_coor[i], real_coor[i], initial_H)

refined_homographies.append(final_H)

return np.array(refined_homographies)

3.intrinsics.py

这是用于求解内参矩阵的文件

#!usr/bin/env/ python

# _*_ coding:utf-8 _*_

import numpy as np

#返回pq位置对应的v向量

def create_v(p, q, H):

H = H.reshape(3, 3)

return np.array([

H[0, p] * H[0, q],

H[0, p] * H[1, q] + H[1, p] * H[0, q],

H[1, p] * H[1, q],

H[2, p] * H[0, q] + H[0, p] * H[2, q],

H[2, p] * H[1, q] + H[1, p] * H[2, q],

H[2, p] * H[2, q]

])

#返回相机内参矩阵A

def get_intrinsics_param(H):

#构建V矩阵

V = np.array([])

for i in range(len(H)):

V = np.append(V, np.array([create_v(0, 1, H[i]), create_v(0, 0 , H[i])- create_v(1, 1 , H[i])]))

#求解V*b = 0中的b

U, S, VT = np.linalg.svd((np.array(V, dtype='float')).reshape((-1, 6)))

#最小的奇异值对应的奇异向量,S求出来按大小排列的,最后的最小

b = VT[-1]

#求取相机内参

w = b[0] * b[2] * b[5] - b[1] * b[1] * b[5] - b[0] * b[4] * b[4] + 2 * b[1] * b[3] * b[4] - b[2] * b[3] * b[3]

d = b[0] * b[2] - b[1] * b[1]

alpha = np.sqrt(w / (d * b[0]))

beta = np.sqrt(w / d**2 * b[0])

gamma = np.sqrt(w / (d**2 * b[0])) * b[1]

uc = (b[1] * b[4] - b[2] * b[3]) / d

vc = (b[1] * b[3] - b[0] * b[4]) / d

return np.array([

[alpha, gamma, uc],

[0, beta, vc],

[0, 0, 1]

])

4.extrinsics.py

这是用于求解外参矩阵的文件

#!usr/bin/env/ python

# _*_ coding:utf-8 _*_

import numpy as np

#返回每一幅图的外参矩阵[R|t]

def get_extrinsics_param(H, intrinsics_param):

extrinsics_param = []

inv_intrinsics_param = np.linalg.inv(intrinsics_param)

for i in range(len(H)):

h0 = (H[i].reshape(3, 3))[:, 0]

h1 = (H[i].reshape(3, 3))[:, 1]

h2 = (H[i].reshape(3, 3))[:, 2]

scale_factor = 1 / np.linalg.norm(np.dot(inv_intrinsics_param, h0))

r0 = scale_factor * np.dot(inv_intrinsics_param, h0)

r1 = scale_factor * np.dot(inv_intrinsics_param, h1)

t = scale_factor * np.dot(inv_intrinsics_param, h2)

r2 = np.cross(r0, r1)

R = np.array([r0, r1, r2, t]).transpose()

extrinsics_param.append(R)

return extrinsics_param

5.distortion.py

这是用于求解畸变矫正系数的文件

#!usr/bin/env/ python

# _*_ coding:utf-8 _*_

import numpy as np

#返回畸变矫正系数k0,k1

def get_distortion(intrinsic_param, extrinsic_param, pic_coor, real_coor):

D = []

d = []

for i in range(len(pic_coor)):

for j in range(len(pic_coor[i])):

#转换为齐次坐标

single_coor = np.array([(real_coor[i])[j, 0], (real_coor[i])[j, 1], 0, 1])

#利用现有内参及外参求出估计图像坐标

u = np.dot(np.dot(intrinsic_param, extrinsic_param[i]), single_coor)

[u_estim, v_estim] = [u[0]/u[2], u[1]/u[2]]

coor_norm = np.dot(extrinsic_param[i], single_coor)

coor_norm /= coor_norm[-1]

#r = np.linalg.norm((real_coor[i])[j])

r = np.linalg.norm(coor_norm)

D.append(np.array([(u_estim - intrinsic_param[0, 2]) * r ** 2, (u_estim - intrinsic_param[0, 2]) * r ** 4]))

D.append(np.array([(v_estim - intrinsic_param[1, 2]) * r ** 2, (v_estim - intrinsic_param[1, 2]) * r ** 4]))

#求出估计坐标与真实坐标的残差

d.append(pic_coor[i][j, 0] - u_estim)

d.append(pic_coor[i][j, 1] - v_estim)

'''

D.append(np.array([(pic_coor[i][j, 0] - intrinsic_param[0, 2]) * r ** 2, (pic_coor[i][j, 0] - intrinsic_param[0, 2]) * r ** 4]))

D.append(np.array([(pic_coor[i][j, 1] - intrinsic_param[1, 2]) * r ** 2, (pic_coor[i][j, 1] - intrinsic_param[1, 2]) * r ** 4]))

#求出估计坐标与真实坐标的残差

d.append(u_estim - pic_coor[i][j, 0])

d.append(v_estim - pic_coor[i][j, 1])

'''

D = np.array(D)

temp = np.dot(np.linalg.inv(np.dot(D.T, D)), D.T)

k = np.dot(temp, d)

'''

#也可利用SVD求解D * k = d中的k

U, S, Vh=np.linalg.svd(D, full_matrices=False)

temp_S = np.array([[S[0], 0],

[0, S[1]]])

temp_res = np.dot(Vh.transpose(), np.linalg.inv(temp_S))

temp_res_res = np.dot(temp_res, U.transpose())

k = np.dot(temp_res_res, d)

'''

return k

6.refine_all.py

这是用于微调参数的文件

#!usr/bin/env/ python

# _*_ coding:utf-8 _*_

import numpy as np

import math

from scipy import optimize as opt

#微调所有参数

def refinall_all_param(A, k, W, real_coor, pic_coor):

#整合参数

P_init = compose_paramter_vector(A, k, W)

#复制一份真实坐标

X_double = np.zeros((2 * len(real_coor) * len(real_coor[0]), 3))

Y = np.zeros((2 * len(real_coor) * len(real_coor[0])))

M = len(real_coor)

N = len(real_coor[0])

for i in range(M):

for j in range(N):

X_double[(i * N + j) * 2] = (real_coor[i])[j]

X_double[(i * N + j) * 2 + 1] = (real_coor[i])[j]

Y[(i * N + j) * 2] = (pic_coor[i])[j, 0]

Y[(i * N + j) * 2 + 1] = (pic_coor[i])[j, 1]

#微调所有参数

P = opt.leastsq(value,

P_init,

args=(W, real_coor, pic_coor),

Dfun=jacobian)[0]

#raial_error表示利用标定后的参数计算得到的图像坐标与真实图像坐标点的平均像素距离

error = value(P, W, real_coor, pic_coor)

raial_error = [np.sqrt(error[2 * i]**2 + error[2 * i + 1]**2) for i in range(len(error) // 2)]

print("total max error: ", np.max(raial_error))

#返回拆解后参数,分别为内参矩阵,畸变矫正系数,每幅图对应外参矩阵

return decompose_paramter_vector(P)

#把所有参数整合到一个数组内

def compose_paramter_vector(A, k, W):

alpha = np.array([A[0, 0], A[1, 1], A[0, 1], A[0, 2], A[1, 2], k[0], k[1]])

P = alpha

for i in range(len(W)):

R, t = (W[i])[:, :3], (W[i])[:, 3]

#旋转矩阵转换为一维向量形式

zrou = to_rodrigues_vector(R)

w = np.append(zrou, t)

P = np.append(P, w)

return P

#分解参数集合,得到对应的内参,外参,畸变矫正系数

def decompose_paramter_vector(P):

[alpha, beta, gamma, uc, vc, k0, k1] = P[0:7]

A = np.array([[alpha, gamma, uc],

[0, beta, vc],

[0, 0, 1]])

k = np.array([k0, k1])

W = []

M = (len(P) - 7) // 6

for i in range(M):

m = 7 + 6 * i

zrou = P[m:m+3]

t = (P[m+3:m+6]).reshape(3, -1)

#将旋转矩阵一维向量形式还原为矩阵形式

R = to_rotation_matrix(zrou)

#依次拼接每幅图的外参

w = np.concatenate((R, t), axis=1)

W.append(w)

W = np.array(W)

return A, k, W

#返回从真实世界坐标映射的图像坐标

def get_single_project_coor(A, W, k, coor):

single_coor = np.array([coor[0], coor[1], coor[2], 1])

#'''

coor_norm = np.dot(W, single_coor)

coor_norm /= coor_norm[-1]

#r = np.linalg.norm(coor)

r = np.linalg.norm(coor_norm)

uv = np.dot(np.dot(A, W), single_coor)

uv /= uv[-1]

#畸变

u0 = uv[0]

v0 = uv[1]

uc = A[0, 2]

vc = A[1, 2]

#u = (uc * r**2 * k[0] + uc * r**4 * k[1] - u0) / (r**2 * k[0] + r**4 * k[1] - 1)

#v = (vc * r**2 * k[0] + vc * r**4 * k[1] - v0) / (r**2 * k[0] + r**4 * k[1] - 1)

u = u0 + (u0 - uc) * r**2 * k[0] + (u0 - uc) * r**4 * k[1]

v = v0 + (v0 - vc) * r**2 * k[0] + (v0 - vc) * r**4 * k[1]

'''

uv = np.dot(W, single_coor)

uv /= uv[-1]

# 透镜矫正

x0 = uv[0]

y0 = uv[1]

r = np.linalg.norm(np.array([x0, y0]))

k0 = 0

k1 = 0

x = x0 * (1 + r ** 2 * k0 + r ** 4 * k1)

y = y0 * (1 + r ** 2 * k0 + r ** 4 * k1)

#u = A[0, 0] * x + A[0, 2]

#v = A[1, 1] * y + A[1, 2]

[u, v, _] = np.dot(A, np.array([x, y, 1]))

'''

return np.array([u, v])

#返回所有点的真实世界坐标映射到的图像坐标与真实图像坐标的残差

def value(P, org_W, X, Y_real):

M = (len(P) - 7) // 6

N = len(X[0])

A = np.array([

[P[0], P[2], P[3]],

[0, P[1], P[4]],

[0, 0, 1]

])

Y = np.array([])

for i in range(M):

m = 7 + 6 * i

#取出当前图像对应的外参

w = P[m:m + 6]

# 不用旋转矩阵的变换是因为会有精度损失

'''

R = to_rotation_matrix(w[:3])

t = w[3:].reshape(3, 1)

W = np.concatenate((R, t), axis=1)

'''

W = org_W[i]

#计算每幅图的坐标残差

for j in range(N):

Y = np.append(Y, get_single_project_coor(A, W, np.array([P[5], P[6]]), (X[i])[j]))

error_Y = np.array(Y_real).reshape(-1) - Y

return error_Y

#计算对应jacobian矩阵

def jacobian(P, WW, X, Y_real):

M = (len(P) - 7) // 6

N = len(X[0])

K = len(P)

A = np.array([

[P[0], P[2], P[3]],

[0, P[1], P[4]],

[0, 0, 1]

])

res = np.array([])

for i in range(M):

m = 7 + 6 * i

w = P[m:m + 6]

R = to_rotation_matrix(w[:3])

t = w[3:].reshape(3, 1)

W = np.concatenate((R, t), axis=1)

for j in range(N):

res = np.append(res, get_single_project_coor(A, W, np.array([P[5], P[6]]), (X[i])[j]))

#求得x, y方向对P[k]的偏导

J = np.zeros((K, 2 * M * N))

for k in range(K):

J[k] = np.gradient(res, P[k])

return J.T

#将旋转矩阵分解为一个向量并返回,Rodrigues旋转向量与矩阵的变换,最后计算坐标时并未用到,因为会有精度损失

def to_rodrigues_vector(R):

p = 0.5 * np.array([[R[2, 1] - R[1, 2]],

[R[0, 2] - R[2, 0]],

[R[1, 0] - R[0, 1]]])

c = 0.5 * (np.trace(R) - 1)

if np.linalg.norm(p) == 0:

if c == 1:

zrou = np.array([0, 0, 0])

elif c == -1:

R_plus = R + np.eye(3, dtype='float')

norm_array = np.array([np.linalg.norm(R_plus[:, 0]),

np.linalg.norm(R_plus[:, 1]),

np.linalg.norm(R_plus[:, 2])])

v = R_plus[:, np.where(norm_array == max(norm_array))]

u = v / np.linalg.norm(v)

if u[0] < 0 or (u[0] == 0 and u[1] < 0) or (u[0] == u[1] and u[0] == 0 and u[2] < 0):

u = -u

zrou = math.pi * u

else:

zrou = []

else:

u = p / np.linalg.norm(p)

theata = math.atan2(np.linalg.norm(p), c)

zrou = theata * u

return zrou

#把旋转矩阵的一维向量形式还原为旋转矩阵并返回

def to_rotation_matrix(zrou):

theta = np.linalg.norm(zrou)

zrou_prime = zrou / theta

W = np.array([[0, -zrou_prime[2], zrou_prime[1]],

[zrou_prime[2], 0, -zrou_prime[0]],

[-zrou_prime[1], zrou_prime[0], 0]])

R = np.eye(3, dtype='float') + W * math.sin(theta) + np.dot(W, W) * (1 - math.cos(theta))

return R

7.结果

拍摄的不同角度,不同高度的图像

运行结果:

博主的手机是华为p9,后置摄像头是1200万像素。

内部参数矩阵是为:

[ 9.95397796e+02, -5.74043156e+00, 5.30659959e+02, 0.00000000e+00, 1.04963119e+03, 6.55565437e+02, 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]

因为代码是以一个方格为一个单位,没有考虑单位长度,所以要求真实的参数应该乘一个单位长度,博主采用的方格的尺寸是2.98cm的,自己拿excel画的,get了一个新技能~~

原文标题:张正友相机标定法原理与实现

文章出处:【微信公众号:新机器视觉】欢迎添加关注!文章转载请注明出处。

责任编辑:haq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 3D
    3D
    +关注

    关注

    9

    文章

    2861

    浏览量

    107314
  • 相机
    +关注

    关注

    4

    文章

    1343

    浏览量

    53497
  • 机器视觉
    +关注

    关注

    161

    文章

    4342

    浏览量

    120098

原文标题:张正友相机标定法原理与实现

文章出处:【微信号:vision263com,微信公众号:新机器视觉】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    思特分享 绘制江河湖海:uEye相机助力水路航道自动3D测绘系统

    TAPS 是德国 Fraunhofer 研究所为河道测量而设计的半自动测向系统,通过搭载两台思特 uEye FA 工业相机 实现对海岸区域进行高效精准的三维建模和航道地图重建。
    的头像 发表于 10-30 16:18 161次阅读
    <b class='flag-5'>友</b>思特分享  绘制江河湖海:uEye<b class='flag-5'>相机</b>助力水路航道自动3D测绘系统

    思特分享 车载同步技术创新:多相机系统如何实现精准数据采集与实时处理?

    车载多相机采集系统是智能驾驶技术实际应用中的的“眼睛”,思特车载图像采集和回放系统切实提升了系统的实时同步采集与回放能力,为ADAS等应用的决策系统提供了可靠的核心数据。
    的头像 发表于 10-16 16:14 306次阅读
    <b class='flag-5'>友</b>思特分享 车载同步技术创新:多<b class='flag-5'>相机</b>系统如何<b class='flag-5'>实现</b>精准数据采集与实时处理?

    基于DCC和标定相机镜头畸变校正

    电子发烧友网站提供《基于DCC和标定相机镜头畸变校正.pdf》资料免费下载
    发表于 08-29 10:37 0次下载
    基于DCC和<b class='flag-5'>张</b>氏<b class='flag-5'>标定</b>的<b class='flag-5'>相机</b>镜头畸变校正

    思特新品 多光谱与高光谱相机:基于随心而定的可调谐滤光片技术

    高光谱成像拓展了人类的视野,让我们能看到可见光之外的东西。思特高光谱相机与多光谱相机基于可调谐滤光片技术,具备紧凑、高分辨率、低成本的优势,将其应用拓展至智慧农业、工业检测、皮肤检测等领域。
    的头像 发表于 08-09 17:36 342次阅读
    <b class='flag-5'>友</b>思特新品 多光谱与高光谱<b class='flag-5'>相机</b>:基于随心而定的可调谐滤光片技术

    C#之Delta并联机械手的视觉相机标定与形状匹配

    本文主要介绍如何通过运动VPLC711视觉接口来实现相机标定和形状匹配功能。
    的头像 发表于 06-26 15:11 809次阅读
    C#之Delta并联机械手的视觉<b class='flag-5'>相机</b><b class='flag-5'>标定</b>与形状匹配

    思特应用 | 慧眼识珠:如何实现无障碍高光谱成像?

    近红外相机可帮助人眼捕捉不同材料之间光谱特征的微小差异。思特 Monarch 微型可调近红外相机以其小体积、低成本、高性能,3步即可快速实现各种材料的分类应用。
    的头像 发表于 06-05 17:12 290次阅读
    <b class='flag-5'>友</b>思特应用 | 慧眼识珠:如何<b class='flag-5'>实现</b>无障碍高光谱成像?

    相机标定技术的性能分析与工具比较

    相机在从遥感、测绘、机器人技术到内窥镜等一系列应用中都是不可或缺的。这些应用通常需要了解相机中真实世界点和它们在图像中的几何关系。
    发表于 04-30 09:28 868次阅读
    <b class='flag-5'>相机</b><b class='flag-5'>标定</b>技术的性能分析与工具比较

    思特应用 | 稳步前行:基于FPGA 3D相机实现轮胎定位检测应用

    乘用车辆的长期稳定行驶离不开轮胎等零部件的定期检测。思特 3D相机可实时采集车辆四轮的三维点云图,提取关键信息并进行计算分析,实现车辆四轮定位的精确测量。
    的头像 发表于 04-24 17:00 344次阅读
    <b class='flag-5'>友</b>思特应用 | 稳步前行:基于FPGA 3D<b class='flag-5'>相机</b><b class='flag-5'>实现</b>轮胎定位检测应用

    康谋技术 |深入探讨:自动驾驶中的相机标定技术

    算法之间,是确保传感器数据准确性的基础,同时也是实现传感器融合的关键先决条件。在众多传感器中,相机以其丰富的信息获取能力和成本效益而成为自动驾驶系统中的首选。相机
    的头像 发表于 04-17 17:08 859次阅读
    康谋技术 |深入探讨:自动驾驶中的<b class='flag-5'>相机</b><b class='flag-5'>标定</b>技术

    工业相机单目和双目的区别

    工业相机标定的方法根据工业相机的数目可分为单目标定、双目标定Q以及多目标定
    的头像 发表于 03-26 16:26 1780次阅读
    工业<b class='flag-5'>相机</b>单目和双目的区别

    思特案例 | 捕捉“五彩斑斓的黑”:锗基短波红外相机的多种成像应用

    思特 BeyonSense 1短波红外相机,带您轻松捕捉人眼可见范围以外的"五彩斑斓的黑色",以丰富成像结果看见锗基短波红外相机在灵敏度与像素上的更多未来前景。
    的头像 发表于 01-03 15:11 402次阅读
    <b class='flag-5'>友</b>思特案例 | 捕捉“五彩斑斓的黑”:锗基短波红外<b class='flag-5'>相机</b>的多种成像应用

    阵列相机三维重建系统,谁才是顶流?

    标定过程仅需10分钟,能够自动完成所有校准步骤,无需人工干预。通过该标定解决方案,相机模组能够实现亚像素级别的精度和稳定性。
    的头像 发表于 12-19 15:14 1182次阅读

    相机标定中的坐标变换原理难点分析

    相机标定中的基本坐标系有:像素坐标系、图像坐标系、相机坐标系、世界坐标系,这些坐标系之间都有一定的转换关系,若这些转换关系已知,就可以得到世界坐标(棋盘上的点)和像素坐标之间的关系。
    发表于 12-19 10:42 1210次阅读
    <b class='flag-5'>相机</b><b class='flag-5'>标定</b>中的坐标变换原理难点分析

    采集激光雷达和相机的初始标定数据

    包含了计算相机内参,获得标定数据,优化计算外参和雷达相机融合应用相关的代码。 本方案中使用了标定板角点作为标定目标物,由于Livox雷达非重
    的头像 发表于 11-28 11:09 521次阅读
    采集激光雷达和<b class='flag-5'>相机</b>的初始<b class='flag-5'>标定</b>数据

    相机标定中各种标定板介绍以及优缺点分析

    在选择标定板时,一个重要的考虑因素是它的物理尺寸。这最终关系到最终应用的测量视场(FOV)。这是因为相机需要聚焦在特定的距离上标定。改变焦距长度会轻微地影响对焦距离,这会影响之前的标定
    的头像 发表于 11-25 14:36 953次阅读
    <b class='flag-5'>相机</b><b class='flag-5'>标定</b>中各种<b class='flag-5'>标定</b>板介绍以及优缺点分析