← Blog · 2026-11-04 · Tái tạo 3D · ~15 phút đọc

Hiệu chuẩn camera bằng bàn cờ: phương pháp Zhang

Vài bức ảnh chụp một mẫu phẳng cho ra ma trận nội tại của camera theo công thức đóng như thế nào, vì sao ba góc nhìn là mức tối thiểu thực tế, và một thí nghiệm Monte-Carlo về ảnh hưởng của nhiễu và số góc nhìn.

1. Bối cảnh

Hai bài trước trên blog này — tam giác hoá và thuật toán 8 điểm — đều bắt đầu từ toạ độ pixel và một mô hình camera. Tái tạo 3D theo đơn vị đo thực cần ma trận nội tại \(K\) của camera: tiêu cự, điểm chính và (trong mô hình tổng quát) độ xiên. Ước lượng \(K\) chính là hiệu chuẩn camera.

Hiệu chuẩn cổ điển cần một vật chuẩn 3D gia công chính xác. Zhang (2000) chỉ ra rằng chỉ cần chụp một mẫu phẳng — ví dụ bàn cờ in ra dán lên tấm cứng — ở vài hướng khác nhau, không cần biết mẫu đã chuyển động ra sao. Quy trình của ông có giai đoạn đầu cho nghiệm công thức đóng, sau đó là tinh chỉnh phi tuyến theo cực đại hoá hợp lý và mô hình hoá méo xuyên tâm của ống kính. Bài này dẫn xuất và cài đặt riêng giai đoạn công thức đóng, rồi đo xem nó hoạt động tốt đến đâu trên dữ liệu tổng hợp.

2. Một mặt phẳng cho ta một homography

Dùng mô hình lỗ kim \(s\,\tilde{\mathbf m}=K[R\mid\mathbf t]\,\tilde{\mathbf M}\) với $$K=\begin{pmatrix}\alpha&\gamma&u_0\\0&\beta&v_0\\0&0&1\end{pmatrix},$$ trong đó \(\alpha,\beta\) là tiêu cự tính bằng pixel, \((u_0,v_0)\) là điểm chính và \(\gamma\) là độ xiên. Đặt hệ toạ độ thế giới lên mẫu, nên mọi điểm của mẫu có \(Z=0\). Cột thứ ba của \(R\) biến mất; viết \(R=[\mathbf r_1\ \mathbf r_2\ \mathbf r_3]\):

$$s\begin{pmatrix}u\\v\\1\end{pmatrix}=K[\mathbf r_1\ \mathbf r_2\ \mathbf t]\begin{pmatrix}X\\Y\\1\end{pmatrix}\ \Longrightarrow\ H=[\mathbf h_1\ \mathbf h_2\ \mathbf h_3]=\lambda K[\mathbf r_1\ \mathbf r_2\ \mathbf t],$$

với một hệ số \(\lambda\) chưa biết. \(H\) là homography 3×3 giữa mặt phẳng mẫu và ảnh. Có thể ước lượng nó từ các góc đã phát hiện bằng chính DLT chuẩn hoá của các bài trước (homography có 8 bậc tự do nên 4 cặp điểm là đủ; lưới 9×6 cho 54 điểm).

Trái: lưới 9 x 6 điểm của mẫu trên mặt phẳng Z=0. Phải: ảnh của lưới đó trong bốn góc nhìn ngẫu nhiên.
Hình 1. Thiết lập tổng hợp dùng bên dưới. Trái: lưới góc 9×6, ô vuông 25 mm. Phải: bốn góc nhìn ngẫu nhiên của lưới. (Nhãn trong hình bằng tiếng Anh.)

3. Hai ràng buộc cho mỗi góc nhìn

Từ \(H=\lambda K[\mathbf r_1\ \mathbf r_2\ \mathbf t]\) ta có \(\mathbf r_1=\lambda^{-1}K^{-1}\mathbf h_1\) và \(\mathbf r_2=\lambda^{-1}K^{-1}\mathbf h_2\). Vì \(R\) là ma trận quay, \(\mathbf r_1\) và \(\mathbf r_2\) trực chuẩn: \(\mathbf r_1^\top\mathbf r_2=0\) và \(\|\mathbf r_1\|=\|\mathbf r_2\|\). Thay vào, với mỗi góc nhìn ta được

$$\mathbf h_1^\top B\,\mathbf h_2=0,\qquad \mathbf h_1^\top B\,\mathbf h_1=\mathbf h_2^\top B\,\mathbf h_2,\qquad B=K^{-\top}K^{-1}.$$

\(B\) đối xứng nên được mô tả bằng vector \(\mathbf b=(B_{11},B_{12},B_{22},B_{13},B_{23},B_{33})^\top\). Với \(\mathbf h_i=(h_{i1},h_{i2},h_{i3})^\top\), định nghĩa

$$\mathbf v_{ij}=\big(h_{i1}h_{j1},\ h_{i1}h_{j2}+h_{i2}h_{j1},\ h_{i2}h_{j2},\ h_{i3}h_{j1}+h_{i1}h_{j3},\ h_{i3}h_{j2}+h_{i2}h_{j3},\ h_{i3}h_{j3}\big)^\top,$$

để \(\mathbf h_i^\top B\mathbf h_j=\mathbf v_{ij}^\top\mathbf b\). Hai ràng buộc của một góc nhìn trở thành hai phương trình tuyến tính theo \(\mathbf b\), và \(n\) góc nhìn xếp chồng thành

$$V\mathbf b=\mathbf 0,\qquad V=\begin{pmatrix}\mathbf v_{12}^\top\\(\mathbf v_{11}-\mathbf v_{22})^\top\\\vdots\end{pmatrix}\in\mathbb R^{2n\times 6}.$$

4. Cần bao nhiêu góc nhìn?

\(B\) chỉ xác định sai khác một hệ số tỉ lệ nên có 5 bậc tự do — bằng số ẩn của \(K\). Mỗi góc nhìn cho 2 phương trình, vì vậy:

Như các bài trước, với dữ liệu nhiễu ta giải \(\min_{\|\mathbf b\|=1}\|V\mathbf b\|^2\): \(\mathbf b\) là vector kỳ dị phải của \(V\) ứng với giá trị kỳ dị nhỏ nhất. Các tham số nội tại sau đó suy ra từ \(B\) bằng công thức đóng (Zhang, Phụ lục B):

$$v_0=\frac{B_{12}B_{13}-B_{11}B_{23}}{B_{11}B_{22}-B_{12}^2},\quad \lambda'=B_{33}-\frac{B_{13}^2+v_0(B_{12}B_{13}-B_{11}B_{23})}{B_{11}},$$ $$\alpha=\sqrt{\lambda'/B_{11}},\quad \beta=\sqrt{\frac{\lambda' B_{11}}{B_{11}B_{22}-B_{12}^2}},\quad \gamma=-\frac{B_{12}\,\alpha^2\beta}{\lambda'},\quad u_0=\frac{\gamma v_0}{\beta}-\frac{B_{13}\,\alpha^2}{\lambda'}.$$

Hãy chú ý các căn bậc hai. Với camera thật \(B\) xác định dương, nhưng ước lượng từ dữ liệu nhiễu có thể rơi ra ngoài miền đó, và khi ấy không có nghiệm thực. Ta sẽ thấy điều này xảy ra thật trong thí nghiệm. Khi đã biết \(K\), tư thế của mỗi góc nhìn suy ra từ \(\mathbf r_1=\lambda K^{-1}\mathbf h_1\), \(\mathbf r_2=\lambda K^{-1}\mathbf h_2\), \(\mathbf r_3=\mathbf r_1\times\mathbf r_2\), \(\mathbf t=\lambda K^{-1}\mathbf h_3\) với \(\lambda=1/\|K^{-1}\mathbf h_1\|\) (rồi chiếu về ma trận quay gần nhất).

5. Cài đặt

Phần lõi của giai đoạn công thức đóng (NumPy). Script đầy đủ, gồm bộ sinh dữ liệu tổng hợp, thí nghiệm và cả hai hình, có thể tải về.

import numpy as np

def vij(H, i, j):
    h_i, h_j = H[:, i], H[:, j]
    return np.array([h_i[0]*h_j[0], h_i[0]*h_j[1] + h_i[1]*h_j[0], h_i[1]*h_j[1],
                     h_i[2]*h_j[0] + h_i[0]*h_j[2], h_i[2]*h_j[1] + h_i[1]*h_j[2], h_i[2]*h_j[2]])

def calibrate(Hs, N):
    """Hs: list of 3x3 homographies (pattern plane -> pixels).
    N: a similarity that shifts/scales pixel coordinates (conditioning), K = inv(N) @ K_normalised."""
    rows = []
    for H in Hs:
        Hn = N @ H
        rows += [vij(Hn, 0, 1), vij(Hn, 0, 0) - vij(Hn, 1, 1)]
    _, _, Vt = np.linalg.svd(np.array(rows))
    b = Vt[-1] if Vt[-1][0] > 0 else -Vt[-1]
    B11, B12, B22, B13, B23, B33 = b
    den = B11*B22 - B12**2
    v0 = (B12*B13 - B11*B23) / den
    lam = B33 - (B13**2 + v0*(B12*B13 - B11*B23)) / B11
    if den <= 0 or lam/B11 <= 0 or lam*B11/den <= 0:
        raise ValueError("estimated B is not positive definite")
    alpha = np.sqrt(lam / B11)
    beta = np.sqrt(lam * B11 / den)
    gamma = -B12 * alpha**2 * beta / lam
    u0 = gamma * v0 / beta - B13 * alpha**2 / lam
    return np.linalg.inv(N) @ np.array([[alpha, gamma, u0], [0, beta, v0], [0, 0, 1.0]])

Toạ độ pixel được dời về tâm ảnh và chia cho 280 (ma trận N) trước khi dựng \(V\), vì cùng lý do điều kiện số như bài 8 điểm; ở đây tôi không đo riêng ảnh hưởng của bước này.

Kiểm tra tính đúng đắn

Với các góc không nhiễu từ 5 góc nhìn ngẫu nhiên, ước lượng bằng đúng \(K\) thật đến sai số làm tròn: \(\max|K-K_{\text{true}}|=2{,}3\times10^{-13}\). Với chỉ 2 góc nhìn và phương trình độ xiên bằng 0, sai số là \(3{,}2\times10^{-8}\) (vẫn đúng trên thực tế, nhưng hệ có điều kiện số kém hơn).

6. Thí nghiệm: nhiễu và số góc nhìn

Thiết lập (tổng hợp, tái lập hoàn toàn). Tham số nội tại thật \(\alpha=820\), \(\beta=810\), \((u_0,v_0)=(322,238)\), \(\gamma=0\), kích thước ảnh 640×480. Lưới góc 9×6, ô vuông 25 mm. Mỗi góc nhìn có tư thế ngẫu nhiên: góc quay 15–50° quanh một trục ngẫu nhiên, tâm mẫu cách camera 0,45–0,75 m; tư thế nào có góc ra khỏi ảnh thì bị loại. Nhiễu Gaussian độc lập độ lệch chuẩn \(\sigma\) được cộng vào mọi góc, mỗi góc nhìn ước lượng một homography, và \(K\) tính bằng công thức đóng với độ xiên tự do. Với mỗi trong 12 tổ hợp \(\sigma\in\{0{,}1;0{,}5;1{,}0\}\) px và \(n\in\{3,5,10,20\}\) góc nhìn, tôi chạy 300 lần (NumPy seed 2026) và báo cáo trung vị trên các lần thành công của: sai số tương đối trung bình của \(\alpha\) và \(\beta\) (%), và khoảng cách giữa điểm chính ước lượng và điểm chính thật (px). Không mô phỏng méo ống kính và không có tinh chỉnh phi tuyến.

σ (px)3 góc nhìn5 góc nhìn10 góc nhìn20 góc nhìn
0,10,327 % / 1,86 px0,190 % / 1,23 px0,105 % / 0,85 px0,086 % / 0,62 px
0,51,576 % / 9,83 px0,836 % / 6,22 px0,548 % / 4,51 px0,459 % / 3,03 px
1,03,550 % / 20,98 px *1,782 % / 12,66 px1,090 % / 8,56 px0,901 % / 6,48 px

Mỗi ô: trung vị sai số tiêu cự / trung vị sai số điểm chính. * Trong 0,7 % của 300 lần chạy (3 góc nhìn, σ=1 px), \(B\) ước lượng không xác định dương và công thức đóng thất bại; các ô khác có 0 % thất bại, và trung vị đã loại các lần thất bại.

Đồ thị log-log sai số tiêu cự và sai số điểm chính theo số góc nhìn cho ba mức nhiễu
Hình 2. Trung vị sai số tiêu cự (trái) và sai số điểm chính (phải) theo số góc nhìn, mỗi đường một mức nhiễu. Đầu ra của script trong bài. (Nhãn trong hình bằng tiếng Anh.)

Các con số cho thấy gì

Hạn chế. Dữ liệu tổng hợp, nhiễu Gaussian đẳng hướng với cùng \(\sigma\) cho mọi góc, không có méo ống kính và không có góc ngoại lai, phân bố tư thế chỉ là một lựa chọn cụ thể, và tôi chỉ đánh giá giai đoạn công thức đóng. Hiệu chuẩn thực thêm bước ước lượng méo xuyên tâm và tinh chỉnh phi tuyến cực tiểu hoá sai số chiếu lại của mọi góc trên mọi tham số — Zhang báo cáo đây là một phần của quy trình; ở đây tôi không định lượng mức cải thiện so với công thức đóng. Kết quả trên camera thật còn phụ thuộc chất lượng bộ phát hiện góc.

7. Bài học thực hành

  1. Dùng ít nhất 5 góc nhìn, tốt nhất từ 10 trở lên, với mẫu nghiêng ở nhiều mức và nhiều hướng khác nhau — không phải 3 góc gần như song song.
  2. Để mẫu lớn trong ảnh và trải khắp trường nhìn; nhiễu vị trí góc chuyển thẳng thành sai số của \(K\).
  3. Coi nghiệm công thức đóng là giá trị khởi tạo. Khi làm thật, hãy tinh chỉnh toàn bộ tham số (và méo) bằng cách cực tiểu hoá sai số chiếu lại, đồng thời kiểm tra RMS sai số chiếu lại cuối cùng và độ lệch giữa các lần hiệu chuẩn độc lập.
  4. Nếu \(B\) không xác định dương, dữ liệu không nhất quán với mô hình (quá ít hoặc quá giống nhau góc nhìn, lỗi phát hiện góc): hãy chụp thêm thay vì ép ra nghiệm.
  5. Khi đã biết \(K\), bạn có thể khử méo ảnh, ước lượng ma trận cốt yếu thay vì \(F\) như trong bài 8 điểm, và tam giác hoá theo đơn vị đo thực như trong bài tam giác hoá.
Cần tái tạo 3D, chatbot agent hay công cụ AI theo yêu cầu?
Tôi nhận làm các việc này theo dạng freelance. Mô tả dự án của bạn trong vài câu, tôi sẽ phản hồi với câu hỏi và báo giá phạm vi cố định.

Nhận báo giá Dịch vụ tái tạo 3D

Tài liệu tham khảo

  1. Z. Zhang. “A flexible new technique for camera calibration.” IEEE Transactions on Pattern Analysis and Machine Intelligence 22(11):1330–1334, tháng 11/2000. doi:10.1109/34.888718. (Cũng là báo cáo kỹ thuật Microsoft Research MSR-TR-98-71, trang công bố.)
  2. R. Hartley và A. Zisserman. Multiple View Geometry in Computer Vision, tái bản lần 2. Cambridge University Press, 2004. ISBN 978-0-521-54051-3. Trang nhà xuất bản.

Code: zhang_calibration.py (Python 3.12, NumPy, Matplotlib). Mọi con số ở mục 5–6 là đầu ra của script này.