1、准备一个标定板,按照上文提出的要求制作。为了简便也可以用A4纸打印,粘贴在一个平板上。
2、采集棋盘格标定板若干张图片,一般10至20张就够。但是每次拍摄,标定板角度和位置要改变。
3、检测图片中角点。
4、利用解析解估算方法计算出5个内部参数,以及6个外部参数
5、使用畸变公式的线性方程组求解近似的畸变系数(或者直接使用0也可以)
6、使用非线性优化法计算精确的内外参数和畸变系数。
# -*- coding:utf-8 -*-
import os
import cv2
import numpy as np
import glob
import configparser
class MonocularCalibration(object):
'''单目标定和矫正类'''
def __init__(self, data_root, cali_file="MonoCalib_Param_720p.ini", board_size=(7,11), img_shape=(720, 1280), suffix="png"):
self.cali_file = os.path.join(data_root, cali_file)
self.H, self.W = img_shape
if os.path.exists(self.cali_file):
print("\n===> Read Calibration file from {}...".format(self.cali_file))
self.read_cali_file(self.cali_file)
else:
print("\n===> Start Calibration...")
# 步骤1:初始化
self.board_size = board_size
# 步骤2:标定
self.Mono_Calibration(corner_h=self.board_size[0], corner_w=self.board_size[1], source_path=data_root, suffix=suffix)
# 步骤3:生成标定文件
self.write_cali_file(cali_file=self.cali_file)
# 计算单目相机内参、畸变系数
def Mono_Calibration(self, corner_h, corner_w, source_path, suffix="png"):
# 设置寻找亚像素角点的参数,采用的停止准则是最大循环次数30和最大误差容限0.001
criteria = (cv2.TERM_CRITERIA_MAX_ITER | cv2.TERM_CRITERIA_EPS, 30, 0.001)
# 获取标定板角点的位置
# 世界坐标系中的棋盘格点,例如(0,0,0), (1,0,0), (2,0,0) ....,(8,5,0),去掉Z坐标,记为二维矩阵
objp = np.zeros((corner_h * corner_w, 3), np.float32)
# 将世界坐标系建在标定板上,所有点的Z坐标全部为0,所以只需要赋值x和y
objp[:, :2] = np.mgrid[0:corner_w, 0:corner_h].T.reshape(-1, 2)
square_size = 50.0 # 18.1 # 18.1 mm
objp = objp * square_size
# 储存棋盘格角点的世界坐标和图像坐标对
obj_points = [] # 存储3D点 # 在世界坐标系中的三维点
img_points = [] # 存储2D点 # 在图像平面的二维点
images = glob.glob(os.path.join(source_path, "*." + suffix))
for index, fname in enumerate(images):
print("=== ", index)
img = cv2.imread(fname)
h_, w_, _ = img.shape
if h_ != self.H or w_ != self.W:
img = cv2.resize(img, (self.W, self.H), interpolation=cv2.INTER_CUBIC)
self.gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
size = self.gray.shape[::-1]
ret, corners = cv2.findChessboardCorners(self.gray, (corner_w, corner_h), None)
if (corners[0, 0, 0] < corners[-1, 0, 0]):
print("*" * 5 + " order of {} is inverse! ".format(index) + "*" * 5)
corners = np.flip(corners, axis=0).copy()
if ret:
obj_points.append(objp)
corners2 = cv2.cornerSubPix(self.gray, corners, (11, 11), (-1, -1), criteria) # 在原角点的基础上寻找亚像素角点
img_points.append(corners2)
cv2.drawChessboardCorners(img, (corner_w, corner_h), corners2, ret) # 记住,OpenCV的绘制函数一般无返回值
cv2.imshow('img', img)
cv2.waitKey(50)
cv2.destroyAllWindows()
# print("img_points: ", img_points.shape)
# print("obj_points: ", obj_points.shape)
# 标定
ret, self.mtx, self.dist, self.rvecs, self.tvecs = cv2.calibrateCamera(obj_points, img_points, self.gray.shape[::-1], None, None)
print("ret:", ret)
print("mtx:\n", self.mtx) # 内参数矩阵
print("dist:\n", self.dist) # 畸变系数 distortion cofficients = (k_1,k_2,p_1,p_2,k_3)
# 并且通过实验表明,distortion cofficients = (k_1,k_2,p_1,p_2,k_3)
# 三个参数的时候由于k3所对应的非线性较为剧烈。估计的不好,容易产生极大的扭曲,所以k_3强制设置为0.0
# self.dist[0, 4] = 0.0
return self.mtx, self.dist, self.rvecs, self.tvecs
def Mono_Undisort_list(self, source_path, suffix="png"):
# 对所有图片进行去畸变,有两种方法实现分别为: undistort()和remap()
images = glob.glob(os.path.join(source_path, "*." + suffix))
for fname in images:
fname = '/'.join(fname.split('\\'))
print(fname)
prefix = fname.split('/')[-1].replace(".", "_undistort.")
# print(prefix)
img = cv2.imread(fname)
h_, w_, _ = img.shape
if h_ != self.H or w_ != self.W:
img = cv2.resize(img, (self.W, self.H), interpolation=cv2.INTER_CUBIC)
dst = self.Mono_Rectify(img, flag=True)
print("img: ", img.shape)
print("dst: ", dst.shape)
# cv2.imshow('img', img)
# cv2.imshow('dst', dst)
#
# cv2.waitKey(500)
if not os.path.isdir(os.path.join(source_path, "mono_rec")):
os.mkdir(os.path.join(source_path, "mono_rec"))
cv2.imwrite(os.path.join(source_path, "mono_rec", prefix), dst)
def Mono_Rectify(self, image, flag=True):
image = cv2.resize(image, (self.W, self.H))
# 使用 cv.undistort()进行畸变校正
if flag:
# 矫正单目图像:直接使用计算的相机内参矩阵
image_rec = cv2.undistort(image, self.mtx, self.dist, None, self.mtx)
else:
# 矫正单目图像:使用计算的相机内参矩阵计算新的内参矩阵
'''
1、默认情况下,我们通常不会求取新的CameraMatrix,这样代码中会默认使用标定得到的CameraMatrix。
而这个摄像机矩阵是在理想情况下没有考虑畸变得到的,所以并不准确,重要的是fx和fy的值会比考虑畸变情况下的偏大,
会损失很多有效像素。我们可以通过这个函数getOptimalNewCameraMatrix ()求取一个新的摄像机内参矩阵,
2、cv2.getOptimalNewCameraMatrix()。如果参数alpha = 0, 它返回含有最小不需要像素的非扭曲图像,
所以它可能移除一些图像角点。如果alpha = 1, 所有像素都返回。还会返回一个 ROI 图像,我们可以用来对结果进行裁剪。
'''
h, w = self.H, self.W
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(self.mtx, self.dist, (w, h), 0, (w, h))
image_rec = cv2.undistort(image, self.mtx, self.dist, None, newcameramtx)
# crop and save the image
# x, y, w, h = roi
# image_rec = image_rec[y:y + h, x:x + w]
return image_rec
def read_cali_file(self, cali_file):
con = configparser.ConfigParser()
con.read(cali_file, encoding='utf-8')
sections = con.sections()
# print(sections.items)
calib = con.items('Calib')
rectify = con.items('Rectify')
calib = dict(calib)
rectify = dict(rectify)
self.u0 = calib['u0']
self.v0 = calib['v0']
self.fx = calib['fx']
self.fy = calib['fy']
self.mtx = np.array([[rectify['mtx_0'], rectify['mtx_1'], rectify['mtx_2']],
[rectify['mtx_3'], rectify['mtx_4'], rectify['mtx_5']],
[rectify['mtx_6'], rectify['mtx_7'], rectify['mtx_8']]]).astype('float32')
self.dist = np.array([[rectify['dist_k1'],
rectify['dist_k2'],
rectify['dist_p1'],
rectify['dist_p2'],
rectify['dist_k3']]]).astype('float32')
def write_cali_file(self, cali_file="./MonoCalib_Param.ini"):
self.u0 = self.mtx[0, 2]
self.v0 = self.mtx[1, 2]
self.fx = self.mtx[0, 0]
self.fy = self.mtx[1, 1]
self.Calib = {"fx": self.fx,
"fy": self.fy,
"u0": self.u0,
"v0": self.v0,
}
# 相机内参矩阵
# fx s x0
# 0 fy y0
# 0 0 1
Rectify = {}
for i in range(3):
for j in range(3):
Rectify['mtx_{}'.format(i*3+j)] = self.mtx[i, j]
# 相机畸变系数
# 畸变系数 distortion cofficients = (k_1,k_2,p_1,p_2,k_3)
Rectify['dist_k1'] = self.dist[0, 0]
Rectify['dist_k2'] = self.dist[0, 1]
Rectify['dist_p1'] = self.dist[0, 2]
Rectify['dist_p2'] = self.dist[0, 3]
Rectify['dist_k3'] = self.dist[0, 4]
self.Rectify = Rectify
# Write calibration param to file.
config = configparser.ConfigParser()
config["Calib"] = self.Calib
config["Rectify"] = self.Rectify
# with open("Calib_Param_4.ini", 'w') as configfile:
with open(cali_file, 'w') as configfile:
config.write(configfile)
def main():
source_path = "path to your image"
mono_calibration = MonocularCalibration(data_root=source_path,
cali_file="MonoCalib_Param_720p.ini",
board_size=(7, 11),
img_shape=(720, 1280),
suffix="png")
print(mono_calibration.mtx)
print(mono_calibration.dist)
data_root = source_path
mono_calibration.Mono_Undisort_list(data_root, suffix="png")
if __name__ == '__main__':
main()
————————————————
原文链接:https://blog.csdn.net/qq_38906523/article/details/125321999