産業用カメラのキャリブレーション (Zhang Zhengyou キャリブレーション方法)

目次

カメラキャリブレーションの考え方

a. カメラキャリブレーションの定義

b. カメラキャリブレーションの目的

カメラのキャリブレーションのプロセス

a. キャリブレーションボードの選択

b. キャリブレーションボードの配置と撮影

c. キャリブレーションボードのコーナーポイントの抽出

Zhang Zhengyou 校正方法

a. 逆カメラ行列

b. 逆解歪み係数

Python を使用したカメラのキャリブレーション

a. OpenCV をインストールする

b. キャリブレーション ボードの写真を準備します。

c. OpenCV を使用したコーナー検出

d. ホモグラフィ行列を解く

e. 内部および外部マトリックス パラメータの計算

f. 歪み補正係数の解法

g. カメラキャリブレーションプログラムの実現

h. 主な機能ソリューション

カメラのキャリブレーション エラー

a. エラーの原因

b. エラー分析

c. エラー訂正

参考記事


カメラキャリブレーションの考え方

a. カメラキャリブレーションの定義

カメラのキャリブレーションとは、カメラの内部および外部パラメータを決定するプロセスを指します。

  • 内部パラメータには、カメラの焦点距離、ピクセル サイズ、主点の位置などが含まれます。
  • 外部パラメータには、カメラの位置、向きなどが含まれます。

カメラのキャリブレーションにより、ピクセル座標を実際の物理座標に関連付けて、3D 再構成、姿勢推定、ターゲット追跡などのマシン ビジョン アプリケーションを実現できます。カメラ キャリブレーションの目的は、正確なカメラ パラメーターを取得して、画像処理アルゴリズムの精度と堅牢性を確保することです。

b. カメラキャリブレーションの目的

カメラ キャリブレーションの主な目的は、カメラの内部および外部パラメーターを取得し、ピクセル座標と実際の物理座標との対応を確立して、マシン ビジョン アプリケーションを実現することです。具体的には、カメラのキャリブレーションの目的には次の側面が含まれます。

  • 正確な測定:カメラのキャリブレーションにより、オブジェクトのサイズ、形状、位置、および姿勢などの情報を正確に測定できます。

  • 3D 再構築: カメラのキャリブレーションにより、3D シーン内のオブジェクトの座標情報を取得できるため、3D 再構築が実現します。

  • ターゲット追跡: カメラのキャリブレーションにより、ピクセル座標を実際の物理座標に関連付けて、異なるフレーム間でターゲットの追跡を実現できます。

  • ロボットのナビゲーション: カメラのキャリブレーションを通じて、ロボットのナビゲーションと制御を実現するために、環境に対するロボットの位置と姿勢の情報を取得できます。

  • 自動生産:カメラのキャリブレーションにより、自動生産ラインでの物体検出、測定、選別などの機能を実現できます。

つまり、カメラキャリブレーションの目的は、カメラの内部および外部パラメータを正確に取得して、マシンビジョンアプリケーションを実現し、生産効率と製品品質を向上させることです。率直に言えば、カメラのキャリブレーションの目的は、カメラの行列と歪み係数を取得することです。

カメラのキャリブレーションのプロセス

a. キャリブレーションボードの選択

カメラのキャリブレーションでは、適切なキャリブレーション ボードを選択することが非常に重要です。

サイズに関しては、カメラの視野と最小ピクセル サイズに従って選択する必要があります.通常、キャリブレーション プレートはカメラの視野の少なくとも 80% を占めます.形状に関しては、長方形、プロトタイプ、ここでは、チェッカーボード グリッドを使用します。十分なキャリブレーション ポイントを提供でき、キャリブレーション位置は比較的簡単に決定できます。

b. キャリブレーションボードの配置と撮影

キャリブレーション ボードの配置と撮影プロセスは、カメラ キャリブレーションの精度と信頼性に大きな影響を与えます。

配置に関しては、カメラの視野内に配置する必要があり、キャリブレーションボードの歪みや曲がりを避けるために、キャリブレーションボードの平坦性と安定性を確保する必要があります。同時に、キャリブレーション プレートをカメラの光軸に対して垂直に配置する必要があります。撮影角度に関しては、キャリブレーションボードは、見下ろしたり、見上げたり、左右の側面図など、さまざまな角度から撮影する必要があります。同時に、キャリブレーション プレートを撮影するときは、影や反射を避けて、キャリブレーション プレートの表面が滑らかで均一であることを確認する必要があります。ショットの数に関しては、通常、カメラのキャリブレーションのために、さまざまな角度と位置でキャリブレーション ボードの写真を少なくとも 10 枚撮影する必要があります。撮影条件に関しては、キャリブレーションボードが撮影しているときに撮影環境の安定性を確保する必要があり、光の変化やオブジェクトの動きなどの要因がキャリブレーション結果に影響を与えないようにする必要があります。同時に、撮影時にカメラの露出、フォーカス、およびその他の設定が一貫していることを確認してください。

c. キャリブレーションボードのコーナーポイントの抽出

ここでは、Python で OpenCV メソッドを使用し、cv2.findChessboardCorners() 関数を使用して、キャリブレーション ボードのコーナー ポイントを自動的に抽出します。

その技術的なルートは次のとおりです。

  1. 画像の前処理:通常、コーナー抽出の前に、グレースケール、スムージング、2 値化などを含むキャリブレーション プレートの画像を前処理する必要があります。これらのプロセスにより、コーナー抽出の精度と安定性が向上します。

  2. キャリブレーション ボードのパラメーター設定: cv2.findChessboardCorners() 関数を使用する場合、キャリブレーション ボードのサイズ、各行と列のコーナー ポイントの数など、キャリブレーション ボードのパラメーターを設定する必要があります。これらのパラメータは、コーナー ポイント抽出の精度と安定性を確保するために、実際の状況に応じて設定する必要があります。

  3. コーナー抽出アルゴリズムの選択: OpenCV は、Harris、Shi-Tomasi、FAST など、さまざまなコーナー抽出アルゴリズムを提供します。カメラのキャリブレーションを実行する場合、通常、キャリブレーション ボードに適したコーナー ポイント抽出アルゴリズムが選択され、コーナー ポイント抽出の精度と安定性が向上します。

  4. コーナー座標の最適化: cv2.findChessboardCorners() 関数は、キャリブレーション ボードのコーナー座標を自動的に抽出できますが、これらの座標にはいくつかのエラーがある場合があります。キャリブレーションの精度を向上させるために、コーナー座標を最適化できます。たとえば、cv2.cornerSubPix() 関数を使用してサブピクセル レベルでコーナー ポイントを最適化します。

したがって、キャリブレーションボードのコーナーポイントを抽出する際には、コーナーポイント抽出の精度と安定性を確保するために、前処理、パラメータ設定、アルゴリズムの選択、および座標の最適化に注意を払う必要があります。


Zhang Zhengyou 校正方法

Zhang Zhengyou のキャリブレーション方法は、カメラのキャリブレーションの古典的な方法です。ここでは、数学的な観点から説明します。

a. 逆カメラ行列

歪みを考慮せずに、画像内のキャリブレーションボードのコーナー検出を実行して、コーナーポイントのピクセル座標値を取得します。これは、ワールド座標系のキャリブレーションボードのコーナーポイントの[u \quad v \quad 1]^{T}座標に変換されます。チェッカーボードのサイズ[^{W}X\quad ^{W}Y\quad ^{W}Z\quad 1]^{T}

このようにして、次のような式を得ることができます。

\left [ \begin {array}{ccc}\hat{u} \\ \hat{v}\\ 1 \end{array}\right ]^{T}=K[^{C}_{W}R \quad ^{C}P_{w_{0}}]\left [ \begin {array}{ccc}^{W}X \\ ^{W}Y\\ ^{W}Z\\1 \end{配列}\右]^{T}

ただし、計算の便宜上、^{W}Z=0キャリブレーション プレートを XY 平面に配置することがよくあります。

この式は次のように簡略化されます。

\left [ \begin {array}{ccc}\hat{u} \\ \hat{v}\\ 1 \end{array}\right ]^{T}=K[r_{1}\quad r_{2 } \quad p ]\left [ \begin {array}{ccc}^{W}X \\ ^{W}Y\\1 \end{array}\right ]^{T}

ここでのr_{1}合計は、r_{2}回転行列 の最初の 2 列であり^{C}_{W}R、キャリブレーション プレートから撮像面までのホモグラフィ行列は次のように設定されます。

H=\left [ \begin {array}{ccc} h_{11} &h_{12}&h_{13}\\ h_{21}&h_{22}&h_{23}\\ h_{31}&h_{32}&h_ {33}\end{} \right ]=[h_{1} \quad h_{2}\quad h_{3} ]

Hは、スケーリング係数をもつ正則 3×3 行列です。

既知のH逆解の内部パラメーター マトリックスに基づいて、K次のように代入することでh_{i}=Kr_{i}(i=1,2)知ることができます。

\small \left\{\begin{matrix} h_{1}^{T}Qh_{2}=0\\ h_{1}^{T}Qh_{1}=h_{2}^{T}Qh_{ 2}=1 \end{行列}\right.

その中で\small Q=K^{-T}K^{-1}、対称行列はK5 つの要素を含み、 3 組の方程式Hで解ける唯一の閉解を必要とするKため、キャリブレーション中に 3 組以上の写真を撮影する必要があります。K対応する外部パラメータ行列を計算できます

h_{i}^TQh_{j}=[h_{i1} \quad h_{i2} \quad h_{i3} ]\left [ \begin {array}{ccc} q_{1} &h_{2}&h_{3 }\\ h_{2}&h_{4}&h_{5}\\ h_{3}&h_{5}&h_{6}\end{} \right ] \left [ \begin {array}{ccc}h_{j1 } \\ h_{j2}\\ h_{j3} \end{配列}\right ]=v_{ij}^{T}Q

として書き換えることができます

\left [ \begin {array}{ccc} v_{12} ^{T}\\ v​​_{11}^{T}-v{22}^T\end{} \right ] q=0

その中で、行列はのベクトル形式である既知Ⅴの から完全に導出されますH1 つはせいぜい 2 つの線形方程式しか寄与できないため、内部パラメータ マトリックスを決定するには、少なくとも 3 つのキャリブレーション プレートが必要です (通常 、エンジニアリングには 10 から 20 のキャリブレーション プレートが適しています)。qQHV_{q}=0

上記の計算では、カメラの歪みの影響は無視され (もちろん、これは最初に与えられた条件でもあります)、最小二乗法を使用して、実際の半径方向の歪みの歪み係数を推定します (接線方向の歪みは無視します)。内部パラメータと外部パラメータ 最後に、最尤推定法を使用して最適化を行い、より高い精度の値を取得します。

b. 逆解歪み係数

(Zhang Zhengyou のキャリブレーション方法では、接線方向の歪みではなく、半径方向の歪みのみが考慮されます)得られた内部パラメーター マトリックスを使用して、歪みパラメーターを逆に解きます。

設定:

  • 歪みのないピクセル座標は[\hat{u} \quad \hat{v}]^{T}
  • 撮像面の座標は[\hat{x} \quad \hat{y}]^{T}
  • 歪んだピクセル座標は[u \quad v]^{T}
  • 撮像面の座標は[x \quad y]^{T}
  • 内部参照s=0

次に、次のとおりです。

\begin{cases} u=f_{u}x+c_{u}\\ v​​=f_{v}y+c_{v} \end{cases}  \begin{cases} \hat{u}=f_{u}\hat{x}+c_{u}\\ \hat{v}=f_{v}\hat{y}+c_{v} \end{ケース}

2 つの式を引き、 に代入しf_{u}x=u-c_{u}f_{v}y=v-c_{v}半径方向の歪みの式

\begin{cases} \hat{x}=x(1+k_{1}r^{2}+k_{2}r^{4})\\ \hat{y}=y(1+k_{1 }r^{2}+k_{2}r^{4}) \end{cases}

推測した

\left [ \begin {array}{ccc} (u-c_{u})r ^{2}&(u-c_{u})r ^{4}\\ (v-c_{v})r^ {2}&(v-c_{v})r^{4} \end{} \right ] [ \begin {array}{ccc} k_{1}\\k_{2} \end{}]=[ \begin {array}{ccc} \hat{u}-u\\ \hat{v}-v \end{}]

しかし、

\left [ \begin {array}{ccc}\hat{u} \\ \hat{v}\\ 1 \end{array}\right ]^{T}=K[^{C}_{W}R \quad ^{C}P_{w_{0}}]\left [ \begin {array}{ccc}^{W}X \\ ^{W}Y\\ ^{W}Z\\1 \end{配列}\右]^{T}

Mキャリブレーション ボードの写真では、各写真がN頂点を取り、不均一な線形方程式を構築できます。

D_{2MN\times 2}k=d_{2MN\times 1}

 ここで、 はk=[k_{1}\quad k_{2}]^{T}歪み係数です。最後に、回帰は最小二乗法によって実行されます

 k=(D^{T}D)^{-1}D^{T}d

Python を使用したカメラのキャリブレーション

a. OpenCV をインストールする

まず、OpenCV ライブラリをインストールする必要があります。pip を使用してインストールできます。

pip install opencv-python
pip install opencv-contrib-python

b. キャリブレーション ボードの写真を準備します。

この部分は、キャリブレーション ボードを購入していない場合に使用します。コードを使用して生成し、印刷して写真を撮ることができます。

import cv2
import numpy as np

# 定义棋盘格的尺寸
size = 140
# 定义标定板尺寸
boardx = size * 10
boardy = size * 7

canvas = np.zeros((boardy, boardx, 1), np.uint8) # 创建画布
for i in range(0, boardx):
    for j in range(0, boardy):
        if (int(i/size) + int(j/size)) % 2 != 0: # 判定是否为奇数格
            canvas[j, i] = 255
cv2.imwrite("chessboard.png", canvas)

ここで生成される画像サイズは 1400*980 です。サイズを変更する場合は、 ps を使用して変更するのが最も簡単な方法です。

c. OpenCV を使用したコーナー検出

カメラのキャリブレーションを実行するときは、キャリブレーション ボードのコーナー ポイントを検出し、その後のキャリブレーション計算に使用する必要があります。OpenCV はコーナー検出のための関数  'findChessboardCorners'  を提供します。

OpenCV を使用したコーナー検出のサンプル コードを次に示します。

import cv2
import numpy as np

pic_name='chessboard.png'
img = cv2.imread(pic_name)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 设置棋盘格尺寸和数量
board_size = (6, 9)
# 找到棋盘格角点
_, corners = cv2.findChessboardCorners(gray, board_size, None)

if _:
    # 提取亚像素角点
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
    corners = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
    # 在图像上绘制角点
    img = cv2.drawChessboardCorners(img, board_size, corners, _)
    cv2.imwrite('corners.png', img)
else:
    print(f'Unable to find corners in the {pic_name} image.')

これは別検出の効果ですが、実際の市松模様の写真では効果があまり良くなく、改善待ちです。

d. ホモグラフィ行列を解く

次のコードはEating Leeのブログを参照しています. ここでは、丁寧な説明と分析を行い、このブログに私自身のことを追加したことを確認します.

#homography.py

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)

実現されるのは、画像レジストレーションにおけるホモグラフィ変換のソリューションです。

具体的には、実装手順は次のとおりです。

  1. 入力画像とグラウンド トゥルース座標データは、正規化行列を使用して正規化されます。
  2. M 行列を作成し、SVD で M*h = 0 の h を解きます。
  3. 推定されたホモグラフィは、最終的なホモグラフィを取得するために微調整されます。

この部分については、上記の逆カメラ行列の式と合わせて理解することができます。

e. 内部および外部マトリックス パラメータの計算

#InExparam.py

import numpy as np

# 返回每一幅图的外参矩阵[R|t]
def get_ex_param(H, intrinsics_param):
    ex_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()
        ex_param.append(R)

    return ex_param

# 返回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_in_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]
    ])

get_ex_param は、ホモグラフィ行列 H から各画像の外部パラメータ行列 [R|t] の計算を実現します。ここで、R は回転行列を表し、t は平行移動ベクトルを表します。

具体的な実装プロセスは次のとおりです。

  1. 最初に、inv_intrinsics_param として記録されているカメラの内部パラメーター マトリックスを反転します。

  2. 各ピクチャのホモグラフィ行列 H に対して、3 つの列ベクトル h0、h1、h2 が抽出されます。

  3. inv_intrinsics_param を h0 で乗算する 2 ノルムの逆数として計算されるスケーリング係数 scale_factor を計算します。

  4. スケーリング係数に従って、回転行列 R の 3 つの列 r0、r1、および r2 を計算します。ここで、r0 と r1 はそれぞれ inv_intrinsics_param と h0、h1 で乗算され、スケーリング係数で乗算され、r2 はベクトルの外積によって計算されます。

  5. inv_intrinsics_param を h2 で乗算し、スケーリング係数を乗算して計算される変換ベクトル t を計算します。

  6. R と t を外部パラメーター行列 [R|t] に連結し、リスト ex_param に追加します。

  7. すべてのホモグラフィ行列 H について上記の計算が完了したら、ex_param リストを返します。

create_v ホモグラフィ行列 H と 2 点 p と q の添字を指定して、画像上のこれら 2 点に対応する固有ベクトル v を計算します。ここでの特徴ベクトル v は、画像上の 2 点間の位置関係によって決定され、回転、平行移動、スケールなど、2 点間の幾何学的情報が反映されます。

具体的には、このコードは次の手順を実装します。

  1. H 行列を 3x3 形式に再構築します。

  2. p と q の添え字に従って特徴ベクトル v を計算します。v は 6 次元ベクトルです。

  3. v を返します。

get_in_param は、複数の画像のホモグラフィ行列からカメラの内部パラメータ行列 A を推定することを実現します。ここで、A は、カメラの焦点距離、主点位置、カメラの歪みパラメータなどを含む 3x3 行列です。

主なプロセスは次のとおりです。

  1. 入力ホモグラフィ行列 H に従って、V 行列を作成します。V のサイズは 2n 行 6 列です。ここで、n は入力ホモグラフィ行列 H の数です。

  2. V に対して特異値分解を実行して、特異値行列 S と右特異ベクトル行列 VT を取得します。

  3. S の最後の特異値、つまり VT の最後の行に対応する右特異ベクトルを Vb=0 の解 b とします。

  4. b.に従って、カメラの内部パラメーター行列 A を計算します。

  5. カメラの内部パラメーター行列 A を返します。

f. 歪み補正係数の解法

#distortion.py

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 = np.array(D)
    temp = np.dot(np.linalg.inv(np.dot(D.T, D)), D.T)
    k = np.dot(temp, d)

    return k

カメラの歪み補正を実現し、歪み補正係数k0、k1を返す。

具体的な実装プロセスは次のとおりです。

  1. カメラの内部パラメーター マトリックス (intrinsic_param)、外部パラメーター マトリックス (extrinsic_param)、実画像座標 (real_coor)、および歪んだ画像座標 (pic_coor) を入力します。

  2. 各実座標 (real_coor[i])[j] について、同次座標に変換し、内部パラメーター マトリックスと外部パラメーター マトリックスを使用して、推定された画像座標 (u_estim、v_estim) を見つけます。

  3. 実座標の正規化座標 (coor_norm) から半径 r を計算し、推定画像座標と内部パラメータ行列を用いて歪み係数 D を計算し、D 配列に格納します。

  4. 推定座標と真の座標の残差を計算し、それらを d 配列に格納します。

  5. D 配列と d 配列に対して最小二乗法を使用して歪み補正係数 k0 と k1 を計算し、結果を返します。

g. カメラキャリブレーションプログラムの実現

#refine_all.py

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:\t", 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

カメラのキャリブレーションの目的は、取得した画像座標を変換するために、カメラの内部パラメーター (焦点距離、ピクセル間隔、主点座標などを含む) と歪みパラメーター (半径方向の歪みと接線方向の歪みを含む) を決定することです。カメラから実世界の座標へ。

具体的な実装プロセスは次のとおりです。与えられた実世界の座標と対応する画像座標に従って、最小二乗法を使用して、各画像のカメラの内部パラメーター、歪みパラメーター、および外部パラメーターが最適化アルゴリズムによって解決され、最適なカメラのキャリブレーション パラメータ。その中で、カメラの内部パラメーターと歪みパラメーターはすべての画像で共有されますが、各画像の外部パラメーターは個別に計算されます。

このプログラムは、次の機能を実装します。

  1. refinall_all_param: すべてのパラメータを微調整します
  2. compose_paramter_vector: すべてのパラメーターを配列に統合します
  3. decompose_paramter_vector: パラメータ セットを分解して、対応する内部パラメータ、外部パラメータ、および歪み補正係数を取得します。
  4. get_single_project_coor: カメラの組み込み関数、歪みパラメーター、および外部関数に従って、現実世界の座標からマッピングされた画像座標を返します

h. 主な機能ソリューション

import cv2
import numpy as np
import os

from parameter.homography import get_homography
from parameter.InExparam import get_ex_param,get_in_param
from parameter.distortion import get_distortion
from parameter.refine_all import refinall_all_param

def calibrate():
    # 求单应矩阵
    H = get_homography(pic_points, real_points_x_y)

    # 求内参
    intrinsics_param = get_in_param(H)

    # 求对应每幅图外参
    extrinsics_param = get_ex_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:\t", new_intrinsics_param)
    print("distortionk:\t", new_k)
    print("extrinsics_parm:\t", new_extrinsics_param)


if __name__ == "__main__":
    file_dir = r'E:\pythonconda2\demarcate\calibration_pic'
    # 标定所用图像
    pic_name = os.listdir(file_dir)

    # 由于棋盘为二维平面,设定世界坐标系在棋盘上,一个单位代表一个棋盘宽度,产生世界坐标系三维坐标
    cross_corners = [7, 4]  # 棋盘方块交界点排列
    real_coor = np.zeros((cross_corners[0] * cross_corners[1], 3), np.float32)
    real_coor[:, :2] = np.mgrid[0:7, 0:4].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 = cv2.imread(pic_path)

        # 寻找到棋盘角点
        succ, pic_coor = cv2.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()

カメラの内部パラメーター (焦点距離、主点などを含む) と外部パラメーター (空間内のカメラの位置と方向などを含む) のグループを調整することにより、カメラによってキャプチャされた画像は、現実の幾何学的情報を正確に反映できます。シーン。特定の実装では、プログラムはチェッカーボードのコーナーポイントを見つけることにより、画像と実際のシーンとの対応関係を確立し、複数の画像の対応する点を使用してカメラの内部および外部パラメーターを計算し、歪みも実行します。補正およびパラメータ 微調整およびその他の処理。

具体的なプロセスは次のとおりです。

  1. キャリブレーションに必要なフォルダー パスとチェッカーボード コーナーの配置を定義します。
  2. 3D ワールド座標系を作成し、各グリッドのサイズを設定して、対応する座標を生成します。
  3. キャリブレーションに使用される画像フォルダーをトラバースし、各画像を読み取ります。
  4. 画像ごとに、OpenCV の findChessboardCorners 関数を使用してチェス盤のコーナー ポイントを見つけ、それらを pic_points 配列に格納します。
  5. 世界座標系の 3D 座標を real_points 配列に格納します。
  6. ワールド座標系の x 座標と y 座標を real_points_x_y 配列に格納します。
  7. 調整するには、関数 calibration() を呼び出します。この関数の実装プロセスは次のとおりです。
  • 関数 findChessboardCorners を使用して、すべての画像のコーナー座標と対応するワールド座標系の 3D 座標を取得します。
  • 関数 get_homography を使用して、すべてのイメージのホモグラフィ行列 H を取得します。
  • get_in_param 関数を使用して、内部パラメーターを見つけます。
  • get_ex_param 関数を使用して、各画像の外部パラメーターを見つけます。
  • get_distortion 関数を使用して、画像の歪みを修正します。
  • refinall_all_param 関数を使用して、内部パラメーター、歪みパラメーター、および外部パラメーターを微調整します。
  • 校正結果の内部基準、歪みパラメータ、外部基準を出力します。

カメラのキャリブレーション エラー

a. エラーの原因

  • チェッカーボード キャリブレーション ボードの製造精度は高くないため、不規則なグリッド サイズや不正確なグリッド位置などの問題が発生します。
  • カメラ イメージングの非線形歪み: カメラ イメージング プロセスは、光学的要因、機械的要因などの影響を受け、画像内のピクセルの位置と実際の位置との間に誤差が生じます。
  • カメラの姿勢エラー: カメラのキャリブレーションでは、カメラの内部および外部パラメーターを計算するために複数の写真を撮る必要があり、さまざまな写真を撮るときのカメラの姿勢の違いもエラーにつながります。
  • 撮影プロセス中の環境の変化: 照明の変化、カメラの動き、キャリブレーション ボードの動きなどは、キャリブレーション結果に影響します。

b. エラー分析

再投影誤差と歪み補正誤差で分析できます。再投影誤差とは、較正されたパラメータを新しい画像に適用して、新しい画像内の点の投影位置と実際の点の位置との間の誤差を計算することを指します。

再投影誤差が小さいほど、キャリブレーション結果はより正確になります。

歪み補正誤差は、計算された新しい画像内の点の投影位置と、歪み補正が適用された後の実際の点の位置との間の誤差です。歪み補正後の誤差が再投影誤差より小さい場合、歪み補正は一定の役割を果たしています。

c. エラー訂正

  • キャリブレーション ボードの精度を向上させ、チェッカーボードのサイズをより規則的にし、グリッドの位置をより正確にします。
  • 高精度のカメラを使用して、カメラ イメージングの非線形歪みを減らします。
  • キャプチャされた写真の品質を向上させ、カメラの姿勢の安定性を確保します。
  • キャリブレーションを複数回実行し、平均値を計算するか、最適化してキャリブレーション精度を向上させます。
  • 歪みの問題については、歪み補正を実行し、歪み補正係数を計算によって取得して、画像の歪みを補正し、キャリブレーション結果に対する歪みの影響を軽減することができます。

参考記事

私も初めて学ぶので、大物の記事を参考にしながら、自分で情報を見つけて書いています。

(9 メッセージ) Python-OpenCV と PS を使用してチェッカーボード キャリブレーションを作成する board_opencv でチェスボードを作成する_None072 のブログ - CSDN ブログ(7 メッセージ) コンピューター ビジョン チュートリアル 0-4: 手動の Zhang Zhengyou キャリブレーション メソッド、詳細な画像の歪み (コード付き)_Zhang Zhengyou校正コード_Winter さんのブログ - CSDN ブログ

(メッセージ数 7) (超詳細) Zhang Zhengyou のキャリブレーションの原理と式の導出_Zhang Zhengyou のキャリブレーション式の推論_jiayuzhang128 のブログ - CSDN ブログ カメラのキャリブレーションに関する最も詳細で完全な説明 (メッセージ数 6) Zhang Zhengyou のカメラのキャリブレーションの原理と実装_Zhang Zhengyou のキャリブレーション方法_Eating Leeのブログ-CSDN ブログ

おすすめ

転載: blog.csdn.net/m0_62919535/article/details/130168114