2024-11-05 21:17:00 +08:00
|
|
|
|
import os.path as osp
|
|
|
|
|
import glob
|
|
|
|
|
import cv2 as cv
|
|
|
|
|
import numpy as np
|
2024-11-06 20:55:17 +08:00
|
|
|
|
import datetime
|
2024-11-05 21:17:00 +08:00
|
|
|
|
import argparse
|
2024-11-21 22:39:22 +08:00
|
|
|
|
from tqdm import tqdm
|
2024-12-05 21:27:45 +08:00
|
|
|
|
from calib_tools import write_json, read_json
|
|
|
|
|
from calib_tools import read_img_paths, create_output_folder
|
|
|
|
|
from calib_tools import DataPath
|
2024-11-05 21:25:01 +08:00
|
|
|
|
|
2024-11-21 22:39:22 +08:00
|
|
|
|
|
2024-12-05 21:27:45 +08:00
|
|
|
|
def format_intri_json_data(mtx, dist, image_shape, error):
|
2024-11-06 20:55:17 +08:00
|
|
|
|
data = {
|
|
|
|
|
"time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
|
|
|
"K": mtx.tolist(),
|
|
|
|
|
"dist": dist.tolist(),
|
2024-11-21 22:39:22 +08:00
|
|
|
|
"image_shape": image_shape,
|
|
|
|
|
"error": error
|
2024-11-06 20:55:17 +08:00
|
|
|
|
}
|
|
|
|
|
return data
|
2024-11-05 21:17:00 +08:00
|
|
|
|
|
2024-11-21 22:39:22 +08:00
|
|
|
|
def calibrate_camera(camera, chessboardSize, squareSize, visualization):
|
2024-11-06 20:55:17 +08:00
|
|
|
|
# 设置输出目录
|
|
|
|
|
if visualization:
|
2024-12-05 21:27:45 +08:00
|
|
|
|
outputFolder = create_output_folder(DataPath.intri_chessboard_vis, osp.basename(camera))
|
2024-11-06 20:55:17 +08:00
|
|
|
|
|
|
|
|
|
# 图片路径
|
2024-11-21 22:39:22 +08:00
|
|
|
|
imgPaths = read_img_paths(camera)
|
2024-11-05 21:17:00 +08:00
|
|
|
|
if len(imgPaths) == 0:
|
2024-11-21 22:39:22 +08:00
|
|
|
|
print("No images found!\n")
|
2024-11-05 21:17:00 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 存储世界坐标和像素坐标
|
|
|
|
|
# 计算出棋盘格中每个网格角点的坐标,之后当成世界坐标
|
|
|
|
|
board_w, board_h = chessboardSize
|
|
|
|
|
board_grid = np.zeros((board_w * board_h, 3), np.float32)
|
|
|
|
|
board_grid[:, :2] = np.mgrid[0:board_w, 0:board_h].T.reshape(-1, 2) * squareSize
|
|
|
|
|
|
|
|
|
|
pointsWorld = []
|
|
|
|
|
pointsPixel = []
|
|
|
|
|
|
|
|
|
|
# 遍历图片
|
|
|
|
|
for imgPath in imgPaths:
|
|
|
|
|
img = cv.imread(imgPath)
|
|
|
|
|
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
|
|
|
|
|
|
|
|
|
|
# 查找角点
|
|
|
|
|
ret, corners = cv.findChessboardCorners(gray, (board_w, board_h), None)
|
|
|
|
|
if ret:
|
2024-11-05 21:25:01 +08:00
|
|
|
|
cv.cornerSubPix(gray, corners, (11, 11), (-1, -1),
|
|
|
|
|
(cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001))
|
2024-11-05 21:17:00 +08:00
|
|
|
|
pointsWorld.append(board_grid)
|
|
|
|
|
pointsPixel.append(corners)
|
2024-11-06 20:55:17 +08:00
|
|
|
|
if visualization:
|
|
|
|
|
cv.drawChessboardCorners(img, (board_w, board_h), corners, ret)
|
|
|
|
|
cv.imwrite(osp.join(outputFolder, osp.basename(imgPath)), img)
|
2024-11-05 21:17:00 +08:00
|
|
|
|
|
|
|
|
|
# 标定相机
|
2024-11-06 20:55:17 +08:00
|
|
|
|
image_shape = gray.shape[::-1]
|
|
|
|
|
ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(pointsWorld, pointsPixel, image_shape, None, None)
|
2024-11-21 22:39:22 +08:00
|
|
|
|
# print("Intrinsic matrix:\n", mtx.astype(np.float32))
|
|
|
|
|
# print("Distortion coefficients:\n", dist.astype(np.float32))
|
2024-11-05 21:17:00 +08:00
|
|
|
|
|
|
|
|
|
# 计算重投影误差
|
|
|
|
|
nimg = len(pointsWorld)
|
|
|
|
|
img_error = np.zeros(nimg)
|
|
|
|
|
for i in range(nimg):
|
|
|
|
|
imgpoints2, _ = cv.projectPoints(pointsWorld[i], rvecs[i], tvecs[i], mtx, dist)
|
|
|
|
|
error = cv.norm(pointsPixel[i], imgpoints2, cv.NORM_L2) / len(imgpoints2)
|
|
|
|
|
img_error[i] = error
|
|
|
|
|
|
2024-11-06 20:55:17 +08:00
|
|
|
|
# good_img = np.where(img_error < 0.5)[0]
|
|
|
|
|
# mean_error = np.mean(img_error[good_img])
|
|
|
|
|
mean_error = np.mean(img_error)
|
2024-11-21 22:39:22 +08:00
|
|
|
|
print("Reprojection error: ", mean_error)
|
2024-11-05 21:17:00 +08:00
|
|
|
|
|
|
|
|
|
# 挑选出重投影误差小于1.0的图片,重新标定相机
|
2024-11-06 20:55:17 +08:00
|
|
|
|
# if len(good_img) == 0:
|
|
|
|
|
# print("No images with error < 0.5")
|
|
|
|
|
# elif len(good_img) == nimg:
|
|
|
|
|
# print("All images have error < 0.5")
|
|
|
|
|
# pass
|
|
|
|
|
# else:
|
|
|
|
|
# pointsWorld2 = [pointsWorld[i] for i in good_img]
|
|
|
|
|
# pointsPixel2 = [pointsPixel[i] for i in good_img]
|
|
|
|
|
# ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(pointsWorld2, pointsPixel2, image_shape, None, None)
|
|
|
|
|
# print("Intrinsic matrix:\n", mtx.astype(np.float32))
|
|
|
|
|
# print("Distortion coefficients:\n", dist.astype(np.float32))
|
|
|
|
|
|
2024-11-21 22:39:22 +08:00
|
|
|
|
# data = format_json_data(mtx, dist, image_shape)
|
|
|
|
|
# # 在文件夹根目录下保存相机内参
|
|
|
|
|
# outputJsonPath = osp.join(baseFolder, "intri_calib.json")
|
|
|
|
|
# write_json(data, outputJsonPath)
|
|
|
|
|
return mtx, dist, image_shape, mean_error
|
2024-11-06 20:55:17 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# calibrate_cameras函数中,照片按照相机编号进行分类
|
2024-11-21 22:39:22 +08:00
|
|
|
|
def calibrate_cameras(chessboardSize, squareSize, visualization):
|
2024-12-05 21:27:45 +08:00
|
|
|
|
cameras_path = glob.glob(osp.join(DataPath.intri_chessboard_data, "cam[0-7]"))
|
2024-11-21 22:39:22 +08:00
|
|
|
|
if len(cameras_path) == 0:
|
2024-11-06 20:55:17 +08:00
|
|
|
|
print("No camera folders found!")
|
|
|
|
|
return
|
2024-11-21 22:39:22 +08:00
|
|
|
|
|
2024-11-06 20:55:17 +08:00
|
|
|
|
data = {}
|
2024-11-21 22:39:22 +08:00
|
|
|
|
for camera_path in tqdm(cameras_path, desc="Processing Cameras", ncols=100):
|
|
|
|
|
cameraId = osp.basename(camera_path)
|
|
|
|
|
print("\nCalibrating camera {}... ".format(cameraId))
|
|
|
|
|
mtx, dist, image_shape, error = calibrate_camera(camera_path, chessboardSize, squareSize, visualization)
|
2024-12-05 21:27:45 +08:00
|
|
|
|
data[cameraId] = format_intri_json_data(mtx, dist, image_shape, error)
|
|
|
|
|
write_json(data, osp.join(DataPath.intri_json_path, "intri.json"))
|
|
|
|
|
print("Calibration data saved to: ", osp.join(DataPath.intri_json_path, "intri.json"))
|
2024-11-21 22:39:22 +08:00
|
|
|
|
|
2024-11-06 20:55:17 +08:00
|
|
|
|
|
|
|
|
|
# 去除图像畸变
|
2024-12-05 21:27:45 +08:00
|
|
|
|
def unistort_img(img, mtx, dist):
|
2024-11-05 21:25:01 +08:00
|
|
|
|
h, w = img.shape[:2]
|
|
|
|
|
newcameramtx, roi = cv.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))
|
|
|
|
|
dst = cv.undistort(img, mtx, dist, None, newcameramtx)
|
|
|
|
|
x, y, w, h = roi
|
|
|
|
|
dst = dst[y:y + h, x:x + w]
|
|
|
|
|
return dst
|
|
|
|
|
|
2024-11-21 22:39:22 +08:00
|
|
|
|
|
2024-12-05 21:27:45 +08:00
|
|
|
|
# 用于去除整个文件夹中的图像畸变
|
|
|
|
|
def unistort_imgs(mtx, dist):
|
|
|
|
|
imgPaths = read_img_paths(DataPath.intri_undistort_data)
|
2024-11-05 21:25:01 +08:00
|
|
|
|
if len(imgPaths) == 0:
|
|
|
|
|
print("No images found!")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
for imgPath in imgPaths:
|
|
|
|
|
img = cv.imread(imgPath)
|
2024-12-05 21:27:45 +08:00
|
|
|
|
dst = unistort_img(img, mtx, dist)
|
|
|
|
|
cv.imwrite(osp.join(DataPath.intri_undistort_result, osp.basename(imgPath)), dst)
|
2024-11-05 21:25:01 +08:00
|
|
|
|
|
2024-12-05 21:27:45 +08:00
|
|
|
|
print("Distortion corrected images saved to: ", DataPath.intri_undistort_result)
|
2024-11-05 21:17:00 +08:00
|
|
|
|
|
2024-11-05 21:25:01 +08:00
|
|
|
|
|
2024-11-05 21:17:00 +08:00
|
|
|
|
if __name__ == "__main__":
|
2024-11-06 20:55:17 +08:00
|
|
|
|
parser = argparse.ArgumentParser(description="相机内参标定和图像去畸变")
|
2024-12-05 21:27:45 +08:00
|
|
|
|
parser.add_argument("--action", type=str, required=True, choices=["cameras", "undistort"],
|
2024-11-21 22:39:22 +08:00
|
|
|
|
help=" --action cameras: 标定多个相机"
|
2024-11-06 20:55:17 +08:00
|
|
|
|
" --action distortion: 去除图像畸变")
|
2024-11-21 22:39:22 +08:00
|
|
|
|
parser.add_argument("--chessboardSize", type=str, default="11,8",
|
|
|
|
|
help="棋盘格角点数 (列数, 行数),例如 '11,8'")
|
|
|
|
|
parser.add_argument("--squareSize", type=float, default=60.0,
|
|
|
|
|
help="棋盘格方块的实际边长(单位与数据一致,例如 mm 或 m)")
|
2024-11-06 20:55:17 +08:00
|
|
|
|
parser.add_argument("--no-vis", dest="vis", action="store_false", help="禁用标定结果的可视化输出")
|
2024-11-05 21:25:01 +08:00
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
chessboardSize = tuple(map(int, args.chessboardSize.split(",")))
|
2024-11-21 22:39:22 +08:00
|
|
|
|
if args.action == "cameras":
|
|
|
|
|
calibrate_cameras(chessboardSize, args.squareSize, args.vis)
|
2024-12-05 21:27:45 +08:00
|
|
|
|
elif args.action == "undistort":
|
2024-11-21 22:39:22 +08:00
|
|
|
|
print("Removing image distortion, require input folder")
|
2024-12-05 21:27:45 +08:00
|
|
|
|
data = read_json(osp.join(DataPath.intri_json_path, "intri.json"))
|
2024-11-06 20:55:17 +08:00
|
|
|
|
mtx = np.array(data["K"])
|
|
|
|
|
dist = np.array(data["dist"])
|
2024-12-05 21:27:45 +08:00
|
|
|
|
unistort_imgs(mtx, dist)
|
2024-11-06 20:55:17 +08:00
|
|
|
|
else:
|
|
|
|
|
print("Invalid action!")
|
|
|
|
|
parser.print_help()
|