深入浅出理解相机标定原理

news2024/11/24 0:29:05

重要说明:本文从网上资料整理而来,仅记录博主学习相关知识点的过程,侵删。

一、参考资料

微信公众号:计算机视觉life 专栏:#相机标定

Camera Calibration

张正友标定法-完整学习笔记-从原理到实战

二、相机标定相关介绍

1. 透视投影

用中心投影法,将物体投射到投射面上,从而获得一种较为接近视觉效果的单面投影图,也就是我们看到景物近大远小的一种成像方式。

2. 相机标定的概念

简单来说,摄像机标定(Camera calibration)就是从世界坐标系转换到像素坐标系的过程,该过程包括两个阶段:建立相机成像几何模型,并矫正透镜畸变。

建立相机成像几何模型:建立物体从三维世界映射到相机成像平面这一过程中的几何模型。最关键的部分是,得到相机的内参外参

矫正透镜畸变:由于透镜制造工艺,产生多种形式的畸变。为了去除畸变,计算并利用畸变系数来矫正像差,使成像后的图像与真实世界的景象保持一致。

3. 四大坐标系

世界坐标系(world coordinate system):用户定义的三维世界的坐标系,为了描述目标物在真实世界里的位置而被引入。单位为米,坐标系为 ( X w , Y w , Z w ) (X_w,Y_w,Z_w) (Xw,Yw,Zw)

相机坐标系(camera coordinate system):在相机上建立的坐标系,为了从相机的角度描述物体位置而定义,作为沟通世界坐标系和图像/像素坐标系的中间一环。单位为米,一般取相机的光学轴为Z轴,坐标系为 ( X c , Y c , Z c ) (X_c, Y_c,Z_c) (Xc,Yc,Zc)

图像物理坐标系(image coordinate system):为了描述成像过程中物体从相机坐标系到图像坐标系的投影透射关系而引入,方便进一步得到像素坐标系下的坐标。 单位为毫米,坐标原点为相机光轴与图像物理坐标系交点的位置,坐标系为 ( x , y ) (x,y) (x,y)

图像像素坐标系(pixel coordinate system):为了描述物体成像后的像点在数字图像上(相片)的坐标而引入,是我们真正从相机内读取到的信息所在的坐标系。单位为像素,坐标原点在左上角,坐标系为 ( u , v ) (u,v) (u,v)

四个坐标系之间的关系,如下图所示:
在这里插入图片描述

其中,相机坐标系的Z轴与光轴重合,且垂直于图像坐标系平面并通过图像坐标系的原点,相机坐标系与图像坐标系之间的距离为焦距f(也即图像坐标系原点与焦点重合)。像素坐标系平面u-v和图像坐标系平面x-y重合,但像素坐标系原点位于图中左上角(之所以这么定义,目的是从存储信息的首地址开始读写)。

4. 四大坐标系的转换

在这里插入图片描述

4.1 世界坐标系vs相机坐标系

世界坐标系转换到相机坐标系下,这两个坐标系之间的转换属于刚体运动,物体只改变两个坐标系中的空间位置(平移)和朝向(旋转),而不改变其形状。他们之间的转换关系可以用平移矩阵T和旋转矩阵R来完成,这两个矩阵反映了世界坐标系与相机坐标系之间的转换关系,合称为外参矩阵 L w L_w Lw。获得了外参矩阵,已知世界坐标系中的一点,通过转换关系就可以得到此点在相机坐标系中的位置。

变换矩阵由一个旋转矩阵和平移向量组合成的齐次坐标系来表示:
[ x C y C z C 1 ] = [ R t 0 3 T 1 ] [ x W y W z W 1 ] = [ r 1 r 2 r 3 t ] [ x W y W 0 1 ] = [ r 1 r 2 t ] [ x W y W 1 ] \left[\begin{array}{c}x_{C}\\y_{C}\\z_{C}\\1\end{array}\right]=\left[\begin{array}{cc}R&t\\0_{3}^{T}&1\end{array}\right]\left[\begin{array}{c}x_{W}\\y_{W}\\z_{W}\\1\end{array}\right]=\left[\begin{array}{cc}r_{1}&r_{2}&r_{3}&t\end{array}\right]\left[\begin{array}{c}x_{W}\\y_{W}\\0\\1\end{array}\right]=\left[\begin{array}{cc}r_{1}&r_{2}&t\end{array}\right]\left[\begin{array}{c}x_{W}\\y_{W}\\1\end{array}\right] xCyCzC1 =[R03Tt1] xWyWzW1 =[r1r2r3t] xWyW01 =[r1r2t] xWyW1
其中,R为旋转矩阵,t为平移向量。假定世界坐标系中,物点所在平面过世界坐标系原点,且与 Z w = 0 Z_w=0 Zw=0 轴垂直,即棋盘平面与 X w − Y w X_w - Y_w XwYw 平面重合,目的在于方便后续计算。其中,变化矩阵为:
[ R t 0 3 T 1 ] \begin{bmatrix}R&t\\0_3^T&1\end{bmatrix} [R03Tt1]
下图表示用R、t将世界坐标系转换到相机坐标系的过程。
在这里插入图片描述

4.2 相机坐标系vs图像物理坐标系(无畸变)

相机坐标系转换到图像物理坐标系下,就是将三维的坐标系转换为二维的坐标系,也即投影透视过程。如下图所示,小孔成像过程:针孔面(相机坐标系)在图像平面(图像物理坐标系)和物点平面(棋盘平面)之间,所成图像为倒立实像
在这里插入图片描述

但是,为了在数学上更方便描述,将相机坐标系和图像物理坐标系位置对调(没有实际的物理意义,只是方便计算),变成下图所示:
在这里插入图片描述
在这里插入图片描述

假设相机坐标系中有一点M,根据相似三角形原理,则在图像物理坐标系下(无畸变)的成像点P的坐标为:
x p = f x M z M , y p = f y M z M \mathrm{x}_{p}=f\frac{x_{M}}{z_{M}},y_{p}=f\frac{y_{M}}{z_{M}} xp=fzMxM,yp=fzMyM
将上式转化为齐次坐标表示形式为:
z M [ x p y p 1 ] = [ f 0 0 0 0 f 0 0 0 0 1 0 ] [ x M y M z M 1 ] = [ f 0 0 0 f 0 0 0 1 ] [ 1 0 0 0 0 1 0 0 0 0 1 0 ] [ x M y M z M 1 ] z_{M}\left[\begin{array}{c}{x_{p}}\\{y_{p}}\\{1}\\\end{array}\right]=\left[\begin{array}{ccc}{f}&{0}&{0}&{0}\\{0}&{f}&{0}&{0}\\{0}&{0}&{1}&{0}\\\end{array}\right]\left[\begin{array}{c}{x_{\mathrm{M}}}\\{y_{\mathrm{M}}}\\{z_{\mathrm{M}}}\\{1}\\\end{array}\right]=\left[\begin{array}{ccc}{f}&{0}&{0}\\{0}&{f}&{0}\\{0}&{0}&{1}\\\end{array}\right]\left[\begin{array}{ccc}{1}&{0}&{0}&{0}\\{0}&{1}&{0}&{0}\\{0}&{0}&{1}&{0}\\\end{array}\right]\left[\begin{array}{cc}{x_{\mathrm{M}}}\\{y_{\mathrm{M}}}\\{z_{\mathrm{M}}}\\{1}\\\end{array}\right] zM xpyp1 = f000f0001000 xMyMzM1 = f000f0001 100010001000 xMyMzM1

4.3 相机坐标系vs图像物理坐标系(有畸变)

请参考章节:相机畸变模型

4.4 图像物理坐标系vs图像像素坐标系

以一个形象的例子,说明图像物理坐标系与图像像素坐标系之间的区别。物理坐标系是一个连续的概念,它以毫米为单位,就好比某一观众在电影院里的具体坐标值为(3.4, 5.9);而像素坐标系是一个离散的概念,它是以像素为单位,之鞥你是整数值坐标,就好比某一观众在电影院里的位置是(第三排,第六列)。另外需要注意的是,这两个坐标系的原点位置不相同,物理坐标系的原点是相机光轴与图像物理坐标系的交点,通常称其为主点;而像素坐标系则以像素图像的左上角为原点

由于定义的图像像素坐标系原点与图像物理坐标系原点不重合,假设图像物理坐标系原点在图像像素坐标系下的坐标为 ( u 0 , v 0 ) (u_0, v_0) (u0,v0),每个像素点在图像物理坐标系x轴、y轴方向的尺寸为: d x 、 d y d_x、d_y dxdy,且像点在图像物理坐标系下的坐标为 ( x c , y c ) (x_c,y_c) (xc,yc),可得想点在图像坐标系下的坐标为:
u = x c d x + u 0 , v = y c d y + v 0 u=\frac{x_{c}}{d_{x}}+u_{0},\mathbf{v}=\frac{y_{c}}{d_{y}}+\mathbf{v}_{0} u=dxxc+u0,v=dyyc+v0
化为齐次坐标系的表现形式为:
[ u ν 1 ] = [ 1 / d x 0 u 0 0 1 / d y v 0 0 0 1 ] [ x c y c 1 ] \begin{bmatrix}u\\\nu\\1\end{bmatrix}=\begin{bmatrix}1/d_x&0&u_0\\0&1/d_y&v_0\\0&0&1\end{bmatrix}\begin{bmatrix}x_c\\y_c\\1\end{bmatrix} uν1 = 1/dx0001/dy0u0v01 xcyc1

5. 相机畸变模型

透镜的畸变主要分为:径向畸变和切向畸变,还有薄透镜畸变等,但都没有径向畸变和切向畸变影响显著,因此一般只考虑径向畸变k和切向畸变p。一共需要5个畸变参数(k1、k2、k3、p1和p2)来描述透镜畸变,其中径向畸变有3个参数 (k1、k2、k3),切向畸变有2个参数 (p1、p2)。

5.1 径向畸变

径向畸变是由于透镜形状的制造工艺导致的,越向透镜边缘移动,径向畸变越严重。下图所示是径向畸变的两种类型:桶型畸变(k<0)和枕型畸变(k>0)。
在这里插入图片描述

  • 当k>0时,r越大(点离中心越远),畸变量越大,r越小,畸变量越小,呈枕型。
  • 当k<0时,r越大(点离中心越远),畸变量越小,r越小,畸变量越大,呈桶型。

实际情况中,常用r=0处的泰勒级数展开的前几项来近似描述径向畸变。矫正径向畸变前后的坐标关系为:
{ x d i s t o r t e d = x p ( 1 + k 1 r 2 + k 2 r 4 + k 3 r 6 ) y d i s t o r t e d = y p ( 1 + k 1 r 2 + k 2 r 4 + k 3 r 6 ) \begin{cases}\mathbf{x}_{distorted}=x_p(1+k_1r^2+k_2r^4+k_3r^6)\\y_{distorted}=y_p(1+k_1r^2+k_2r^4+k_3r^6)\end{cases} {xdistorted=xp(1+k1r2+k2r4+k3r6)ydistorted=yp(1+k1r2+k2r4+k3r6)
由此可知,对于径向畸变,有3个径向畸变参数(k1、k2、K3)需要求解。

研究表明,径向畸变参数一般选择2或3个k值,可以获得较好结果。如果k取得再多,影响不会很大可以忽略,也有可能导致效果不好。

5.2 切向畸变

切向畸变是由于透镜和CMOS或者CCD的安装位置误差导致。由此,如果存在切向畸变,一个矩形被投影到成像平面时,很可能会变成一个梯形。切向畸变需要2个额外的畸变参数(p1和p2)来描述,矫正前后的坐标关系为:
{ x d i s t o r t e d = x p + [ 2 p 1 x p y p + p 2 ( r 2 + 2 x p 2 ) ] y d i s t o r t e d = y p + [ p 1 ( r 2 + 2 y p 2 ) + 2 p 2 x p y p ] \begin{cases}\mathbf{x}_{distorted}=x_{p}+[2p_{1}x_{p}y_{p}+p_{2}(r^{2}+2{x_{p}}^{2})]\\\mathbf{y}_{distorted}=y_{p}+[p_{1}(r^{2}+2{y_{p}}^{2})+2p_{2}x_{p}y_{p}]\end{cases} {xdistorted=xp+[2p1xpyp+p2(r2+2xp2)]ydistorted=yp+[p1(r2+2yp2)+2p2xpyp]

6. 相机标定参数

内参

  • 焦距长度: f x 、 f y f_x、f_y fxfy

  • 主点(光心)像素坐标: c x 、 c y c_x、c_y cxcy

  • 畸变系数: k 1 , k 2 , k 3 , p 1 , p 2 k_{1},k_{2},k_{3},p_{1,}p_{2} k1,k2,k3,p1,p2

c a m e r a matri x = [ f x 0 c x 0 f y c y 0 0 1 ] camera\textit{matri}x=\begin{bmatrix}f_x&0&c_x\\0&f_y&c_y\\0&0&1\end{bmatrix} cameramatrix= fx000fy0cxcy1

D i s t o r t i o n   c o e f f i c i e n t s = ( k 1   k 2   p 1   p 2   k 3 ) Distortion \ coefficients = (k_1 \ k_2 \ p_1 \ p_2 \ k_3) Distortion coefficients=(k1 k2 p1 p2 k3)

外参

  • 旋转矩阵R;
  • 平移矩阵T。

7. 单应矩阵

张正友标定算法原理详解

7.1 引言

图像像素坐标系与世界坐标系之间的坐标映射关系,公式如下:
[ u ν 1 ] = s [ f x γ u 0 0 f y ν 0 0 0 1 ] [ r 1 r 2 t ] [ x W y W 1 ] \begin{bmatrix}u\\\nu\\1\end{bmatrix}=s\begin{bmatrix}f_x&\gamma&u_0\\0&f_y&\nu_0\\0&0&1\end{bmatrix}\begin{bmatrix}r_1&r_2&t\end{bmatrix}\begin{bmatrix}x_W\\y_W\\1\end{bmatrix} uν1 =s fx00γfy0u0ν01 [r1r2t] xWyW1
其中,u、v表示图像像素坐标系中的坐标,s表示尺度因子, f x 、 f y 、 u 0 、 v 0 、 γ f_x、f_y、u_0、v_0、\gamma fxfyu0v0γ 表示5个相机内参,R、t表示相机外参, X w 、 Y w 、 Z w X_w、Y_w、Z_w XwYwZw 表示世界坐标系中的坐标,棋盘平面位于世界坐标系中 Z w = 0 Z_w=0 Zw=0 的平面。

7.2 单应性的概念

单应性(Homography)变换,描述物体在世界坐标系和图像像素坐标系之间的位置映射关系,这个变换矩阵称为单应性矩阵。在上述式子中,单应性矩阵定义为:
H = s [ f x γ u 0 0 f y ν 0 0 0 1 ] [ r 1 r 2 t ] = s M [ r 1 r 2 t ] H=s\begin{bmatrix}f_x&\gamma&u_0\\0&f_y&\nu_0\\0&0&1\end{bmatrix}\begin{bmatrix}r_1&r_2&t\end{bmatrix}=sM\begin{bmatrix}r_1&r_2&t\end{bmatrix} H=s fx00γfy0u0ν01 [r1r2t]=sM[r1r2t]
其中,M是相机内参矩阵:
M = [ f x γ u 0 0 f y ν 0 0 0 1 ] M=\left[\begin{array}{ccc}f_x&\gamma&u_0\\0&f_y&\nu_0\\0&0&1\end{array}\right] M= fx00γfy0u0ν01
单应性矩阵同时包含了相机内参和外参。

7.3 单应性的应用

7.3.1 图像矫正

用单应性矩阵进行图像矫正,最少需要四个对应点。
在这里插入图片描述

7.3.2 视角变换

单应性矩阵可以方便的将普通视图转换成鸟瞰图,如下图所示,左边是普通视图,右边是鸟瞰图:
在这里插入图片描述

7.3.3 图像拼接

既然单应性矩阵可以进行视角转换,那么把不同角度拍摄的图像都转换到同一视角下,就可以实现图像拼接了。如下图所示,通过单应性矩阵H可以将image1和image2都变换到同一个平面。
在这里插入图片描述
在这里插入图片描述

7.3.4 增强现实AR

平面二维标图案(marker)经常用来做AR展示,根据marker不同视角下的图像,可以方便的得到虚拟物体的位置姿态并进行现实,如下图所示:
在这里插入图片描述

7.4 单应性矩阵的原理

假设两张图像中的对应点的齐次坐标为 ( x ′ , y ′ , 1 ) (x^{\prime},y^{\prime},1) (x,y,1),单应性矩阵H定义为:
H = [ h 11 h 12 h 13 h 21 h 22 h 23 h 31 h 32 h 33 ] H=\left[\begin{array}{ccc}h_{_{11}}&h_{_{12}}&h_{_{13}}\\h_{_{21}}&h_{_{22}}&h_{_{23}}\\h_{_{31}}&h_{_{32}}&h_{_{33}}\end{array}\right] H= h11h21h31h12h22h32h13h23h33
则有:
[ x ′ y ′ 1 ] ∼ [ h 11 h 12 h 13 h 21 h 22 h 23 h 31 h 32 h 33 ] [ x y 1 ] \left[\begin{array}{c}x'\\y'\\1\end{array}\right]\sim\left[\begin{array}{ccc}h_{11}&h_{12}&h_{13}\\h_{21}&h_{22}&h_{23}\\h_{31}&h_{32}&h_{33}\end{array}\right] \begin{bmatrix}x\\y\\1\\\end{bmatrix} xy1 h11h21h31h12h22h32h13h23h33 xy1
矩阵展开后有3个等式,将第三个等式带入前两个等式中,可得:
x ′ = h 11 x + h 12 y + h 13 h 31 x + h 32 y + h 33 x'=\frac{h_{11}x+h_{12}y+h_{13}}{h_{31}x+h_{32}y+h_{33}} x=h31x+h32y+h33h11x+h12y+h13

y ′ = h 21 x + h 22 y + h 23 h 31 x + h 32 y + h 33 y'=\frac{h_{21}x+h_{22}y+h_{23}}{h_{31}x+h_{32}y+h_{33}} y=h31x+h32y+h33h21x+h22y+h23

也就是一个点对,对应两个等式。

将H添加约束条件,使得H矩阵模变为1,即 ∣ ∣ H ∣ ∣ = 1 ||{H}||=1 ∣∣H∣∣=1,如下:
x ′ = h 11 x + h 12 y + h 13 h 31 x + h 32 y + h 33 ( 1 ) x'=\frac{h_{11}x+h_{12}y+h_{13}}{h_{31}x+h_{32}y+h_{33}} \quad (1) x=h31x+h32y+h33h11x+h12y+h13(1)

y ′ = h 21 x + h 22 y + h 23 h 31 x + h 32 y + h 33 ( 2 ) y'=\frac{h_{21}x+h_{22}y+h_{23}}{h_{31}x+h_{32}y+h_{33}} \quad (2) y=h31x+h32y+h33h21x+h22y+h23(2)

h 11 2 + h 12 2 + h 13 2 + h 21 2 + h 22 2 + h 23 2 + h 31 2 + h 32 2 + h 33 2 = 1 h_{11}^2+h_{12}^2+h_{13}^2+h_{21}^2+h_{22}^2+h_{23}^2+h_{31}^2+h_{32}^2+h_{33}^2=1 h112+h122+h132+h212+h222+h232+h312+h322+h332=1

对上式(1)、(2)乘以分母展开,得:
( h 31 x + h 32 y + h 33 ) x ′ = h 11 x + h 12 y + h 13 \left(h_{31}x+h_{32}y+h_{33}\right)x^{\prime}=h_{11}x+h_{12}y+h_{13} (h31x+h32y+h33)x=h11x+h12y+h13

( h 31 x + h 32 y + h 33 ) y ′ = h 21 x + h 22 y + h 23 \left(h_{31}x+h_{32}y+h_{33}\right)y^{\prime}=h_{21}x+h_{22}y+h_{23} (h31x+h32y+h33)y=h21x+h22y+h23

整理,得到:
h 11 x + h 12 y + h 13 − h 31 x x ′ − h 32 y x ′ − h 33 x ′ = 0 h_{11}x+h_{12}y+h_{13}-h_{31}xx^{\prime}-h_{32}yx^{\prime}-h_{33}x^{\prime}=0 h11x+h12y+h13h31xxh32yxh33x=0

h 21 x + h 22 y + h 23 − h 31 x y ′ − h 32 y y ′ − h 33 y ′ = 0 h_{21}x+h_{22}y+h_{23}-h_{31}xy'-h_{32}yy'-h_{33}y'=0 h21x+h22y+h23h31xyh32yyh33y=0

假设我们得到两幅图片中对应的N个点对(特征点匹配对),可以得到如下线性方程组:
在这里插入图片描述

写成矩阵形式:
2 N x 9 9 x 1 2 N x 1 A h = 0 \begin{array}{cccc}\mathbf{2Nx9}&\mathbf{9x1}&&\mathbf{2Nx1}\\\mathbf{A}&\mathbf{h}&=&\mathbf{0}\end{array} 2Nx9A9x1h=2Nx10
在真实的应用场景中,计算的点对中都会包含噪声。比如,点的位置偏差几个像素,甚至出现特征点对误匹配的现象,如果只是用4个点对来计算单应性矩阵,会出现很大的误差。因此,为了使得计算更精确,一般都会使用远大于4个点对来计算单应性矩阵。另外,上述的线性方程组采用直接线性解法,通常很难得到最优解,所以实际使用中一般会使用其他优化方法,如奇异值分解、Levenberg-Marquarat(LM)算法等进行求解。

7.5 单应性矩阵的计算流程

根据打印的棋盘标定图和拍摄的照片来计算单应性矩阵H。其大致流程如下:

  1. 打印一张棋盘格标定图纸,将其贴在平面物体的表面;

  2. 拍摄一组不同方向棋盘格的图片,可以通过移动相机来实现,也可以移动标定图片来实现;

  3. 对于每张拍摄的棋盘图片,检测图片中所有的棋盘格的角点。

  4. 因为棋盘标定图纸中所有角点的世界坐标是已知的,这些角点对应的图像像素坐标也是已知的。如果有N>=4个配对点对,就可以根据LM等优化方法得到其单应性矩阵H,匹配点对越多,计算结果越鲁棒。计算单应性矩阵一般不需要自己写函数实现,在OpenCV中有现成的函数可以调用,对应的C++函数是:

    Mat findHomography(InputArray srcPoints, InputArray dstPoints, int method=0, double ransacReprojThreshold=3, OutputArray mask=noArray() )
    

    从函数来看,只要输入匹配点对,指定具体计算方法,即可得到单应性矩阵。

三、张正友相机标定法

1. 引言

张正友博士在1999年发表在国际顶级会议ICCV的论文《Flexible Camera Calibration By Viewing a Plane From Unknown Orientations》,提出一种利用平面棋盘格进行相机标定的实用方法。该方法介于摄影标定法和自标定法之间,既客服了摄影标定法需要的高精度三维标定物的缺点,又解决了自标定法鲁棒性差的难题。标定过程仅需要一个打印出来的棋盘格,并从不同方向拍摄几组图像即可,任何人都可以自己制作标图案。不仅实用灵活方便,而且精度很高,鲁棒性好。因此,很快被全世界广泛采用,极大的促进了三维计算机视觉从实验室走向真实世界的进程。

张正友标定法,使用二维方格组成的标定板进行标定,采集标定板不同位姿图片,提取图片中角点像素坐标,通过单应矩阵计算出相机的内外参数初始值,利用非线性最小二乘法估计畸变系数,最后使用极大似然估计法优化参数。该方法操作简单,精度高,满足大部分的场合。

2. 张正友简介

张正友博士,是世界著名的计算机视觉和多媒体技术的专家,ACM Fellow,IEEE Fellow。现任微软研究院视觉计算组高级研究员。他在立体视觉、三维重建、运动分析、图像配准、摄像机标定等方面都有开创性的贡献。

3. 棋盘格标定板

3.1 棋盘格标定板简介

棋盘格标定板是一块由黑白方块间隔组成的标定板,用来作为相机标定的标定物(从真实世界映射到数字图像内的对象)。之所以使用棋盘作为标定物,是因为相对于复杂的三维物体,平面棋盘模式更容易处理。与此同时,二维物体相对于三维物体会缺少一部分信息,通过多次改变棋盘的方位来捕捉图像,以获得更丰富的坐标信息。如下图所示,是相机在不同方向下拍摄的同一个棋盘格标定板的图像。
在这里插入图片描述

3.2 获取棋盘格标定板

3.2.1 方格标定板

标定板的尺寸:w=9,h=6。
在这里插入图片描述

3.2.2 圆形标定板

标定板的尺寸:w=11,h=4。
在这里插入图片描述

标定板的尺寸:w=17,h=6。
在这里插入图片描述

3.2.3 ChAruco标定板

  • 7X5 ChAruco board;
  • square size: 30 mm ;
  • marker size: 15 mm;
  • aruco dict: DICT_5X5_100;
  • page width: 210 mm, page height: 297 mm。
    在这里插入图片描述

3.2.4 生成棋盘格标定板

Create calibration pattern

用python生成自定义的标定板,下载python脚本文件:gen_pattern.py

3.3 角点

角点就是黑白棋盘格交叉点,如下图所示,中间品红的圆圈内就是一个角点。
在这里插入图片描述

如下图所示,X方向有8个角点,Y方向有3个角点。
在这里插入图片描述

3.4 内角点

角点是指黑白色相接的方块定点部分;内角点是不与标定板边缘接触的内部角点。

patternSize:(w,h),棋盘上每一排和每一列的内角数。w=棋盘板一行上黑白块的数量-1,h=棋盘板一列上黑白块的数量-1,例如:9x4的棋盘板,则(w,h)=(8,3)

3.5 棋盘格标定板坐标系

棋盘格标定板坐标系,默认采用平面标定模式,即Z轴都为零。

定义棋盘格标定板位于世界坐标系 Z w = 0 Z_w=0 Zw=0 的平面上,世界坐标系的原点位于棋盘格标定板的固定一角,比如下图中的黄色点。图像像素坐标系的原点位于图像左上角。
在这里插入图片描述

写坐标时,要保证Z轴为0,按照先X变化,后Y变化,从小到大的顺序写。如果棋盘标定板的网格尺寸为5cm,世界坐标系下的坐标可以写为:(0,0,0),(5,0,0), (10,0,0)…(0,5,0), (5,5,0), (10,5,0),…

4. 张正友标定法计算流程

采用棋盘格标定板进行标定,需要找到棋盘格内角点,根据棋盘格内角点在图像像素坐标系下的坐标和各点对应的世界坐标系下的坐标,拍摄多个位置的棋盘格,多点求解相机内外参。

使用OpenCV进行张正友标定法,计算流程如下:

  1. 准备标定图片,理论上至少4张,一般在多个角度采集20张左右;
  2. 提取标定板的角点,并计算出标定板上角点的坐标,一般将标定板当作 X-Y 平面,Z为0;
  3. 相机标定,通过张正友标定法计算出内参外参以及畸变参数;
  4. 对标定结果进行评价,一般通过重投影误差进行评价;
  5. 查看标定效果,利用标定结果对棋盘图进行矫正。

5. 核心函数

cornerSubPix()

OpenCV中feature2D学习——亚像素级角点检测(cornerSubPix)

OpenCV3 亚像素角点检测:cornerSubPix()

功能:亚像素级别的角点检测,将内角点位置精确到亚像素级精度。

void cornerSubPix(InputArray image, 
                  InputOutputArray corners, 
                  Size winSize, 
                  Size zeroZone, 
                  TermCriteria criteria);

参数解释

  • image 表示输入原始的棋盘标定板图像(图像矩阵),是输入参数。图像矩阵必须是8-bit灰度图或者彩色图像,在图像传入函数之前,一般经过灰度处理,还有滤波操作;

  • corners 表示内角点坐标,既是输入参数,也是输出参数。把 findChessboardCorners() 输出的内角点作为该函数的输入corners,经过亚像素级别的内角点检测之后输出corners;

  • winSize 表示搜索窗口边长的一半。例如,如果winSize=Size(5, 5),则一个大小为 ( 5 ∗ 2 + 1 ) ∗ ( 5 ∗ 2 + 1 ) = 11 ∗ 11 (5*2+1)*(5 *2+1)=11*11 (52+1)(52+1)=1111 的搜索窗口将被使用;

  • zeroZone 表示搜索区域中间的dead region边长的一半,有时用于避免自相关矩阵的奇异性。如果值设为(-1,-1)则表示没有这个区域;

  • criteria 表示角点亚像素级精准化迭代过程的终止条件。可选的值有:

    • cv::TermCriteria::MAX_ITER 表示迭代次数达到最大次数时停止;
    • cv::TermCriteria::EPS 表示角点位置变化已经达到最小值时停止迭代。

    二者均使用 cv::TermCriteria() 构造函数进行指定,且可以组合使用。

    //指定亚像素计算迭代条件
    cv::TermCriteria criteria = cv::TermCriteria(
        cv::TermCriteria::MAX_ITER + cv::TermCriteria::EPS,  // 终止条件
        40,  // 最大次数
        0.01); // 最小值
    

findChessboardCorners()

findChessboardCorners()角点检测详解

功能:角点检测,获取棋盘格标定板内角点位置。

返回值:所有角点被检测出且以一定顺序排列,返回非0数,否则返回0。

// 函数原型
findChessboardCorners(InputArray image,
                      Size patternSize,
                      OutputArray corners,
                      int flags);

参数解释

  • image 表示输入原始的棋盘标定板图像(图像矩阵),是输入参数。图像矩阵必须是8-bit灰度图或者彩色图像,在图像传入函数之前,一般经过灰度处理,还有滤波操作;

  • patternSize 表示内角点的size,是输入参数。

    数据类型:Size patternSize(w,h),w、h分别表示棋盘上每一排和每一列的内角数。

    w=棋盘格标定板一行上黑白块的总数-1,h=棋盘格标定板一列上黑白块的总数-1

    例如:10x6的棋盘格标定板,则(w,h)=(9,5)。

    注意:根据内角点的尺寸,确定标定板的方向。具体来说,w和h不相同,该函数可辨别标定板的方向;如果w和h相同,该函数每次画出的角点起始位置会发生变化,不利于标定

  • corners 表示检测到的内角点的输出数组,输出参数。

    数据类型:vector<vector<point2f>>

  • flags 标志位,是输入参数,有默认值。

    • CV_CALIB_CB_ADAPTIVE_THRESH:函数默认方式,使用自适应阈值法对图像进行二值化,而不是使用一个固定的阈值;
    • CV_CALIB_CB_NORMALIZE_IMAGE:在利用固定阈值或自适应阈值法二值化图像之前,调用EqualizeHist()函数进行图像归一化处理,利用直方图均衡化图像;
    • CV_CALIB_CB_FILTER_QUADS:二值化完成后,函数开始定位图像中的四边形(这里不应该称之为正方形,因为存在畸变)。使用额外的标准(如轮廓面积,周长,正方形形状)来过滤掉在轮廓检索阶段提取的假四边形,从而使得角点检测更准确更严格;
    • CALIB_CB_FAST_CHECK:快速检测选项,对图像进行一个快速检查机制以查找棋盘板的角点。对于检测角点极可能不成功检测的情况,这个标志位可以使函数效率提升。

    注意:标志位可组合使用。CALIB_CB_FAST_CHECK一般用于快速检测,很有可能检测不成功,特别是棋盘光线不均匀时。总体来说,CV_CALIB_CB_ADAPTIVE_THRESH是最可能检测到棋盘格的方式。组合使用推荐CV_CALIB_CB_ADAPTIVE_THRESH|CV_CALIB_CB_NORMALIZE_IMAGE,如果默认方式或这个组合方式检测不到角点,基本就需要重新采图了。

该函数的功能是判断图像内是否包含完整的棋盘格,如果能完全检测出来,就把角点位置顺序(从左到右,从上到下)记录下来,并返回非0数,否则返回0。这里对 patternSize 参数要求非常严格,函数必须检测出相同的size才会返回非0,否则返回0。

该函数检测的角点坐标是不准确的,获得角点精确坐标,可使用 cornerSubPix() 函数,进行角点亚像素提取,使用方法可参考博客:角点检测及优化函数使用

drawChessboardCorners()

功能:把检测到的角点在原图中画出来。

calibrateCamera()

OpenCV函数用法之calibrateCamera

功能:相机标定。

double calibrateCamera(InputArrayOfArrays objectPoints, 
                       InputArrayOfArrays imagePoints,
                       Size imageSize, 
                       InputOutputArray cameraMatrix, 
                       InputOutputArray distCoeffs, 
                       OutputArrayOfArrays rvecs, 
                       OutputArrayOfArrays tvecs, 
                       int flags=0);

参数解释

  • objectPoints 表示世界坐标系下的点,即3D points。

    数据类型为:std::vector<std::vector<cv::Point3f>> objectPoints,第一层vector表示每一个视角(每一张图),第二层vector表示每一个点。

  • imagePoints 表示其对应图像像素坐标系下的点,即2D points。这个值可以通过 findChessboardCorners() 函数从图像中获得。

    数据类型为:std::vector<std::vector<cv::Point2f>> imagePoints,第一层vector表示每一个视角,第二层vector表示每一个内角点。

  • imageSize 表示图像的大小,在计算相机的内参和畸变矩阵需要用到;

  • cameraMatrix 表示相机内参矩阵,该矩阵大小为 3x3。该矩阵由相机标定输出的结果。

    数据类型为:cv::Mat cameraMatrix

  • distCoeffs 表示畸变矩阵,具体尺寸取决于参数 flags。该矩阵由相机标定输出的结果。

    数据类型:cv::Mat distCoeffs

  • rvecs 表示旋转向量。每个 vector<Point3f> 会得到一个 rvecs,每个 vec 为 3x1,可以用 Rodrigues() 函数转换为3x3的旋转矩阵。

    数据类型:vector<cv::Mat> rvecs

  • tvecs 表示平移向量,和rvecs 一样,每个 vec 为3x1。

    数据类型:vector<cv::Mat> tvecs

    参数 rvecs 和 tvecs 是相机外参,对于每一个视图,都可以将其世界坐标系转成相机坐标系

  • flags 表示标定时所采用的参数,包括决定是否使用初始值、畸变矩阵的参数个数等。有如下参数:

    • CV_CALIB_USE_INTRINSIC_GUESS:使用该参数时,在cameraMatrix矩阵中应该有fx,fy,cx,cy的估计值。否则的话,将初始化图像的中心点(cx,cy),并使用最小二乘估算出fx,fy;
    • CV_CALIB_FIX_PRINCIPAL_POINT:在进行优化时会固定光轴点。当CV_CALIB_USE_INTRINSIC_GUESS参数被设置,光轴点将保持在中心或者某个输入的值;
    • CV_CALIB_FIX_ASPECT_RATIO:固定fx/fy的比值,只将fy作为可变量,进行优化计算。当CV_CALIB_USE_INTRINSIC_GUESS没有被设置,fx和fy将会被忽略。只有fx/fy的比值在计算中会被用到;
    • CV_CALIB_ZERO_TANGENT_DIST:设定切向畸变参数(p1,p2)为零;
    • CV_CALIB_FIX_K1,…,CV_CALIB_FIX_K6:对应的径向畸变在优化中保持不变;
    • CV_CALIB_RATIONAL_MODEL:计算k4,k5,k6三个畸变参数。如果没有设置,则只计算其它5个畸变参数。

6. 示例代码

用OpenCV实现张正友标定流程。

6.1 C++实现

张正友视觉标定算法学习笔记

示例一

对多张视图进行标定。

#include <iostream>
#include <opencv2/opencv.hpp>
#include  <boost/filesystem.hpp>

std::vector<std::string> get_all_image_file(std::string image_folder_path){
    boost::filesystem::path dirpath = image_folder_path;
    boost::filesystem::directory_iterator end;
    std::vector<std::string> files;
    for (boost::filesystem::directory_iterator iter(dirpath); iter != end; iter++)
    {
        boost::filesystem::path p = *iter;
        files.push_back(dirpath.string()+ "/"+ p.leaf().string());
    }
     std::sort(files.begin(),files.end());
    return files;
}

std::vector<cv::Mat> get_all_iamge(std::string image_folder_path)
{
    std::vector<cv::Mat> images;
    std::vector<std::string> image_files_path = get_all_image_file(image_folder_path);
    for(int i=0; i<  image_files_path.size() ;i++){
        cv::Mat image;
        image = cv::imread(image_files_path[i]);
        images.push_back(image);
    }
    return images;
}

int find_chessboard(cv::Mat image, std::vector<cv::Point2f> &image_points, cv::Size board_size)
{
    if (0 == findChessboardCorners(image, board_size, image_points))
    {
        std::cout<<"can not find chessboard corners!\n";
        return 0;
    }
    else
    {
        cv::Mat view_gray;
        cv::cvtColor(image,view_gray,cv::COLOR_RGB2GRAY);
        //对粗提取的角点进行亚像素精确化
        cv::find4QuadCornerSubpix(view_gray,image_points,cv::Size(11,11)); 
        //int nChessBoardFlags = cv::CALIB_CB_EXHAUSTIVE | cv::CALIB_CB_ACCURACY;
        //bool bFindResult = findChessboardCornersSB(view_gray,board_size,image_points,nChessBoardFlags );   
        //Opencv4 识别棋盘格方法,比opencv3有较大提升
    }
    return 1;
}

int init_chessboard_3dpoints(cv::Size board_size, std::vector<cv::Point3f> &points, float point_size)
{
    cv::Size2f square_size = cv::Size2f(point_size,point_size);
    for (int i=0;i<board_size.height;i++){
        for (int j=0;j<board_size.width;j++){
            cv::Point3f realPoint;
            realPoint.x = j*square_size.width;
            realPoint.y = i*square_size.height;
            realPoint.z = 0;
            points.push_back(realPoint);
        }
    }
    return 0;
}


void calib_monocular(std::vector<cv::Mat> images){
    cv::Size image_size;
    cv::Size board_size(11,4);
    std::vector<cv::Mat> images_tvecs_mat;
    std::vector<cv::Mat> images_rvecs_mat;
    image_size.width = images[0].cols;
    image_size.height = images[0].rows;
    std::vector<std::vector<cv::Point2f> > images_points;
  	// 识别所有图片的棋盘格
    for(int i=0;i<images.size();i++){
        std::vector<cv::Point2f> image_points;
        if(find_chessboard(images[i],image_points,board_size)>0){
             images_points.push_back(image_points);
        }
    }

    std::vector<cv::Point3f> image_points_in3d;
	// 计算棋盘格角点在棋盘格坐标系中的位置
    init_chessboard_3dpoints(board_size,image_points_in3d,0.045);  // 0.045为棋盘格一个格子的大小
    std::vector<std::vector<cv::Point3f> > images_points_in3d;
	// 生成所有识别出的标定板对应在各自棋盘格坐标系中的位置
    for(int i=0;i<images_points.size();i++){
        images_points_in3d.push_back(image_points_in3d);
    }
    cv::Mat intrinsic,distortion;
	// 使用张正友标定法计算内参畸变以及外参
    cv::calibrateCamera(images_points_in3d,
                        images_points,
                        image_size,
                        intrinsic,
                        distortion,
                        images_rvecs_mat,
                        images_tvecs_mat);
}

int main(int argc, char *argv[])
{
    std::string image_file_path = argv[1];
    std::vector<cv::Mat> images = get_all_iamge(image_file_path);
    calib_monocular(images);
    return 0;
}

在这里插入图片描述

示例二

对单张视图进行标定。

int cols = 10;
int rows = 7;
float distance = 30;    //间距30mm

cv::Size patternSize(cols,rows);
std::vector<cv::Point2f> corners;
std::vector<std::vector<cv::Point2f>> cornersVect;
std::vector<cv::Point3f> worldPoints;
std::vector<std::vector<cv::Point3f>> worldPointsVect;

for (int i=0;i<cols;i++)
{
    for (int j=0;j<rows;j++)
    {
        worldPoints.push_back(cv::Point3f(i*distance,j*distance,0));
    }
}

bool find=cv::findChessboardCorners(image,patternSize,corners);
cv::drawChessboardCorners(image,patternSize,corners,find);
cv::Mat cameraMatirx,distCoeffs;
std::vector<cv::Mat> rvecs,tvecs,rvecs2,tvecs2;
if (find)
{    
    cornersVect.push_back(corners);
    worldPointsVect.push_back(worldPoints);
    cv::calibrateCamera(worldPointsVect,
                        cornersVect,image.size(),
                        cameraMatirx,
                        distCoeffs,
                        rvecs,tvecs);
}

6.2 Python实现

[学习笔记]python-Opencv cv2.findChessboardCorners() 的基本使用

示例一

对单张视图进行标定。

import numpy as np
import cv2
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

fname='calibration_test.png'

image=cv2.imread(fname)
# plt.imshow(image)
# plt.show()

gray=cv2.cvtColor(image,cv2.COLOR_RGB2GRAY)
plt.imshow(gray,cmap='gray')
plt.show()

# Find the chessboard corners
nx=8
ny=6
ret, corners = cv2.findChessboardCorners(gray, (nx, ny), None)

print('ret:',ret)
# print(len(corners))

# If found, draw corners
if ret == True:
    # Draw and display the corners
    cv2.drawChessboardCorners(image, (nx, ny), corners, ret)
    plt.imshow(image)

如下图所示,9x7的棋盘格标定板,一行棋盘格为9,一列棋盘格为7,则内角点 patternSize(w,h) 中的w=8,h=6。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

示例二

对单张视图进行标定。

import numpy as np
import cv2 as cv
import glob


# termination criteria
# 
criteria = (cv.TERM_CRITERIA_MAX_ITER + cv.TERM_CRITERIA_EPS, 30, 0.001)

# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
"""
objp = 
[[0 0 0], 
[0 0 0],
...,
[0 0 0]], (42, 3)
"""
objp = np.zeros((6*7,3), np.float32)

"""
np.mgrid[0:7,0:6] = array([
[[0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1],
[2, 2, 2, 2, 2, 2],
[3, 3, 3, 3, 3, 3],
[4, 4, 4, 4, 4, 4],
[5, 5, 5, 5, 5, 5],
[6, 6, 6, 6, 6, 6]],

[[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5]]]), (2, 7, 6)

objp = array([
[0 0 0], 
[1 0 0], 
[2 0 0],
...,
[6 5 0]], (42, 3)
"""
objp[:,:2] = np.mgrid[0:7,0:6].T.reshape(-1,2)

# Arrays to store object points and image points from all the images.
objpoints = [] # 3d point in real world space
imgpoints = [] # 2d points in image plane.

images = glob.glob('*.jpg')
for fname in images:
	img = cv.imread(fname)
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    
    # Find the chess board corners
    ret, corners = cv.findChessboardCorners(gray, (7,6), None)
    
    # If found, add object points, image points (after refining them)
    if ret == True:
        objpoints.append(objp)
        corners2 = cv.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria)
        imgpoints.append(corners2)
        
        # Draw and display the corners
        cv.drawChessboardCorners(img, (7,6), corners2, ret)

        cv.imshow('img', img)
        cv.waitKey(500)
cv.destroyAllWindows()

# Calibration
"""
camera matrix: mtx
distortion coefficients: dist
rotation vectors: rvecs
translation vectors: tvecs
"""
ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)

if ret == True:
    # Re-projection Error
    mean_error = 0
    for i in range(len(objpoints)):
         imgpoints2, _ = cv.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
         error = cv.norm(imgpoints[i], imgpoints2, cv.NORM_L2)/len(imgpoints2)
         mean_error += error
    print( "total error: {}".format(mean_error/len(objpoints)) )

如下图所示,8x7的棋盘格标定板,一行棋盘格为9,一列棋盘格为7,则内角点 patternSize(w,h) 中的w=7,h=6。
在这里插入图片描述
在这里插入图片描述

四、相关经验

opencv标定实现总结(圆点,棋盘格和非对称圆点)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/923456.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言刷题训练DAY.12

1.统计成绩 解题思路&#xff1a; 这里我们设置两个变量记录最大值和最小值&#xff0c;再用一个sum统计分数总和即可。 解题代码&#xff1a; #include<stdio.h> int main() {int n 0;scanf("%d", &n);double arr[100] { 0 };int i 0;//最高分double …

UVM Heartbeat机制

1. 前言 在硬件电路中&#xff0c;为了使系统在异常情况下能自动复位&#xff0c;一般都需要引入看门狗(Watchdog)。看门狗其实就是一个定时器电路。当看门狗启动后&#xff0c;计数器开始自动计数&#xff0c;经过一定时间&#xff0c;如果没有被清零&#xff0c;计数器溢出就…

谁为软件质量负责?

如果我们直接问自己以下问题——谁负责软件质量&#xff0c;我们首先想到的就是——当然是测试人员&#xff01;但这并不像看起来那么容易。 IT领域有几个特点&#xff0c;总是发生在软件测试的最后阶段&#xff0c;就在它发布之前。它们使我们不仅从软件的技术质量保证的角度…

学习JAVA打卡第四十一天

字符串与字符数组、字节数组 ⑴字符串与字符数组 String类的构造方法String&#xff08;char a[]&#xff09;和String&#xff08;char a[]&#xff09;,int offset,int length,分别用数组a中的全部字符和部分字符创建string对象。 String类也提供将string对象的字符序列存…

vray for Cinema 4D如何创建照片级真实室内效果?

在V-Ray for Cinema 4D中渲染 3D室内设计一开始可能会很困难&#xff0c;但您投入的努力总会得到回报。无论您的技术水平和创意眼光如何&#xff0c;在 V-Ray 中创建室内设计时都需要考虑很多事项。 在这篇博文中&#xff0c;我们将分享一些最佳实践&#xff0c;帮助您创建令人…

12-总结-从 0 开始搭建框架

项目结构: 一. 安装第三方库 # 下载源: https://pypi.tuna.tsinghua.edu.cn/simple/pip3 install flask2.3.2 pip3 install jinja23.1.2 pip install pymysql # 保持持久化pip install flask-script2.0.3 # 管理应用程序 pip install flask-sqlalchemy3.0.3 # 实现ORM映射…

高等数学之不定积分

由导函数推原函数就是不定积分 只是一个&#xff01; 记得常数C!!! 不定积分的几何含义

基于TechGrow实现Hexo引流微信公众号

首发博客地址 https://blog.zysicyj.top/ # 前言 Hexo 博客建议安装 hexo-readmore (opens new window) 插件&#xff0c;将 TechGrow (opens new window) 的免费微信公众号引流工具整合到博客中&#xff0c;用户扫码关注微信公众号后可以解锁全站文章&#xff0c;让微信公众号…

基于乌燕鸥算法优化的BP神经网络(预测应用) - 附代码

基于乌燕鸥算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于乌燕鸥算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.乌燕鸥优化BP神经网络2.1 BP神经网络参数设置2.2 乌燕鸥算法应用 4.测试结果&#xff1a;5.Matlab代…

数字的画笔:数据可视化的魅力与实用性

数据可视化是一种强大的工具&#xff0c;用于将复杂的数据和信息以图形化的方式呈现&#xff0c;以便人们更容易理解、分析和发现其中的模式和趋势。通过图表、图形和其他可视元素&#xff0c;数据可视化可以帮助我们将抽象的数字转化为有意义的视觉呈现&#xff0c;从而提升了…

QT6安装完成后,再安装低版本的MinGW或其他组件方式

首先进入点击安装的uinstall Qt 并不是真的卸载 通过下面几步 1&#xff0c;首先登录自己账户 2&#xff0c;然后进入欢迎中&#xff0c;点击“添加和移除组件” 3&#xff0c;然后检索自己需要的安装内容

MDTA模块(Restormer)

From a layer normalized tensor Y ∈ R H ^ W ^ C ^ \mathbf{Y} \in \mathbb{R}^{\hat{H} \times \hat{W} \times \hat{C}} Y∈RH^W^C^, our MDTA first generates query ( Q ) (\mathbf{Q}) (Q), key ( K ) (\mathbf{K}) (K) and value ( V ) (\mathbf{V}) (V) project…

前端基础踩坑记录

前言&#xff1a;在做vue项目时&#xff0c;有时代码没有报错&#xff0c;但运行时却各种问题&#xff0c;没有报错排查起来就很费劲&#xff0c;本人感悟&#xff1a;写前端&#xff0c;需要好的眼神&#xff01;&#xff01;&#xff01;谨以此博客记录下自己的踩坑点。 一、…

vue学习之hello world

依赖引入 <script src"https://unpkg.com/vue2.6.10/dist/vue.js"></script>Hello world 实现 <html><head><style></style></head><body><script src"https://unpkg.com/vue2.6.10/dist/vue.js">…

日志搞不定?手把手教你如何使用Log4j2

系列文章目录 从零开始&#xff0c;手把手教你搭建Spring Boot后台工程并说明 Spring框架与SpringBoot的关联与区别 SpringBean生成流程详解 —— 由浅入深(附超精细流程图) Spring监听器用法与原理详解 Spring事务畅谈 —— 由浅入深彻底弄懂 Transactional注解 面试热点详解…

Keepalived+Lvs(dr)调度器主备配置小实验

目录 前言 一、实验拓扑图 二、配置LVS&#xff08;dr&#xff09;模式 三、配置调配器热备 四、测试 总结 前言 Keepalived和LVS&#xff08;Linux Virtual Server&#xff09;是两个常用的开源软件&#xff0c;通常结合使用以提供高可用性和负载均衡的解决方案。 Keepalive…

Mybatis查询一条数据

上一篇我们介绍了在pom文件中引入mybatis依赖&#xff0c;配置了mybatis配置文件&#xff0c;通过读取配置文件创建了会话工厂&#xff0c;使用会话工厂创建会话获取连接对象读取到了数据库的基本信息。 如果您需要对上面的内容进行了解&#xff0c;可以参考Mybatis引入与使用…

【指标】指标公式大全,款款经典(建议珍藏)!-神奇指标网

三、指标源码&#xff1a; 1、连续三天高开高走的选股公式 count(o〉ref(c,1&#xff09;andc>o&#xff0c;3)3&#xff1b; 2、连续3天每天的最低价都比前一天高 count&#xff08;l〉ref(c,1&#xff09;,3)3&#xff1b; 3、周量缩小50%或40&#xff05;或n&#x…

帮助中心实践方式:及时提示反馈,引导自助解决

为了及时高效的帮助用户解决当下实际问题&#xff0c;很多产品都会专门设置一个独立的产品帮助中心&#xff0c;满足客户需要获取解决方案的需要&#xff0c;减轻人工客服端压力。 帮助中心实践方式 常规的帮助中心文档和用户群&#xff0c;解决的是用户遇到问题或者疑问时&am…

Zebec Protocol:模块化 L3 链 Nautilus Chain,深度拓展流支付体系

过去三十年间&#xff0c;全球金融科技领域已经成熟并迅速增长&#xff0c;主要归功于不同的数字支付媒介的出现。然而&#xff0c;由于交易延迟、高额转账费用等问题愈发突出&#xff0c;更高效、更安全、更易访问的支付系统成为新的刚需。 此前&#xff0c;咨询巨头麦肯锡的一…