1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-30 15:53: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
+1
View File
@@ -2,6 +2,7 @@ set(the_description "Object Detection")
ocv_define_module(objdetect
opencv_core
opencv_imgproc
opencv_features
opencv_3d
OPTIONAL
opencv_dnn
@@ -45,6 +45,7 @@
#define OPENCV_OBJDETECT_HPP
#include "opencv2/core.hpp"
#include "opencv2/features.hpp"
#include "opencv2/objdetect/aruco_detector.hpp"
#include "opencv2/objdetect/graphical_code_detector.hpp"
#include "opencv2/objdetect/mcc_checker_detector.hpp"
@@ -316,6 +317,269 @@ public:
CV_WRAP void setArucoParameters(const aruco::DetectorParameters& params);
};
enum { CALIB_CB_ADAPTIVE_THRESH = 1,
CALIB_CB_NORMALIZE_IMAGE = 2,
CALIB_CB_FILTER_QUADS = 4,
CALIB_CB_FAST_CHECK = 8,
CALIB_CB_EXHAUSTIVE = 16,
CALIB_CB_ACCURACY = 32,
CALIB_CB_LARGER = 64,
CALIB_CB_MARKER = 128,
CALIB_CB_PLAIN = 256
};
enum { CALIB_CB_SYMMETRIC_GRID = 1,
CALIB_CB_ASYMMETRIC_GRID = 2,
CALIB_CB_CLUSTERING = 4
};
/** @brief Finds the positions of internal corners of the chessboard.
@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_column) = 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:
- @ref CALIB_CB_ADAPTIVE_THRESH Use adaptive thresholding to convert the image to black
and white, rather than a fixed threshold level (computed from the average image brightness).
- @ref CALIB_CB_NORMALIZE_IMAGE Normalize the image gamma with equalizeHist before
applying fixed or adaptive thresholding.
- @ref CALIB_CB_FILTER_QUADS Use additional criteria (like contour area, perimeter,
square-like shape) to filter out false quads extracted at the contour retrieval stage.
- @ref CALIB_CB_FAST_CHECK Run a fast check on the image that looks for chessboard corners,
and shortcut the call if none is found. This can drastically speed up the call in the
degenerate condition when no chessboard is observed.
- @ref CALIB_CB_PLAIN All other flags are ignored. The input image is taken as is.
No image processing is done to improve to find the checkerboard. This has the effect of speeding up the
execution of the function but could lead to not recognizing the checkerboard if the image
is not previously binarized in the appropriate manner.
The function attempts to determine whether the input image is a view of the chessboard pattern and
locate the internal chessboard corners. The function returns a non-zero value if all of the corners
are found and they are placed in a certain order (row by row, left to right in every row).
Otherwise, if the function fails to find all the corners or reorder them, it returns 0. For example,
a regular chessboard has 8 x 8 squares and 7 x 7 internal corners, that is, points where the black
squares touch each other. The detected coordinates are approximate, and to determine their positions
more accurately, the function calls #cornerSubPix. You also may use the function #cornerSubPix with
different parameters if returned coordinates are not accurate enough.
Sample usage of detecting and drawing chessboard corners: :
@code
Size patternsize(8,6); //interior number of corners
Mat gray = ....; //source image
vector<Point2f> corners; //this will be filled by the detected corners
//CALIB_CB_FAST_CHECK saves a lot of time on images
//that do not contain any chessboard corners
bool patternfound = findChessboardCorners(gray, patternsize, corners,
CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE
+ CALIB_CB_FAST_CHECK);
if(patternfound)
cornerSubPix(gray, corners, Size(11, 11), Size(-1, -1),
TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));
drawChessboardCorners(img, patternsize, Mat(corners), patternfound);
@endcode
@note The function requires white space (like a square-thick border, the wider the better) around
the board to make the detection more robust in various environments. Otherwise, if there is no
border and the background is dark, the outer black squares cannot be segmented properly and so the
square grouping and ordering algorithm fails.
Use the `generate_pattern.py` Python script (@ref tutorial_camera_calibration_pattern)
to create the desired checkerboard pattern.
*/
CV_EXPORTS_W bool findChessboardCorners( InputArray image, Size patternSize, OutputArray corners,
int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE );
/*
Checks whether the image contains chessboard of the specific size or not.
If yes, nonzero value is returned.
*/
CV_EXPORTS_W bool checkChessboard(InputArray img, Size size);
/** @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_column) = 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:
- @ref CALIB_CB_NORMALIZE_IMAGE Normalize the image gamma with equalizeHist before detection.
- @ref CALIB_CB_EXHAUSTIVE Run an exhaustive search to improve detection rate.
- @ref CALIB_CB_ACCURACY Up sample input image to improve sub-pixel accuracy due to aliasing effects.
- @ref CALIB_CB_LARGER The detected pattern is allowed to be larger than patternSize (see description).
- @ref CALIB_CB_MARKER The detected pattern must have a marker (see description).
This should be used if an accurate camera calibration is required.
@param meta Optional output array of detected corners (CV_8UC1 and size = cv::Size(columns,rows)).
Each entry stands for one corner of the pattern and can have one of the following values:
- 0 = no meta data attached
- 1 = left-top corner of a black cell
- 2 = left-top corner of a white cell
- 3 = left-top corner of a black cell with a white marker dot
- 4 = left-top corner of a white cell with a black marker dot (pattern origin in case of markers otherwise first corner)
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.
In the case, the flags @ref CALIB_CB_LARGER or @ref CALIB_CB_MARKER are given,
the result can be recovered from the optional meta array. Both flags are
helpful to use calibration patterns exceeding the field of view of the camera.
These oversized patterns allow more accurate calibrations as corners can be
utilized, which are as close as possible to the image borders. For a
consistent coordinate system across all images, the optional marker (see image
below) can be used to move the origin of the board to the location where the
black circle is located.
@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.
Use the `generate_pattern.py` Python script (@ref tutorial_camera_calibration_pattern)
to create the corresponding checkerboard pattern:
\image html pics/checkerboard_radon.png width=60%
*/
CV_EXPORTS_AS(findChessboardCornersSBWithMeta)
bool findChessboardCornersSB(InputArray image,Size patternSize, OutputArray corners,
int flags,OutputArray meta);
/** @overload */
CV_EXPORTS_W inline
bool findChessboardCornersSB(InputArray image, Size patternSize, OutputArray corners,
int flags = 0)
{
return findChessboardCornersSB(image, patternSize, corners, flags, noArray());
}
/** @brief Estimates the sharpness of a detected chessboard.
Image sharpness, as well as brightness, are a critical parameter for accuracte
camera calibration. For accessing these parameters for filtering out
problematic calibraiton images, this method calculates edge profiles by traveling from
black to white chessboard cell centers. Based on this, the number of pixels is
calculated required to transit from black to white. This width of the
transition area is a good indication of how sharp the chessboard is imaged
and should be below ~3.0 pixels.
@param image Gray image used to find chessboard corners
@param patternSize Size of a found chessboard pattern
@param corners Corners found by #findChessboardCornersSB
@param rise_distance Rise distance 0.8 means 10% ... 90% of the final signal strength
@param vertical By default edge responses for horizontal lines are calculated
@param sharpness Optional output array with a sharpness value for calculated edge responses (see description)
The optional sharpness array is of type CV_32FC1 and has for each calculated
profile one row with the following five entries:
* 0 = x coordinate of the underlying edge in the image
* 1 = y coordinate of the underlying edge in the image
* 2 = width of the transition area (sharpness)
* 3 = signal strength in the black cell (min brightness)
* 4 = signal strength in the white cell (max brightness)
@return Scalar(average sharpness, average min brightness, average max brightness,0)
*/
CV_EXPORTS_W Scalar estimateChessboardSharpness(InputArray image, Size patternSize, InputArray corners,
float rise_distance=0.8F,bool vertical=false,
OutputArray sharpness=noArray());
//! finds subpixel-accurate positions of the chessboard corners
CV_EXPORTS_W bool find4QuadCornerSubpix( InputArray img, InputOutputArray corners, Size region_size );
/** @brief Renders the detected chessboard corners.
@param image Destination image. It must be an 8-bit color image.
@param patternSize Number of inner corners per a chessboard row and column
(patternSize = cv::Size(points_per_row,points_per_column)).
@param corners Array of detected corners, the output of #findChessboardCorners.
@param patternWasFound Parameter indicating whether the complete board was found or not. The
return value of #findChessboardCorners should be passed here.
The function draws individual chessboard corners detected either as red circles if the board was not
found, or as colored corners connected with lines if the board was found.
*/
CV_EXPORTS_W void drawChessboardCorners( InputOutputArray image, Size patternSize,
InputArray corners, bool patternWasFound );
struct CV_EXPORTS_W_SIMPLE CirclesGridFinderParameters
{
CV_WRAP CirclesGridFinderParameters();
CV_PROP_RW cv::Size2f densityNeighborhoodSize;
CV_PROP_RW float minDensity;
CV_PROP_RW int kmeansAttempts;
CV_PROP_RW int minDistanceToAddKeypoint;
CV_PROP_RW int keypointScale;
CV_PROP_RW float minGraphConfidence;
CV_PROP_RW float vertexGain;
CV_PROP_RW float vertexPenalty;
CV_PROP_RW float existingVertexGain;
CV_PROP_RW float edgeGain;
CV_PROP_RW float edgePenalty;
CV_PROP_RW float convexHullFactor;
CV_PROP_RW float minRNGEdgeSwitchDist;
enum GridType
{
SYMMETRIC_GRID, ASYMMETRIC_GRID
};
CV_PROP_RW GridType gridType;
CV_PROP_RW float squareSize; //!< Distance between two adjacent points. Used by CALIB_CB_CLUSTERING.
CV_PROP_RW float maxRectifiedDistance; //!< Max deviation from prediction. Used by CALIB_CB_CLUSTERING.
};
/** @brief Finds centers in the grid of circles.
@param image grid view of input circles; it must be an 8-bit grayscale or color image.
@param patternSize number of circles per row and column
( patternSize = Size(points_per_row, points_per_column) ).
@param centers output array of detected centers.
@param flags various operation flags that can be one of the following values:
- @ref CALIB_CB_SYMMETRIC_GRID uses symmetric pattern of circles.
- @ref CALIB_CB_ASYMMETRIC_GRID uses asymmetric pattern of circles.
- @ref CALIB_CB_CLUSTERING uses a special algorithm for grid detection. It is more robust to
perspective distortions but much more sensitive to background clutter.
@param blobDetector feature detector that finds blobs like dark circles on light background.
If `blobDetector` is NULL then `image` represents Point2f array of candidates.
@param parameters struct for finding circles in a grid pattern.
The function attempts to determine whether the input image contains a grid of circles. If it is, the
function locates centers of the circles. The function returns a non-zero value if all of the centers
have been found and they have been placed in a certain order (row by row, left to right in every
row). Otherwise, if the function fails to find all the corners or reorder them, it returns 0.
Sample usage of detecting and drawing the centers of circles: :
@code
Size patternsize(7,7); //number of centers
Mat gray = ...; //source image
vector<Point2f> centers; //this will be filled by the detected centers
bool patternfound = findCirclesGrid(gray, patternsize, centers);
drawChessboardCorners(img, patternsize, Mat(centers), patternfound);
@endcode
@note The function requires white space (like a square-thick border, the wider the better) around
the board to make the detection more robust in various environments.
*/
CV_EXPORTS_W bool findCirclesGrid( InputArray image, Size patternSize,
OutputArray centers, int flags,
const Ptr<FeatureDetector> &blobDetector,
const CirclesGridFinderParameters& parameters);
/** @overload */
CV_EXPORTS_W bool findCirclesGrid( InputArray image, Size patternSize,
OutputArray centers, int flags = CALIB_CB_SYMMETRIC_GRID,
const Ptr<FeatureDetector> &blobDetector = cv::SimpleBlobDetector::create());
//! @}
}
@@ -65,5 +65,9 @@
"suffix": "Ljava_util_List",
"v_type": "vector_NativeByteArray"
}
},
"func_arg_fix" : {
"findChessboardCorners" : { "corners" : {"ctype" : "vector_Point2f"} },
"drawChessboardCorners" : { "corners" : {"ctype" : "vector_Point2f"} }
}
}
@@ -0,0 +1,100 @@
package org.opencv.test.objdetect;
import java.util.ArrayList;
import org.opencv.cv3d.Cv3d;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfDouble;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.MatOfPoint3f;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.objdetect.Objdetect;
import org.opencv.test.OpenCVTestCase;
import org.opencv.imgproc.Imgproc;
public class ObjdetectTest extends OpenCVTestCase {
Size size;
@Override
protected void setUp() throws Exception {
super.setUp();
size = new Size(3, 3);
}
public void testFindChessboardCornersMatSizeMat() {
Size patternSize = new Size(9, 6);
MatOfPoint2f corners = new MatOfPoint2f();
Objdetect.findChessboardCorners(grayChess, patternSize, corners);
assertFalse(corners.empty());
}
public void testFindChessboardCornersMatSizeMatInt() {
Size patternSize = new Size(9, 6);
MatOfPoint2f corners = new MatOfPoint2f();
Objdetect.findChessboardCorners(grayChess, patternSize, corners, Objdetect.CALIB_CB_ADAPTIVE_THRESH + Objdetect.CALIB_CB_NORMALIZE_IMAGE
+ Objdetect.CALIB_CB_FAST_CHECK);
assertFalse(corners.empty());
}
public void testFind4QuadCornerSubpix() {
Size patternSize = new Size(9, 6);
MatOfPoint2f corners = new MatOfPoint2f();
Size region_size = new Size(5, 5);
Objdetect.findChessboardCorners(grayChess, patternSize, corners);
Objdetect.find4QuadCornerSubpix(grayChess, corners, region_size);
assertFalse(corners.empty());
}
public void testFindCirclesGridMatSizeMat() {
int size = 300;
Mat img = new Mat(size, size, CvType.CV_8U);
img.setTo(new Scalar(255));
Mat centers = new Mat();
assertFalse(Objdetect.findCirclesGrid(img, new Size(5, 5), centers));
for (int i = 0; i < 5; i++)
for (int j = 0; j < 5; j++) {
Point pt = new Point(size * (2 * i + 1) / 10, size * (2 * j + 1) / 10);
Imgproc.circle(img, pt, 10, new Scalar(0), -1);
}
assertTrue(Objdetect.findCirclesGrid(img, new Size(5, 5), centers));
assertEquals(1, centers.rows());
assertEquals(25, centers.cols());
assertEquals(CvType.CV_32FC2, centers.type());
}
public void testFindCirclesGridMatSizeMatInt() {
int size = 300;
Mat img = new Mat(size, size, CvType.CV_8U);
img.setTo(new Scalar(255));
Mat centers = new Mat();
assertFalse(Objdetect.findCirclesGrid(img, new Size(3, 5), centers, Objdetect.CALIB_CB_CLUSTERING
| Objdetect.CALIB_CB_ASYMMETRIC_GRID));
int step = size * 2 / 15;
int offsetx = size / 6;
int offsety = (size - 4 * step) / 2;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 5; j++) {
Point pt = new Point(offsetx + (2 * i + j % 2) * step, offsety + step * j);
Imgproc.circle(img, pt, 10, new Scalar(0), -1);
}
assertTrue(Objdetect.findCirclesGrid(img, new Size(3, 5), centers, Objdetect.CALIB_CB_CLUSTERING
| Objdetect.CALIB_CB_ASYMMETRIC_GRID));
assertEquals(1, centers.rows());
assertEquals(15, centers.cols());
assertEquals(CvType.CV_32FC2, centers.type());
}
}
@@ -3,5 +3,10 @@
"QRCodeDetectorAruco": {
"getDetectorParameters": { "declaration" : [""], "implementation" : [""] }
}
},
"func_arg_fix" : {
"Calib" : {
"findCirclesGrid" : { "blobDetector" : {"defval" : "cv::SimpleBlobDetector::create()"} }
}
}
}
@@ -0,0 +1,88 @@
import XCTest
import OpenCV
class ObjdetectTest: OpenCVTestCase {
var size = Size()
override func setUp() {
super.setUp()
size = Size(width: 3, height: 3)
}
override func tearDown() {
super.tearDown()
}
func testFindChessboardCornersMatSizeMat() {
let patternSize = Size(width: 9, height: 6)
let corners = MatOfPoint2f()
Calib.findChessboardCorners(image: grayChess, patternSize: patternSize, corners: corners)
XCTAssertFalse(corners.empty())
}
func testFindChessboardCornersMatSizeMatInt() {
let patternSize = Size(width: 9, height: 6)
let corners = MatOfPoint2f()
Calib.findChessboardCorners(image: grayChess, patternSize: patternSize, corners: corners, flags: Calib.CALIB_CB_ADAPTIVE_THRESH + Calib.CALIB_CB_NORMALIZE_IMAGE + Calib.CALIB_CB_FAST_CHECK)
XCTAssertFalse(corners.empty())
}
func testFind4QuadCornerSubpix() {
let patternSize = Size(width: 9, height: 6)
let corners = MatOfPoint2f()
let region_size = Size(width: 5, height: 5)
Calib.findChessboardCorners(image: grayChess, patternSize: patternSize, corners: corners)
Calib.find4QuadCornerSubpix(img: grayChess, corners: corners, region_size: region_size)
XCTAssertFalse(corners.empty())
}
func testFindCirclesGridMatSizeMat() {
let size = 300
let img = Mat(rows:Int32(size), cols:Int32(size), type:CvType.CV_8U)
img.setTo(scalar: Scalar(255))
let centers = Mat()
XCTAssertFalse(Calib.findCirclesGrid(image: img, patternSize: Size(width: 5, height: 5), centers: centers))
for i in 0..<5 {
for j in 0..<5 {
let x = Int32(size * (2 * i + 1) / 10)
let y = Int32(size * (2 * j + 1) / 10)
let pt = Point(x: x, y: y)
Imgproc.circle(img: img, center: pt, radius: 10, color: Scalar(0), thickness: -1)
}
}
XCTAssert(Calib.findCirclesGrid(image: img, patternSize:Size(width:5, height:5), centers:centers))
XCTAssertEqual(25, centers.rows())
XCTAssertEqual(1, centers.cols())
XCTAssertEqual(CvType.CV_32FC2, centers.type())
}
func testFindCirclesGridMatSizeMatInt() {
let size:Int32 = 300
let img = Mat(rows:size, cols: size, type: CvType.CV_8U)
img.setTo(scalar: Scalar(255))
let centers = Mat()
XCTAssertFalse(Calib.findCirclesGrid(image: img, patternSize: Size(width: 3, height: 5), centers: centers, flags: Calib.CALIB_CB_CLUSTERING | Calib.CALIB_CB_ASYMMETRIC_GRID))
let step = size * 2 / 15
let offsetx = size / 6
let offsety = (size - 4 * step) / 2
for i:Int32 in 0...2 {
for j:Int32 in 0...4 {
let pt = Point(x: offsetx + (2 * i + j % 2) * step, y: offsety + step * j)
Imgproc.circle(img: img, center: pt, radius: 10, color: Scalar(0), thickness: -1)
}
}
XCTAssert(Calib.findCirclesGrid(image: img, patternSize: Size(width: 3, height: 5), centers: centers, flags: Calib.CALIB_CB_CLUSTERING | Calib.CALIB_CB_ASYMMETRIC_GRID))
XCTAssertEqual(15, centers.rows())
XCTAssertEqual(1, centers.cols())
XCTAssertEqual(CvType.CV_32FC2, centers.type())
}
}
@@ -0,0 +1,42 @@
#include "perf_precomp.hpp"
namespace opencv_test
{
using namespace perf;
typedef tuple<std::string, cv::Size> String_Size_t;
typedef perf::TestBaseWithParam<String_Size_t> String_Size;
PERF_TEST_P(String_Size, asymm_circles_grid, testing::Values(
String_Size_t("cv/cameracalibration/asymmetric_circles/acircles1.png", Size(7,13)),
String_Size_t("cv/cameracalibration/asymmetric_circles/acircles2.png", Size(7,13)),
String_Size_t("cv/cameracalibration/asymmetric_circles/acircles3.png", Size(7,13)),
String_Size_t("cv/cameracalibration/asymmetric_circles/acircles4.png", Size(5,5)),
String_Size_t("cv/cameracalibration/asymmetric_circles/acircles5.png", Size(5,5)),
String_Size_t("cv/cameracalibration/asymmetric_circles/acircles6.png", Size(5,5)),
String_Size_t("cv/cameracalibration/asymmetric_circles/acircles7.png", Size(3,9)),
String_Size_t("cv/cameracalibration/asymmetric_circles/acircles8.png", Size(3,9)),
String_Size_t("cv/cameracalibration/asymmetric_circles/acircles9.png", Size(3,9))
)
)
{
string filename = getDataPath(get<0>(GetParam()));
Size gridSize = get<1>(GetParam());
Mat frame = imread(filename);
if (frame.empty())
FAIL() << "Unable to load source image " << filename;
vector<Point2f> ptvec;
ptvec.resize(gridSize.area());
cvtColor(frame, frame, COLOR_BGR2GRAY);
declare.in(frame).out(ptvec);
TEST_CYCLE() ASSERT_TRUE(findCirclesGrid(frame, gridSize, ptvec, CALIB_CB_CLUSTERING | CALIB_CB_ASYMMETRIC_GRID));
SANITY_CHECK(ptvec, 2);
}
} // namespace
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;
}
}
@@ -0,0 +1,331 @@
/*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*/
#include "test_precomp.hpp"
#include "test_chessboardgenerator.hpp"
namespace cv {
ChessBoardGenerator::ChessBoardGenerator(const Size& _patternSize) : sensorWidth(32), sensorHeight(24),
squareEdgePointsNum(200), min_cos(std::sqrt(3.f)*0.5f), cov(0.5),
patternSize(_patternSize), rendererResolutionMultiplier(4), tvec(Mat::zeros(1, 3, CV_32F))
{
rvec.create(3, 1, CV_32F);
Rodrigues(Mat::eye(3, 3, CV_32F), rvec);
}
void ChessBoardGenerator::generateEdge(const Point3f& p1, const Point3f& p2, vector<Point3f>& out) const
{
Point3f step = (p2 - p1) * (1.f/squareEdgePointsNum);
for(size_t n = 0; n < squareEdgePointsNum; ++n)
out.push_back( p1 + step * (float)n);
}
Size ChessBoardGenerator::cornersSize() const
{
return Size(patternSize.width-1, patternSize.height-1);
}
struct Mult
{
float m;
Mult(int mult) : m((float)mult) {}
Point2f operator()(const Point2f& p)const { return p * m; }
};
void ChessBoardGenerator::generateBasis(Point3f& pb1, Point3f& pb2) const
{
RNG& rng = theRNG();
Vec3f n;
for(;;)
{
n[0] = rng.uniform(-1.f, 1.f);
n[1] = rng.uniform(-1.f, 1.f);
n[2] = rng.uniform(0.0f, 1.f);
float len = (float)norm(n);
if (len < 1e-3)
continue;
n[0]/=len;
n[1]/=len;
n[2]/=len;
if (n[2] > min_cos)
break;
}
Vec3f n_temp = n; n_temp[0] += 100;
Vec3f b1 = n.cross(n_temp);
Vec3f b2 = n.cross(b1);
float len_b1 = (float)norm(b1);
float len_b2 = (float)norm(b2);
pb1 = Point3f(b1[0]/len_b1, b1[1]/len_b1, b1[2]/len_b1);
pb2 = Point3f(b2[0]/len_b1, b2[1]/len_b2, b2[2]/len_b2);
}
Mat ChessBoardGenerator::generateChessBoard(const Mat& bg, const Mat& camMat, const Mat& distCoeffs,
const Point3f& zero, const Point3f& pb1, const Point3f& pb2,
float sqWidth, float sqHeight, const vector<Point3f>& whole,
vector<Point2f>& corners) const
{
vector< vector<Point> > squares_black;
for(int i = 0; i < patternSize.width; ++i)
for(int j = 0; j < patternSize.height; ++j)
if ( (i % 2 == 0 && j % 2 == 0) || (i % 2 != 0 && j % 2 != 0) )
{
vector<Point3f> pts_square3d;
vector<Point2f> pts_square2d;
Point3f p1 = zero + (i + 0) * sqWidth * pb1 + (j + 0) * sqHeight * pb2;
Point3f p2 = zero + (i + 1) * sqWidth * pb1 + (j + 0) * sqHeight * pb2;
Point3f p3 = zero + (i + 1) * sqWidth * pb1 + (j + 1) * sqHeight * pb2;
Point3f p4 = zero + (i + 0) * sqWidth * pb1 + (j + 1) * sqHeight * pb2;
generateEdge(p1, p2, pts_square3d);
generateEdge(p2, p3, pts_square3d);
generateEdge(p3, p4, pts_square3d);
generateEdge(p4, p1, pts_square3d);
projectPoints(pts_square3d, rvec, tvec, camMat, distCoeffs, pts_square2d);
squares_black.resize(squares_black.size() + 1);
vector<Point2f> temp;
approxPolyDP(pts_square2d, temp, 1.0, true);
transform(temp.begin(), temp.end(), back_inserter(squares_black.back()), Mult(rendererResolutionMultiplier));
}
/* calculate corners */
corners3d.clear();
for(int j = 0; j < patternSize.height - 1; ++j)
for(int i = 0; i < patternSize.width - 1; ++i)
corners3d.push_back(zero + (i + 1) * sqWidth * pb1 + (j + 1) * sqHeight * pb2);
corners.clear();
projectPoints(corners3d, rvec, tvec, camMat, distCoeffs, corners);
vector<Point3f> whole3d;
vector<Point2f> whole2d;
generateEdge(whole[0], whole[1], whole3d);
generateEdge(whole[1], whole[2], whole3d);
generateEdge(whole[2], whole[3], whole3d);
generateEdge(whole[3], whole[0], whole3d);
projectPoints(whole3d, rvec, tvec, camMat, distCoeffs, whole2d);
vector<Point2f> temp_whole2d;
approxPolyDP(whole2d, temp_whole2d, 1.0, true);
vector< vector<Point > > whole_contour(1);
transform(temp_whole2d.begin(), temp_whole2d.end(),
back_inserter(whole_contour.front()), Mult(rendererResolutionMultiplier));
Mat result;
if (rendererResolutionMultiplier == 1)
{
result = bg.clone();
drawContours(result, whole_contour, -1, Scalar::all(255), FILLED, LINE_AA);
drawContours(result, squares_black, -1, Scalar::all(0), FILLED, LINE_AA);
}
else
{
Mat tmp;
resize(bg, tmp, bg.size() * rendererResolutionMultiplier, 0, 0, INTER_LINEAR_EXACT);
drawContours(tmp, whole_contour, -1, Scalar::all(255), FILLED, LINE_AA);
drawContours(tmp, squares_black, -1, Scalar::all(0), FILLED, LINE_AA);
resize(tmp, result, bg.size(), 0, 0, INTER_AREA);
}
return result;
}
Mat ChessBoardGenerator::operator ()(const Mat& bg, const Mat& camMat, const Mat& distCoeffs, vector<Point2f>& corners) const
{
cov = std::min(cov, 0.8);
double fovx, fovy, focalLen;
Point2d principalPoint;
double aspect;
calibrationMatrixValues( camMat, bg.size(), sensorWidth, sensorHeight,
fovx, fovy, focalLen, principalPoint, aspect);
RNG& rng = theRNG();
float d1 = static_cast<float>(rng.uniform(0.1, 10.0));
float ah = static_cast<float>(rng.uniform(-fovx/2 * cov, fovx/2 * cov) * CV_PI / 180);
float av = static_cast<float>(rng.uniform(-fovy/2 * cov, fovy/2 * cov) * CV_PI / 180);
Point3f p;
p.z = std::cos(ah) * d1;
p.x = std::sin(ah) * d1;
p.y = p.z * std::tan(av);
Point3f pb1, pb2;
generateBasis(pb1, pb2);
float cbHalfWidth = static_cast<float>(norm(p) * std::sin( std::min(fovx, fovy) * 0.5 * CV_PI / 180));
float cbHalfHeight = cbHalfWidth * patternSize.height / patternSize.width;
float cbHalfWidthEx = cbHalfWidth * ( patternSize.width + 1) / patternSize.width;
float cbHalfHeightEx = cbHalfHeight * (patternSize.height + 1) / patternSize.height;
vector<Point3f> pts3d(4);
vector<Point2f> pts2d(4);
for(;;)
{
pts3d[0] = p + pb1 * cbHalfWidthEx + cbHalfHeightEx * pb2;
pts3d[1] = p + pb1 * cbHalfWidthEx - cbHalfHeightEx * pb2;
pts3d[2] = p - pb1 * cbHalfWidthEx - cbHalfHeightEx * pb2;
pts3d[3] = p - pb1 * cbHalfWidthEx + cbHalfHeightEx * pb2;
/* can remake with better perf */
projectPoints(pts3d, rvec, tvec, camMat, distCoeffs, pts2d);
bool inrect1 = pts2d[0].x < bg.cols && pts2d[0].y < bg.rows && pts2d[0].x > 0 && pts2d[0].y > 0;
bool inrect2 = pts2d[1].x < bg.cols && pts2d[1].y < bg.rows && pts2d[1].x > 0 && pts2d[1].y > 0;
bool inrect3 = pts2d[2].x < bg.cols && pts2d[2].y < bg.rows && pts2d[2].x > 0 && pts2d[2].y > 0;
bool inrect4 = pts2d[3].x < bg.cols && pts2d[3].y < bg.rows && pts2d[3].x > 0 && pts2d[3].y > 0;
if (inrect1 && inrect2 && inrect3 && inrect4)
break;
cbHalfWidth*=0.8f;
cbHalfHeight = cbHalfWidth * patternSize.height / patternSize.width;
cbHalfWidthEx = cbHalfWidth * ( patternSize.width + 1) / patternSize.width;
cbHalfHeightEx = cbHalfHeight * (patternSize.height + 1) / patternSize.height;
}
Point3f zero = p - pb1 * cbHalfWidth - cbHalfHeight * pb2;
float sqWidth = 2 * cbHalfWidth/patternSize.width;
float sqHeight = 2 * cbHalfHeight/patternSize.height;
return generateChessBoard(bg, camMat, distCoeffs, zero, pb1, pb2, sqWidth, sqHeight, pts3d, corners);
}
Mat ChessBoardGenerator::operator ()(const Mat& bg, const Mat& camMat, const Mat& distCoeffs,
const Size2f& squareSize, vector<Point2f>& corners) const
{
cov = std::min(cov, 0.8);
double fovx, fovy, focalLen;
Point2d principalPoint;
double aspect;
calibrationMatrixValues( camMat, bg.size(), sensorWidth, sensorHeight,
fovx, fovy, focalLen, principalPoint, aspect);
RNG& rng = theRNG();
float d1 = static_cast<float>(rng.uniform(0.1, 10.0));
float ah = static_cast<float>(rng.uniform(-fovx/2 * cov, fovx/2 * cov) * CV_PI / 180);
float av = static_cast<float>(rng.uniform(-fovy/2 * cov, fovy/2 * cov) * CV_PI / 180);
Point3f p;
p.z = std::cos(ah) * d1;
p.x = std::sin(ah) * d1;
p.y = p.z * std::tan(av);
Point3f pb1, pb2;
generateBasis(pb1, pb2);
float cbHalfWidth = squareSize.width * patternSize.width * 0.5f;
float cbHalfHeight = squareSize.height * patternSize.height * 0.5f;
float cbHalfWidthEx = cbHalfWidth * ( patternSize.width + 1) / patternSize.width;
float cbHalfHeightEx = cbHalfHeight * (patternSize.height + 1) / patternSize.height;
vector<Point3f> pts3d(4);
vector<Point2f> pts2d(4);
for(;;)
{
pts3d[0] = p + pb1 * cbHalfWidthEx + cbHalfHeightEx * pb2;
pts3d[1] = p + pb1 * cbHalfWidthEx - cbHalfHeightEx * pb2;
pts3d[2] = p - pb1 * cbHalfWidthEx - cbHalfHeightEx * pb2;
pts3d[3] = p - pb1 * cbHalfWidthEx + cbHalfHeightEx * pb2;
/* can remake with better perf */
projectPoints(pts3d, rvec, tvec, camMat, distCoeffs, pts2d);
bool inrect1 = pts2d[0].x < bg.cols && pts2d[0].y < bg.rows && pts2d[0].x > 0 && pts2d[0].y > 0;
bool inrect2 = pts2d[1].x < bg.cols && pts2d[1].y < bg.rows && pts2d[1].x > 0 && pts2d[1].y > 0;
bool inrect3 = pts2d[2].x < bg.cols && pts2d[2].y < bg.rows && pts2d[2].x > 0 && pts2d[2].y > 0;
bool inrect4 = pts2d[3].x < bg.cols && pts2d[3].y < bg.rows && pts2d[3].x > 0 && pts2d[3].y > 0;
if ( inrect1 && inrect2 && inrect3 && inrect4)
break;
p.z *= 1.1f;
}
Point3f zero = p - pb1 * cbHalfWidth - cbHalfHeight * pb2;
return generateChessBoard(bg, camMat, distCoeffs, zero, pb1, pb2,
squareSize.width, squareSize.height, pts3d, corners);
}
Mat ChessBoardGenerator::operator ()(const Mat& bg, const Mat& camMat, const Mat& distCoeffs,
const Size2f& squareSize, const Point3f& pos, vector<Point2f>& corners) const
{
cov = std::min(cov, 0.8);
Point3f p = pos;
Point3f pb1, pb2;
generateBasis(pb1, pb2);
float cbHalfWidth = squareSize.width * patternSize.width * 0.5f;
float cbHalfHeight = squareSize.height * patternSize.height * 0.5f;
float cbHalfWidthEx = cbHalfWidth * ( patternSize.width + 1) / patternSize.width;
float cbHalfHeightEx = cbHalfHeight * (patternSize.height + 1) / patternSize.height;
vector<Point3f> pts3d(4);
vector<Point2f> pts2d(4);
pts3d[0] = p + pb1 * cbHalfWidthEx + cbHalfHeightEx * pb2;
pts3d[1] = p + pb1 * cbHalfWidthEx - cbHalfHeightEx * pb2;
pts3d[2] = p - pb1 * cbHalfWidthEx - cbHalfHeightEx * pb2;
pts3d[3] = p - pb1 * cbHalfWidthEx + cbHalfHeightEx * pb2;
/* can remake with better perf */
projectPoints(pts3d, rvec, tvec, camMat, distCoeffs, pts2d);
Point3f zero = p - pb1 * cbHalfWidth - cbHalfHeight * pb2;
return generateChessBoard(bg, camMat, distCoeffs, zero, pb1, pb2,
squareSize.width, squareSize.height, pts3d, corners);
}
} // namespace
@@ -0,0 +1,43 @@
// 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 CV_CHESSBOARDGENERATOR_H143KJTVYM389YTNHKFDHJ89NYVMO3VLMEJNTBGUEIYVCM203P
#define CV_CHESSBOARDGENERATOR_H143KJTVYM389YTNHKFDHJ89NYVMO3VLMEJNTBGUEIYVCM203P
namespace cv
{
using std::vector;
class ChessBoardGenerator
{
public:
double sensorWidth;
double sensorHeight;
size_t squareEdgePointsNum;
double min_cos;
mutable double cov;
Size patternSize;
int rendererResolutionMultiplier;
ChessBoardGenerator(const Size& patternSize = Size(8, 6));
Mat operator()(const Mat& bg, const Mat& camMat, const Mat& distCoeffs, std::vector<Point2f>& corners) const;
Mat operator()(const Mat& bg, const Mat& camMat, const Mat& distCoeffs, const Size2f& squareSize, std::vector<Point2f>& corners) const;
Mat operator()(const Mat& bg, const Mat& camMat, const Mat& distCoeffs, const Size2f& squareSize, const Point3f& pos, std::vector<Point2f>& corners) const;
Size cornersSize() const;
mutable std::vector<Point3f> corners3d;
private:
void generateEdge(const Point3f& p1, const Point3f& p2, std::vector<Point3f>& out) const;
Mat generateChessBoard(const Mat& bg, const Mat& camMat, const Mat& distCoeffs,
const Point3f& zero, const Point3f& pb1, const Point3f& pb2,
float sqWidth, float sqHeight, const std::vector<Point3f>& whole, std::vector<Point2f>& corners) const;
void generateBasis(Point3f& pb1, Point3f& pb2) const;
Mat rvec, tvec;
};
}
#endif
@@ -0,0 +1,869 @@
/*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 "test_precomp.hpp"
#include "test_chessboardgenerator.hpp"
#include <functional>
namespace opencv_test { namespace {
#define _L2_ERR
//#define DEBUG_CHESSBOARD
#ifdef DEBUG_CHESSBOARD
void show_points( const Mat& gray, const Mat& expected, const vector<Point2f>& actual, bool was_found )
{
Mat rgb( gray.size(), CV_8U);
merge(vector<Mat>(3, gray), rgb);
for(size_t i = 0; i < actual.size(); i++ )
circle( rgb, actual[i], 5, Scalar(0, 0, 200), 1, LINE_AA);
if( !expected.empty() )
{
const Point2f* u_data = expected.ptr<Point2f>();
size_t count = expected.cols * expected.rows;
for(size_t i = 0; i < count; i++ )
circle(rgb, u_data[i], 4, Scalar(0, 240, 0), 1, LINE_AA);
}
putText(rgb, was_found ? "FOUND !!!" : "NOT FOUND", Point(5, 20), FONT_HERSHEY_PLAIN, 1, Scalar(0, 240, 0));
imshow( "test", rgb ); while ((uchar)waitKey(0) != 'q') {};
}
#else
#define show_points(...)
#endif
enum Pattern { CHESSBOARD, CHESSBOARD_SB, CHESSBOARD_PLAIN, CIRCLES_GRID, ASYMMETRIC_CIRCLES_GRID};
class CV_ChessboardDetectorTest : public cvtest::BaseTest
{
public:
CV_ChessboardDetectorTest( Pattern pattern, int algorithmFlags = 0 );
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;
};
CV_ChessboardDetectorTest::CV_ChessboardDetectorTest( Pattern _pattern, int _algorithmFlags )
{
pattern = _pattern;
algorithmFlags = _algorithmFlags;
}
double calcError(const vector<Point2f>& v, const Mat& u)
{
int count_exp = u.cols * u.rows;
const Point2f* u_data = u.ptr<Point2f>();
double err = std::numeric_limits<double>::max();
for( int k = 0; k < 2; ++k )
{
double err1 = 0;
for( int j = 0; j < count_exp; ++j )
{
int j1 = k == 0 ? j : count_exp - j - 1;
double dx = fabs( v[j].x - u_data[j1].x );
double dy = fabs( v[j].y - u_data[j1].y );
#if defined(_L2_ERR)
err1 += dx*dx + dy*dy;
#else
dx = MAX( dx, dy );
if( dx > err1 )
err1 = dx;
#endif //_L2_ERR
//printf("dx = %f\n", dx);
}
//printf("\n");
err = min(err, err1);
}
#if defined(_L2_ERR)
err = sqrt(err/count_exp);
#endif //_L2_ERR
return err;
}
const double rough_success_error_level = 2.5;
const double precise_success_error_level = 2;
/* ///////////////////// chess_corner_test ///////////////////////// */
void CV_ChessboardDetectorTest::run( int /*start_from */)
{
ts->set_failed_test_info( cvtest::TS::OK );
/*if (!checkByGenerator())
return;*/
switch( pattern )
{
case CHESSBOARD_SB:
checkByGeneratorHighAccuracy(); // not supported by CHESSBOARD
/* fallthrough */
case CHESSBOARD_PLAIN:
checkByGenerator();
if (ts->get_err_code() != cvtest::TS::OK)
{
break;
}
run_batch("negative_list.dat");
if (ts->get_err_code() != cvtest::TS::OK)
{
break;
}
run_batch("chessboard_list.dat");
if (ts->get_err_code() != cvtest::TS::OK)
{
break;
}
break;
case CHESSBOARD:
checkByGenerator();
if (ts->get_err_code() != cvtest::TS::OK)
{
break;
}
run_batch("negative_list.dat");
if (ts->get_err_code() != cvtest::TS::OK)
{
break;
}
run_batch("chessboard_list.dat");
if (ts->get_err_code() != cvtest::TS::OK)
{
break;
}
run_batch("chessboard_list_subpixel.dat");
break;
case CIRCLES_GRID:
run_batch("circles_list.dat");
break;
case ASYMMETRIC_CIRCLES_GRID:
run_batch("acircles_list.dat");
break;
}
}
void CV_ChessboardDetectorTest::run_batch( const string& filename )
{
ts->printf(cvtest::TS::LOG, "\nRunning batch %s\n", filename.c_str());
//#define WRITE_POINTS 1
#ifndef WRITE_POINTS
double max_rough_error = 0, max_precise_error = 0;
#endif
string folder;
switch( pattern )
{
case CHESSBOARD:
case CHESSBOARD_SB:
case CHESSBOARD_PLAIN:
folder = string(ts->get_data_path()) + "cameracalibration/";
break;
case CIRCLES_GRID:
folder = string(ts->get_data_path()) + "cameracalibration/circles/";
break;
case ASYMMETRIC_CIRCLES_GRID:
folder = string(ts->get_data_path()) + "cameracalibration/asymmetric_circles/";
break;
}
FileStorage fs( folder + filename, FileStorage::READ );
FileNode board_list = fs["boards"];
if( !fs.isOpened() || board_list.empty() || !board_list.isSeq() || board_list.size() % 2 != 0 )
{
ts->printf( cvtest::TS::LOG, "%s can not be read or is not valid\n", (folder + filename).c_str() );
ts->printf( cvtest::TS::LOG, "fs.isOpened=%d, board_list.empty=%d, board_list.isSeq=%d,board_list.size()%2=%d\n",
fs.isOpened(), (int)board_list.empty(), board_list.isSeq(), board_list.size()%2);
ts->set_failed_test_info( cvtest::TS::FAIL_MISSING_TEST_DATA );
return;
}
int progress = 0;
int max_idx = (int)board_list.size()/2;
if(filename.compare("chessboard_list.dat") == 0 && pattern == CHESSBOARD_PLAIN)
max_idx = 7;
double sum_error = 0.0;
int count = 0;
for(int idx = 0; idx < max_idx; ++idx )
{
ts->update_context( this, idx, true );
/* read the image */
String img_file = board_list[idx * 2];
Mat gray = imread( folder + img_file, IMREAD_GRAYSCALE);
if( gray.empty() )
{
ts->printf( cvtest::TS::LOG, "one of chessboard images can't be read: %s\n", img_file.c_str() );
ts->set_failed_test_info( cvtest::TS::FAIL_MISSING_TEST_DATA );
return;
}
String _filename = folder + (String)board_list[idx * 2 + 1];
bool doesContatinChessboard;
float sharpness;
Mat expected;
{
FileStorage fs1(_filename, FileStorage::READ);
fs1["corners"] >> expected;
fs1["isFound"] >> doesContatinChessboard;
fs1["sharpness"] >> sharpness ;
fs1.release();
}
size_t count_exp = static_cast<size_t>(expected.cols * expected.rows);
Size pattern_size = expected.size();
Mat ori;
vector<Point2f> v;
int flags = 0;
switch( pattern )
{
case CHESSBOARD:
flags = CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE;
break;
case CHESSBOARD_PLAIN: {
flags = CALIB_CB_PLAIN;
ori = gray.clone();
int min_size = cvRound((gray.cols * gray.rows * 0.05) / ((pattern_size.width+1) * (pattern_size.height+1)));
if(min_size%2==0) min_size += 1;
adaptiveThreshold(gray, gray, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, min_size, 0);
dilate(gray, gray, Mat(), Point(-1, -1), 1);
break;
}
case CIRCLES_GRID:
case CHESSBOARD_SB:
case ASYMMETRIC_CIRCLES_GRID:
default:
flags = 0;
}
bool result = findChessboardCornersWrapper(gray, pattern_size,v,flags);
if(result && pattern == CHESSBOARD_PLAIN) {
gray = ori;
cornerSubPix(gray, v, Size(6,6), Size(-1,-1), TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 30, 0.1));
}
if(result && sharpness && (pattern == CHESSBOARD_SB || pattern == CHESSBOARD || pattern == CHESSBOARD_PLAIN))
{
Scalar s= estimateChessboardSharpness(gray,pattern_size,v);
if(fabs(s[0] - sharpness) > 0.1)
{
ts->printf(cvtest::TS::LOG, "chessboard image has a wrong sharpness in %s. Expected %f but measured %f\n", img_file.c_str(),sharpness,s[0]);
ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
show_points( gray, expected, v, result );
return;
}
}
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;
}
if( result )
{
#ifndef WRITE_POINTS
double err = calcError(v, expected);
max_rough_error = MAX( max_rough_error, err );
#endif
if( pattern == CHESSBOARD || pattern == CHESSBOARD_PLAIN )
cornerSubPix( gray, v, Size(5, 5), Size(-1,-1), TermCriteria(TermCriteria::EPS|TermCriteria::MAX_ITER, 30, 0.1));
//find4QuadCornerSubpix(gray, v, Size(5, 5));
show_points( gray, expected, v, result );
#ifndef WRITE_POINTS
// printf("called find4QuadCornerSubpix\n");
err = calcError(v, expected);
sum_error += err;
count++;
if( err > precise_success_error_level )
{
ts->printf( cvtest::TS::LOG, "Image %s: bad accuracy of adjusted corners %f\n", img_file.c_str(), err );
ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY );
return;
}
ts->printf(cvtest::TS::LOG, "Error on %s is %f\n", img_file.c_str(), err);
max_precise_error = MAX( max_precise_error, err );
#endif
}
else
{
show_points( gray, Mat(), v, result );
}
#ifdef WRITE_POINTS
Mat mat_v(pattern_size, CV_32FC2, (void*)&v[0]);
FileStorage fs(_filename, FileStorage::WRITE);
fs << "isFound" << result;
fs << "corners" << mat_v;
fs.release();
#endif
progress = update_progress( progress, idx, max_idx, 0 );
}
if (count != 0)
sum_error /= count;
ts->printf(cvtest::TS::LOG, "Average error is %f (%d patterns have been found)\n", sum_error, count);
}
double calcErrorMinError(const Size& cornSz, const vector<Point2f>& corners_found, const vector<Point2f>& corners_generated)
{
Mat m1(cornSz, CV_32FC2, (Point2f*)&corners_generated[0]);
Mat m2; flip(m1, m2, 0);
Mat m3; flip(m1, m3, 1); m3 = m3.t(); flip(m3, m3, 1);
Mat m4 = m1.t(); flip(m4, m4, 1);
double min1 = min(calcError(corners_found, m1), calcError(corners_found, m2));
double min2 = min(calcError(corners_found, m3), calcError(corners_found, m4));
return min(min1, min2);
}
bool validateData(const ChessBoardGenerator& cbg, const Size& imgSz,
const vector<Point2f>& corners_generated)
{
Size cornersSize = cbg.cornersSize();
Mat_<Point2f> mat(cornersSize.height, cornersSize.width, (Point2f*)&corners_generated[0]);
double minNeibDist = std::numeric_limits<double>::max();
double tmp = 0;
for(int i = 1; i < mat.rows - 2; ++i)
for(int j = 1; j < mat.cols - 2; ++j)
{
const Point2f& cur = mat(i, j);
tmp = cv::norm(cur - mat(i + 1, j + 1)); // TODO cvtest
if (tmp < minNeibDist)
minNeibDist = tmp;
tmp = cv::norm(cur - mat(i - 1, j + 1)); // TODO cvtest
if (tmp < minNeibDist)
minNeibDist = tmp;
tmp = cv::norm(cur - mat(i + 1, j - 1)); // TODO cvtest
if (tmp < minNeibDist)
minNeibDist = tmp;
tmp = cv::norm(cur - mat(i - 1, j - 1)); // TODO cvtest
if (tmp < minNeibDist)
minNeibDist = tmp;
}
const double threshold = 0.25;
double cbsize = (max(cornersSize.width, cornersSize.height) + 1) * minNeibDist;
int imgsize = min(imgSz.height, imgSz.width);
return imgsize * threshold < cbsize;
}
bool CV_ChessboardDetectorTest::findChessboardCornersWrapper(InputArray image, Size patternSize, OutputArray corners,int flags)
{
switch(pattern)
{
case CHESSBOARD:
case CHESSBOARD_PLAIN:
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;
//theRNG() = 0x58e6e895b9913160;
//cv::DefaultRngAuto dra;
//theRNG() = *ts->get_rng();
Mat bg(Size(800, 600), CV_8UC3, Scalar::all(255));
randu(bg, Scalar::all(0), Scalar::all(255));
GaussianBlur(bg, bg, Size(5, 5), 0.0);
Mat_<float> camMat(3, 3);
camMat << 300.f, 0.f, bg.cols/2.f, 0, 300.f, bg.rows/2.f, 0.f, 0.f, 1.f;
Mat_<float> distCoeffs(1, 5);
distCoeffs << 1.2f, 0.2f, 0.f, 0.f, 0.f;
const Size sizes[] = { Size(6, 6), Size(8, 6), Size(11, 12), Size(5, 4) };
const size_t sizes_num = sizeof(sizes)/sizeof(sizes[0]);
const int test_num = 16;
int progress = 0;
for(int i = 0; i < test_num; ++i)
{
SCOPED_TRACE(cv::format("test_num=%d", test_num));
progress = update_progress( progress, i, test_num, 0 );
ChessBoardGenerator cbg(sizes[i % sizes_num]);
vector<Point2f> corners_generated;
Mat cb = cbg(bg, camMat, distCoeffs, corners_generated);
if(!validateData(cbg, cb.size(), corners_generated))
{
ts->printf( cvtest::TS::LOG, "Chess board skipped - too small" );
continue;
}
/*cb = cb * 0.8 + Scalar::all(30);
GaussianBlur(cb, cb, Size(3, 3), 0.8); */
//cv::addWeighted(cb, 0.8, bg, 0.2, 20, cb);
//cv::namedWindow("CB"); cv::imshow("CB", cb); cv::waitKey();
vector<Point2f> corners_found;
int flags = i % 8; // need to check branches for all flags
bool found = findChessboardCornersWrapper(cb, cbg.cornersSize(), corners_found, flags);
if (!found)
{
ts->printf( cvtest::TS::LOG, "Chess board corners not found\n" );
ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY );
res = false;
return res;
}
double err = calcErrorMinError(cbg.cornersSize(), corners_found, corners_generated);
EXPECT_LE(err, rough_success_error_level) << "bad accuracy of corner guesses";
#if 0
if (err >= rough_success_error_level)
{
imshow("cb", cb);
Mat cb_corners = cb.clone();
cv::drawChessboardCorners(cb_corners, cbg.cornersSize(), Mat(corners_found), found);
imshow("corners", cb_corners);
waitKey(0);
}
#endif
}
/* ***** negative ***** */
{
vector<Point2f> corners_found;
bool found = findChessboardCornersWrapper(bg, Size(8, 7), corners_found,0);
if (found)
res = false;
ChessBoardGenerator cbg(Size(8, 7));
vector<Point2f> cg;
Mat cb = cbg(bg, camMat, distCoeffs, cg);
found = findChessboardCornersWrapper(cb, Size(3, 4), corners_found,0);
if (found)
res = false;
Point2f c = std::accumulate(cg.begin(), cg.end(), Point2f(), std::plus<Point2f>()) * (1.f/cg.size());
Mat_<double> aff(2, 3);
aff << 1.0, 0.0, -(double)c.x, 0.0, 1.0, 0.0;
Mat sh;
warpAffine(cb, sh, aff, cb.size());
found = findChessboardCornersWrapper(sh, cbg.cornersSize(), corners_found,0);
if (found)
res = false;
vector< vector<Point> > cnts(1);
vector<Point>& cnt = cnts[0];
cnt.push_back(cg[ 0]); cnt.push_back(cg[0+2]);
cnt.push_back(cg[7+0]); cnt.push_back(cg[7+2]);
cv::drawContours(cb, cnts, -1, Scalar::all(128), FILLED);
found = findChessboardCornersWrapper(cb, cbg.cornersSize(), corners_found,0);
if (found)
res = false;
cv::drawChessboardCorners(cb, cbg.cornersSize(), Mat(corners_found), found);
}
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*CV_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_ChessboardDetector3, accuracy) { CV_ChessboardDetectorTest test( CHESSBOARD_PLAIN ); 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
TEST(Calib3d_AsymmetricCirclesPatternDetectorWithClustering, accuracy) { CV_ChessboardDetectorTest test( ASYMMETRIC_CIRCLES_GRID, CALIB_CB_CLUSTERING ); test.safe_run(); }
#endif
TEST(Calib3d_ChessboardWithMarkers, regression_25806_white)
{
const cv::String dataDir = string(TS::ptr()->get_data_path()) + "cameracalibration/";
const cv::Mat image = cv::imread(dataDir + "checkerboard_marker_white.png");
ASSERT_FALSE(image.empty());
std::vector<Point2f> corners;
const bool success = cv::findChessboardCornersSB(image, Size(9, 14), corners, CALIB_CB_MARKER);
ASSERT_TRUE(success);
}
TEST(Calib3d_ChessboardWithMarkers, regression_25806_black)
{
const cv::String dataDir = string(TS::ptr()->get_data_path()) + "cameracalibration/";
const cv::Mat image = cv::imread(dataDir + "checkerboard_marker_black.png");
ASSERT_FALSE(image.empty());
std::vector<Point2f> corners;
const bool success = cv::findChessboardCornersSB(image, Size(9, 14), corners, CALIB_CB_MARKER);
ASSERT_TRUE(success);
}
TEST(Calib3d_CirclesPatternDetectorWithClustering, accuracy)
{
cv::String dataDir = string(TS::ptr()->get_data_path()) + "cameracalibration/circles/";
cv::Mat expected;
FileStorage fs(dataDir + "circles_corners15.dat", FileStorage::READ);
fs["corners"] >> expected;
fs.release();
cv::Mat image = cv::imread(dataDir + "circles15.png");
ASSERT_FALSE(image.empty());
std::vector<Point2f> centers;
cv::findCirclesGrid(image, Size(10, 8), centers, CALIB_CB_SYMMETRIC_GRID | CALIB_CB_CLUSTERING);
ASSERT_EQ(expected.total(), centers.size());
double error = calcError(centers, expected);
ASSERT_LE(error, precise_success_error_level);
}
TEST(Calib3d_AsymmetricCirclesPatternDetector, regression_18713)
{
float pts_[][2] = {
{ 166.5, 107 }, { 146, 236 }, { 147, 92 }, { 184, 162 }, { 150, 185.5 },
{ 215, 105 }, { 270.5, 186 }, { 159, 142 }, { 6, 205.5 }, { 32, 148.5 },
{ 126, 163.5 }, { 181, 208.5 }, { 240.5, 62 }, { 84.5, 76.5 }, { 190, 120.5 },
{ 10, 189 }, { 266, 104 }, { 307.5, 207.5 }, { 97, 184 }, { 116.5, 210 },
{ 114, 139 }, { 84.5, 233 }, { 269.5, 139 }, { 136, 126.5 }, { 120, 107.5 },
{ 129.5, 65.5 }, { 212.5, 140.5 }, { 204.5, 60.5 }, { 207.5, 241 }, { 61.5, 94.5 },
{ 186.5, 61.5 }, { 220, 63 }, { 239, 120.5 }, { 212, 186 }, { 284, 87.5 },
{ 62, 114.5 }, { 283, 61.5 }, { 238.5, 88.5 }, { 243, 159 }, { 245, 208 },
{ 298.5, 158.5 }, { 57, 129 }, { 156.5, 63.5 }, { 192, 90.5 }, { 281, 235.5 },
{ 172, 62.5 }, { 291.5, 119.5 }, { 90, 127 }, { 68.5, 166.5 }, { 108.5, 83.5 },
{ 22, 176 }
};
Mat candidates(51, 1, CV_32FC2, (void*)pts_);
Size patternSize(4, 9);
std::vector< Point2f > result;
bool res = false;
// issue reports about hangs
EXPECT_NO_THROW(res = findCirclesGrid(candidates, patternSize, result, CALIB_CB_ASYMMETRIC_GRID, Ptr<FeatureDetector>()/*blobDetector=NULL*/));
EXPECT_FALSE(res);
if (cvtest::debugLevel > 0)
{
std::cout << Mat(candidates) << std::endl;
std::cout << Mat(result) << std::endl;
Mat img(Size(400, 300), CV_8UC3, Scalar::all(0));
std::vector< Point2f > centers;
candidates.copyTo(centers);
for (size_t i = 0; i < centers.size(); i++)
{
const Point2f& pt = centers[i];
//printf("{ %g, %g }, \n", pt.x, pt.y);
circle(img, pt, 5, Scalar(0, 255, 0));
}
for (size_t i = 0; i < result.size(); i++)
{
const Point2f& pt = result[i];
circle(img, pt, 10, Scalar(0, 0, 255));
}
imwrite("test_18713.png", img);
if (cvtest::debugLevel >= 10)
{
imshow("result", img);
waitKey();
}
}
}
TEST(Calib3d_AsymmetricCirclesPatternDetector, regression_19498)
{
float pts_[121][2] = {
{ 84.7462f, 404.504f }, { 49.1586f, 404.092f }, { 12.3362f, 403.434f }, { 102.542f, 386.214f }, { 67.6042f, 385.475f },
{ 31.4982f, 384.569f }, { 141.231f, 377.856f }, { 332.834f, 370.745f }, { 85.7663f, 367.261f }, { 50.346f, 366.051f },
{ 13.7726f, 364.663f }, { 371.746f, 362.011f }, { 68.8543f, 347.883f }, { 32.9334f, 346.263f }, { 331.926f, 343.291f },
{ 351.535f, 338.112f }, { 51.7951f, 328.247f }, { 15.4613f, 326.095f }, { 311.719f, 319.578f }, { 330.947f, 313.708f },
{ 256.706f, 307.584f }, { 34.6834f, 308.167f }, { 291.085f, 295.429f }, { 17.4316f, 287.824f }, { 252.928f, 277.92f },
{ 270.19f, 270.93f }, { 288.473f, 263.484f }, { 216.401f, 260.94f }, { 232.195f, 253.656f }, { 266.757f, 237.708f },
{ 211.323f, 229.005f }, { 227.592f, 220.498f }, { 154.749f, 188.52f }, { 222.52f, 184.906f }, { 133.85f, 163.968f },
{ 200.024f, 158.05f }, { 147.485f, 153.643f }, { 161.967f, 142.633f }, { 177.396f, 131.059f }, { 125.909f, 128.116f },
{ 139.817f, 116.333f }, { 91.8639f, 114.454f }, { 104.343f, 102.542f }, { 117.635f, 89.9116f }, { 70.9465f, 89.4619f },
{ 82.8524f, 76.7862f }, { 131.738f, 76.4741f }, { 95.5012f, 63.3351f }, { 109.034f, 49.0424f }, { 314.886f, 374.711f },
{ 351.735f, 366.489f }, { 279.113f, 357.05f }, { 313.371f, 348.131f }, { 260.123f, 335.271f }, { 276.346f, 330.325f },
{ 293.588f, 325.133f }, { 240.86f, 313.143f }, { 273.436f, 301.667f }, { 206.762f, 296.574f }, { 309.877f, 288.796f },
{ 187.46f, 274.319f }, { 201.521f, 267.804f }, { 248.973f, 245.918f }, { 181.644f, 244.655f }, { 196.025f, 237.045f },
{ 148.41f, 229.131f }, { 161.604f, 221.215f }, { 175.455f, 212.873f }, { 244.748f, 211.459f }, { 128.661f, 206.109f },
{ 190.217f, 204.108f }, { 141.346f, 197.568f }, { 205.876f, 194.781f }, { 168.937f, 178.948f }, { 121.006f, 173.714f },
{ 183.998f, 168.806f }, { 88.9095f, 159.731f }, { 100.559f, 149.867f }, { 58.553f, 146.47f }, { 112.849f, 139.302f },
{ 80.0968f, 125.74f }, { 39.24f, 123.671f }, { 154.582f, 103.85f }, { 59.7699f, 101.49f }, { 266.334f, 385.387f },
{ 234.053f, 368.718f }, { 263.347f, 361.184f }, { 244.763f, 339.958f }, { 198.16f, 328.214f }, { 211.675f, 323.407f },
{ 225.905f, 318.426f }, { 192.98f, 302.119f }, { 221.267f, 290.693f }, { 161.437f, 286.46f }, { 236.656f, 284.476f },
{ 168.023f, 251.799f }, { 105.385f, 221.988f }, { 116.724f, 214.25f }, { 97.2959f, 191.81f }, { 108.89f, 183.05f },
{ 77.9896f, 169.242f }, { 48.6763f, 156.088f }, { 68.9635f, 136.415f }, { 29.8484f, 133.886f }, { 49.1966f, 112.826f },
{ 113.059f, 29.003f }, { 251.698f, 388.562f }, { 281.689f, 381.929f }, { 297.875f, 378.518f }, { 248.376f, 365.025f },
{ 295.791f, 352.763f }, { 216.176f, 348.586f }, { 230.143f, 344.443f }, { 179.89f, 307.457f }, { 174.083f, 280.51f },
{ 142.867f, 265.085f }, { 155.127f, 258.692f }, { 124.187f, 243.661f }, { 136.01f, 236.553f }, { 86.4651f, 200.13f },
{ 67.5711f, 178.221f }
};
Mat candidates(121, 1, CV_32FC2, (void*)pts_);
Size patternSize(13, 8);
std::vector< Point2f > result;
bool res = false;
EXPECT_NO_THROW(res = findCirclesGrid(candidates, patternSize, result, CALIB_CB_SYMMETRIC_GRID, Ptr<FeatureDetector>()/*blobDetector=NULL*/));
EXPECT_FALSE(res);
}
TEST(Calib3d_RotatedCirclesPatternDetector, issue_24964)
{
string path = cvtest::findDataFile("cameracalibration/circles/circles_24964.png");
Mat image = cv::imread(path);
ASSERT_FALSE(image.empty()) << "Can't read image: " << path;
vector<Point2f> centers;
Size parrernSize(7, 6);
Mat goldCenters(parrernSize.height, parrernSize.width, CV_32FC2);
Point2f firstGoldCenter(380.f, 430.f);
for (int i = 0; i < parrernSize.height; i++)
{
for (int j = 0; j < parrernSize.width; j++)
{
goldCenters.at<Point2f>(i, j) = Point2f(firstGoldCenter.x + j * 100.f, firstGoldCenter.y + i * 100.f);
}
}
bool found = false;
found = findCirclesGrid(image, parrernSize, centers, CALIB_CB_SYMMETRIC_GRID);
EXPECT_TRUE(found);
ASSERT_EQ(centers.size(), (size_t)parrernSize.area());
double error = calcError(centers, goldCenters);
EXPECT_LE(error, precise_success_error_level);
// "rotate" the circle grid by 90 degrees
swap(parrernSize.height, parrernSize.width);
found = findCirclesGrid(image, parrernSize, centers, CALIB_CB_SYMMETRIC_GRID);
error = calcError(centers, goldCenters.t());
EXPECT_TRUE(found);
ASSERT_EQ(centers.size(), (size_t)parrernSize.area());
EXPECT_LE(error, precise_success_error_level);
}
TEST(Calib3d_CornerOrdering, issue_26830) {
const cv::String dataDir = string(TS::ptr()->get_data_path()) + "cameracalibration/";
const cv::Mat image = cv::imread(dataDir + "checkerboard_marker_white.png");
std::vector<Point2f> cornersMinimumSizeMatchesPatternSize;
ASSERT_TRUE(cv::findChessboardCornersSB(image, Size(14, 9), cornersMinimumSizeMatchesPatternSize, CALIB_CB_MARKER | CALIB_CB_LARGER));
std::vector<Point2f> cornersMinimumSizeSmallerThanPatternSize;
ASSERT_TRUE(cv::findChessboardCornersSB(image, Size(4, 4), cornersMinimumSizeSmallerThanPatternSize, CALIB_CB_MARKER | CALIB_CB_LARGER));
ASSERT_EQ(cornersMinimumSizeMatchesPatternSize, cornersMinimumSizeSmallerThanPatternSize);
}
}} // namespace
/* End of file. */
@@ -0,0 +1,115 @@
/*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 "test_precomp.hpp"
#include "test_chessboardgenerator.hpp"
namespace opencv_test { namespace {
class CV_ChessboardDetectorBadArgTest : public cvtest::BadArgTest
{
public:
CV_ChessboardDetectorBadArgTest() { flags0 = 0; }
protected:
void run(int);
bool checkByGenerator();
Mat img;
Size pattern_size, pattern_size0;
int flags, flags0;
vector<Point2f> corners;
_InputArray img_arg;
_OutputArray corners_arg;
void initArgs()
{
img_arg = img;
corners_arg = corners;
pattern_size = pattern_size0;
flags = flags0;
}
void run_func()
{
findChessboardCorners(img_arg, pattern_size, corners_arg, flags);
}
};
/* ///////////////////// chess_corner_test ///////////////////////// */
void CV_ChessboardDetectorBadArgTest::run( int /*start_from */)
{
Mat bg(800, 600, CV_8U, Scalar(0));
Mat_<float> camMat(3, 3);
camMat << 300.f, 0.f, bg.cols/2.f, 0, 300.f, bg.rows/2.f, 0.f, 0.f, 1.f;
Mat_<float> distCoeffs(1, 5);
distCoeffs << 1.2f, 0.2f, 0.f, 0.f, 0.f;
ChessBoardGenerator cbg(Size(8,6));
vector<Point2f> exp_corn;
Mat cb = cbg(bg, camMat, distCoeffs, exp_corn);
/* /*//*/ */
int errors = 0;
flags = CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE;
img = cb.clone();
initArgs();
pattern_size = Size(2,2);
errors += run_test_case( Error::StsOutOfRange, "Invalid pattern size" );
pattern_size = cbg.cornersSize();
cb.convertTo(img, CV_32F);
errors += run_test_case( Error::StsUnsupportedFormat, "Not 8-bit image" );
cv::merge(vector<Mat>(2, cb), img);
errors += run_test_case( Error::StsUnsupportedFormat, "2 channel image" );
if (errors)
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
else
ts->set_failed_test_info(cvtest::TS::OK);
}
TEST(Calib3d_ChessboardDetector, badarg) { CV_ChessboardDetectorBadArgTest test; test.safe_run(); }
}} // namespace
/* End of file. */
@@ -0,0 +1,160 @@
/*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 "test_precomp.hpp"
#include "opencv2/imgproc.hpp"
namespace opencv_test { namespace {
class CV_ChessboardDetectorTimingTest : public cvtest::BaseTest
{
public:
CV_ChessboardDetectorTimingTest();
protected:
void run(int);
};
CV_ChessboardDetectorTimingTest::CV_ChessboardDetectorTimingTest()
{
}
/* ///////////////////// chess_corner_test ///////////////////////// */
void CV_ChessboardDetectorTimingTest::run( int start_from )
{
int code = cvtest::TS::OK;
/* test parameters */
std::string filepath;
std::string filename;
std::vector<Point2f> v;
Mat img, gray, thresh;
int idx, max_idx;
int progress = 0;
filepath = cv::format("%scameracalibration/", ts->get_data_path().c_str() );
filename = cv::format("%schessboard_timing_list.dat", filepath.c_str() );
cv::FileStorage fs( filename, FileStorage::READ );
cv::FileNode board_list = fs["boards"];
cv::FileNodeIterator bl_it = board_list.begin();
if( !fs.isOpened() || !board_list.isSeq() || board_list.size() % 4 != 0 )
{
ts->printf( cvtest::TS::LOG, "chessboard_timing_list.dat can not be read or is not valid" );
code = cvtest::TS::FAIL_MISSING_TEST_DATA;
goto _exit_;
}
max_idx = (int)(board_list.size()/4);
for( idx = 0; idx < start_from; idx++ )
{
bl_it += 4;
}
for( idx = start_from; idx < max_idx; idx++ )
{
Size pattern_size;
std::string imgname; read(*bl_it++, imgname, "dummy.txt");
int is_chessboard = 0;
read(*bl_it++, is_chessboard, 0);
read(*bl_it++, pattern_size.width, -1);
read(*bl_it++, pattern_size.height, -1);
ts->update_context( this, idx-1, true );
/* read the image */
filename = cv::format("%s%s", filepath.c_str(), imgname.c_str() );
img = cv::imread( filename );
if( img.empty() )
{
ts->printf( cvtest::TS::LOG, "one of chessboard images can't be read: %s\n", filename.c_str() );
code = cvtest::TS::FAIL_MISSING_TEST_DATA;
continue;
}
ts->printf(cvtest::TS::LOG, "%s: chessboard %d:\n", imgname.c_str(), is_chessboard);
cvtColor(img, gray, COLOR_BGR2GRAY);
int64 _time0 = cv::getTickCount();
bool result = cv::checkChessboard(gray, pattern_size);
int64 _time01 = cv::getTickCount();
bool result1 = findChessboardCorners(gray, pattern_size, v, 15);
int64 _time1 = cv::getTickCount();
if( result != (is_chessboard != 0))
{
ts->printf( cvtest::TS::LOG, "Error: chessboard was %sdetected in the image %s\n",
result ? "" : "not ", imgname.c_str() );
code = cvtest::TS::FAIL_INVALID_OUTPUT;
goto _exit_;
}
if(result != result1)
{
ts->printf( cvtest::TS::LOG, "Warning: results differ cvCheckChessboard %d, cvFindChessboardCorners %d\n",
(int)result, (int)result1);
}
int num_pixels = gray.cols*gray.rows;
float check_chessboard_time = float(_time01 - _time0)/(float)cv::getTickFrequency(); // in s
ts->printf(cvtest::TS::LOG, " cvCheckChessboard time s: %f, us per pixel: %f\n",
check_chessboard_time, check_chessboard_time*1e6/num_pixels);
float find_chessboard_time = float(_time1 - _time01)/(float)cv::getTickFrequency();
ts->printf(cvtest::TS::LOG, " cvFindChessboard time s: %f, us per pixel: %f\n",
find_chessboard_time, find_chessboard_time*1e6/num_pixels);
progress = update_progress( progress, idx-1, max_idx, 0 );
}
_exit_:
if( code < 0 )
ts->set_failed_test_info( code );
}
TEST(Calib3d_ChessboardDetector, timing) { CV_ChessboardDetectorTimingTest test; test.safe_run(); }
}} // namespace
/* End of file. */
@@ -0,0 +1,258 @@
/*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 "opencv2/core/types.hpp"
#include "test_precomp.hpp"
#include "test_chessboardgenerator.hpp"
namespace opencv_test { namespace {
class CV_ChessboardSubpixelTest : public cvtest::BaseTest
{
public:
CV_ChessboardSubpixelTest();
protected:
Mat intrinsic_matrix_;
Mat distortion_coeffs_;
Size image_size_;
void run(int);
void generateIntrinsicParams();
};
int calcDistance(const vector<Point2f>& set1, const vector<Point2f>& set2, double& mean_dist)
{
if(set1.size() != set2.size())
{
return 0;
}
std::vector<int> indices;
double sum_dist = 0.0;
for(size_t i = 0; i < set1.size(); i++)
{
double min_dist = std::numeric_limits<double>::max();
int min_idx = -1;
for(int j = 0; j < (int)set2.size(); j++)
{
double dist = cv::norm(set1[i] - set2[j]); // TODO cvtest
if(dist < min_dist)
{
min_idx = j;
min_dist = dist;
}
}
// check validity of min_idx
if(min_idx == -1)
{
return 0;
}
std::vector<int>::iterator it = std::find(indices.begin(), indices.end(), min_idx);
if(it != indices.end())
{
// there are two points in set1 corresponding to the same point in set2
return 0;
}
indices.push_back(min_idx);
// printf("dist %d = %f\n", (int)i, min_dist);
sum_dist += min_dist*min_dist;
}
mean_dist = sqrt(sum_dist/set1.size());
// printf("sum_dist = %f, set1.size() = %d, mean_dist = %f\n", sum_dist, (int)set1.size(), mean_dist);
return 1;
}
CV_ChessboardSubpixelTest::CV_ChessboardSubpixelTest() :
intrinsic_matrix_(Size(3, 3), CV_64FC1), distortion_coeffs_(Size(1, 4), CV_64FC1),
image_size_(640, 480)
{
}
/* ///////////////////// chess_corner_test ///////////////////////// */
void CV_ChessboardSubpixelTest::run( int )
{
int code = cvtest::TS::OK;
int progress = 0;
RNG& rng = ts->get_rng();
const int runs_count = 20;
const int max_pattern_size = 8;
const int min_pattern_size = 5;
Mat bg(image_size_, CV_8UC1);
bg = Scalar(0);
double sum_dist = 0.0;
int count = 0;
for(int i = 0; i < runs_count; i++)
{
const int pattern_width = min_pattern_size + cvtest::randInt(rng) % (max_pattern_size - min_pattern_size);
const int pattern_height = min_pattern_size + cvtest::randInt(rng) % (max_pattern_size - min_pattern_size);
Size pattern_size;
if(pattern_width > pattern_height)
{
pattern_size = Size(pattern_height, pattern_width);
}
else
{
pattern_size = Size(pattern_width, pattern_height);
}
ChessBoardGenerator gen_chessboard(Size(pattern_size.width + 1, pattern_size.height + 1));
// generates intrinsic camera and distortion matrices
generateIntrinsicParams();
vector<Point2f> corners;
Mat chessboard_image = gen_chessboard(bg, intrinsic_matrix_, distortion_coeffs_, corners);
vector<Point2f> test_corners;
bool result = findChessboardCorners(chessboard_image, pattern_size, test_corners, 15);
if (!result && cvtest::debugLevel > 0)
{
ts->printf(cvtest::TS::LOG, "Warning: chessboard was not detected! Writing image to test.png\n");
ts->printf(cvtest::TS::LOG, "Size = %d, %d\n", pattern_size.width, pattern_size.height);
ts->printf(cvtest::TS::LOG, "Intrinsic params: fx = %f, fy = %f, cx = %f, cy = %f\n",
intrinsic_matrix_.at<double>(0, 0), intrinsic_matrix_.at<double>(1, 1),
intrinsic_matrix_.at<double>(0, 2), intrinsic_matrix_.at<double>(1, 2));
ts->printf(cvtest::TS::LOG, "Distortion matrix: %f, %f, %f, %f, %f\n",
distortion_coeffs_.at<double>(0, 0), distortion_coeffs_.at<double>(0, 1),
distortion_coeffs_.at<double>(0, 2), distortion_coeffs_.at<double>(0, 3),
distortion_coeffs_.at<double>(0, 4));
imwrite("test.png", chessboard_image);
}
if (!result)
{
continue;
}
double dist1 = 0.0;
int ret = calcDistance(corners, test_corners, dist1);
if(ret == 0)
{
ts->printf(cvtest::TS::LOG, "findChessboardCorners returns invalid corner coordinates!\n");
code = cvtest::TS::FAIL_INVALID_OUTPUT;
break;
}
cornerSubPix(chessboard_image, test_corners,
Size(3, 3), Size(1, 1), TermCriteria(TermCriteria::EPS|TermCriteria::MAX_ITER, 300, 0.1));
find4QuadCornerSubpix(chessboard_image, test_corners, Size(5, 5));
double dist2 = 0.0;
ret = calcDistance(corners, test_corners, dist2);
if(ret == 0)
{
ts->printf(cvtest::TS::LOG, "findCornerSubpix returns invalid corner coordinates!\n");
code = cvtest::TS::FAIL_INVALID_OUTPUT;
break;
}
ts->printf(cvtest::TS::LOG, "Error after findChessboardCorners: %f, after findCornerSubPix: %f\n",
dist1, dist2);
sum_dist += dist2;
count++;
const double max_reduce_factor = 0.8;
if(dist1 < dist2*max_reduce_factor)
{
ts->printf(cvtest::TS::LOG, "findCornerSubPix increases average error!\n");
code = cvtest::TS::FAIL_INVALID_OUTPUT;
break;
}
progress = update_progress( progress, i-1, runs_count, 0 );
}
ASSERT_NE(0, count);
sum_dist /= count;
ts->printf(cvtest::TS::LOG, "Average error after findCornerSubpix: %f\n", sum_dist);
if( code < 0 )
ts->set_failed_test_info( code );
}
void CV_ChessboardSubpixelTest::generateIntrinsicParams()
{
RNG& rng = ts->get_rng();
const double max_focus_length = 1000.0;
const double max_focus_diff = 5.0;
double fx = cvtest::randReal(rng)*max_focus_length;
double fy = fx + cvtest::randReal(rng)*max_focus_diff;
double cx = image_size_.width/2;
double cy = image_size_.height/2;
double k1 = 0.5*cvtest::randReal(rng);
double k2 = 0.05*cvtest::randReal(rng);
double p1 = 0.05*cvtest::randReal(rng);
double p2 = 0.05*cvtest::randReal(rng);
double k3 = 0.0;
intrinsic_matrix_ = (Mat_<double>(3, 3) << fx, 0.0, cx, 0.0, fy, cy, 0.0, 0.0, 1.0);
distortion_coeffs_ = (Mat_<double>(1, 5) << k1, k2, p1, p2, k3);
}
TEST(Calib3d_ChessboardSubPixDetector, accuracy) { CV_ChessboardSubpixelTest test; test.safe_run(); }
TEST(Calib3d_CornerSubPix, regression_7204)
{
cv::Mat image(cv::Size(70, 38), CV_8UC1, cv::Scalar::all(0));
image(cv::Rect(65, 26, 5, 5)).setTo(cv::Scalar::all(255));
image(cv::Rect(55, 31, 8, 1)).setTo(cv::Scalar::all(255));
image(cv::Rect(56, 35, 14, 2)).setTo(cv::Scalar::all(255));
image(cv::Rect(66, 24, 4, 2)).setTo(cv::Scalar::all(255));
image.at<uchar>(24, 69) = 0;
std::vector<cv::Point2f> corners;
corners.push_back(cv::Point2f(65, 30));
cv::cornerSubPix(image, corners, cv::Size(3, 3), cv::Size(-1, -1),
cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 30, 0.1));
}
}} // namespace
/* End of file. */
+1
View File
@@ -5,6 +5,7 @@
#define __OPENCV_TEST_PRECOMP_HPP__
#include "opencv2/ts.hpp"
#include "opencv2/3d.hpp"
#include "opencv2/objdetect.hpp"
#include <random>