// This file is part of OpenCV project. // It is subject to the license terms in the LICENSE file found in the top-level directory // of this distribution and at http://opencv.org/license.html. #ifndef CHESSBOARD_HPP_ #define CHESSBOARD_HPP_ #include "opencv2/core.hpp" #include "opencv2/features2d.hpp" #include #include #include namespace cv { namespace details{ /** * \brief Fast point sysmetric cross detector based on a localized radon transformation */ class FastX : public cv::Feature2D { public: struct Parameters { float strength; //!< minimal strength of a valid junction in dB float resolution; //!< angle resolution in radians int branches; //!< the number of branches int min_scale; //!< scale level [0..8] int max_scale; //!< scale level [0..8] bool filter; //!< post filter feature map to improve impulse response bool super_resolution; //!< up-sample Parameters() { strength = 40; resolution = float(CV_PI*0.25); branches = 2; min_scale = 2; max_scale = 5; super_resolution = true; filter = true; } }; public: FastX(const Parameters &config = Parameters()); virtual ~FastX(){}; void reconfigure(const Parameters ¶); //declaration to be wrapped by rbind void detect(cv::InputArray image,std::vector& keypoints, cv::InputArray mask=cv::Mat())override {cv::Feature2D::detect(image.getMat(),keypoints,mask.getMat());} virtual void detectAndCompute(cv::InputArray image, cv::InputArray mask, std::vector& keypoints, cv::OutputArray descriptors, bool useProvidedKeyPoints = false)override; void detectImpl(const cv::Mat& image, std::vector& keypoints, std::vector &feature_maps, const cv::Mat& mask=cv::Mat())const; void detectImpl(const cv::Mat& image, std::vector &rotated_images, std::vector &feature_maps, const cv::Mat& mask=cv::Mat())const; void findKeyPoints(const std::vector &feature_map, std::vector& keypoints, const cv::Mat& mask = cv::Mat())const; std::vector > calcAngles(const std::vector &rotated_images, std::vector &keypoints)const; // define pure virtual methods virtual int descriptorSize()const override{return 0;}; virtual int descriptorType()const override{return 0;}; virtual void operator()( cv::InputArray image, cv::InputArray mask, std::vector& keypoints, cv::OutputArray descriptors, bool useProvidedKeypoints=false )const { descriptors.clear(); detectImpl(image.getMat(),keypoints,mask); if(!useProvidedKeypoints) // suppress compiler warning return; return; } protected: virtual void computeImpl( const cv::Mat& image, std::vector& keypoints, cv::Mat& descriptors)const { descriptors = cv::Mat(); detectImpl(image,keypoints); } private: void detectImpl(const cv::Mat& _src, std::vector& keypoints, const cv::Mat& mask)const; virtual void detectImpl(cv::InputArray image, std::vector& keypoints, cv::InputArray mask=cv::noArray())const; void rotate(float angle,cv::InputArray img,cv::Size size,cv::OutputArray out)const; void calcFeatureMap(const cv::Mat &images,cv::Mat& out)const; private: Parameters parameters; }; /** * \brief Ellipse class */ class Ellipse { public: Ellipse(); Ellipse(const cv::Point2f ¢er, const cv::Size2f &axes, float angle); void draw(cv::InputOutputArray img,const cv::Scalar &color = cv::Scalar::all(120))const; bool contains(const cv::Point2f &pt)const; cv::Point2f getCenter()const; const cv::Size2f &getAxes()const; private: cv::Point2f center; cv::Size2f axes; float angle,cosf,sinf; }; /** * \brief Chessboard corner detector * * The detectors tries to find all chessboard corners of an imaged * chessboard and returns them as an ordered vector of KeyPoints. * Thereby, the left top corner has index 0 and the bottom right * corner n*m-1. */ class Chessboard: public cv::Feature2D { public: static const int DUMMY_FIELD_SIZE = 100; // in pixel /** * \brief Configuration of a chessboard corner detector * */ struct Parameters { cv::Size chessboard_size; //!< size of the chessboard int min_scale; //!< scale level [0..8] int max_scale; //!< scale level [0..8] int max_points; //!< maximal number of points regarded int max_tests; //!< maximal number of tested hypothesis bool super_resolution; //!< use super-repsolution for chessboard detection bool larger; //!< indicates if larger boards should be returned bool marker; //!< indicates that valid boards must have a white and black cirlce marker used for orientation Parameters() { chessboard_size = cv::Size(9,6); min_scale = 3; max_scale = 4; super_resolution = true; max_points = 200; max_tests = 50; larger = false; marker = false; } Parameters(int scale,int _max_points): min_scale(scale), max_scale(scale), max_points(_max_points) { chessboard_size = cv::Size(9,6); } }; /** * \brief Gets the 3D objects points for the chessboard assuming the * left top corner is located at the origin. * * \param[in] pattern_size Number of rows and cols of the pattern * \param[in] cell_size Size of one cell * * \returns Returns the object points as CV_32FC3 */ static cv::Mat getObjectPoints(const cv::Size &pattern_size,float cell_size); /** * \brief Class for searching and storing chessboard corners. * * The search is based on a feature map having strong pixel * values at positions where a chessboard corner is located. * * The board must be rectangular but supports empty cells * */ class Board { public: /** * \brief Estimates the position of the next point on a line using cross ratio constrain * * cross ratio: * d12/d34 = d13/d24 * * point order on the line: * pt1 --> pt2 --> pt3 --> pt4 * * \param[in] pt1 First point coordinate * \param[in] pt2 Second point coordinate * \param[in] pt3 Third point coordinate * \param[out] pt4 Forth point coordinate * */ static bool estimatePoint(const cv::Point2f &p0,const cv::Point2f &p1,const cv::Point2f &p2,cv::Point2f &p3); // using 1D homography static bool estimatePoint(const cv::Point2f &p0,const cv::Point2f &p1,const cv::Point2f &p2,const cv::Point2f &p3, cv::Point2f &p4); /** * \brief Checks if all points of a row or column have a valid cross ratio constraint * * cross ratio: * d12/d34 = d13/d24 * * point order on the row/column: * pt1 --> pt2 --> pt3 --> pt4 * * \param[in] points THe points of the row/column * */ static bool checkRowColumn(const std::vector &points); /** * \brief Estimates the search area for the next point on the line using cross ratio * * point order on the line: * (p0) --> p1 --> p2 --> p3 --> search area * * \param[in] p1 First point coordinate * \param[in] p2 Second point coordinate * \param[in] p3 Third point coordinate * \param[in] p Percentage of d34 used for the search area width and height [0..1] * \param[out] ellipse The search area * \param[in] p0 optional point to improve accuracy * * \return Returns false if no search area can be calculated * */ static bool estimateSearchArea(const cv::Point2f &p1,const cv::Point2f &p2,const cv::Point2f &p3,float p, Ellipse &ellipse,const cv::Point2f *p0 =NULL); /** * \brief Estimates the search area for a specific point based on the given homography * * \param[in] H homography describing the transformation from ideal board to real one * \param[in] row Row of the point * \param[in] col Col of the point * \param[in] p Percentage [0..1] * * \return Returns false if no search area can be calculated * */ static Ellipse estimateSearchArea(cv::Mat H,int row, int col,float p,int field_size = DUMMY_FIELD_SIZE); /** * \brief Searches for the maximum in a given search area * * \param[in] map feature map * \param[in] ellipse search area * \param[in] min_val Minimum value of the maximum to be accepted as maximum * * \return Returns a negative value if all points are outside the ellipse * */ static float findMaxPoint(cv::flann::Index &index,const cv::Mat &data,const Ellipse &ellipse,float white_angle,float black_angle,cv::Point2f &pt); /** * \brief Searches for the next point using cross ratio constrain * * \param[in] index flann index * \param[in] data extended flann data * \param[in] pt1 * \param[in] pt2 * \param[in] pt3 * \param[in] white_angle * \param[in] black_angle * \param[in] min_response * \param[out] point The resulting point * * \return Returns false if no point could be found * */ static bool findNextPoint(cv::flann::Index &index,const cv::Mat &data, const cv::Point2f &pt1,const cv::Point2f &pt2, const cv::Point2f &pt3, float white_angle,float black_angle,float min_response,cv::Point2f &point); /** * \brief Creates a new Board object * */ Board(float white_angle=0,float black_angle=0); Board(const cv::Size &size, const std::vector &points,float white_angle=0,float black_angle=0); Board(const Chessboard::Board &other); virtual ~Board(); Board& operator=(const Chessboard::Board &other); /** * \brief Draws the corners into the given image * * \param[in] m The image * \param[out] m The resulting image * \param[in] H optional homography to calculate search area * */ void draw(cv::InputArray m,cv::OutputArray out,cv::InputArray H=cv::Mat())const; /** * \brief Estimates the pose of the chessboard * */ bool estimatePose(const cv::Size2f &real_size,cv::InputArray _K,cv::OutputArray rvec,cv::OutputArray tvec)const; /** * \brief Clears all internal data of the object * */ void clear(); /** * \brief Returns the angle of the black diagnonale * */ float getBlackAngle()const; /** * \brief Returns the angle of the black diagnonale * */ float getWhiteAngle()const; /** * \brief Initializes a 3x3 grid from 9 corner coordinates * * All points must be ordered: * p0 p1 p2 * p3 p4 p5 * p6 p7 p8 * * \param[in] points vector of points * * \return Returns false if the grid could not be initialized */ bool init(const std::vector points); /** * \brief Returns true if the board is empty * */ bool isEmpty() const; /** * \brief Returns all board corners as ordered vector * * The left top corner has index 0 and the bottom right * corner rows*cols-1. All corners which only belong to * empty cells are returned as NaN. */ std::vector getCorners(bool ball=true) const; /** * \brief Returns all board corners as ordered vector of KeyPoints * * The left top corner has index 0 and the bottom right * corner rows*cols-1. * * \param[in] ball if set to false only non empty points are returned * */ std::vector getKeyPoints(bool ball=true) const; /** * \brief Returns the centers of the chessboard cells * * The left top corner has index 0 and the bottom right * corner (rows-1)*(cols-1)-1. * */ std::vector getCellCenters() const; /** * \brief Returns all cells as mats of four points each describing their corners. * * The left top cell has index 0 * */ std::vector getCells(float shrink_factor = 1.0,bool bwhite=true,bool bblack = true) const; /** * \brief Estimates the homography between an ideal board * and reality based on the already recovered points * * \param[in] rect selecting a subset of the already recovered points * \param[in] field_size The field size of the ideal board * */ cv::Mat estimateHomography(cv::Rect rect,int field_size = DUMMY_FIELD_SIZE)const; /** * \brief Estimates the homography between an ideal board * and reality based on the already recovered points * * \param[in] field_size The field size of the ideal board * */ cv::Mat estimateHomography(int field_size = DUMMY_FIELD_SIZE)const; /** * \brief Warp image to match ideal checkerboard * */ cv::Mat warpImage(cv::InputArray image)const; /** * \brief Returns the size of the board * */ cv::Size getSize() const; /** * \brief Returns the number of cols * */ size_t colCount() const; /** * \brief Returns the number of rows * */ size_t rowCount() const; /** * \brief Returns the inner contour of the board including only valid corners * * \info the contour might be non squared if not all points of the board are defined * */ std::vector getContour()const; /** * \brief Masks the found board in the given image * */ void maskImage(cv::InputOutputArray img,const cv::Scalar &color=cv::Scalar::all(0))const; /** * \brief Grows the board in all direction until no more corners are found in the feature map * * \param[in] data CV_32FC1 data of the flann index * \param[in] flann_index flann index * * \returns the number of grows */ int grow(const cv::Mat &data,cv::flann::Index &flann_index); /** * \brief Validates all corners using guided search based on the given homography * * \param[in] data CV_32FC1 data of the flann index * \param[in] flann_index flann index * \param[in] h Homography describing the transformation from ideal board to the real one * \param[in] min_response Min response * * \returns the number of valid corners */ int validateCorners(const cv::Mat &data,cv::flann::Index &flann_index,const cv::Mat &h,float min_response=0); /** * \brief check that no corner is used more than once * * \returns Returns false if a corner is used more than once */ bool checkUnique()const; /** * \brief Returns false if the angles of the contour are smaller than 35° * */ bool validateContour()const; /** \brief delete left column of the board */ bool shrinkLeft(); /** \brief delete right column of the board */ bool shrinkRight(); /** \brief shrink first row of the board */ bool shrinkTop(); /** \brief delete last row of the board */ bool shrinkBottom(); /** * \brief Grows the board to the left by adding one column. * * \param[in] map CV_32FC1 feature map * * \returns Returns false if the feature map has no maxima at the requested positions */ bool growLeft(const cv::Mat &map,cv::flann::Index &flann_index); void growLeft(); /** * \brief Grows the board to the top by adding one row. * * \param[in] map CV_32FC1 feature map * * \returns Returns false if the feature map has no maxima at the requested positions */ bool growTop(const cv::Mat &map,cv::flann::Index &flann_index); void growTop(); /** * \brief Grows the board to the right by adding one column. * * \param[in] map CV_32FC1 feature map * * \returns Returns false if the feature map has no maxima at the requested positions */ bool growRight(const cv::Mat &map,cv::flann::Index &flann_index); void growRight(); /** * \brief Grows the board to the bottom by adding one row. * * \param[in] map CV_32FC1 feature map * * \returns Returns false if the feature map has no maxima at the requested positions */ bool growBottom(const cv::Mat &map,cv::flann::Index &flann_index); void growBottom(); /** * \brief Adds one column on the left side * * \param[in] points The corner coordinates * */ void addColumnLeft(const std::vector &points); /** * \brief Adds one column at the top * * \param[in] points The corner coordinates * */ void addRowTop(const std::vector &points); /** * \brief Adds one column on the right side * * \param[in] points The corner coordinates * */ void addColumnRight(const std::vector &points); /** * \brief Adds one row at the bottom * * \param[in] points The corner coordinates * */ void addRowBottom(const std::vector &points); /** * \brief Rotates the board 90° degrees to the left */ void rotateLeft(); /** * \brief Rotates the board 90° degrees to the right */ void rotateRight(); /** * \brief Flips the board along its local x(width) coordinate direction */ void flipVertical(); /** * \brief Flips the board along its local y(height) coordinate direction */ void flipHorizontal(); /** * \brief Flips and rotates the board so that the angle of * either the black or white diagonal is bigger than the x * and y axis of the board and from a right handed * coordinate system */ void normalizeOrientation(bool bblack=true); /** * \brief Flips and rotates the board so that the marker * is normalized */ bool normalizeMarkerOrientation(); /** * \brief Exchanges the stored board with the board stored in other */ void swap(Chessboard::Board &other); bool operator==(const Chessboard::Board& other) const {return rows*cols == other.rows*other.cols;}; bool operator< (const Chessboard::Board& other) const {return rows*cols < other.rows*other.cols;}; bool operator> (const Chessboard::Board& other) const {return rows*cols > other.rows*other.cols;}; bool operator>= (const cv::Size& size)const { return rows*cols >= size.width*size.height; }; /** * \brief Returns a specific corner * * \info raises runtime_error if row col does not exists */ cv::Point2f& getCorner(int row,int col); /** * \brief Returns true if the cell is empty meaning at least one corner is NaN */ bool isCellEmpty(int row,int col); /** * \brief Returns the mapping from all corners idx to only valid corners idx */ std::map getMapping()const; /** * \brief Returns true if the cell is black * */ bool isCellBlack(int row,int col)const; /** * \brief Returns true if the cell has a round marker at its * center * */ bool hasCellMarker(int row,int col); /** * \brief Detects round markers in the chessboard fields based * on the given image and the already recoverd board corners * * \returns Returns the number of found markes * */ int detectMarkers(cv::InputArray image); /** * \brief Calculates the average edge sharpness for the chessboard * * \param[in] image The image where the chessboard was detected * \param[in] rise_distante Rise distance 0.8 means 10% ... 90% * \param[in] vertical by default only edge response for horiontal lines are calculated * * \returns Scalar(sharpness, average min_val, average max_val) * * \author aduda@krakenrobotik.de */ cv::Scalar calcEdgeSharpness(cv::InputArray image,float rise_distance=0.8,bool vertical=false,cv::OutputArray sharpness=cv::noArray()); /** * \brief Gets the 3D objects points for the chessboard * assuming the left top corner is located at the origin. In * case the board as a marker, the white marker cell is at position zero * * \param[in] cell_size Size of one cell * * \returns Returns the object points as CV_32FC3 */ cv::Mat getObjectPoints(float cell_size)const; /** * \brief Returns the angle the board is rotated agains the x-axis of the image plane * \returns Returns the object points as CV_32FC3 */ float getAngle()const; /** * \brief Returns true if the main direction of the board is close to the image x-axis than y-axis */ bool isHorizontal()const; /** * \brief Updates the search angles */ void setAngles(float white,float black); private: // stores one cell // in general a cell is initialized by the Board so that: // * all corners are always pointing to a valid cv::Point2f // * depending on the position left,top,right and bottom might be set to NaN // * A cell is empty if at least one corner is NaN struct Cell { cv::Point2f *top_left,*top_right,*bottom_right,*bottom_left; // corners Cell *left,*top,*right,*bottom; // neighbouring cells bool black; // set to true if cell is black bool marker; // set to true if cell has a round marker in its center Cell(); bool empty()const; // indicates if the cell is empty (one of its corners has NaN) int getRow()const; int getCol()const; cv::Point2f getCenter()const; bool isInside(const cv::Point2f &pt)const; // check if point is inside the cell }; // corners enum CornerIndex { TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT }; Cell* getCell(int row,int column); // returns a specific cell const Cell* getCell(int row,int column)const; // returns a specific cell void drawEllipses(const std::vector &ellipses); // Iterator for iterating over board corners class PointIter { public: PointIter(Cell *cell,CornerIndex corner_index); PointIter(const PointIter &other); void operator=(const PointIter &other); bool valid() const; // returns if the pointer is pointing to a cell bool left(bool check_empty=false); // moves one corner to the left or returns false bool right(bool check_empty=false); // moves one corner to the right or returns false bool bottom(bool check_empty=false); // moves one corner to the bottom or returns false bool top(bool check_empty=false); // moves one corner to the top or returns false bool checkCorner()const; // returns true if the current corner belongs to at least one // none empty cell bool isNaN()const; // returns true if the current corner is NaN const cv::Point2f* operator*() const; // current corner coordinate cv::Point2f* operator*(); // current corner coordinate const cv::Point2f* operator->() const; // current corner coordinate cv::Point2f* operator->(); // current corner coordinate Cell *getCell(); // current cell private: CornerIndex corner_index; Cell *cell; }; std::vector cells; // storage for all board cells std::vector corners; // storage for all corners Cell *top_left; // pointer to the top left corner of the board in its local coordinate system int rows; // number of inner pattern rows int cols; // number of inner pattern cols float white_angle,black_angle; }; public: /** * \brief Creates a chessboard corner detectors * * \param[in] config Configuration used to detect chessboard corners * */ Chessboard(const Parameters &config = Parameters()); virtual ~Chessboard(); void reconfigure(const Parameters &config = Parameters()); Parameters getPara()const; /* * \brief Detects chessboard corners in the given image. * * The detectors tries to find all chessboard corners of an imaged * chessboard and returns them as an ordered vector of KeyPoints. * Thereby, the left top corner has index 0 and the bottom right * corner n*m-1. * * \param[in] image The image * \param[out] keypoints The detected corners as a vector of ordered KeyPoints * \param[in] mask Currently not supported * */ void detect(cv::InputArray image,std::vector& keypoints, cv::InputArray mask=cv::Mat())override {cv::Feature2D::detect(image.getMat(),keypoints,mask.getMat());} virtual void detectAndCompute(cv::InputArray image,cv::InputArray mask, std::vector& keypoints,cv::OutputArray descriptors, bool useProvidedKeyPoints = false)override; /* * \brief Detects chessboard corners in the given image. * * The detectors tries to find all chessboard corners of an imaged * chessboard and returns them as an ordered vector of KeyPoints. * Thereby, the left top corner has index 0 and the bottom right * corner n*m-1. * * \param[in] image The image * \param[out] keypoints The detected corners as a vector of ordered KeyPoints * \param[out] feature_maps The feature map generated by LRJT and used to find the corners * \param[in] mask Currently not supported * */ void detectImpl(const cv::Mat& image, std::vector& keypoints,std::vector &feature_maps,const cv::Mat& mask)const; Chessboard::Board detectImpl(const cv::Mat& image,std::vector &feature_maps,const cv::Mat& mask)const; // define pure virtual methods virtual int descriptorSize()const override{return 0;}; virtual int descriptorType()const override{return 0;}; virtual void operator()( cv::InputArray image, cv::InputArray mask, std::vector& keypoints, cv::OutputArray descriptors, bool useProvidedKeypoints=false )const { descriptors.clear(); detectImpl(image.getMat(),keypoints,mask); if(!useProvidedKeypoints) // suppress compiler warning return; return; } protected: virtual void computeImpl( const cv::Mat& image, std::vector& keypoints, cv::Mat& descriptors)const { descriptors = cv::Mat(); detectImpl(image,keypoints); } // indicates why a board could not be initialized for a certain keypoint enum BState { MISSING_POINTS = 0, // at least 5 points are needed MISSING_PAIRS = 1, // at least two pairs are needed WRONG_PAIR_ANGLE = 2, // angle between pairs is too small WRONG_CONFIGURATION = 3, // point configuration is wrong and does not belong to a board FOUND_BOARD = 4 // board was found }; void findKeyPoints(const cv::Mat& image, std::vector& keypoints,std::vector &feature_maps, std::vector > &angles ,const cv::Mat& mask)const; cv::Mat buildData(const std::vector& keypoints)const; std::vector getInitialPoints(cv::flann::Index &flann_index,const cv::Mat &data,const cv::KeyPoint ¢er,float white_angle,float black_angle, float min_response = 0)const; BState generateBoards(cv::flann::Index &flann_index,const cv::Mat &data, const cv::KeyPoint ¢er, float white_angle,float black_angle,float min_response,const cv::Mat &img, std::vector &boards)const; private: void detectImpl(const cv::Mat&,std::vector&, const cv::Mat& mast =cv::Mat())const; virtual void detectImpl(cv::InputArray image, std::vector& keypoints, cv::InputArray mask=cv::noArray())const; private: Parameters parameters; // storing the configuration of the detector }; }} // end namespace details and cv #endif