0. 内容
可用于相机标定图片筛选
相机标定
三维点重投影会二维图像
绘制Z平面以及连接线
注意:该标定方法适用于畸变较小得普通相机,鱼眼相机标定如下:Opencv相机标定(2):鱼眼相机校正_m0_58772523的博客-CSDN博客...
1. 图片筛选
利用opencv中findChessboardCorners判断图片是否可以检测到对应角点,若检测到角点ret值为True,当检测不到角点时删除对应图片。
import os
import cv2
import glob
def delete_pic(inter_corner_shape, img_dir, img_type):
images = glob.glob(img_dir + os.sep + '**.' + img_type)
for fname in images:
img = cv2.imread(fname)
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, cp_img = cv2.findChessboardCorners(gray_img,
(inter_corner_shape[0], inter_corner_shape[1]), None)
if not ret:
os.remove(fname)
if __name__ == '__main__':
inter_corner_shape = (9, 6)
img_dir = "./data"
img_type = "jpg"
delete_pic(inter_corner_shape, img_dir, img_type)
2. 线条和平面绘制辅助函数(含详细注释)
# 二维图片, 图片角点坐标, 所需投影的三维点在图像中的投影点坐标
def draw(img, corners, imgpts):
# 获取第一个图像角点信息
corner = tuple(corners[0].ravel().astype(np.int64))
# 利用第一个图像角点坐标和投影点的坐标绘制想要的直线和平面
# np.ravel()函数为多维数组的维度降为一维
# 新版本的opencv在绘制直线和平面时需手动将坐标信息转为整型,老版本绘制直线时不需要(测试发现的)
img = cv2.drawContours(img, [np.array([list(corners[0].ravel()), list(imgpts[1].ravel()),
list(imgpts[2].ravel()), list(imgpts[3].ravel())]).astype(np.int64)], -1,
(175, 0, 175), -3)
img = cv2.line(img, corner, tuple(imgpts[0].ravel().astype(np.int64)), (255, 0, 0), 5)
img = cv2.line(img, corner, tuple(imgpts[1].ravel().astype(np.int64)), (0, 255, 0), 5)
# img = cv2.line(img, corner, tuple(imgpts[2].ravel().astype(np.int64)), (0, 0, 255), 5)
img = cv2.line(img, corner, tuple(imgpts[3].ravel().astype(np.int64)), (0, 0, 255), 5)
return img
3. 主函数(相机标定:获取内参、畸变系数,外参<标定阶段没太大作用>)
# 定义角点数量, 棋盘格尺寸(应该可以随意写,不影响参数标定),图像路径, 图像类型
def calib(inter_corner_shape, size_per_grid, img_dir, img_type):
# 生成角点的世界坐标系坐标
# np.mgrid() 返回多维结构,具体功能建议自行尝试
w, h = inter_corner_shape
cp_int = np.zeros((w * h, 3), np.float32)
cp_int[:, :2] = np.mgrid[0:w, 0:h].T.reshape(-1, 2)
cp_world = cp_int * size_per_grid
obj_points = []
img_points = []
# 角点提取
images = glob.glob(img_dir + os.sep + '**.' + img_type)
for fname in images:
img = cv2.imread(fname)
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 得到是否有角点以及角点
ret, cp_img = cv2.findChessboardCorners(gray_img, (w, h), None)
if ret:
# 得到优化后的角点
cp_img = cv2.cornerSubPix(gray_img, cp_img, (11, 11), (-1, -1), criteria)
obj_points.append(cp_world)
img_points.append(cp_img)
# 绘制角点,可以检查角点提取是否准确
cv2.drawChessboardCorners(img, (w, h), cp_img, ret)
cv2.imshow('FoundCorners', img)
cv2.waitKey(100)
cv2.destroyAllWindows()
# 相机标定
# 参数:世界坐标系坐标,角点坐标,图像,None,None(标定方法,默认)
# 需要注意:opencv中图像为宽、高,与numpy的读取方式不同
# 传参数应依次传numpy的1, 0两个维度
_, mat_inter, coff_dis, v_rot, v_trans = cv2.calibrateCamera(obj_points,
img_points, gray_img.shape[::-1], None, None)
# 内参
print("internal matrix:\n", mat_inter)
# 畸变系数
print("distortion cofficients:\n", coff_dis)
# 旋转向量
# print("rotation vectors:\n", v_rot)
# 平移向量
# print("translation vectors:\n", v_trans)
# 将世界坐标系的点投影回二维平面,计算投影点与角点的距离误差
total_error = 0
for i in range(len(obj_points)):
img_points_repro, _ = cv2.projectPoints(obj_points[i], v_rot[i], v_trans[i], mat_inter, coff_dis)
error = cv2.norm(img_points[i], img_points_repro, cv2.NORM_L2) / len(img_points_repro)
total_error += error
print(("Average Error of Reproject: "), total_error / len(obj_points))
return mat_inter, coff_dis, cp_world
4. 实现(含详细注释)
if __name__ == '__main__':
# 定义角点数量
inter_corner_shape = (9, 6)
# 定义棋盘格尺寸
size_per_grid = 0.02
# 定义图片路径
img_dir = "./data"
# 定义图片格式
img_type = "jpg"
# 相机标定
mat_inter, coff_dis, cp_world = calib(inter_corner_shape, size_per_grid, img_dir, img_type)
# 读取图片测试标定效果
img = cv2.imread("./data/test01.jpg")
h, w = img.shape[:2]
# new为优化参数矩阵,roi为opencv认为的感兴趣区域,可用于去畸变后的黑色边缘裁剪
# 当参数1改为0时,opencv会对图像自动裁剪
# 注意w和h
new, roi = cv2.getOptimalNewCameraMatrix(mat_inter, coff_dis, (w, h), 1, (w, h))
x, y, w, h = roi
print(roi)
# 图像去畸变,方法一
dst = cv2.undistort(img, mat_inter, coff_dis, None, new)
# 图像去畸变,方法二,有点点问题,可以自己尝试
# mapx, mapy = cv2.initUndistortRectifyMap(mat_inter, coff_dis, None, new, (w, h), 5)
# dst = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)
cv2.namedWindow('origin', cv2.WINDOW_NORMAL)
cv2.namedWindow('no_cut', cv2.WINDOW_NORMAL)
cv2.namedWindow('cut', cv2.WINDOW_NORMAL)
cv2.namedWindow('img_with_line', cv2.WINDOW_NORMAL)
cv2.imshow('origin', img)
cv2.imshow('cut', dst[y: y+h, x: x+w])
cv2.imshow('no_cut', dst)
# 可视化连线,流程如下
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
# 找角点 -> 优化角点 -> 获取外参 -> 外参矩阵变换 -> 选择要绘制的世界坐标系点 -> 得到世界坐标系点在图像上的投影点 -> 绘制连线
ok, corners = cv2.findChessboardCorners(gray, (inter_corner_shape[0], inter_corner_shape[1]), )
if ok:
exact_corners = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
_, rvec, tvec, inliers = cv2.solvePnPRansac(cp_world, exact_corners, mat_inter, coff_dis)
axis = 0.02 * np.float32([[0, 0, -8], [8, 0, 0], [8, 5, 0], [0, 5, 0]]).reshape(-1, 3)
imgpts, _ = cv2.projectPoints(axis, rvec, tvec, mat_inter, coff_dis)
img = draw(img, corners, imgpts)
cv2.imshow('img_with_line', img)
cv2.waitKey(0)
5. 结果展示
6. 代码链接
360sorround/calibration at main · liuweixue001/360sorround (github.com)
7. 后续更新
1. 图像去畸变、鸟瞰图生成、图像拼接、图像融合:liuweixue001/360sorround: 360环视--相机校正->畸变处理->俯视变换->图像拼接 (github.com)
2. C++版本相关工作:
liuweixue001/360surround-2.0-: 一个简单案例,包含:图像去畸变、俯视变换、图像拼接和融合 (github.com)