Program Listing for File calibration_manager.hpp#

Return to documentation for file (src/calibration/calibration_manager.hpp)

#pragma once
#include <QImage>
#include <QObject>
#include <memory>

#include "core/settings.hpp"
#include "video/video_frame.hpp"

namespace mosaic {

/// @brief Runs a full checkerboard calibration pipeline using OpenCV.
///
/// Checkerboard calibration determines the intrinsic camera parameters
/// (focal length, principal point, and lens distortion coefficients) needed
/// to undistort frames and to triangulate 3-D positions across cameras.
///
/// Requires @c MOSAIC_HAVE_OPENCV — is_available() returns @c false otherwise.
///
/// @par Workflow
/// @code{.cpp}
/// CalibrationManager cal;
/// cal.set_board({9, 6, 25.0});          // 9×6 inner corners, 25 mm squares
///
/// connect(&cal, &CalibrationManager::corners_detected, this,
///         [](int viewIdx, bool found, QImage preview) {
///             // update a preview label in the UI
///         });
/// connect(&cal, &CalibrationManager::calibration_done, this,
///         [&](double rms, bool ok) {
///             if (ok) settings.video.cameras[0].calibration = cal.result();
///         });
///
/// // Feed frames as they arrive from VideoGrabber:
/// cal.feed_frame(frame);   // repeat until view_count() >= 10
///
/// cal.calibrate();         // async — emits calibration_done() when done
/// @endcode
///
/// @see CalibrationData, VideoFrame, CalibrationW
class CalibrationManager : public QObject {
    Q_OBJECT
   public:
    /// @brief Checkerboard geometry parameters.
    struct BoardSpec {
        int cols            = 9;    ///< Number of *inner* corner columns.
        int rows            = 6;    ///< Number of *inner* corner rows.
        double squareSizeMm = 25.0; ///< Physical side length of one square, in mm.
    };

    explicit CalibrationManager(QObject* parent = nullptr);
    ~CalibrationManager() override;

    /// @returns @c true when OpenCV is compiled in (@c MOSAIC_HAVE_OPENCV is defined).
    [[nodiscard]] static bool is_available();

    /// @brief Sets the checkerboard geometry used for detection and calibration.
    ///
    /// Call before the first feed_frame().  Changing the spec after capturing
    /// views requires clear_views() to discard incompatible data.
    ///
    /// @param spec  Board geometry (cols, rows, square size in mm).
    void set_board(const BoardSpec& spec);

    /// @brief Runs corner detection on a single frame.
    ///
    /// This is the main ingestion method.  Call it each time a new frame
    /// arrives from VideoGrabber (or whenever the user clicks "Capture").
    /// The method is synchronous and may take a few milliseconds per frame.
    ///
    /// Emits corners_detected() with a preview image regardless of whether
    /// corners were found.
    ///
    /// @param frame  A valid VideoFrame in BGR8 format.
    /// @returns      @c true if a complete checkerboard was detected and the
    ///               view was accepted into the calibration set.
    bool feed_frame(const VideoFrame& frame);

    /// @returns The number of successfully detected views accumulated so far.
    ///          At least 10 views are recommended before calling calibrate().
    [[nodiscard]] int view_count() const;

    /// @brief Clears all accumulated views and the last calibration result.
    void clear_views();

    /// @brief Runs @c cv::calibrateCamera() on a background thread.
    ///
    /// Emits calibration_started(), calibration_progress(), and
    /// calibration_done() in sequence.  The caller must wait for
    /// calibration_done() before reading result().
    ///
    /// Requires at least 5 accepted views (10+ recommended).
    void calibrate();

    /// @returns The most recent calibration result.  Only valid after
    ///          calibration_done() has been emitted with @c success = @c true.
    [[nodiscard]] CalibrationData result() const;

    /// @returns @c true if a valid calibration result is available.
    [[nodiscard]] bool has_result() const;

   signals:
    /// Emitted after each call to feed_frame().
    /// @param viewIndex  Running index of this detection attempt.
    /// @param found      @c true if all corners were detected and the view was accepted.
    /// @param preview    BGR frame with detected corners drawn on it (RGB for Qt).
    void corners_detected(int viewIndex, bool found, QImage preview);

    /// Emitted when calibrate() starts its background thread.
    void calibration_started();

    /// @param percentage  Rough completion percentage (0–100).
    void calibration_progress(int percentage);

    /// Emitted when calibration finishes.
    /// @param rmsError  Mean reprojection error in pixels.  Negative on failure.
    /// @param success   @c true if calibration converged successfully.
    void calibration_done(double rmsError, bool success);

   private:
    struct Impl;
    std::unique_ptr<Impl> d;
};

} // namespace mosaic