camera_calibrate/calibrate_intri.py

200 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
import os.path as osp
import glob
import cv2 as cv
import numpy as np
import json
import datetime
import argparse
from tqdm import tqdm
def format_json_data(mtx, dist, image_shape, error):
data = {
"time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"K": mtx.tolist(),
"dist": dist.tolist(),
"image_shape": image_shape,
"error": error
}
return data
def write_json(data, output_path):
with open(output_path, "w") as f:
json.dump(data, f, indent=4)
def read_json(input):
with open(input, "r") as f:
data = json.load(f)
return data
def read_img_paths(imgFolder):
imgPaths = []
for extension in ["jpg", "png", "jpeg", "bmp"]:
imgPaths += glob.glob(osp.join(imgFolder, "*.{}".format(extension)))
return imgPaths
def create_output_folder(baseFolder, outputFolder):
folder = osp.join(baseFolder, outputFolder)
if not osp.exists(folder):
os.makedirs(folder)
return folder
base_path = "data"
intri_img_path = osp.join(base_path, "chessboard", "intri")
intri_vis_path = osp.join(base_path, "vis", "intri")
json_output_path = osp.join(base_path, 'output_json')
distortion_images_path = osp.join(base_path, "distortion_images")
def calibrate_camera(camera, chessboardSize, squareSize, visualization):
# 设置输出目录
if visualization:
outputFolder = create_output_folder(intri_vis_path, osp.basename(camera))
# 图片路径
imgPaths = read_img_paths(camera)
if len(imgPaths) == 0:
print("No images found!\n")
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:
cv.cornerSubPix(gray, corners, (11, 11), (-1, -1),
(cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001))
pointsWorld.append(board_grid)
pointsPixel.append(corners)
if visualization:
cv.drawChessboardCorners(img, (board_w, board_h), corners, ret)
cv.imwrite(osp.join(outputFolder, osp.basename(imgPath)), img)
# 标定相机
image_shape = gray.shape[::-1]
ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(pointsWorld, pointsPixel, image_shape, None, None)
# print("Intrinsic matrix:\n", mtx.astype(np.float32))
# print("Distortion coefficients:\n", dist.astype(np.float32))
# 计算重投影误差
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
# good_img = np.where(img_error < 0.5)[0]
# mean_error = np.mean(img_error[good_img])
mean_error = np.mean(img_error)
print("Reprojection error: ", mean_error)
# 挑选出重投影误差小于1.0的图片,重新标定相机
# 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))
# 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
# calibrate_cameras函数中照片按照相机编号进行分类
# baseFolder: 包含图片和输出数据的文件夹,默认是./data可以通过--folder参数指定
def calibrate_cameras(chessboardSize, squareSize, visualization):
cameras_path = glob.glob(osp.join(intri_img_path, "cam[0-7]"))
if len(cameras_path) == 0:
print("No camera folders found!")
return
data = {}
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)
data[cameraId] = format_json_data(mtx, dist, image_shape, error)
write_json(data, osp.join(json_output_path, "intri.json"))
print("Calibration data saved to: ", osp.join(json_output_path, "intri.json"))
# 去除图像畸变
def remove_image_distortion(img, mtx, dist):
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
# 用于去除整个文件夹中的图像畸变保存到文件夹下的distortion_corrected_images文件夹中
def remove_images_distortion(mtx, dist):
imgPaths = read_img_paths(distortion_images_path)
if len(imgPaths) == 0:
print("No images found!")
return
outputFolder = create_output_folder(distortion_images_path, "output_images")
for imgPath in imgPaths:
img = cv.imread(imgPath)
dst = remove_image_distortion(img, mtx, dist)
cv.imwrite(osp.join(outputFolder, osp.basename(imgPath)), dst)
print("Distortion corrected images saved to: ", outputFolder)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="相机内参标定和图像去畸变")
parser.add_argument("--action", type=str, required=True, choices=["cameras", "distortion"],
help=" --action cameras: 标定多个相机"
" --action distortion: 去除图像畸变")
parser.add_argument("--chessboardSize", type=str, default="11,8",
help="棋盘格角点数 (列数, 行数),例如 '11,8'")
parser.add_argument("--squareSize", type=float, default=60.0,
help="棋盘格方块的实际边长(单位与数据一致,例如 mm 或 m")
parser.add_argument("--no-vis", dest="vis", action="store_false", help="禁用标定结果的可视化输出")
args = parser.parse_args()
chessboardSize = tuple(map(int, args.chessboardSize.split(",")))
if args.action == "cameras":
calibrate_cameras(chessboardSize, args.squareSize, args.vis)
elif args.action == "distortion":
print("Removing image distortion, require input folder")
data = read_json(osp.join(json_output_path, "intri.json"))
mtx = np.array(data["K"])
dist = np.array(data["dist"])
remove_images_distortion(mtx, dist)
else:
print("Invalid action!")
parser.print_help()