Python图像处理 —— 相机标定、畸变矫正

Posted on 2020-09-02  12 Views


实验数据

  • 标定图(黑白棋盘图)若干(不少于10张拍摄不同角度的图片,焦距不变)

过程

  • 每张图片提取角点信息
  • 每张图片提取亚像素角点信息
  • 相机标定
  • 畸变矫正

内参数矩阵、畸变系数

  • 这里保存为json文件
{
  "mtx": [
    [
      3096.855023641478,
      0.0,
      1029.4641305618723
    ],
    [
      0.0,
      3091.0248015384295,
      333.24889640636945
    ],
    [
      0.0,
      0.0,
      1.0
    ]
  ],
  "dist": [
    [
      -0.102230276138444,
      -6.080581251041141,
      0.013564529498038387,
      -0.0027957254737596593,
      39.277124343630284
    ]
  ]
}

矫正前图像

矫正后图像

代码

  • 包括图片矫正、视频矫正
import os
import cv2
import glob
import json
import numpy as np


class CameraCalibration:

    def calibration_init(self, width, height, pic_path, save_parameters=True):
        # 亚像素角点的参数
        criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)

        # 标定板角点的位置
        objp = np.zeros((width * height, 3), np.float32)
        objp[:, :2] = np.mgrid[0:width, 0:height].T.reshape(-1, 2)

        # 存储点
        obj_points, img_points = [], []

        images = glob.glob(pic_path)
        for i in images:
            img = cv2.imread(i)
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            # 棋盘格角点
            ret, corners = cv2.findChessboardCorners(gray, (width, height), None)
            # 存储角点
            if ret:
                cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
                obj_points.append(objp)
                img_points.append(corners)

        # 标定
        ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(obj_points, img_points, gray.shape[::-1], None, None)

        if save_parameters:
            self.__parameters_init(mtx, dist)
        else:
            print("ret:", ret)
            print("mtx:\n", mtx)  # 内参数矩阵
            print("dist:\n", dist)  # 畸变系数   distortion cofficients = (k_1,k_2,p_1,p_2,k_3)
            print("rvecs:\n", rvecs)  # 旋转向量  # 外参数
            print("tvecs:\n", tvecs)  # 平移向量  # 外参数

    def __parameters_init(self, mtx, dist):
        parameters_path = './parameters.json'
        if not os.path.exists(parameters_path):
            os.system(r'touch %s' % parameters_path)
            self.__write_parameters(mtx, dist)
        else:
            self.__write_parameters(mtx, dist)

    @staticmethod
    def __write_parameters(mtx, dist):
        with open("./parameters.json", "w", encoding='utf-8') as f:
            content = {"mtx": mtx.tolist(), "dist": dist.tolist()}
            f.write(json.dumps(content, ensure_ascii=False))
        f.close()


class CameraCorrect:

    def __init__(self):
        self.__correct_init()

    # 畸变参数初始化
    def __correct_init(self, parameters_file_path: str = "./parameters.json"):
        if not os.path.exists(parameters_file_path):
            pass
        else:
            with open(parameters_file_path, "r", encoding='utf-8') as f:
                json_data = json.load(f)
                self.mtx = np.array(json_data["mtx"])
                self.dist = np.array(json_data["dist"])

    # 图片畸变校准
    def correct_pic(self, origin_path: str, target_path: str = "./", file_name: str = "cor_pic",
                    file_format: str = "png"):
        img = cv2.imread(origin_path)
        pic_height, pic_width = img.shape[:2]
        file_format = self.__calibration_pic_format(file_format.lower())
        cv2.imwrite(target_path + file_name + "." + file_format,
                    self.__distortion_correction(img, pic_width, pic_height))

    # 视频畸变校准(直接输出视频)
    def correct_vid2vid(self, origin_path: str, target_path: str = "./", file_name: str = "cor_vid",
                        file_format: str = "mp4", file_coding: str = "mp4v"):
        cap = cv2.VideoCapture(origin_path)
        # 获取视频参数
        fps, count, vid_width, vid_height = self.__get_capture_attribute(cap)
        # 格式编码校对
        if self.__calibration_format_coding(file_format, file_coding) is False:
            file_format, file_coding = "mp4", "mp4v"
        out = cv2.VideoWriter(target_path + file_name + "." + file_format,
                              cv2.VideoWriter_fourcc(*self.__get_vid_coding(file_coding)), fps,
                              (vid_width, vid_height))
        for i in range(count):
            ret, img = cap.read()
            if ret:
                img = cv2.resize(img, (vid_width, vid_height))
                dst = self.__distortion_correction(img, vid_width, vid_height)
                out.write(dst)
        cap.release()
        out.release()

    # 视频畸变校准(校准抽取所有帧)
    def correct_vid2pic(self, origin_path: str, target_path: str = "./", file_name: str = "cor_pic",
                        file_format: str = "png"):
        file_format = self.__calibration_pic_format(file_format.lower())
        cap = cv2.VideoCapture(origin_path)
        fps, count, vid_width, vid_height = self.__get_capture_attribute(cap)

        for i in range(count):
            ret, img = cap.read()
            if ret:
                img = cv2.resize(img, (vid_width, vid_height))
                cv2.imwrite(target_path + file_name + "_" + str(i) + "." + file_format,
                            self.__distortion_correction(img, vid_width, vid_height))
        cap.release()
        cv2.destroyAllWindows()

    # 获取视频参数
    @staticmethod
    def __get_capture_attribute(vid: cv2.VideoCapture):
        fps = int(vid.get(cv2.CAP_PROP_FPS))  # 视频帧率
        count = int(vid.get(cv2.CAP_PROP_FRAME_COUNT))  # 视频帧数
        width = int(vid.get(cv2.CAP_PROP_FRAME_WIDTH))  # 视频宽度 vid.shape[1]
        height = int(vid.get(cv2.CAP_PROP_FRAME_HEIGHT))  # 视频高度 vid.shape[0]
        return fps, count, width, height

    # 图片格式校对
    @staticmethod
    def __calibration_pic_format(file_format: str):
        if file_format == "jpg" or file_format == "png" or file_format == "bmp" or file_format == "tiff" or file_format == "tif":
            return file_format
        else:
            return "png"

    # 视频格式编码校对
    @staticmethod
    def __calibration_format_coding(file_format: str, file_coding: str):
        file_format, file_coding = file_format.lower(), file_coding.lower()
        if file_format == "mp4" and file_coding == "mp4v":
            return True
        elif file_format == "avi" and (file_coding == "yuv" or file_coding == "mpeg-1" or file_coding == "mpeg-4"):
            return True
        elif file_format == "ogv" and file_coding == "ogg vorbis":
            return True
        elif file_format == "flv" and file_coding == "flash":
            return True
        else:
            return False

    # 获取编码参数
    @staticmethod
    def __get_vid_coding(file_coding: str):
        file_coding = file_coding.lower()
        if file_coding == "mp4v":
            return 'mp4v'
        elif file_coding == "yuv":
            return 'I420'
        elif file_coding == "mpeg-1":
            return 'PIMI'
        elif file_coding == "mp4g-4":
            return 'XVID'
        elif file_coding == "ogg vorbis":
            return 'THEO'
        elif file_coding == "flash":
            return 'FLV1'
        else:
            return 'mp4v'

    # 畸变矫正
    def __distortion_correction(self, img, origin_width, origin_height):
        # 去畸变
        new_camera_mtx, roi = cv2.getOptimalNewCameraMatrix(self.mtx, self.dist, (origin_width, origin_height), 0,
                                                            (origin_width, origin_height))
        dst = cv2.undistort(img, self.mtx, self.dist, None, new_camera_mtx)
        # 根据ROI区域裁剪图片
        x, y, width, height = roi
        dst = dst[y:y + height, x:x + width]
        dst = cv2.resize(dst, (origin_width, origin_height))
        return dst


if __name__ == '__main__':
    cal = CameraCalibration()
    cal.calibration_init(9, 6, "./pic/*.jpg")
    cor = CameraCorrect()
    cor.correct_pic("./pic/cm_flag_5.jpg", "./output_pic/")
    cor.correct_vid2vid("./vid/vid_demo_1.mp4", "./output_vid/")
    cor.correct_vid2pic("./vid/vid_demo_1.mp4", "./output_vid/")


粉色的花瓣,美丽地缠绕在身上。依在风里。