Merge pull request #12147 from D-Alex:master

* add new chessboard detector

The chessboar detector is based on the paper.
Accurate Detection and Localization of Checkerboard Corners for
Calibration Alexander Duda, Udo Frese
British Machine Vision Conference, o.A., 2018.

It utilizes point symmetry of checkerboard corners in combination with a
localized Radon transform approximated by box filters to achieve high
performance even on large images. Here, tests have shown that the
ability to localize checkerboard corners is close to the theoretical
limit of 1/100 of a pixel while being considerably less sensitive
to image noise than standard methods.

* chessboard: add reference to bibtex file

* chessboard: add dependency to opencv_flann

* fix: test chesscorners. It is valid to return an empty list

In case no chessboard was detected it should be valid for the detector
to return an empty list.

For simplifcation, it should be allowed to return any number of corners
if they are flagged as not found.

* fix: opencv.bib remove empty lines

* fix: doc findChessboardCorners replace cvSize with cv::Size

* chessboard tests: factor out logic selecting detector

* chessboard: add unit test for findChessboardCorners2

This is includes a new chessboard generator which supports subpix
corners with high accuracy by wrapping an optimal chessboard using
wrapPerspective.

* fix: chessboard unit test - overwrite of default parameter flag of findCirclesGrid

* chessboard: remove trailing whitespace

* chessboard: fix debug drawing

* chessboard: fix some issues during code review

* chessboard: normalize asymmetric chessboard

* chessboard: fix float double warning

* remove trailing whitespace

* chessboards: fix compiler warnings

* chessboards: fix compiler warnings

* checkerboard: some performance improvements

* chessboard: remove NULL macros for language bindinges from internal headers

* chessboard: shorten license terms

* chessboard: remove unused internal method

* chessboard: set helper functions to static

* chessboard: fix normalizePoints1D using unshifted points

* chessboard: remove wrongly copied text

* chessboard: use CV_CheckTypeEQ macro

* chessboard: comment all NaN checks

* chessboard: use consistent color conversion

* chessboard: use CheckChannelEQ macro

* chessboard: assume gray color image for internal methods

* chessboard: use std::swap

* chessboard: use Mat.dataend

* chessboard: fix compiler warnings

* chessboard: replace some checks witch CV_CHECK macro

* chessboard: fix comparison function for partial sort

* chessboard: small cleanup

* chessboard: use short license header

* chessboard: rename findChessboard2 to findChessboardSB

* chessboard: fix type in unit test
pull/12539/head
Alexander Duda 6 years ago committed by Alexander Alekhin
parent 87b5737293
commit a024593fa6
  1. 9
      doc/opencv.bib
  2. 2
      modules/calib3d/CMakeLists.txt
  3. BIN
      modules/calib3d/doc/pics/checkerboard_radon.png
  4. 30
      modules/calib3d/include/opencv2/calib3d.hpp
  5. 3205
      modules/calib3d/src/chessboard.cpp
  6. 770
      modules/calib3d/src/chessboard.hpp
  7. 177
      modules/calib3d/test/test_chesscorners.cpp

@ -1016,17 +1016,22 @@
year = {2017},
organization = {IEEE}
}
@ARTICLE{gonzalez,
title={Digital Image Fundamentals, Digital Imaging Processing},
author={Gonzalez, Rafael C and others},
year={1987},
publisher={Addison Wesley Publishing Company}
}
@ARTICLE{gruzman,
title={Цифровая обработка изображений в информационных системах},
author={Грузман, И.С. and Киричук, В.С. and Косых, В.П. and Перетягин, Г.И. and Спектор, А.А.},
year={2000},
publisher={Изд-во НГТУ Новосибирск}
}
@INPROCEEDINGS{duda2018,
title = {Accurate Detection and Localization of Checkerboard Corners for Calibration},
year = {2018},
booktitle = {29th British Machine Vision Conference. British Machine Vision Conference (BMVC-29), September 3-6, Newcastle, United Kingdom},
publisher = {BMVA Press},
author = {Alexander Duda and Udo Frese},
}

@ -1,2 +1,2 @@
set(the_description "Camera Calibration and 3D Reconstruction")
ocv_define_module(calib3d opencv_imgproc opencv_features2d WRAP java python)
ocv_define_module(calib3d opencv_imgproc opencv_features2d opencv_flann WRAP java python)

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@ -793,7 +793,7 @@ CV_EXPORTS_W Mat initCameraMatrix2D( InputArrayOfArrays objectPoints,
@param image Source chessboard view. It must be an 8-bit grayscale or color image.
@param patternSize Number of inner corners per a chessboard row and column
( patternSize = cvSize(points_per_row,points_per_colum) = cvSize(columns,rows) ).
( patternSize = cv::Size(points_per_row,points_per_colum) = cv::Size(columns,rows) ).
@param corners Output array of detected corners.
@param flags Various operation flags that can be zero or a combination of the following values:
- **CALIB_CB_ADAPTIVE_THRESH** Use adaptive thresholding to convert the image to black
@ -841,6 +841,34 @@ square grouping and ordering algorithm fails.
CV_EXPORTS_W bool findChessboardCorners( InputArray image, Size patternSize, OutputArray corners,
int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE );
/** @brief Finds the positions of internal corners of the chessboard using a sector based approach.
@param image Source chessboard view. It must be an 8-bit grayscale or color image.
@param patternSize Number of inner corners per a chessboard row and column
( patternSize = cv::Size(points_per_row,points_per_colum) = cv::Size(columns,rows) ).
@param corners Output array of detected corners.
@param flags operation flags for future improvements
The function is analog to findchessboardCorners but uses a localized radon
transformation approximated by box filters being more robust to all sort of
noise, faster on larger images and is able to directly return the sub-pixel
position of the internal chessboard corners. The Method is based on the paper
@cite duda2018 "Accurate Detection and Localization of Checkerboard Corners for
Calibration" demonstrating that the returned sub-pixel positions are more
accurate than the one returned by cornerSubPix allowing a precise camera
calibration for demanding applications.
@note The function requires a white boarder with roughly the same width as one
of the checkerboard fields around the whole board to improve the detection in
various environments. In addition, because of the localized radon
transformation it is beneficial to use round corners for the field corners
which are located on the outside of the board. The following figure illustrates
a sample checkerboard optimized for the detection. However, any other checkerboard
can be used as well.
![Checkerboard](pics/checkerboard_radon.png)
*/
CV_EXPORTS_W bool findChessboardCornersSB(InputArray image,Size patternSize, OutputArray corners,int flags=0);
//! finds subpixel-accurate positions of the chessboard corners
CV_EXPORTS bool find4QuadCornerSubpix( InputArray img, InputOutputArray corners, Size region_size );

File diff suppressed because it is too large Load Diff

@ -0,0 +1,770 @@
// 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 <vector>
#include <set>
#include <map>
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(M_PI*0.25);
branches = 2;
min_scale = 2;
max_scale = 5;
super_resolution = 1;
filter = true;
}
};
public:
FastX(const Parameters &config = Parameters());
virtual ~FastX(){};
void reconfigure(const Parameters &para);
//declaration to be wrapped by rbind
void detect(cv::InputArray image,std::vector<cv::KeyPoint>& 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<cv::KeyPoint>& keypoints,
cv::OutputArray descriptors,
bool useProvidedKeyPoints = false)override;
void detectImpl(const cv::Mat& image,
std::vector<cv::KeyPoint>& keypoints,
std::vector<cv::Mat> &feature_maps,
const cv::Mat& mask=cv::Mat())const;
void detectImpl(const cv::Mat& image,
std::vector<cv::Mat> &rotated_images,
std::vector<cv::Mat> &feature_maps,
const cv::Mat& mask=cv::Mat())const;
void findKeyPoints(const std::vector<cv::Mat> &feature_map,
std::vector<cv::KeyPoint>& keypoints,
const cv::Mat& mask = cv::Mat())const;
std::vector<std::vector<float> > calcAngles(const std::vector<cv::Mat> &rotated_images,
std::vector<cv::KeyPoint> &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<cv::KeyPoint>& 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<cv::KeyPoint>& keypoints, cv::Mat& descriptors)const
{
descriptors = cv::Mat();
detectImpl(image,keypoints);
}
private:
void detectImpl(const cv::Mat& _src, std::vector<cv::KeyPoint>& keypoints, const cv::Mat& mask)const;
virtual void detectImpl(cv::InputArray image, std::vector<cv::KeyPoint>& keypoints, cv::InputArray mask=cv::noArray())const;
void rotate(float angle,const cv::Mat &img,cv::Size size,cv::Mat &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 &center, const cv::Size2f &axes, float angle);
Ellipse(const Ellipse &other);
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
Parameters()
{
chessboard_size = cv::Size(9,6);
min_scale = 2;
max_scale = 4;
super_resolution = true;
max_points = 400;
max_tests = 100;
larger = 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<cv::Point2f> &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 descriping 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<cv::Point2f> &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<cv::Point2f> 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<cv::Point2f> 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<cv::KeyPoint> 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<cv::Point2f> getCellCenters() 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 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 inlcuding only valid corners
*
* \info the contour might be non squared if not all points of the board are defined
*
*/
std::vector<cv::Point2f> getContour()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 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<cv::Point2f> &points);
/**
* \brief Adds one column at the top
*
* \param[in] points The corner coordinates
*
*/
void addRowTop(const std::vector<cv::Point2f> &points);
/**
* \brief Adds one column on the right side
*
* \param[in] points The corner coordinates
*
*/
void addColumnRight(const std::vector<cv::Point2f> &points);
/**
* \brief Adds one row at the bottom
*
* \param[in] points The corner coordinates
*
*/
void addRowBottom(const std::vector<cv::Point2f> &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 anlge of
* either the black or white diagonale is bigger than the x
* and y axis of the board and from a right handed
* coordinate system
*/
void normalizeOrientation(bool bblack=true);
/**
* \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<int,int> getMapping()const;
/**
* \brief Estimates rotation of the board around the camera axis
*/
double estimateRotZ()const;
/**
* \brief Returns true if the cell is black
*
*/
bool isCellBlack(int row,int cola)const;
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
Cell();
bool empty()const; // indicates if the cell is empty (one of its corners has NaN)
int getRow()const;
int getCol()const;
};
// 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<Ellipse> &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 ture if the current corner belongs to at least one
// none empty cell
bool isNaN()const; // returns true if the currnet 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<Cell*> cells; // storage for all board cells
std::vector<cv::Point2f*> 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 row cells
int cols; // number of col cells
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<cv::KeyPoint>& 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<cv::KeyPoint>& 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<cv::KeyPoint>& keypoints,std::vector<cv::Mat> &feature_maps,const cv::Mat& mask)const;
Chessboard::Board detectImpl(const cv::Mat& image,std::vector<cv::Mat> &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<cv::KeyPoint>& 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<cv::KeyPoint>& 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<cv::KeyPoint>& keypoints,std::vector<cv::Mat> &feature_maps,
std::vector<std::vector<float> > &angles ,const cv::Mat& mask)const;
cv::Mat buildData(const std::vector<cv::KeyPoint>& keypoints)const;
std::vector<cv::KeyPoint> getInitialPoints(cv::flann::Index &flann_index,const cv::Mat &data,const cv::KeyPoint &center,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 &center,
float white_angle,float black_angle,float min_response,const cv::Mat &img,
std::vector<Chessboard::Board> &boards)const;
private:
void detectImpl(const cv::Mat&,std::vector<cv::KeyPoint>&, const cv::Mat& mast =cv::Mat())const;
virtual void detectImpl(cv::InputArray image, std::vector<cv::KeyPoint>& keypoints, cv::InputArray mask=cv::noArray())const;
private:
Parameters parameters; // storing the configuration of the detector
};
}} // end namespace details and cv
#endif

@ -73,7 +73,7 @@ void show_points( const Mat& gray, const Mat& expected, const vector<Point2f>& a
#define show_points(...)
#endif
enum Pattern { CHESSBOARD, CIRCLES_GRID, ASYMMETRIC_CIRCLES_GRID };
enum Pattern { CHESSBOARD,CHESSBOARD_SB,CIRCLES_GRID, ASYMMETRIC_CIRCLES_GRID};
class CV_ChessboardDetectorTest : public cvtest::BaseTest
{
@ -83,6 +83,10 @@ protected:
void run(int);
void run_batch(const string& filename);
bool checkByGenerator();
bool checkByGeneratorHighAccuracy();
// wraps calls based on the given pattern
bool findChessboardCornersWrapper(InputArray image, Size patternSize, OutputArray corners,int flags);
Pattern pattern;
int algorithmFlags;
@ -142,6 +146,8 @@ void CV_ChessboardDetectorTest::run( int /*start_from */)
return;*/
switch( pattern )
{
case CHESSBOARD_SB:
checkByGeneratorHighAccuracy(); // not supported by CHESSBOARD
case CHESSBOARD:
checkByGenerator();
if (ts->get_err_code() != cvtest::TS::OK)
@ -183,6 +189,7 @@ void CV_ChessboardDetectorTest::run_batch( const string& filename )
switch( pattern )
{
case CHESSBOARD:
case CHESSBOARD_SB:
folder = string(ts->get_data_path()) + "cv/cameracalibration/";
break;
case CIRCLES_GRID:
@ -238,24 +245,24 @@ void CV_ChessboardDetectorTest::run_batch( const string& filename )
Size pattern_size = expected.size();
vector<Point2f> v;
bool result = false;
int flags = 0;
switch( pattern )
{
case CHESSBOARD:
result = findChessboardCorners(gray, pattern_size, v, CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE);
flags = CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE;
break;
case CIRCLES_GRID:
result = findCirclesGrid(gray, pattern_size, v);
break;
case CHESSBOARD_SB:
case ASYMMETRIC_CIRCLES_GRID:
result = findCirclesGrid(gray, pattern_size, v, CALIB_CB_ASYMMETRIC_GRID | algorithmFlags);
break;
default:
flags = 0;
}
if( result ^ doesContatinChessboard || v.size() != count_exp )
bool result = findChessboardCornersWrapper(gray, pattern_size,v,flags);
if(result ^ doesContatinChessboard || (doesContatinChessboard && v.size() != count_exp))
{
ts->printf( cvtest::TS::LOG, "chessboard is detected incorrectly in %s\n", img_file.c_str() );
ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
show_points( gray, expected, v, result );
return;
}
@ -355,6 +362,28 @@ bool validateData(const ChessBoardGenerator& cbg, const Size& imgSz,
return imgsize * threshold < cbsize;
}
bool CV_ChessboardDetectorTest::findChessboardCornersWrapper(InputArray image, Size patternSize, OutputArray corners,int flags)
{
switch(pattern)
{
case CHESSBOARD:
return findChessboardCorners(image,patternSize,corners,flags);
case CHESSBOARD_SB:
// check default settings until flags have been specified
return findChessboardCornersSB(image,patternSize,corners,0);
case ASYMMETRIC_CIRCLES_GRID:
flags |= CALIB_CB_ASYMMETRIC_GRID | algorithmFlags;
return findCirclesGrid(image, patternSize,corners,flags);
case CIRCLES_GRID:
flags |= CALIB_CB_SYMMETRIC_GRID;
return findCirclesGrid(image, patternSize,corners,flags);
default:
ts->printf( cvtest::TS::LOG, "Internal Error: unsupported chessboard pattern" );
ts->set_failed_test_info( cvtest::TS::FAIL_GENERIC);
}
return false;
}
bool CV_ChessboardDetectorTest::checkByGenerator()
{
bool res = true;
@ -399,7 +428,7 @@ bool CV_ChessboardDetectorTest::checkByGenerator()
vector<Point2f> corners_found;
int flags = i % 8; // need to check branches for all flags
bool found = findChessboardCorners(cb, cbg.cornersSize(), corners_found, flags);
bool found = findChessboardCornersWrapper(cb, cbg.cornersSize(), corners_found, flags);
if (!found)
{
ts->printf( cvtest::TS::LOG, "Chess board corners not found\n" );
@ -421,7 +450,7 @@ bool CV_ChessboardDetectorTest::checkByGenerator()
/* ***** negative ***** */
{
vector<Point2f> corners_found;
bool found = findChessboardCorners(bg, Size(8, 7), corners_found);
bool found = findChessboardCornersWrapper(bg, Size(8, 7), corners_found,0);
if (found)
res = false;
@ -430,7 +459,7 @@ bool CV_ChessboardDetectorTest::checkByGenerator()
vector<Point2f> cg;
Mat cb = cbg(bg, camMat, distCoeffs, cg);
found = findChessboardCorners(cb, Size(3, 4), corners_found);
found = findChessboardCornersWrapper(cb, Size(3, 4), corners_found,0);
if (found)
res = false;
@ -441,7 +470,7 @@ bool CV_ChessboardDetectorTest::checkByGenerator()
Mat sh;
warpAffine(cb, sh, aff, cb.size());
found = findChessboardCorners(sh, cbg.cornersSize(), corners_found);
found = findChessboardCornersWrapper(sh, cbg.cornersSize(), corners_found,0);
if (found)
res = false;
@ -451,7 +480,7 @@ bool CV_ChessboardDetectorTest::checkByGenerator()
cnt.push_back(cg[7+0]); cnt.push_back(cg[7+2]);
cv::drawContours(cb, cnts, -1, Scalar::all(128), FILLED);
found = findChessboardCorners(cb, cbg.cornersSize(), corners_found);
found = findChessboardCornersWrapper(cb, cbg.cornersSize(), corners_found,0);
if (found)
res = false;
@ -461,7 +490,127 @@ bool CV_ChessboardDetectorTest::checkByGenerator()
return res;
}
// generates artificial checkerboards using warpPerspective which supports
// subpixel rendering. The transformation is found by transferring corners to
// the camera image using a virtual plane.
bool CV_ChessboardDetectorTest::checkByGeneratorHighAccuracy()
{
// draw 2D pattern
cv::Size pattern_size(6,5);
int cell_size = 80;
bool bwhite = true;
cv::Mat image = cv::Mat::ones((pattern_size.height+3)*cell_size,(pattern_size.width+3)*cell_size,CV_8UC1)*255;
cv::Mat pimage = image(Rect(cell_size,cell_size,(pattern_size.width+1)*cell_size,(pattern_size.height+1)*cell_size));
pimage = 0;
for(int row=0;row<=pattern_size.height;++row)
{
int y = int(cell_size*row+0.5F);
bool bwhite2 = bwhite;
for(int col=0;col<=pattern_size.width;++col)
{
if(bwhite2)
{
int x = int(cell_size*col+0.5F);
pimage(cv::Rect(x,y,cell_size,cell_size)) = 255;
}
bwhite2 = !bwhite2;
}
bwhite = !bwhite;
}
// generate 2d points
std::vector<Point2f> pts1,pts2,pts1_all,pts2_all;
std::vector<Point3f> pts3d;
for(int row=0;row<pattern_size.height;++row)
{
int y = int(cell_size*(row+2));
for(int col=0;col<pattern_size.width;++col)
{
int x = int(cell_size*(col+2));
pts1_all.push_back(cv::Point2f(x-0.5F,y-0.5F));
}
}
// back project chessboard corners to a virtual plane
double fx = 500;
double fy = 500;
cv::Point2f center(250,250);
double fxi = 1.0/fx;
double fyi = 1.0/fy;
for(auto &&pt : pts1_all)
{
// calc camera ray
cv::Vec3f ray(float((pt.x-center.x)*fxi),float((pt.y-center.y)*fyi),1.0F);
ray /= cv::norm(ray);
// intersect ray with virtual plane
cv::Scalar plane(0,0,1,-1);
cv::Vec3f n(float(plane(0)),float(plane(1)),float(plane(2)));
cv::Point3f p0(0,0,0);
cv::Point3f l0(0,0,0); // camera center in world coordinates
p0.z = float(-plane(3)/plane(2));
double val1 = ray.dot(n);
if(val1 == 0)
{
ts->printf( cvtest::TS::LOG, "Internal Error: ray and plane are parallel" );
ts->set_failed_test_info( cvtest::TS::FAIL_GENERIC);
return false;
}
pts3d.push_back(Point3f(ray/val1*cv::Vec3f((p0-l0)).dot(n))+l0);
}
// generate multiple rotations
for(int i=15;i<90;i=i+15)
{
// project 3d points to new camera
Vec3f rvec(0.0F,0.05F,float(float(i)/180.0*M_PI));
Vec3f tvec(0,0,0);
cv::Mat k = (cv::Mat_<double>(3,3) << fx/2,0,center.x*2, 0,fy/2,center.y, 0,0,1);
cv::projectPoints(pts3d,rvec,tvec,k,cv::Mat(),pts2_all);
// get perspective transform using four correspondences and wrap original image
pts1.clear();
pts2.clear();
pts1.push_back(pts1_all[0]);
pts1.push_back(pts1_all[pattern_size.width-1]);
pts1.push_back(pts1_all[pattern_size.width*pattern_size.height-1]);
pts1.push_back(pts1_all[pattern_size.width*(pattern_size.height-1)]);
pts2.push_back(pts2_all[0]);
pts2.push_back(pts2_all[pattern_size.width-1]);
pts2.push_back(pts2_all[pattern_size.width*pattern_size.height-1]);
pts2.push_back(pts2_all[pattern_size.width*(pattern_size.height-1)]);
Mat m2 = getPerspectiveTransform(pts1,pts2);
Mat out(image.size(),image.type());
warpPerspective(image,out,m2,out.size());
// find checkerboard
vector<Point2f> corners_found;
bool found = findChessboardCornersWrapper(out,pattern_size,corners_found,0);
if (!found)
{
ts->printf( cvtest::TS::LOG, "Chess board corners not found\n" );
ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY );
return false;
}
double err = calcErrorMinError(pattern_size,corners_found,pts2_all);
if(err > 0.08)
{
ts->printf( cvtest::TS::LOG, "bad accuracy of corner guesses" );
ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY );
return false;
}
//cv::cvtColor(out,out,cv::COLOR_GRAY2BGR);
//cv::drawChessboardCorners(out,pattern_size,corners_found,true);
//cv::imshow("img",out);
//cv::waitKey(-1);
}
return true;
}
TEST(Calib3d_ChessboardDetector, accuracy) { CV_ChessboardDetectorTest test( CHESSBOARD ); test.safe_run(); }
TEST(Calib3d_ChessboardDetector2, accuracy) { CV_ChessboardDetectorTest test( CHESSBOARD_SB ); test.safe_run(); }
TEST(Calib3d_CirclesPatternDetector, accuracy) { CV_ChessboardDetectorTest test( CIRCLES_GRID ); test.safe_run(); }
TEST(Calib3d_AsymmetricCirclesPatternDetector, accuracy) { CV_ChessboardDetectorTest test( ASYMMETRIC_CIRCLES_GRID ); test.safe_run(); }
#ifdef HAVE_OPENCV_FLANN

Loading…
Cancel
Save