Skip to content

OpenCV Parameters Interop (Python)

#!/usr/bin/env python3

import argparse
import cv2
import json
import math
import numpy as np
import os


def _object(value, label):
    if not isinstance(value, dict):
        raise ValueError(f"{label} must be a JSON object")
    return value


def _number(value, label):
    if isinstance(value, bool) or not isinstance(value, (int, float)):
        raise ValueError(f"{label} must be a finite number")
    try:
        value = float(value)
    except OverflowError as error:
        raise ValueError(f"{label} must be a finite number") from error
    if not math.isfinite(value):
        raise ValueError(f"{label} must be a finite number")
    return value


def _vector(value, label):
    if not isinstance(value, list) or len(value) != 3:
        raise ValueError(f"{label} must contain exactly three numbers")
    return [_number(item, label) for item in value]


def readCalibParameters(filePath):
    """Return K, distortion, rotation and translation lists for OpenCV cameras.

    Read a current libCalibGeom calibration export, not a CalibGeom .calib project.
    Rotation/translation vectors retain the existing three-element list shape.
    """
    if os.path.splitext(os.fspath(filePath))[1].lower() == ".calib":
        raise ValueError("Expected exported calibration JSON, not a .calib project")
    with open(filePath, encoding="utf-8") as file:
        j = _object(json.load(file), "Calibration")
    if "Calibration" in j or "format" in j or "initialization" in j:
        raise ValueError("Expected current calibration JSON; legacy archives and "
                         "CalibGeom projects are not supported. Export Parameters first.")
    cameras = j.get("cameras")
    if not isinstance(cameras, list) or not cameras:
        raise ValueError("Calibration must contain a nonempty cameras array")

    K = []
    k = []
    cam_rvecs = []
    cam_tvecs = []

    distortion_names = ("k1", "k2", "p1", "p2", "k3", "k4", "k5", "k6",
                        "s1", "s2", "s3", "s4", "tauX", "tauY")
    names = ("f", "ar", "cx", "cy") + distortion_names
    for i, camera in enumerate(cameras):
        label = f"Camera {i}"
        try:
            camera = _object(camera, label)
            model = _object(camera["model"], f"{label} model")
            if model["type"] != "OpenCV":
                raise ValueError(f"{label} must use the OpenCV model")
            image_size = _object(model["imageSize"], f"{label} imageSize")
            for dimension in ("width", "height"):
                size = _number(image_size[dimension], f"{label} {dimension}")
                if size <= 0 or not size.is_integer():
                    raise ValueError(f"{label} {dimension} must be a positive integer")
            parameters = _object(model["parameters"], f"{label} parameters")
            if set(parameters) != set(names):
                raise ValueError(f"{label} must contain all 18 OpenCV parameters")
            values = {}
            for name in names:
                item = _object(parameters[name], f"{label} parameter {name}")
                if item["state"] not in ("free", "fixed", "disabled"):
                    raise ValueError(f"{label} parameter {name} has an unknown state")
                # Projection uses stored values, even when fixed or disabled.
                values[name] = _number(item["value"], f"{label} parameter {name}")
            f, ar = values["f"], values["ar"]
            fy = _number(f * ar, f"{label} vertical focal length")
            if f <= 0 or ar <= 0 or fy <= 0:
                raise ValueError(f"{label} focal length and aspect ratio must be positive")
            K.append(np.array([[f, 0.0, values["cx"]],
                               [0.0, fy, values["cy"]], [0.0, 0.0, 1.0]]))
            k.append(np.array([values[name] for name in distortion_names]))
            transform = _object(camera["extrinsics"], f"{label} extrinsics")
            rot = _object(transform["rotation"], f"{label} rotation")
            translation = _object(transform["translation"], f"{label} translation")
            cam_rvecs.append(_vector(rot["rvec"], f"{label} rotation.rvec"))
            cam_tvecs.append(_vector(translation["vals"], f"{label} translation.vals"))
        except KeyError as error:
            raise ValueError(f"{label} is missing required field {error.args[0]}") from error

    return K, k, cam_rvecs, cam_tvecs


def main():
    parser = argparse.ArgumentParser(description="Read libCalibGeom OpenCV-compatible parameters from a calibration JSON file.")
    parser.add_argument("json_file", help="Path to the calibration JSON file.")
    parser.add_argument("image_path", help="Path to the image to undistort.")
    args = parser.parse_args()

    try:
        K, k, cam_rvecs, cam_tvecs = readCalibParameters(args.json_file)
        image = cv2.imread(args.image_path)
        if image is None:
            raise OSError(f"Could not read image: {args.image_path}")
        image_undistorted = cv2.undistort(image, K[0], k[0], None, K[0])
        filename, file_extension = os.path.splitext(args.image_path)
        imageOutPath = filename + "_undistorted" + file_extension
        if not cv2.imwrite(imageOutPath, image_undistorted):
            raise OSError(f"Could not write image: {imageOutPath}")
        print(f"Wrote {imageOutPath}")
    except (OSError, ValueError, cv2.error) as error:
        parser.exit(1, f"Error: {error}\n")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Classes / Functions

  • Calibration
  • Camera
  • CameraModelOpenCV
  • CameraModelType
  • This script reads current libCalibGeom calibration JSON or Calib GEOM's parameter JSON export and maps it into OpenCV matrices/arrays. Calib GEOM .calib projects and legacy archive JSON are not supported.
  • It does not import libCalibGeom at runtime; it consumes the calibration serialization output.