feat(camera): 添加相机标定与运动估计功能
- 新增 camera_calibration.py 文件,实现相机标定功能 - 新增 motion_estimation.py 文件,实现运动估计功能- 编写实验报告,总结相机标定与运动估计的原理和实验结果
This commit is contained in:
commit
39d6065521
56
camera_calibration.py
Normal file
56
camera_calibration.py
Normal file
@ -0,0 +1,56 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import glob
|
||||
|
||||
# 棋盘格尺寸:内角点数量(宽度,高度)
|
||||
chessboard_size = (9, 6)
|
||||
|
||||
# 创建棋盘格世界坐标系下的三维点阵
|
||||
# 生成形如(0,0,0), (1,0,0)...(8,5,0)的三维坐标
|
||||
objp = np.zeros((chessboard_size[0] * chessboard_size[1], 3), np.float32)
|
||||
objp[:, :2] = np.mgrid[0:chessboard_size[0], 0:chessboard_size[1]].T.reshape(-1, 2)
|
||||
|
||||
# 存储对象点和图像点的列表
|
||||
objpoints = [] # 世界坐标系中的3D点
|
||||
imgpoints = [] # 图像坐标系中的2D点
|
||||
|
||||
# 加载所有棋盘格图像路径
|
||||
images = glob.glob('images/*.jpg')
|
||||
|
||||
for fname in images:
|
||||
img = cv2.imread(fname)
|
||||
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
# 寻找棋盘格角点
|
||||
ret, corners = cv2.findChessboardCorners(gray, chessboard_size, None)
|
||||
|
||||
if ret:
|
||||
objpoints.append(objp)
|
||||
imgpoints.append(corners)
|
||||
|
||||
# 绘制并显示检测到的角点
|
||||
cv2.drawChessboardCorners(img, chessboard_size, corners, ret)
|
||||
cv2.imshow('Corners', img)
|
||||
cv2.waitKey(0) # 修改此处:改为等待用户按键关闭窗口
|
||||
|
||||
# 销毁所有OpenCV创建的窗口
|
||||
cv2.destroyAllWindows()
|
||||
|
||||
if len(objpoints) > 0:
|
||||
# 执行相机标定,获取相机矩阵、畸变系数等参数
|
||||
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
|
||||
|
||||
# 输出相机矩阵和畸变系数
|
||||
print("相机矩阵:\n", mtx)
|
||||
print("畸变系数:\n", dist)
|
||||
|
||||
# 计算重投影误差
|
||||
mean_error = 0
|
||||
for i in range(len(objpoints)):
|
||||
imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
|
||||
error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
|
||||
mean_error += error
|
||||
|
||||
print("平均重投影误差: ", mean_error / len(objpoints))
|
||||
else:
|
||||
print("未找到有效的棋盘格图像。")
|
BIN
images/calib_1.jpg
Normal file
BIN
images/calib_1.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
BIN
images/left.jpg
Normal file
BIN
images/left.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
images/right.jpg
Normal file
BIN
images/right.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
55
motion_estimation.py
Normal file
55
motion_estimation.py
Normal file
@ -0,0 +1,55 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
# 读取左右视角图像(灰度模式)
|
||||
img1 = cv2.imread('images/left.jpg', 0)
|
||||
img2 = cv2.imread('images/right.jpg', 0)
|
||||
|
||||
# 初始化SIFT特征检测器
|
||||
sift = cv2.SIFT_create() # 创建SIFT对象
|
||||
|
||||
# 检测关键点并计算描述符
|
||||
kp1, des1 = sift.detectAndCompute(img1, None)
|
||||
kp2, des2 = sift.detectAndCompute(img2, None)
|
||||
|
||||
# FLANN匹配器参数配置
|
||||
FLANN_INDEX_KDTREE = 1
|
||||
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5) # KD树算法参数
|
||||
search_params = dict(checks=50) # 搜索参数:检查次数
|
||||
|
||||
# 创建FLANN匹配器实例
|
||||
flann = cv2.FlannBasedMatcher(index_params, search_params)
|
||||
matches = flann.knnMatch(des1, des2, k=2) # k近邻匹配
|
||||
|
||||
pts1 = []
|
||||
pts2 = []
|
||||
good_matches = [] # 初始化优质匹配列表
|
||||
|
||||
# 使用Lowe's比率测试筛选优质匹配
|
||||
for i, (m, n) in enumerate(matches):
|
||||
if m.distance < 0.7 * n.distance: # 修改此处:调整距离比阈值
|
||||
good_matches.append(m)
|
||||
pts2.append(kp2[m.trainIdx].pt) # 记录右图匹配点坐标
|
||||
pts1.append(kp1[m.queryIdx].pt) # 记录左图匹配点坐标
|
||||
|
||||
if len(good_matches) > 8:
|
||||
pts1 = np.int32(pts1)
|
||||
pts2 = np.int32(pts2)
|
||||
|
||||
# 计算基础矩阵(使用RANSAC算法)
|
||||
F, mask = cv2.findFundamentalMat(pts1, pts2, cv2.FM_RANSAC)
|
||||
print("基础矩阵:\n", F)
|
||||
|
||||
# 通过基础矩阵恢复旋转和平移变换
|
||||
_, R, t, _ = cv2.recoverPose(F, pts1, pts2)
|
||||
print("旋转矩阵:\n", R)
|
||||
print("平移向量:\n", t)
|
||||
|
||||
# 可视化优质匹配结果
|
||||
img_match = cv2.drawMatches(img1, kp1, img2, kp2, good_matches, None,
|
||||
flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
|
||||
cv2.imshow('特征匹配', img_match)
|
||||
cv2.waitKey(0)
|
||||
cv2.destroyAllWindows()
|
||||
else:
|
||||
print("匹配点不足,无法计算基础矩阵。")
|
43
实验报告_相机标定与运动估计.md
Normal file
43
实验报告_相机标定与运动估计.md
Normal file
@ -0,0 +1,43 @@
|
||||
# 计算机视觉实验报告:相机标定与运动估计
|
||||
|
||||
## 一、 实验目的
|
||||
1. 了解相机标定和运动估计的基本原理;
|
||||
2. 了解相机标定和运动估计的应用;
|
||||
3. 掌握相机标定和运动估计实现方法。
|
||||
|
||||
## 二、 基本思想
|
||||
### 相机标定
|
||||
相机标定旨在计算相机的内部参数(焦距、主点坐标等)和外部参数(位置和方向),以及校正镜头畸变。使用棋盘格图案作为标定物,通过已知的3D-2D对应关系来求解相机矩阵和畸变系数。
|
||||
|
||||
### 运动估计
|
||||
运动估计是通过分析连续图像帧之间的特征匹配,确定两个视图之间的相对旋转和平移变换。这通常涉及基础矩阵或本质矩阵的计算,从而恢复场景的三维结构和相机运动。
|
||||
|
||||
## 三、 源码
|
||||
### 相机标定代码
|
||||
```python
|
||||
# 见camera_calibration.py文件
|
||||
```
|
||||
|
||||
### 运动估计代码
|
||||
```python
|
||||
# 见motion_estimation.py文件
|
||||
```
|
||||
|
||||
## 四、 算法分析
|
||||
### 时间复杂度
|
||||
- **相机标定**:主要耗时在角点检测O(N)和线性优化迭代求解参数,整体近似为O(N·T),其中N为像素数,T为迭代次数。
|
||||
- **运动估计**:SIFT特征提取为O(M), 特征匹配为O(M²),RANSAC筛选为O(K)。总体时间复杂度约为O(M²+K)。
|
||||
|
||||
### 空间复杂度
|
||||
- **相机标定**:存储对象点和图像点的数组为O(P),P为角点数目。
|
||||
- **运动估计**:保存关键点和描述符的空间为O(Q), Q为特征数量。
|
||||
|
||||
## 五、 实验运行结果图
|
||||
### 相机标定角点检测示例
|
||||

|
||||
|
||||
### 运动估计特征匹配图
|
||||

|
||||
|
||||
## 六、 实验总结
|
||||
这里可以添加你的实验心得和遇到的问题及解决方案。
|
Loading…
Reference in New Issue
Block a user