NMV 相机标定工具
海康威视工业相机(MVS SDK)驱动及内参标定完整流程。
环境要求
- Linux aarch64(推荐 ARM 架构)
- Python 3.x
- OpenCV (
pip install opencv-python) - 海康 MVS SDK(arm64 版)
目录
1. MVS SDK 安装
下载 ARM 版本 SDK:
https://www.hikrobotics.com/cn/machinevision/service/download/?module=0
使用 .deb 包安装。
安装完成后,SDK 路径为 /opt/MVS/。
2. 相机驱动
CAM.py 是海康相机的 Python 驱动封装,接口与 cv2.VideoCapture 一致。
核心功能
| 方法 | 说明 |
|---|---|
read() |
等价于 cv2.VideoCapture.read(),返回 (ret, frame),frame 为 BGR 格式 numpy 数组 |
start() |
开始采集 |
release() |
释放相机资源 |
set(prop, value) |
设置参数(曝光、增益、帧率等) |
set_exposure(us) |
设置曝光时间(微秒),自动关闭自动曝光 |
set_gain(gain) |
设置增益(dB),自动关闭自动增益 |
set_auto_exposure(enable) |
开启/关闭自动曝光 |
get_exposure() |
获取当前曝光时间 |
get_gain() |
获取当前增益 |
cv2.VideoCapture 属性映射
| OpenCV 属性 | 相机参数 | 说明 |
|---|---|---|
CAP_PROP_FRAME_WIDTH (3) |
Width | 图像宽度 |
CAP_PROP_FRAME_HEIGHT (4) |
Height | 图像高度 |
CAP_PROP_EXPOSURE (9) |
ExposureTime | 曝光时间(微秒) |
CAP_PROP_GAIN (11) |
Gain | 增益(dB) |
CAP_PROP_FPS (15) |
FrameRate | 帧率 |
使用示例
python
from CAM import HikCamera
import cv2
capture = HikCamera(index=0)
# 手动设置曝光(微秒)
capture.set_exposure(50000) # 50ms 曝光
# capture.set_gain(10.0) # 10dB 增益
print(f"Exposure: {capture.get_exposure()} us")
print(f"Gain: {capture.get_gain()} dB")
while True:
ret, frame = capture.read()
if ret and frame is not None:
cv2.imshow("HikCamera", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
capture.release()
cv2.destroyAllWindows()
3. 拍摄采集
test.py 用于实时预览并保存标定图像。按 s 保存当前帧,按 q 退出。
python
from CAM import HikCamera
import cv2
from datetime import datetime
capture = HikCamera(index=0)
# 设置曝光和增益
capture.set_exposure(30000) # 30ms 曝光
capture.set_gain(20.0) # 20dB 增益
while True:
ret, frame = capture.read()
if ret:
cv2.imshow("Camera", frame)
key = cv2.waitKey(1) & 0xFF
if key == ord('s'):
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"photo_{timestamp}.jpg"
cv2.imwrite(filename, frame)
print(f"已保存: {filename}")
elif key == ord('q'):
break
capture.release()
cv2.destroyAllWindows()
4. 标定板生成
生成指定尺寸的棋盘格标定板,保存为 chessboard.png。
参数说明
| 参数 | 默认值 | 说明 |
|---|---|---|
pattern_size |
(9, 6) |
内角点数量(列, 行) |
square_size |
30 mm |
每个格子的物理尺寸 |
image_size |
(330, 270) |
输出图像尺寸(宽, 高) |
代码
python
import cv2
import numpy as np
pattern_size = (9, 6) # 内角点数量(列, 行)
square_size = 30 # 每个格子的物理尺寸(毫米)
image_size = (330, 270) # 图像尺寸(宽, 高)
board_w = (pattern_size[0] + 1) * square_size
board_h = (pattern_size[1] + 1) * square_size
offset_x = (image_size[0] - board_w) // 2
offset_y = (image_size[1] - board_h) // 2
board = np.full((image_size[1], image_size[0]), 255, dtype=np.uint8)
for row in range(pattern_size[1] + 1):
for col in range(pattern_size[0] + 1):
if (row + col) % 2 == 0:
x1 = offset_x + col * square_size
y1 = offset_y + row * square_size
x2 = offset_x + (col + 1) * square_size
y2 = offset_y + (row + 1) * square_size
cv2.rectangle(board, (x1, y1), (x2, y2), 0, -1)
cv2.imwrite("chessboard.png", board)
print(f"标定板尺寸: {board_w}x{board_h}px, 居中于 {image_size[0]}x{image_size[1]}px 图像中")
print(f"物理尺寸: {pattern_size[0]+1}x{pattern_size[1]+1} 格子, 每格 {square_size}mm")
5. 多角度拍摄
使用第 3 步的拍摄代码,对打印出的棋盘格标定板拍摄 20~30 张不同角度和距离的照片,建议:
- 包含棋盘格位于画面各区域的照片
- 包含不同倾斜角度(俯仰、偏航、翻滚)的照片
- 覆盖画面中心和边缘
- 棋盘格占画面 30%~70% 为宜
建议保存路径:/home/a1/FAST-LIVO-/NMV/Desktop/
6. 内参标定
运行标定代码,从 Desktop/ 目录读取图像,计算相机内参和畸变系数。
python
import cv2
import numpy as np
import glob
# 标定板参数(必须与 creat.py 保持一致)
pattern_size = (9, 6) # 内角点(列, 行)
square_size = 30 # 每格物理尺寸(毫米)
# 准备世界坐标系中的点
objp = np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2)
objp *= square_size
objpoints = [] # 3d 点
imgpoints = [] # 2d 点
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
images = glob.glob(r'/home/a1/FAST-LIVO-/NMV/Desktop/*.jpg')
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)
if ret:
objpoints.append(objp.copy())
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
imgpoints.append(corners2)
if len(objpoints) == 0:
print("错误: 未找到有效的标定图像")
else:
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints,
(gray.shape[1], gray.shape[0]),
None, None
)
mean_error = 0
for i in range(len(objpoints)):
imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
mean_error += cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints[i])
mean_error /= len(objpoints)
print(f"内参矩阵 mtx:\n{mtx}")
print(f"畸变系数 dist:\n{dist}")
print(f"重投影误差: {mean_error:.4f} 像素")
print(f"共 {len(objpoints)} 张图像标定成功")
np.savez('calib_result.npz', mtx=mtx, dist=dist, rvecs=rvecs, tvecs=tvecs)
print("标定结果已保存到 calib_result.npz")
7. 标定结果
典型标定输出示例:
内参矩阵 mtx:
[[1.30604136e+03 0.00000000e+00 6.47029232e+02]
[0.00000000e+00 1.30584980e+03 4.84674313e+02]
[0.00000000e+00 0.00000000e+00 1.00000000e+00]]
畸变系数 dist:
[[-2.84377903e-01 2.83396706e+00 -1.15991120e-04 2.67718470e-03
-1.09059273e+01]]
重投影误差: 0.0597 像素
共 29 张图像标定成功
标定结果已保存到 calib_result.npz
参数解读
- 内参矩阵
mtx:相机固有参数,(fx, fy)为焦距,(cx, cy)为主点坐标 - 畸变系数
dist:径向畸变k1, k2, k3和切向畸变p1, p2 - 重投影误差 :越小越好,一般
< 1 像素可接受
文件结构
NMV/
├── CAM.py # 海康相机驱动
├── test.py # 采集拍摄脚本
├── creat.py # 棋盘格生成脚本
├── calibrate.py # 标定脚本
├── calib_result.npz # 标定结果(输出)
└── Desktop/ # 标定图像存放目录
└── *.jpg
感谢王同学的倾情制作