1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-30 07:43:03 +04:00

Merge pull request #28804 from asmorkalov:as/calib_boards_migration

Migrated chessboard and circles grid detectors to objdetect #28804
  
OpenCV Contrib: https://github.com/opencv/opencv_contrib/pull/4125
OpenCV Extra: https://github.com/opencv/opencv_extra/pull/1375

### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [ ] The PR is proposed to the proper branch
- [ ] There is a reference to the original bug report and related work
- [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [ ] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
Alexander Smorkalov
2026-05-27 16:06:51 +03:00
committed by GitHub
parent dafe7cef97
commit e19b0ebc5c
41 changed files with 880 additions and 456 deletions
File diff suppressed because it is too large Load Diff
+211
View File
@@ -0,0 +1,211 @@
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// Intel License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2000, Intel Corporation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of Intel Corporation may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#include "precomp.hpp"
#include <vector>
#include <algorithm>
namespace cv {
using namespace std;
static void icvGetQuadrangleHypotheses(const std::vector<std::vector< cv::Point > > & contours, const std::vector< cv::Vec4i > & hierarchy, std::vector<std::pair<float, int> >& quads, int class_id)
{
const float min_aspect_ratio = 0.3f;
const float max_aspect_ratio = 3.0f;
const float min_box_size = 10.0f;
for (size_t i = 0; i < contours.size(); ++i)
{
if (hierarchy.at(i)[3] != -1)
continue; // skip holes
const std::vector< cv::Point > & c = contours[i];
cv::RotatedRect box = cv::minAreaRect(c);
float box_size = MAX(box.size.width, box.size.height);
if(box_size < min_box_size)
{
continue;
}
float aspect_ratio = box.size.width/MAX(box.size.height, 1);
if(aspect_ratio < min_aspect_ratio || aspect_ratio > max_aspect_ratio)
{
continue;
}
quads.emplace_back(box_size, class_id);
}
}
static void countClasses(const std::vector<std::pair<float, int> >& pairs, size_t idx1, size_t idx2, std::vector<int>& counts)
{
counts.assign(2, 0);
for(size_t i = idx1; i != idx2; i++)
{
counts[pairs[i].second]++;
}
}
inline bool less_pred(const std::pair<float, int>& p1, const std::pair<float, int>& p2)
{
return p1.first < p2.first;
}
static void fillQuads(Mat & white, Mat & black, double white_thresh, double black_thresh, vector<pair<float, int> > & quads)
{
Mat thresh;
{
vector< vector<Point> > contours;
vector< Vec4i > hierarchy;
threshold(white, thresh, white_thresh, 255, THRESH_BINARY);
findContours(thresh, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
icvGetQuadrangleHypotheses(contours, hierarchy, quads, 1);
}
{
vector< vector<Point> > contours;
vector< Vec4i > hierarchy;
threshold(black, thresh, black_thresh, 255, THRESH_BINARY_INV);
findContours(thresh, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
icvGetQuadrangleHypotheses(contours, hierarchy, quads, 0);
}
}
static bool checkQuads(vector<pair<float, int> > & quads, const cv::Size & size)
{
const size_t min_quads_count = size.width*size.height/2;
std::sort(quads.begin(), quads.end(), less_pred);
// now check if there are many hypotheses with similar sizes
// do this by floodfill-style algorithm
const float size_rel_dev = 0.4f;
for(size_t i = 0; i < quads.size(); i++)
{
size_t j = i + 1;
for(; j < quads.size(); j++)
{
if(quads[j].first/quads[i].first > 1.0f + size_rel_dev)
{
break;
}
}
if(j + 1 > min_quads_count + i)
{
// check the number of black and white squares
std::vector<int> counts;
countClasses(quads, i, j, counts);
const int black_count = cvRound(ceil(size.width/2.0)*ceil(size.height/2.0));
const int white_count = cvRound(floor(size.width/2.0)*floor(size.height/2.0));
if(counts[0] < black_count*0.75 ||
counts[1] < white_count*0.75)
{
continue;
}
return true;
}
}
return false;
}
bool checkChessboard(InputArray _img, Size size)
{
Mat img = _img.getMat();
CV_Assert(img.channels() == 1 && img.depth() == CV_8U);
const int erosion_count = 1;
const float black_level = 20.f;
const float white_level = 130.f;
const float black_white_gap = 70.f;
Mat white;
Mat black;
erode(img, white, Mat(), Point(-1, -1), erosion_count);
dilate(img, black, Mat(), Point(-1, -1), erosion_count);
bool result = false;
for(float thresh_level = black_level; thresh_level < white_level && !result; thresh_level += 20.0f)
{
vector<pair<float, int> > quads;
fillQuads(white, black, thresh_level + black_white_gap, thresh_level, quads);
if (checkQuads(quads, size))
result = true;
}
return result;
}
// does a fast check if a chessboard is in the input image. This is a workaround to
// a problem of cvFindChessboardCorners being slow on images with no chessboard
// - src: input binary image
// - size: chessboard size
// Returns 1 if a chessboard can be in this image and findChessboardCorners should be called,
// 0 if there is no chessboard, -1 in case of error
int checkChessboardBinary(const cv::Mat & img, const cv::Size & size)
{
CV_Assert(img.channels() == 1 && img.depth() == CV_8U);
Mat white = img.clone();
Mat black = img.clone();
int result = 0;
for ( int erosion_count = 0; erosion_count <= 3; erosion_count++ )
{
if ( 1 == result )
break;
if ( 0 != erosion_count ) // first iteration keeps original images
{
erode(white, white, Mat(), Point(-1, -1), 1);
dilate(black, black, Mat(), Point(-1, -1), 1);
}
vector<pair<float, int> > quads;
fillQuads(white, black, 128, 128, quads);
if (checkQuads(quads, size))
result = 1;
}
return result;
}
}
File diff suppressed because it is too large Load Diff
+872
View File
@@ -0,0 +1,872 @@
// 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/features.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(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 &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,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 &center, 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 circle 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:
* p0 --> p1 --> p2 --> p3
*
* \param[in] p0 First point coordinate
* \param[in] p1 Second point coordinate
* \param[in] p2 Third point coordinate
* \param[out] p3 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 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<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] out 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 Returns all cells as mats of four points each describing their corners.
*
* The left top cell has index 0
*
*/
std::vector<cv::Mat> 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<cv::Point2f> 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<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 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<int,int> 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_distance 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<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 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<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 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<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
File diff suppressed because it is too large Load Diff
+199
View File
@@ -0,0 +1,199 @@
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#ifndef CIRCLESGRID_HPP_
#define CIRCLESGRID_HPP_
#include <fstream>
#include <set>
#include <list>
#include <numeric>
#include <map>
namespace cv {
class CirclesGridClusterFinder
{
CirclesGridClusterFinder& operator=(const CirclesGridClusterFinder&);
CirclesGridClusterFinder(const CirclesGridClusterFinder&);
public:
CirclesGridClusterFinder(const CirclesGridFinderParameters &parameters)
{
isAsymmetricGrid = parameters.gridType == CirclesGridFinderParameters::ASYMMETRIC_GRID;
squareSize = parameters.squareSize;
maxRectifiedDistance = parameters.maxRectifiedDistance;
}
void findGrid(const std::vector<Point2f> &points, Size patternSize, std::vector<Point2f>& centers);
//cluster 2d points by geometric coordinates
void hierarchicalClustering(const std::vector<Point2f> &points, const Size &patternSize, std::vector<Point2f> &patternPoints);
private:
void findCorners(const std::vector<Point2f> &hull2f, std::vector<Point2f> &corners);
void findOutsideCorners(const std::vector<Point2f> &corners, std::vector<Point2f> &outsideCorners);
void getSortedCorners(const std::vector<Point2f> &hull2f, const std::vector<Point2f> &patternPoints, const std::vector<Point2f> &corners, const std::vector<Point2f> &outsideCorners, std::vector<Point2f> &sortedCorners);
void rectifyPatternPoints(const std::vector<Point2f> &patternPoints, const std::vector<Point2f> &sortedCorners, std::vector<Point2f> &rectifiedPatternPoints);
void parsePatternPoints(const std::vector<Point2f> &patternPoints, const std::vector<Point2f> &rectifiedPatternPoints, std::vector<Point2f> &centers);
float squareSize, maxRectifiedDistance;
bool isAsymmetricGrid;
Size patternSize;
};
class Graph
{
public:
typedef std::set<size_t> Neighbors;
struct Vertex
{
Neighbors neighbors;
};
typedef std::map<size_t, Vertex> Vertices;
Graph(size_t n);
void addVertex(size_t id);
void addEdge(size_t id1, size_t id2);
void removeEdge(size_t id1, size_t id2);
bool doesVertexExist(size_t id) const;
bool areVerticesAdjacent(size_t id1, size_t id2) const;
size_t getVerticesCount() const;
size_t getDegree(size_t id) const;
const Neighbors& getNeighbors(size_t id) const;
void floydWarshall(Mat &distanceMatrix, int infinity = -1) const;
private:
Vertices vertices;
};
struct Path
{
int firstVertex;
int lastVertex;
int length;
std::vector<size_t> vertices;
Path(int first = -1, int last = -1, int len = -1)
{
firstVertex = first;
lastVertex = last;
length = len;
}
};
class CirclesGridFinder
{
public:
CirclesGridFinder(Size patternSize, const std::vector<Point2f> &testKeypoints,
const CirclesGridFinderParameters &parameters = CirclesGridFinderParameters());
bool findHoles();
static Mat rectifyGrid(Size detectedGridSize, const std::vector<Point2f>& centers, const std::vector<
Point2f> &keypoint, std::vector<Point2f> &warpedKeypoints);
void getHoles(std::vector<Point2f> &holes) const;
void getAsymmetricHoles(std::vector<Point2f> &holes) const;
Size getDetectedGridSize() const;
void drawBasis(const std::vector<Point2f> &basis, Point2f origin, Mat &drawImg) const;
void drawBasisGraphs(const std::vector<Graph> &basisGraphs, Mat &drawImg, bool drawEdges = true,
bool drawVertices = true) const;
void drawHoles(const Mat &srcImage, Mat &drawImage) const;
private:
void computeRNG(Graph &rng, std::vector<Point2f> &vectors, Mat *drawImage = 0) const;
void rng2gridGraph(Graph &rng, std::vector<Point2f> &vectors) const;
void eraseUsedGraph(std::vector<Graph> &basisGraphs) const;
void filterOutliersByDensity(const std::vector<Point2f> &samples, std::vector<Point2f> &filteredSamples);
void findBasis(const std::vector<Point2f> &samples, std::vector<Point2f> &basis,
std::vector<Graph> &basisGraphs);
void findMCS(const std::vector<Point2f> &basis, std::vector<Graph> &basisGraphs);
size_t findLongestPath(std::vector<Graph> &basisGraphs, Path &bestPath);
float computeGraphConfidence(const std::vector<Graph> &basisGraphs, bool addRow, const std::vector<size_t> &points,
const std::vector<size_t> &seeds);
void addHolesByGraph(const std::vector<Graph> &basisGraphs, bool addRow, Point2f basisVec);
size_t findNearestKeypoint(Point2f pt) const;
void addPoint(Point2f pt, std::vector<size_t> &points);
void findCandidateLine(std::vector<size_t> &line, size_t seedLineIdx, bool addRow, Point2f basisVec, std::vector<
size_t> &seeds);
void findCandidateHoles(std::vector<size_t> &above, std::vector<size_t> &below, bool addRow, Point2f basisVec,
std::vector<size_t> &aboveSeeds, std::vector<size_t> &belowSeeds);
static bool areCentersNew(const std::vector<size_t> &newCenters, const std::vector<std::vector<size_t> > &holes);
bool isDetectionCorrect();
static void insertWinner(float aboveConfidence, float belowConfidence, float minConfidence, bool addRow,
const std::vector<size_t> &above, const std::vector<size_t> &below, std::vector<std::vector<
size_t> > &holes);
struct Segment
{
Point2f s;
Point2f e;
Segment(Point2f _s, Point2f _e);
};
//if endpoint is on a segment then function return false
static bool areSegmentsIntersecting(Segment seg1, Segment seg2);
static bool doesIntersectionExist(const std::vector<Segment> &corner, const std::vector<std::vector<Segment> > &segments);
void getCornerSegments(const std::vector<std::vector<size_t> > &points, std::vector<std::vector<Segment> > &segments,
std::vector<Point> &cornerIndices, std::vector<Point> &firstSteps,
std::vector<Point> &secondSteps) const;
size_t getFirstCorner(std::vector<Point> &largeCornerIndices, std::vector<Point> &smallCornerIndices,
std::vector<Point> &firstSteps, std::vector<Point> &secondSteps) const;
static double getDirection(Point2f p1, Point2f p2, Point2f p3);
std::vector<Point2f> keypoints;
std::vector<std::vector<size_t> > holes;
std::vector<std::vector<size_t> > holes2;
std::vector<std::vector<size_t> > *largeHoles;
std::vector<std::vector<size_t> > *smallHoles;
const Size_<size_t> patternSize;
CirclesGridFinderParameters parameters;
bool rotatedGrid = false;
CirclesGridFinder& operator=(const CirclesGridFinder&);
CirclesGridFinder(const CirclesGridFinder&);
};
}
#endif /* CIRCLESGRID_HPP_ */
+8
View File
@@ -48,6 +48,7 @@
#include "opencv2/objdetect/barcode.hpp"
#include "opencv2/imgproc.hpp"
#include <opencv2/core/utils/logger.hpp>
#include "opencv2/core/utility.hpp"
#include "opencv2/core/ocl.hpp"
#include "opencv2/core/private.hpp"
@@ -56,4 +57,11 @@
#include <array>
#include <vector>
namespace cv {
int checkChessboardBinary(const Mat & img, const Size & size);
}
#endif
+240
View File
@@ -0,0 +1,240 @@
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// Intel License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2000, Intel Corporation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of Intel Corporation may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#include "precomp.hpp"
#include <limits>
#include <utility>
#include <algorithm>
#include <math.h>
namespace cv {
inline bool is_smaller(const std::pair<int, float>& p1, const std::pair<int, float>& p2)
{
return p1.second < p2.second;
}
static void orderContours(const std::vector<std::vector<Point> >& contours, Point2f point, std::vector<std::pair<int, float> >& order)
{
order.clear();
size_t i, j, n = contours.size();
for(i = 0; i < n; i++)
{
size_t ni = contours[i].size();
float min_dist = std::numeric_limits<float>::max();
for(j = 0; j < ni; j++)
{
double dist = norm(Point2f((float)contours[i][j].x, (float)contours[i][j].y) - point);
min_dist = (float)MIN((double)min_dist, dist);
}
order.push_back(std::pair<int, float>((int)i, min_dist));
}
std::sort(order.begin(), order.end(), is_smaller);
}
// fit second order curve to a set of 2D points
inline void fitCurve2Order(const std::vector<Point2f>& /*points*/, std::vector<float>& /*curve*/)
{
// TBD
}
inline void findCurvesCross(const std::vector<float>& /*curve1*/, const std::vector<float>& /*curve2*/, Point2f& /*cross_point*/)
{
}
static void findLinesCrossPoint(Point2f origin1, Point2f dir1, Point2f origin2, Point2f dir2, Point2f& cross_point)
{
float det = dir2.x*dir1.y - dir2.y*dir1.x;
Point2f offset = origin2 - origin1;
float alpha = (dir2.x*offset.y - dir2.y*offset.x)/det;
cross_point = origin1 + dir1*alpha;
}
static void findCorner(const std::vector<Point2f>& contour, Point2f point, Point2f& corner)
{
// find the nearest point
double min_dist = std::numeric_limits<double>::max();
int min_idx = -1;
// find corner idx
for(size_t i = 0; i < contour.size(); i++)
{
double dist = norm(contour[i] - point);
if(dist < min_dist)
{
min_dist = dist;
min_idx = (int)i;
}
}
CV_Assert(min_idx >= 0);
// temporary solution, have to make something more precise
corner = contour[min_idx];
return;
}
static int segment_hist_max(const Mat& hist, int& low_thresh, int& high_thresh)
{
Mat bw;
double total_sum = sum(hist).val[0];
double quantile_sum = 0.0;
//double min_quantile = 0.2;
double low_sum = 0;
double max_segment_length = 0;
int max_start_x = -1;
int max_end_x = -1;
int start_x = 0;
const double out_of_bells_fraction = 0.1;
for(int x = 0; x < hist.size[0]; x++)
{
quantile_sum += hist.at<float>(x);
if(quantile_sum < 0.2*total_sum) continue;
if(quantile_sum - low_sum > out_of_bells_fraction*total_sum)
{
if(max_segment_length < x - start_x)
{
max_segment_length = x - start_x;
max_start_x = start_x;
max_end_x = x;
}
low_sum = quantile_sum;
start_x = x;
}
}
if(start_x == -1)
{
return 0;
}
else
{
low_thresh = cvRound(max_start_x + 0.25*(max_end_x - max_start_x));
high_thresh = cvRound(max_start_x + 0.75*(max_end_x - max_start_x));
return 1;
}
}
bool find4QuadCornerSubpix(InputArray _img, InputOutputArray _corners, Size region_size)
{
CV_INSTRUMENT_REGION();
Mat img = _img.getMat(), cornersM = _corners.getMat();
int ncorners = cornersM.checkVector(2, CV_32F);
CV_Assert( ncorners >= 0 );
Point2f* corners = cornersM.ptr<Point2f>();
const int nbins = 256;
float ranges[] = {0, 256};
const float* _ranges = ranges;
Mat hist;
Mat black_comp, white_comp;
for(int i = 0; i < ncorners; i++)
{
int channels = 0;
Rect roi(cvRound(corners[i].x - region_size.width), cvRound(corners[i].y - region_size.height),
region_size.width*2 + 1, region_size.height*2 + 1);
Mat img_roi = img(roi);
calcHist(&img_roi, 1, &channels, Mat(), hist, 1, &nbins, &_ranges);
int black_thresh = 0, white_thresh = 0;
segment_hist_max(hist, black_thresh, white_thresh);
threshold(img, black_comp, black_thresh, 255.0, THRESH_BINARY_INV);
threshold(img, white_comp, white_thresh, 255.0, THRESH_BINARY);
const int erode_count = 1;
erode(black_comp, black_comp, Mat(), Point(-1, -1), erode_count);
erode(white_comp, white_comp, Mat(), Point(-1, -1), erode_count);
std::vector<std::vector<Point> > white_contours, black_contours;
findContours(black_comp, black_contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
findContours(white_comp, white_contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
if(black_contours.size() < 5 || white_contours.size() < 5) continue;
// find two white and black blobs that are close to the input point
std::vector<std::pair<int, float> > white_order, black_order;
orderContours(black_contours, corners[i], black_order);
orderContours(white_contours, corners[i], white_order);
const float max_dist = 10.0f;
if(black_order[0].second > max_dist || black_order[1].second > max_dist ||
white_order[0].second > max_dist || white_order[1].second > max_dist)
{
continue; // there will be no improvement in this corner position
}
const std::vector<Point>* quads[4] = {&black_contours[black_order[0].first], &black_contours[black_order[1].first],
&white_contours[white_order[0].first], &white_contours[white_order[1].first]};
std::vector<Point2f> quads_approx[4];
Point2f quad_corners[4];
for(int k = 0; k < 4; k++)
{
std::vector<Point2f> temp;
for(size_t j = 0; j < quads[k]->size(); j++) temp.push_back((*quads[k])[j]);
approxPolyDP(Mat(temp), quads_approx[k], 0.5, true);
findCorner(quads_approx[k], corners[i], quad_corners[k]);
quad_corners[k] += Point2f(0.5f, 0.5f);
}
// cross two lines
Point2f origin1 = quad_corners[0];
Point2f dir1 = quad_corners[1] - quad_corners[0];
Point2f origin2 = quad_corners[2];
Point2f dir2 = quad_corners[3] - quad_corners[2];
double angle = std::acos(dir1.dot(dir2)/(norm(dir1)*norm(dir2)));
if(cvIsNaN(angle) || cvIsInf(angle) || angle < 0.5 || angle > CV_PI - 0.5) continue;
findLinesCrossPoint(origin1, dir1, origin2, dir2, corners[i]);
}
return true;
}
}