-1

I got this camera 170 FOV, put it under my car's winch at the front and I just cannot unwrap the image (calibration video and images). I use this repository for my 360 car view project.

As calibration with --fisheye is crashing. (I use a video multiplexer to get all 4 camera frames at once as one frame so there's division to quadrants happening in the code)

Code:

"""
~~~~~~~~~~~~~~~~~~~~~~~~~~
Fisheye Camera calibration
~~~~~~~~~~~~~~~~~~~~~~~~~~

Usage:
    python calibrate_camera.py \
        -i 0 \
        -grid 9x6 \
        -out fisheye.yaml \
        -framestep 20 \
        --resolution 640x480
        --fisheye
"""
import argparse
import os
import numpy as np
import cv2
from surround_view import CaptureThread, MultiBufferManager
from surround_view.multiplex_capture_thread import MultiplexCaptureThread
from surround_view import MultiBufferManager
import surround_view.utils as utils


# we will save the camera param file to this directory
TARGET_DIR = os.path.join(os.getcwd(), "yaml")

# default param file
DEFAULT_PARAM_FILE = os.path.join(TARGET_DIR, "camera_params.yaml")


def main():
    parser = argparse.ArgumentParser()

    # input video stream
    parser.add_argument("-i", "--input", type=int, default=1,
                        help="input camera device")

    # chessboard pattern size
    parser.add_argument("-grid", "--grid", default="9x6",
                        help="size of the calibrate grid pattern")

    parser.add_argument("-r", "--resolution", default="960x540",
                        help="resolution of the camera image")
    
    parser.add_argument("--quadrant", type=int, default=0,
                        help="which quadrant of the multiplexed image to use (0=front,1=back,2=left,3=right)")


    parser.add_argument("-framestep", type=int, default=30,
                        help="use every nth frame in the video")

    parser.add_argument("-o", "--output", default=DEFAULT_PARAM_FILE,
                        help="path to output yaml file")

    parser.add_argument("-fisheye", "--fisheye", action="store_true",
                        help="set true if this is a fisheye camera")

    parser.add_argument("-flip", "--flip", default=0, type=int,
                        help="flip method of the camera")

    parser.add_argument("--no_gst", action="store_true",
                        help="set true if not use gstreamer for the camera capture")

    args = parser.parse_args()

    if not os.path.exists(TARGET_DIR):
        os.mkdir(TARGET_DIR)

    text1 = "press c to calibrate"
    text2 = "press q to quit"
    text3 = "device: {}".format(args.input)
    text4 = "press SPACE to capture sample"
    font = cv2.FONT_HERSHEY_SIMPLEX
    fontscale = 0.6

    resolution_str = args.resolution.split("x")
    W = int(resolution_str[0])
    H = int(resolution_str[1])
    grid_size = tuple(int(x) for x in args.grid.split("x"))
    grid_points = np.zeros((1, np.prod(grid_size), 3), np.float32)
    grid_points[0, :, :2] = np.indices(grid_size).T.reshape(-1, 2)

    objpoints = []  # 3d point in real world space
    imgpoints = []  # 2d points in image plane

    # Request full source resolution as 2x quadrant (e.g., 960x540 quadrant => 1920x1080 source)
    cap_thread = MultiplexCaptureThread(webcam_id=args.input, resolution=(W * 2, H * 2))


    buffer_manager = MultiBufferManager()

    # ✅ Register 4 virtual quadrant devices in the buffer manager
    for cam_id in cap_thread.device_ids:
        cap_thread.device_id = cam_id
        buffer_manager.bind_thread(cap_thread, buffer_size=8)

    if cap_thread.connect_camera():
        cap_thread.start()
    else:
        print("Cannot open USB camera")
        return


    quit = False
    do_calib = False
    i = -1
    while True:
        i += 1
        img = buffer_manager.get_device(args.quadrant).get().image

        # On first frame, print debug info about feed dimensions
        if i == 0:
            req_src_w, req_src_h = (W * 2, H * 2)
            print("[DEBUG] Requested source resolution: {}x{}".format(req_src_w, req_src_h))
            if hasattr(cap_thread, "resolution") and cap_thread.resolution is not None:
                print("[DEBUG] Actual camera source resolution: {}x{}".format(cap_thread.resolution[0], cap_thread.resolution[1]))
            print("[DEBUG] Quadrant frame size (received): {}x{}".format(img.shape[1], img.shape[0]))

        # Only attempt corner detection and sample collection every `framestep` frames
        if i % args.framestep == 0:
            print("searching for chessboard corners in frame " + str(i) + "...")
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            found, corners = cv2.findChessboardCorners(
                gray,
                grid_size,
                cv2.CALIB_CB_ADAPTIVE_THRESH +
                cv2.CALIB_CB_NORMALIZE_IMAGE +
                cv2.CALIB_CB_FILTER_QUADS
            )
            if found:
                term = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_COUNT, 30, 0.01)
                cv2.cornerSubPix(gray, corners, (5, 5), (-1, -1), term)
                print("OK")
                imgpoints.append(corners)
                objpoints.append(grid_points)
                cv2.drawChessboardCorners(img, grid_size, corners, found)

        cv2.putText(img, text1, (20, 70), font, fontscale, (255, 200, 0), 2)
        cv2.putText(img, text2, (20, 110), font, fontscale, (255, 200, 0), 2)
        cv2.putText(img, text3, (20, 30), font, fontscale, (255, 200, 0), 2)
        cv2.putText(img, text4, (20, 150), font, fontscale, (255, 200, 0), 2)

        disp_w = int(img.shape[1])
        disp_h = int(img.shape[0])

        if i == 0:  # only set once on first frame        
            cv2.namedWindow("corners", cv2.WINDOW_NORMAL)
            cv2.resizeWindow("corners", disp_w, disp_h)

        disp = img
        cv2.resize(img, (disp_w, disp_h))
        cv2.imshow("corners", disp)
        key = cv2.waitKey(1) & 0xFF
        if key == ord("c"):
            print("\nPerforming calibration...\n")
            N_OK = len(objpoints)
            if N_OK < 1:
                print("No corners detected, calibration failed")
                continue
            else:
                print(f"Proceeding with {N_OK} sample(s)")
                do_calib = True
                break

        elif key == ord("q"):
            quit = True
            break

        elif key == 32:  # SPACE: manual capture of current frame
            gray_manual = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            found_manual, corners_manual = cv2.findChessboardCorners(
                gray_manual,
                grid_size,
                cv2.CALIB_CB_ADAPTIVE_THRESH +
                cv2.CALIB_CB_NORMALIZE_IMAGE +
                cv2.CALIB_CB_FILTER_QUADS
            )
            if found_manual:
                term_manual = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_COUNT, 30, 0.01)
                cv2.cornerSubPix(gray_manual, corners_manual, (5, 5), (-1, -1), term_manual)
                print("OK (manual)")
                imgpoints.append(corners_manual)
                objpoints.append(grid_points)
                cv2.drawChessboardCorners(img, grid_size, corners_manual, found_manual)
            else:
                print("No corners detected in current frame (manual capture skipped)")

    if quit:
        cv2.destroyAllWindows()

    if do_calib:
        N_OK = len(objpoints)
        K = np.zeros((3, 3))
        D = np.zeros((4, 1))
        rvecs = [np.zeros((1, 1, 3), dtype=np.float64) for _ in range(N_OK)]
        tvecs = [np.zeros((1, 1, 3), dtype=np.float64) for _ in range(N_OK)]
        calibration_flags = (cv2.fisheye.CALIB_RECOMPUTE_EXTRINSIC +
                             cv2.fisheye.CALIB_CHECK_COND +
                             cv2.fisheye.CALIB_FIX_SKEW)

        if args.fisheye:
            ret, mtx, dist, rvecs, tvecs = cv2.fisheye.calibrate(
                objpoints,
                imgpoints,
                (W, H),
                K,
                D,
                rvecs,
                tvecs,
                calibration_flags,
                (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-6)
            )
        else:
            ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
                objpoints,
                imgpoints,
                (W, H),
                None,
                None)

        if ret:
            fs = cv2.FileStorage(args.output, cv2.FILE_STORAGE_WRITE)
            fs.write("resolution", np.int32([W, H]))
            # Save the actual calibration results, not the initial placeholders
            fs.write("camera_matrix", mtx)
            fs.write("dist_coeffs", dist)
            fs.release()
            print("successfully saved camera data")
            cv2.putText(img, "Success!", (220, 240), font, 2, (0, 0, 255), 2)

        else:
            cv2.putText(img, "Failed!", (220, 240), font, 2, (0, 0, 255), 2)

        cv2.imshow("corners", img)
        cv2.waitKey(0)


if __name__ == "__main__":
    main()

Error:

cv2.error: OpenCV(4.12.0) D:\a\opencv-python\opencv-python\opencv\modules\calib3d\src\fisheye.cpp:1484:
error: (-3:Internal error) CALIB_CHECK_COND - Ill-conditioned matrix for input array 0 in function
'cv::internal::CalibrateExtrinsics'

I use normal calibration but the undistorted image seems like just 30% undistorted:

distorted but undistorted but still distorted

Tell me how I can undistort the image.

UPDATE

At some point I've managed to turn off the CALIB_CHECK_COND flag and got this image, where the edges are too distorted. Is there a way to undistort them too?

enter image description here

3
  • 2
    calib.io/blogs/knowledge-base/calibration-best-practices -- your calibration set probably never has the pattern going into corners. Commented Oct 17 at 17:41
  • I've tried countless times but the calibration results are either inconsistent or just the same. Commented Oct 21 at 8:47
  • Your thumbs are covering the top left and right corners of the calibration chart. So you will not get all the required calibration points. Commented Oct 21 at 14:41

1 Answer 1

0

Looks like the best autocalibreation is manual one. Used AI to create me a script to adjust all the values of the camera_matrix and dist_coeffs manually until i got the desired picture in the live preview.

Sign up to request clarification or add additional context in comments.

3 Comments

maybe add a more elaborate answer so that others can solve this issue, if they have the same problem? Maybe code + before and after pics
The images cannot be added to the comment for some reason. The code is a one time AI generated solution that worked for me in my environment and my input data. You can get one by asking ChatGPT to write a python script with sliders to adjust every value in a yaml file with live preview and then adjust the prompt for your environment,
You can edit your answer and include these stuff, if you want

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.