完成标定和去畸变功能,加入argparse
This commit is contained in:
parent
3e63b4760a
commit
87b48807e9
@ -4,19 +4,49 @@ import glob
|
|||||||
import cv2 as cv
|
import cv2 as cv
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import json
|
import json
|
||||||
|
import datetime
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
def calibrate_camera(imgFolder, chessboardSize, squareSize):
|
def format_json_data(mtx, dist, image_shape):
|
||||||
|
data = {
|
||||||
|
"time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
"K": mtx.tolist(),
|
||||||
|
"dist": dist.tolist(),
|
||||||
|
"image_shape": image_shape
|
||||||
|
}
|
||||||
|
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
|
||||||
|
|
||||||
|
def calibrate_camera(baseFolder, chessboardSize, squareSize, visualization):
|
||||||
# 设置输出目录
|
# 设置输出目录
|
||||||
outputFolder = osp.join(imgFolder, "output")
|
if visualization:
|
||||||
if not osp.exists(outputFolder):
|
outputFolder = create_output_folder(baseFolder, "calibrateOutputImages")
|
||||||
os.makedirs(outputFolder)
|
|
||||||
|
|
||||||
# 图片路径
|
# 图片路径
|
||||||
imgPaths = []
|
imgPaths = read_img_paths(baseFolder)
|
||||||
for extension in ["jpg", "png", "jpeg"]:
|
|
||||||
imgPaths += glob.glob(osp.join(imgFolder, "*.{}".format(extension)))
|
|
||||||
if len(imgPaths) == 0:
|
if len(imgPaths) == 0:
|
||||||
print("No images found!")
|
print("No images found!")
|
||||||
return
|
return
|
||||||
@ -42,12 +72,13 @@ def calibrate_camera(imgFolder, chessboardSize, squareSize):
|
|||||||
(cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001))
|
(cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001))
|
||||||
pointsWorld.append(board_grid)
|
pointsWorld.append(board_grid)
|
||||||
pointsPixel.append(corners)
|
pointsPixel.append(corners)
|
||||||
|
if visualization:
|
||||||
cv.drawChessboardCorners(img, (board_w, board_h), corners, ret)
|
cv.drawChessboardCorners(img, (board_w, board_h), corners, ret)
|
||||||
cv.imwrite(osp.join(outputFolder, osp.basename(imgPath)), img)
|
cv.imwrite(osp.join(outputFolder, osp.basename(imgPath)), img)
|
||||||
|
|
||||||
# 标定相机
|
# 标定相机
|
||||||
ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(pointsWorld, pointsPixel, gray.shape[::-1], None, None)
|
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("Intrinsic matrix:\n", mtx.astype(np.float32))
|
||||||
print("Distortion coefficients:\n", dist.astype(np.float32))
|
print("Distortion coefficients:\n", dist.astype(np.float32))
|
||||||
|
|
||||||
@ -59,27 +90,49 @@ def calibrate_camera(imgFolder, chessboardSize, squareSize):
|
|||||||
error = cv.norm(pointsPixel[i], imgpoints2, cv.NORM_L2) / len(imgpoints2)
|
error = cv.norm(pointsPixel[i], imgpoints2, cv.NORM_L2) / len(imgpoints2)
|
||||||
img_error[i] = error
|
img_error[i] = error
|
||||||
|
|
||||||
good_img = np.where(img_error < 0.5)[0]
|
# good_img = np.where(img_error < 0.5)[0]
|
||||||
mean_error = np.mean(img_error[good_img])
|
# mean_error = np.mean(img_error[good_img])
|
||||||
print("Reprojection error: ", mean_error)
|
mean_error = np.mean(img_error)
|
||||||
|
print("\nReprojection error: ", mean_error)
|
||||||
|
|
||||||
# 挑选出重投影误差小于1.0的图片,重新标定相机
|
# 挑选出重投影误差小于1.0的图片,重新标定相机
|
||||||
if len(good_img) == 0:
|
# if len(good_img) == 0:
|
||||||
print("No images with error < 0.5")
|
# print("No images with error < 0.5")
|
||||||
elif len(good_img) == nimg:
|
# elif len(good_img) == nimg:
|
||||||
print("All images have error < 0.5")
|
# print("All images have error < 0.5")
|
||||||
pass
|
# pass
|
||||||
else:
|
# else:
|
||||||
pointsWorld2 = [pointsWorld[i] for i in good_img]
|
# pointsWorld2 = [pointsWorld[i] for i in good_img]
|
||||||
pointsPixel2 = [pointsPixel[i] for i in good_img]
|
# pointsPixel2 = [pointsPixel[i] for i in good_img]
|
||||||
ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(pointsWorld2, pointsPixel2, gray.shape[::-1], None, None)
|
# ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(pointsWorld2, pointsPixel2, image_shape, None, None)
|
||||||
print("Intrinsic matrix:\n", mtx.astype(np.float32))
|
# print("Intrinsic matrix:\n", mtx.astype(np.float32))
|
||||||
print("Distortion coefficients:\n", dist.astype(np.float32))
|
# print("Distortion coefficients:\n", dist.astype(np.float32))
|
||||||
|
|
||||||
return mtx, dist
|
data = format_json_data(mtx, dist, image_shape)
|
||||||
|
# 在文件夹根目录下保存相机内参
|
||||||
|
outputJsonPath = osp.join(baseFolder, "intri_calib.json")
|
||||||
|
write_json(data, outputJsonPath)
|
||||||
|
return mtx, dist, image_shape
|
||||||
|
|
||||||
|
|
||||||
def undistort_image(img, mtx, dist):
|
# calibrate_cameras函数中,照片按照相机编号进行分类
|
||||||
|
def calibrate_cameras(baseFolder, chessboardSize, squareSize, visualization):
|
||||||
|
cameras = glob.glob(osp.join(baseFolder, '[0-9]'))
|
||||||
|
if len(cameras) == 0:
|
||||||
|
print("No camera folders found!")
|
||||||
|
return
|
||||||
|
outputJsonPath = osp.join(baseFolder, "intri_calib.json")
|
||||||
|
data = {}
|
||||||
|
for camera in cameras:
|
||||||
|
cameraId = osp.basename(camera)
|
||||||
|
print("\nCalibrating camera{}...".format(cameraId))
|
||||||
|
mtx, dist, image_shape = calibrate_camera(camera, chessboardSize, squareSize, visualization)
|
||||||
|
data[cameraId] = format_json_data(mtx, dist, image_shape)
|
||||||
|
write_json(data, outputJsonPath)
|
||||||
|
print("\nCalibration data saved to: ", outputJsonPath)
|
||||||
|
|
||||||
|
# 去除图像畸变
|
||||||
|
def remove_image_distortion(img, mtx, dist):
|
||||||
h, w = img.shape[:2]
|
h, w = img.shape[:2]
|
||||||
newcameramtx, roi = cv.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))
|
newcameramtx, roi = cv.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))
|
||||||
dst = cv.undistort(img, mtx, dist, None, newcameramtx)
|
dst = cv.undistort(img, mtx, dist, None, newcameramtx)
|
||||||
@ -87,55 +140,50 @@ def undistort_image(img, mtx, dist):
|
|||||||
dst = dst[y:y + h, x:x + w]
|
dst = dst[y:y + h, x:x + w]
|
||||||
return dst
|
return dst
|
||||||
|
|
||||||
|
# 用于去除整个文件夹中的图像畸变,保存到文件夹下的distortion_corrected_images文件夹中
|
||||||
def undistort_images(imgFolder, mtx, dist):
|
def remove_images_distortion(baseFolder, mtx, dist):
|
||||||
imgPaths = []
|
imgPaths = read_img_paths(baseFolder)
|
||||||
for extension in ["jpg", "png", "jpeg"]:
|
|
||||||
imgPaths += glob.glob(osp.join(imgFolder, "*.{}".format(extension)))
|
|
||||||
if len(imgPaths) == 0:
|
if len(imgPaths) == 0:
|
||||||
print("No images found!")
|
print("No images found!")
|
||||||
return
|
return
|
||||||
|
|
||||||
outputFolder = osp.join(imgFolder, "undistorted_images")
|
outputFolder = create_output_folder(baseFolder, "distortion_corrected_images")
|
||||||
if not osp.exists(outputFolder):
|
|
||||||
os.makedirs(outputFolder)
|
|
||||||
|
|
||||||
for imgPath in imgPaths:
|
for imgPath in imgPaths:
|
||||||
img = cv.imread(imgPath)
|
img = cv.imread(imgPath)
|
||||||
dst = undistort_image(img, mtx, dist)
|
dst = remove_image_distortion(img, mtx, dist)
|
||||||
cv.imwrite(osp.join(outputFolder, osp.basename(imgPath)), dst)
|
cv.imwrite(osp.join(outputFolder, osp.basename(imgPath)), dst)
|
||||||
|
|
||||||
print("Undistorted images saved to: ", outputFolder)
|
print("Distortion corrected images saved to: ", outputFolder)
|
||||||
|
|
||||||
|
|
||||||
def calibrate_cameras(imgFolder, chessboardSize, squareSize):
|
|
||||||
mtxs = []
|
|
||||||
dists = []
|
|
||||||
for folder in glob.glob(osp.join(imgFolder, "*")):
|
|
||||||
if not osp.isdir(folder):
|
|
||||||
continue
|
|
||||||
mtx, dist = calibrate_camera(folder, chessboardSize, squareSize)
|
|
||||||
mtxs.append(mtx)
|
|
||||||
dists.append(dist)
|
|
||||||
return mtxs, dists
|
|
||||||
|
|
||||||
|
|
||||||
def write_json_data(mtx, dist, outputFolder):
|
|
||||||
data = {
|
|
||||||
"intrinsic_matrix": mtx.tolist(),
|
|
||||||
"distortion_coefficients": dist.tolist()
|
|
||||||
}
|
|
||||||
with open(osp.join(outputFolder, "calibration.json"), "w") as f:
|
|
||||||
json.dump(data, f, indent=4)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser(description="相机内参标定和图像去畸变")
|
||||||
parser.add_argument("imgFolder", help="Folder containing images for calibration")
|
parser.add_argument("--action", type=str, required=True, choices=["camera", "cameras", "distortion"],
|
||||||
parser.add_argument("--chessboardSize", help="Size of chessboard (rows, cols)", default="11,8")
|
help=" --action camera: 标定单个相机"
|
||||||
parser.add_argument("--squareSize", help="Size of square in chessboard", default=60)
|
" --action cameras: 标定多个相机"
|
||||||
|
" --action distortion: 去除图像畸变")
|
||||||
|
parser.add_argument("--folder", type=str, required=True, help="包含相机文件夹的基础文件夹")
|
||||||
|
parser.add_argument("--chessboardSize", type=str, default="11,8", help="棋盘格尺寸,格式为'w,h'")
|
||||||
|
parser.add_argument("--squareSize", type=float, default=60.0, help="棋盘格方块尺寸")
|
||||||
|
parser.add_argument("--no-vis", dest="vis", action="store_false", help="禁用标定结果的可视化输出")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
chessboardSize = tuple(map(int, args.chessboardSize.split(",")))
|
chessboardSize = tuple(map(int, args.chessboardSize.split(",")))
|
||||||
squareSize = float(args.squareSize)
|
if args.action == "camera":
|
||||||
|
print("Calibrating camera...")
|
||||||
|
mtx, dist, image_shape = calibrate_camera(args.folder, chessboardSize, args.squareSize, args.vis)
|
||||||
|
outputJsonPath = osp.join(args.folder, "intri_calib.json")
|
||||||
|
print("Calibration data saved to: ", outputJsonPath)
|
||||||
|
elif args.action == "cameras":
|
||||||
|
calibrate_cameras(args.folder, chessboardSize, args.squareSize, args.vis)
|
||||||
|
elif args.action == "distortion":
|
||||||
|
print("Removing image distortion...")
|
||||||
|
data = read_json(osp.join(args.folder, "intri_calib.json"))
|
||||||
|
mtx = np.array(data["K"])
|
||||||
|
dist = np.array(data["dist"])
|
||||||
|
remove_images_distortion(args.folder, mtx, dist)
|
||||||
|
else:
|
||||||
|
print("Invalid action!")
|
||||||
|
parser.print_help()
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user