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:
committed by
GitHub
parent
dafe7cef97
commit
e19b0ebc5c
@@ -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
@@ -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
@@ -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 ¶);
|
||||
|
||||
//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 ¢er, const cv::Size2f &axes, float angle);
|
||||
|
||||
void draw(cv::InputOutputArray img,const cv::Scalar &color = cv::Scalar::all(120))const;
|
||||
bool contains(const cv::Point2f &pt)const;
|
||||
cv::Point2f getCenter()const;
|
||||
const cv::Size2f &getAxes()const;
|
||||
|
||||
private:
|
||||
cv::Point2f center;
|
||||
cv::Size2f axes;
|
||||
float angle,cosf,sinf;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Chessboard corner detector
|
||||
*
|
||||
* The detectors tries to find all chessboard corners of an imaged
|
||||
* chessboard and returns them as an ordered vector of KeyPoints.
|
||||
* Thereby, the left top corner has index 0 and the bottom right
|
||||
* corner n*m-1.
|
||||
*/
|
||||
class Chessboard: public cv::Feature2D
|
||||
{
|
||||
public:
|
||||
static const int DUMMY_FIELD_SIZE = 100; // in pixel
|
||||
|
||||
/**
|
||||
* \brief Configuration of a chessboard corner detector
|
||||
*
|
||||
*/
|
||||
struct Parameters
|
||||
{
|
||||
cv::Size chessboard_size; //!< size of the chessboard
|
||||
int min_scale; //!< scale level [0..8]
|
||||
int max_scale; //!< scale level [0..8]
|
||||
int max_points; //!< maximal number of points regarded
|
||||
int max_tests; //!< maximal number of tested hypothesis
|
||||
bool super_resolution; //!< use super-repsolution for chessboard detection
|
||||
bool larger; //!< indicates if larger boards should be returned
|
||||
bool marker; //!< indicates that valid boards must have a white and black 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 ¢er,float white_angle,float black_angle, float min_response = 0)const;
|
||||
BState generateBoards(cv::flann::Index &flann_index,const cv::Mat &data, const cv::KeyPoint ¢er,
|
||||
float white_angle,float black_angle,float min_response,const cv::Mat &img,
|
||||
std::vector<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
@@ -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 ¶meters)
|
||||
{
|
||||
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> ¢ers);
|
||||
|
||||
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 ¶meters = 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_ */
|
||||
@@ -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
|
||||
|
||||
@@ -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. */
|
||||
@@ -5,6 +5,7 @@
|
||||
#define __OPENCV_TEST_PRECOMP_HPP__
|
||||
|
||||
#include "opencv2/ts.hpp"
|
||||
#include "opencv2/3d.hpp"
|
||||
#include "opencv2/objdetect.hpp"
|
||||
|
||||
#include <random>
|
||||
|
||||
Reference in New Issue
Block a user