mirror of
https://github.com/opencv/opencv.git
synced 2026-07-31 08:13:04 +04:00
Merge branch 4.x
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
@article{Aruco2014,
|
||||
author = {S. Garrido-Jurado and R. Mu\~noz-Salinas and F.J. Madrid-Cuevas and M.J. Mar\'in-Jim\'enez}
|
||||
title = {Automatic generation and detection of highly reliable fiducial markers under occlusion},
|
||||
year = {2014},
|
||||
pages = {2280 - 2292},
|
||||
journal = {Pattern Recognition},
|
||||
volume = {47},
|
||||
number = {6},
|
||||
issn = {0031-3203},
|
||||
doi = {http://dx.doi.org/10.1016/j.patcog.2014.01.005},
|
||||
url = {http://www.sciencedirect.com/science/article/pii/S0031320314000235}
|
||||
}
|
||||
|
||||
@inproceedings{wang2016iros,
|
||||
author = {John Wang and Edwin Olson},
|
||||
title = {{AprilTag} 2: Efficient and robust fiducial detection},
|
||||
booktitle = {Proceedings of the {IEEE/RSJ} International Conference on Intelligent Robots and Systems {(IROS)}},
|
||||
year = {2016},
|
||||
month = {October}
|
||||
}
|
||||
@@ -105,6 +105,26 @@ using a Boosted Cascade of Simple Features. IEEE CVPR, 2001. The paper is availa
|
||||
@defgroup objdetect_dnn_face DNN-based face detection and recognition
|
||||
Check @ref tutorial_dnn_face "the corresponding tutorial" for more details.
|
||||
@defgroup objdetect_common Common functions and classes
|
||||
@defgroup objdetect_aruco ArUco markers and boards detection for robust camera pose estimation
|
||||
@{
|
||||
ArUco Marker Detection
|
||||
Square fiducial markers (also known as Augmented Reality Markers) are useful for easy,
|
||||
fast and robust camera pose estimation.
|
||||
|
||||
The main functionality of ArucoDetector class is detection of markers in an image. If the markers are grouped
|
||||
as a board, then you can try to recover the missing markers with ArucoDetector::refineDetectedMarkers().
|
||||
ArUco markers can also be used for advanced chessboard corner finding. To do this, group the markers in the
|
||||
CharucoBoard and find the corners of the chessboard with the CharucoDetector::detectBoard().
|
||||
|
||||
The implementation is based on the ArUco Library by R. Muñoz-Salinas and S. Garrido-Jurado @cite Aruco2014.
|
||||
|
||||
Markers can also be detected based on the AprilTag 2 @cite wang2016iros fiducial detection method.
|
||||
|
||||
@sa @cite Aruco2014
|
||||
This code has been originally developed by Sergio Garrido-Jurado as a project
|
||||
for Google Summer of Code 2015 (GSoC 15).
|
||||
@}
|
||||
|
||||
@}
|
||||
*/
|
||||
|
||||
@@ -751,6 +771,12 @@ public:
|
||||
*/
|
||||
CV_WRAP void setEpsY(double epsY);
|
||||
|
||||
/** @brief use markers to improve the position of the corners of the QR code
|
||||
*
|
||||
* alignmentMarkers using by default
|
||||
*/
|
||||
CV_WRAP void setUseAlignmentMarkers(bool useAlignmentMarkers);
|
||||
|
||||
/** @brief Detects QR code in image and returns the quadrangle containing the code.
|
||||
@param img grayscale or color (BGR) image containing (or not) QR code.
|
||||
@param points Output vector of vertices of the minimum-area quadrangle containing the code.
|
||||
@@ -836,5 +862,7 @@ protected:
|
||||
|
||||
#include "opencv2/objdetect/detection_based_tracker.hpp"
|
||||
#include "opencv2/objdetect/face.hpp"
|
||||
#include "opencv2/objdetect/aruco_detector.hpp"
|
||||
#include "opencv2/objdetect/charuco_detector.hpp"
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
// 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 OPENCV_OBJDETECT_ARUCO_BOARD_HPP
|
||||
#define OPENCV_OBJDETECT_ARUCO_BOARD_HPP
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
|
||||
namespace cv {
|
||||
namespace aruco {
|
||||
//! @addtogroup objdetect_aruco
|
||||
//! @{
|
||||
|
||||
class Dictionary;
|
||||
|
||||
/** @brief Board of ArUco markers
|
||||
*
|
||||
* A board is a set of markers in the 3D space with a common coordinate system.
|
||||
* The common form of a board of marker is a planar (2D) board, however any 3D layout can be used.
|
||||
* A Board object is composed by:
|
||||
* - The object points of the marker corners, i.e. their coordinates respect to the board system.
|
||||
* - The dictionary which indicates the type of markers of the board
|
||||
* - The identifier of all the markers in the board.
|
||||
*/
|
||||
class CV_EXPORTS_W_SIMPLE Board {
|
||||
public:
|
||||
/** @brief Common Board constructor
|
||||
*
|
||||
* @param objPoints array of object points of all the marker corners in the board
|
||||
* @param dictionary the dictionary of markers employed for this board
|
||||
* @param ids vector of the identifiers of the markers in the board
|
||||
*/
|
||||
CV_WRAP Board(InputArrayOfArrays objPoints, const Dictionary& dictionary, InputArray ids);
|
||||
|
||||
/** @brief return the Dictionary of markers employed for this board
|
||||
*/
|
||||
CV_WRAP const Dictionary& getDictionary() const;
|
||||
|
||||
/** @brief return array of object points of all the marker corners in the board.
|
||||
*
|
||||
* Each marker include its 4 corners in this order:
|
||||
* - objPoints[i][0] - left-top point of i-th marker
|
||||
* - objPoints[i][1] - right-top point of i-th marker
|
||||
* - objPoints[i][2] - right-bottom point of i-th marker
|
||||
* - objPoints[i][3] - left-bottom point of i-th marker
|
||||
*
|
||||
* Markers are placed in a certain order - row by row, left to right in every row. For M markers, the size is Mx4.
|
||||
*/
|
||||
CV_WRAP const std::vector<std::vector<Point3f> >& getObjPoints() const;
|
||||
|
||||
/** @brief vector of the identifiers of the markers in the board (should be the same size as objPoints)
|
||||
* @return vector of the identifiers of the markers
|
||||
*/
|
||||
CV_WRAP const std::vector<int>& getIds() const;
|
||||
|
||||
/** @brief get coordinate of the bottom right corner of the board, is set when calling the function create()
|
||||
*/
|
||||
CV_WRAP const Point3f& getRightBottomCorner() const;
|
||||
|
||||
/** @brief Given a board configuration and a set of detected markers, returns the corresponding
|
||||
* image points and object points to call solvePnP()
|
||||
*
|
||||
* @param detectedCorners List of detected marker corners of the board.
|
||||
* For CharucoBoard class you can set list of charuco corners.
|
||||
* @param detectedIds List of identifiers for each marker or list of charuco identifiers for each corner.
|
||||
* For CharucoBoard class you can set list of charuco identifiers for each corner.
|
||||
* @param objPoints Vector of vectors of board marker points in the board coordinate space.
|
||||
* @param imgPoints Vector of vectors of the projections of board marker corner points.
|
||||
*/
|
||||
CV_WRAP void matchImagePoints(InputArrayOfArrays detectedCorners, InputArray detectedIds,
|
||||
OutputArray objPoints, OutputArray imgPoints) const;
|
||||
|
||||
/** @brief Draw a planar board
|
||||
*
|
||||
* @param outSize size of the output image in pixels.
|
||||
* @param img output image with the board. The size of this image will be outSize
|
||||
* and the board will be on the center, keeping the board proportions.
|
||||
* @param marginSize minimum margins (in pixels) of the board in the output image
|
||||
* @param borderBits width of the marker borders.
|
||||
*
|
||||
* This function return the image of the board, ready to be printed.
|
||||
*/
|
||||
CV_WRAP void generateImage(Size outSize, OutputArray img, int marginSize = 0, int borderBits = 1) const;
|
||||
|
||||
CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to “protected” (need to fix bindings first)
|
||||
Board();
|
||||
|
||||
struct Impl;
|
||||
protected:
|
||||
Board(const Ptr<Impl>& impl);
|
||||
Ptr<Impl> impl;
|
||||
};
|
||||
|
||||
/** @brief Planar board with grid arrangement of markers
|
||||
*
|
||||
* More common type of board. All markers are placed in the same plane in a grid arrangement.
|
||||
* The board image can be drawn using generateImage() method.
|
||||
*/
|
||||
class CV_EXPORTS_W_SIMPLE GridBoard : public Board {
|
||||
public:
|
||||
/**
|
||||
* @brief GridBoard constructor
|
||||
*
|
||||
* @param size number of markers in x and y directions
|
||||
* @param markerLength marker side length (normally in meters)
|
||||
* @param markerSeparation separation between two markers (same unit as markerLength)
|
||||
* @param dictionary dictionary of markers indicating the type of markers
|
||||
* @param ids set of marker ids in dictionary to use on board.
|
||||
*/
|
||||
CV_WRAP GridBoard(const Size& size, float markerLength, float markerSeparation,
|
||||
const Dictionary &dictionary, InputArray ids = noArray());
|
||||
|
||||
CV_WRAP Size getGridSize() const;
|
||||
CV_WRAP float getMarkerLength() const;
|
||||
CV_WRAP float getMarkerSeparation() const;
|
||||
|
||||
CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to “protected” (need to fix bindings first)
|
||||
GridBoard();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief ChArUco board is a planar chessboard where the markers are placed inside the white squares of a chessboard.
|
||||
*
|
||||
* The benefits of ChArUco boards is that they provide both, ArUco markers versatility and chessboard corner precision,
|
||||
* which is important for calibration and pose estimation. The board image can be drawn using generateImage() method.
|
||||
*/
|
||||
class CV_EXPORTS_W_SIMPLE CharucoBoard : public Board {
|
||||
public:
|
||||
/** @brief CharucoBoard constructor
|
||||
*
|
||||
* @param size number of chessboard squares in x and y directions
|
||||
* @param squareLength squareLength chessboard square side length (normally in meters)
|
||||
* @param markerLength marker side length (same unit than squareLength)
|
||||
* @param dictionary dictionary of markers indicating the type of markers
|
||||
* @param ids array of id used markers
|
||||
* The first markers in the dictionary are used to fill the white chessboard squares.
|
||||
*/
|
||||
CV_WRAP CharucoBoard(const Size& size, float squareLength, float markerLength,
|
||||
const Dictionary &dictionary, InputArray ids = noArray());
|
||||
|
||||
CV_WRAP Size getChessboardSize() const;
|
||||
CV_WRAP float getSquareLength() const;
|
||||
CV_WRAP float getMarkerLength() const;
|
||||
|
||||
/** @brief get CharucoBoard::chessboardCorners
|
||||
*/
|
||||
CV_WRAP std::vector<Point3f> getChessboardCorners() const;
|
||||
|
||||
/** @brief get CharucoBoard::nearestMarkerIdx
|
||||
*/
|
||||
CV_PROP std::vector<std::vector<int> > getNearestMarkerIdx() const;
|
||||
|
||||
/** @brief get CharucoBoard::nearestMarkerCorners
|
||||
*/
|
||||
CV_PROP std::vector<std::vector<int> > getNearestMarkerCorners() const;
|
||||
|
||||
/** @brief check whether the ChArUco markers are collinear
|
||||
*
|
||||
* @param charucoIds list of identifiers for each corner in charucoCorners per frame.
|
||||
* @return bool value, 1 (true) if detected corners form a line, 0 (false) if they do not.
|
||||
* solvePnP, calibration functions will fail if the corners are collinear (true).
|
||||
*
|
||||
* The number of ids in charucoIDs should be <= the number of chessboard corners in the board.
|
||||
* This functions checks whether the charuco corners are on a straight line (returns true, if so), or not (false).
|
||||
* Axis parallel, as well as diagonal and other straight lines detected. Degenerate cases:
|
||||
* for number of charucoIDs <= 2,the function returns true.
|
||||
*/
|
||||
CV_WRAP bool checkCharucoCornersCollinear(InputArray charucoIds) const;
|
||||
|
||||
CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to “protected” (need to fix bindings first)
|
||||
CharucoBoard();
|
||||
};
|
||||
|
||||
//! @}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,367 @@
|
||||
// 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 OPENCV_OBJDETECT_ARUCO_DETECTOR_HPP
|
||||
#define OPENCV_OBJDETECT_ARUCO_DETECTOR_HPP
|
||||
|
||||
#include <opencv2/objdetect/aruco_dictionary.hpp>
|
||||
#include <opencv2/objdetect/aruco_board.hpp>
|
||||
|
||||
namespace cv {
|
||||
namespace aruco {
|
||||
|
||||
//! @addtogroup objdetect_aruco
|
||||
//! @{
|
||||
|
||||
enum CornerRefineMethod{
|
||||
CORNER_REFINE_NONE, ///< Tag and corners detection based on the ArUco approach
|
||||
CORNER_REFINE_SUBPIX, ///< ArUco approach and refine the corners locations using corner subpixel accuracy
|
||||
CORNER_REFINE_CONTOUR, ///< ArUco approach and refine the corners locations using the contour-points line fitting
|
||||
CORNER_REFINE_APRILTAG, ///< Tag and corners detection based on the AprilTag 2 approach @cite wang2016iros
|
||||
};
|
||||
|
||||
/** @brief struct DetectorParameters is used by ArucoDetector
|
||||
*/
|
||||
struct CV_EXPORTS_W_SIMPLE DetectorParameters {
|
||||
CV_WRAP DetectorParameters() {
|
||||
adaptiveThreshWinSizeMin = 3;
|
||||
adaptiveThreshWinSizeMax = 23;
|
||||
adaptiveThreshWinSizeStep = 10;
|
||||
adaptiveThreshConstant = 7;
|
||||
minMarkerPerimeterRate = 0.03;
|
||||
maxMarkerPerimeterRate = 4.;
|
||||
polygonalApproxAccuracyRate = 0.03;
|
||||
minCornerDistanceRate = 0.05;
|
||||
minDistanceToBorder = 3;
|
||||
minMarkerDistanceRate = 0.05;
|
||||
cornerRefinementMethod = CORNER_REFINE_NONE;
|
||||
cornerRefinementWinSize = 5;
|
||||
cornerRefinementMaxIterations = 30;
|
||||
cornerRefinementMinAccuracy = 0.1;
|
||||
markerBorderBits = 1;
|
||||
perspectiveRemovePixelPerCell = 4;
|
||||
perspectiveRemoveIgnoredMarginPerCell = 0.13;
|
||||
maxErroneousBitsInBorderRate = 0.35;
|
||||
minOtsuStdDev = 5.0;
|
||||
errorCorrectionRate = 0.6;
|
||||
aprilTagQuadDecimate = 0.0;
|
||||
aprilTagQuadSigma = 0.0;
|
||||
aprilTagMinClusterPixels = 5;
|
||||
aprilTagMaxNmaxima = 10;
|
||||
aprilTagCriticalRad = (float)(10* CV_PI /180);
|
||||
aprilTagMaxLineFitMse = 10.0;
|
||||
aprilTagMinWhiteBlackDiff = 5;
|
||||
aprilTagDeglitch = 0;
|
||||
detectInvertedMarker = false;
|
||||
useAruco3Detection = false;
|
||||
minSideLengthCanonicalImg = 32;
|
||||
minMarkerLengthRatioOriginalImg = 0.0;
|
||||
};
|
||||
|
||||
/** @brief Read a new set of DetectorParameters from FileNode (use FileStorage.root()).
|
||||
*/
|
||||
CV_WRAP bool readDetectorParameters(const FileNode& fn);
|
||||
|
||||
/** @brief Write a set of DetectorParameters to FileStorage
|
||||
*/
|
||||
CV_WRAP bool writeDetectorParameters(FileStorage& fs, const String& name = String());
|
||||
|
||||
/// minimum window size for adaptive thresholding before finding contours (default 3).
|
||||
CV_PROP_RW int adaptiveThreshWinSizeMin;
|
||||
|
||||
/// maximum window size for adaptive thresholding before finding contours (default 23).
|
||||
CV_PROP_RW int adaptiveThreshWinSizeMax;
|
||||
|
||||
/// increments from adaptiveThreshWinSizeMin to adaptiveThreshWinSizeMax during the thresholding (default 10).
|
||||
CV_PROP_RW int adaptiveThreshWinSizeStep;
|
||||
|
||||
/// constant for adaptive thresholding before finding contours (default 7)
|
||||
CV_PROP_RW double adaptiveThreshConstant;
|
||||
|
||||
/** @brief determine minimum perimeter for marker contour to be detected.
|
||||
*
|
||||
* This is defined as a rate respect to the maximum dimension of the input image (default 0.03).
|
||||
*/
|
||||
CV_PROP_RW double minMarkerPerimeterRate;
|
||||
|
||||
/** @brief determine maximum perimeter for marker contour to be detected.
|
||||
*
|
||||
* This is defined as a rate respect to the maximum dimension of the input image (default 4.0).
|
||||
*/
|
||||
CV_PROP_RW double maxMarkerPerimeterRate;
|
||||
|
||||
/// minimum accuracy during the polygonal approximation process to determine which contours are squares. (default 0.03)
|
||||
CV_PROP_RW double polygonalApproxAccuracyRate;
|
||||
|
||||
/// minimum distance between corners for detected markers relative to its perimeter (default 0.05)
|
||||
CV_PROP_RW double minCornerDistanceRate;
|
||||
|
||||
/// minimum distance of any corner to the image border for detected markers (in pixels) (default 3)
|
||||
CV_PROP_RW int minDistanceToBorder;
|
||||
|
||||
/** @brief minimum mean distance beetween two marker corners to be considered imilar, so that the smaller one is removed.
|
||||
*
|
||||
* The rate is relative to the smaller perimeter of the two markers (default 0.05).
|
||||
*/
|
||||
CV_PROP_RW double minMarkerDistanceRate;
|
||||
|
||||
/** @brief default value CORNER_REFINE_NONE */
|
||||
CV_PROP_RW CornerRefineMethod cornerRefinementMethod;
|
||||
|
||||
/// window size for the corner refinement process (in pixels) (default 5).
|
||||
CV_PROP_RW int cornerRefinementWinSize;
|
||||
|
||||
/// maximum number of iterations for stop criteria of the corner refinement process (default 30).
|
||||
CV_PROP_RW int cornerRefinementMaxIterations;
|
||||
|
||||
/// minimum error for the stop cristeria of the corner refinement process (default: 0.1)
|
||||
CV_PROP_RW double cornerRefinementMinAccuracy;
|
||||
|
||||
/// number of bits of the marker border, i.e. marker border width (default 1).
|
||||
CV_PROP_RW int markerBorderBits;
|
||||
|
||||
/// number of bits (per dimension) for each cell of the marker when removing the perspective (default 4).
|
||||
CV_PROP_RW int perspectiveRemovePixelPerCell;
|
||||
|
||||
/** @brief width of the margin of pixels on each cell not considered for the determination of the cell bit.
|
||||
*
|
||||
* Represents the rate respect to the total size of the cell, i.e. perspectiveRemovePixelPerCell (default 0.13)
|
||||
*/
|
||||
CV_PROP_RW double perspectiveRemoveIgnoredMarginPerCell;
|
||||
|
||||
/** @brief maximum number of accepted erroneous bits in the border (i.e. number of allowed white bits in the border).
|
||||
*
|
||||
* Represented as a rate respect to the total number of bits per marker (default 0.35).
|
||||
*/
|
||||
CV_PROP_RW double maxErroneousBitsInBorderRate;
|
||||
|
||||
/** @brief minimun standard deviation in pixels values during the decodification step to apply Otsu
|
||||
* thresholding (otherwise, all the bits are set to 0 or 1 depending on mean higher than 128 or not) (default 5.0)
|
||||
*/
|
||||
CV_PROP_RW double minOtsuStdDev;
|
||||
|
||||
/// error correction rate respect to the maximun error correction capability for each dictionary (default 0.6).
|
||||
CV_PROP_RW double errorCorrectionRate;
|
||||
|
||||
/** @brief April :: User-configurable parameters.
|
||||
*
|
||||
* Detection of quads can be done on a lower-resolution image, improving speed at a cost of
|
||||
* pose accuracy and a slight decrease in detection rate. Decoding the binary payload is still
|
||||
*/
|
||||
CV_PROP_RW float aprilTagQuadDecimate;
|
||||
|
||||
/// what Gaussian blur should be applied to the segmented image (used for quad detection?)
|
||||
CV_PROP_RW float aprilTagQuadSigma;
|
||||
|
||||
// April :: Internal variables
|
||||
/// reject quads containing too few pixels (default 5).
|
||||
CV_PROP_RW int aprilTagMinClusterPixels;
|
||||
|
||||
/// how many corner candidates to consider when segmenting a group of pixels into a quad (default 10).
|
||||
CV_PROP_RW int aprilTagMaxNmaxima;
|
||||
|
||||
/** @brief reject quads where pairs of edges have angles that are close to straight or close to 180 degrees.
|
||||
*
|
||||
* Zero means that no quads are rejected. (In radians) (default 10*PI/180)
|
||||
*/
|
||||
CV_PROP_RW float aprilTagCriticalRad;
|
||||
|
||||
/// when fitting lines to the contours, what is the maximum mean squared error
|
||||
CV_PROP_RW float aprilTagMaxLineFitMse;
|
||||
|
||||
/** @brief add an extra check that the white model must be (overall) brighter than the black model.
|
||||
*
|
||||
* When we build our model of black & white pixels, we add an extra check that the white model must be (overall)
|
||||
* brighter than the black model. How much brighter? (in pixel values, [0,255]), (default 5)
|
||||
*/
|
||||
CV_PROP_RW int aprilTagMinWhiteBlackDiff;
|
||||
|
||||
/// should the thresholded image be deglitched? Only useful for very noisy images (default 0).
|
||||
CV_PROP_RW int aprilTagDeglitch;
|
||||
|
||||
/** @brief to check if there is a white marker.
|
||||
*
|
||||
* In order to generate a "white" marker just invert a normal marker by using a tilde, ~markerImage. (default false)
|
||||
*/
|
||||
CV_PROP_RW bool detectInvertedMarker;
|
||||
|
||||
/** @brief enable the new and faster Aruco detection strategy.
|
||||
*
|
||||
* Proposed in the paper:
|
||||
* Romero-Ramirez et al: Speeded up detection of squared fiducial markers (2018)
|
||||
* https://www.researchgate.net/publication/325787310_Speeded_Up_Detection_of_Squared_Fiducial_Markers
|
||||
*/
|
||||
CV_PROP_RW bool useAruco3Detection;
|
||||
|
||||
/// minimum side length of a marker in the canonical image. Latter is the binarized image in which contours are searched.
|
||||
CV_PROP_RW int minSideLengthCanonicalImg;
|
||||
|
||||
/// range [0,1], eq (2) from paper. The parameter tau_i has a direct influence on the processing speed.
|
||||
CV_PROP_RW float minMarkerLengthRatioOriginalImg;
|
||||
};
|
||||
|
||||
/** @brief struct RefineParameters is used by ArucoDetector
|
||||
*/
|
||||
struct CV_EXPORTS_W_SIMPLE RefineParameters {
|
||||
CV_WRAP RefineParameters(float minRepDistance = 10.f, float errorCorrectionRate = 3.f, bool checkAllOrders = true);
|
||||
|
||||
|
||||
/** @brief Read a new set of RefineParameters from FileNode (use FileStorage.root()).
|
||||
*/
|
||||
CV_WRAP bool readRefineParameters(const FileNode& fn);
|
||||
|
||||
/** @brief Write a set of RefineParameters to FileStorage
|
||||
*/
|
||||
CV_WRAP bool writeRefineParameters(FileStorage& fs, const String& name = String());
|
||||
|
||||
/** @brief minRepDistance minimum distance between the corners of the rejected candidate and the reprojected marker
|
||||
in order to consider it as a correspondence.
|
||||
*/
|
||||
CV_PROP_RW float minRepDistance;
|
||||
|
||||
/** @brief minRepDistance rate of allowed erroneous bits respect to the error correction capability of the used dictionary.
|
||||
*
|
||||
* -1 ignores the error correction step.
|
||||
*/
|
||||
CV_PROP_RW float errorCorrectionRate;
|
||||
|
||||
/** @brief checkAllOrders consider the four posible corner orders in the rejectedCorners array.
|
||||
*
|
||||
* If it set to false, only the provided corner order is considered (default true).
|
||||
*/
|
||||
CV_PROP_RW bool checkAllOrders;
|
||||
};
|
||||
|
||||
/** @brief The main functionality of ArucoDetector class is detection of markers in an image with detectMarkers() method.
|
||||
*
|
||||
* After detecting some markers in the image, you can try to find undetected markers from this dictionary with
|
||||
* refineDetectedMarkers() method.
|
||||
*
|
||||
* @see DetectorParameters, RefineParameters
|
||||
*/
|
||||
class CV_EXPORTS_W ArucoDetector : public Algorithm
|
||||
{
|
||||
public:
|
||||
/** @brief Basic ArucoDetector constructor
|
||||
*
|
||||
* @param dictionary indicates the type of markers that will be searched
|
||||
* @param detectorParams marker detection parameters
|
||||
* @param refineParams marker refine detection parameters
|
||||
*/
|
||||
CV_WRAP ArucoDetector(const Dictionary &dictionary = getPredefinedDictionary(cv::aruco::DICT_4X4_50),
|
||||
const DetectorParameters &detectorParams = DetectorParameters(),
|
||||
const RefineParameters& refineParams = RefineParameters());
|
||||
|
||||
/** @brief Basic marker detection
|
||||
*
|
||||
* @param image input image
|
||||
* @param corners vector of detected marker corners. For each marker, its four corners
|
||||
* are provided, (e.g std::vector<std::vector<cv::Point2f> > ). For N detected markers,
|
||||
* the dimensions of this array is Nx4. The order of the corners is clockwise.
|
||||
* @param ids vector of identifiers of the detected markers. The identifier is of type int
|
||||
* (e.g. std::vector<int>). For N detected markers, the size of ids is also N.
|
||||
* The identifiers have the same order than the markers in the imgPoints array.
|
||||
* @param rejectedImgPoints contains the imgPoints of those squares whose inner code has not a
|
||||
* correct codification. Useful for debugging purposes.
|
||||
*
|
||||
* Performs marker detection in the input image. Only markers included in the specific dictionary
|
||||
* are searched. For each detected marker, it returns the 2D position of its corner in the image
|
||||
* and its corresponding identifier.
|
||||
* Note that this function does not perform pose estimation.
|
||||
* @note The function does not correct lens distortion or takes it into account. It's recommended to undistort
|
||||
* input image with corresponging camera model, if camera parameters are known
|
||||
* @sa undistort, estimatePoseSingleMarkers, estimatePoseBoard
|
||||
*/
|
||||
CV_WRAP void detectMarkers(InputArray image, OutputArrayOfArrays corners, OutputArray ids,
|
||||
OutputArrayOfArrays rejectedImgPoints = noArray()) const;
|
||||
|
||||
/** @brief Refind not detected markers based on the already detected and the board layout
|
||||
*
|
||||
* @param image input image
|
||||
* @param board layout of markers in the board.
|
||||
* @param detectedCorners vector of already detected marker corners.
|
||||
* @param detectedIds vector of already detected marker identifiers.
|
||||
* @param rejectedCorners vector of rejected candidates during the marker detection process.
|
||||
* @param cameraMatrix optional input 3x3 floating-point camera matrix
|
||||
* \f$A = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{1}\f$
|
||||
* @param distCoeffs optional vector of distortion coefficients
|
||||
* \f$(k_1, k_2, p_1, p_2[, k_3[, k_4, k_5, k_6],[s_1, s_2, s_3, s_4]])\f$ of 4, 5, 8 or 12 elements
|
||||
* @param recoveredIdxs Optional array to returns the indexes of the recovered candidates in the
|
||||
* original rejectedCorners array.
|
||||
*
|
||||
* This function tries to find markers that were not detected in the basic detecMarkers function.
|
||||
* First, based on the current detected marker and the board layout, the function interpolates
|
||||
* the position of the missing markers. Then it tries to find correspondence between the reprojected
|
||||
* markers and the rejected candidates based on the minRepDistance and errorCorrectionRate parameters.
|
||||
* If camera parameters and distortion coefficients are provided, missing markers are reprojected
|
||||
* using projectPoint function. If not, missing marker projections are interpolated using global
|
||||
* homography, and all the marker corners in the board must have the same Z coordinate.
|
||||
*/
|
||||
CV_WRAP void refineDetectedMarkers(InputArray image, const Board &board,
|
||||
InputOutputArrayOfArrays detectedCorners,
|
||||
InputOutputArray detectedIds, InputOutputArrayOfArrays rejectedCorners,
|
||||
InputArray cameraMatrix = noArray(), InputArray distCoeffs = noArray(),
|
||||
OutputArray recoveredIdxs = noArray()) const;
|
||||
|
||||
CV_WRAP const Dictionary& getDictionary() const;
|
||||
CV_WRAP void setDictionary(const Dictionary& dictionary);
|
||||
|
||||
CV_WRAP const DetectorParameters& getDetectorParameters() const;
|
||||
CV_WRAP void setDetectorParameters(const DetectorParameters& detectorParameters);
|
||||
|
||||
CV_WRAP const RefineParameters& getRefineParameters() const;
|
||||
CV_WRAP void setRefineParameters(const RefineParameters& refineParameters);
|
||||
|
||||
/** @brief Stores algorithm parameters in a file storage
|
||||
*/
|
||||
virtual void write(FileStorage& fs) const override;
|
||||
|
||||
/** @brief simplified API for language bindings
|
||||
*/
|
||||
CV_WRAP inline void write(FileStorage& fs, const String& name) { Algorithm::write(fs, name); }
|
||||
|
||||
/** @brief Reads algorithm parameters from a file storage
|
||||
*/
|
||||
CV_WRAP virtual void read(const FileNode& fn) override;
|
||||
protected:
|
||||
struct ArucoDetectorImpl;
|
||||
Ptr<ArucoDetectorImpl> arucoDetectorImpl;
|
||||
};
|
||||
|
||||
/** @brief Draw detected markers in image
|
||||
*
|
||||
* @param image input/output image. It must have 1 or 3 channels. The number of channels is not altered.
|
||||
* @param corners positions of marker corners on input image.
|
||||
* (e.g std::vector<std::vector<cv::Point2f> > ). For N detected markers, the dimensions of
|
||||
* this array should be Nx4. The order of the corners should be clockwise.
|
||||
* @param ids vector of identifiers for markers in markersCorners .
|
||||
* Optional, if not provided, ids are not painted.
|
||||
* @param borderColor color of marker borders. Rest of colors (text color and first corner color)
|
||||
* are calculated based on this one to improve visualization.
|
||||
*
|
||||
* Given an array of detected marker corners and its corresponding ids, this functions draws
|
||||
* the markers in the image. The marker borders are painted and the markers identifiers if provided.
|
||||
* Useful for debugging purposes.
|
||||
*/
|
||||
CV_EXPORTS_W void drawDetectedMarkers(InputOutputArray image, InputArrayOfArrays corners,
|
||||
InputArray ids = noArray(), Scalar borderColor = Scalar(0, 255, 0));
|
||||
|
||||
/** @brief Generate a canonical marker image
|
||||
*
|
||||
* @param dictionary dictionary of markers indicating the type of markers
|
||||
* @param id identifier of the marker that will be returned. It has to be a valid id in the specified dictionary.
|
||||
* @param sidePixels size of the image in pixels
|
||||
* @param img output image with the marker
|
||||
* @param borderBits width of the marker border.
|
||||
*
|
||||
* This function returns a marker image in its canonical form (i.e. ready to be printed)
|
||||
*/
|
||||
CV_EXPORTS_W void generateImageMarker(const Dictionary &dictionary, int id, int sidePixels, OutputArray img,
|
||||
int borderBits = 1);
|
||||
|
||||
//! @}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,147 @@
|
||||
// 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 OPENCV_OBJDETECT_DICTIONARY_HPP
|
||||
#define OPENCV_OBJDETECT_DICTIONARY_HPP
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
|
||||
namespace cv {
|
||||
namespace aruco {
|
||||
|
||||
//! @addtogroup objdetect_aruco
|
||||
//! @{
|
||||
|
||||
|
||||
/** @brief Dictionary/Set of markers, it contains the inner codification
|
||||
*
|
||||
* BytesList contains the marker codewords where:
|
||||
* - bytesList.rows is the dictionary size
|
||||
* - each marker is encoded using `nbytes = ceil(markerSize*markerSize/8.)`
|
||||
* - each row contains all 4 rotations of the marker, so its length is `4*nbytes`
|
||||
*
|
||||
* `bytesList.ptr(i)[k*nbytes + j]` is then the j-th byte of i-th marker, in its k-th rotation.
|
||||
*/
|
||||
class CV_EXPORTS_W_SIMPLE Dictionary {
|
||||
|
||||
public:
|
||||
CV_PROP_RW Mat bytesList; // marker code information
|
||||
CV_PROP_RW int markerSize; // number of bits per dimension
|
||||
CV_PROP_RW int maxCorrectionBits; // maximum number of bits that can be corrected
|
||||
|
||||
|
||||
CV_WRAP Dictionary();
|
||||
|
||||
CV_WRAP Dictionary(const Mat &bytesList, int _markerSize, int maxcorr = 0);
|
||||
|
||||
|
||||
|
||||
/** @brief Read a new dictionary from FileNode.
|
||||
*
|
||||
* Dictionary format:\n
|
||||
* nmarkers: 35\n
|
||||
* markersize: 6\n
|
||||
* maxCorrectionBits: 5\n
|
||||
* marker_0: "101011111011111001001001101100000000"\n
|
||||
* ...\n
|
||||
* marker_34: "011111010000111011111110110101100101"
|
||||
*/
|
||||
CV_WRAP bool readDictionary(const cv::FileNode& fn);
|
||||
|
||||
/** @brief Write a dictionary to FileStorage, format is the same as in readDictionary().
|
||||
*/
|
||||
CV_WRAP void writeDictionary(FileStorage& fs, const String& name = String());
|
||||
|
||||
/** @brief Given a matrix of bits. Returns whether if marker is identified or not.
|
||||
*
|
||||
* It returns by reference the correct id (if any) and the correct rotation
|
||||
*/
|
||||
CV_WRAP bool identify(const Mat &onlyBits, CV_OUT int &idx, CV_OUT int &rotation, double maxCorrectionRate) const;
|
||||
|
||||
/** @brief Returns the distance of the input bits to the specific id.
|
||||
*
|
||||
* If allRotations is true, the four posible bits rotation are considered
|
||||
*/
|
||||
CV_WRAP int getDistanceToId(InputArray bits, int id, bool allRotations = true) const;
|
||||
|
||||
|
||||
/** @brief Generate a canonical marker image
|
||||
*/
|
||||
CV_WRAP void generateImageMarker(int id, int sidePixels, OutputArray _img, int borderBits = 1) const;
|
||||
|
||||
|
||||
/** @brief Transform matrix of bits to list of bytes in the 4 rotations
|
||||
*/
|
||||
CV_WRAP static Mat getByteListFromBits(const Mat &bits);
|
||||
|
||||
|
||||
/** @brief Transform list of bytes to matrix of bits
|
||||
*/
|
||||
CV_WRAP static Mat getBitsFromByteList(const Mat &byteList, int markerSize);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/** @brief Predefined markers dictionaries/sets
|
||||
*
|
||||
* Each dictionary indicates the number of bits and the number of markers contained
|
||||
* - DICT_ARUCO_ORIGINAL: standard ArUco Library Markers. 1024 markers, 5x5 bits, 0 minimum
|
||||
distance
|
||||
*/
|
||||
enum PredefinedDictionaryType {
|
||||
DICT_4X4_50 = 0, ///< 4x4 bits, minimum hamming distance between any two codes = 4, 50 codes
|
||||
DICT_4X4_100, ///< 4x4 bits, minimum hamming distance between any two codes = 3, 100 codes
|
||||
DICT_4X4_250, ///< 4x4 bits, minimum hamming distance between any two codes = 3, 250 codes
|
||||
DICT_4X4_1000, ///< 4x4 bits, minimum hamming distance between any two codes = 2, 1000 codes
|
||||
DICT_5X5_50, ///< 5x5 bits, minimum hamming distance between any two codes = 8, 50 codes
|
||||
DICT_5X5_100, ///< 5x5 bits, minimum hamming distance between any two codes = 7, 100 codes
|
||||
DICT_5X5_250, ///< 5x5 bits, minimum hamming distance between any two codes = 6, 250 codes
|
||||
DICT_5X5_1000, ///< 5x5 bits, minimum hamming distance between any two codes = 5, 1000 codes
|
||||
DICT_6X6_50, ///< 6x6 bits, minimum hamming distance between any two codes = 13, 50 codes
|
||||
DICT_6X6_100, ///< 6x6 bits, minimum hamming distance between any two codes = 12, 100 codes
|
||||
DICT_6X6_250, ///< 6x6 bits, minimum hamming distance between any two codes = 11, 250 codes
|
||||
DICT_6X6_1000, ///< 6x6 bits, minimum hamming distance between any two codes = 9, 1000 codes
|
||||
DICT_7X7_50, ///< 7x7 bits, minimum hamming distance between any two codes = 19, 50 codes
|
||||
DICT_7X7_100, ///< 7x7 bits, minimum hamming distance between any two codes = 18, 100 codes
|
||||
DICT_7X7_250, ///< 7x7 bits, minimum hamming distance between any two codes = 17, 250 codes
|
||||
DICT_7X7_1000, ///< 7x7 bits, minimum hamming distance between any two codes = 14, 1000 codes
|
||||
DICT_ARUCO_ORIGINAL, ///< 6x6 bits, minimum hamming distance between any two codes = 3, 1024 codes
|
||||
DICT_APRILTAG_16h5, ///< 4x4 bits, minimum hamming distance between any two codes = 5, 30 codes
|
||||
DICT_APRILTAG_25h9, ///< 5x5 bits, minimum hamming distance between any two codes = 9, 35 codes
|
||||
DICT_APRILTAG_36h10, ///< 6x6 bits, minimum hamming distance between any two codes = 10, 2320 codes
|
||||
DICT_APRILTAG_36h11 ///< 6x6 bits, minimum hamming distance between any two codes = 11, 587 codes
|
||||
};
|
||||
|
||||
|
||||
/** @brief Returns one of the predefined dictionaries defined in PredefinedDictionaryType
|
||||
*/
|
||||
CV_EXPORTS Dictionary getPredefinedDictionary(PredefinedDictionaryType name);
|
||||
|
||||
|
||||
/** @brief Returns one of the predefined dictionaries referenced by DICT_*.
|
||||
*/
|
||||
CV_EXPORTS_W Dictionary getPredefinedDictionary(int dict);
|
||||
|
||||
/** @brief Extend base dictionary by new nMarkers
|
||||
*
|
||||
* @param nMarkers number of markers in the dictionary
|
||||
* @param markerSize number of bits per dimension of each markers
|
||||
* @param baseDictionary Include the markers in this dictionary at the beginning (optional)
|
||||
* @param randomSeed a user supplied seed for theRNG()
|
||||
*
|
||||
* This function creates a new dictionary composed by nMarkers markers and each markers composed
|
||||
* by markerSize x markerSize bits. If baseDictionary is provided, its markers are directly
|
||||
* included and the rest are generated based on them. If the size of baseDictionary is higher
|
||||
* than nMarkers, only the first nMarkers in baseDictionary are taken and no new marker is added.
|
||||
*/
|
||||
CV_EXPORTS_W Dictionary extendDictionary(int nMarkers, int markerSize, const Dictionary &baseDictionary = Dictionary(),
|
||||
int randomSeed=0);
|
||||
|
||||
|
||||
|
||||
//! @}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,154 @@
|
||||
// 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 OPENCV_OBJDETECT_CHARUCO_DETECTOR_HPP
|
||||
#define OPENCV_OBJDETECT_CHARUCO_DETECTOR_HPP
|
||||
|
||||
#include "opencv2/objdetect/aruco_detector.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace aruco {
|
||||
|
||||
//! @addtogroup objdetect_aruco
|
||||
//! @{
|
||||
|
||||
struct CV_EXPORTS_W_SIMPLE CharucoParameters {
|
||||
CharucoParameters() {
|
||||
minMarkers = 2;
|
||||
tryRefineMarkers = false;
|
||||
}
|
||||
/// cameraMatrix optional 3x3 floating-point camera matrix
|
||||
CV_PROP_RW Mat cameraMatrix;
|
||||
|
||||
/// distCoeffs optional vector of distortion coefficients
|
||||
CV_PROP_RW Mat distCoeffs;
|
||||
|
||||
/// minMarkers number of adjacent markers that must be detected to return a charuco corner, default = 2
|
||||
CV_PROP_RW int minMarkers;
|
||||
|
||||
/// try to use refine board, default false
|
||||
CV_PROP_RW bool tryRefineMarkers;
|
||||
};
|
||||
|
||||
class CV_EXPORTS_W CharucoDetector : public Algorithm {
|
||||
public:
|
||||
/** @brief Basic CharucoDetector constructor
|
||||
*
|
||||
* @param board ChAruco board
|
||||
* @param charucoParams charuco detection parameters
|
||||
* @param detectorParams marker detection parameters
|
||||
* @param refineParams marker refine detection parameters
|
||||
*/
|
||||
CV_WRAP CharucoDetector(const CharucoBoard& board,
|
||||
const CharucoParameters& charucoParams = CharucoParameters(),
|
||||
const DetectorParameters &detectorParams = DetectorParameters(),
|
||||
const RefineParameters& refineParams = RefineParameters());
|
||||
|
||||
CV_WRAP const CharucoBoard& getBoard() const;
|
||||
CV_WRAP void setBoard(const CharucoBoard& board);
|
||||
|
||||
CV_WRAP const CharucoParameters& getCharucoParameters() const;
|
||||
CV_WRAP void setCharucoParameters(CharucoParameters& charucoParameters);
|
||||
|
||||
CV_WRAP const DetectorParameters& getDetectorParameters() const;
|
||||
CV_WRAP void setDetectorParameters(const DetectorParameters& detectorParameters);
|
||||
|
||||
CV_WRAP const RefineParameters& getRefineParameters() const;
|
||||
CV_WRAP void setRefineParameters(const RefineParameters& refineParameters);
|
||||
|
||||
/**
|
||||
* @brief detect aruco markers and interpolate position of ChArUco board corners
|
||||
* @param image input image necesary for corner refinement. Note that markers are not detected and
|
||||
* should be sent in corners and ids parameters.
|
||||
* @param charucoCorners interpolated chessboard corners.
|
||||
* @param charucoIds interpolated chessboard corners identifiers.
|
||||
* @param markerCorners vector of already detected markers corners. For each marker, its four
|
||||
* corners are provided, (e.g std::vector<std::vector<cv::Point2f> > ). For N detected markers, the
|
||||
* dimensions of this array should be Nx4. The order of the corners should be clockwise.
|
||||
* If markerCorners and markerCorners are empty, the function detect aruco markers and ids.
|
||||
* @param markerIds list of identifiers for each marker in corners.
|
||||
* If markerCorners and markerCorners are empty, the function detect aruco markers and ids.
|
||||
*
|
||||
* This function receives the detected markers and returns the 2D position of the chessboard corners
|
||||
* from a ChArUco board using the detected Aruco markers.
|
||||
*
|
||||
* If markerCorners and markerCorners are empty, the detectMarkers() will run and detect aruco markers and ids.
|
||||
*
|
||||
* If camera parameters are provided, the process is based in an approximated pose estimation, else it is based on local homography.
|
||||
* Only visible corners are returned. For each corner, its corresponding identifier is also returned in charucoIds.
|
||||
* @sa findChessboardCorners
|
||||
*/
|
||||
CV_WRAP void detectBoard(InputArray image, OutputArray charucoCorners, OutputArray charucoIds,
|
||||
InputOutputArrayOfArrays markerCorners = noArray(),
|
||||
InputOutputArray markerIds = noArray()) const;
|
||||
|
||||
/**
|
||||
* @brief Detect ChArUco Diamond markers
|
||||
*
|
||||
* @param image input image necessary for corner subpixel.
|
||||
* @param diamondCorners output list of detected diamond corners (4 corners per diamond). The order
|
||||
* is the same than in marker corners: top left, top right, bottom right and bottom left. Similar
|
||||
* format than the corners returned by detectMarkers (e.g std::vector<std::vector<cv::Point2f> > ).
|
||||
* @param diamondIds ids of the diamonds in diamondCorners. The id of each diamond is in fact of
|
||||
* type Vec4i, so each diamond has 4 ids, which are the ids of the aruco markers composing the
|
||||
* diamond.
|
||||
* @param markerCorners list of detected marker corners from detectMarkers function.
|
||||
* If markerCorners and markerCorners are empty, the function detect aruco markers and ids.
|
||||
* @param markerIds list of marker ids in markerCorners.
|
||||
* If markerCorners and markerCorners are empty, the function detect aruco markers and ids.
|
||||
*
|
||||
* This function detects Diamond markers from the previous detected ArUco markers. The diamonds
|
||||
* are returned in the diamondCorners and diamondIds parameters. If camera calibration parameters
|
||||
* are provided, the diamond search is based on reprojection. If not, diamond search is based on
|
||||
* homography. Homography is faster than reprojection, but less accurate.
|
||||
*/
|
||||
CV_WRAP void detectDiamonds(InputArray image, OutputArrayOfArrays diamondCorners, OutputArray diamondIds,
|
||||
InputOutputArrayOfArrays markerCorners = noArray(),
|
||||
InputOutputArrayOfArrays markerIds = noArray()) const;
|
||||
protected:
|
||||
struct CharucoDetectorImpl;
|
||||
Ptr<CharucoDetectorImpl> charucoDetectorImpl;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Draws a set of Charuco corners
|
||||
* @param image input/output image. It must have 1 or 3 channels. The number of channels is not
|
||||
* altered.
|
||||
* @param charucoCorners vector of detected charuco corners
|
||||
* @param charucoIds list of identifiers for each corner in charucoCorners
|
||||
* @param cornerColor color of the square surrounding each corner
|
||||
*
|
||||
* This function draws a set of detected Charuco corners. If identifiers vector is provided, it also
|
||||
* draws the id of each corner.
|
||||
*/
|
||||
CV_EXPORTS_W void drawDetectedCornersCharuco(InputOutputArray image, InputArray charucoCorners,
|
||||
InputArray charucoIds = noArray(), Scalar cornerColor = Scalar(255, 0, 0));
|
||||
|
||||
/**
|
||||
* @brief Draw a set of detected ChArUco Diamond markers
|
||||
*
|
||||
* @param image input/output image. It must have 1 or 3 channels. The number of channels is not
|
||||
* altered.
|
||||
* @param diamondCorners positions of diamond corners in the same format returned by
|
||||
* detectCharucoDiamond(). (e.g std::vector<std::vector<cv::Point2f> > ). For N detected markers,
|
||||
* the dimensions of this array should be Nx4. The order of the corners should be clockwise.
|
||||
* @param diamondIds vector of identifiers for diamonds in diamondCorners, in the same format
|
||||
* returned by detectCharucoDiamond() (e.g. std::vector<Vec4i>).
|
||||
* Optional, if not provided, ids are not painted.
|
||||
* @param borderColor color of marker borders. Rest of colors (text color and first corner color)
|
||||
* are calculated based on this one.
|
||||
*
|
||||
* Given an array of detected diamonds, this functions draws them in the image. The marker borders
|
||||
* are painted and the markers identifiers if provided.
|
||||
* Useful for debugging purposes.
|
||||
*/
|
||||
CV_EXPORTS_W void drawDetectedDiamonds(InputOutputArray image, InputArrayOfArrays diamondCorners,
|
||||
InputArray diamondIds = noArray(),
|
||||
Scalar borderColor = Scalar(0, 0, 255));
|
||||
|
||||
//! @}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,113 @@
|
||||
package org.opencv.test.aruco;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.test.OpenCVTestCase;
|
||||
import org.junit.Assert;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfInt;
|
||||
import org.opencv.core.Size;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.objdetect.*;
|
||||
|
||||
|
||||
public class ArucoTest extends OpenCVTestCase {
|
||||
|
||||
public void testGenerateBoards() {
|
||||
Dictionary dictionary = Objdetect.getPredefinedDictionary(Objdetect.DICT_4X4_50);
|
||||
|
||||
Mat point1 = new Mat(4, 3, CvType.CV_32FC1);
|
||||
int row = 0, col = 0;
|
||||
double squareLength = 40.;
|
||||
point1.put(row, col, 0, 0, 0,
|
||||
0, squareLength, 0,
|
||||
squareLength, squareLength, 0,
|
||||
0, squareLength, 0);
|
||||
List<Mat>objPoints = new ArrayList<Mat>();
|
||||
objPoints.add(point1);
|
||||
|
||||
Mat ids = new Mat(1, 1, CvType.CV_32SC1);
|
||||
ids.put(row, col, 0);
|
||||
|
||||
Board board = new Board(objPoints, dictionary, ids);
|
||||
|
||||
Mat image = new Mat();
|
||||
board.generateImage(new Size(80, 80), image, 2);
|
||||
|
||||
assertTrue(image.total() > 0);
|
||||
}
|
||||
|
||||
public void testArucoIssue3133() {
|
||||
byte[][] marker = {{0,1,1},{1,1,1},{0,1,1}};
|
||||
Dictionary dictionary = Objdetect.extendDictionary(1, 3);
|
||||
dictionary.set_maxCorrectionBits(0);
|
||||
Mat markerBits = new Mat(3, 3, CvType.CV_8UC1);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
markerBits.put(i, j, marker[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
Mat markerCompressed = Dictionary.getByteListFromBits(markerBits);
|
||||
assertMatNotEqual(markerCompressed, dictionary.get_bytesList());
|
||||
|
||||
dictionary.set_bytesList(markerCompressed);
|
||||
assertMatEqual(markerCompressed, dictionary.get_bytesList());
|
||||
}
|
||||
|
||||
public void testArucoDetector() {
|
||||
Dictionary dictionary = Objdetect.getPredefinedDictionary(0);
|
||||
DetectorParameters detectorParameters = new DetectorParameters();
|
||||
ArucoDetector detector = new ArucoDetector(dictionary, detectorParameters);
|
||||
|
||||
Mat markerImage = new Mat();
|
||||
int id = 1, offset = 5, size = 40;
|
||||
Objdetect.generateImageMarker(dictionary, id, size, markerImage, detectorParameters.get_markerBorderBits());
|
||||
|
||||
Mat image = new Mat(markerImage.rows() + 2*offset, markerImage.cols() + 2*offset,
|
||||
CvType.CV_8UC1, new Scalar(255));
|
||||
Mat m = image.submat(offset, size+offset, offset, size+offset);
|
||||
markerImage.copyTo(m);
|
||||
|
||||
List<Mat> corners = new ArrayList();
|
||||
Mat ids = new Mat();
|
||||
detector.detectMarkers(image, corners, ids);
|
||||
|
||||
assertEquals(1, corners.size());
|
||||
Mat res = corners.get(0);
|
||||
assertArrayEquals(new double[]{offset, offset}, res.get(0, 0), 0.0);
|
||||
assertArrayEquals(new double[]{size + offset - 1, offset}, res.get(0, 1), 0.0);
|
||||
assertArrayEquals(new double[]{size + offset - 1, size + offset - 1}, res.get(0, 2), 0.0);
|
||||
assertArrayEquals(new double[]{offset, size + offset - 1}, res.get(0, 3), 0.0);
|
||||
}
|
||||
|
||||
public void testCharucoDetector() {
|
||||
Dictionary dictionary = Objdetect.getPredefinedDictionary(0);
|
||||
int boardSizeX = 3, boardSizeY = 3;
|
||||
CharucoBoard board = new CharucoBoard(new Size(boardSizeX, boardSizeY), 1.f, 0.8f, dictionary);
|
||||
CharucoDetector charucoDetector = new CharucoDetector(board);
|
||||
|
||||
int cellSize = 80;
|
||||
Mat boardImage = new Mat();
|
||||
board.generateImage(new Size(cellSize*boardSizeX, cellSize*boardSizeY), boardImage);
|
||||
|
||||
assertTrue(boardImage.total() > 0);
|
||||
|
||||
Mat charucoCorners = new Mat();
|
||||
Mat charucoIds = new Mat();
|
||||
charucoDetector.detectBoard(boardImage, charucoCorners, charucoIds);
|
||||
|
||||
assertEquals(4, charucoIds.total());
|
||||
int[] intCharucoIds = (new MatOfInt(charucoIds)).toArray();
|
||||
Assert.assertArrayEquals(new int[]{0, 1, 2, 3}, intCharucoIds);
|
||||
|
||||
double eps = 0.2;
|
||||
assertArrayEquals(new double[]{cellSize, cellSize}, charucoCorners.get(0, 0), eps);
|
||||
assertArrayEquals(new double[]{2*cellSize, cellSize}, charucoCorners.get(1, 0), eps);
|
||||
assertArrayEquals(new double[]{cellSize, 2*cellSize}, charucoCorners.get(2, 0), eps);
|
||||
assertArrayEquals(new double[]{2*cellSize, 2*cellSize}, charucoCorners.get(3, 0), eps);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import os, tempfile, numpy as np
|
||||
|
||||
import cv2 as cv
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class aruco_objdetect_test(NewOpenCVTests):
|
||||
|
||||
def test_idsAccessibility(self):
|
||||
|
||||
ids = np.arange(17)
|
||||
rev_ids = ids[::-1]
|
||||
|
||||
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_5X5_250)
|
||||
board = cv.aruco.CharucoBoard((7, 5), 1, 0.5, aruco_dict)
|
||||
|
||||
np.testing.assert_array_equal(board.getIds().squeeze(), ids)
|
||||
|
||||
board = cv.aruco.CharucoBoard((7, 5), 1, 0.5, aruco_dict, rev_ids)
|
||||
np.testing.assert_array_equal(board.getIds().squeeze(), rev_ids)
|
||||
|
||||
board = cv.aruco.CharucoBoard((7, 5), 1, 0.5, aruco_dict, ids)
|
||||
np.testing.assert_array_equal(board.getIds().squeeze(), ids)
|
||||
|
||||
def test_identify(self):
|
||||
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_50)
|
||||
expected_idx = 9
|
||||
expected_rotation = 2
|
||||
bit_marker = np.array([[0, 1, 1, 0], [1, 0, 1, 0], [1, 1, 1, 1], [0, 0, 1, 1]], dtype=np.uint8)
|
||||
|
||||
check, idx, rotation = aruco_dict.identify(bit_marker, 0)
|
||||
|
||||
self.assertTrue(check, True)
|
||||
self.assertEqual(idx, expected_idx)
|
||||
self.assertEqual(rotation, expected_rotation)
|
||||
|
||||
def test_getDistanceToId(self):
|
||||
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_50)
|
||||
idx = 7
|
||||
rotation = 3
|
||||
bit_marker = np.array([[0, 1, 0, 1], [0, 1, 1, 1], [1, 1, 0, 0], [0, 1, 0, 0]], dtype=np.uint8)
|
||||
dist = aruco_dict.getDistanceToId(bit_marker, idx)
|
||||
|
||||
self.assertEqual(dist, 0)
|
||||
|
||||
def test_aruco_detector(self):
|
||||
aruco_params = cv.aruco.DetectorParameters()
|
||||
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_250)
|
||||
aruco_detector = cv.aruco.ArucoDetector(aruco_dict, aruco_params)
|
||||
id = 2
|
||||
marker_size = 100
|
||||
offset = 10
|
||||
img_marker = cv.aruco.generateImageMarker(aruco_dict, id, marker_size, aruco_params.markerBorderBits)
|
||||
img_marker = np.pad(img_marker, pad_width=offset, mode='constant', constant_values=255)
|
||||
gold_corners = np.array([[offset, offset],[marker_size+offset-1.0,offset],
|
||||
[marker_size+offset-1.0,marker_size+offset-1.0],
|
||||
[offset, marker_size+offset-1.0]], dtype=np.float32)
|
||||
corners, ids, rejected = aruco_detector.detectMarkers(img_marker)
|
||||
|
||||
self.assertEqual(1, len(ids))
|
||||
self.assertEqual(id, ids[0])
|
||||
for i in range(0, len(corners)):
|
||||
np.testing.assert_array_equal(gold_corners, corners[i].reshape(4, 2))
|
||||
|
||||
def test_aruco_detector_refine(self):
|
||||
aruco_params = cv.aruco.DetectorParameters()
|
||||
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_250)
|
||||
aruco_detector = cv.aruco.ArucoDetector(aruco_dict, aruco_params)
|
||||
board_size = (3, 4)
|
||||
board = cv.aruco.GridBoard(board_size, 5.0, 1.0, aruco_dict)
|
||||
board_image = board.generateImage((board_size[0]*50, board_size[1]*50), marginSize=10)
|
||||
|
||||
corners, ids, rejected = aruco_detector.detectMarkers(board_image)
|
||||
self.assertEqual(board_size[0]*board_size[1], len(ids))
|
||||
|
||||
part_corners, part_ids, part_rejected = corners[:-1], ids[:-1], list(rejected)
|
||||
part_rejected.append(corners[-1])
|
||||
|
||||
refine_corners, refine_ids, refine_rejected, recovered_ids = aruco_detector.refineDetectedMarkers(board_image, board, part_corners, part_ids, part_rejected)
|
||||
|
||||
self.assertEqual(board_size[0] * board_size[1], len(refine_ids))
|
||||
self.assertEqual(1, len(recovered_ids))
|
||||
|
||||
self.assertEqual(ids[-1], refine_ids[-1])
|
||||
self.assertEqual((1, 4, 2), refine_corners[0].shape)
|
||||
np.testing.assert_array_equal(corners, refine_corners)
|
||||
|
||||
def test_write_read_dictionary(self):
|
||||
try:
|
||||
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_5X5_50)
|
||||
markers_gold = aruco_dict.bytesList
|
||||
|
||||
# write aruco_dict
|
||||
fd, filename = tempfile.mkstemp(prefix="opencv_python_aruco_dict_", suffix=".yml")
|
||||
os.close(fd)
|
||||
|
||||
fs_write = cv.FileStorage(filename, cv.FileStorage_WRITE)
|
||||
aruco_dict.writeDictionary(fs_write)
|
||||
fs_write.release()
|
||||
|
||||
# reset aruco_dict
|
||||
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_6X6_250)
|
||||
|
||||
# read aruco_dict
|
||||
fs_read = cv.FileStorage(filename, cv.FileStorage_READ)
|
||||
aruco_dict.readDictionary(fs_read.root())
|
||||
fs_read.release()
|
||||
|
||||
# check equal
|
||||
self.assertEqual(aruco_dict.markerSize, 5)
|
||||
self.assertEqual(aruco_dict.maxCorrectionBits, 3)
|
||||
np.testing.assert_array_equal(aruco_dict.bytesList, markers_gold)
|
||||
|
||||
finally:
|
||||
if os.path.exists(filename):
|
||||
os.remove(filename)
|
||||
|
||||
def test_charuco_detector(self):
|
||||
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_250)
|
||||
board_size = (3, 3)
|
||||
board = cv.aruco.CharucoBoard(board_size, 1.0, .8, aruco_dict)
|
||||
charuco_detector = cv.aruco.CharucoDetector(board)
|
||||
cell_size = 100
|
||||
|
||||
image = board.generateImage((cell_size*board_size[0], cell_size*board_size[1]))
|
||||
|
||||
list_gold_corners = []
|
||||
for i in range(1, board_size[0]):
|
||||
for j in range(1, board_size[1]):
|
||||
list_gold_corners.append((j*cell_size, i*cell_size))
|
||||
gold_corners = np.array(list_gold_corners, dtype=np.float32)
|
||||
|
||||
charucoCorners, charucoIds, markerCorners, markerIds = charuco_detector.detectBoard(image)
|
||||
|
||||
self.assertEqual(len(charucoIds), 4)
|
||||
for i in range(0, 4):
|
||||
self.assertEqual(charucoIds[i], i)
|
||||
np.testing.assert_allclose(gold_corners, charucoCorners.reshape(-1, 2), 0.01, 0.1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -43,10 +43,10 @@ class qrcode_detector_test(NewOpenCVTests):
|
||||
retval, decoded_data, points, straight_qrcode = detector.detectAndDecodeMulti(img)
|
||||
self.assertTrue(retval)
|
||||
self.assertEqual(len(decoded_data), 6)
|
||||
self.assertEqual(decoded_data[0], "TWO STEPS FORWARD")
|
||||
self.assertEqual(decoded_data[1], "EXTRA")
|
||||
self.assertEqual(decoded_data[2], "SKIP")
|
||||
self.assertEqual(decoded_data[3], "STEP FORWARD")
|
||||
self.assertEqual(decoded_data[4], "STEP BACK")
|
||||
self.assertEqual(decoded_data[5], "QUESTION")
|
||||
self.assertTrue("TWO STEPS FORWARD" in decoded_data)
|
||||
self.assertTrue("EXTRA" in decoded_data)
|
||||
self.assertTrue("SKIP" in decoded_data)
|
||||
self.assertTrue("STEP FORWARD" in decoded_data)
|
||||
self.assertTrue("STEP BACK" in decoded_data)
|
||||
self.assertTrue("QUESTION" in decoded_data)
|
||||
self.assertEqual(points.shape, (6, 4, 2))
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
// 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
|
||||
#include "perf_precomp.hpp"
|
||||
#include "opencv2/3d.hpp"
|
||||
|
||||
namespace opencv_test {
|
||||
using namespace perf;
|
||||
|
||||
typedef tuple<bool, int> UseArucoParams;
|
||||
typedef TestBaseWithParam<UseArucoParams> EstimateAruco;
|
||||
#define ESTIMATE_PARAMS Combine(Values(false, true), Values(-1))
|
||||
|
||||
static double deg2rad(double deg) { return deg * CV_PI / 180.; }
|
||||
|
||||
class MarkerPainter
|
||||
{
|
||||
private:
|
||||
int imgMarkerSize = 0;
|
||||
Mat cameraMatrix;
|
||||
public:
|
||||
MarkerPainter(const int size) {
|
||||
setImgMarkerSize(size);
|
||||
}
|
||||
|
||||
void setImgMarkerSize(const int size) {
|
||||
imgMarkerSize = size;
|
||||
cameraMatrix = Mat::eye(3, 3, CV_64FC1);
|
||||
cameraMatrix.at<double>(0, 0) = cameraMatrix.at<double>(1, 1) = imgMarkerSize;
|
||||
cameraMatrix.at<double>(0, 2) = imgMarkerSize / 2.0;
|
||||
cameraMatrix.at<double>(1, 2) = imgMarkerSize / 2.0;
|
||||
}
|
||||
|
||||
static std::pair<Mat, Mat> getSyntheticRT(double yaw, double pitch, double distance) {
|
||||
auto rvec_tvec = std::make_pair(Mat(3, 1, CV_64FC1), Mat(3, 1, CV_64FC1));
|
||||
Mat& rvec = rvec_tvec.first;
|
||||
Mat& tvec = rvec_tvec.second;
|
||||
|
||||
// Rvec
|
||||
// first put the Z axis aiming to -X (like the camera axis system)
|
||||
Mat rotZ(3, 1, CV_64FC1);
|
||||
rotZ.ptr<double>(0)[0] = 0;
|
||||
rotZ.ptr<double>(0)[1] = 0;
|
||||
rotZ.ptr<double>(0)[2] = -0.5 * CV_PI;
|
||||
|
||||
Mat rotX(3, 1, CV_64FC1);
|
||||
rotX.ptr<double>(0)[0] = 0.5 * CV_PI;
|
||||
rotX.ptr<double>(0)[1] = 0;
|
||||
rotX.ptr<double>(0)[2] = 0;
|
||||
|
||||
Mat camRvec, camTvec;
|
||||
composeRT(rotZ, Mat(3, 1, CV_64FC1, Scalar::all(0)), rotX, Mat(3, 1, CV_64FC1, Scalar::all(0)),
|
||||
camRvec, camTvec);
|
||||
|
||||
// now pitch and yaw angles
|
||||
Mat rotPitch(3, 1, CV_64FC1);
|
||||
rotPitch.ptr<double>(0)[0] = 0;
|
||||
rotPitch.ptr<double>(0)[1] = pitch;
|
||||
rotPitch.ptr<double>(0)[2] = 0;
|
||||
|
||||
Mat rotYaw(3, 1, CV_64FC1);
|
||||
rotYaw.ptr<double>(0)[0] = yaw;
|
||||
rotYaw.ptr<double>(0)[1] = 0;
|
||||
rotYaw.ptr<double>(0)[2] = 0;
|
||||
|
||||
composeRT(rotPitch, Mat(3, 1, CV_64FC1, Scalar::all(0)), rotYaw,
|
||||
Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, tvec);
|
||||
|
||||
// compose both rotations
|
||||
composeRT(camRvec, Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec,
|
||||
Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, tvec);
|
||||
|
||||
// Tvec, just move in z (camera) direction the specific distance
|
||||
tvec.ptr<double>(0)[0] = 0.;
|
||||
tvec.ptr<double>(0)[1] = 0.;
|
||||
tvec.ptr<double>(0)[2] = distance;
|
||||
return rvec_tvec;
|
||||
}
|
||||
|
||||
std::pair<Mat, vector<Point2f> > getProjectMarker(int id, double yaw, double pitch,
|
||||
const aruco::DetectorParameters& parameters,
|
||||
const aruco::Dictionary& dictionary) {
|
||||
auto marker_corners = std::make_pair(Mat(imgMarkerSize, imgMarkerSize, CV_8UC1, Scalar::all(255)), vector<Point2f>());
|
||||
Mat& img = marker_corners.first;
|
||||
vector<Point2f>& corners = marker_corners.second;
|
||||
|
||||
// canonical image
|
||||
const int markerSizePixels = static_cast<int>(imgMarkerSize/sqrt(2.f));
|
||||
aruco::generateImageMarker(dictionary, id, markerSizePixels, img, parameters.markerBorderBits);
|
||||
|
||||
// get rvec and tvec for the perspective
|
||||
const double distance = 0.1;
|
||||
auto rvec_tvec = MarkerPainter::getSyntheticRT(yaw, pitch, distance);
|
||||
Mat& rvec = rvec_tvec.first;
|
||||
Mat& tvec = rvec_tvec.second;
|
||||
|
||||
const float markerLength = 0.05f;
|
||||
vector<Point3f> markerObjPoints;
|
||||
markerObjPoints.emplace_back(Point3f(-markerLength / 2.f, +markerLength / 2.f, 0));
|
||||
markerObjPoints.emplace_back(markerObjPoints[0] + Point3f(markerLength, 0, 0));
|
||||
markerObjPoints.emplace_back(markerObjPoints[0] + Point3f(markerLength, -markerLength, 0));
|
||||
markerObjPoints.emplace_back(markerObjPoints[0] + Point3f(0, -markerLength, 0));
|
||||
|
||||
// project markers and draw them
|
||||
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
|
||||
projectPoints(markerObjPoints, rvec, tvec, cameraMatrix, distCoeffs, corners);
|
||||
|
||||
vector<Point2f> originalCorners;
|
||||
originalCorners.emplace_back(Point2f(0.f, 0.f));
|
||||
originalCorners.emplace_back(originalCorners[0]+Point2f((float)markerSizePixels, 0));
|
||||
originalCorners.emplace_back(originalCorners[0]+Point2f((float)markerSizePixels, (float)markerSizePixels));
|
||||
originalCorners.emplace_back(originalCorners[0]+Point2f(0, (float)markerSizePixels));
|
||||
|
||||
Mat transformation = getPerspectiveTransform(originalCorners, corners);
|
||||
|
||||
warpPerspective(img, img, transformation, Size(imgMarkerSize, imgMarkerSize), INTER_NEAREST, BORDER_CONSTANT,
|
||||
Scalar::all(255));
|
||||
return marker_corners;
|
||||
}
|
||||
|
||||
std::pair<Mat, map<int, vector<Point2f> > > getProjectMarkersTile(const int numMarkers,
|
||||
const aruco::DetectorParameters& params,
|
||||
const aruco::Dictionary& dictionary) {
|
||||
Mat tileImage(imgMarkerSize*numMarkers, imgMarkerSize*numMarkers, CV_8UC1, Scalar::all(255));
|
||||
map<int, vector<Point2f> > idCorners;
|
||||
|
||||
int iter = 0, pitch = 0, yaw = 0;
|
||||
for (int i = 0; i < numMarkers; i++) {
|
||||
for (int j = 0; j < numMarkers; j++) {
|
||||
int currentId = iter;
|
||||
auto marker_corners = getProjectMarker(currentId, deg2rad(70+yaw), deg2rad(pitch), params, dictionary);
|
||||
Point2i startPoint(j*imgMarkerSize, i*imgMarkerSize);
|
||||
Mat tmp_roi = tileImage(Rect(startPoint.x, startPoint.y, imgMarkerSize, imgMarkerSize));
|
||||
marker_corners.first.copyTo(tmp_roi);
|
||||
|
||||
for (Point2f& point: marker_corners.second)
|
||||
point += static_cast<Point2f>(startPoint);
|
||||
idCorners[currentId] = marker_corners.second;
|
||||
auto test = idCorners[currentId];
|
||||
yaw = (yaw + 10) % 51; // 70+yaw >= 70 && 70+yaw <= 120
|
||||
iter++;
|
||||
}
|
||||
pitch = (pitch + 60) % 360;
|
||||
}
|
||||
return std::make_pair(tileImage, idCorners);
|
||||
}
|
||||
};
|
||||
|
||||
static inline double getMaxDistance(map<int, vector<Point2f> > &golds, const vector<int>& ids,
|
||||
const vector<vector<Point2f> >& corners) {
|
||||
std::map<int, double> mapDist;
|
||||
for (const auto& el : golds)
|
||||
mapDist[el.first] = std::numeric_limits<double>::max();
|
||||
for (size_t i = 0; i < ids.size(); i++) {
|
||||
int id = ids[i];
|
||||
const auto gold_corners = golds.find(id);
|
||||
if (gold_corners != golds.end()) {
|
||||
double distance = 0.;
|
||||
for (int c = 0; c < 4; c++)
|
||||
distance = std::max(distance, cv::norm(gold_corners->second[c] - corners[i][c]));
|
||||
mapDist[id] = distance;
|
||||
}
|
||||
}
|
||||
return std::max_element(std::begin(mapDist), std::end(mapDist),
|
||||
[](const pair<int, double>& p1, const pair<int, double>& p2){return p1.second < p2.second;})->second;
|
||||
}
|
||||
|
||||
PERF_TEST_P(EstimateAruco, ArucoFirst, ESTIMATE_PARAMS) {
|
||||
UseArucoParams testParams = GetParam();
|
||||
aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
|
||||
aruco::DetectorParameters detectorParams;
|
||||
detectorParams.minDistanceToBorder = 1;
|
||||
detectorParams.markerBorderBits = 1;
|
||||
detectorParams.cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
|
||||
|
||||
const int markerSize = 100;
|
||||
const int numMarkersInRow = 9;
|
||||
//USE_ARUCO3
|
||||
detectorParams.useAruco3Detection = get<0>(testParams);
|
||||
if (detectorParams.useAruco3Detection) {
|
||||
detectorParams.minSideLengthCanonicalImg = 32;
|
||||
detectorParams.minMarkerLengthRatioOriginalImg = 0.04f / numMarkersInRow;
|
||||
}
|
||||
aruco::ArucoDetector detector(dictionary, detectorParams);
|
||||
MarkerPainter painter(markerSize);
|
||||
auto image_map = painter.getProjectMarkersTile(numMarkersInRow, detectorParams, dictionary);
|
||||
|
||||
// detect markers
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
TEST_CYCLE() {
|
||||
detector.detectMarkers(image_map.first, corners, ids);
|
||||
}
|
||||
ASSERT_EQ(numMarkersInRow*numMarkersInRow, static_cast<int>(ids.size()));
|
||||
double maxDistance = getMaxDistance(image_map.second, ids, corners);
|
||||
ASSERT_LT(maxDistance, 3.);
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
PERF_TEST_P(EstimateAruco, ArucoSecond, ESTIMATE_PARAMS) {
|
||||
UseArucoParams testParams = GetParam();
|
||||
aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
|
||||
aruco::DetectorParameters detectorParams;
|
||||
detectorParams.minDistanceToBorder = 1;
|
||||
detectorParams.markerBorderBits = 1;
|
||||
detectorParams.cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
|
||||
|
||||
//USE_ARUCO3
|
||||
detectorParams.useAruco3Detection = get<0>(testParams);
|
||||
if (detectorParams.useAruco3Detection) {
|
||||
detectorParams.minSideLengthCanonicalImg = 64;
|
||||
detectorParams.minMarkerLengthRatioOriginalImg = 0.f;
|
||||
}
|
||||
aruco::ArucoDetector detector(dictionary, detectorParams);
|
||||
const int markerSize = 200;
|
||||
const int numMarkersInRow = 11;
|
||||
MarkerPainter painter(markerSize);
|
||||
auto image_map = painter.getProjectMarkersTile(numMarkersInRow, detectorParams, dictionary);
|
||||
|
||||
// detect markers
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
TEST_CYCLE() {
|
||||
detector.detectMarkers(image_map.first, corners, ids);
|
||||
}
|
||||
ASSERT_EQ(numMarkersInRow*numMarkersInRow, static_cast<int>(ids.size()));
|
||||
double maxDistance = getMaxDistance(image_map.second, ids, corners);
|
||||
ASSERT_LT(maxDistance, 3.);
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
struct Aruco3Params {
|
||||
bool useAruco3Detection = false;
|
||||
float minMarkerLengthRatioOriginalImg = 0.f;
|
||||
int minSideLengthCanonicalImg = 0;
|
||||
|
||||
Aruco3Params(bool useAruco3, float minMarkerLen, int minSideLen): useAruco3Detection(useAruco3),
|
||||
minMarkerLengthRatioOriginalImg(minMarkerLen),
|
||||
minSideLengthCanonicalImg(minSideLen) {}
|
||||
friend std::ostream& operator<<(std::ostream& os, const Aruco3Params& d) {
|
||||
os << d.useAruco3Detection << " " << d.minMarkerLengthRatioOriginalImg << " " << d.minSideLengthCanonicalImg;
|
||||
return os;
|
||||
}
|
||||
};
|
||||
typedef tuple<Aruco3Params, pair<int, int>> ArucoTestParams;
|
||||
|
||||
typedef TestBaseWithParam<ArucoTestParams> EstimateLargeAruco;
|
||||
#define ESTIMATE_FHD_PARAMS Combine(Values(Aruco3Params(false, 0.f, 0), Aruco3Params(true, 0.f, 32), \
|
||||
Aruco3Params(true, 0.015f, 32), Aruco3Params(true, 0.f, 16), Aruco3Params(true, 0.0069f, 16)), \
|
||||
Values(std::make_pair(1440, 1), std::make_pair(480, 3), std::make_pair(144, 10)))
|
||||
|
||||
PERF_TEST_P(EstimateLargeAruco, ArucoFHD, ESTIMATE_FHD_PARAMS) {
|
||||
ArucoTestParams testParams = GetParam();
|
||||
aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
|
||||
aruco::DetectorParameters detectorParams;
|
||||
detectorParams.minDistanceToBorder = 1;
|
||||
detectorParams.markerBorderBits = 1;
|
||||
detectorParams.cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
|
||||
|
||||
//USE_ARUCO3
|
||||
detectorParams.useAruco3Detection = get<0>(testParams).useAruco3Detection;
|
||||
if (detectorParams.useAruco3Detection) {
|
||||
detectorParams.minSideLengthCanonicalImg = get<0>(testParams).minSideLengthCanonicalImg;
|
||||
detectorParams.minMarkerLengthRatioOriginalImg = get<0>(testParams).minMarkerLengthRatioOriginalImg;
|
||||
}
|
||||
aruco::ArucoDetector detector(dictionary, detectorParams);
|
||||
const int markerSize = get<1>(testParams).first; // 1440 or 480 or 144
|
||||
const int numMarkersInRow = get<1>(testParams).second; // 1 or 3 or 144
|
||||
MarkerPainter painter(markerSize); // num pixels is 1440x1440 as in FHD 1920x1080
|
||||
auto image_map = painter.getProjectMarkersTile(numMarkersInRow, detectorParams, dictionary);
|
||||
|
||||
// detect markers
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
TEST_CYCLE()
|
||||
{
|
||||
detector.detectMarkers(image_map.first, corners, ids);
|
||||
}
|
||||
ASSERT_EQ(numMarkersInRow*numMarkersInRow, static_cast<int>(ids.size()));
|
||||
double maxDistance = getMaxDistance(image_map.second, ids, corners);
|
||||
ASSERT_LT(maxDistance, 3.);
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -55,6 +55,10 @@ PERF_TEST_P_(Perf_Objdetect_QRCode, decode)
|
||||
|
||||
typedef ::perf::TestBaseWithParam< std::string > Perf_Objdetect_QRCode_Multi;
|
||||
|
||||
static inline bool compareCorners(const Point2f& corner1, const Point2f& corner2) {
|
||||
return corner1.x == corner2.x ? corner1.y < corner2.y : corner1.x < corner2.x;
|
||||
}
|
||||
|
||||
PERF_TEST_P_(Perf_Objdetect_QRCode_Multi, detectMulti)
|
||||
{
|
||||
const std::string name_current_image = GetParam();
|
||||
@@ -66,15 +70,19 @@ PERF_TEST_P_(Perf_Objdetect_QRCode_Multi, detectMulti)
|
||||
std::vector<Point2f> corners;
|
||||
QRCodeDetector qrcode;
|
||||
TEST_CYCLE() ASSERT_TRUE(qrcode.detectMulti(src, corners));
|
||||
sort(corners.begin(), corners.end(), compareCorners);
|
||||
SANITY_CHECK(corners);
|
||||
}
|
||||
|
||||
static inline bool compareQR(const pair<string, Mat>& v1, const pair<string, Mat>& v2) {
|
||||
return v1.first < v2.first;
|
||||
}
|
||||
|
||||
#ifdef HAVE_QUIRC
|
||||
PERF_TEST_P_(Perf_Objdetect_QRCode_Multi, decodeMulti)
|
||||
{
|
||||
const std::string name_current_image = GetParam();
|
||||
const std::string root = "cv/qrcode/multiple/";
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat src = imread(image_path);
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
@@ -91,15 +99,21 @@ PERF_TEST_P_(Perf_Objdetect_QRCode_Multi, decodeMulti)
|
||||
ASSERT_FALSE(decoded_info[i].empty());
|
||||
}
|
||||
}
|
||||
std::vector < std::vector< uint8_t > > decoded_info_uint8_t;
|
||||
for(size_t i = 0; i < decoded_info.size(); i++)
|
||||
{
|
||||
std::vector< uint8_t > tmp(decoded_info[i].begin(), decoded_info[i].end());
|
||||
decoded_info_uint8_t.push_back(tmp);
|
||||
ASSERT_EQ(decoded_info.size(), straight_barcode.size());
|
||||
vector<pair<string, Mat> > result;
|
||||
for (size_t i = 0ull; i < decoded_info.size(); i++) {
|
||||
result.push_back(make_pair(decoded_info[i], straight_barcode[i]));
|
||||
}
|
||||
SANITY_CHECK(decoded_info_uint8_t);
|
||||
SANITY_CHECK(straight_barcode);
|
||||
|
||||
sort(result.begin(), result.end(), compareQR);
|
||||
vector<vector<uint8_t> > decoded_info_sort;
|
||||
vector<Mat> straight_barcode_sort;
|
||||
for (size_t i = 0ull; i < result.size(); i++) {
|
||||
vector<uint8_t> tmp(result[i].first.begin(), result[i].first.end());
|
||||
decoded_info_sort.push_back(tmp);
|
||||
straight_barcode_sort.push_back(result[i].second);
|
||||
}
|
||||
SANITY_CHECK(decoded_info_sort);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,117 @@
|
||||
// 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.
|
||||
//
|
||||
// Copyright (C) 2013-2016, The Regents of The University of Michigan.
|
||||
//
|
||||
// This software was developed in the APRIL Robotics Lab under the
|
||||
// direction of Edwin Olson, ebolson@umich.edu. This software may be
|
||||
// available under alternative licensing terms; contact the address above.
|
||||
//
|
||||
// The views and conclusions contained in the software and documentation are those
|
||||
// of the authors and should not be interpreted as representing official policies,
|
||||
// either expressed or implied, of the Regents of The University of Michigan.
|
||||
|
||||
// limitation: image size must be <32768 in width and height. This is
|
||||
// because we use a fixed-point 16 bit integer representation with one
|
||||
// fractional bit.
|
||||
|
||||
#ifndef _OPENCV_APRIL_QUAD_THRESH_HPP_
|
||||
#define _OPENCV_APRIL_QUAD_THRESH_HPP_
|
||||
|
||||
#include "unionfind.hpp"
|
||||
#include "zmaxheap.hpp"
|
||||
#include "zarray.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace aruco {
|
||||
|
||||
static inline uint32_t u64hash_2(uint64_t x) {
|
||||
return uint32_t((2654435761UL * x) >> 32);
|
||||
}
|
||||
|
||||
struct uint64_zarray_entry{
|
||||
uint64_t id;
|
||||
zarray_t *cluster;
|
||||
|
||||
struct uint64_zarray_entry *next;
|
||||
};
|
||||
|
||||
struct pt{
|
||||
// Note: these represent 2*actual value.
|
||||
uint16_t x, y;
|
||||
float theta;
|
||||
int16_t gx, gy;
|
||||
};
|
||||
|
||||
struct remove_vertex{
|
||||
int i; // which vertex to remove?
|
||||
int left, right; // left vertex, right vertex
|
||||
|
||||
double err;
|
||||
};
|
||||
|
||||
struct segment{
|
||||
int is_vertex;
|
||||
|
||||
// always greater than zero, but right can be > size, which denotes
|
||||
// a wrap around back to the beginning of the points. and left < right.
|
||||
int left, right;
|
||||
};
|
||||
|
||||
struct line_fit_pt{
|
||||
double Mx, My;
|
||||
double Mxx, Myy, Mxy;
|
||||
double W; // total weight
|
||||
};
|
||||
|
||||
/**
|
||||
* lfps contains *cumulative* moments for N points, with
|
||||
* index j reflecting points [0,j] (inclusive).
|
||||
* fit a line to the points [i0, i1] (inclusive). i0, i1 are both (0, sz)
|
||||
* if i1 < i0, we treat this as a wrap around.
|
||||
*/
|
||||
void fit_line(struct line_fit_pt *lfps, int sz, int i0, int i1, double *lineparm, double *err, double *mse);
|
||||
|
||||
int err_compare_descending(const void *_a, const void *_b);
|
||||
|
||||
/**
|
||||
1. Identify A) white points near a black point and B) black points near a white point.
|
||||
|
||||
2. Find the connected components within each of the classes above,
|
||||
yielding clusters of "white-near-black" and
|
||||
"black-near-white". (These two classes are kept separate). Each
|
||||
segment has a unique id.
|
||||
|
||||
3. For every pair of "white-near-black" and "black-near-white"
|
||||
clusters, find the set of points that are in one and adjacent to the
|
||||
other. In other words, a "boundary" layer between the two
|
||||
clusters. (This is actually performed by iterating over the pixels,
|
||||
rather than pairs of clusters.) Critically, this helps keep nearby
|
||||
edges from becoming connected.
|
||||
**/
|
||||
int quad_segment_maxima(const DetectorParameters &td, int sz, struct line_fit_pt *lfps, int indices[4]);
|
||||
|
||||
/**
|
||||
* returns 0 if the cluster looks bad.
|
||||
*/
|
||||
int quad_segment_agg(int sz, struct line_fit_pt *lfps, int indices[4]);
|
||||
|
||||
/**
|
||||
* return 1 if the quad looks okay, 0 if it should be discarded
|
||||
* quad
|
||||
**/
|
||||
int fit_quad(const DetectorParameters &_params, const Mat im, zarray_t *cluster, struct sQuad *quad);
|
||||
|
||||
|
||||
void threshold(const Mat mIm, const DetectorParameters ¶meters, Mat& mThresh);
|
||||
|
||||
|
||||
zarray_t *apriltag_quad_thresh(const DetectorParameters ¶meters, const Mat & mImg,
|
||||
std::vector<std::vector<Point> > &contours);
|
||||
|
||||
void _apriltag(Mat im_orig, const DetectorParameters &_params, std::vector<std::vector<Point2f> > &candidates,
|
||||
std::vector<std::vector<Point> > &contours);
|
||||
|
||||
}}
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,131 @@
|
||||
// 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.
|
||||
//
|
||||
// Copyright (C) 2013-2016, The Regents of The University of Michigan.
|
||||
//
|
||||
// This software was developed in the APRIL Robotics Lab under the
|
||||
// direction of Edwin Olson, ebolson@umich.edu. This software may be
|
||||
// available under alternative licensing terms; contact the address above.
|
||||
//
|
||||
// The views and conclusions contained in the software and documentation are those
|
||||
// of the authors and should not be interpreted as representing official policies,
|
||||
// either expressed or implied, of the Regents of The University of Michigan.
|
||||
#ifndef _OPENCV_UNIONFIND_HPP_
|
||||
#define _OPENCV_UNIONFIND_HPP_
|
||||
|
||||
namespace cv {
|
||||
namespace aruco {
|
||||
|
||||
typedef struct unionfind unionfind_t;
|
||||
struct unionfind{
|
||||
uint32_t maxid;
|
||||
struct ufrec *data;
|
||||
};
|
||||
|
||||
struct ufrec{
|
||||
// the parent of this node. If a node's parent is its own index,
|
||||
// then it is a root.
|
||||
uint32_t parent;
|
||||
|
||||
// for the root of a connected component, the number of components
|
||||
// connected to it. For intermediate values, it's not meaningful.
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
static inline unionfind_t *unionfind_create(uint32_t maxid){
|
||||
unionfind_t *uf = (unionfind_t*) calloc(1, sizeof(unionfind_t));
|
||||
uf->maxid = maxid;
|
||||
uf->data = (struct ufrec*) malloc((maxid+1) * sizeof(struct ufrec));
|
||||
for (unsigned int i = 0; i <= maxid; i++) {
|
||||
uf->data[i].size = 1;
|
||||
uf->data[i].parent = i;
|
||||
}
|
||||
return uf;
|
||||
}
|
||||
|
||||
static inline void unionfind_destroy(unionfind_t *uf){
|
||||
free(uf->data);
|
||||
free(uf);
|
||||
}
|
||||
|
||||
/*
|
||||
static inline uint32_t unionfind_get_representative(unionfind_t *uf, uint32_t id)
|
||||
{
|
||||
// base case: a node is its own parent
|
||||
if (uf->data[id].parent == id)
|
||||
return id;
|
||||
|
||||
// otherwise, recurse
|
||||
uint32_t root = unionfind_get_representative(uf, uf->data[id].parent);
|
||||
|
||||
// short circuit the path. [XXX This write prevents tail recursion]
|
||||
uf->data[id].parent = root;
|
||||
|
||||
return root;
|
||||
}
|
||||
*/
|
||||
|
||||
// this one seems to be every-so-slightly faster than the recursive
|
||||
// version above.
|
||||
static inline uint32_t unionfind_get_representative(unionfind_t *uf, uint32_t id){
|
||||
uint32_t root = id;
|
||||
|
||||
// chase down the root
|
||||
while (uf->data[root].parent != root) {
|
||||
root = uf->data[root].parent;
|
||||
}
|
||||
|
||||
// go back and collapse the tree.
|
||||
//
|
||||
// XXX: on some of our workloads that have very shallow trees
|
||||
// (e.g. image segmentation), we are actually faster not doing
|
||||
// this...
|
||||
while (uf->data[id].parent != root) {
|
||||
uint32_t tmp = uf->data[id].parent;
|
||||
uf->data[id].parent = root;
|
||||
id = tmp;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
static inline uint32_t unionfind_get_set_size(unionfind_t *uf, uint32_t id){
|
||||
uint32_t repid = unionfind_get_representative(uf, id);
|
||||
return uf->data[repid].size;
|
||||
}
|
||||
|
||||
static inline uint32_t unionfind_connect(unionfind_t *uf, uint32_t aid, uint32_t bid){
|
||||
uint32_t aroot = unionfind_get_representative(uf, aid);
|
||||
uint32_t broot = unionfind_get_representative(uf, bid);
|
||||
|
||||
if (aroot == broot)
|
||||
return aroot;
|
||||
|
||||
// we don't perform "union by rank", but we perform a similar
|
||||
// operation (but probably without the same asymptotic guarantee):
|
||||
// We join trees based on the number of *elements* (as opposed to
|
||||
// rank) contained within each tree. I.e., we use size as a proxy
|
||||
// for rank. In my testing, it's often *faster* to use size than
|
||||
// rank, perhaps because the rank of the tree isn't that critical
|
||||
// if there are very few nodes in it.
|
||||
uint32_t asize = uf->data[aroot].size;
|
||||
uint32_t bsize = uf->data[broot].size;
|
||||
|
||||
// optimization idea: We could shortcut some or all of the tree
|
||||
// that is grafted onto the other tree. Pro: those nodes were just
|
||||
// read and so are probably in cache. Con: it might end up being
|
||||
// wasted effort -- the tree might be grafted onto another tree in
|
||||
// a moment!
|
||||
if (asize > bsize) {
|
||||
uf->data[broot].parent = aroot;
|
||||
uf->data[aroot].size += bsize;
|
||||
return aroot;
|
||||
} else {
|
||||
uf->data[aroot].parent = broot;
|
||||
uf->data[broot].size += asize;
|
||||
return broot;
|
||||
}
|
||||
}
|
||||
}}
|
||||
#endif
|
||||
@@ -0,0 +1,148 @@
|
||||
// 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.
|
||||
//
|
||||
// Copyright (C) 2013-2016, The Regents of The University of Michigan.
|
||||
//
|
||||
// This software was developed in the APRIL Robotics Lab under the
|
||||
// direction of Edwin Olson, ebolson@umich.edu. This software may be
|
||||
// available under alternative licensing terms; contact the address above.
|
||||
//
|
||||
// The views and conclusions contained in the software and documentation are those
|
||||
// of the authors and should not be interpreted as representing official policies,
|
||||
// either expressed or implied, of the Regents of The University of Michigan.
|
||||
#ifndef _OPENCV_ZARRAY_HPP_
|
||||
#define _OPENCV_ZARRAY_HPP_
|
||||
|
||||
|
||||
namespace cv {
|
||||
namespace aruco {
|
||||
|
||||
|
||||
struct sQuad{
|
||||
float p[4][2]; // corners
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a structure which acts as a resize-able array ala Java's ArrayList.
|
||||
*/
|
||||
typedef struct zarray zarray_t;
|
||||
struct zarray{
|
||||
size_t el_sz; // size of each element
|
||||
|
||||
int size; // how many elements?
|
||||
int alloc; // we've allocated storage for how many elements?
|
||||
char *data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates and returns a variable array structure capable of holding elements of
|
||||
* the specified size. It is the caller's responsibility to call zarray_destroy()
|
||||
* on the returned array when it is no longer needed.
|
||||
*/
|
||||
inline static zarray_t *_zarray_create(size_t el_sz){
|
||||
zarray_t *za = (zarray_t*) calloc(1, sizeof(zarray_t));
|
||||
za->el_sz = el_sz;
|
||||
return za;
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees all resources associated with the variable array structure which was
|
||||
* created by zarray_create(). After calling, 'za' will no longer be valid for storage.
|
||||
*/
|
||||
inline static void _zarray_destroy(zarray_t *za){
|
||||
if (za == NULL)
|
||||
return;
|
||||
|
||||
if (za->data != NULL)
|
||||
free(za->data);
|
||||
memset(za, 0, sizeof(zarray_t));
|
||||
free(za);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the number of elements currently being contained by the passed
|
||||
* array, which may be different from its capacity. The index of the last element
|
||||
* in the array will be one less than the returned value.
|
||||
*/
|
||||
inline static int _zarray_size(const zarray_t *za){
|
||||
return za->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates enough internal storage in the supplied variable array structure to
|
||||
* guarantee that the supplied number of elements (capacity) can be safely stored.
|
||||
*/
|
||||
inline static void _zarray_ensure_capacity(zarray_t *za, int capacity){
|
||||
if (capacity <= za->alloc)
|
||||
return;
|
||||
|
||||
while (za->alloc < capacity) {
|
||||
za->alloc *= 2;
|
||||
if (za->alloc < 8)
|
||||
za->alloc = 8;
|
||||
}
|
||||
|
||||
za->data = (char*) realloc(za->data, za->alloc * za->el_sz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new element to the end of the supplied array, and sets its value
|
||||
* (by copying) from the data pointed to by the supplied pointer 'p'.
|
||||
* Automatically ensures that enough storage space is available for the new element.
|
||||
*/
|
||||
inline static void _zarray_add(zarray_t *za, const void *p){
|
||||
_zarray_ensure_capacity(za, za->size + 1);
|
||||
|
||||
memcpy(&za->data[za->size*za->el_sz], p, za->el_sz);
|
||||
za->size++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the element from the supplied array located at the zero-based
|
||||
* index of 'idx' and copies its value into the variable pointed to by the pointer
|
||||
* 'p'.
|
||||
*/
|
||||
inline static void _zarray_get(const zarray_t *za, int idx, void *p){
|
||||
CV_DbgAssert(idx >= 0);
|
||||
CV_DbgAssert(idx < za->size);
|
||||
|
||||
memcpy(p, &za->data[idx*za->el_sz], za->el_sz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to zarray_get(), but returns a "live" pointer to the internal
|
||||
* storage, avoiding a memcpy. This pointer is not valid across
|
||||
* operations which might move memory around (i.e. zarray_remove_value(),
|
||||
* zarray_remove_index(), zarray_insert(), zarray_sort(), zarray_clear()).
|
||||
* 'p' should be a pointer to the pointer which will be set to the internal address.
|
||||
*/
|
||||
inline static void _zarray_get_volatile(const zarray_t *za, int idx, void *p){
|
||||
CV_DbgAssert(idx >= 0);
|
||||
CV_DbgAssert(idx < za->size);
|
||||
|
||||
*((void**) p) = &za->data[idx*za->el_sz];
|
||||
}
|
||||
|
||||
inline static void _zarray_truncate(zarray_t *za, int sz){
|
||||
za->size = sz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the current element at index 'idx' by copying its value from
|
||||
* the data pointed to by 'p'. The previous value of the changed element will be
|
||||
* copied into the data pointed to by 'outp' if it is not null.
|
||||
*/
|
||||
static inline void _zarray_set(zarray_t *za, int idx, const void *p, void *outp){
|
||||
CV_DbgAssert(idx >= 0);
|
||||
CV_DbgAssert(idx < za->size);
|
||||
|
||||
if (outp != NULL)
|
||||
memcpy(outp, &za->data[idx*za->el_sz], za->el_sz);
|
||||
|
||||
memcpy(&za->data[idx*za->el_sz], p, za->el_sz);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,207 @@
|
||||
// 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.
|
||||
//
|
||||
// Copyright (C) 2013-2016, The Regents of The University of Michigan.
|
||||
//
|
||||
// This software was developed in the APRIL Robotics Lab under the
|
||||
// direction of Edwin Olson, ebolson@umich.edu. This software may be
|
||||
// available under alternative licensing terms; contact the address above.
|
||||
//
|
||||
// The views and conclusions contained in the software and documentation are those
|
||||
// of the authors and should not be interpreted as representing official policies,
|
||||
// either expressed or implied, of the Regents of The University of Michigan.
|
||||
|
||||
#include "../../precomp.hpp"
|
||||
#include "zmaxheap.hpp"
|
||||
|
||||
|
||||
// 0
|
||||
// 1 2
|
||||
// 3 4 5 6
|
||||
// 7 8 9 10 11 12 13 14
|
||||
//
|
||||
// Children of node i: 2*i+1, 2*i+2
|
||||
// Parent of node i: (i-1) / 2
|
||||
//
|
||||
// Heap property: a parent is greater than (or equal to) its children.
|
||||
|
||||
#define MIN_CAPACITY 16
|
||||
namespace cv {
|
||||
namespace aruco {
|
||||
struct zmaxheap
|
||||
{
|
||||
size_t el_sz;
|
||||
|
||||
int size;
|
||||
int alloc;
|
||||
|
||||
float *values;
|
||||
char *data;
|
||||
|
||||
void (*swap)(zmaxheap_t *heap, int a, int b);
|
||||
};
|
||||
|
||||
static inline void _swap_default(zmaxheap_t *heap, int a, int b)
|
||||
{
|
||||
float t = heap->values[a];
|
||||
heap->values[a] = heap->values[b];
|
||||
heap->values[b] = t;
|
||||
|
||||
cv::AutoBuffer<char> tmp(heap->el_sz);
|
||||
memcpy(tmp.data(), &heap->data[a*heap->el_sz], heap->el_sz);
|
||||
memcpy(&heap->data[a*heap->el_sz], &heap->data[b*heap->el_sz], heap->el_sz);
|
||||
memcpy(&heap->data[b*heap->el_sz], tmp.data(), heap->el_sz);
|
||||
}
|
||||
|
||||
static inline void _swap_pointer(zmaxheap_t *heap, int a, int b)
|
||||
{
|
||||
float t = heap->values[a];
|
||||
heap->values[a] = heap->values[b];
|
||||
heap->values[b] = t;
|
||||
|
||||
void **pp = (void**) heap->data;
|
||||
void *tmp = pp[a];
|
||||
pp[a] = pp[b];
|
||||
pp[b] = tmp;
|
||||
}
|
||||
|
||||
|
||||
zmaxheap_t *zmaxheap_create(size_t el_sz)
|
||||
{
|
||||
zmaxheap_t *heap = (zmaxheap_t*)calloc(1, sizeof(zmaxheap_t));
|
||||
heap->el_sz = el_sz;
|
||||
|
||||
heap->swap = _swap_default;
|
||||
|
||||
if (el_sz == sizeof(void*))
|
||||
heap->swap = _swap_pointer;
|
||||
|
||||
return heap;
|
||||
}
|
||||
|
||||
void zmaxheap_destroy(zmaxheap_t *heap)
|
||||
{
|
||||
free(heap->values);
|
||||
free(heap->data);
|
||||
memset(heap, 0, sizeof(zmaxheap_t));
|
||||
free(heap);
|
||||
}
|
||||
|
||||
static void _zmaxheap_ensure_capacity(zmaxheap_t *heap, int capacity)
|
||||
{
|
||||
if (heap->alloc >= capacity)
|
||||
return;
|
||||
|
||||
int newcap = heap->alloc;
|
||||
|
||||
while (newcap < capacity) {
|
||||
if (newcap < MIN_CAPACITY) {
|
||||
newcap = MIN_CAPACITY;
|
||||
continue;
|
||||
}
|
||||
|
||||
newcap *= 2;
|
||||
}
|
||||
|
||||
heap->values = (float*)realloc(heap->values, newcap * sizeof(float));
|
||||
heap->data = (char*)realloc(heap->data, newcap * heap->el_sz);
|
||||
heap->alloc = newcap;
|
||||
}
|
||||
|
||||
void zmaxheap_add(zmaxheap_t *heap, void *p, float v)
|
||||
{
|
||||
_zmaxheap_ensure_capacity(heap, heap->size + 1);
|
||||
|
||||
int idx = heap->size;
|
||||
|
||||
heap->values[idx] = v;
|
||||
memcpy(&heap->data[idx*heap->el_sz], p, heap->el_sz);
|
||||
|
||||
heap->size++;
|
||||
|
||||
while (idx > 0) {
|
||||
|
||||
int parent = (idx - 1) / 2;
|
||||
|
||||
// we're done!
|
||||
if (heap->values[parent] >= v)
|
||||
break;
|
||||
|
||||
// else, swap and recurse upwards.
|
||||
heap->swap(heap, idx, parent);
|
||||
idx = parent;
|
||||
}
|
||||
}
|
||||
|
||||
// Removes the item in the heap at the given index. Returns 1 if the
|
||||
// item existed. 0 Indicates an invalid idx (heap is smaller than
|
||||
// idx). This is mostly intended to be used by zmaxheap_remove_max.
|
||||
static int zmaxheap_remove_index(zmaxheap_t *heap, int idx, void *p, float *v)
|
||||
{
|
||||
if (idx >= heap->size)
|
||||
return 0;
|
||||
|
||||
// copy out the requested element from the heap.
|
||||
if (v != NULL)
|
||||
*v = heap->values[idx];
|
||||
if (p != NULL)
|
||||
memcpy(p, &heap->data[idx*heap->el_sz], heap->el_sz);
|
||||
|
||||
heap->size--;
|
||||
|
||||
// If this element is already the last one, then there's nothing
|
||||
// for us to do.
|
||||
if (idx == heap->size)
|
||||
return 1;
|
||||
|
||||
// copy last element to first element. (which probably upsets
|
||||
// the heap property).
|
||||
heap->values[idx] = heap->values[heap->size];
|
||||
memcpy(&heap->data[idx*heap->el_sz], &heap->data[heap->el_sz * heap->size], heap->el_sz);
|
||||
|
||||
// now fix the heap. Note, as we descend, we're "pushing down"
|
||||
// the same node the entire time. Thus, while the index of the
|
||||
// parent might change, the parent_score doesn't.
|
||||
int parent = idx;
|
||||
float parent_score = heap->values[idx];
|
||||
|
||||
// descend, fixing the heap.
|
||||
while (parent < heap->size) {
|
||||
|
||||
int left = 2*parent + 1;
|
||||
int right = left + 1;
|
||||
|
||||
// assert(parent_score == heap->values[parent]);
|
||||
|
||||
float left_score = (left < heap->size) ? heap->values[left] : -INFINITY;
|
||||
float right_score = (right < heap->size) ? heap->values[right] : -INFINITY;
|
||||
|
||||
// put the biggest of (parent, left, right) as the parent.
|
||||
|
||||
// already okay?
|
||||
if (parent_score >= left_score && parent_score >= right_score)
|
||||
break;
|
||||
|
||||
// if we got here, then one of the children is bigger than the parent.
|
||||
if (left_score >= right_score) {
|
||||
CV_Assert(left < heap->size);
|
||||
heap->swap(heap, parent, left);
|
||||
parent = left;
|
||||
} else {
|
||||
// right_score can't be less than left_score if right_score is -INFINITY.
|
||||
CV_Assert(right < heap->size);
|
||||
heap->swap(heap, parent, right);
|
||||
parent = right;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int zmaxheap_remove_max(zmaxheap_t *heap, void *p, float *v)
|
||||
{
|
||||
return zmaxheap_remove_index(heap, 0, p, v);
|
||||
}
|
||||
|
||||
}}
|
||||
@@ -0,0 +1,38 @@
|
||||
// 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.
|
||||
//
|
||||
// Copyright (C) 2013-2016, The Regents of The University of Michigan.
|
||||
//
|
||||
// This software was developed in the APRIL Robotics Lab under the
|
||||
// direction of Edwin Olson, ebolson@umich.edu. This software may be
|
||||
// available under alternative licensing terms; contact the address above.
|
||||
//
|
||||
// The views and conclusions contained in the software and documentation are those
|
||||
// of the authors and should not be interpreted as representing official policies,
|
||||
// either expressed or implied, of the Regents of The University of Michigan.
|
||||
#ifndef _OPENCV_ZMAXHEAP_HPP_
|
||||
#define _OPENCV_ZMAXHEAP_HPP_
|
||||
|
||||
namespace cv {
|
||||
namespace aruco {
|
||||
typedef struct zmaxheap zmaxheap_t;
|
||||
|
||||
typedef struct zmaxheap_iterator zmaxheap_iterator_t;
|
||||
struct zmaxheap_iterator {
|
||||
zmaxheap_t *heap;
|
||||
int in, out;
|
||||
};
|
||||
|
||||
zmaxheap_t *zmaxheap_create(size_t el_sz);
|
||||
|
||||
void zmaxheap_destroy(zmaxheap_t *heap);
|
||||
|
||||
void zmaxheap_add(zmaxheap_t *heap, void *p, float v);
|
||||
|
||||
// returns 0 if the heap is empty, so you can do
|
||||
// while (zmaxheap_remove_max(...)) { }
|
||||
int zmaxheap_remove_max(zmaxheap_t *heap, void *p, float *v);
|
||||
|
||||
}}
|
||||
#endif
|
||||
@@ -0,0 +1,579 @@
|
||||
// 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
|
||||
|
||||
#include "../precomp.hpp"
|
||||
#include "opencv2/objdetect/aruco_board.hpp"
|
||||
|
||||
#include <opencv2/objdetect/aruco_dictionary.hpp>
|
||||
#include <numeric>
|
||||
|
||||
namespace cv {
|
||||
namespace aruco {
|
||||
using namespace std;
|
||||
|
||||
struct Board::Impl {
|
||||
Dictionary dictionary;
|
||||
std::vector<int> ids;
|
||||
std::vector<std::vector<Point3f> > objPoints;
|
||||
Point3f rightBottomBorder;
|
||||
|
||||
explicit Impl(const Dictionary& _dictionary):
|
||||
dictionary(_dictionary)
|
||||
{}
|
||||
|
||||
virtual ~Impl() {}
|
||||
|
||||
Impl(const Impl&) = delete;
|
||||
Impl& operator=(const Impl&) = delete;
|
||||
|
||||
virtual void matchImagePoints(InputArray detectedCorners, InputArray detectedIds, OutputArray _objPoints,
|
||||
OutputArray imgPoints) const;
|
||||
|
||||
virtual void generateImage(Size outSize, OutputArray img, int marginSize, int borderBits) const;
|
||||
};
|
||||
|
||||
void Board::Impl::matchImagePoints(InputArray detectedCorners, InputArray detectedIds, OutputArray _objPoints,
|
||||
OutputArray imgPoints) const {
|
||||
|
||||
CV_Assert(ids.size() == objPoints.size());
|
||||
CV_Assert(detectedIds.total() == detectedCorners.total());
|
||||
|
||||
size_t nDetectedMarkers = detectedIds.total();
|
||||
|
||||
vector<Point3f> objPnts;
|
||||
objPnts.reserve(nDetectedMarkers);
|
||||
|
||||
vector<Point2f> imgPnts;
|
||||
imgPnts.reserve(nDetectedMarkers);
|
||||
|
||||
// look for detected markers that belong to the board and get their information
|
||||
for(unsigned int i = 0; i < nDetectedMarkers; i++) {
|
||||
int currentId = detectedIds.getMat().ptr< int >(0)[i];
|
||||
for(unsigned int j = 0; j < ids.size(); j++) {
|
||||
if(currentId == ids[j]) {
|
||||
for(int p = 0; p < 4; p++) {
|
||||
objPnts.push_back(objPoints[j][p]);
|
||||
imgPnts.push_back(detectedCorners.getMat(i).ptr<Point2f>(0)[p]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create output
|
||||
Mat(objPnts).copyTo(_objPoints);
|
||||
Mat(imgPnts).copyTo(imgPoints);
|
||||
}
|
||||
|
||||
void Board::Impl::generateImage(Size outSize, OutputArray img, int marginSize, int borderBits) const {
|
||||
CV_Assert(!outSize.empty());
|
||||
CV_Assert(marginSize >= 0);
|
||||
|
||||
img.create(outSize, CV_8UC1);
|
||||
Mat out = img.getMat();
|
||||
out.setTo(Scalar::all(255));
|
||||
out.adjustROI(-marginSize, -marginSize, -marginSize, -marginSize);
|
||||
|
||||
// calculate max and min values in XY plane
|
||||
CV_Assert(objPoints.size() > 0);
|
||||
float minX, maxX, minY, maxY;
|
||||
minX = maxX = objPoints[0][0].x;
|
||||
minY = maxY = objPoints[0][0].y;
|
||||
|
||||
for(unsigned int i = 0; i < objPoints.size(); i++) {
|
||||
for(int j = 0; j < 4; j++) {
|
||||
minX = min(minX, objPoints[i][j].x);
|
||||
maxX = max(maxX, objPoints[i][j].x);
|
||||
minY = min(minY, objPoints[i][j].y);
|
||||
maxY = max(maxY, objPoints[i][j].y);
|
||||
}
|
||||
}
|
||||
|
||||
float sizeX = maxX - minX;
|
||||
float sizeY = maxY - minY;
|
||||
|
||||
// proportion transformations
|
||||
float xReduction = sizeX / float(out.cols);
|
||||
float yReduction = sizeY / float(out.rows);
|
||||
|
||||
// determine the zone where the markers are placed
|
||||
if(xReduction > yReduction) {
|
||||
int nRows = int(sizeY / xReduction);
|
||||
int rowsMargins = (out.rows - nRows) / 2;
|
||||
out.adjustROI(-rowsMargins, -rowsMargins, 0, 0);
|
||||
} else {
|
||||
int nCols = int(sizeX / yReduction);
|
||||
int colsMargins = (out.cols - nCols) / 2;
|
||||
out.adjustROI(0, 0, -colsMargins, -colsMargins);
|
||||
}
|
||||
|
||||
// now paint each marker
|
||||
Mat marker;
|
||||
Point2f outCorners[3];
|
||||
Point2f inCorners[3];
|
||||
for(unsigned int m = 0; m < objPoints.size(); m++) {
|
||||
// transform corners to markerZone coordinates
|
||||
for(int j = 0; j < 3; j++) {
|
||||
Point2f pf = Point2f(objPoints[m][j].x, objPoints[m][j].y);
|
||||
// move top left to 0, 0
|
||||
pf -= Point2f(minX, minY);
|
||||
pf.x = pf.x / sizeX * float(out.cols);
|
||||
pf.y = pf.y / sizeY * float(out.rows);
|
||||
outCorners[j] = pf;
|
||||
}
|
||||
|
||||
// get marker
|
||||
Size dst_sz(outCorners[2] - outCorners[0]); // assuming CCW order
|
||||
dst_sz.width = dst_sz.height = std::min(dst_sz.width, dst_sz.height); //marker should be square
|
||||
dictionary.generateImageMarker(ids[m], dst_sz.width, marker, borderBits);
|
||||
|
||||
if((outCorners[0].y == outCorners[1].y) && (outCorners[1].x == outCorners[2].x)) {
|
||||
// marker is aligned to image axes
|
||||
marker.copyTo(out(Rect(outCorners[0], dst_sz)));
|
||||
continue;
|
||||
}
|
||||
|
||||
// interpolate tiny marker to marker position in markerZone
|
||||
inCorners[0] = Point2f(-0.5f, -0.5f);
|
||||
inCorners[1] = Point2f(marker.cols - 0.5f, -0.5f);
|
||||
inCorners[2] = Point2f(marker.cols - 0.5f, marker.rows - 0.5f);
|
||||
|
||||
// remove perspective
|
||||
Mat transformation = getAffineTransform(inCorners, outCorners);
|
||||
warpAffine(marker, out, transformation, out.size(), INTER_LINEAR,
|
||||
BORDER_TRANSPARENT);
|
||||
}
|
||||
}
|
||||
|
||||
Board::Board(const Ptr<Impl>& _impl):
|
||||
impl(_impl)
|
||||
{
|
||||
CV_Assert(impl);
|
||||
}
|
||||
|
||||
Board::Board():
|
||||
impl(nullptr)
|
||||
{}
|
||||
|
||||
Board::Board(InputArrayOfArrays objPoints, const Dictionary &dictionary, InputArray ids):
|
||||
Board(new Board::Impl(dictionary)) {
|
||||
CV_Assert(ids.size() == objPoints.size());
|
||||
CV_Assert(objPoints.total() == ids.total());
|
||||
CV_Assert(objPoints.type() == CV_32FC3 || objPoints.type() == CV_32FC1);
|
||||
|
||||
vector<vector<Point3f> > obj_points_vector;
|
||||
Point3f rightBottomBorder = Point3f(0.f, 0.f, 0.f);
|
||||
for (unsigned int i = 0; i < objPoints.total(); i++) {
|
||||
vector<Point3f> corners;
|
||||
Mat corners_mat = objPoints.getMat(i);
|
||||
|
||||
if (corners_mat.type() == CV_32FC1)
|
||||
corners_mat = corners_mat.reshape(3);
|
||||
CV_Assert(corners_mat.total() == 4);
|
||||
|
||||
for (int j = 0; j < 4; j++) {
|
||||
const Point3f &corner = corners_mat.at<Point3f>(j);
|
||||
corners.push_back(corner);
|
||||
rightBottomBorder.x = std::max(rightBottomBorder.x, corner.x);
|
||||
rightBottomBorder.y = std::max(rightBottomBorder.y, corner.y);
|
||||
rightBottomBorder.z = std::max(rightBottomBorder.z, corner.z);
|
||||
}
|
||||
obj_points_vector.push_back(corners);
|
||||
}
|
||||
|
||||
ids.copyTo(impl->ids);
|
||||
impl->objPoints = obj_points_vector;
|
||||
impl->rightBottomBorder = rightBottomBorder;
|
||||
}
|
||||
|
||||
const Dictionary& Board::getDictionary() const {
|
||||
CV_Assert(this->impl);
|
||||
return this->impl->dictionary;
|
||||
}
|
||||
|
||||
const vector<vector<Point3f> >& Board::getObjPoints() const {
|
||||
CV_Assert(this->impl);
|
||||
return this->impl->objPoints;
|
||||
}
|
||||
|
||||
const Point3f& Board::getRightBottomCorner() const {
|
||||
CV_Assert(this->impl);
|
||||
return this->impl->rightBottomBorder;
|
||||
}
|
||||
|
||||
const vector<int>& Board::getIds() const {
|
||||
CV_Assert(this->impl);
|
||||
return this->impl->ids;
|
||||
}
|
||||
|
||||
/** @brief Implementation of draw planar board that accepts a raw Board pointer.
|
||||
*/
|
||||
void Board::generateImage(Size outSize, OutputArray img, int marginSize, int borderBits) const {
|
||||
CV_Assert(this->impl);
|
||||
impl->generateImage(outSize, img, marginSize, borderBits);
|
||||
}
|
||||
|
||||
void Board::matchImagePoints(InputArray detectedCorners, InputArray detectedIds, OutputArray objPoints,
|
||||
OutputArray imgPoints) const {
|
||||
CV_Assert(this->impl);
|
||||
impl->matchImagePoints(detectedCorners, detectedIds, objPoints, imgPoints);
|
||||
}
|
||||
|
||||
struct GridBoardImpl : public Board::Impl {
|
||||
GridBoardImpl(const Dictionary& _dictionary, const Size& _size, float _markerLength, float _markerSeparation):
|
||||
Board::Impl(_dictionary),
|
||||
size(_size),
|
||||
markerLength(_markerLength),
|
||||
markerSeparation(_markerSeparation)
|
||||
{
|
||||
CV_Assert(size.width*size.height > 0 && markerLength > 0 && markerSeparation > 0);
|
||||
}
|
||||
|
||||
// number of markers in X and Y directions
|
||||
const Size size;
|
||||
// marker side length (normally in meters)
|
||||
float markerLength;
|
||||
// separation between markers in the grid
|
||||
float markerSeparation;
|
||||
};
|
||||
|
||||
GridBoard::GridBoard() {}
|
||||
|
||||
GridBoard::GridBoard(const Size& size, float markerLength, float markerSeparation,
|
||||
const Dictionary &dictionary, InputArray ids):
|
||||
Board(new GridBoardImpl(dictionary, size, markerLength, markerSeparation)) {
|
||||
|
||||
size_t totalMarkers = (size_t) size.width*size.height;
|
||||
CV_Assert(ids.empty() || totalMarkers == ids.total());
|
||||
vector<vector<Point3f> > objPoints;
|
||||
objPoints.reserve(totalMarkers);
|
||||
|
||||
if(!ids.empty()) {
|
||||
ids.copyTo(impl->ids);
|
||||
} else {
|
||||
impl->ids = std::vector<int>(totalMarkers);
|
||||
std::iota(impl->ids.begin(), impl->ids.end(), 0);
|
||||
}
|
||||
|
||||
// calculate Board objPoints
|
||||
for (int y = 0; y < size.height; y++) {
|
||||
for (int x = 0; x < size.width; x++) {
|
||||
vector <Point3f> corners(4);
|
||||
corners[0] = Point3f(x * (markerLength + markerSeparation),
|
||||
y * (markerLength + markerSeparation), 0);
|
||||
corners[1] = corners[0] + Point3f(markerLength, 0, 0);
|
||||
corners[2] = corners[0] + Point3f(markerLength, markerLength, 0);
|
||||
corners[3] = corners[0] + Point3f(0, markerLength, 0);
|
||||
objPoints.push_back(corners);
|
||||
}
|
||||
}
|
||||
impl->objPoints = objPoints;
|
||||
impl->rightBottomBorder = Point3f(size.width * markerLength + markerSeparation * (size.width - 1),
|
||||
size.height * markerLength + markerSeparation * (size.height - 1), 0.f);
|
||||
}
|
||||
|
||||
Size GridBoard::getGridSize() const {
|
||||
CV_Assert(impl);
|
||||
return static_pointer_cast<GridBoardImpl>(impl)->size;
|
||||
}
|
||||
|
||||
float GridBoard::getMarkerLength() const {
|
||||
CV_Assert(impl);
|
||||
return static_pointer_cast<GridBoardImpl>(impl)->markerLength;
|
||||
}
|
||||
|
||||
float GridBoard::getMarkerSeparation() const {
|
||||
CV_Assert(impl);
|
||||
return static_pointer_cast<GridBoardImpl>(impl)->markerSeparation;
|
||||
}
|
||||
|
||||
struct CharucoBoardImpl : Board::Impl {
|
||||
CharucoBoardImpl(const Dictionary& _dictionary, const Size& _size, float _squareLength, float _markerLength):
|
||||
Board::Impl(_dictionary),
|
||||
size(_size),
|
||||
squareLength(_squareLength),
|
||||
markerLength(_markerLength)
|
||||
{}
|
||||
|
||||
// chessboard size
|
||||
Size size;
|
||||
|
||||
// Physical size of chessboard squares side (normally in meters)
|
||||
float squareLength;
|
||||
|
||||
// Physical marker side length (normally in meters)
|
||||
float markerLength;
|
||||
|
||||
// vector of chessboard 3D corners precalculated
|
||||
std::vector<Point3f> chessboardCorners;
|
||||
|
||||
// for each charuco corner, nearest marker id and nearest marker corner id of each marker
|
||||
std::vector<std::vector<int> > nearestMarkerIdx;
|
||||
std::vector<std::vector<int> > nearestMarkerCorners;
|
||||
|
||||
void calcNearestMarkerCorners();
|
||||
|
||||
void matchImagePoints(InputArrayOfArrays detectedCorners, InputArray detectedIds,
|
||||
OutputArray objPoints, OutputArray imgPoints) const override;
|
||||
|
||||
void generateImage(Size outSize, OutputArray img, int marginSize, int borderBits) const override;
|
||||
};
|
||||
|
||||
/** Fill nearestMarkerIdx and nearestMarkerCorners arrays */
|
||||
void CharucoBoardImpl::calcNearestMarkerCorners() {
|
||||
nearestMarkerIdx.resize(chessboardCorners.size());
|
||||
nearestMarkerCorners.resize(chessboardCorners.size());
|
||||
unsigned int nMarkers = (unsigned int)objPoints.size();
|
||||
unsigned int nCharucoCorners = (unsigned int)chessboardCorners.size();
|
||||
for(unsigned int i = 0; i < nCharucoCorners; i++) {
|
||||
double minDist = -1; // distance of closest markers
|
||||
Point3f charucoCorner = chessboardCorners[i];
|
||||
for(unsigned int j = 0; j < nMarkers; j++) {
|
||||
// calculate distance from marker center to charuco corner
|
||||
Point3f center = Point3f(0, 0, 0);
|
||||
for(unsigned int k = 0; k < 4; k++)
|
||||
center += objPoints[j][k];
|
||||
center /= 4.;
|
||||
double sqDistance;
|
||||
Point3f distVector = charucoCorner - center;
|
||||
sqDistance = distVector.x * distVector.x + distVector.y * distVector.y;
|
||||
if(j == 0 || fabs(sqDistance - minDist) < cv::pow(0.01 * squareLength, 2)) {
|
||||
// if same minimum distance (or first iteration), add to nearestMarkerIdx vector
|
||||
nearestMarkerIdx[i].push_back(j);
|
||||
minDist = sqDistance;
|
||||
} else if(sqDistance < minDist) {
|
||||
// if finding a closest marker to the charuco corner
|
||||
nearestMarkerIdx[i].clear(); // remove any previous added marker
|
||||
nearestMarkerIdx[i].push_back(j); // add the new closest marker index
|
||||
minDist = sqDistance;
|
||||
}
|
||||
}
|
||||
// for each of the closest markers, search the marker corner index closer
|
||||
// to the charuco corner
|
||||
for(unsigned int j = 0; j < nearestMarkerIdx[i].size(); j++) {
|
||||
nearestMarkerCorners[i].resize(nearestMarkerIdx[i].size());
|
||||
double minDistCorner = -1;
|
||||
for(unsigned int k = 0; k < 4; k++) {
|
||||
double sqDistance;
|
||||
Point3f distVector = charucoCorner - objPoints[nearestMarkerIdx[i][j]][k];
|
||||
sqDistance = distVector.x * distVector.x + distVector.y * distVector.y;
|
||||
if(k == 0 || sqDistance < minDistCorner) {
|
||||
// if this corner is closer to the charuco corner, assing its index
|
||||
// to nearestMarkerCorners
|
||||
minDistCorner = sqDistance;
|
||||
nearestMarkerCorners[i][j] = k;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CharucoBoardImpl::matchImagePoints(InputArrayOfArrays detectedCorners, InputArray detectedIds,
|
||||
OutputArray _objPoints, OutputArray imgPoints) const {
|
||||
if (detectedCorners.kind() == _InputArray::STD_VECTOR_VECTOR ||
|
||||
detectedCorners.isMatVector() || detectedCorners.isUMatVector())
|
||||
Board::Impl::matchImagePoints(detectedCorners, detectedIds, _objPoints, imgPoints);
|
||||
else {
|
||||
CV_Assert(detectedCorners.isMat() || detectedCorners.isVector());
|
||||
size_t nDetected = detectedCorners.total();
|
||||
vector<Point3f> objPnts(nDetected);
|
||||
vector<Point2f> imgPnts(nDetected);
|
||||
for(size_t i = 0ull; i < nDetected; i++) {
|
||||
int pointId = detectedIds.getMat().at<int>((int)i);
|
||||
CV_Assert(pointId >= 0 && pointId < (int)chessboardCorners.size());
|
||||
objPnts[i] = chessboardCorners[pointId];
|
||||
imgPnts[i] = detectedCorners.getMat().at<Point2f>((int)i);
|
||||
}
|
||||
Mat(objPnts).copyTo(_objPoints);
|
||||
Mat(imgPnts).copyTo(imgPoints);
|
||||
}
|
||||
}
|
||||
|
||||
void CharucoBoardImpl::generateImage(Size outSize, OutputArray img, int marginSize, int borderBits) const {
|
||||
CV_Assert(!outSize.empty());
|
||||
CV_Assert(marginSize >= 0);
|
||||
|
||||
img.create(outSize, CV_8UC1);
|
||||
img.setTo(255);
|
||||
Mat out = img.getMat();
|
||||
Mat noMarginsImg =
|
||||
out.colRange(marginSize, out.cols - marginSize).rowRange(marginSize, out.rows - marginSize);
|
||||
|
||||
double totalLengthX, totalLengthY;
|
||||
totalLengthX = squareLength * size.width;
|
||||
totalLengthY = squareLength * size.height;
|
||||
|
||||
// proportional transformation
|
||||
double xReduction = totalLengthX / double(noMarginsImg.cols);
|
||||
double yReduction = totalLengthY / double(noMarginsImg.rows);
|
||||
|
||||
// determine the zone where the chessboard is placed
|
||||
Mat chessboardZoneImg;
|
||||
if(xReduction > yReduction) {
|
||||
int nRows = int(totalLengthY / xReduction);
|
||||
int rowsMargins = (noMarginsImg.rows - nRows) / 2;
|
||||
chessboardZoneImg = noMarginsImg.rowRange(rowsMargins, noMarginsImg.rows - rowsMargins);
|
||||
} else {
|
||||
int nCols = int(totalLengthX / yReduction);
|
||||
int colsMargins = (noMarginsImg.cols - nCols) / 2;
|
||||
chessboardZoneImg = noMarginsImg.colRange(colsMargins, noMarginsImg.cols - colsMargins);
|
||||
}
|
||||
|
||||
// determine the margins to draw only the markers
|
||||
// take the minimum just to be sure
|
||||
double squareSizePixels = min(double(chessboardZoneImg.cols) / double(size.width),
|
||||
double(chessboardZoneImg.rows) / double(size.height));
|
||||
|
||||
double diffSquareMarkerLength = (squareLength - markerLength) / 2;
|
||||
int diffSquareMarkerLengthPixels =
|
||||
int(diffSquareMarkerLength * squareSizePixels / squareLength);
|
||||
|
||||
// draw markers
|
||||
Mat markersImg;
|
||||
Board::Impl::generateImage(chessboardZoneImg.size(), markersImg, diffSquareMarkerLengthPixels, borderBits);
|
||||
markersImg.copyTo(chessboardZoneImg);
|
||||
|
||||
// now draw black squares
|
||||
for(int y = 0; y < size.height; y++) {
|
||||
for(int x = 0; x < size.width; x++) {
|
||||
|
||||
if(y % 2 != x % 2) continue; // white corner, dont do anything
|
||||
|
||||
double startX, startY;
|
||||
startX = squareSizePixels * double(x);
|
||||
startY = squareSizePixels * double(y);
|
||||
|
||||
Mat squareZone = chessboardZoneImg.rowRange(int(startY), int(startY + squareSizePixels))
|
||||
.colRange(int(startX), int(startX + squareSizePixels));
|
||||
|
||||
squareZone.setTo(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CharucoBoard::CharucoBoard(){}
|
||||
|
||||
CharucoBoard::CharucoBoard(const Size& size, float squareLength, float markerLength,
|
||||
const Dictionary &dictionary, InputArray ids):
|
||||
Board(new CharucoBoardImpl(dictionary, size, squareLength, markerLength)) {
|
||||
|
||||
CV_Assert(size.width > 1 && size.height > 1 && markerLength > 0 && squareLength > markerLength);
|
||||
|
||||
vector<vector<Point3f> > objPoints;
|
||||
float diffSquareMarkerLength = (squareLength - markerLength) / 2;
|
||||
int totalMarkers = (int)(ids.total());
|
||||
ids.copyTo(impl->ids);
|
||||
// calculate Board objPoints
|
||||
int nextId = 0;
|
||||
for(int y = 0; y < size.height; y++) {
|
||||
for(int x = 0; x < size.width; x++) {
|
||||
|
||||
if(y % 2 == x % 2) continue; // black corner, no marker here
|
||||
|
||||
vector<Point3f> corners(4);
|
||||
corners[0] = Point3f(x * squareLength + diffSquareMarkerLength,
|
||||
y * squareLength + diffSquareMarkerLength, 0);
|
||||
corners[1] = corners[0] + Point3f(markerLength, 0, 0);
|
||||
corners[2] = corners[0] + Point3f(markerLength, markerLength, 0);
|
||||
corners[3] = corners[0] + Point3f(0, markerLength, 0);
|
||||
objPoints.push_back(corners);
|
||||
// first ids in dictionary
|
||||
if (totalMarkers == 0)
|
||||
impl->ids.push_back(nextId);
|
||||
nextId++;
|
||||
}
|
||||
}
|
||||
if (totalMarkers > 0 && nextId != totalMarkers)
|
||||
CV_Error(cv::Error::StsBadSize, "Size of ids must be equal to the number of markers: "+std::to_string(nextId));
|
||||
impl->objPoints = objPoints;
|
||||
|
||||
// now fill chessboardCorners
|
||||
std::vector<Point3f> & c = static_pointer_cast<CharucoBoardImpl>(impl)->chessboardCorners;
|
||||
for(int y = 0; y < size.height - 1; y++) {
|
||||
for(int x = 0; x < size.width - 1; x++) {
|
||||
Point3f corner;
|
||||
corner.x = (x + 1) * squareLength;
|
||||
corner.y = (y + 1) * squareLength;
|
||||
corner.z = 0;
|
||||
c.push_back(corner);
|
||||
}
|
||||
}
|
||||
impl->rightBottomBorder = Point3f(size.width * squareLength, size.height * squareLength, 0.f);
|
||||
static_pointer_cast<CharucoBoardImpl>(impl)->calcNearestMarkerCorners();
|
||||
}
|
||||
|
||||
Size CharucoBoard::getChessboardSize() const {
|
||||
CV_Assert(impl);
|
||||
return static_pointer_cast<CharucoBoardImpl>(impl)->size;
|
||||
}
|
||||
|
||||
float CharucoBoard::getSquareLength() const {
|
||||
CV_Assert(impl);
|
||||
return static_pointer_cast<CharucoBoardImpl>(impl)->squareLength;
|
||||
}
|
||||
|
||||
float CharucoBoard::getMarkerLength() const {
|
||||
CV_Assert(impl);
|
||||
return static_pointer_cast<CharucoBoardImpl>(impl)->markerLength;
|
||||
}
|
||||
|
||||
bool CharucoBoard::checkCharucoCornersCollinear(InputArray charucoIds) const {
|
||||
CV_Assert(impl);
|
||||
|
||||
unsigned int nCharucoCorners = (unsigned int)charucoIds.getMat().total();
|
||||
if (nCharucoCorners <= 2)
|
||||
return true;
|
||||
|
||||
// only test if there are 3 or more corners
|
||||
auto board = static_pointer_cast<CharucoBoardImpl>(impl);
|
||||
CV_Assert(board->chessboardCorners.size() >= charucoIds.getMat().total());
|
||||
|
||||
Vec<double, 3> point0(board->chessboardCorners[charucoIds.getMat().at<int>(0)].x,
|
||||
board->chessboardCorners[charucoIds.getMat().at<int>(0)].y, 1);
|
||||
|
||||
Vec<double, 3> point1(board->chessboardCorners[charucoIds.getMat().at<int>(1)].x,
|
||||
board->chessboardCorners[charucoIds.getMat().at<int>(1)].y, 1);
|
||||
|
||||
// create a line from the first two points.
|
||||
Vec<double, 3> testLine = point0.cross(point1);
|
||||
Vec<double, 3> testPoint(0, 0, 1);
|
||||
|
||||
double divisor = sqrt(testLine[0]*testLine[0] + testLine[1]*testLine[1]);
|
||||
CV_Assert(divisor != 0.0);
|
||||
|
||||
// normalize the line with normal
|
||||
testLine /= divisor;
|
||||
|
||||
double dotProduct;
|
||||
for (unsigned int i = 2; i < nCharucoCorners; i++){
|
||||
testPoint(0) = board->chessboardCorners[charucoIds.getMat().at<int>(i)].x;
|
||||
testPoint(1) = board->chessboardCorners[charucoIds.getMat().at<int>(i)].y;
|
||||
|
||||
// if testPoint is on testLine, dotProduct will be zero (or very, very close)
|
||||
dotProduct = testPoint.dot(testLine);
|
||||
|
||||
if (std::abs(dotProduct) > 1e-6){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// no points found that were off of testLine, return true that all points collinear.
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<Point3f> CharucoBoard::getChessboardCorners() const {
|
||||
CV_Assert(impl);
|
||||
return static_pointer_cast<CharucoBoardImpl>(impl)->chessboardCorners;
|
||||
}
|
||||
|
||||
std::vector<std::vector<int> > CharucoBoard::getNearestMarkerIdx() const {
|
||||
CV_Assert(impl);
|
||||
return static_pointer_cast<CharucoBoardImpl>(impl)->nearestMarkerIdx;
|
||||
}
|
||||
|
||||
std::vector<std::vector<int> > CharucoBoard::getNearestMarkerCorners() const {
|
||||
CV_Assert(impl);
|
||||
return static_pointer_cast<CharucoBoardImpl>(impl)->nearestMarkerCorners;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,442 @@
|
||||
// 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
|
||||
|
||||
#include "../precomp.hpp"
|
||||
#include "opencv2/core/hal/hal.hpp"
|
||||
|
||||
#include "aruco_utils.hpp"
|
||||
#include "predefined_dictionaries.hpp"
|
||||
#include "apriltag/predefined_dictionaries_apriltag.hpp"
|
||||
#include <opencv2/objdetect/aruco_dictionary.hpp>
|
||||
|
||||
namespace cv {
|
||||
namespace aruco {
|
||||
|
||||
using namespace std;
|
||||
|
||||
Dictionary::Dictionary(): markerSize(0), maxCorrectionBits(0) {}
|
||||
|
||||
|
||||
Dictionary::Dictionary(const Mat &_bytesList, int _markerSize, int _maxcorr) {
|
||||
markerSize = _markerSize;
|
||||
maxCorrectionBits = _maxcorr;
|
||||
bytesList = _bytesList;
|
||||
}
|
||||
|
||||
|
||||
bool Dictionary::readDictionary(const cv::FileNode& fn) {
|
||||
int nMarkers = 0, _markerSize = 0;
|
||||
if (fn.empty() || !readParameter("nmarkers", nMarkers, fn) || !readParameter("markersize", _markerSize, fn))
|
||||
return false;
|
||||
Mat bytes(0, 0, CV_8UC1), marker(_markerSize, _markerSize, CV_8UC1);
|
||||
std::string markerString;
|
||||
for (int i = 0; i < nMarkers; i++) {
|
||||
std::ostringstream ostr;
|
||||
ostr << i;
|
||||
if (!readParameter("marker_" + ostr.str(), markerString, fn))
|
||||
return false;
|
||||
for (int j = 0; j < (int) markerString.size(); j++)
|
||||
marker.at<unsigned char>(j) = (markerString[j] == '0') ? 0 : 1;
|
||||
bytes.push_back(Dictionary::getByteListFromBits(marker));
|
||||
}
|
||||
int _maxCorrectionBits = 0;
|
||||
readParameter("maxCorrectionBits", _maxCorrectionBits, fn);
|
||||
*this = Dictionary(bytes, _markerSize, _maxCorrectionBits);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Dictionary::writeDictionary(FileStorage& fs, const String &name)
|
||||
{
|
||||
CV_Assert(fs.isOpened());
|
||||
|
||||
if (!name.empty())
|
||||
fs << name << "{";
|
||||
|
||||
fs << "nmarkers" << bytesList.rows;
|
||||
fs << "markersize" << markerSize;
|
||||
fs << "maxCorrectionBits" << maxCorrectionBits;
|
||||
for (int i = 0; i < bytesList.rows; i++) {
|
||||
Mat row = bytesList.row(i);;
|
||||
Mat bitMarker = getBitsFromByteList(row, markerSize);
|
||||
std::ostringstream ostr;
|
||||
ostr << i;
|
||||
string markerName = "marker_" + ostr.str();
|
||||
string marker;
|
||||
for (int j = 0; j < markerSize * markerSize; j++)
|
||||
marker.push_back(bitMarker.at<uint8_t>(j) + '0');
|
||||
fs << markerName << marker;
|
||||
}
|
||||
|
||||
if (!name.empty())
|
||||
fs << "}";
|
||||
}
|
||||
|
||||
|
||||
bool Dictionary::identify(const Mat &onlyBits, int &idx, int &rotation, double maxCorrectionRate) const {
|
||||
CV_Assert(onlyBits.rows == markerSize && onlyBits.cols == markerSize);
|
||||
|
||||
int maxCorrectionRecalculed = int(double(maxCorrectionBits) * maxCorrectionRate);
|
||||
|
||||
// get as a byte list
|
||||
Mat candidateBytes = getByteListFromBits(onlyBits);
|
||||
|
||||
idx = -1; // by default, not found
|
||||
|
||||
// search closest marker in dict
|
||||
for(int m = 0; m < bytesList.rows; m++) {
|
||||
int currentMinDistance = markerSize * markerSize + 1;
|
||||
int currentRotation = -1;
|
||||
for(unsigned int r = 0; r < 4; r++) {
|
||||
int currentHamming = cv::hal::normHamming(
|
||||
bytesList.ptr(m)+r*candidateBytes.cols,
|
||||
candidateBytes.ptr(),
|
||||
candidateBytes.cols);
|
||||
|
||||
if(currentHamming < currentMinDistance) {
|
||||
currentMinDistance = currentHamming;
|
||||
currentRotation = r;
|
||||
}
|
||||
}
|
||||
|
||||
// if maxCorrection is fulfilled, return this one
|
||||
if(currentMinDistance <= maxCorrectionRecalculed) {
|
||||
idx = m;
|
||||
rotation = currentRotation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return idx != -1;
|
||||
}
|
||||
|
||||
|
||||
int Dictionary::getDistanceToId(InputArray bits, int id, bool allRotations) const {
|
||||
|
||||
CV_Assert(id >= 0 && id < bytesList.rows);
|
||||
|
||||
unsigned int nRotations = 4;
|
||||
if(!allRotations) nRotations = 1;
|
||||
|
||||
Mat candidateBytes = getByteListFromBits(bits.getMat());
|
||||
int currentMinDistance = int(bits.total() * bits.total());
|
||||
for(unsigned int r = 0; r < nRotations; r++) {
|
||||
int currentHamming = cv::hal::normHamming(
|
||||
bytesList.ptr(id) + r*candidateBytes.cols,
|
||||
candidateBytes.ptr(),
|
||||
candidateBytes.cols);
|
||||
|
||||
if(currentHamming < currentMinDistance) {
|
||||
currentMinDistance = currentHamming;
|
||||
}
|
||||
}
|
||||
return currentMinDistance;
|
||||
}
|
||||
|
||||
|
||||
void Dictionary::generateImageMarker(int id, int sidePixels, OutputArray _img, int borderBits) const {
|
||||
CV_Assert(sidePixels >= (markerSize + 2*borderBits));
|
||||
CV_Assert(id < bytesList.rows);
|
||||
CV_Assert(borderBits > 0);
|
||||
|
||||
_img.create(sidePixels, sidePixels, CV_8UC1);
|
||||
|
||||
// create small marker with 1 pixel per bin
|
||||
Mat tinyMarker(markerSize + 2 * borderBits, markerSize + 2 * borderBits, CV_8UC1,
|
||||
Scalar::all(0));
|
||||
Mat innerRegion = tinyMarker.rowRange(borderBits, tinyMarker.rows - borderBits)
|
||||
.colRange(borderBits, tinyMarker.cols - borderBits);
|
||||
// put inner bits
|
||||
Mat bits = 255 * getBitsFromByteList(bytesList.rowRange(id, id + 1), markerSize);
|
||||
CV_Assert(innerRegion.total() == bits.total());
|
||||
bits.copyTo(innerRegion);
|
||||
|
||||
// resize tiny marker to output size
|
||||
cv::resize(tinyMarker, _img.getMat(), _img.getMat().size(), 0, 0, INTER_NEAREST);
|
||||
}
|
||||
|
||||
|
||||
Mat Dictionary::getByteListFromBits(const Mat &bits) {
|
||||
// integer ceil
|
||||
int nbytes = (bits.cols * bits.rows + 8 - 1) / 8;
|
||||
|
||||
Mat candidateByteList(1, nbytes, CV_8UC4, Scalar::all(0));
|
||||
unsigned char currentBit = 0;
|
||||
int currentByte = 0;
|
||||
|
||||
// the 4 rotations
|
||||
uchar* rot0 = candidateByteList.ptr();
|
||||
uchar* rot1 = candidateByteList.ptr() + 1*nbytes;
|
||||
uchar* rot2 = candidateByteList.ptr() + 2*nbytes;
|
||||
uchar* rot3 = candidateByteList.ptr() + 3*nbytes;
|
||||
|
||||
for(int row = 0; row < bits.rows; row++) {
|
||||
for(int col = 0; col < bits.cols; col++) {
|
||||
// circular shift
|
||||
rot0[currentByte] <<= 1;
|
||||
rot1[currentByte] <<= 1;
|
||||
rot2[currentByte] <<= 1;
|
||||
rot3[currentByte] <<= 1;
|
||||
// set bit
|
||||
rot0[currentByte] |= bits.at<uchar>(row, col);
|
||||
rot1[currentByte] |= bits.at<uchar>(col, bits.cols - 1 - row);
|
||||
rot2[currentByte] |= bits.at<uchar>(bits.rows - 1 - row, bits.cols - 1 - col);
|
||||
rot3[currentByte] |= bits.at<uchar>(bits.rows - 1 - col, row);
|
||||
currentBit++;
|
||||
if(currentBit == 8) {
|
||||
// next byte
|
||||
currentBit = 0;
|
||||
currentByte++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return candidateByteList;
|
||||
}
|
||||
|
||||
|
||||
Mat Dictionary::getBitsFromByteList(const Mat &byteList, int markerSize) {
|
||||
CV_Assert(byteList.total() > 0 &&
|
||||
byteList.total() >= (unsigned int)markerSize * markerSize / 8 &&
|
||||
byteList.total() <= (unsigned int)markerSize * markerSize / 8 + 1);
|
||||
Mat bits(markerSize, markerSize, CV_8UC1, Scalar::all(0));
|
||||
|
||||
unsigned char base2List[] = { 128, 64, 32, 16, 8, 4, 2, 1 };
|
||||
int currentByteIdx = 0;
|
||||
// we only need the bytes in normal rotation
|
||||
unsigned char currentByte = byteList.ptr()[0];
|
||||
int currentBit = 0;
|
||||
for(int row = 0; row < bits.rows; row++) {
|
||||
for(int col = 0; col < bits.cols; col++) {
|
||||
if(currentByte >= base2List[currentBit]) {
|
||||
bits.at<unsigned char>(row, col) = 1;
|
||||
currentByte -= base2List[currentBit];
|
||||
}
|
||||
currentBit++;
|
||||
if(currentBit == 8) {
|
||||
currentByteIdx++;
|
||||
currentByte = byteList.ptr()[currentByteIdx];
|
||||
// if not enough bits for one more byte, we are in the end
|
||||
// update bit position accordingly
|
||||
if(8 * (currentByteIdx + 1) > (int)bits.total())
|
||||
currentBit = 8 * (currentByteIdx + 1) - (int)bits.total();
|
||||
else
|
||||
currentBit = 0; // ok, bits enough for next byte
|
||||
}
|
||||
}
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
|
||||
Dictionary getPredefinedDictionary(PredefinedDictionaryType name) {
|
||||
// DictionaryData constructors calls
|
||||
// moved out of globals so construted on first use, which allows lazy-loading of opencv dll
|
||||
static const Dictionary DICT_ARUCO_DATA = Dictionary(Mat(1024, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_ARUCO_BYTES), 5, 0);
|
||||
|
||||
static const Dictionary DICT_4X4_50_DATA = Dictionary(Mat(50, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 1);
|
||||
static const Dictionary DICT_4X4_100_DATA = Dictionary(Mat(100, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 1);
|
||||
static const Dictionary DICT_4X4_250_DATA = Dictionary(Mat(250, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 1);
|
||||
static const Dictionary DICT_4X4_1000_DATA = Dictionary(Mat(1000, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 0);
|
||||
|
||||
static const Dictionary DICT_5X5_50_DATA = Dictionary(Mat(50, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 3);
|
||||
static const Dictionary DICT_5X5_100_DATA = Dictionary(Mat(100, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 3);
|
||||
static const Dictionary DICT_5X5_250_DATA = Dictionary(Mat(250, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 2);
|
||||
static const Dictionary DICT_5X5_1000_DATA = Dictionary(Mat(1000, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 2);
|
||||
|
||||
static const Dictionary DICT_6X6_50_DATA = Dictionary(Mat(50, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 6);
|
||||
static const Dictionary DICT_6X6_100_DATA = Dictionary(Mat(100, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 5);
|
||||
static const Dictionary DICT_6X6_250_DATA = Dictionary(Mat(250, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 5);
|
||||
static const Dictionary DICT_6X6_1000_DATA = Dictionary(Mat(1000, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 4);
|
||||
|
||||
static const Dictionary DICT_7X7_50_DATA = Dictionary(Mat(50, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 9);
|
||||
static const Dictionary DICT_7X7_100_DATA = Dictionary(Mat(100, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 8);
|
||||
static const Dictionary DICT_7X7_250_DATA = Dictionary(Mat(250, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 8);
|
||||
static const Dictionary DICT_7X7_1000_DATA = Dictionary(Mat(1000, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 6);
|
||||
|
||||
static const Dictionary DICT_APRILTAG_16h5_DATA = Dictionary(Mat(30, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_16h5_BYTES), 4, 0);
|
||||
static const Dictionary DICT_APRILTAG_25h9_DATA = Dictionary(Mat(35, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_25h9_BYTES), 5, 0);
|
||||
static const Dictionary DICT_APRILTAG_36h10_DATA = Dictionary(Mat(2320, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h10_BYTES), 6, 0);
|
||||
static const Dictionary DICT_APRILTAG_36h11_DATA = Dictionary(Mat(587, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h11_BYTES), 6, 0);
|
||||
|
||||
switch(name) {
|
||||
|
||||
case DICT_ARUCO_ORIGINAL:
|
||||
return Dictionary(DICT_ARUCO_DATA);
|
||||
|
||||
case DICT_4X4_50:
|
||||
return Dictionary(DICT_4X4_50_DATA);
|
||||
case DICT_4X4_100:
|
||||
return Dictionary(DICT_4X4_100_DATA);
|
||||
case DICT_4X4_250:
|
||||
return Dictionary(DICT_4X4_250_DATA);
|
||||
case DICT_4X4_1000:
|
||||
return Dictionary(DICT_4X4_1000_DATA);
|
||||
|
||||
case DICT_5X5_50:
|
||||
return Dictionary(DICT_5X5_50_DATA);
|
||||
case DICT_5X5_100:
|
||||
return Dictionary(DICT_5X5_100_DATA);
|
||||
case DICT_5X5_250:
|
||||
return Dictionary(DICT_5X5_250_DATA);
|
||||
case DICT_5X5_1000:
|
||||
return Dictionary(DICT_5X5_1000_DATA);
|
||||
|
||||
case DICT_6X6_50:
|
||||
return Dictionary(DICT_6X6_50_DATA);
|
||||
case DICT_6X6_100:
|
||||
return Dictionary(DICT_6X6_100_DATA);
|
||||
case DICT_6X6_250:
|
||||
return Dictionary(DICT_6X6_250_DATA);
|
||||
case DICT_6X6_1000:
|
||||
return Dictionary(DICT_6X6_1000_DATA);
|
||||
|
||||
case DICT_7X7_50:
|
||||
return Dictionary(DICT_7X7_50_DATA);
|
||||
case DICT_7X7_100:
|
||||
return Dictionary(DICT_7X7_100_DATA);
|
||||
case DICT_7X7_250:
|
||||
return Dictionary(DICT_7X7_250_DATA);
|
||||
case DICT_7X7_1000:
|
||||
return Dictionary(DICT_7X7_1000_DATA);
|
||||
|
||||
case DICT_APRILTAG_16h5:
|
||||
return Dictionary(DICT_APRILTAG_16h5_DATA);
|
||||
case DICT_APRILTAG_25h9:
|
||||
return Dictionary(DICT_APRILTAG_25h9_DATA);
|
||||
case DICT_APRILTAG_36h10:
|
||||
return Dictionary(DICT_APRILTAG_36h10_DATA);
|
||||
case DICT_APRILTAG_36h11:
|
||||
return Dictionary(DICT_APRILTAG_36h11_DATA);
|
||||
|
||||
}
|
||||
return Dictionary(DICT_4X4_50_DATA);
|
||||
}
|
||||
|
||||
|
||||
Dictionary getPredefinedDictionary(int dict) {
|
||||
return getPredefinedDictionary(PredefinedDictionaryType(dict));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Generates a random marker Mat of size markerSize x markerSize
|
||||
*/
|
||||
static Mat _generateRandomMarker(int markerSize, RNG &rng) {
|
||||
Mat marker(markerSize, markerSize, CV_8UC1, Scalar::all(0));
|
||||
for(int i = 0; i < markerSize; i++) {
|
||||
for(int j = 0; j < markerSize; j++) {
|
||||
unsigned char bit = (unsigned char) (rng.uniform(0,2));
|
||||
marker.at<unsigned char>(i, j) = bit;
|
||||
}
|
||||
}
|
||||
return marker;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate selfDistance of the codification of a marker Mat. Self distance is the Hamming
|
||||
* distance of the marker to itself in the other rotations.
|
||||
* See S. Garrido-Jurado, R. Muñoz-Salinas, F. J. Madrid-Cuevas, and M. J. Marín-Jiménez. 2014.
|
||||
* "Automatic generation and detection of highly reliable fiducial markers under occlusion".
|
||||
* Pattern Recogn. 47, 6 (June 2014), 2280-2292. DOI=10.1016/j.patcog.2014.01.005
|
||||
*/
|
||||
static int _getSelfDistance(const Mat &marker) {
|
||||
Mat bytes = Dictionary::getByteListFromBits(marker);
|
||||
int minHamming = (int)marker.total() + 1;
|
||||
for(int r = 1; r < 4; r++) {
|
||||
int currentHamming = cv::hal::normHamming(bytes.ptr(), bytes.ptr() + bytes.cols*r, bytes.cols);
|
||||
if(currentHamming < minHamming) minHamming = currentHamming;
|
||||
}
|
||||
return minHamming;
|
||||
}
|
||||
|
||||
|
||||
Dictionary extendDictionary(int nMarkers, int markerSize, const Dictionary &baseDictionary, int randomSeed) {
|
||||
RNG rng((uint64)(randomSeed));
|
||||
|
||||
Dictionary out = Dictionary(Mat(), markerSize);
|
||||
out.markerSize = markerSize;
|
||||
|
||||
// theoretical maximum intermarker distance
|
||||
// See S. Garrido-Jurado, R. Muñoz-Salinas, F. J. Madrid-Cuevas, and M. J. Marín-Jiménez. 2014.
|
||||
// "Automatic generation and detection of highly reliable fiducial markers under occlusion".
|
||||
// Pattern Recogn. 47, 6 (June 2014), 2280-2292. DOI=10.1016/j.patcog.2014.01.005
|
||||
int C = (int)std::floor(float(markerSize * markerSize) / 4.f);
|
||||
int tau = 2 * (int)std::floor(float(C) * 4.f / 3.f);
|
||||
|
||||
// if baseDictionary is provided, calculate its intermarker distance
|
||||
if(baseDictionary.bytesList.rows > 0) {
|
||||
CV_Assert(baseDictionary.markerSize == markerSize);
|
||||
out.bytesList = baseDictionary.bytesList.clone();
|
||||
|
||||
int minDistance = markerSize * markerSize + 1;
|
||||
for(int i = 0; i < out.bytesList.rows; i++) {
|
||||
Mat markerBytes = out.bytesList.rowRange(i, i + 1);
|
||||
Mat markerBits = Dictionary::getBitsFromByteList(markerBytes, markerSize);
|
||||
minDistance = min(minDistance, _getSelfDistance(markerBits));
|
||||
for(int j = i + 1; j < out.bytesList.rows; j++) {
|
||||
minDistance = min(minDistance, out.getDistanceToId(markerBits, j));
|
||||
}
|
||||
}
|
||||
tau = minDistance;
|
||||
}
|
||||
|
||||
// current best option
|
||||
int bestTau = 0;
|
||||
Mat bestMarker;
|
||||
|
||||
// after these number of unproductive iterations, the best option is accepted
|
||||
const int maxUnproductiveIterations = 5000;
|
||||
int unproductiveIterations = 0;
|
||||
|
||||
while(out.bytesList.rows < nMarkers) {
|
||||
Mat currentMarker = _generateRandomMarker(markerSize, rng);
|
||||
|
||||
int selfDistance = _getSelfDistance(currentMarker);
|
||||
int minDistance = selfDistance;
|
||||
|
||||
// if self distance is better or equal than current best option, calculate distance
|
||||
// to previous accepted markers
|
||||
if(selfDistance >= bestTau) {
|
||||
for(int i = 0; i < out.bytesList.rows; i++) {
|
||||
int currentDistance = out.getDistanceToId(currentMarker, i);
|
||||
minDistance = min(currentDistance, minDistance);
|
||||
if(minDistance <= bestTau) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if distance is high enough, accept the marker
|
||||
if(minDistance >= tau) {
|
||||
unproductiveIterations = 0;
|
||||
bestTau = 0;
|
||||
Mat bytes = Dictionary::getByteListFromBits(currentMarker);
|
||||
out.bytesList.push_back(bytes);
|
||||
} else {
|
||||
unproductiveIterations++;
|
||||
|
||||
// if distance is not enough, but is better than the current best option
|
||||
if(minDistance > bestTau) {
|
||||
bestTau = minDistance;
|
||||
bestMarker = currentMarker;
|
||||
}
|
||||
|
||||
// if number of unproductive iterarions has been reached, accept the current best option
|
||||
if(unproductiveIterations == maxUnproductiveIterations) {
|
||||
unproductiveIterations = 0;
|
||||
tau = bestTau;
|
||||
bestTau = 0;
|
||||
Mat bytes = Dictionary::getByteListFromBits(bestMarker);
|
||||
out.bytesList.push_back(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update the maximum number of correction bits for the generated dictionary
|
||||
out.maxCorrectionBits = (tau - 1) / 2;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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
|
||||
|
||||
#include "../precomp.hpp"
|
||||
#include "aruco_utils.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace aruco {
|
||||
using namespace std;
|
||||
|
||||
void _copyVector2Output(vector<vector<Point2f> > &vec, OutputArrayOfArrays out, const float scale) {
|
||||
out.create((int)vec.size(), 1, CV_32FC2);
|
||||
if(out.isMatVector()) {
|
||||
for (unsigned int i = 0; i < vec.size(); i++) {
|
||||
out.create(4, 1, CV_32FC2, i);
|
||||
Mat &m = out.getMatRef(i);
|
||||
Mat(Mat(vec[i]).t()*scale).copyTo(m);
|
||||
}
|
||||
}
|
||||
else if(out.isUMatVector()) {
|
||||
for (unsigned int i = 0; i < vec.size(); i++) {
|
||||
out.create(4, 1, CV_32FC2, i);
|
||||
UMat &m = out.getUMatRef(i);
|
||||
Mat(Mat(vec[i]).t()*scale).copyTo(m);
|
||||
}
|
||||
}
|
||||
else if(out.kind() == _OutputArray::STD_VECTOR_VECTOR){
|
||||
for (unsigned int i = 0; i < vec.size(); i++) {
|
||||
out.create(4, 1, CV_32FC2, i);
|
||||
Mat m = out.getMat(i);
|
||||
Mat(Mat(vec[i]).t()*scale).copyTo(m);
|
||||
}
|
||||
}
|
||||
else {
|
||||
CV_Error(cv::Error::StsNotImplemented,
|
||||
"Only Mat vector, UMat vector, and vector<vector> OutputArrays are currently supported.");
|
||||
}
|
||||
}
|
||||
|
||||
void _convertToGrey(InputArray _in, OutputArray _out) {
|
||||
CV_Assert(_in.type() == CV_8UC1 || _in.type() == CV_8UC3);
|
||||
if(_in.type() == CV_8UC3)
|
||||
cvtColor(_in, _out, COLOR_BGR2GRAY);
|
||||
else
|
||||
_in.copyTo(_out);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// 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 __OPENCV_OBJDETECT_ARUCO_UTILS_HPP__
|
||||
#define __OPENCV_OBJDETECT_ARUCO_UTILS_HPP__
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace cv {
|
||||
namespace aruco {
|
||||
|
||||
/**
|
||||
* @brief Copy the contents of a corners vector to an OutputArray, settings its size.
|
||||
*/
|
||||
void _copyVector2Output(std::vector<std::vector<Point2f> > &vec, OutputArrayOfArrays out, const float scale = 1.f);
|
||||
|
||||
/**
|
||||
* @brief Convert input image to gray if it is a 3-channels image
|
||||
*/
|
||||
void _convertToGrey(InputArray _in, OutputArray _out);
|
||||
|
||||
template<typename T>
|
||||
inline bool readParameter(const std::string& name, T& parameter, const FileNode& node)
|
||||
{
|
||||
if (!node.empty() && !node[name].empty()) {
|
||||
node[name] >> parameter;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline bool readWriteParameter(const std::string& name, T& parameter, const FileNode* readNode, FileStorage* writeStorage)
|
||||
{
|
||||
if (readNode)
|
||||
return readParameter(name, parameter, *readNode);
|
||||
CV_Assert(writeStorage);
|
||||
*writeStorage << name << parameter;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,521 @@
|
||||
// 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
|
||||
|
||||
#include "../precomp.hpp"
|
||||
|
||||
#include <opencv2/3d.hpp>
|
||||
#include "opencv2/objdetect/charuco_detector.hpp"
|
||||
#include "aruco_utils.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace aruco {
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct CharucoDetector::CharucoDetectorImpl {
|
||||
CharucoBoard board;
|
||||
CharucoParameters charucoParameters;
|
||||
ArucoDetector arucoDetector;
|
||||
|
||||
CharucoDetectorImpl(const CharucoBoard& _board, const CharucoParameters _charucoParameters,
|
||||
const ArucoDetector& _arucoDetector): board(_board), charucoParameters(_charucoParameters),
|
||||
arucoDetector(_arucoDetector)
|
||||
{}
|
||||
|
||||
/** Calculate the maximum window sizes for corner refinement for each charuco corner based on the distance
|
||||
* to their closest markers */
|
||||
vector<Size> getMaximumSubPixWindowSizes(InputArrayOfArrays markerCorners, InputArray markerIds,
|
||||
InputArray charucoCorners) {
|
||||
size_t nCharucoCorners = charucoCorners.getMat().total();
|
||||
|
||||
CV_Assert(board.getNearestMarkerIdx().size() == nCharucoCorners);
|
||||
|
||||
vector<Size> winSizes(nCharucoCorners, Size(-1, -1));
|
||||
for(size_t i = 0ull; i < nCharucoCorners; i++) {
|
||||
if(charucoCorners.getMat().at<Point2f>((int)i) == Point2f(-1.f, -1.f)) continue;
|
||||
if(board.getNearestMarkerIdx()[i].empty()) continue;
|
||||
double minDist = -1;
|
||||
int counter = 0;
|
||||
// calculate the distance to each of the closest corner of each closest marker
|
||||
for(size_t j = 0; j < board.getNearestMarkerIdx()[i].size(); j++) {
|
||||
// find marker
|
||||
int markerId = board.getIds()[board.getNearestMarkerIdx()[i][j]];
|
||||
int markerIdx = -1;
|
||||
for(size_t k = 0; k < markerIds.getMat().total(); k++) {
|
||||
if(markerIds.getMat().at<int>((int)k) == markerId) {
|
||||
markerIdx = (int)k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(markerIdx == -1) continue;
|
||||
Point2f markerCorner =
|
||||
markerCorners.getMat(markerIdx).at<Point2f>(board.getNearestMarkerCorners()[i][j]);
|
||||
Point2f charucoCorner = charucoCorners.getMat().at<Point2f>((int)i);
|
||||
double dist = norm(markerCorner - charucoCorner);
|
||||
if(minDist == -1) minDist = dist; // if first distance, just assign it
|
||||
minDist = min(dist, minDist);
|
||||
counter++;
|
||||
}
|
||||
// if this is the first closest marker, dont do anything
|
||||
if(counter == 0)
|
||||
continue;
|
||||
else {
|
||||
// else, calculate the maximum window size
|
||||
int winSizeInt = int(minDist - 2); // remove 2 pixels for safety
|
||||
if(winSizeInt < 1) winSizeInt = 1; // minimum size is 1
|
||||
if(winSizeInt > 10) winSizeInt = 10; // maximum size is 10
|
||||
winSizes[i] = Size(winSizeInt, winSizeInt);
|
||||
}
|
||||
}
|
||||
return winSizes;
|
||||
}
|
||||
|
||||
/** @brief From all projected chessboard corners, select those inside the image and apply subpixel refinement */
|
||||
void selectAndRefineChessboardCorners(InputArray allCorners, InputArray image, OutputArray selectedCorners,
|
||||
OutputArray selectedIds, const vector<Size> &winSizes) {
|
||||
const int minDistToBorder = 2; // minimum distance of the corner to the image border
|
||||
// remaining corners, ids and window refinement sizes after removing corners outside the image
|
||||
vector<Point2f> filteredChessboardImgPoints;
|
||||
vector<Size> filteredWinSizes;
|
||||
vector<int> filteredIds;
|
||||
// filter corners outside the image
|
||||
Rect innerRect(minDistToBorder, minDistToBorder, image.getMat().cols - 2 * minDistToBorder,
|
||||
image.getMat().rows - 2 * minDistToBorder);
|
||||
for(unsigned int i = 0; i < allCorners.getMat().total(); i++) {
|
||||
if(innerRect.contains(allCorners.getMat().at<Point2f>(i))) {
|
||||
filteredChessboardImgPoints.push_back(allCorners.getMat().at<Point2f>(i));
|
||||
filteredIds.push_back(i);
|
||||
filteredWinSizes.push_back(winSizes[i]);
|
||||
}
|
||||
}
|
||||
// if none valid, return 0
|
||||
if(filteredChessboardImgPoints.empty()) return;
|
||||
// corner refinement, first convert input image to grey
|
||||
Mat grey;
|
||||
if(image.type() == CV_8UC3)
|
||||
cvtColor(image, grey, COLOR_BGR2GRAY);
|
||||
else
|
||||
grey = image.getMat();
|
||||
//// For each of the charuco corners, apply subpixel refinement using its correspondind winSize
|
||||
parallel_for_(Range(0, (int)filteredChessboardImgPoints.size()), [&](const Range& range) {
|
||||
const int begin = range.start;
|
||||
const int end = range.end;
|
||||
for (int i = begin; i < end; i++) {
|
||||
vector<Point2f> in;
|
||||
in.push_back(filteredChessboardImgPoints[i] - Point2f(0.5, 0.5)); // adjust sub-pixel coordinates for cornerSubPix
|
||||
Size winSize = filteredWinSizes[i];
|
||||
if (winSize.height == -1 || winSize.width == -1)
|
||||
winSize = Size(arucoDetector.getDetectorParameters().cornerRefinementWinSize,
|
||||
arucoDetector.getDetectorParameters().cornerRefinementWinSize);
|
||||
cornerSubPix(grey, in, winSize, Size(),
|
||||
TermCriteria(TermCriteria::MAX_ITER | TermCriteria::EPS,
|
||||
arucoDetector.getDetectorParameters().cornerRefinementMaxIterations,
|
||||
arucoDetector.getDetectorParameters().cornerRefinementMinAccuracy));
|
||||
filteredChessboardImgPoints[i] = in[0] + Point2f(0.5, 0.5);
|
||||
}
|
||||
});
|
||||
// parse output
|
||||
Mat(filteredChessboardImgPoints).copyTo(selectedCorners);
|
||||
Mat(filteredIds).copyTo(selectedIds);
|
||||
}
|
||||
|
||||
/** Interpolate charuco corners using approximated pose estimation */
|
||||
void interpolateCornersCharucoApproxCalib(InputArrayOfArrays markerCorners, InputArray markerIds,
|
||||
InputArray image, OutputArray charucoCorners, OutputArray charucoIds) {
|
||||
CV_Assert(image.getMat().channels() == 1 || image.getMat().channels() == 3);
|
||||
CV_Assert(markerCorners.total() == markerIds.getMat().total());
|
||||
|
||||
// approximated pose estimation using marker corners
|
||||
Mat approximatedRvec, approximatedTvec;
|
||||
Mat objPoints, imgPoints; // object and image points for the solvePnP function
|
||||
printf("before board.matchImagePoints(markerCorners, markerIds, objPoints, imgPoints);\n");
|
||||
board.matchImagePoints(markerCorners, markerIds, objPoints, imgPoints);
|
||||
printf("after board.matchImagePoints(markerCorners, markerIds, objPoints, imgPoints);\n");
|
||||
if (objPoints.total() < 4ull) // need, at least, 4 corners
|
||||
return;
|
||||
|
||||
solvePnP(objPoints, imgPoints, charucoParameters.cameraMatrix, charucoParameters.distCoeffs, approximatedRvec, approximatedTvec);
|
||||
printf("after solvePnP\n");
|
||||
|
||||
// project chessboard corners
|
||||
vector<Point2f> allChessboardImgPoints;
|
||||
projectPoints(board.getChessboardCorners(), approximatedRvec, approximatedTvec, charucoParameters.cameraMatrix,
|
||||
charucoParameters.distCoeffs, allChessboardImgPoints);
|
||||
printf("after projectPoints\n");
|
||||
// calculate maximum window sizes for subpixel refinement. The size is limited by the distance
|
||||
// to the closes marker corner to avoid erroneous displacements to marker corners
|
||||
vector<Size> subPixWinSizes = getMaximumSubPixWindowSizes(markerCorners, markerIds, allChessboardImgPoints);
|
||||
// filter corners outside the image and subpixel-refine charuco corners
|
||||
printf("before selectAndRefineChessboardCorners\n");
|
||||
selectAndRefineChessboardCorners(allChessboardImgPoints, image, charucoCorners, charucoIds, subPixWinSizes);
|
||||
}
|
||||
|
||||
/** Interpolate charuco corners using local homography */
|
||||
void interpolateCornersCharucoLocalHom(InputArrayOfArrays markerCorners, InputArray markerIds, InputArray image,
|
||||
OutputArray charucoCorners, OutputArray charucoIds) {
|
||||
CV_Assert(image.getMat().channels() == 1 || image.getMat().channels() == 3);
|
||||
CV_Assert(markerCorners.total() == markerIds.getMat().total());
|
||||
size_t nMarkers = markerIds.getMat().total();
|
||||
// calculate local homographies for each marker
|
||||
vector<Mat> transformations(nMarkers);
|
||||
vector<bool> validTransform(nMarkers, false);
|
||||
const auto& ids = board.getIds();
|
||||
for(size_t i = 0ull; i < nMarkers; i++) {
|
||||
vector<Point2f> markerObjPoints2D;
|
||||
int markerId = markerIds.getMat().at<int>((int)i);
|
||||
auto it = find(ids.begin(), ids.end(), markerId);
|
||||
if(it == ids.end()) continue;
|
||||
auto boardIdx = it - ids.begin();
|
||||
markerObjPoints2D.resize(4ull);
|
||||
for(size_t j = 0ull; j < 4ull; j++)
|
||||
markerObjPoints2D[j] =
|
||||
Point2f(board.getObjPoints()[boardIdx][j].x, board.getObjPoints()[boardIdx][j].y);
|
||||
transformations[i] = getPerspectiveTransform(markerObjPoints2D, markerCorners.getMat((int)i));
|
||||
// set transform as valid if transformation is non-singular
|
||||
double det = determinant(transformations[i]);
|
||||
validTransform[i] = std::abs(det) > 1e-6;
|
||||
}
|
||||
size_t nCharucoCorners = (size_t)board.getChessboardCorners().size();
|
||||
vector<Point2f> allChessboardImgPoints(nCharucoCorners, Point2f(-1, -1));
|
||||
// for each charuco corner, calculate its interpolation position based on the closest markers
|
||||
// homographies
|
||||
for(size_t i = 0ull; i < nCharucoCorners; i++) {
|
||||
Point2f objPoint2D = Point2f(board.getChessboardCorners()[i].x, board.getChessboardCorners()[i].y);
|
||||
vector<Point2f> interpolatedPositions;
|
||||
for(size_t j = 0ull; j < board.getNearestMarkerIdx()[i].size(); j++) {
|
||||
int markerId = board.getIds()[board.getNearestMarkerIdx()[i][j]];
|
||||
int markerIdx = -1;
|
||||
for(size_t k = 0ull; k < markerIds.getMat().total(); k++) {
|
||||
if(markerIds.getMat().at<int>((int)k) == markerId) {
|
||||
markerIdx = (int)k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (markerIdx != -1 &&
|
||||
validTransform[markerIdx])
|
||||
{
|
||||
vector<Point2f> in, out;
|
||||
in.push_back(objPoint2D);
|
||||
perspectiveTransform(in, out, transformations[markerIdx]);
|
||||
interpolatedPositions.push_back(out[0]);
|
||||
}
|
||||
}
|
||||
// none of the closest markers detected
|
||||
if(interpolatedPositions.empty()) continue;
|
||||
// more than one closest marker detected, take middle point
|
||||
if(interpolatedPositions.size() > 1ull) {
|
||||
allChessboardImgPoints[i] = (interpolatedPositions[0] + interpolatedPositions[1]) / 2.;
|
||||
}
|
||||
// a single closest marker detected
|
||||
else allChessboardImgPoints[i] = interpolatedPositions[0];
|
||||
}
|
||||
// calculate maximum window sizes for subpixel refinement. The size is limited by the distance
|
||||
// to the closes marker corner to avoid erroneous displacements to marker corners
|
||||
vector<Size> subPixWinSizes = getMaximumSubPixWindowSizes(markerCorners, markerIds, allChessboardImgPoints);
|
||||
// filter corners outside the image and subpixel-refine charuco corners
|
||||
selectAndRefineChessboardCorners(allChessboardImgPoints, image, charucoCorners, charucoIds, subPixWinSizes);
|
||||
}
|
||||
|
||||
/** Remove charuco corners if any of their minMarkers closest markers has not been detected */
|
||||
int filterCornersWithoutMinMarkers(InputArray _allCharucoCorners, InputArray allCharucoIds, InputArray allArucoIds,
|
||||
OutputArray _filteredCharucoCorners, OutputArray _filteredCharucoIds) {
|
||||
CV_Assert(charucoParameters.minMarkers >= 0 && charucoParameters.minMarkers <= 2);
|
||||
vector<Point2f> filteredCharucoCorners;
|
||||
vector<int> filteredCharucoIds;
|
||||
// for each charuco corner
|
||||
for(unsigned int i = 0; i < allCharucoIds.getMat().total(); i++) {
|
||||
int currentCharucoId = allCharucoIds.getMat().at<int>(i);
|
||||
int totalMarkers = 0; // nomber of closest marker detected
|
||||
// look for closest markers
|
||||
for(unsigned int m = 0; m < board.getNearestMarkerIdx()[currentCharucoId].size(); m++) {
|
||||
int markerId = board.getIds()[board.getNearestMarkerIdx()[currentCharucoId][m]];
|
||||
bool found = false;
|
||||
for(unsigned int k = 0; k < allArucoIds.getMat().total(); k++) {
|
||||
if(allArucoIds.getMat().at<int>(k) == markerId) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(found) totalMarkers++;
|
||||
}
|
||||
// if enough markers detected, add the charuco corner to the final list
|
||||
if(totalMarkers >= charucoParameters.minMarkers) {
|
||||
filteredCharucoIds.push_back(currentCharucoId);
|
||||
filteredCharucoCorners.push_back(_allCharucoCorners.getMat().at<Point2f>(i));
|
||||
}
|
||||
}
|
||||
// parse output
|
||||
Mat(filteredCharucoCorners).copyTo(_filteredCharucoCorners);
|
||||
Mat(filteredCharucoIds).copyTo(_filteredCharucoIds);
|
||||
return (int)_filteredCharucoIds.total();
|
||||
}
|
||||
};
|
||||
|
||||
CharucoDetector::CharucoDetector(const CharucoBoard &board, const CharucoParameters &charucoParams,
|
||||
const DetectorParameters &detectorParams, const RefineParameters& refineParams) {
|
||||
this->charucoDetectorImpl = makePtr<CharucoDetectorImpl>(board, charucoParams, ArucoDetector(board.getDictionary(), detectorParams, refineParams));
|
||||
}
|
||||
|
||||
const CharucoBoard& CharucoDetector::getBoard() const {
|
||||
return charucoDetectorImpl->board;
|
||||
}
|
||||
|
||||
void CharucoDetector::setBoard(const CharucoBoard& board) {
|
||||
this->charucoDetectorImpl->board = board;
|
||||
charucoDetectorImpl->arucoDetector.setDictionary(board.getDictionary());
|
||||
}
|
||||
|
||||
const CharucoParameters &CharucoDetector::getCharucoParameters() const {
|
||||
return charucoDetectorImpl->charucoParameters;
|
||||
}
|
||||
|
||||
void CharucoDetector::setCharucoParameters(CharucoParameters &charucoParameters) {
|
||||
charucoDetectorImpl->charucoParameters = charucoParameters;
|
||||
}
|
||||
|
||||
const DetectorParameters& CharucoDetector::getDetectorParameters() const {
|
||||
return charucoDetectorImpl->arucoDetector.getDetectorParameters();
|
||||
}
|
||||
|
||||
void CharucoDetector::setDetectorParameters(const DetectorParameters& detectorParameters) {
|
||||
charucoDetectorImpl->arucoDetector.setDetectorParameters(detectorParameters);
|
||||
}
|
||||
|
||||
const RefineParameters& CharucoDetector::getRefineParameters() const {
|
||||
return charucoDetectorImpl->arucoDetector.getRefineParameters();
|
||||
}
|
||||
|
||||
void CharucoDetector::setRefineParameters(const RefineParameters& refineParameters) {
|
||||
charucoDetectorImpl->arucoDetector.setRefineParameters(refineParameters);
|
||||
}
|
||||
|
||||
void CharucoDetector::detectBoard(InputArray image, OutputArray charucoCorners, OutputArray charucoIds,
|
||||
InputOutputArrayOfArrays markerCorners, InputOutputArray markerIds) const {
|
||||
CV_Assert((markerCorners.empty() && markerIds.empty() && !image.empty()) || (markerCorners.size() == markerIds.size()));
|
||||
vector<vector<Point2f>> tmpMarkerCorners;
|
||||
vector<int> tmpMarkerIds;
|
||||
InputOutputArrayOfArrays _markerCorners = markerCorners.needed() ? markerCorners : tmpMarkerCorners;
|
||||
InputOutputArray _markerIds = markerIds.needed() ? markerIds : tmpMarkerIds;
|
||||
|
||||
if (markerCorners.empty() && markerIds.empty()) {
|
||||
vector<vector<Point2f> > rejectedMarkers;
|
||||
charucoDetectorImpl->arucoDetector.detectMarkers(image, _markerCorners, _markerIds, rejectedMarkers);
|
||||
if (charucoDetectorImpl->charucoParameters.tryRefineMarkers)
|
||||
charucoDetectorImpl->arucoDetector.refineDetectedMarkers(image, charucoDetectorImpl->board, _markerCorners,
|
||||
_markerIds, rejectedMarkers);
|
||||
}
|
||||
// if camera parameters are avaible, use approximated calibration
|
||||
if(!charucoDetectorImpl->charucoParameters.cameraMatrix.empty())
|
||||
charucoDetectorImpl->interpolateCornersCharucoApproxCalib(_markerCorners, _markerIds, image, charucoCorners,
|
||||
charucoIds);
|
||||
// else use local homography
|
||||
else
|
||||
charucoDetectorImpl->interpolateCornersCharucoLocalHom(_markerCorners, _markerIds, image, charucoCorners,
|
||||
charucoIds);
|
||||
// to return a charuco corner, its closest aruco markers should have been detected
|
||||
charucoDetectorImpl->filterCornersWithoutMinMarkers(charucoCorners, charucoIds, _markerIds, charucoCorners,
|
||||
charucoIds);
|
||||
}
|
||||
|
||||
void CharucoDetector::detectDiamonds(InputArray image, OutputArrayOfArrays _diamondCorners, OutputArray _diamondIds,
|
||||
InputOutputArrayOfArrays inMarkerCorners, InputOutputArrayOfArrays inMarkerIds) const {
|
||||
CV_Assert(getBoard().getChessboardSize() == Size(3, 3));
|
||||
CV_Assert((inMarkerCorners.empty() && inMarkerIds.empty() && !image.empty()) || (inMarkerCorners.size() == inMarkerIds.size()));
|
||||
|
||||
vector<vector<Point2f>> tmpMarkerCorners;
|
||||
vector<int> tmpMarkerIds;
|
||||
InputOutputArrayOfArrays _markerCorners = inMarkerCorners.needed() ? inMarkerCorners : tmpMarkerCorners;
|
||||
InputOutputArray _markerIds = inMarkerIds.needed() ? inMarkerIds : tmpMarkerIds;
|
||||
if (_markerCorners.empty() && _markerIds.empty()) {
|
||||
charucoDetectorImpl->arucoDetector.detectMarkers(image, _markerCorners, _markerIds);
|
||||
}
|
||||
|
||||
const float minRepDistanceRate = 1.302455f;
|
||||
vector<vector<Point2f>> diamondCorners;
|
||||
vector<Vec4i> diamondIds;
|
||||
|
||||
// stores if the detected markers have been assigned or not to a diamond
|
||||
vector<bool> assigned(_markerIds.total(), false);
|
||||
if(_markerIds.total() < 4ull) return; // a diamond need at least 4 markers
|
||||
|
||||
// convert input image to grey
|
||||
Mat grey;
|
||||
if(image.type() == CV_8UC3)
|
||||
cvtColor(image, grey, COLOR_BGR2GRAY);
|
||||
else
|
||||
grey = image.getMat();
|
||||
auto board = getBoard();
|
||||
|
||||
// for each of the detected markers, try to find a diamond
|
||||
for(unsigned int i = 0; i < (unsigned int)_markerIds.total(); i++) {
|
||||
if(assigned[i]) continue;
|
||||
|
||||
// calculate marker perimeter
|
||||
float perimeterSq = 0;
|
||||
Mat corners = _markerCorners.getMat(i);
|
||||
for(int c = 0; c < 4; c++) {
|
||||
Point2f edge = corners.at<Point2f>(c) - corners.at<Point2f>((c + 1) % 4);
|
||||
perimeterSq += edge.x*edge.x + edge.y*edge.y;
|
||||
}
|
||||
// maximum reprojection error relative to perimeter
|
||||
float minRepDistance = sqrt(perimeterSq) * minRepDistanceRate;
|
||||
|
||||
int currentId = _markerIds.getMat().at<int>(i);
|
||||
|
||||
// prepare data to call refineDetectedMarkers()
|
||||
// detected markers (only the current one)
|
||||
vector<Mat> currentMarker;
|
||||
vector<int> currentMarkerId;
|
||||
currentMarker.push_back(_markerCorners.getMat(i));
|
||||
currentMarkerId.push_back(currentId);
|
||||
|
||||
// marker candidates (the rest of markers if they have not been assigned)
|
||||
vector<Mat> candidates;
|
||||
vector<int> candidatesIdxs;
|
||||
for(unsigned int k = 0; k < assigned.size(); k++) {
|
||||
if(k == i) continue;
|
||||
if(!assigned[k]) {
|
||||
candidates.push_back(_markerCorners.getMat(k));
|
||||
candidatesIdxs.push_back(k);
|
||||
}
|
||||
}
|
||||
if(candidates.size() < 3ull) break; // we need at least 3 free markers
|
||||
// modify charuco layout id to make sure all the ids are different than current id
|
||||
vector<int> tmpIds(4ull);
|
||||
for(int k = 1; k < 4; k++)
|
||||
tmpIds[k] = currentId + 1 + k;
|
||||
// current id is assigned to [0], so it is the marker on the top
|
||||
tmpIds[0] = currentId;
|
||||
|
||||
// create Charuco board layout for diamond (3x3 layout)
|
||||
charucoDetectorImpl->board = CharucoBoard(Size(3, 3), board.getSquareLength(),
|
||||
board.getMarkerLength(), board.getDictionary(), tmpIds);
|
||||
|
||||
// try to find the rest of markers in the diamond
|
||||
vector<int> acceptedIdxs;
|
||||
if (currentMarker.size() != 4ull)
|
||||
{
|
||||
RefineParameters refineParameters(minRepDistance, -1.f, false);
|
||||
RefineParameters tmp = charucoDetectorImpl->arucoDetector.getRefineParameters();
|
||||
charucoDetectorImpl->arucoDetector.setRefineParameters(refineParameters);
|
||||
charucoDetectorImpl->arucoDetector.refineDetectedMarkers(grey, getBoard(), currentMarker, currentMarkerId,
|
||||
candidates,
|
||||
noArray(), noArray(), acceptedIdxs);
|
||||
charucoDetectorImpl->arucoDetector.setRefineParameters(tmp);
|
||||
}
|
||||
|
||||
// if found, we have a diamond
|
||||
if(currentMarker.size() == 4ull) {
|
||||
assigned[i] = true;
|
||||
// calculate diamond id, acceptedIdxs array indicates the markers taken from candidates array
|
||||
Vec4i markerId;
|
||||
markerId[0] = currentId;
|
||||
for(int k = 1; k < 4; k++) {
|
||||
int currentMarkerIdx = candidatesIdxs[acceptedIdxs[k - 1]];
|
||||
markerId[k] = _markerIds.getMat().at<int>(currentMarkerIdx);
|
||||
assigned[currentMarkerIdx] = true;
|
||||
}
|
||||
|
||||
// interpolate the charuco corners of the diamond
|
||||
vector<Point2f> currentMarkerCorners;
|
||||
Mat aux;
|
||||
detectBoard(grey, currentMarkerCorners, aux, currentMarker, currentMarkerId);
|
||||
|
||||
// if everything is ok, save the diamond
|
||||
if(currentMarkerCorners.size() > 0ull) {
|
||||
// reorder corners
|
||||
vector<Point2f> currentMarkerCornersReorder;
|
||||
currentMarkerCornersReorder.resize(4);
|
||||
currentMarkerCornersReorder[0] = currentMarkerCorners[0];
|
||||
currentMarkerCornersReorder[1] = currentMarkerCorners[1];
|
||||
currentMarkerCornersReorder[2] = currentMarkerCorners[3];
|
||||
currentMarkerCornersReorder[3] = currentMarkerCorners[2];
|
||||
|
||||
diamondCorners.push_back(currentMarkerCornersReorder);
|
||||
diamondIds.push_back(markerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
charucoDetectorImpl->board = board;
|
||||
|
||||
if(diamondIds.size() > 0ull) {
|
||||
// parse output
|
||||
Mat(diamondIds).copyTo(_diamondIds);
|
||||
|
||||
_diamondCorners.create((int)diamondCorners.size(), 1, CV_32FC2);
|
||||
for(unsigned int i = 0; i < diamondCorners.size(); i++) {
|
||||
_diamondCorners.create(4, 1, CV_32FC2, i, true);
|
||||
for(int j = 0; j < 4; j++) {
|
||||
_diamondCorners.getMat(i).at<Point2f>(j) = diamondCorners[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void drawDetectedCornersCharuco(InputOutputArray _image, InputArray _charucoCorners,
|
||||
InputArray _charucoIds, Scalar cornerColor) {
|
||||
CV_Assert(!_image.getMat().empty() &&
|
||||
(_image.getMat().channels() == 1 || _image.getMat().channels() == 3));
|
||||
CV_Assert((_charucoCorners.getMat().total() == _charucoIds.getMat().total()) ||
|
||||
_charucoIds.getMat().total() == 0);
|
||||
|
||||
size_t nCorners = _charucoCorners.getMat().total();
|
||||
for(size_t i = 0; i < nCorners; i++) {
|
||||
Point2f corner = _charucoCorners.getMat().at<Point2f>((int)i);
|
||||
// draw first corner mark
|
||||
rectangle(_image, corner - Point2f(3, 3), corner + Point2f(3, 3), cornerColor, 1, LINE_AA);
|
||||
// draw ID
|
||||
if(!_charucoIds.empty()) {
|
||||
int id = _charucoIds.getMat().at<int>((int)i);
|
||||
stringstream s;
|
||||
s << "id=" << id;
|
||||
putText(_image, s.str(), corner + Point2f(5, -5), FONT_HERSHEY_SIMPLEX, 0.5,
|
||||
cornerColor, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void drawDetectedDiamonds(InputOutputArray _image, InputArrayOfArrays _corners, InputArray _ids, Scalar borderColor) {
|
||||
CV_Assert(_image.getMat().total() != 0 &&
|
||||
(_image.getMat().channels() == 1 || _image.getMat().channels() == 3));
|
||||
CV_Assert((_corners.total() == _ids.total()) || _ids.total() == 0);
|
||||
|
||||
// calculate colors
|
||||
Scalar textColor, cornerColor;
|
||||
textColor = cornerColor = borderColor;
|
||||
swap(textColor.val[0], textColor.val[1]); // text color just sawp G and R
|
||||
swap(cornerColor.val[1], cornerColor.val[2]); // corner color just sawp G and B
|
||||
|
||||
int nMarkers = (int)_corners.total();
|
||||
for(int i = 0; i < nMarkers; i++) {
|
||||
Mat currentMarker = _corners.getMat(i);
|
||||
CV_Assert(currentMarker.total() == 4 && currentMarker.type() == CV_32FC2);
|
||||
|
||||
// draw marker sides
|
||||
for(int j = 0; j < 4; j++) {
|
||||
Point2f p0, p1;
|
||||
p0 = currentMarker.at< Point2f >(j);
|
||||
p1 = currentMarker.at< Point2f >((j + 1) % 4);
|
||||
line(_image, p0, p1, borderColor, 1);
|
||||
}
|
||||
|
||||
// draw first corner mark
|
||||
rectangle(_image, currentMarker.at< Point2f >(0) - Point2f(3, 3),
|
||||
currentMarker.at< Point2f >(0) + Point2f(3, 3), cornerColor, 1, LINE_AA);
|
||||
|
||||
// draw id composed by four numbers
|
||||
if(_ids.total() != 0) {
|
||||
Point2f cent(0, 0);
|
||||
for(int p = 0; p < 4; p++)
|
||||
cent += currentMarker.at< Point2f >(p);
|
||||
cent = cent / 4.;
|
||||
stringstream s;
|
||||
s << "id=" << _ids.getMat().at< Vec4i >(i);
|
||||
putText(_image, s.str(), cent, FONT_HERSHEY_SIMPLEX, 0.5, textColor, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@
|
||||
#include "precomp.hpp"
|
||||
#include "opencv2/objdetect.hpp"
|
||||
#include "opencv2/3d.hpp"
|
||||
#include <opencv2/core/utils/logger.hpp>
|
||||
|
||||
#ifdef HAVE_QUIRC
|
||||
#include "quirc.h"
|
||||
@@ -15,7 +16,6 @@
|
||||
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <queue>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
@@ -23,6 +23,7 @@
|
||||
namespace cv
|
||||
{
|
||||
using std::vector;
|
||||
using std::pair;
|
||||
|
||||
static bool checkQRInputImage(InputArray img, Mat& gray)
|
||||
{
|
||||
@@ -136,7 +137,7 @@ void QRDetect::init(const Mat& src, double eps_vertical_, double eps_horizontal_
|
||||
const int width = cvRound(src.size().width * coeff_expansion);
|
||||
const int height = cvRound(src.size().height * coeff_expansion);
|
||||
Size new_size(width, height);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT);
|
||||
}
|
||||
else if (min_side > 512.0)
|
||||
{
|
||||
@@ -524,7 +525,7 @@ bool QRDetect::localization()
|
||||
const int height = cvRound(bin_barcode.size().height * coeff_expansion);
|
||||
Size new_size(width, height);
|
||||
Mat intermediate;
|
||||
resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR);
|
||||
resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR_EXACT);
|
||||
bin_barcode = intermediate.clone();
|
||||
for (size_t i = 0; i < localization_points.size(); i++)
|
||||
{
|
||||
@@ -537,7 +538,7 @@ bool QRDetect::localization()
|
||||
const int height = cvRound(bin_barcode.size().height / coeff_expansion);
|
||||
Size new_size(width, height);
|
||||
Mat intermediate;
|
||||
resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR);
|
||||
resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR_EXACT);
|
||||
bin_barcode = intermediate.clone();
|
||||
for (size_t i = 0; i < localization_points.size(); i++)
|
||||
{
|
||||
@@ -948,6 +949,7 @@ vector<Point2f> QRDetect::getQuadrilateral(vector<Point2f> angle_list)
|
||||
return result_angle_list;
|
||||
}
|
||||
|
||||
|
||||
struct QRCodeDetector::Impl
|
||||
{
|
||||
public:
|
||||
@@ -955,9 +957,13 @@ public:
|
||||
~Impl() {}
|
||||
|
||||
double epsX, epsY;
|
||||
vector<vector<Point2f>> alignmentMarkers;
|
||||
vector<Point2f> updateQrCorners;
|
||||
bool useAlignmentMarkers = true;
|
||||
};
|
||||
|
||||
QRCodeDetector::QRCodeDetector() : p(new Impl) {}
|
||||
|
||||
QRCodeDetector::~QRCodeDetector() {}
|
||||
|
||||
void QRCodeDetector::setEpsX(double epsX) { p->epsX = epsX; }
|
||||
@@ -981,6 +987,7 @@ bool QRCodeDetector::detect(InputArray in, OutputArray points) const
|
||||
class QRDecode
|
||||
{
|
||||
public:
|
||||
QRDecode(bool useAlignmentMarkers);
|
||||
void init(const Mat &src, const vector<Point2f> &points);
|
||||
Mat getIntermediateBarcode() { return intermediate; }
|
||||
Mat getStraightBarcode() { return straight; }
|
||||
@@ -988,9 +995,23 @@ public:
|
||||
std::string getDecodeInformation() { return result_info; }
|
||||
bool straightDecodingProcess();
|
||||
bool curvedDecodingProcess();
|
||||
vector<Point2f> alignment_coords;
|
||||
float coeff_expansion = 1.f;
|
||||
vector<Point2f> getOriginalPoints() {return original_points;}
|
||||
bool useAlignmentMarkers;
|
||||
protected:
|
||||
bool updatePerspective();
|
||||
double getNumModules();
|
||||
Mat getHomography() {
|
||||
CV_TRACE_FUNCTION();
|
||||
vector<Point2f> perspective_points = {{0.f, 0.f}, {test_perspective_size, 0.f},
|
||||
{test_perspective_size, test_perspective_size},
|
||||
{0.f, test_perspective_size}};
|
||||
vector<Point2f> pts = original_points;
|
||||
return findHomography(pts, perspective_points);
|
||||
}
|
||||
bool updatePerspective(const Mat& H);
|
||||
bool versionDefinition();
|
||||
void detectAlignment();
|
||||
bool samplingForVersion();
|
||||
bool decodingProcess();
|
||||
inline double pointPosition(Point2f a, Point2f b , Point2f c);
|
||||
@@ -1019,6 +1040,7 @@ protected:
|
||||
const static int NUM_SIDES = 2;
|
||||
Mat original, bin_barcode, no_border_intermediate, intermediate, straight, curved_to_straight, test_image;
|
||||
vector<Point2f> original_points;
|
||||
Mat homography;
|
||||
vector<Point2f> original_curved_points;
|
||||
vector<Point> qrcode_locations;
|
||||
vector<std::pair<size_t, Point> > closest_points;
|
||||
@@ -1070,6 +1092,7 @@ float static getMinSideLen(const vector<Point2f> &points) {
|
||||
return static_cast<float>(res);
|
||||
}
|
||||
|
||||
|
||||
void QRDecode::init(const Mat &src, const vector<Point2f> &points)
|
||||
{
|
||||
CV_TRACE_FUNCTION();
|
||||
@@ -2180,6 +2203,8 @@ bool QRDecode::straightenQRCodeInParts()
|
||||
pts.push_back(center_point);
|
||||
|
||||
Mat H = findHomography(pts, perspective_points);
|
||||
if (H.empty())
|
||||
return false;
|
||||
Mat temp_intermediate(temporary_size, CV_8UC1);
|
||||
warpPerspective(test_mask, temp_intermediate, H, temporary_size, INTER_NEAREST);
|
||||
perspective_result += temp_intermediate;
|
||||
@@ -2212,6 +2237,8 @@ bool QRDecode::straightenQRCodeInParts()
|
||||
perspective_straight_points.push_back(Point2f(perspective_curved_size * 0.5f, perspective_curved_size * 0.5f));
|
||||
|
||||
Mat H = findHomography(original_curved_points, perspective_straight_points);
|
||||
if (H.empty())
|
||||
return false;
|
||||
warpPerspective(inversion, temp_result, H, temporary_size, INTER_NEAREST, BORDER_REPLICATE);
|
||||
|
||||
no_border_intermediate = temp_result(Range(1, temp_result.rows), Range(1, temp_result.cols));
|
||||
@@ -2250,33 +2277,124 @@ bool QRDecode::preparingCurvedQRCodes()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QRDecode::updatePerspective()
|
||||
{
|
||||
CV_TRACE_FUNCTION();
|
||||
const Point2f centerPt = intersectionLines(original_points[0], original_points[2],
|
||||
original_points[1], original_points[3]);
|
||||
if (cvIsNaN(centerPt.x) || cvIsNaN(centerPt.y))
|
||||
/**
|
||||
* @param finderPattern 4 points of finder pattern markers, calculated by findPatternsVerticesPoints()
|
||||
* @return true if the pattern has the correct side lengths
|
||||
*/
|
||||
static inline bool checkFinderPatternByAspect(const vector<Point> &finderPattern) {
|
||||
if (finderPattern.size() != 4ull)
|
||||
return false;
|
||||
float sidesLen[4];
|
||||
for (size_t i = 0; i < finderPattern.size(); i++) {
|
||||
sidesLen[i] = (sqrt(normL2Sqr<float>(Point2f(finderPattern[i] - finderPattern[(i+1ull)%finderPattern.size()]))));
|
||||
}
|
||||
const float maxSide = max(max(sidesLen[0], sidesLen[1]), max(sidesLen[2], sidesLen[3]));
|
||||
const float minSide = min(min(sidesLen[0], sidesLen[1]), min(sidesLen[2], sidesLen[3]));
|
||||
|
||||
const Size temporary_size(cvRound(test_perspective_size), cvRound(test_perspective_size));
|
||||
const float patternMaxRelativeLen = .3f;
|
||||
if (1.f - minSide / maxSide > patternMaxRelativeLen)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
vector<Point2f> perspective_points;
|
||||
perspective_points.push_back(Point2f(0.f, 0.f));
|
||||
perspective_points.push_back(Point2f(test_perspective_size, 0.f));
|
||||
/**
|
||||
* @param finderPattern - 4 points of finder pattern markers, calculated by findPatternsVerticesPoints()
|
||||
* @param cornerPointsQR - 4 corner points of QR code
|
||||
* @return pair<int, int> first - the index in points of finderPattern closest to the corner of the QR code,
|
||||
* second - the index in points of cornerPointsQR closest to the corner of finderPattern
|
||||
*
|
||||
* This function matches finder patterns to the corners of the QR code. Points of finder pattern calculated by
|
||||
* findPatternsVerticesPoints() may be erroneous, so they are checked.
|
||||
*/
|
||||
static inline std::pair<int, int> matchPatternPoints(const vector<Point> &finderPattern,
|
||||
const vector<Point2f>& cornerPointsQR) {
|
||||
if (!checkFinderPatternByAspect(finderPattern))
|
||||
return std::make_pair(-1, -1);
|
||||
|
||||
perspective_points.push_back(Point2f(test_perspective_size, test_perspective_size));
|
||||
perspective_points.push_back(Point2f(0.f, test_perspective_size));
|
||||
float distanceToOrig = normL2Sqr<float>(Point2f(finderPattern[0]) - cornerPointsQR[0]);
|
||||
int closestFinderPatternV = 0;
|
||||
int closetOriginalV = 0;
|
||||
|
||||
perspective_points.push_back(Point2f(test_perspective_size * 0.5f, test_perspective_size * 0.5f));
|
||||
for (size_t i = 0ull; i < finderPattern.size(); i++) {
|
||||
for (size_t j = 0ull; j < cornerPointsQR.size(); j++) {
|
||||
const float tmp = normL2Sqr<float>(Point2f(finderPattern[i]) - cornerPointsQR[j]);
|
||||
if (tmp < distanceToOrig) {
|
||||
distanceToOrig = tmp;
|
||||
closestFinderPatternV = (int)i;
|
||||
closetOriginalV = (int)j;
|
||||
}
|
||||
}
|
||||
}
|
||||
distanceToOrig = sqrt(distanceToOrig);
|
||||
|
||||
vector<Point2f> pts = original_points;
|
||||
pts.push_back(centerPt);
|
||||
// check that the distance from the QR pattern to the corners of the QR code is small
|
||||
const float originalQrSide = sqrt(normL2Sqr<float>(cornerPointsQR[0] - cornerPointsQR[1]))*0.5f +
|
||||
sqrt(normL2Sqr<float>(cornerPointsQR[0] - cornerPointsQR[3]))*0.5f;
|
||||
const float maxRelativeDistance = .1f;
|
||||
|
||||
Mat H = findHomography(pts, perspective_points);
|
||||
Mat bin_original;
|
||||
adaptiveThreshold(original, bin_original, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 83, 2);
|
||||
if (distanceToOrig/originalQrSide > maxRelativeDistance)
|
||||
return std::make_pair(-1, -1);
|
||||
return std::make_pair(closestFinderPatternV, closetOriginalV);
|
||||
}
|
||||
|
||||
double QRDecode::getNumModules() {
|
||||
vector<vector<Point>> finderPatterns;
|
||||
double numModulesX = 0., numModulesY = 0.;
|
||||
if (findPatternsVerticesPoints(finderPatterns)) {
|
||||
double pattern_distance[4] = {0.,0.,0.,0.};
|
||||
for (auto& pattern : finderPatterns) {
|
||||
auto indexes = matchPatternPoints(pattern, original_points);
|
||||
if (indexes == std::make_pair(-1, -1))
|
||||
return 0.;
|
||||
Point2f vf[4] = {pattern[indexes.first % 4], pattern[(1+indexes.first) % 4],
|
||||
pattern[(2+indexes.first) % 4], pattern[(3+indexes.first) % 4]};
|
||||
for (int i = 1; i < 4; i++) {
|
||||
pattern_distance[indexes.second] += (norm(vf[i] - vf[i-1]));
|
||||
}
|
||||
pattern_distance[indexes.second] += norm(vf[3] - vf[0]);
|
||||
pattern_distance[indexes.second] /= 4.;
|
||||
}
|
||||
const double moduleSizeX = (pattern_distance[0] + pattern_distance[1])/(2.*7.);
|
||||
const double moduleSizeY = (pattern_distance[0] + pattern_distance[3])/(2.*7.);
|
||||
numModulesX = norm(original_points[1] - original_points[0])/moduleSizeX;
|
||||
numModulesY = norm(original_points[3] - original_points[0])/moduleSizeY;
|
||||
}
|
||||
return (numModulesX + numModulesY)/2.;
|
||||
}
|
||||
|
||||
// use code from https://stackoverflow.com/questions/13238704/calculating-the-position-of-qr-code-alignment-patterns
|
||||
static inline vector<pair<int, int>> getAlignmentCoordinates(int version) {
|
||||
if (version <= 1) return {};
|
||||
int intervals = (version / 7) + 1; // Number of gaps between alignment patterns
|
||||
int distance = 4 * version + 4; // Distance between first and last alignment pattern
|
||||
int step = cvRound((double)distance / (double)intervals); // Round equal spacing to nearest integer
|
||||
step += step & 0b1; // Round step to next even number
|
||||
vector<int> coordinates((size_t)intervals + 1ull);
|
||||
coordinates[0] = 6; // First coordinate is always 6 (can't be calculated with step)
|
||||
for (int i = 1; i <= intervals; i++) {
|
||||
coordinates[i] = (6 + distance - step * (intervals - i)); // Start right/bottom and go left/up by step*k
|
||||
}
|
||||
if (version >= 7) {
|
||||
return {std::make_pair(coordinates.back(), coordinates.back()),
|
||||
std::make_pair(coordinates.back(), coordinates[coordinates.size()-2]),
|
||||
std::make_pair(coordinates[coordinates.size()-2], coordinates.back()),
|
||||
std::make_pair(coordinates[coordinates.size()-2], coordinates[coordinates.size()-2]),
|
||||
std::make_pair(coordinates[0], coordinates[1]),
|
||||
std::make_pair(coordinates[1], coordinates[0]),
|
||||
};
|
||||
}
|
||||
return {std::make_pair(coordinates.back(), coordinates.back())};
|
||||
}
|
||||
|
||||
|
||||
bool QRDecode::updatePerspective(const Mat& H)
|
||||
{
|
||||
if (H.empty())
|
||||
return false;
|
||||
homography = H;
|
||||
Mat temp_intermediate;
|
||||
warpPerspective(bin_original, temp_intermediate, H, temporary_size, INTER_NEAREST);
|
||||
const Size temporary_size(cvRound(test_perspective_size), cvRound(test_perspective_size));
|
||||
warpPerspective(bin_barcode, temp_intermediate, H, temporary_size, INTER_NEAREST);
|
||||
no_border_intermediate = temp_intermediate(Range(1, temp_intermediate.rows), Range(1, temp_intermediate.cols));
|
||||
|
||||
const int border = cvRound(0.1 * test_perspective_size);
|
||||
@@ -2285,7 +2403,7 @@ bool QRDecode::updatePerspective()
|
||||
return true;
|
||||
}
|
||||
|
||||
inline Point computeOffset(const vector<Point>& v)
|
||||
static inline Point computeOffset(const vector<Point>& v)
|
||||
{
|
||||
// compute the width/height of convex hull
|
||||
Rect areaBox = boundingRect(v);
|
||||
@@ -2299,9 +2417,80 @@ inline Point computeOffset(const vector<Point>& v)
|
||||
return offset;
|
||||
}
|
||||
|
||||
// QR code with version 7 or higher has a special 18 bit version number code.
|
||||
// @return std::pair<double, int> first - distance to estimatedVersion, second - version
|
||||
/**
|
||||
* @param numModules - estimated numModules
|
||||
* @param estimatedVersion
|
||||
* @return pair<double, int>, first - Hamming distance to 18 bit code, second - closest version
|
||||
*
|
||||
* QR code with version 7 or higher has a special 18 bit version number code:
|
||||
* https://www.thonky.com/qr-code-tutorial/format-version-information
|
||||
*/
|
||||
static inline std::pair<double, int> getVersionByCode(double numModules, Mat qr, int estimatedVersion) {
|
||||
const double moduleSize = qr.rows / numModules;
|
||||
Point2d startVersionInfo1 = Point2d((numModules-8.-3.)*moduleSize, 0.);
|
||||
Point2d endVersionInfo1 = Point2d((numModules-8.)*moduleSize, moduleSize*6.);
|
||||
Point2d startVersionInfo2 = Point2d(0., (numModules-8.-3.)*moduleSize);
|
||||
Point2d endVersionInfo2 = Point2d(moduleSize*6., (numModules-8.)*moduleSize);
|
||||
Mat v1(qr, Rect2d(startVersionInfo1, endVersionInfo1));
|
||||
Mat v2(qr, Rect2d(startVersionInfo2, endVersionInfo2));
|
||||
const double thresh = 127.;
|
||||
resize(v1, v1, Size(3, 6), 0., 0., INTER_AREA);
|
||||
threshold(v1, v1, thresh, 255, THRESH_BINARY);
|
||||
resize(v2, v2, Size(6, 3), 0., 0., INTER_AREA);
|
||||
threshold(v2, v2, thresh, 255, THRESH_BINARY);
|
||||
|
||||
Mat version1, version2;
|
||||
// convert version1 (top right version information block) and
|
||||
// version2 (bottom left version information block) to version table format
|
||||
// https://www.thonky.com/qr-code-tutorial/format-version-tables
|
||||
rotate((255-v1)/255, version1, ROTATE_180), rotate(((255-v2)/255).t(), version2, ROTATE_180);
|
||||
|
||||
static uint8_t versionCodes[][18] = {{0,0,0,1,1,1,1,1,0,0,1,0,0,1,0,1,0,0},{0,0,1,0,0,0,0,1,0,1,1,0,1,1,1,1,0,0},
|
||||
{0,0,1,0,0,1,1,0,1,0,1,0,0,1,1,0,0,1},{0,0,1,0,1,0,0,1,0,0,1,1,0,1,0,0,1,1},
|
||||
{0,0,1,0,1,1,1,0,1,1,1,1,1,1,0,1,1,0},{0,0,1,1,0,0,0,1,1,1,0,1,1,0,0,0,1,0},
|
||||
{0,0,1,1,0,1,1,0,0,0,0,1,0,0,0,1,1,1},{0,0,1,1,1,0,0,1,1,0,0,0,0,0,1,1,0,1},
|
||||
{0,0,1,1,1,1,1,0,0,1,0,0,1,0,1,0,0,0},{0,1,0,0,0,0,1,0,1,1,0,1,1,1,1,0,0,0},
|
||||
{0,1,0,0,0,1,0,1,0,0,0,1,0,1,1,1,0,1},{0,1,0,0,1,0,1,0,1,0,0,0,0,1,0,1,1,1},
|
||||
{0,1,0,0,1,1,0,1,0,1,0,0,1,1,0,0,1,0},{0,1,0,1,0,0,1,0,0,1,1,0,1,0,0,1,1,0},
|
||||
{0,1,0,1,0,1,0,1,1,0,1,0,0,0,0,0,1,1},{0,1,0,1,1,0,1,0,0,0,1,1,0,0,1,0,0,1},
|
||||
{0,1,0,1,1,1,0,1,1,1,1,1,1,0,1,1,0,0},{0,1,1,0,0,0,1,1,1,0,1,1,0,0,0,1,0,0},
|
||||
{0,1,1,0,0,1,0,0,0,1,1,1,1,0,0,0,0,1},{0,1,1,0,1,0,1,1,1,1,1,0,1,0,1,0,1,1},
|
||||
{0,1,1,0,1,1,0,0,0,0,1,0,0,0,1,1,1,0},{0,1,1,1,0,0,1,1,0,0,0,0,0,1,1,0,1,0},
|
||||
{0,1,1,1,0,1,0,0,1,1,0,0,1,1,1,1,1,1},{0,1,1,1,1,0,1,1,0,1,0,1,1,1,0,1,0,1},
|
||||
{0,1,1,1,1,1,0,0,1,0,0,1,0,1,0,0,0,0},{1,0,0,0,0,0,1,0,0,1,1,1,0,1,0,1,0,1},
|
||||
{1,0,0,0,0,1,0,1,1,0,1,1,1,1,0,0,0,0},{1,0,0,0,1,0,1,0,0,0,1,0,1,1,1,0,1,0},
|
||||
{1,0,0,0,1,1,0,1,1,1,1,0,0,1,1,1,1,1},{1,0,0,1,0,0,1,0,1,1,0,0,0,0,1,0,1,1},
|
||||
{1,0,0,1,0,1,0,1,0,0,0,0,1,0,1,1,1,0},{1,0,0,1,1,0,1,0,1,0,0,1,1,0,0,1,0,0},
|
||||
{1,0,0,1,1,1,0,1,0,1,0,1,0,0,0,0,0,1},{1,0,1,0,0,0,1,1,0,0,0,1,1,0,1,0,0,1}
|
||||
};
|
||||
double minDist = 19.;
|
||||
int bestVersion = -1;
|
||||
const double penaltyFactor = 0.8;
|
||||
|
||||
for (int i = 0; i < (int)(sizeof(versionCodes)/sizeof(versionCodes[0])); i++) {
|
||||
Mat currVers(Size(3, 6), CV_8UC1, versionCodes[i]);
|
||||
// minimum hamming distance between version = 8
|
||||
double tmp = norm(currVers, version1, NORM_HAMMING) + penaltyFactor*abs(estimatedVersion-i-7);
|
||||
if (tmp < minDist) {
|
||||
bestVersion = i+7;
|
||||
minDist = tmp;
|
||||
}
|
||||
tmp = norm(currVers, version2, NORM_HAMMING) + penaltyFactor*abs(estimatedVersion-i-7);
|
||||
if (tmp < minDist) {
|
||||
bestVersion = i+7;
|
||||
minDist = tmp;
|
||||
}
|
||||
}
|
||||
return std::make_pair(minDist, bestVersion);
|
||||
}
|
||||
|
||||
bool QRDecode::versionDefinition()
|
||||
{
|
||||
CV_TRACE_FUNCTION();
|
||||
CV_LOG_DEBUG(NULL, "QR corners: " << original_points[0] << " " << original_points[1] << " " << original_points[2] <<
|
||||
" " << original_points[3]);
|
||||
LineIterator line_iter(intermediate, Point2f(0, 0), Point2f(test_perspective_size, test_perspective_size));
|
||||
Point black_point = Point(0, 0);
|
||||
for(int j = 0; j < line_iter.count; j++, ++line_iter)
|
||||
@@ -2359,12 +2548,120 @@ bool QRDecode::versionDefinition()
|
||||
transition_y++;
|
||||
}
|
||||
}
|
||||
version = saturate_cast<uint8_t>((std::min(transition_x, transition_y) - 1) * 0.25 - 1);
|
||||
if ( !( 0 < version && version <= 40 ) ) { return false; }
|
||||
|
||||
const int versionByTransition = saturate_cast<uint8_t>((std::min(transition_x, transition_y) - 1) * 0.25 - 1);
|
||||
const int numModulesByTransition = 21 + (versionByTransition - 1) * 4;
|
||||
|
||||
const double numModulesByFinderPattern = getNumModules();
|
||||
const double versionByFinderPattern = (numModulesByFinderPattern - 21.) * .25 + 1.;
|
||||
bool useFinderPattern = false;
|
||||
const double thresholdFinderPattern = 0.2;
|
||||
const double roundingError = abs(numModulesByFinderPattern - cvRound(numModulesByFinderPattern));
|
||||
|
||||
if (cvRound(versionByFinderPattern) >= 1 && versionByFinderPattern <= 6. &&
|
||||
transition_x != transition_y && roundingError < thresholdFinderPattern) {
|
||||
useFinderPattern = true;
|
||||
}
|
||||
|
||||
bool useCode = false;
|
||||
int versionByCode = 7;
|
||||
if (cvRound(versionByFinderPattern) >= 7 || versionByTransition >= 7) {
|
||||
vector<std::pair<double, int>> versionAndDistances;
|
||||
if (cvRound(versionByFinderPattern) >= 7) {
|
||||
versionAndDistances.push_back(getVersionByCode(numModulesByFinderPattern, no_border_intermediate,
|
||||
cvRound(versionByFinderPattern)));
|
||||
}
|
||||
if (versionByTransition >= 7) {
|
||||
versionAndDistances.push_back(getVersionByCode(numModulesByTransition, no_border_intermediate,
|
||||
versionByTransition));
|
||||
}
|
||||
const auto& bestVersion = min(versionAndDistances.front(), versionAndDistances.back());
|
||||
double distanceByCode = bestVersion.first;
|
||||
versionByCode = bestVersion.second;
|
||||
if (distanceByCode < 5.) {
|
||||
useCode = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (useCode) {
|
||||
CV_LOG_DEBUG(NULL, "Version type: useCode");
|
||||
version = (uint8_t)versionByCode;
|
||||
}
|
||||
else if (useFinderPattern ) {
|
||||
CV_LOG_DEBUG(NULL, "Version type: useFinderPattern");
|
||||
version = (uint8_t)cvRound(versionByFinderPattern);
|
||||
}
|
||||
else {
|
||||
CV_LOG_DEBUG(NULL, "Version type: useTransition");
|
||||
version = (uint8_t)versionByTransition;
|
||||
}
|
||||
version_size = 21 + (version - 1) * 4;
|
||||
if ( !(0 < version && version <= 40) ) { return false; }
|
||||
CV_LOG_DEBUG(NULL, "QR version: " << (int)version);
|
||||
return true;
|
||||
}
|
||||
|
||||
void QRDecode::detectAlignment() {
|
||||
vector<pair<int, int>> alignmentPositions = getAlignmentCoordinates(version);
|
||||
if (alignmentPositions.size() > 0) {
|
||||
vector<Point2f> perspective_points = {{0.f, 0.f}, {test_perspective_size, 0.f}, {0.f, test_perspective_size}};
|
||||
vector<Point2f> object_points = {original_points[0], original_points[1], original_points[3]};
|
||||
|
||||
// create alignment image
|
||||
static uint8_t alignmentMarker[25] = {
|
||||
0, 0, 0, 0, 0,
|
||||
0, 255, 255, 255, 0,
|
||||
0, 255, 0, 255, 0,
|
||||
0, 255, 255, 255, 0,
|
||||
0, 0, 0, 0, 0
|
||||
};
|
||||
Mat alignmentMarkerMat(5, 5, CV_8UC1, alignmentMarker);
|
||||
const float module_size = test_perspective_size / version_size;
|
||||
Mat resizedAlignmentMarker;
|
||||
resize(alignmentMarkerMat, resizedAlignmentMarker,
|
||||
Size(cvRound(module_size * 5.f), cvRound(module_size * 5.f)), 0, 0, INTER_AREA);
|
||||
const float module_offset = 1.9f;
|
||||
const float offset = (module_size * (5 + module_offset * 2)); // 5 modules in alignment marker, 2 x module_offset modules in offset
|
||||
for (const pair<int, int>& alignmentPos : alignmentPositions) {
|
||||
const float left_top_x = (module_size * (alignmentPos.first - 2.f - module_offset)); // add offset
|
||||
const float left_top_y = (module_size * (alignmentPos.second - 2.f - module_offset)); // add offset
|
||||
Mat subImage(no_border_intermediate, Rect(cvRound(left_top_x), cvRound(left_top_y), cvRound(offset), cvRound(offset)));
|
||||
Mat resTemplate;
|
||||
matchTemplate(subImage, resizedAlignmentMarker, resTemplate, TM_CCOEFF_NORMED);
|
||||
double minVal = 0., maxVal = 0.;
|
||||
Point minLoc, maxLoc, matchLoc;
|
||||
minMaxLoc(resTemplate, &minVal, &maxVal, &minLoc, &maxLoc);
|
||||
CV_LOG_DEBUG(NULL, "Alignment maxVal: " << maxVal);
|
||||
if (maxVal > 0.65) {
|
||||
const float templateOffset = static_cast<float>(resizedAlignmentMarker.size().width) / 2.f;
|
||||
Point2f alignmentCoord(Point2f(maxLoc.x + left_top_x + templateOffset, maxLoc.y + left_top_y + templateOffset));
|
||||
alignment_coords.push_back(alignmentCoord);
|
||||
perspectiveTransform(alignment_coords, alignment_coords, homography.inv());
|
||||
CV_LOG_DEBUG(NULL, "Alignment coords: " << alignment_coords);
|
||||
const float relativePosX = (alignmentPos.first + 0.5f) / version_size;
|
||||
const float relativePosY = (alignmentPos.second + 0.5f) / version_size;
|
||||
perspective_points.push_back({relativePosX * test_perspective_size, relativePosY * test_perspective_size});
|
||||
object_points.push_back(alignment_coords.back());
|
||||
}
|
||||
}
|
||||
if (object_points.size() > 3ull) {
|
||||
double ransacReprojThreshold = 10.;
|
||||
if (version == 2) { // in low version original_points[2] may be calculated more accurately using intersections method
|
||||
object_points.push_back(original_points[2]);
|
||||
ransacReprojThreshold = 5.; // set more strict ransacReprojThreshold
|
||||
perspective_points.push_back({test_perspective_size, test_perspective_size});
|
||||
}
|
||||
Mat H = findHomography(object_points, perspective_points, RANSAC, ransacReprojThreshold);
|
||||
if (H.empty())
|
||||
return;
|
||||
updatePerspective(H);
|
||||
vector<Point2f> newCorner2 = {{test_perspective_size, test_perspective_size}};
|
||||
perspectiveTransform(newCorner2, newCorner2, H.inv());
|
||||
original_points[2] = newCorner2.front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool QRDecode::samplingForVersion()
|
||||
{
|
||||
CV_TRACE_FUNCTION();
|
||||
@@ -2451,8 +2748,10 @@ bool QRDecode::decodingProcess()
|
||||
bool QRDecode::straightDecodingProcess()
|
||||
{
|
||||
#ifdef HAVE_QUIRC
|
||||
if (!updatePerspective()) { return false; }
|
||||
if (!updatePerspective(getHomography())) { return false; }
|
||||
if (!versionDefinition()) { return false; }
|
||||
if (useAlignmentMarkers)
|
||||
detectAlignment();
|
||||
if (!samplingForVersion()) { return false; }
|
||||
if (!decodingProcess()) { return false; }
|
||||
return true;
|
||||
@@ -2476,6 +2775,13 @@ bool QRDecode::curvedDecodingProcess()
|
||||
#endif
|
||||
}
|
||||
|
||||
QRDecode::QRDecode(bool _useAlignmentMarkers):
|
||||
useAlignmentMarkers(_useAlignmentMarkers),
|
||||
version(0),
|
||||
version_size(0),
|
||||
test_perspective_size(0.f)
|
||||
{}
|
||||
|
||||
std::string QRCodeDetector::decode(InputArray in, InputArray points,
|
||||
OutputArray straight_qrcode)
|
||||
{
|
||||
@@ -2488,7 +2794,7 @@ std::string QRCodeDetector::decode(InputArray in, InputArray points,
|
||||
CV_Assert(src_points.size() == 4);
|
||||
CV_CheckGT(contourArea(src_points), 0.0, "Invalid QR code source points");
|
||||
|
||||
QRDecode qrdec;
|
||||
QRDecode qrdec(p->useAlignmentMarkers);
|
||||
qrdec.init(inarr, src_points);
|
||||
bool ok = qrdec.straightDecodingProcess();
|
||||
|
||||
@@ -2501,7 +2807,10 @@ std::string QRCodeDetector::decode(InputArray in, InputArray points,
|
||||
{
|
||||
qrdec.getStraightBarcode().convertTo(straight_qrcode, CV_8UC1);
|
||||
}
|
||||
|
||||
if (ok && !decoded_info.empty()) {
|
||||
p->alignmentMarkers = {qrdec.alignment_coords};
|
||||
p->updateQrCorners = qrdec.getOriginalPoints();
|
||||
}
|
||||
return ok ? decoded_info : std::string();
|
||||
}
|
||||
|
||||
@@ -2517,7 +2826,7 @@ cv::String QRCodeDetector::decodeCurved(InputArray in, InputArray points,
|
||||
CV_Assert(src_points.size() == 4);
|
||||
CV_CheckGT(contourArea(src_points), 0.0, "Invalid QR code source points");
|
||||
|
||||
QRDecode qrdec;
|
||||
QRDecode qrdec(p->useAlignmentMarkers);
|
||||
qrdec.init(inarr, src_points);
|
||||
bool ok = qrdec.curvedDecodingProcess();
|
||||
|
||||
@@ -2742,7 +3051,7 @@ void QRDetectMulti::init(const Mat& src, double eps_vertical_, double eps_horizo
|
||||
const int width = cvRound(src.size().width * coeff_expansion);
|
||||
const int height = cvRound(src.size().height * coeff_expansion);
|
||||
Size new_size(width, height);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT);
|
||||
}
|
||||
else if (min_side > 512.0)
|
||||
{
|
||||
@@ -3099,7 +3408,7 @@ int QRDetectMulti::findNumberLocalizationPoints(vector<Point2f>& tmp_localizatio
|
||||
const int height = cvRound(bin_barcode.size().height * coeff_expansion);
|
||||
Size new_size(width, height);
|
||||
Mat intermediate;
|
||||
resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR);
|
||||
resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR_EXACT);
|
||||
bin_barcode = intermediate.clone();
|
||||
}
|
||||
else if (purpose == ZOOMING)
|
||||
@@ -3108,7 +3417,7 @@ int QRDetectMulti::findNumberLocalizationPoints(vector<Point2f>& tmp_localizatio
|
||||
const int height = cvRound(bin_barcode.size().height / coeff_expansion);
|
||||
Size new_size(width, height);
|
||||
Mat intermediate;
|
||||
resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR);
|
||||
resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR_EXACT);
|
||||
bin_barcode = intermediate.clone();
|
||||
}
|
||||
else
|
||||
@@ -3126,7 +3435,7 @@ void QRDetectMulti::findQRCodeContours(vector<Point2f>& tmp_localization_points,
|
||||
const int width = cvRound(bin_barcode.size().width);
|
||||
const int height = cvRound(bin_barcode.size().height);
|
||||
Size new_size(width, height);
|
||||
resize(bar, bar, new_size, 0, 0, INTER_LINEAR);
|
||||
resize(bar, bar, new_size, 0, 0, INTER_LINEAR_EXACT);
|
||||
blur(bar, blur_image, Size(3, 3));
|
||||
threshold(blur_image, threshold_output, 50, 255, THRESH_BINARY);
|
||||
|
||||
@@ -3552,15 +3861,15 @@ public:
|
||||
else if (std::min(inarr.size().width, inarr.size().height) > 512)
|
||||
{
|
||||
const int min_side = std::min(inarr.size().width, inarr.size().height);
|
||||
double coeff_expansion = min_side / 512;
|
||||
const int width = cvRound(inarr.size().width / coeff_expansion);
|
||||
const int height = cvRound(inarr.size().height / coeff_expansion);
|
||||
qrdec[i].coeff_expansion = min_side / 512.f;
|
||||
const int width = cvRound(inarr.size().width / qrdec[i].coeff_expansion);
|
||||
const int height = cvRound(inarr.size().height / qrdec[i].coeff_expansion);
|
||||
Size new_size(width, height);
|
||||
Mat inarr2;
|
||||
resize(inarr, inarr2, new_size, 0, 0, INTER_AREA);
|
||||
for (size_t j = 0; j < 4; j++)
|
||||
for (size_t j = 0ull; j < 4ull; j++)
|
||||
{
|
||||
src_points[i][j] /= static_cast<float>(coeff_expansion);
|
||||
src_points[i][j] /= qrdec[i].coeff_expansion;
|
||||
}
|
||||
qrdec[i].init(inarr2, src_points[i]);
|
||||
ok = qrdec[i].straightDecodingProcess();
|
||||
@@ -3568,6 +3877,8 @@ public:
|
||||
{
|
||||
decoded_info[i] = qrdec[i].getDecodeInformation();
|
||||
straight_barcode[i] = qrdec[i].getStraightBarcode();
|
||||
for (size_t j = 0ull; j < qrdec[i].alignment_coords.size(); j++)
|
||||
qrdec[i].alignment_coords[j] *= qrdec[i].coeff_expansion;
|
||||
}
|
||||
}
|
||||
if (decoded_info[i].empty())
|
||||
@@ -3608,7 +3919,7 @@ bool QRCodeDetector::decodeMulti(
|
||||
}
|
||||
}
|
||||
CV_Assert(src_points.size() > 0);
|
||||
vector<QRDecode> qrdec(src_points.size());
|
||||
vector<QRDecode> qrdec(src_points.size(), p->useAlignmentMarkers);
|
||||
vector<Mat> straight_barcode(src_points.size());
|
||||
vector<std::string> info(src_points.size());
|
||||
ParallelDecodeProcess parallelDecodeProcess(inarr, qrdec, info, straight_barcode, src_points);
|
||||
@@ -3639,6 +3950,13 @@ bool QRCodeDetector::decodeMulti(
|
||||
{
|
||||
decoded_info.push_back(info[i]);
|
||||
}
|
||||
p->alignmentMarkers.resize(src_points.size());
|
||||
p->updateQrCorners.resize(src_points.size()*4ull);
|
||||
for (size_t i = 0ull; i < src_points.size(); i++) {
|
||||
p->alignmentMarkers[i] = qrdec[i].alignment_coords;
|
||||
for (size_t j = 0ull; j < 4ull; j++)
|
||||
p->updateQrCorners[i*4ull+j] = qrdec[i].getOriginalPoints()[j] * qrdec[i].coeff_expansion;
|
||||
}
|
||||
if (!decoded_info.empty())
|
||||
return true;
|
||||
else
|
||||
@@ -3669,7 +3987,13 @@ bool QRCodeDetector::detectAndDecodeMulti(
|
||||
updatePointsResult(points_, points);
|
||||
decoded_info.clear();
|
||||
ok = decodeMulti(inarr, points, decoded_info, straight_qrcode);
|
||||
updatePointsResult(points_, p->updateQrCorners);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void QRCodeDetector::setUseAlignmentMarkers(bool useAlignmentMarkers) {
|
||||
p->useAlignmentMarkers = useAlignmentMarkers;
|
||||
}
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
// 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.
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
#include "test_aruco_utils.hpp"
|
||||
|
||||
namespace opencv_test {
|
||||
|
||||
vector<Point2f> getAxis(InputArray _cameraMatrix, InputArray _distCoeffs, InputArray _rvec,
|
||||
InputArray _tvec, float length, const float offset) {
|
||||
vector<Point3f> axis;
|
||||
axis.push_back(Point3f(offset, offset, 0.f));
|
||||
axis.push_back(Point3f(length+offset, offset, 0.f));
|
||||
axis.push_back(Point3f(offset, length+offset, 0.f));
|
||||
axis.push_back(Point3f(offset, offset, length));
|
||||
vector<Point2f> axis_to_img;
|
||||
projectPoints(axis, _rvec, _tvec, _cameraMatrix, _distCoeffs, axis_to_img);
|
||||
return axis_to_img;
|
||||
}
|
||||
|
||||
vector<Point2f> getMarkerById(int id, const vector<vector<Point2f> >& corners, const vector<int>& ids) {
|
||||
for (size_t i = 0ull; i < ids.size(); i++)
|
||||
if (ids[i] == id)
|
||||
return corners[i];
|
||||
return vector<Point2f>();
|
||||
}
|
||||
|
||||
void getSyntheticRT(double yaw, double pitch, double distance, Mat& rvec, Mat& tvec) {
|
||||
rvec = Mat::zeros(3, 1, CV_64FC1);
|
||||
tvec = Mat::zeros(3, 1, CV_64FC1);
|
||||
|
||||
// rotate "scene" in pitch axis (x-axis)
|
||||
Mat rotPitch(3, 1, CV_64FC1);
|
||||
rotPitch.at<double>(0) = -pitch;
|
||||
rotPitch.at<double>(1) = 0;
|
||||
rotPitch.at<double>(2) = 0;
|
||||
|
||||
// rotate "scene" in yaw (y-axis)
|
||||
Mat rotYaw(3, 1, CV_64FC1);
|
||||
rotYaw.at<double>(0) = 0;
|
||||
rotYaw.at<double>(1) = yaw;
|
||||
rotYaw.at<double>(2) = 0;
|
||||
|
||||
// compose both rotations
|
||||
composeRT(rotPitch, Mat(3, 1, CV_64FC1, Scalar::all(0)), rotYaw,
|
||||
Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, tvec);
|
||||
|
||||
// Tvec, just move in z (camera) direction the specific distance
|
||||
tvec.at<double>(0) = 0.;
|
||||
tvec.at<double>(1) = 0.;
|
||||
tvec.at<double>(2) = distance;
|
||||
}
|
||||
|
||||
void projectMarker(Mat& img, const aruco::Board& board, int markerIndex, Mat cameraMatrix, Mat rvec, Mat tvec,
|
||||
int markerBorder) {
|
||||
// canonical image
|
||||
Mat markerImg;
|
||||
const int markerSizePixels = 100;
|
||||
aruco::generateImageMarker(board.getDictionary(), board.getIds()[markerIndex], markerSizePixels, markerImg, markerBorder);
|
||||
|
||||
// projected corners
|
||||
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
|
||||
vector<Point2f> corners;
|
||||
|
||||
// get max coordinate of board
|
||||
Point3f maxCoord = board.getRightBottomCorner();
|
||||
// copy objPoints
|
||||
vector<Point3f> objPoints = board.getObjPoints()[markerIndex];
|
||||
// move the marker to the origin
|
||||
for (size_t i = 0; i < objPoints.size(); i++)
|
||||
objPoints[i] -= (maxCoord / 2.f);
|
||||
|
||||
projectPoints(objPoints, rvec, tvec, cameraMatrix, distCoeffs, corners);
|
||||
|
||||
// get perspective transform
|
||||
vector<Point2f> originalCorners;
|
||||
originalCorners.push_back(Point2f(0, 0));
|
||||
originalCorners.push_back(Point2f((float)markerSizePixels, 0));
|
||||
originalCorners.push_back(Point2f((float)markerSizePixels, (float)markerSizePixels));
|
||||
originalCorners.push_back(Point2f(0, (float)markerSizePixels));
|
||||
Mat transformation = getPerspectiveTransform(originalCorners, corners);
|
||||
|
||||
// apply transformation
|
||||
Mat aux;
|
||||
const char borderValue = 127;
|
||||
warpPerspective(markerImg, aux, transformation, img.size(), INTER_NEAREST, BORDER_CONSTANT,
|
||||
Scalar::all(borderValue));
|
||||
|
||||
// copy only not-border pixels
|
||||
for (int y = 0; y < aux.rows; y++) {
|
||||
for (int x = 0; x < aux.cols; x++) {
|
||||
if (aux.at< unsigned char >(y, x) == borderValue) continue;
|
||||
img.at< unsigned char >(y, x) = aux.at< unsigned char >(y, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Mat projectBoard(const aruco::GridBoard& board, Mat cameraMatrix, double yaw, double pitch, double distance,
|
||||
Size imageSize, int markerBorder) {
|
||||
Mat rvec, tvec;
|
||||
getSyntheticRT(yaw, pitch, distance, rvec, tvec);
|
||||
|
||||
Mat img = Mat(imageSize, CV_8UC1, Scalar::all(255));
|
||||
for (unsigned int index = 0; index < board.getIds().size(); index++)
|
||||
projectMarker(img, board, index, cameraMatrix, rvec, tvec, markerBorder);
|
||||
return img;
|
||||
}
|
||||
|
||||
/** Check if a set of 3d points are enough for calibration. Z coordinate is ignored.
|
||||
* Only axis parallel lines are considered */
|
||||
static bool _arePointsEnoughForPoseEstimation(const std::vector<Point3f> &points) {
|
||||
if(points.size() < 4) return false;
|
||||
|
||||
std::vector<double> sameXValue; // different x values in points
|
||||
std::vector<int> sameXCounter; // number of points with the x value in sameXValue
|
||||
for(unsigned int i = 0; i < points.size(); i++) {
|
||||
bool found = false;
|
||||
for(unsigned int j = 0; j < sameXValue.size(); j++) {
|
||||
if(sameXValue[j] == points[i].x) {
|
||||
found = true;
|
||||
sameXCounter[j]++;
|
||||
}
|
||||
}
|
||||
if(!found) {
|
||||
sameXValue.push_back(points[i].x);
|
||||
sameXCounter.push_back(1);
|
||||
}
|
||||
}
|
||||
|
||||
// count how many x values has more than 2 points
|
||||
int moreThan2 = 0;
|
||||
for(unsigned int i = 0; i < sameXCounter.size(); i++) {
|
||||
if(sameXCounter[i] >= 2) moreThan2++;
|
||||
}
|
||||
|
||||
// if we have more than 1 two xvalues with more than 2 points, calibration is ok
|
||||
if(moreThan2 > 1)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool getCharucoBoardPose(InputArray charucoCorners, InputArray charucoIds, const aruco::CharucoBoard &board,
|
||||
InputArray cameraMatrix, InputArray distCoeffs, InputOutputArray rvec, InputOutputArray tvec,
|
||||
bool useExtrinsicGuess) {
|
||||
CV_Assert((charucoCorners.getMat().total() == charucoIds.getMat().total()));
|
||||
if(charucoIds.getMat().total() < 4) return false; // need, at least, 4 corners
|
||||
|
||||
std::vector<Point3f> objPoints;
|
||||
objPoints.reserve(charucoIds.getMat().total());
|
||||
for(unsigned int i = 0; i < charucoIds.getMat().total(); i++) {
|
||||
int currId = charucoIds.getMat().at< int >(i);
|
||||
CV_Assert(currId >= 0 && currId < (int)board.getChessboardCorners().size());
|
||||
objPoints.push_back(board.getChessboardCorners()[currId]);
|
||||
}
|
||||
|
||||
// points need to be in different lines, check if detected points are enough
|
||||
if(!_arePointsEnoughForPoseEstimation(objPoints)) return false;
|
||||
|
||||
solvePnP(objPoints, charucoCorners, cameraMatrix, distCoeffs, rvec, tvec, useExtrinsicGuess);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return object points for the system centered in a middle (by default) or in a top left corner of single
|
||||
* marker, given the marker length
|
||||
*/
|
||||
static Mat _getSingleMarkerObjectPoints(float markerLength, bool use_aruco_ccw_center) {
|
||||
CV_Assert(markerLength > 0);
|
||||
Mat objPoints(4, 1, CV_32FC3);
|
||||
// set coordinate system in the top-left corner of the marker, with Z pointing out
|
||||
if (use_aruco_ccw_center) {
|
||||
objPoints.ptr<Vec3f>(0)[0] = Vec3f(-markerLength/2.f, markerLength/2.f, 0);
|
||||
objPoints.ptr<Vec3f>(0)[1] = Vec3f(markerLength/2.f, markerLength/2.f, 0);
|
||||
objPoints.ptr<Vec3f>(0)[2] = Vec3f(markerLength/2.f, -markerLength/2.f, 0);
|
||||
objPoints.ptr<Vec3f>(0)[3] = Vec3f(-markerLength/2.f, -markerLength/2.f, 0);
|
||||
}
|
||||
else {
|
||||
objPoints.ptr<Vec3f>(0)[0] = Vec3f(0.f, 0.f, 0);
|
||||
objPoints.ptr<Vec3f>(0)[1] = Vec3f(markerLength, 0.f, 0);
|
||||
objPoints.ptr<Vec3f>(0)[2] = Vec3f(markerLength, markerLength, 0);
|
||||
objPoints.ptr<Vec3f>(0)[3] = Vec3f(0.f, markerLength, 0);
|
||||
}
|
||||
return objPoints;
|
||||
}
|
||||
|
||||
void getMarkersPoses(InputArrayOfArrays corners, float markerLength, InputArray cameraMatrix, InputArray distCoeffs,
|
||||
OutputArray _rvecs, OutputArray _tvecs, OutputArray objPoints,
|
||||
bool use_aruco_ccw_center, SolvePnPMethod solvePnPMethod) {
|
||||
CV_Assert(markerLength > 0);
|
||||
Mat markerObjPoints = _getSingleMarkerObjectPoints(markerLength, use_aruco_ccw_center);
|
||||
int nMarkers = (int)corners.total();
|
||||
_rvecs.create(nMarkers, 1, CV_64FC3);
|
||||
_tvecs.create(nMarkers, 1, CV_64FC3);
|
||||
|
||||
Mat rvecs = _rvecs.getMat(), tvecs = _tvecs.getMat();
|
||||
for (int i = 0; i < nMarkers; i++)
|
||||
solvePnP(markerObjPoints, corners.getMat(i), cameraMatrix, distCoeffs, rvecs.at<Vec3d>(i), tvecs.at<Vec3d>(i),
|
||||
false, solvePnPMethod);
|
||||
|
||||
if(objPoints.needed())
|
||||
markerObjPoints.convertTo(objPoints, -1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// 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.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "opencv2/3d.hpp"
|
||||
|
||||
namespace opencv_test {
|
||||
|
||||
static inline double deg2rad(double deg) { return deg * CV_PI / 180.; }
|
||||
|
||||
vector<Point2f> getAxis(InputArray _cameraMatrix, InputArray _distCoeffs, InputArray _rvec, InputArray _tvec,
|
||||
float length, const float offset = 0.f);
|
||||
|
||||
vector<Point2f> getMarkerById(int id, const vector<vector<Point2f> >& corners, const vector<int>& ids);
|
||||
|
||||
/**
|
||||
* @brief Get rvec and tvec from yaw, pitch and distance
|
||||
*/
|
||||
void getSyntheticRT(double yaw, double pitch, double distance, Mat& rvec, Mat& tvec);
|
||||
|
||||
/**
|
||||
* @brief Project a synthetic marker
|
||||
*/
|
||||
void projectMarker(Mat& img, const aruco::Board& board, int markerIndex, Mat cameraMatrix, Mat rvec, Mat tvec,
|
||||
int markerBorder);
|
||||
|
||||
/**
|
||||
* @brief Get a synthetic image of GridBoard in perspective
|
||||
*/
|
||||
Mat projectBoard(const aruco::GridBoard& board, Mat cameraMatrix, double yaw, double pitch, double distance,
|
||||
Size imageSize, int markerBorder);
|
||||
|
||||
bool getCharucoBoardPose(InputArray charucoCorners, InputArray charucoIds, const aruco::CharucoBoard &board,
|
||||
InputArray cameraMatrix, InputArray distCoeffs, InputOutputArray rvec,
|
||||
InputOutputArray tvec, bool useExtrinsicGuess = false);
|
||||
|
||||
void getMarkersPoses(InputArrayOfArrays corners, float markerLength, InputArray cameraMatrix, InputArray distCoeffs,
|
||||
OutputArray _rvecs, OutputArray _tvecs, OutputArray objPoints = noArray(),
|
||||
bool use_aruco_ccw_center = true, SolvePnPMethod solvePnPMethod = SolvePnPMethod::SOLVEPNP_ITERATIVE);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,704 @@
|
||||
// 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.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "opencv2/objdetect/aruco_detector.hpp"
|
||||
#include "opencv2/3d.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
/**
|
||||
* @brief Draw 2D synthetic markers and detect them
|
||||
*/
|
||||
class CV_ArucoDetectionSimple : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_ArucoDetectionSimple();
|
||||
|
||||
protected:
|
||||
void run(int);
|
||||
};
|
||||
|
||||
|
||||
CV_ArucoDetectionSimple::CV_ArucoDetectionSimple() {}
|
||||
|
||||
|
||||
void CV_ArucoDetectionSimple::run(int) {
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_6X6_250));
|
||||
|
||||
// 20 images
|
||||
for(int i = 0; i < 20; i++) {
|
||||
|
||||
const int markerSidePixels = 100;
|
||||
int imageSize = markerSidePixels * 2 + 3 * (markerSidePixels / 2);
|
||||
|
||||
// draw synthetic image and store marker corners and ids
|
||||
vector<vector<Point2f> > groundTruthCorners;
|
||||
vector<int> groundTruthIds;
|
||||
Mat img = Mat(imageSize, imageSize, CV_8UC1, Scalar::all(255));
|
||||
for(int y = 0; y < 2; y++) {
|
||||
for(int x = 0; x < 2; x++) {
|
||||
Mat marker;
|
||||
int id = i * 4 + y * 2 + x;
|
||||
aruco::generateImageMarker(detector.getDictionary(), id, markerSidePixels, marker);
|
||||
Point2f firstCorner =
|
||||
Point2f(markerSidePixels / 2.f + x * (1.5f * markerSidePixels),
|
||||
markerSidePixels / 2.f + y * (1.5f * markerSidePixels));
|
||||
Mat aux = img.colRange((int)firstCorner.x, (int)firstCorner.x + markerSidePixels)
|
||||
.rowRange((int)firstCorner.y, (int)firstCorner.y + markerSidePixels);
|
||||
marker.copyTo(aux);
|
||||
groundTruthIds.push_back(id);
|
||||
groundTruthCorners.push_back(vector<Point2f>());
|
||||
groundTruthCorners.back().push_back(firstCorner);
|
||||
groundTruthCorners.back().push_back(firstCorner + Point2f(markerSidePixels - 1, 0));
|
||||
groundTruthCorners.back().push_back(
|
||||
firstCorner + Point2f(markerSidePixels - 1, markerSidePixels - 1));
|
||||
groundTruthCorners.back().push_back(firstCorner + Point2f(0, markerSidePixels - 1));
|
||||
}
|
||||
}
|
||||
if(i % 2 == 1) img.convertTo(img, CV_8UC3);
|
||||
|
||||
// detect markers
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
|
||||
detector.detectMarkers(img, corners, ids);
|
||||
|
||||
// check detection results
|
||||
for(unsigned int m = 0; m < groundTruthIds.size(); m++) {
|
||||
int idx = -1;
|
||||
for(unsigned int k = 0; k < ids.size(); k++) {
|
||||
if(groundTruthIds[m] == ids[k]) {
|
||||
idx = (int)k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(idx == -1) {
|
||||
ts->printf(cvtest::TS::LOG, "Marker not detected");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
for(int c = 0; c < 4; c++) {
|
||||
double dist = cv::norm(groundTruthCorners[m][c] - corners[idx][c]); // TODO cvtest
|
||||
if(dist > 0.001) {
|
||||
ts->printf(cvtest::TS::LOG, "Incorrect marker corners position");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static double deg2rad(double deg) { return deg * CV_PI / 180.; }
|
||||
|
||||
/**
|
||||
* @brief Get rvec and tvec from yaw, pitch and distance
|
||||
*/
|
||||
static void getSyntheticRT(double yaw, double pitch, double distance, Mat &rvec, Mat &tvec) {
|
||||
|
||||
rvec = Mat(3, 1, CV_64FC1);
|
||||
tvec = Mat(3, 1, CV_64FC1);
|
||||
|
||||
// Rvec
|
||||
// first put the Z axis aiming to -X (like the camera axis system)
|
||||
Mat rotZ(3, 1, CV_64FC1);
|
||||
rotZ.ptr<double>(0)[0] = 0;
|
||||
rotZ.ptr<double>(0)[1] = 0;
|
||||
rotZ.ptr<double>(0)[2] = -0.5 * CV_PI;
|
||||
|
||||
Mat rotX(3, 1, CV_64FC1);
|
||||
rotX.ptr<double>(0)[0] = 0.5 * CV_PI;
|
||||
rotX.ptr<double>(0)[1] = 0;
|
||||
rotX.ptr<double>(0)[2] = 0;
|
||||
|
||||
Mat camRvec, camTvec;
|
||||
composeRT(rotZ, Mat(3, 1, CV_64FC1, Scalar::all(0)), rotX, Mat(3, 1, CV_64FC1, Scalar::all(0)),
|
||||
camRvec, camTvec);
|
||||
|
||||
// now pitch and yaw angles
|
||||
Mat rotPitch(3, 1, CV_64FC1);
|
||||
rotPitch.ptr<double>(0)[0] = 0;
|
||||
rotPitch.ptr<double>(0)[1] = pitch;
|
||||
rotPitch.ptr<double>(0)[2] = 0;
|
||||
|
||||
Mat rotYaw(3, 1, CV_64FC1);
|
||||
rotYaw.ptr<double>(0)[0] = yaw;
|
||||
rotYaw.ptr<double>(0)[1] = 0;
|
||||
rotYaw.ptr<double>(0)[2] = 0;
|
||||
|
||||
composeRT(rotPitch, Mat(3, 1, CV_64FC1, Scalar::all(0)), rotYaw,
|
||||
Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, tvec);
|
||||
|
||||
// compose both rotations
|
||||
composeRT(camRvec, Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec,
|
||||
Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, tvec);
|
||||
|
||||
// Tvec, just move in z (camera) direction the specific distance
|
||||
tvec.ptr<double>(0)[0] = 0.;
|
||||
tvec.ptr<double>(0)[1] = 0.;
|
||||
tvec.ptr<double>(0)[2] = distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a synthetic image of a marker with perspective
|
||||
*/
|
||||
static Mat projectMarker(const aruco::Dictionary &dictionary, int id, Mat cameraMatrix, double yaw,
|
||||
double pitch, double distance, Size imageSize, int markerBorder,
|
||||
vector<Point2f> &corners, int encloseMarker=0) {
|
||||
|
||||
// canonical image
|
||||
Mat marker, markerImg;
|
||||
const int markerSizePixels = 100;
|
||||
|
||||
aruco::generateImageMarker(dictionary, id, markerSizePixels, marker, markerBorder);
|
||||
marker.copyTo(markerImg);
|
||||
|
||||
if(encloseMarker){ //to enclose the marker
|
||||
int enclose = int(marker.rows/4);
|
||||
markerImg = Mat::zeros(marker.rows+(2*enclose), marker.cols+(enclose*2), CV_8UC1);
|
||||
|
||||
Mat field= markerImg.rowRange(int(enclose), int(markerImg.rows-enclose))
|
||||
.colRange(int(0), int(markerImg.cols));
|
||||
field.setTo(255);
|
||||
field= markerImg.rowRange(int(0), int(markerImg.rows))
|
||||
.colRange(int(enclose), int(markerImg.cols-enclose));
|
||||
field.setTo(255);
|
||||
|
||||
field = markerImg(Rect(enclose,enclose,marker.rows,marker.cols));
|
||||
marker.copyTo(field);
|
||||
}
|
||||
|
||||
// get rvec and tvec for the perspective
|
||||
Mat rvec, tvec;
|
||||
getSyntheticRT(yaw, pitch, distance, rvec, tvec);
|
||||
|
||||
const float markerLength = 0.05f;
|
||||
vector<Point3f> markerObjPoints;
|
||||
markerObjPoints.push_back(Point3f(-markerLength / 2.f, +markerLength / 2.f, 0));
|
||||
markerObjPoints.push_back(markerObjPoints[0] + Point3f(markerLength, 0, 0));
|
||||
markerObjPoints.push_back(markerObjPoints[0] + Point3f(markerLength, -markerLength, 0));
|
||||
markerObjPoints.push_back(markerObjPoints[0] + Point3f(0, -markerLength, 0));
|
||||
|
||||
// project markers and draw them
|
||||
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
|
||||
projectPoints(markerObjPoints, rvec, tvec, cameraMatrix, distCoeffs, corners);
|
||||
|
||||
vector<Point2f> originalCorners;
|
||||
originalCorners.push_back(Point2f(0+float(encloseMarker*markerSizePixels/4), 0+float(encloseMarker*markerSizePixels/4)));
|
||||
originalCorners.push_back(originalCorners[0]+Point2f((float)markerSizePixels, 0));
|
||||
originalCorners.push_back(originalCorners[0]+Point2f((float)markerSizePixels, (float)markerSizePixels));
|
||||
originalCorners.push_back(originalCorners[0]+Point2f(0, (float)markerSizePixels));
|
||||
|
||||
Mat transformation = getPerspectiveTransform(originalCorners, corners);
|
||||
|
||||
Mat img(imageSize, CV_8UC1, Scalar::all(255));
|
||||
Mat aux;
|
||||
const char borderValue = 127;
|
||||
warpPerspective(markerImg, aux, transformation, imageSize, INTER_NEAREST, BORDER_CONSTANT,
|
||||
Scalar::all(borderValue));
|
||||
|
||||
// copy only not-border pixels
|
||||
for(int y = 0; y < aux.rows; y++) {
|
||||
for(int x = 0; x < aux.cols; x++) {
|
||||
if(aux.at<unsigned char>(y, x) == borderValue) continue;
|
||||
img.at<unsigned char>(y, x) = aux.at<unsigned char>(y, x);
|
||||
}
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
enum class ArucoAlgParams
|
||||
{
|
||||
USE_DEFAULT = 0,
|
||||
USE_APRILTAG=1, /// Detect marker candidates :: using AprilTag
|
||||
DETECT_INVERTED_MARKER, /// Check if there is a white marker
|
||||
USE_ARUCO3 /// Check if aruco3 should be used
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Draws markers in perspective and detect them
|
||||
*/
|
||||
class CV_ArucoDetectionPerspective : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_ArucoDetectionPerspective(ArucoAlgParams arucoAlgParam) : arucoAlgParams(arucoAlgParam) {}
|
||||
|
||||
protected:
|
||||
void run(int);
|
||||
ArucoAlgParams arucoAlgParams;
|
||||
};
|
||||
|
||||
|
||||
void CV_ArucoDetectionPerspective::run(int) {
|
||||
|
||||
int iter = 0;
|
||||
int szEnclosed = 0;
|
||||
Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1);
|
||||
Size imgSize(500, 500);
|
||||
cameraMatrix.at<double>(0, 0) = cameraMatrix.at<double>(1, 1) = 650;
|
||||
cameraMatrix.at<double>(0, 2) = imgSize.width / 2;
|
||||
cameraMatrix.at<double>(1, 2) = imgSize.height / 2;
|
||||
aruco::DetectorParameters params;
|
||||
params.minDistanceToBorder = 1;
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_6X6_250), params);
|
||||
|
||||
// detect from different positions
|
||||
for(double distance = 0.1; distance < 0.7; distance += 0.2) {
|
||||
for(int pitch = 0; pitch < 360; pitch += (distance == 0.1? 60:180)) {
|
||||
for(int yaw = 70; yaw <= 120; yaw += 40){
|
||||
int currentId = iter % 250;
|
||||
int markerBorder = iter % 2 + 1;
|
||||
iter++;
|
||||
vector<Point2f> groundTruthCorners;
|
||||
aruco::DetectorParameters detectorParameters = params;
|
||||
detectorParameters.markerBorderBits = markerBorder;
|
||||
|
||||
/// create synthetic image
|
||||
Mat img=
|
||||
projectMarker(detector.getDictionary(), currentId, cameraMatrix, deg2rad(yaw), deg2rad(pitch),
|
||||
distance, imgSize, markerBorder, groundTruthCorners, szEnclosed);
|
||||
// marker :: Inverted
|
||||
if(ArucoAlgParams::DETECT_INVERTED_MARKER == arucoAlgParams){
|
||||
img = ~img;
|
||||
detectorParameters.detectInvertedMarker = true;
|
||||
}
|
||||
|
||||
if(ArucoAlgParams::USE_APRILTAG == arucoAlgParams){
|
||||
detectorParameters.cornerRefinementMethod = aruco::CORNER_REFINE_APRILTAG;
|
||||
}
|
||||
|
||||
if (ArucoAlgParams::USE_ARUCO3 == arucoAlgParams) {
|
||||
detectorParameters.useAruco3Detection = true;
|
||||
detectorParameters.cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX;
|
||||
}
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
|
||||
// detect markers
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
detector.detectMarkers(img, corners, ids);
|
||||
|
||||
// check results
|
||||
if(ids.size() != 1 || (ids.size() == 1 && ids[0] != currentId)) {
|
||||
if(ids.size() != 1)
|
||||
ts->printf(cvtest::TS::LOG, "Incorrect number of detected markers");
|
||||
else
|
||||
ts->printf(cvtest::TS::LOG, "Incorrect marker id");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
for(int c = 0; c < 4; c++) {
|
||||
double dist = cv::norm(groundTruthCorners[c] - corners[0][c]); // TODO cvtest
|
||||
if(dist > 5) {
|
||||
ts->printf(cvtest::TS::LOG, "Incorrect marker corners position");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// change the state :: to detect an enclosed inverted marker
|
||||
if(ArucoAlgParams::DETECT_INVERTED_MARKER == arucoAlgParams && distance == 0.1){
|
||||
distance -= 0.1;
|
||||
szEnclosed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Check max and min size in marker detection parameters
|
||||
*/
|
||||
class CV_ArucoDetectionMarkerSize : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_ArucoDetectionMarkerSize();
|
||||
|
||||
protected:
|
||||
void run(int);
|
||||
};
|
||||
|
||||
|
||||
CV_ArucoDetectionMarkerSize::CV_ArucoDetectionMarkerSize() {}
|
||||
|
||||
|
||||
void CV_ArucoDetectionMarkerSize::run(int) {
|
||||
aruco::DetectorParameters params;
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_6X6_250), params);
|
||||
int markerSide = 20;
|
||||
int imageSize = 200;
|
||||
|
||||
// 10 cases
|
||||
for(int i = 0; i < 10; i++) {
|
||||
Mat marker;
|
||||
int id = 10 + i * 20;
|
||||
|
||||
// create synthetic image
|
||||
Mat img = Mat(imageSize, imageSize, CV_8UC1, Scalar::all(255));
|
||||
aruco::generateImageMarker(detector.getDictionary(), id, markerSide, marker);
|
||||
Mat aux = img.colRange(30, 30 + markerSide).rowRange(50, 50 + markerSide);
|
||||
marker.copyTo(aux);
|
||||
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
|
||||
// set a invalid minMarkerPerimeterRate
|
||||
aruco::DetectorParameters detectorParameters = params;
|
||||
detectorParameters.minMarkerPerimeterRate = min(4., (4. * markerSide) / float(imageSize) + 0.1);
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
detector.detectMarkers(img, corners, ids);
|
||||
if(corners.size() != 0) {
|
||||
ts->printf(cvtest::TS::LOG, "Error in DetectorParameters::minMarkerPerimeterRate");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
return;
|
||||
}
|
||||
|
||||
// set an valid minMarkerPerimeterRate
|
||||
detectorParameters = params;
|
||||
detectorParameters.minMarkerPerimeterRate = max(0., (4. * markerSide) / float(imageSize) - 0.1);
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
detector.detectMarkers(img, corners, ids);
|
||||
if(corners.size() != 1 || (corners.size() == 1 && ids[0] != id)) {
|
||||
ts->printf(cvtest::TS::LOG, "Error in DetectorParameters::minMarkerPerimeterRate");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
return;
|
||||
}
|
||||
|
||||
// set a invalid maxMarkerPerimeterRate
|
||||
detectorParameters = params;
|
||||
detectorParameters.maxMarkerPerimeterRate = min(4., (4. * markerSide) / float(imageSize) - 0.1);
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
detector.detectMarkers(img, corners, ids);
|
||||
if(corners.size() != 0) {
|
||||
ts->printf(cvtest::TS::LOG, "Error in DetectorParameters::maxMarkerPerimeterRate");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
return;
|
||||
}
|
||||
|
||||
// set an valid maxMarkerPerimeterRate
|
||||
detectorParameters = params;
|
||||
detectorParameters.maxMarkerPerimeterRate = max(0., (4. * markerSide) / float(imageSize) + 0.1);
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
detector.detectMarkers(img, corners, ids);
|
||||
if(corners.size() != 1 || (corners.size() == 1 && ids[0] != id)) {
|
||||
ts->printf(cvtest::TS::LOG, "Error in DetectorParameters::maxMarkerPerimeterRate");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Check error correction in marker bits
|
||||
*/
|
||||
class CV_ArucoBitCorrection : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_ArucoBitCorrection();
|
||||
|
||||
protected:
|
||||
void run(int);
|
||||
};
|
||||
|
||||
|
||||
CV_ArucoBitCorrection::CV_ArucoBitCorrection() {}
|
||||
|
||||
|
||||
void CV_ArucoBitCorrection::run(int) {
|
||||
|
||||
aruco::Dictionary dictionary1 = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
|
||||
aruco::Dictionary dictionary2 = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
|
||||
aruco::DetectorParameters params;
|
||||
aruco::ArucoDetector detector1(dictionary1, params);
|
||||
int markerSide = 50;
|
||||
int imageSize = 150;
|
||||
|
||||
// 10 markers
|
||||
for(int l = 0; l < 10; l++) {
|
||||
Mat marker;
|
||||
int id = 10 + l * 20;
|
||||
|
||||
Mat currentCodeBytes = dictionary1.bytesList.rowRange(id, id + 1);
|
||||
aruco::DetectorParameters detectorParameters = detector1.getDetectorParameters();
|
||||
// 5 valid cases
|
||||
for(int i = 0; i < 5; i++) {
|
||||
// how many bit errors (the error is low enough so it can be corrected)
|
||||
detectorParameters.errorCorrectionRate = 0.2 + i * 0.1;
|
||||
detector1.setDetectorParameters(detectorParameters);
|
||||
int errors =
|
||||
(int)std::floor(dictionary1.maxCorrectionBits * detector1.getDetectorParameters().errorCorrectionRate - 1.);
|
||||
|
||||
// create erroneous marker in currentCodeBits
|
||||
Mat currentCodeBits =
|
||||
aruco::Dictionary::getBitsFromByteList(currentCodeBytes, dictionary1.markerSize);
|
||||
for(int e = 0; e < errors; e++) {
|
||||
currentCodeBits.ptr<unsigned char>()[2 * e] =
|
||||
!currentCodeBits.ptr<unsigned char>()[2 * e];
|
||||
}
|
||||
|
||||
// add erroneous marker to dictionary2 in order to create the erroneous marker image
|
||||
Mat currentCodeBytesError = aruco::Dictionary::getByteListFromBits(currentCodeBits);
|
||||
currentCodeBytesError.copyTo(dictionary2.bytesList.rowRange(id, id + 1));
|
||||
Mat img = Mat(imageSize, imageSize, CV_8UC1, Scalar::all(255));
|
||||
dictionary2.generateImageMarker(id, markerSide, marker);
|
||||
Mat aux = img.colRange(30, 30 + markerSide).rowRange(50, 50 + markerSide);
|
||||
marker.copyTo(aux);
|
||||
|
||||
// try to detect using original dictionary
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
detector1.detectMarkers(img, corners, ids);
|
||||
if(corners.size() != 1 || (corners.size() == 1 && ids[0] != id)) {
|
||||
ts->printf(cvtest::TS::LOG, "Error in bit correction");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 5 invalid cases
|
||||
for(int i = 0; i < 5; i++) {
|
||||
// how many bit errors (the error is too high to be corrected)
|
||||
detectorParameters.errorCorrectionRate = 0.2 + i * 0.1;
|
||||
detector1.setDetectorParameters(detectorParameters);
|
||||
int errors =
|
||||
(int)std::floor(dictionary1.maxCorrectionBits * detector1.getDetectorParameters().errorCorrectionRate + 1.);
|
||||
|
||||
// create erroneous marker in currentCodeBits
|
||||
Mat currentCodeBits =
|
||||
aruco::Dictionary::getBitsFromByteList(currentCodeBytes, dictionary1.markerSize);
|
||||
for(int e = 0; e < errors; e++) {
|
||||
currentCodeBits.ptr<unsigned char>()[2 * e] =
|
||||
!currentCodeBits.ptr<unsigned char>()[2 * e];
|
||||
}
|
||||
|
||||
// dictionary3 is only composed by the modified marker (in its original form)
|
||||
aruco::Dictionary _dictionary3 = aruco::Dictionary(
|
||||
dictionary2.bytesList.rowRange(id, id + 1).clone(),
|
||||
dictionary1.markerSize,
|
||||
dictionary1.maxCorrectionBits);
|
||||
aruco::ArucoDetector detector3(_dictionary3, detector1.getDetectorParameters());
|
||||
// add erroneous marker to dictionary2 in order to create the erroneous marker image
|
||||
Mat currentCodeBytesError = aruco::Dictionary::getByteListFromBits(currentCodeBits);
|
||||
currentCodeBytesError.copyTo(dictionary2.bytesList.rowRange(id, id + 1));
|
||||
Mat img = Mat(imageSize, imageSize, CV_8UC1, Scalar::all(255));
|
||||
dictionary2.generateImageMarker(id, markerSide, marker);
|
||||
Mat aux = img.colRange(30, 30 + markerSide).rowRange(50, 50 + markerSide);
|
||||
marker.copyTo(aux);
|
||||
|
||||
// try to detect using dictionary3, it should fail
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
detector3.detectMarkers(img, corners, ids);
|
||||
if(corners.size() != 0) {
|
||||
ts->printf(cvtest::TS::LOG, "Error in DetectorParameters::errorCorrectionRate");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef CV_ArucoDetectionPerspective CV_AprilTagDetectionPerspective;
|
||||
typedef CV_ArucoDetectionPerspective CV_InvertedArucoDetectionPerspective;
|
||||
typedef CV_ArucoDetectionPerspective CV_Aruco3DetectionPerspective;
|
||||
|
||||
TEST(CV_InvertedArucoDetectionPerspective, algorithmic) {
|
||||
CV_InvertedArucoDetectionPerspective test(ArucoAlgParams::DETECT_INVERTED_MARKER);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_AprilTagDetectionPerspective, algorithmic) {
|
||||
CV_AprilTagDetectionPerspective test(ArucoAlgParams::USE_APRILTAG);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_Aruco3DetectionPerspective, algorithmic) {
|
||||
CV_Aruco3DetectionPerspective test(ArucoAlgParams::USE_ARUCO3);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_ArucoDetectionSimple, algorithmic) {
|
||||
CV_ArucoDetectionSimple test;
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_ArucoDetectionPerspective, algorithmic) {
|
||||
CV_ArucoDetectionPerspective test(ArucoAlgParams::USE_DEFAULT);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_ArucoDetectionMarkerSize, algorithmic) {
|
||||
CV_ArucoDetectionMarkerSize test;
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_ArucoBitCorrection, algorithmic) {
|
||||
CV_ArucoBitCorrection test;
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_ArucoDetectMarkers, regression_3192)
|
||||
{
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_4X4_50));
|
||||
vector<int> markerIds;
|
||||
vector<vector<Point2f> > markerCorners;
|
||||
string imgPath = cvtest::findDataFile("aruco/regression_3192.png");
|
||||
Mat image = imread(imgPath);
|
||||
const size_t N = 2ull;
|
||||
const int goldCorners[N][8] = { {345,120, 520,120, 520,295, 345,295}, {101,114, 270,112, 276,287, 101,287} };
|
||||
const int goldCornersIds[N] = { 6, 4 };
|
||||
map<int, const int*> mapGoldCorners;
|
||||
for (size_t i = 0; i < N; i++)
|
||||
mapGoldCorners[goldCornersIds[i]] = goldCorners[i];
|
||||
|
||||
detector.detectMarkers(image, markerCorners, markerIds);
|
||||
|
||||
ASSERT_EQ(N, markerIds.size());
|
||||
for (size_t i = 0; i < N; i++)
|
||||
{
|
||||
int arucoId = markerIds[i];
|
||||
ASSERT_EQ(4ull, markerCorners[i].size());
|
||||
ASSERT_TRUE(mapGoldCorners.find(arucoId) != mapGoldCorners.end());
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
EXPECT_NEAR(static_cast<float>(mapGoldCorners[arucoId][j * 2]), markerCorners[i][j].x, 1.f);
|
||||
EXPECT_NEAR(static_cast<float>(mapGoldCorners[arucoId][j * 2 + 1]), markerCorners[i][j].y, 1.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CV_ArucoDetectMarkers, regression_2492)
|
||||
{
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_5X5_50));
|
||||
aruco::DetectorParameters detectorParameters = detector.getDetectorParameters();
|
||||
detectorParameters.minMarkerDistanceRate = 0.026;
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
vector<int> markerIds;
|
||||
vector<vector<Point2f> > markerCorners;
|
||||
string imgPath = cvtest::findDataFile("aruco/regression_2492.png");
|
||||
Mat image = imread(imgPath);
|
||||
const size_t N = 8ull;
|
||||
const int goldCorners[N][8] = { {179,139, 179,95, 223,95, 223,139}, {99,139, 99,95, 143,95, 143,139},
|
||||
{19,139, 19,95, 63,95, 63,139}, {256,140, 256,93, 303,93, 303,140},
|
||||
{256,62, 259,21, 300,23, 297,64}, {99,21, 143,17, 147,60, 103,64},
|
||||
{69,61, 28,61, 14,21, 58,17}, {174,62, 182,13, 230,19, 223,68} };
|
||||
const int goldCornersIds[N] = {13, 13, 13, 13, 1, 15, 14, 4};
|
||||
map<int, vector<const int*> > mapGoldCorners;
|
||||
for (size_t i = 0; i < N; i++)
|
||||
mapGoldCorners[goldCornersIds[i]].push_back(goldCorners[i]);
|
||||
|
||||
detector.detectMarkers(image, markerCorners, markerIds);
|
||||
|
||||
ASSERT_EQ(N, markerIds.size());
|
||||
for (size_t i = 0; i < N; i++)
|
||||
{
|
||||
int arucoId = markerIds[i];
|
||||
ASSERT_EQ(4ull, markerCorners[i].size());
|
||||
ASSERT_TRUE(mapGoldCorners.find(arucoId) != mapGoldCorners.end());
|
||||
float totalDist = 8.f;
|
||||
for (size_t k = 0ull; k < mapGoldCorners[arucoId].size(); k++)
|
||||
{
|
||||
float dist = 0.f;
|
||||
for (int j = 0; j < 4; j++) // total distance up to 4 points
|
||||
{
|
||||
dist += abs(mapGoldCorners[arucoId][k][j * 2] - markerCorners[i][j].x);
|
||||
dist += abs(mapGoldCorners[arucoId][k][j * 2 + 1] - markerCorners[i][j].y);
|
||||
}
|
||||
totalDist = min(totalDist, dist);
|
||||
}
|
||||
EXPECT_LT(totalDist, 8.f);
|
||||
}
|
||||
}
|
||||
|
||||
struct ArucoThreading: public testing::TestWithParam<aruco::CornerRefineMethod>
|
||||
{
|
||||
struct NumThreadsSetter {
|
||||
NumThreadsSetter(const int num_threads)
|
||||
: original_num_threads_(getNumThreads()) {
|
||||
setNumThreads(num_threads);
|
||||
}
|
||||
|
||||
~NumThreadsSetter() {
|
||||
setNumThreads(original_num_threads_);
|
||||
}
|
||||
private:
|
||||
int original_num_threads_;
|
||||
};
|
||||
};
|
||||
|
||||
TEST_P(ArucoThreading, number_of_threads_does_not_change_results)
|
||||
{
|
||||
// We are not testing against different dictionaries
|
||||
// As we are interested mostly in small images, smaller
|
||||
// markers is better -> 4x4
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_4X4_50));
|
||||
|
||||
// Height of the test image can be chosen quite freely
|
||||
// We aim to test against small images as in those the
|
||||
// number of threads has most effect
|
||||
const int height_img = 20;
|
||||
// Just to get nice white boarder
|
||||
const int shift = height_img > 10 ? 5 : 1;
|
||||
const int height_marker = height_img-2*shift;
|
||||
|
||||
// Create a test image
|
||||
Mat img_marker;
|
||||
aruco::generateImageMarker(detector.getDictionary(), 23, height_marker, img_marker, 1);
|
||||
|
||||
// Copy to bigger image to get a white border
|
||||
Mat img(height_img, height_img, CV_8UC1, Scalar(255));
|
||||
img_marker.copyTo(img(Rect(shift, shift, height_marker, height_marker)));
|
||||
|
||||
aruco::DetectorParameters detectorParameters = detector.getDetectorParameters();
|
||||
detectorParameters.cornerRefinementMethod = GetParam();
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
|
||||
vector<vector<Point2f> > original_corners;
|
||||
vector<int> original_ids;
|
||||
{
|
||||
NumThreadsSetter thread_num_setter(1);
|
||||
detector.detectMarkers(img, original_corners, original_ids);
|
||||
}
|
||||
|
||||
ASSERT_EQ(original_ids.size(), 1ull);
|
||||
ASSERT_EQ(original_corners.size(), 1ull);
|
||||
|
||||
int num_threads_to_test[] = { 2, 8, 16, 32, height_img-1, height_img, height_img+1};
|
||||
|
||||
for (size_t i_num_threads = 0; i_num_threads < sizeof(num_threads_to_test)/sizeof(int); ++i_num_threads) {
|
||||
NumThreadsSetter thread_num_setter(num_threads_to_test[i_num_threads]);
|
||||
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
detector.detectMarkers(img, corners, ids);
|
||||
|
||||
// If we don't find any markers, the test is broken
|
||||
ASSERT_EQ(ids.size(), 1ull);
|
||||
|
||||
// Make sure we got the same result as the first time
|
||||
ASSERT_EQ(corners.size(), original_corners.size());
|
||||
ASSERT_EQ(ids.size(), original_ids.size());
|
||||
ASSERT_EQ(ids.size(), corners.size());
|
||||
for (size_t i = 0; i < corners.size(); ++i) {
|
||||
EXPECT_EQ(ids[i], original_ids[i]);
|
||||
for (size_t j = 0; j < corners[i].size(); ++j) {
|
||||
EXPECT_NEAR(corners[i][j].x, original_corners[i][j].x, 0.1f);
|
||||
EXPECT_NEAR(corners[i][j].y, original_corners[i][j].y, 0.1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
CV_ArucoDetectMarkers, ArucoThreading,
|
||||
::testing::Values(
|
||||
aruco::CORNER_REFINE_NONE,
|
||||
aruco::CORNER_REFINE_SUBPIX,
|
||||
aruco::CORNER_REFINE_CONTOUR,
|
||||
aruco::CORNER_REFINE_APRILTAG
|
||||
));
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,321 @@
|
||||
// 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.
|
||||
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_aruco_utils.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
enum class ArucoAlgParams
|
||||
{
|
||||
USE_DEFAULT = 0,
|
||||
USE_ARUCO3 = 1
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Check pose estimation of aruco board
|
||||
*/
|
||||
class CV_ArucoBoardPose : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_ArucoBoardPose(ArucoAlgParams arucoAlgParams)
|
||||
{
|
||||
aruco::DetectorParameters params;
|
||||
aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
|
||||
params.minDistanceToBorder = 3;
|
||||
if (arucoAlgParams == ArucoAlgParams::USE_ARUCO3) {
|
||||
params.useAruco3Detection = true;
|
||||
params.cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX;
|
||||
params.minSideLengthCanonicalImg = 16;
|
||||
params.errorCorrectionRate = 0.8;
|
||||
}
|
||||
detector = aruco::ArucoDetector(dictionary, params);
|
||||
}
|
||||
|
||||
protected:
|
||||
aruco::ArucoDetector detector;
|
||||
void run(int);
|
||||
};
|
||||
|
||||
|
||||
void CV_ArucoBoardPose::run(int) {
|
||||
int iter = 0;
|
||||
Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1);
|
||||
Size imgSize(500, 500);
|
||||
cameraMatrix.at< double >(0, 0) = cameraMatrix.at< double >(1, 1) = 650;
|
||||
cameraMatrix.at< double >(0, 2) = imgSize.width / 2;
|
||||
cameraMatrix.at< double >(1, 2) = imgSize.height / 2;
|
||||
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
|
||||
const int sizeX = 3, sizeY = 3;
|
||||
aruco::DetectorParameters detectorParameters = detector.getDetectorParameters();
|
||||
|
||||
// for different perspectives
|
||||
for(double distance = 0.2; distance <= 0.4; distance += 0.15) {
|
||||
for(int yaw = -55; yaw <= 50; yaw += 25) {
|
||||
for(int pitch = -55; pitch <= 50; pitch += 25) {
|
||||
vector<int> tmpIds;
|
||||
for(int i = 0; i < sizeX*sizeY; i++)
|
||||
tmpIds.push_back((iter + int(i)) % 250);
|
||||
aruco::GridBoard gridboard(Size(sizeX, sizeY), 0.02f, 0.005f, detector.getDictionary(), tmpIds);
|
||||
int markerBorder = iter % 2 + 1;
|
||||
iter++;
|
||||
// create synthetic image
|
||||
Mat img = projectBoard(gridboard, cameraMatrix, deg2rad(yaw), deg2rad(pitch), distance,
|
||||
imgSize, markerBorder);
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
detectorParameters.markerBorderBits = markerBorder;
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
detector.detectMarkers(img, corners, ids);
|
||||
|
||||
ASSERT_EQ(ids.size(), gridboard.getIds().size());
|
||||
|
||||
// estimate pose
|
||||
Mat rvec, tvec;
|
||||
{
|
||||
Mat objPoints, imgPoints; // get object and image points for the solvePnP function
|
||||
gridboard.matchImagePoints(corners, ids, objPoints, imgPoints);
|
||||
solvePnP(objPoints, imgPoints, cameraMatrix, distCoeffs, rvec, tvec);
|
||||
}
|
||||
|
||||
// check axes
|
||||
vector<Point2f> axes = getAxis(cameraMatrix, distCoeffs, rvec, tvec, gridboard.getRightBottomCorner().x);
|
||||
vector<Point2f> topLeft = getMarkerById(gridboard.getIds()[0], corners, ids);
|
||||
ASSERT_NEAR(topLeft[0].x, axes[0].x, 2.f);
|
||||
ASSERT_NEAR(topLeft[0].y, axes[0].y, 2.f);
|
||||
vector<Point2f> topRight = getMarkerById(gridboard.getIds()[2], corners, ids);
|
||||
ASSERT_NEAR(topRight[1].x, axes[1].x, 2.f);
|
||||
ASSERT_NEAR(topRight[1].y, axes[1].y, 2.f);
|
||||
vector<Point2f> bottomLeft = getMarkerById(gridboard.getIds()[6], corners, ids);
|
||||
ASSERT_NEAR(bottomLeft[3].x, axes[2].x, 2.f);
|
||||
ASSERT_NEAR(bottomLeft[3].y, axes[2].y, 2.f);
|
||||
|
||||
// check estimate result
|
||||
for(unsigned int i = 0; i < ids.size(); i++) {
|
||||
int foundIdx = -1;
|
||||
for(unsigned int j = 0; j < gridboard.getIds().size(); j++) {
|
||||
if(gridboard.getIds()[j] == ids[i]) {
|
||||
foundIdx = int(j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(foundIdx == -1) {
|
||||
ts->printf(cvtest::TS::LOG, "Marker detected with wrong ID in Board test");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
vector< Point2f > projectedCorners;
|
||||
projectPoints(gridboard.getObjPoints()[foundIdx], rvec, tvec, cameraMatrix, distCoeffs,
|
||||
projectedCorners);
|
||||
|
||||
for(int c = 0; c < 4; c++) {
|
||||
double repError = cv::norm(projectedCorners[c] - corners[i][c]); // TODO cvtest
|
||||
if(repError > 5.) {
|
||||
ts->printf(cvtest::TS::LOG, "Corner reprojection error too high");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Check refine strategy
|
||||
*/
|
||||
class CV_ArucoRefine : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_ArucoRefine(ArucoAlgParams arucoAlgParams)
|
||||
{
|
||||
aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
|
||||
aruco::DetectorParameters params;
|
||||
params.minDistanceToBorder = 3;
|
||||
params.cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX;
|
||||
if (arucoAlgParams == ArucoAlgParams::USE_ARUCO3)
|
||||
params.useAruco3Detection = true;
|
||||
aruco::RefineParameters refineParams(10.f, 3.f, true);
|
||||
detector = aruco::ArucoDetector(dictionary, params, refineParams);
|
||||
}
|
||||
|
||||
protected:
|
||||
aruco::ArucoDetector detector;
|
||||
void run(int);
|
||||
};
|
||||
|
||||
|
||||
void CV_ArucoRefine::run(int) {
|
||||
|
||||
int iter = 0;
|
||||
Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1);
|
||||
Size imgSize(500, 500);
|
||||
cameraMatrix.at< double >(0, 0) = cameraMatrix.at< double >(1, 1) = 650;
|
||||
cameraMatrix.at< double >(0, 2) = imgSize.width / 2;
|
||||
cameraMatrix.at< double >(1, 2) = imgSize.height / 2;
|
||||
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
|
||||
aruco::DetectorParameters detectorParameters = detector.getDetectorParameters();
|
||||
|
||||
// for different perspectives
|
||||
for(double distance = 0.2; distance <= 0.4; distance += 0.2) {
|
||||
for(int yaw = -60; yaw < 60; yaw += 30) {
|
||||
for(int pitch = -60; pitch <= 60; pitch += 30) {
|
||||
aruco::GridBoard gridboard(Size(3, 3), 0.02f, 0.005f, detector.getDictionary());
|
||||
int markerBorder = iter % 2 + 1;
|
||||
iter++;
|
||||
|
||||
// create synthetic image
|
||||
Mat img = projectBoard(gridboard, cameraMatrix, deg2rad(yaw), deg2rad(pitch), distance,
|
||||
imgSize, markerBorder);
|
||||
// detect markers
|
||||
vector<vector<Point2f> > corners, rejected;
|
||||
vector<int> ids;
|
||||
detectorParameters.markerBorderBits = markerBorder;
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
detector.detectMarkers(img, corners, ids, rejected);
|
||||
|
||||
// remove a marker from detection
|
||||
int markersBeforeDelete = (int)ids.size();
|
||||
if(markersBeforeDelete < 2) continue;
|
||||
|
||||
rejected.push_back(corners[0]);
|
||||
corners.erase(corners.begin(), corners.begin() + 1);
|
||||
ids.erase(ids.begin(), ids.begin() + 1);
|
||||
|
||||
// try to refind the erased marker
|
||||
detector.refineDetectedMarkers(img, gridboard, corners, ids, rejected, cameraMatrix,
|
||||
distCoeffs, noArray());
|
||||
|
||||
// check result
|
||||
if((int)ids.size() < markersBeforeDelete) {
|
||||
ts->printf(cvtest::TS::LOG, "Error in refine detected markers");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CV_ArucoBoardPose, accuracy) {
|
||||
CV_ArucoBoardPose test(ArucoAlgParams::USE_DEFAULT);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
typedef CV_ArucoBoardPose CV_Aruco3BoardPose;
|
||||
TEST(CV_Aruco3BoardPose, accuracy) {
|
||||
CV_Aruco3BoardPose test(ArucoAlgParams::USE_ARUCO3);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
typedef CV_ArucoRefine CV_Aruco3Refine;
|
||||
|
||||
TEST(CV_ArucoRefine, accuracy) {
|
||||
CV_ArucoRefine test(ArucoAlgParams::USE_DEFAULT);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_Aruco3Refine, accuracy) {
|
||||
CV_Aruco3Refine test(ArucoAlgParams::USE_ARUCO3);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_ArucoBoardPose, CheckNegativeZ)
|
||||
{
|
||||
double matrixData[9] = { -3.9062571886921410e+02, 0., 4.2350000000000000e+02,
|
||||
0., 3.9062571886921410e+02, 2.3950000000000000e+02,
|
||||
0., 0., 1 };
|
||||
cv::Mat cameraMatrix = cv::Mat(3, 3, CV_64F, matrixData);
|
||||
|
||||
vector<cv::Point3f> pts3d1, pts3d2;
|
||||
pts3d1.push_back(cv::Point3f(0.326198f, -0.030621f, 0.303620f));
|
||||
pts3d1.push_back(cv::Point3f(0.325340f, -0.100594f, 0.301862f));
|
||||
pts3d1.push_back(cv::Point3f(0.255859f, -0.099530f, 0.293416f));
|
||||
pts3d1.push_back(cv::Point3f(0.256717f, -0.029557f, 0.295174f));
|
||||
|
||||
pts3d2.push_back(cv::Point3f(-0.033144f, -0.034819f, 0.245216f));
|
||||
pts3d2.push_back(cv::Point3f(-0.035507f, -0.104705f, 0.241987f));
|
||||
pts3d2.push_back(cv::Point3f(-0.105289f, -0.102120f, 0.237120f));
|
||||
pts3d2.push_back(cv::Point3f(-0.102926f, -0.032235f, 0.240349f));
|
||||
|
||||
vector<int> tmpIds = {0, 1};
|
||||
vector<vector<Point3f> > tmpObjectPoints = {pts3d1, pts3d2};
|
||||
aruco::Board board(tmpObjectPoints, aruco::getPredefinedDictionary(0), tmpIds);
|
||||
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<Point2f> pts2d;
|
||||
pts2d.push_back(cv::Point2f(37.7f, 203.3f));
|
||||
pts2d.push_back(cv::Point2f(38.5f, 120.5f));
|
||||
pts2d.push_back(cv::Point2f(105.5f, 115.8f));
|
||||
pts2d.push_back(cv::Point2f(104.2f, 202.7f));
|
||||
corners.push_back(pts2d);
|
||||
pts2d.clear();
|
||||
pts2d.push_back(cv::Point2f(476.0f, 184.2f));
|
||||
pts2d.push_back(cv::Point2f(479.6f, 73.8f));
|
||||
pts2d.push_back(cv::Point2f(590.9f, 77.0f));
|
||||
pts2d.push_back(cv::Point2f(587.5f, 188.1f));
|
||||
corners.push_back(pts2d);
|
||||
|
||||
Vec3d rvec, tvec;
|
||||
int nUsed = 0;
|
||||
{
|
||||
Mat objPoints, imgPoints; // get object and image points for the solvePnP function
|
||||
board.matchImagePoints(corners, board.getIds(), objPoints, imgPoints);
|
||||
nUsed = (int)objPoints.total()/4;
|
||||
solvePnP(objPoints, imgPoints, cameraMatrix, Mat(), rvec, tvec);
|
||||
}
|
||||
ASSERT_EQ(nUsed, 2);
|
||||
|
||||
cv::Matx33d rotm; cv::Point3d out;
|
||||
cv::Rodrigues(rvec, rotm);
|
||||
out = cv::Point3d(tvec) + rotm*Point3d(board.getObjPoints()[0][0]);
|
||||
ASSERT_GT(out.z, 0);
|
||||
|
||||
corners.clear(); pts2d.clear();
|
||||
pts2d.push_back(cv::Point2f(38.4f, 204.5f));
|
||||
pts2d.push_back(cv::Point2f(40.0f, 124.7f));
|
||||
pts2d.push_back(cv::Point2f(102.0f, 119.1f));
|
||||
pts2d.push_back(cv::Point2f(99.9f, 203.6f));
|
||||
corners.push_back(pts2d);
|
||||
pts2d.clear();
|
||||
pts2d.push_back(cv::Point2f(476.0f, 184.3f));
|
||||
pts2d.push_back(cv::Point2f(479.2f, 75.1f));
|
||||
pts2d.push_back(cv::Point2f(588.7f, 79.2f));
|
||||
pts2d.push_back(cv::Point2f(586.3f, 188.5f));
|
||||
corners.push_back(pts2d);
|
||||
|
||||
nUsed = 0;
|
||||
{
|
||||
Mat objPoints, imgPoints; // get object and image points for the solvePnP function
|
||||
board.matchImagePoints(corners, board.getIds(), objPoints, imgPoints);
|
||||
nUsed = (int)objPoints.total()/4;
|
||||
solvePnP(objPoints, imgPoints, cameraMatrix, Mat(), rvec, tvec, true);
|
||||
}
|
||||
ASSERT_EQ(nUsed, 2);
|
||||
|
||||
cv::Rodrigues(rvec, rotm);
|
||||
out = cv::Point3d(tvec) + rotm*Point3d(board.getObjPoints()[0][0]);
|
||||
ASSERT_GT(out.z, 0);
|
||||
}
|
||||
|
||||
TEST(CV_ArucoGenerateBoard, regression_1226) {
|
||||
int bwidth = 1600;
|
||||
int bheight = 1200;
|
||||
|
||||
cv::aruco::Dictionary dict = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_4X4_50);
|
||||
cv::aruco::CharucoBoard board(Size(7, 5), 1.0, 0.75, dict);
|
||||
cv::Size sz(bwidth, bheight);
|
||||
cv::Mat mat;
|
||||
|
||||
ASSERT_NO_THROW(
|
||||
{
|
||||
board.generateImage(sz, mat, 0, 1);
|
||||
});
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,659 @@
|
||||
// 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.
|
||||
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_aruco_utils.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
/**
|
||||
* @brief Get a synthetic image of Chessboard in perspective
|
||||
*/
|
||||
static Mat projectChessboard(int squaresX, int squaresY, float squareSize, Size imageSize,
|
||||
Mat cameraMatrix, Mat rvec, Mat tvec) {
|
||||
|
||||
Mat img(imageSize, CV_8UC1, Scalar::all(255));
|
||||
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
|
||||
|
||||
for(int y = 0; y < squaresY; y++) {
|
||||
float startY = float(y) * squareSize;
|
||||
for(int x = 0; x < squaresX; x++) {
|
||||
if(y % 2 != x % 2) continue;
|
||||
float startX = float(x) * squareSize;
|
||||
|
||||
vector< Point3f > squareCorners;
|
||||
squareCorners.push_back(Point3f(startX, startY, 0) - Point3f(squaresX*squareSize/2.f, squaresY*squareSize/2.f, 0.f));
|
||||
squareCorners.push_back(squareCorners[0] + Point3f(squareSize, 0, 0));
|
||||
squareCorners.push_back(squareCorners[0] + Point3f(squareSize, squareSize, 0));
|
||||
squareCorners.push_back(squareCorners[0] + Point3f(0, squareSize, 0));
|
||||
|
||||
vector< vector< Point2f > > projectedCorners;
|
||||
projectedCorners.push_back(vector< Point2f >());
|
||||
projectPoints(squareCorners, rvec, tvec, cameraMatrix, distCoeffs, projectedCorners[0]);
|
||||
|
||||
vector< vector< Point > > projectedCornersInt;
|
||||
projectedCornersInt.push_back(vector< Point >());
|
||||
|
||||
for(int k = 0; k < 4; k++)
|
||||
projectedCornersInt[0]
|
||||
.push_back(Point((int)projectedCorners[0][k].x, (int)projectedCorners[0][k].y));
|
||||
|
||||
fillPoly(img, projectedCornersInt, Scalar::all(0));
|
||||
}
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Check pose estimation of charuco board
|
||||
*/
|
||||
static Mat projectCharucoBoard(aruco::CharucoBoard& board, Mat cameraMatrix, double yaw,
|
||||
double pitch, double distance, Size imageSize, int markerBorder,
|
||||
Mat &rvec, Mat &tvec) {
|
||||
|
||||
getSyntheticRT(yaw, pitch, distance, rvec, tvec);
|
||||
|
||||
// project markers
|
||||
Mat img = Mat(imageSize, CV_8UC1, Scalar::all(255));
|
||||
for(unsigned int indexMarker = 0; indexMarker < board.getIds().size(); indexMarker++) {
|
||||
projectMarker(img, board, indexMarker, cameraMatrix, rvec, tvec, markerBorder);
|
||||
}
|
||||
|
||||
// project chessboard
|
||||
Mat chessboard =
|
||||
projectChessboard(board.getChessboardSize().width, board.getChessboardSize().height,
|
||||
board.getSquareLength(), imageSize, cameraMatrix, rvec, tvec);
|
||||
|
||||
for(unsigned int i = 0; i < chessboard.total(); i++) {
|
||||
if(chessboard.ptr< unsigned char >()[i] == 0) {
|
||||
img.ptr< unsigned char >()[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check Charuco detection
|
||||
*/
|
||||
class CV_CharucoDetection : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_CharucoDetection();
|
||||
|
||||
protected:
|
||||
void run(int);
|
||||
};
|
||||
|
||||
|
||||
CV_CharucoDetection::CV_CharucoDetection() {}
|
||||
|
||||
|
||||
void CV_CharucoDetection::run(int) {
|
||||
|
||||
int iter = 0;
|
||||
Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1);
|
||||
Size imgSize(500, 500);
|
||||
aruco::DetectorParameters params;
|
||||
params.minDistanceToBorder = 3;
|
||||
aruco::CharucoBoard board(Size(4, 4), 0.03f, 0.015f, aruco::getPredefinedDictionary(aruco::DICT_6X6_250));
|
||||
aruco::CharucoDetector detector(board, aruco::CharucoParameters(), params);
|
||||
|
||||
cameraMatrix.at<double>(0, 0) = cameraMatrix.at<double>(1, 1) = 600;
|
||||
cameraMatrix.at<double>(0, 2) = imgSize.width / 2;
|
||||
cameraMatrix.at<double>(1, 2) = imgSize.height / 2;
|
||||
|
||||
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
|
||||
|
||||
// for different perspectives
|
||||
for(double distance = 0.2; distance <= 0.4; distance += 0.2) {
|
||||
for(int yaw = -55; yaw <= 50; yaw += 25) {
|
||||
for(int pitch = -55; pitch <= 50; pitch += 25) {
|
||||
|
||||
int markerBorder = iter % 2 + 1;
|
||||
iter++;
|
||||
|
||||
// create synthetic image
|
||||
Mat rvec, tvec;
|
||||
Mat img = projectCharucoBoard(board, cameraMatrix, deg2rad(yaw), deg2rad(pitch),
|
||||
distance, imgSize, markerBorder, rvec, tvec);
|
||||
|
||||
// detect markers and interpolate charuco corners
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<Point2f> charucoCorners;
|
||||
vector<int> ids, charucoIds;
|
||||
|
||||
params.markerBorderBits = markerBorder;
|
||||
detector.setDetectorParameters(params);
|
||||
|
||||
//detector.detectMarkers(img, corners, ids);
|
||||
if(iter % 2 == 0) {
|
||||
detector.detectBoard(img, charucoCorners, charucoIds, corners, ids);
|
||||
} else {
|
||||
aruco::CharucoParameters charucoParameters;
|
||||
charucoParameters.cameraMatrix = cameraMatrix;
|
||||
charucoParameters.distCoeffs = distCoeffs;
|
||||
detector.setCharucoParameters(charucoParameters);
|
||||
detector.detectBoard(img, charucoCorners, charucoIds, corners, ids);
|
||||
}
|
||||
|
||||
if(ids.size() == 0) {
|
||||
ts->printf(cvtest::TS::LOG, "Marker detection failed");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
// check results
|
||||
vector< Point2f > projectedCharucoCorners;
|
||||
|
||||
// copy chessboardCorners
|
||||
vector<Point3f> copyChessboardCorners = board.getChessboardCorners();
|
||||
// move copyChessboardCorners points
|
||||
for (size_t i = 0; i < copyChessboardCorners.size(); i++)
|
||||
copyChessboardCorners[i] -= board.getRightBottomCorner() / 2.f;
|
||||
projectPoints(copyChessboardCorners, rvec, tvec, cameraMatrix, distCoeffs,
|
||||
projectedCharucoCorners);
|
||||
|
||||
for(unsigned int i = 0; i < charucoIds.size(); i++) {
|
||||
|
||||
int currentId = charucoIds[i];
|
||||
|
||||
if(currentId >= (int)board.getChessboardCorners().size()) {
|
||||
ts->printf(cvtest::TS::LOG, "Invalid Charuco corner id");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
double repError = cv::norm(charucoCorners[i] - projectedCharucoCorners[currentId]); // TODO cvtest
|
||||
|
||||
|
||||
if(repError > 5.) {
|
||||
ts->printf(cvtest::TS::LOG, "Charuco corner reprojection error too high");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Check charuco pose estimation
|
||||
*/
|
||||
class CV_CharucoPoseEstimation : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_CharucoPoseEstimation();
|
||||
|
||||
protected:
|
||||
void run(int);
|
||||
};
|
||||
|
||||
|
||||
CV_CharucoPoseEstimation::CV_CharucoPoseEstimation() {}
|
||||
|
||||
|
||||
void CV_CharucoPoseEstimation::run(int) {
|
||||
int iter = 0;
|
||||
Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1);
|
||||
Size imgSize(500, 500);
|
||||
aruco::DetectorParameters params;
|
||||
params.minDistanceToBorder = 3;
|
||||
aruco::CharucoBoard board(Size(4, 4), 0.03f, 0.015f, aruco::getPredefinedDictionary(aruco::DICT_6X6_250));
|
||||
aruco::CharucoDetector detector(board, aruco::CharucoParameters(), params);
|
||||
|
||||
cameraMatrix.at<double>(0, 0) = cameraMatrix.at< double >(1, 1) = 650;
|
||||
cameraMatrix.at<double>(0, 2) = imgSize.width / 2;
|
||||
cameraMatrix.at<double>(1, 2) = imgSize.height / 2;
|
||||
|
||||
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
|
||||
// for different perspectives
|
||||
for(double distance = 0.2; distance <= 0.3; distance += 0.1) {
|
||||
for(int yaw = -55; yaw <= 50; yaw += 25) {
|
||||
for(int pitch = -55; pitch <= 50; pitch += 25) {
|
||||
|
||||
int markerBorder = iter % 2 + 1;
|
||||
iter++;
|
||||
|
||||
// get synthetic image
|
||||
Mat rvec, tvec;
|
||||
Mat img = projectCharucoBoard(board, cameraMatrix, deg2rad(yaw), deg2rad(pitch),
|
||||
distance, imgSize, markerBorder, rvec, tvec);
|
||||
|
||||
// detect markers
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
params.markerBorderBits = markerBorder;
|
||||
detector.setDetectorParameters(params);
|
||||
|
||||
// detect markers and interpolate charuco corners
|
||||
vector<Point2f> charucoCorners;
|
||||
vector<int> charucoIds;
|
||||
|
||||
if(iter % 2 == 0) {
|
||||
detector.detectBoard(img, charucoCorners, charucoIds, corners, ids);
|
||||
} else {
|
||||
aruco::CharucoParameters charucoParameters;
|
||||
charucoParameters.cameraMatrix = cameraMatrix;
|
||||
charucoParameters.distCoeffs = distCoeffs;
|
||||
detector.setCharucoParameters(charucoParameters);
|
||||
detector.detectBoard(img, charucoCorners, charucoIds, corners, ids);
|
||||
}
|
||||
ASSERT_EQ(ids.size(), board.getIds().size());
|
||||
if(charucoIds.size() == 0) continue;
|
||||
|
||||
// estimate charuco pose
|
||||
getCharucoBoardPose(charucoCorners, charucoIds, board, cameraMatrix, distCoeffs, rvec, tvec);
|
||||
|
||||
|
||||
// check axes
|
||||
const float offset = (board.getSquareLength() - board.getMarkerLength()) / 2.f;
|
||||
vector<Point2f> axes = getAxis(cameraMatrix, distCoeffs, rvec, tvec, board.getSquareLength(), offset);
|
||||
vector<Point2f> topLeft = getMarkerById(board.getIds()[0], corners, ids);
|
||||
ASSERT_NEAR(topLeft[0].x, axes[1].x, 3.f);
|
||||
ASSERT_NEAR(topLeft[0].y, axes[1].y, 3.f);
|
||||
vector<Point2f> bottomLeft = getMarkerById(board.getIds()[2], corners, ids);
|
||||
ASSERT_NEAR(bottomLeft[0].x, axes[2].x, 3.f);
|
||||
ASSERT_NEAR(bottomLeft[0].y, axes[2].y, 3.f);
|
||||
|
||||
// check estimate result
|
||||
vector< Point2f > projectedCharucoCorners;
|
||||
|
||||
projectPoints(board.getChessboardCorners(), rvec, tvec, cameraMatrix, distCoeffs,
|
||||
projectedCharucoCorners);
|
||||
|
||||
for(unsigned int i = 0; i < charucoIds.size(); i++) {
|
||||
|
||||
int currentId = charucoIds[i];
|
||||
|
||||
if(currentId >= (int)board.getChessboardCorners().size()) {
|
||||
ts->printf(cvtest::TS::LOG, "Invalid Charuco corner id");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
double repError = cv::norm(charucoCorners[i] - projectedCharucoCorners[currentId]); // TODO cvtest
|
||||
|
||||
|
||||
if(repError > 5.) {
|
||||
ts->printf(cvtest::TS::LOG, "Charuco corner reprojection error too high");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Check diamond detection
|
||||
*/
|
||||
class CV_CharucoDiamondDetection : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_CharucoDiamondDetection();
|
||||
|
||||
protected:
|
||||
void run(int);
|
||||
};
|
||||
|
||||
|
||||
CV_CharucoDiamondDetection::CV_CharucoDiamondDetection() {}
|
||||
|
||||
|
||||
void CV_CharucoDiamondDetection::run(int) {
|
||||
|
||||
int iter = 0;
|
||||
Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1);
|
||||
Size imgSize(500, 500);
|
||||
aruco::DetectorParameters params;
|
||||
params.minDistanceToBorder = 0;
|
||||
float squareLength = 0.03f;
|
||||
float markerLength = 0.015f;
|
||||
aruco::CharucoBoard board(Size(3, 3), squareLength, markerLength,
|
||||
aruco::getPredefinedDictionary(aruco::DICT_6X6_250));
|
||||
aruco::CharucoDetector detector(board);
|
||||
|
||||
|
||||
cameraMatrix.at<double>(0, 0) = cameraMatrix.at< double >(1, 1) = 650;
|
||||
cameraMatrix.at<double>(0, 2) = imgSize.width / 2;
|
||||
cameraMatrix.at<double>(1, 2) = imgSize.height / 2;
|
||||
|
||||
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
|
||||
aruco::CharucoParameters charucoParameters;
|
||||
charucoParameters.cameraMatrix = cameraMatrix;
|
||||
charucoParameters.distCoeffs = distCoeffs;
|
||||
detector.setCharucoParameters(charucoParameters);
|
||||
|
||||
// for different perspectives
|
||||
for(double distance = 0.2; distance <= 0.3; distance += 0.1) {
|
||||
for(int yaw = -50; yaw <= 50; yaw += 25) {
|
||||
for(int pitch = -50; pitch <= 50; pitch += 25) {
|
||||
|
||||
int markerBorder = iter % 2 + 1;
|
||||
vector<int> idsTmp;
|
||||
for(int i = 0; i < 4; i++)
|
||||
idsTmp.push_back(4 * iter + i);
|
||||
board = aruco::CharucoBoard(Size(3, 3), squareLength, markerLength,
|
||||
aruco::getPredefinedDictionary(aruco::DICT_6X6_250), idsTmp);
|
||||
detector.setBoard(board);
|
||||
iter++;
|
||||
|
||||
// get synthetic image
|
||||
Mat rvec, tvec;
|
||||
Mat img = projectCharucoBoard(board, cameraMatrix, deg2rad(yaw), deg2rad(pitch),
|
||||
distance, imgSize, markerBorder, rvec, tvec);
|
||||
|
||||
// detect markers
|
||||
vector<vector<Point2f>> corners;
|
||||
vector<int> ids;
|
||||
params.markerBorderBits = markerBorder;
|
||||
detector.setDetectorParameters(params);
|
||||
//detector.detectMarkers(img, corners, ids);
|
||||
|
||||
|
||||
// detect diamonds
|
||||
vector<vector<Point2f>> diamondCorners;
|
||||
vector<Vec4i> diamondIds;
|
||||
|
||||
detector.detectDiamonds(img, diamondCorners, diamondIds, corners, ids);
|
||||
|
||||
// check detect
|
||||
if(ids.size() != 4) {
|
||||
ts->printf(cvtest::TS::LOG, "Not enough markers for diamond detection");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
// check results
|
||||
if(diamondIds.size() != 1) {
|
||||
ts->printf(cvtest::TS::LOG, "Diamond not detected correctly");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < 4; i++) {
|
||||
if(diamondIds[0][i] != board.getIds()[i]) {
|
||||
ts->printf(cvtest::TS::LOG, "Incorrect diamond ids");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
vector< Point2f > projectedDiamondCorners;
|
||||
|
||||
// copy chessboardCorners
|
||||
vector<Point3f> copyChessboardCorners = board.getChessboardCorners();
|
||||
// move copyChessboardCorners points
|
||||
for (size_t i = 0; i < copyChessboardCorners.size(); i++)
|
||||
copyChessboardCorners[i] -= board.getRightBottomCorner() / 2.f;
|
||||
|
||||
projectPoints(copyChessboardCorners, rvec, tvec, cameraMatrix, distCoeffs,
|
||||
projectedDiamondCorners);
|
||||
|
||||
vector< Point2f > projectedDiamondCornersReorder(4);
|
||||
projectedDiamondCornersReorder[0] = projectedDiamondCorners[0];
|
||||
projectedDiamondCornersReorder[1] = projectedDiamondCorners[1];
|
||||
projectedDiamondCornersReorder[2] = projectedDiamondCorners[3];
|
||||
projectedDiamondCornersReorder[3] = projectedDiamondCorners[2];
|
||||
|
||||
|
||||
for(unsigned int i = 0; i < 4; i++) {
|
||||
|
||||
double repError = cv::norm(diamondCorners[0][i] - projectedDiamondCornersReorder[i]); // TODO cvtest
|
||||
|
||||
if(repError > 5.) {
|
||||
ts->printf(cvtest::TS::LOG, "Diamond corner reprojection error too high");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// estimate diamond pose
|
||||
vector< Vec3d > estimatedRvec, estimatedTvec;
|
||||
getMarkersPoses(diamondCorners, squareLength, cameraMatrix, distCoeffs, estimatedRvec,
|
||||
estimatedTvec, noArray(), false);
|
||||
|
||||
// check result
|
||||
vector< Point2f > projectedDiamondCornersPose;
|
||||
vector< Vec3f > diamondObjPoints(4);
|
||||
diamondObjPoints[0] = Vec3f(0.f, 0.f, 0);
|
||||
diamondObjPoints[1] = Vec3f(squareLength, 0.f, 0);
|
||||
diamondObjPoints[2] = Vec3f(squareLength, squareLength, 0);
|
||||
diamondObjPoints[3] = Vec3f(0.f, squareLength, 0);
|
||||
projectPoints(diamondObjPoints, estimatedRvec[0], estimatedTvec[0], cameraMatrix,
|
||||
distCoeffs, projectedDiamondCornersPose);
|
||||
|
||||
for(unsigned int i = 0; i < 4; i++) {
|
||||
double repError = cv::norm(projectedDiamondCornersReorder[i] - projectedDiamondCornersPose[i]); // TODO cvtest
|
||||
|
||||
if(repError > 5.) {
|
||||
ts->printf(cvtest::TS::LOG, "Charuco pose error too high");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check charuco board creation
|
||||
*/
|
||||
class CV_CharucoBoardCreation : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_CharucoBoardCreation();
|
||||
|
||||
protected:
|
||||
void run(int);
|
||||
};
|
||||
|
||||
CV_CharucoBoardCreation::CV_CharucoBoardCreation() {}
|
||||
|
||||
void CV_CharucoBoardCreation::run(int)
|
||||
{
|
||||
aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_5X5_250);
|
||||
int n = 6;
|
||||
|
||||
float markerSizeFactor = 0.5f;
|
||||
|
||||
for (float squareSize_mm = 5.0f; squareSize_mm < 35.0f; squareSize_mm += 0.1f)
|
||||
{
|
||||
aruco::CharucoBoard board_meters(Size(n, n), squareSize_mm*1e-3f,
|
||||
squareSize_mm * markerSizeFactor * 1e-3f, dictionary);
|
||||
|
||||
aruco::CharucoBoard board_millimeters(Size(n, n), squareSize_mm,
|
||||
squareSize_mm * markerSizeFactor, dictionary);
|
||||
|
||||
for (size_t i = 0; i < board_meters.getNearestMarkerIdx().size(); i++)
|
||||
{
|
||||
if (board_meters.getNearestMarkerIdx()[i].size() != board_millimeters.getNearestMarkerIdx()[i].size() ||
|
||||
board_meters.getNearestMarkerIdx()[i][0] != board_millimeters.getNearestMarkerIdx()[i][0])
|
||||
{
|
||||
ts->printf(cvtest::TS::LOG,
|
||||
cv::format("Charuco board topology is sensitive to scale with squareSize=%.1f\n",
|
||||
squareSize_mm).c_str());
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(CV_CharucoDetection, accuracy) {
|
||||
CV_CharucoDetection test;
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_CharucoPoseEstimation, accuracy) {
|
||||
CV_CharucoPoseEstimation test;
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_CharucoDiamondDetection, accuracy) {
|
||||
CV_CharucoDiamondDetection test;
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_CharucoBoardCreation, accuracy) {
|
||||
CV_CharucoBoardCreation test;
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(Charuco, testCharucoCornersCollinear_true)
|
||||
{
|
||||
int squaresX = 13;
|
||||
int squaresY = 28;
|
||||
float squareLength = 300;
|
||||
float markerLength = 150;
|
||||
int dictionaryId = 11;
|
||||
|
||||
aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::PredefinedDictionaryType(dictionaryId));
|
||||
|
||||
aruco::CharucoBoard charucoBoard(Size(squaresX, squaresY), squareLength, markerLength, dictionary);
|
||||
|
||||
// consistency with C++98
|
||||
const int arrLine[9] = {192, 204, 216, 228, 240, 252, 264, 276, 288};
|
||||
vector<int> charucoIdsAxisLine(9, 0);
|
||||
|
||||
for (int i = 0; i < 9; i++){
|
||||
charucoIdsAxisLine[i] = arrLine[i];
|
||||
}
|
||||
|
||||
const int arrDiag[7] = {198, 209, 220, 231, 242, 253, 264};
|
||||
|
||||
vector<int> charucoIdsDiagonalLine(7, 0);
|
||||
|
||||
for (int i = 0; i < 7; i++){
|
||||
charucoIdsDiagonalLine[i] = arrDiag[i];
|
||||
}
|
||||
|
||||
bool resultAxisLine = charucoBoard.checkCharucoCornersCollinear(charucoIdsAxisLine);
|
||||
EXPECT_TRUE(resultAxisLine);
|
||||
|
||||
bool resultDiagonalLine = charucoBoard.checkCharucoCornersCollinear(charucoIdsDiagonalLine);
|
||||
EXPECT_TRUE(resultDiagonalLine);
|
||||
}
|
||||
|
||||
TEST(Charuco, testCharucoCornersCollinear_false)
|
||||
{
|
||||
int squaresX = 13;
|
||||
int squaresY = 28;
|
||||
float squareLength = 300;
|
||||
float markerLength = 150;
|
||||
int dictionaryId = 11;
|
||||
|
||||
aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::PredefinedDictionaryType(dictionaryId));
|
||||
|
||||
aruco::CharucoBoard charucoBoard(Size(squaresX, squaresY), squareLength, markerLength, dictionary);
|
||||
|
||||
// consistency with C++98
|
||||
const int arr[63] = {192, 193, 194, 195, 196, 197, 198, 204, 205, 206, 207, 208,
|
||||
209, 210, 216, 217, 218, 219, 220, 221, 222, 228, 229, 230,
|
||||
231, 232, 233, 234, 240, 241, 242, 243, 244, 245, 246, 252,
|
||||
253, 254, 255, 256, 257, 258, 264, 265, 266, 267, 268, 269,
|
||||
270, 276, 277, 278, 279, 280, 281, 282, 288, 289, 290, 291,
|
||||
292, 293, 294};
|
||||
|
||||
vector<int> charucoIds(63, 0);
|
||||
for (int i = 0; i < 63; i++){
|
||||
charucoIds[i] = arr[i];
|
||||
}
|
||||
|
||||
bool result = charucoBoard.checkCharucoCornersCollinear(charucoIds);
|
||||
|
||||
EXPECT_FALSE(result);
|
||||
}
|
||||
|
||||
// test that ChArUco board detection is subpixel accurate
|
||||
TEST(Charuco, testBoardSubpixelCoords)
|
||||
{
|
||||
cv::Size res{500, 500};
|
||||
cv::Mat K = (cv::Mat_<double>(3,3) <<
|
||||
0.5*res.width, 0, 0.5*res.width,
|
||||
0, 0.5*res.height, 0.5*res.height,
|
||||
0, 0, 1);
|
||||
|
||||
// set expected_corners values
|
||||
cv::Mat expected_corners = (cv::Mat_<float>(9,2) <<
|
||||
200, 200,
|
||||
250, 200,
|
||||
300, 200,
|
||||
200, 250,
|
||||
250, 250,
|
||||
300, 250,
|
||||
200, 300,
|
||||
250, 300,
|
||||
300, 300
|
||||
);
|
||||
|
||||
cv::Mat gray;
|
||||
|
||||
aruco::Dictionary dict = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_APRILTAG_36h11);
|
||||
aruco::CharucoBoard board(Size(4, 4), 1.f, .8f, dict);
|
||||
|
||||
// generate ChArUco board
|
||||
board.generateImage(Size(res.width, res.height), gray, 150);
|
||||
cv::GaussianBlur(gray, gray, Size(5, 5), 1.0);
|
||||
|
||||
aruco::DetectorParameters params;
|
||||
params.cornerRefinementMethod = cv::aruco::CORNER_REFINE_APRILTAG;
|
||||
|
||||
aruco::CharucoParameters charucoParameters;
|
||||
charucoParameters.cameraMatrix = K;
|
||||
aruco::CharucoDetector detector(board, charucoParameters);
|
||||
detector.setDetectorParameters(params);
|
||||
|
||||
std::vector<int> ids;
|
||||
std::vector<std::vector<cv::Point2f>> corners;
|
||||
cv::Mat c_ids, c_corners;
|
||||
|
||||
detector.detectBoard(gray, c_corners, c_ids, corners, ids);
|
||||
|
||||
ASSERT_EQ(ids.size(), size_t(8));
|
||||
ASSERT_EQ(c_corners.rows, expected_corners.rows);
|
||||
EXPECT_NEAR(0, cvtest::norm(expected_corners, c_corners.reshape(1), NORM_INF), 1e-1);
|
||||
}
|
||||
|
||||
TEST(Charuco, issue_14014)
|
||||
{
|
||||
string imgPath = cvtest::findDataFile("aruco/recover.png");
|
||||
Mat img = imread(imgPath);
|
||||
|
||||
aruco::DetectorParameters detectorParams;
|
||||
detectorParams.cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX;
|
||||
detectorParams.cornerRefinementMinAccuracy = 0.01;
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_7X7_250), detectorParams);
|
||||
aruco::CharucoBoard board(Size(8, 5), 0.03455f, 0.02164f, detector.getDictionary());
|
||||
|
||||
vector<Mat> corners, rejectedPoints;
|
||||
vector<int> ids;
|
||||
|
||||
detector.detectMarkers(img, corners, ids, rejectedPoints);
|
||||
|
||||
ASSERT_EQ(corners.size(), 19ull);
|
||||
EXPECT_EQ(Size(4, 1), corners[0].size()); // check dimension of detected corners
|
||||
|
||||
size_t numRejPoints = rejectedPoints.size();
|
||||
ASSERT_EQ(rejectedPoints.size(), 26ull); // optional check to track regressions
|
||||
EXPECT_EQ(Size(4, 1), rejectedPoints[0].size()); // check dimension of detected corners
|
||||
|
||||
detector.refineDetectedMarkers(img, board, corners, ids, rejectedPoints);
|
||||
|
||||
ASSERT_EQ(corners.size(), 20ull);
|
||||
EXPECT_EQ(Size(4, 1), corners[0].size()); // check dimension of rejected corners after successfully refine
|
||||
|
||||
ASSERT_EQ(rejectedPoints.size() + 1, numRejPoints);
|
||||
EXPECT_EQ(Size(4, 1), rejectedPoints[0].size()); // check dimension of rejected corners after successfully refine
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
@@ -3,6 +3,7 @@
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
@@ -11,13 +12,14 @@ std::string qrcode_images_name[] = {
|
||||
"version_2_down.jpg", "version_2_left.jpg", "version_2_right.jpg", "version_2_up.jpg", "version_2_top.jpg",
|
||||
"version_3_down.jpg", "version_3_left.jpg", "version_3_right.jpg", "version_3_up.jpg", "version_3_top.jpg",
|
||||
"version_4_down.jpg", "version_4_left.jpg", "version_4_right.jpg", "version_4_up.jpg", "version_4_top.jpg",
|
||||
"version_5_down.jpg", "version_5_left.jpg"/*"version_5_right.jpg"*/,
|
||||
"version_5_down.jpg", "version_5_left.jpg", /*"version_5_right.jpg",*/ "version_5_up.jpg", "version_5_top.jpg",
|
||||
"russian.jpg", "kanji.jpg", "link_github_ocv.jpg", "link_ocv.jpg", "link_wiki_cv.jpg"
|
||||
// version_5_right.jpg DISABLED after tile fix, PR #22025
|
||||
};
|
||||
|
||||
// Todo: fix corner align in big QRs to enable close_5.png
|
||||
std::string qrcode_images_close[] = {
|
||||
"close_1.png", "close_2.png", "close_3.png", "close_4.png", "close_5.png"
|
||||
"close_1.png", "close_2.png", "close_3.png", "close_4.png"//, "close_5.png"
|
||||
};
|
||||
std::string qrcode_images_monitor[] = {
|
||||
"monitor_1.png", "monitor_2.png", "monitor_3.png", "monitor_4.png", "monitor_5.png"
|
||||
@@ -30,6 +32,7 @@ std::string qrcode_images_multiple[] = {
|
||||
"2_qrcodes.png", "3_close_qrcodes.png", "3_qrcodes.png", "4_qrcodes.png",
|
||||
"5_qrcodes.png", "6_qrcodes.png", "7_qrcodes.png", "8_close_qrcodes.png"
|
||||
};
|
||||
|
||||
//#define UPDATE_QRCODE_TEST_DATA
|
||||
#ifdef UPDATE_QRCODE_TEST_DATA
|
||||
|
||||
@@ -87,7 +90,7 @@ TEST(Objdetect_QRCode_Close, generate_test_data)
|
||||
const int width = cvRound(src.size().width * coeff_expansion);
|
||||
const int height = cvRound(src.size().height * coeff_expansion);
|
||||
Size new_size(width, height);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT);
|
||||
EXPECT_TRUE(detectQRCode(barcode, corners));
|
||||
#ifdef HAVE_QUIRC
|
||||
EXPECT_TRUE(decodeQRCode(barcode, corners, decoded_info, straight_barcode));
|
||||
@@ -125,7 +128,7 @@ TEST(Objdetect_QRCode_Monitor, generate_test_data)
|
||||
const int width = cvRound(src.size().width * coeff_expansion);
|
||||
const int height = cvRound(src.size().height * coeff_expansion);
|
||||
Size new_size(width, height);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT);
|
||||
EXPECT_TRUE(detectQRCode(barcode, corners));
|
||||
#ifdef HAVE_QUIRC
|
||||
EXPECT_TRUE(decodeQRCode(barcode, corners, decoded_info, straight_barcode));
|
||||
@@ -313,7 +316,7 @@ TEST_P(Objdetect_QRCode_Close, regression)
|
||||
const int width = cvRound(src.size().width * coeff_expansion);
|
||||
const int height = cvRound(src.size().height * coeff_expansion);
|
||||
Size new_size(width, height);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT);
|
||||
std::vector<Point> corners;
|
||||
std::string decoded_info;
|
||||
QRCodeDetector qrcode;
|
||||
@@ -380,7 +383,7 @@ TEST_P(Objdetect_QRCode_Monitor, regression)
|
||||
const int width = cvRound(src.size().width * coeff_expansion);
|
||||
const int height = cvRound(src.size().height * coeff_expansion);
|
||||
Size new_size(width, height);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT);
|
||||
std::vector<Point> corners;
|
||||
std::string decoded_info;
|
||||
QRCodeDetector qrcode;
|
||||
@@ -499,7 +502,7 @@ TEST_P(Objdetect_QRCode_Multi, regression)
|
||||
{
|
||||
const std::string name_current_image = GetParam();
|
||||
const std::string root = "qrcode/multiple/";
|
||||
const int pixels_error = 3;
|
||||
const int pixels_error = 4;
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat src = imread(image_path);
|
||||
@@ -758,6 +761,26 @@ TEST(Objdetect_QRCode_decode, decode_regression_version_25)
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(Objdetect_QRCode_decodeMulti, decode_9_qrcodes_version7)
|
||||
{
|
||||
const std::string name_current_image = "9_qrcodes_version7.jpg";
|
||||
const std::string root = "qrcode/multiple/";
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat src = imread(image_path);
|
||||
QRCodeDetector qrcode;
|
||||
std::vector<Point> corners;
|
||||
std::vector<cv::String> decoded_info;
|
||||
|
||||
std::vector<Mat1b> straight_barcode;
|
||||
qrcode.detectAndDecodeMulti(src, decoded_info, corners, straight_barcode);
|
||||
EXPECT_EQ(9ull, decoded_info.size());
|
||||
const string gold_info = "I love OpenCV, QR Code version = 7, error correction = level Quartile";
|
||||
for (const auto& info : decoded_info) {
|
||||
EXPECT_EQ(info, gold_info);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // UPDATE_QRCODE_TEST_DATA
|
||||
|
||||
}} // namespace
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
Reference in New Issue
Block a user