本课学习相机标定的核心原理与实现。我们将从数学基础出发,通过代码实践真正理解每一个算法的运作机制。
针孔相机模型
import numpy as np
# 3D点 -> 2D投影
P3d = np.array([1, 0, 5, 1]) # 齐次坐标
K = np.array([[500,0,320],[0,500,240],[0,0,1]])
Rt = np.array([[1,0,0,0],[0,1,0,0],[0,0,1,0]]) # 单位变换
P = K @ Rt
p2d = P @ P3d
p2d = p2d[:2] / p2d[2]
print(f'3D{P3d[:3]} -> 2D({p2d[0]:.0f},{p2d[1]:.0f})')
内参标定
import cv2, numpy as np
# 生成棋盘格角点
objp = np.zeros((6*9,3), dtype=np.float32)
objp[:,:2] = np.mgrid[0:9,0:6].T.reshape(-1,2)
print(f'棋盘角点数: {len(objp)}')
# 实际标定需要多视角图像
print('内参标定需要10+张不同角度的棋盘格图像')
外参标定
import cv2, numpy as np
# solvePnP求外参
obj_pts = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]], dtype=np.float32)
img_pts = np.array([[320,240],[420,240],[320,340],[320,140]], dtype=np.float32)
K = np.array([[500,0,320],[0,500,240],[0,0,1]], dtype=np.float32)
dist = np.zeros(5)
success, rvec, tvec = cv2.solvePnP(obj_pts, img_pts, K, dist)
if success:
R, _ = cv2.Rodrigues(rvec)
print(f'旋转向量: {rvec.ravel()}')
print(f'平移向量: {tvec.ravel()}')
畸变校正
import cv2, numpy as np
# 径向畸变模拟
img = np.zeros((200,200), dtype=np.uint8)
cv2.circle(img, (100,100), 80, 200, -1)
k1, k2 = 0.1, -0.05
# 用initUndistortRectifyMap模拟
dist = np.array([k1, k2, 0, 0, 0])
K = np.array([[200,0,100],[0,200,100],[0,0,1]], dtype=np.float32)
map1, map2 = cv2.initUndistortRectifyMap(K, dist, None, K, (200,200), cv2.CV_32FC1)
undistorted = cv2.remap(img, map1, map2, cv2.INTER_LINEAR)
print(f'畸变校正: k1={k1}, k2={k2}')
张正友标定法
import numpy as np
# 张正友法:从2D-3D对应估计内参
# 关键约束:|r1|=|r2|=1, r1·r2=0
print('张正友标定法核心约束:')
print('1. 旋转矩阵列向量单位长度')
print('2. 旋转矩阵列向量正交')
print('3. 至少需要3张不同视角图像')
print('4. 每张需要4+个2D-3D对应')
标定精度评估
import cv2, numpy as np
# 重投影误差
obj_pts = np.random.randn(20,3).astype(np.float32)
K = np.array([[500,0,320],[0,500,240],[0,0,1]], dtype=np.float32)
rvec = np.array([0.1, 0.2, 0.05], dtype=np.float32)
tvec = np.array([0, 0, 5], dtype=np.float32)
dist = np.zeros(5)
img_proj, _ = cv2.projectPoints(obj_pts, rvec, tvec, K, dist)
img_proj += np.random.randn(*img_proj.shape) * 0.5 # 加噪声
success, rvec2, tvec2 = cv2.solvePnP(obj_pts, img_proj, K, dist)
img_reproj, _ = cv2.projectPoints(obj_pts, rvec2, tvec2, K, dist)
reproj_err = np.mean(np.sqrt(np.sum((img_proj - img_reproj)**2, axis=2)))
print(f'重投影误差: {reproj_err:.3f}px')
标定实战
import cv2, numpy as np
# 完整标定流程模拟
objp = np.zeros((6*9,3), dtype=np.float32)
objp[:,:2] = np.mgrid[0:9,0:6].T.reshape(-1,2) * 25 # 25mm方格
K = np.array([[800,0,320],[0,800,240],[0,0,1]], dtype=np.float32)
dist = np.array([0.1, -0.05, 0, 0, 0], dtype=np.float32)
obj_points, img_points = [], []
for i in range(10):
rvec = np.random.randn(3).astype(np.float32) * 0.3
tvec = np.array([0, 0, 500], dtype=np.float32) + np.random.randn(3).astype(np.float32) * 10
pts, _ = cv2.projectPoints(objp, rvec, tvec, K, dist)
obj_points.append(objp)
img_points.append(pts)
ret, K_est, dist_est, rvecs, tvecs = cv2.calibrateCamera(obj_points, img_points, (640,480), None, None)
print(f'标定rms: {ret:.4f}')
print(f'焦距估计: fx={K_est[0,0]:.1f}, fy={K_est[1,1]:.1f}')
print(f'真实焦距: fx=800, fy=800')
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 方法A | 简单高效 | 精度有限 | 快速原型 |
| 方法B | 精度高 | 计算量大 | 离线处理 |
| 方法C | 平衡精度与速度 | 参数调优复杂 | 实际应用 |
本课涉及的核心数学公式汇总,方便快速参考:
本节深入探讨相机标定的进阶话题和工程实践中的关键考虑。
# 性能基准测试模板
import time, numpy as np
def benchmark(func, *args, n=100, **kwargs):
times = []
for _ in range(n):
t0 = time.time()
result = func(*args, **kwargs)
times.append(time.time() - t0)
return np.mean(times)*1000, np.std(times)*1000
print(f"性能基准测试: 多次运行取均值和标准差")
print(f"注意: 首次运行可能较慢(JIT编译/缓存加载)")
| 优化方向 | 方法 | 精度影响 | 加速比 |
|---|---|---|---|
| 模型压缩 | 剪枝/蒸馏 | 1-3%下降 | 2-4x |
| 量化 | INT8/FP16 | <1%下降 | 2-8x |
| 算子融合 | TensorRT/ONNX | 无 | 1.5-3x |
| 批处理 | 增大batch size | 无 | 线性 |
以下是一个完整的相机标定处理管道,从数据准备到结果评估,包含所有关键步骤和参数调优建议:
import cv2
import numpy as np
import time
class 相机标定Pipeline:
"""完整的相机标定处理管道"""
def __init__(self, params=None):
self.params = params or {}
self.results = {}
def preprocess(self, image):
"""预处理步骤"""
# 1. 去噪
if self.params.get('denoise', True):
image = cv2.GaussianBlur(image, (3, 3), 1.0)
# 2. 对比度增强
if self.params.get('enhance', False):
if len(image.shape) == 2:
image = cv2.equalizeHist(image)
else:
lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
lab[:,:,0] = cv2.equalizeHist(lab[:,:,0])
image = cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)
# 3. 尺寸调整
max_size = self.params.get('max_size', 1024)
h, w = image.shape[:2]
if max(h, w) > max_size:
scale = max_size / max(h, w)
image = cv2.resize(image, (int(w*scale), int(h*scale)))
return image
def process(self, image):
"""核心处理步骤"""
t0 = time.time()
preprocessed = self.preprocess(image)
t1 = time.time()
# 主处理逻辑
result = self._main_process(preprocessed)
t2 = time.time()
self.results['timing'] = {
'preprocess': (t1-t0)*1000,
'process': (t2-t1)*1000,
'total': (t2-t0)*1000
}
return result
def _main_process(self, image):
"""主处理逻辑(子类可重写)"""
return image
def evaluate(self, result, ground_truth=None):
"""评估处理结果"""
metrics = {}
if ground_truth is not None:
if len(result.shape) == 2:
mse = np.mean((result.astype(float) - ground_truth.astype(float))**2)
metrics['mse'] = mse
metrics['psnr'] = 10 * np.log10(255**2 / (mse + 1e-10))
metrics['timing'] = self.results.get('timing', {})
return metrics
def visualize(self, image, result):
"""可视化对比"""
if len(image.shape) == 2:
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
if len(result.shape) == 2:
result = cv2.cvtColor(result, cv2.COLOR_GRAY2BGR)
h1, w1 = image.shape[:2]
h2, w2 = result.shape[:2]
h, w = max(h1,h2), w1+w2+10
vis = np.zeros((h, w, 3), dtype=np.uint8)
vis[:h1, :w1] = image
vis[:h2, w1+10:] = result
cv2.putText(vis, 'Input', (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,0), 2)
cv2.putText(vis, 'Output', (w1+20, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,0), 2)
return vis
# 使用示例
pipeline = 相机标定Pipeline({'denoise': True, 'enhance': False, 'max_size': 512})
test_img = np.random.randint(0, 256, (256, 256, 3), dtype=np.uint8)
result = pipeline.process(test_img)
metrics = pipeline.evaluate(result)
print(f"处理时间: {metrics['timing'].get('total', 0):.1f}ms")
不同参数对结果的影响:
建议从默认参数开始,根据结果逐步调整。先在少量数据上快速迭代,确定参数后再全量处理。
import numpy as np
class Metrics:
@staticmethod
def mse(img1, img2):
return np.mean((img1.astype(float) - img2.astype(float))**2)
@staticmethod
def psnr(img1, img2, max_val=255):
mse_val = Metrics.mse(img1, img2)
if mse_val == 0: return float('inf')
return 10 * np.log10(max_val**2 / mse_val)
@staticmethod
def iou(mask1, mask2):
inter = np.logical_and(mask1 > 0, mask2 > 0).sum()
union = np.logical_or(mask1 > 0, mask2 > 0).sum()
return inter / (union + 1e-10)
@staticmethod
def f1_score(pred, gt, threshold=0.5):
pred_bin = (pred > threshold)
gt_bin = (gt > 0)
tp = np.logical_and(pred_bin, gt_bin).sum()
precision = tp / (pred_bin.sum() + 1e-10)
recall = tp / (gt_bin.sum() + 1e-10)
return 2 * precision * recall / (precision + recall + 1e-10)
# 示例
img1 = np.random.randint(0, 256, (100,100), dtype=np.uint8)
img2 = img1 + np.random.randint(-10, 10, (100,100))
img2 = np.clip(img2, 0, 255).astype(np.uint8)
print(f"MSE: {Metrics.mse(img1,img2):.2f}")
print(f"PSNR: {Metrics.psnr(img1,img2):.2f} dB")
print(f"说明: PSNR>30dB为良好, >40dB为优秀")
本课所学技术在实际工程中有广泛的应用。以下是几个典型场景:
在制造业中,计算机视觉技术被用于产品缺陷检测。关键挑战包括:缺陷样本少(需要数据增强或异常检测方法)、实时性要求高(流水线速度)、光照变化大。
# 工业质检示例
import cv2, numpy as np
# 模拟产品表面
product = np.full((200,200), 180, dtype=np.uint8)
# 添加划痕缺陷
cv2.line(product, (30,80), (170,120), 50, 2)
# 缺陷检测
blurred = cv2.GaussianBlur(product, (5,5), 1.0)
diff = cv2.absdiff(product, blurred)
_, defects = cv2.threshold(diff, 15, 255, cv2.THRESH_BINARY)
defect_area = np.count_nonzero(defects)
print(f"缺陷面积: {defect_area}px, 缺陷率: {defect_area/product.size:.4%}")
is_defective = defect_area > 50
print(f"检测结果: {'不合格' if is_defective else '合格'}")自动驾驶需要实时处理多种视觉任务:车道检测、目标检测、语义分割。延迟要求<50ms,且需要处理各种天气和光照条件。
# 车道检测示例
import cv2, numpy as np
road = np.zeros((480,640), dtype=np.uint8)
# 模拟道路
cv2.fillPoly(road, [np.array([[200,480],[440,480],[350,200],[290,200]])], 100)
# 模拟车道线
cv2.line(road, (280,480), (320,200), 255, 3)
cv2.line(road, (360,480), (320,200), 255, 3)
# 车道检测
edges = cv2.Canny(road, 50, 150)
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 50, minLineLength=100, maxLineGap=50)
print(f"检测到车道线: {len(lines) if lines is not None else 0} 条")医学影像分析需要高精度和高可靠性。常见应用包括:CT/MRI分割、X光异常检测、病理图像分析。关键要求:误诊率极低、可解释性强、符合医疗法规。
# 简单医学分割
import cv2, numpy as np
# 模拟CT扫描
ct = np.zeros((256,256), dtype=np.uint8)
cv2.ellipse(ct, (128,128), (60,50), 0, 0, 360, 150, -1) # 器官
cv2.circle(ct, (140,120), 15, 200, -1) # 病灶
# Otsu分割
_, mask = cv2.threshold(ct, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
print(f"检测到 {len(contours)} 个区域")
for c in contours:
area = cv2.contourArea(c)
if area > 100:
print(f" 区域面积: {area}, 疑似病灶: {area < 1000}")你已经掌握了相机标定的核心知识,继续前进!