vendor: OpenCV 5.0.0 snapshot at 40738fb16ceddb5fb3fea747585f7ce6abb0605b

This commit is contained in:
Gitea Mirror Bot
2026-08-22 00:10:33 +08:00
commit f7f077da11
6933 changed files with 2335208 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
set(the_description "Object Detection")
ocv_define_module(objdetect
opencv_core
opencv_imgproc
opencv_features
opencv_geometry
OPTIONAL
opencv_dnn
WRAP
python
java
objc
js
)
+50
View File
@@ -0,0 +1,50 @@
@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},
url = {https://april.eecs.umich.edu/pdfs/wang2016iros.pdf}
}
@mastersthesis{Xiangmin2015research,
title={Research on Barcode Recognition Technology In a Complex Background},
author={Xiangmin, Wang},
year={2015},
school={Huazhong University of Science and Technology}
}
@article{bazen2002systematic,
title={Systematic methods for the computation of the directional fields and singular points of fingerprints},
author={Bazen, Asker M and Gerez, Sabih H},
journal={IEEE transactions on pattern analysis and machine intelligence},
volume={24},
number={7},
pages={905--919},
year={2002},
publisher={IEEE}
}
@article{kass1987analyzing,
title={Analyzing oriented patterns},
author={Kass, Michael and Witkin, Andrew},
journal={Computer vision, graphics, and image processing},
volume={37},
number={3},
pages={362--385},
year={1987},
publisher={Elsevier}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

@@ -0,0 +1,597 @@
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
// Copyright (C) 2013, OpenCV Foundation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#ifndef OPENCV_OBJDETECT_HPP
#define OPENCV_OBJDETECT_HPP
#include "opencv2/core.hpp"
#include "opencv2/features.hpp"
#include "opencv2/objdetect/aruco_detector.hpp"
#include "opencv2/objdetect/graphical_code_detector.hpp"
#include "opencv2/objdetect/mcc_checker_detector.hpp"
/**
@defgroup objdetect Object Detection
@{
@defgroup objdetect_barcode Barcode detection and decoding
@defgroup objdetect_qrcode QRCode detection and encoding
@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 marker 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).
<br>
@warning In OpenCV, the order of the returned corners locations for the AprilTag family is not aligned with the ArUco one.\n
Note that this order is also different from the convention adopted by the official [AprilTag library](https://github.com/AprilRobotics/apriltag/).
![](pics/AprilTag_corners_comparison_opencv_april.png) { width=80% }
<br>
An overview of the supported ArUco markers family is visible in the following image:
![](pics/ArUco_family.png) { width=80% }
<br>
An overview of the supported AprilTag markers family is visible in the following image:
![](pics/AprilTag_family.png) { width=80% }
@note The generated images (in the above picture) using @ref aruco::generateImageMarker for the AprilTag markers have been
rotated by 180 degree in order to match the official AprilTag images.
When using the @ref aruco::generateImageMarker function, it will output by default a different image from the official AprilTag convention,
see the [AprilRobotics/apriltag-imgs](https://github.com/AprilRobotics/apriltag-imgs) repository.
This is the reason why you see a different corners order between ArUco and AprilTag in the above image.
<br>
For the ArUco marker family, the recommended family is the DICT_ARUCO_MIP_36h12 one, [see](https://stackoverflow.com/a/51511558).
In general, a smaller marker family (e.g. `4x4` vs `6x6`) should give you a better detection rate with respect to the camera distance,
at the expense of having more probability to have issues with false detection or marker id decoding error.
The number of marker ids in a family is also something to take into account with respect to the application use case and the ability
to correct wrong bits during the marker id decoding process.
You can download some pregenerated MIP_36h12 ArUco marker images from:
- https://sourceforge.net/projects/aruco/files/
- or use the `samples/cpp/tutorial_code/objectDetection/create_marker.cpp` sample to generate the marker image for your
desired marker family (which uses the @ref aruco::generateImageMarker function)
For the AprilTag family, you can find some pregenerated marker images in the
[AprilRobotics/apriltag-imgs](https://github.com/AprilRobotics/apriltag-imgs) repository.
@note For accurate corners location extraction, a white border (to have a strong gradient between white and black transition) around the marker is important.
This is necessary to precisely extract the marker contour in difficult conditions such as bad illumination, confusing color background, etc.
<br>
There are multiple parameters which can be tweaked to improve the marker detection rate or to be adapted to your use case (e.g. image resolution).
Please refer to the:
- @ref aruco::DetectorParameters
- "Detector Parameters" section in the @ref tutorial_aruco_detection tutorial or in the @ref tutorial_aruco_faq page
- [ArUco Library Documentation](https://drive.google.com/file/d/1OiavRVYVJ-WH88sQg1LUsh8CuJZUQyrX) for additional information from the ArUco library
The corner refinement method can be changed according to the @ref aruco::CornerRefineMethod to improve the corners location accuracy
at the expense of more computation time.
<br>
To estimate the marker pose with respect to the camera frame, we recommend you to look at the following sources of information:
- @ref tutorial_aruco_detection for a tutorial about ArUco markers detection
- @ref _3d for some theoretical background about the pinhole camera model and the @ref calib3d_solvePnP page
- @ref solvePnP, @ref solvePnPGeneric, @ref solveP3P for the relevant pose estimation methods
@}
@}
*/
namespace cv
{
//! @addtogroup objdetect_qrcode
//! @{
/** @brief QR code encoder. */
class CV_EXPORTS_W QRCodeEncoder {
protected:
QRCodeEncoder(); // use ::create()
public:
virtual ~QRCodeEncoder();
enum EncodeMode {
MODE_AUTO = -1,
MODE_NUMERIC = 1, // 0b0001
MODE_ALPHANUMERIC = 2, // 0b0010
MODE_BYTE = 4, // 0b0100
MODE_ECI = 7, // 0b0111
MODE_KANJI = 8, // 0b1000
MODE_STRUCTURED_APPEND = 3 // 0b0011
};
enum CorrectionLevel {
CORRECT_LEVEL_L = 0,
CORRECT_LEVEL_M = 1,
CORRECT_LEVEL_Q = 2,
CORRECT_LEVEL_H = 3
};
enum ECIEncodings {
ECI_SHIFT_JIS = 20,
ECI_UTF8 = 26,
};
/** @brief QR code encoder parameters. */
struct CV_EXPORTS_W_SIMPLE Params
{
CV_WRAP Params();
//! The optional version of QR code (by default - maximum possible depending on the length of the string).
CV_PROP_RW int version;
//! The optional level of error correction (by default - the lowest).
CV_PROP_RW QRCodeEncoder::CorrectionLevel correction_level;
//! The optional encoding mode - Numeric, Alphanumeric, Byte, Kanji, ECI or Structured Append.
CV_PROP_RW QRCodeEncoder::EncodeMode mode;
//! The optional number of QR codes to generate in Structured Append mode.
CV_PROP_RW int structure_number;
};
/** @brief Constructor
@param parameters QR code encoder parameters QRCodeEncoder::Params
*/
static CV_WRAP
Ptr<QRCodeEncoder> create(const QRCodeEncoder::Params& parameters = QRCodeEncoder::Params());
/** @brief Generates QR code from input string.
@param encoded_info Input string to encode.
@param qrcode Generated QR code.
*/
CV_WRAP virtual void encode(const String& encoded_info, OutputArray qrcode) = 0;
/** @brief Generates QR code from input string in Structured Append mode. The encoded message is splitting over a number of QR codes.
@param encoded_info Input string to encode.
@param qrcodes Vector of generated QR codes.
*/
CV_WRAP virtual void encodeStructuredAppend(const String& encoded_info, OutputArrayOfArrays qrcodes) = 0;
};
/** @brief QR code detector. */
class CV_EXPORTS_W_SIMPLE QRCodeDetector : public GraphicalCodeDetector
{
public:
CV_WRAP QRCodeDetector();
/** @brief sets the epsilon used during the horizontal scan of QR code stop marker detection.
@param epsX Epsilon neighborhood, which allows you to determine the horizontal pattern
of the scheme 1:1:3:1:1 according to QR code standard.
*/
CV_WRAP QRCodeDetector& setEpsX(double epsX);
/** @brief sets the epsilon used during the vertical scan of QR code stop marker detection.
@param epsY Epsilon neighborhood, which allows you to determine the vertical pattern
of the scheme 1:1:3:1:1 according to QR code standard.
*/
CV_WRAP QRCodeDetector& setEpsY(double epsY);
/** @brief use markers to improve the position of the corners of the QR code
*
* alignmentMarkers using by default
*/
CV_WRAP QRCodeDetector& setUseAlignmentMarkers(bool useAlignmentMarkers);
/** @brief Decodes QR code on a curved surface in image once it's found by the detect() method.
Returns UTF8-encoded output string or empty string if the code cannot be decoded.
@param img grayscale or color (BGR) image containing QR code.
@param points Quadrangle vertices found by detect() method (or some other algorithm).
@param straight_qrcode The optional output image containing rectified and binarized QR code
*/
CV_WRAP cv::String decodeCurved(InputArray img, InputArray points, OutputArray straight_qrcode = noArray());
/** @brief Both detects and decodes QR code on a curved surface
@param img grayscale or color (BGR) image containing QR code.
@param points optional output array of vertices of the found QR code quadrangle. Will be empty if not found.
@param straight_qrcode The optional output image containing rectified and binarized QR code
*/
CV_WRAP std::string detectAndDecodeCurved(InputArray img, OutputArray points=noArray(),
OutputArray straight_qrcode = noArray());
/** @brief Returns a kind of encoding for the decoded info from the latest @ref decode or @ref detectAndDecode call
@param codeIdx an index of the previously decoded QR code.
When @ref decode or @ref detectAndDecode is used, valid value is zero.
For @ref decodeMulti or @ref detectAndDecodeMulti use indices corresponding to the output order.
*/
CV_WRAP QRCodeEncoder::ECIEncodings getEncoding(int codeIdx = 0);
};
/** @brief QR code detector based on Aruco markers detection code. */
class CV_EXPORTS_W_SIMPLE QRCodeDetectorAruco : public GraphicalCodeDetector {
public:
CV_WRAP QRCodeDetectorAruco();
struct CV_EXPORTS_W_SIMPLE Params {
CV_WRAP Params();
/** @brief The minimum allowed pixel size of a QR module in the smallest image in the image pyramid, default 4.f */
CV_PROP_RW float minModuleSizeInPyramid;
/** @brief The maximum allowed relative rotation for finder patterns in the same QR code, default pi/12 */
CV_PROP_RW float maxRotation;
/** @brief The maximum allowed relative mismatch in module sizes for finder patterns in the same QR code, default 1.75f */
CV_PROP_RW float maxModuleSizeMismatch;
/** @brief The maximum allowed module relative mismatch for timing pattern module, default 2.f
*
* If relative mismatch of timing pattern module more this value, penalty points will be added.
* If a lot of penalty points are added, QR code will be rejected. */
CV_PROP_RW float maxTimingPatternMismatch;
/** @brief The maximum allowed percentage of penalty points out of total pins in timing pattern, default 0.4f */
CV_PROP_RW float maxPenalties;
/** @brief The maximum allowed relative color mismatch in the timing pattern, default 0.2f*/
CV_PROP_RW float maxColorsMismatch;
/** @brief The algorithm find QR codes with almost minimum timing pattern score and minimum size, default 0.9f
*
* The QR code with the minimum "timing pattern score" and minimum "size" is selected as the best QR code.
* If for the current QR code "timing pattern score" * scaleTimingPatternScore < "previous timing pattern score" and "size" < "previous size", then
* current QR code set as the best QR code. */
CV_PROP_RW float scaleTimingPatternScore;
};
/** @brief QR code detector constructor for Aruco-based algorithm. See cv::QRCodeDetectorAruco::Params */
CV_WRAP explicit QRCodeDetectorAruco(const QRCodeDetectorAruco::Params& params);
/** @brief Detector parameters getter. See cv::QRCodeDetectorAruco::Params */
CV_WRAP const QRCodeDetectorAruco::Params& getDetectorParameters() const;
/** @brief Detector parameters setter. See cv::QRCodeDetectorAruco::Params */
CV_WRAP QRCodeDetectorAruco& setDetectorParameters(const QRCodeDetectorAruco::Params& params);
/** @brief Aruco detector parameters are used to search for the finder patterns. */
CV_WRAP const aruco::DetectorParameters& getArucoParameters() const;
/** @brief Aruco detector parameters are used to search for the finder patterns. */
CV_WRAP void setArucoParameters(const aruco::DetectorParameters& params);
};
enum { CALIB_CB_ADAPTIVE_THRESH = 1,
CALIB_CB_NORMALIZE_IMAGE = 2,
CALIB_CB_FILTER_QUADS = 4,
CALIB_CB_FAST_CHECK = 8,
CALIB_CB_EXHAUSTIVE = 16,
CALIB_CB_ACCURACY = 32,
CALIB_CB_LARGER = 64,
CALIB_CB_MARKER = 128,
CALIB_CB_PLAIN = 256
};
enum { CALIB_CB_SYMMETRIC_GRID = 1,
CALIB_CB_ASYMMETRIC_GRID = 2,
CALIB_CB_CLUSTERING = 4
};
/** @brief Finds the positions of internal corners of the chessboard.
@param image Source chessboard view. It must be an 8-bit grayscale or color image.
@param patternSize Number of inner corners per a chessboard row and column
( patternSize = cv::Size(points_per_row,points_per_column) = cv::Size(columns,rows) ).
@param corners Output array of detected corners.
@param flags Various operation flags that can be zero or a combination of the following values:
- @ref CALIB_CB_ADAPTIVE_THRESH Use adaptive thresholding to convert the image to black
and white, rather than a fixed threshold level (computed from the average image brightness).
- @ref CALIB_CB_NORMALIZE_IMAGE Normalize the image gamma with equalizeHist before
applying fixed or adaptive thresholding.
- @ref CALIB_CB_FILTER_QUADS Use additional criteria (like contour area, perimeter,
square-like shape) to filter out false quads extracted at the contour retrieval stage.
- @ref CALIB_CB_FAST_CHECK Run a fast check on the image that looks for chessboard corners,
and shortcut the call if none is found. This can drastically speed up the call in the
degenerate condition when no chessboard is observed.
- @ref CALIB_CB_PLAIN All other flags are ignored. The input image is taken as is.
No image processing is done to improve to find the checkerboard. This has the effect of speeding up the
execution of the function but could lead to not recognizing the checkerboard if the image
is not previously binarized in the appropriate manner.
@return True if all of the corners are found and placed in a certain order (row by row,
left to right in every row). Otherwise, if the function fails to find all the corners or reorder them,
it returns false.
The function attempts to determine whether the input image is a view of the chessboard pattern and
locate the internal chessboard corners. For example, a regular chessboard has 8 x 8 squares and
7 x 7 internal corners, that is, points where the black squares touch each other. The detected
coordinates are approximate, and to determine their positions more accurately, the function
calls #cornerSubPix. You also may use the function #cornerSubPix with different parameters if
returned coordinates are not accurate enough.
Sample usage of detecting and drawing chessboard corners: :
@code
Size patternsize(8,6); //interior number of corners
Mat gray = ....; //source image
vector<Point2f> corners; //this will be filled by the detected corners
//CALIB_CB_FAST_CHECK saves a lot of time on images
//that do not contain any chessboard corners
bool patternfound = findChessboardCorners(gray, patternsize, corners,
CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE
+ CALIB_CB_FAST_CHECK);
if(patternfound)
cornerSubPix(gray, corners, Size(11, 11), Size(-1, -1),
TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));
drawChessboardCorners(img, patternsize, Mat(corners), patternfound);
@endcode
@note The function requires white space (like a square-thick border, the wider the better) around
the board to make the detection more robust in various environments. Otherwise, if there is no
border and the background is dark, the outer black squares cannot be segmented properly and so the
square grouping and ordering algorithm fails.
Use the `generate_pattern.py` Python script (@ref tutorial_camera_calibration_pattern)
to create the desired checkerboard pattern.
*/
CV_EXPORTS_W bool findChessboardCorners( InputArray image, Size patternSize, OutputArray corners,
int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE );
/** @brief Checks whether the image contains chessboard of the specific size or not.
@param img Source chessboard view.
@param size Size of the chessboard.
@return Whether a chessboard was found.
*/
CV_EXPORTS_W bool checkChessboard(InputArray img, Size size);
/** @brief Finds the positions of internal corners of the chessboard using a sector based approach.
@param image Source chessboard view. It must be an 8-bit grayscale or color image.
@param patternSize Number of inner corners per a chessboard row and column
( patternSize = cv::Size(points_per_row,points_per_column) = cv::Size(columns,rows) ).
@param corners Output array of detected corners.
@param flags Various operation flags that can be zero or a combination of the following values:
- @ref CALIB_CB_NORMALIZE_IMAGE Normalize the image gamma with equalizeHist before detection.
- @ref CALIB_CB_EXHAUSTIVE Run an exhaustive search to improve detection rate.
- @ref CALIB_CB_ACCURACY Up sample input image to improve sub-pixel accuracy due to aliasing effects.
- @ref CALIB_CB_LARGER The detected pattern is allowed to be larger than patternSize (see description).
- @ref CALIB_CB_MARKER The detected pattern must have a marker (see description).
This should be used if an accurate camera calibration is required.
@param meta Optional output array of detected corners (CV_8UC1 and size = cv::Size(columns,rows)).
Each entry stands for one corner of the pattern and can have one of the following values:
- 0 = no meta data attached
- 1 = left-top corner of a black cell
- 2 = left-top corner of a white cell
- 3 = left-top corner of a black cell with a white marker dot
- 4 = left-top corner of a white cell with a black marker dot (pattern origin in case of markers otherwise first corner)
The function is analog to #findChessboardCorners but uses a localized radon
transformation approximated by box filters being more robust to all sort of
noise, faster on larger images and is able to directly return the sub-pixel
position of the internal chessboard corners. The Method is based on the paper
@cite duda2018 "Accurate Detection and Localization of Checkerboard Corners for
Calibration" demonstrating that the returned sub-pixel positions are more
accurate than the one returned by cornerSubPix allowing a precise camera
calibration for demanding applications.
In the case, the flags @ref CALIB_CB_LARGER or @ref CALIB_CB_MARKER are given,
the result can be recovered from the optional meta array. Both flags are
helpful to use calibration patterns exceeding the field of view of the camera.
These oversized patterns allow more accurate calibrations as corners can be
utilized, which are as close as possible to the image borders. For a
consistent coordinate system across all images, the optional marker (see image
below) can be used to move the origin of the board to the location where the
black circle is located.
@note The function requires a white boarder with roughly the same width as one
of the checkerboard fields around the whole board to improve the detection in
various environments. In addition, because of the localized radon
transformation it is beneficial to use round corners for the field corners
which are located on the outside of the board. The following figure illustrates
a sample checkerboard optimized for the detection. However, any other checkerboard
can be used as well.
Use the `generate_pattern.py` Python script (@ref tutorial_camera_calibration_pattern)
to create the corresponding checkerboard pattern:
\image html pics/checkerboard_radon.png width=60%
*/
CV_EXPORTS_AS(findChessboardCornersSBWithMeta)
bool findChessboardCornersSB(InputArray image,Size patternSize, OutputArray corners,
int flags,OutputArray meta);
/** @overload */
CV_EXPORTS_W inline
bool findChessboardCornersSB(InputArray image, Size patternSize, OutputArray corners,
int flags = 0)
{
return findChessboardCornersSB(image, patternSize, corners, flags, noArray());
}
/** @brief Estimates the sharpness of a detected chessboard.
Image sharpness, as well as brightness, are a critical parameter for accuracte
camera calibration. For accessing these parameters for filtering out
problematic calibraiton images, this method calculates edge profiles by traveling from
black to white chessboard cell centers. Based on this, the number of pixels is
calculated required to transit from black to white. This width of the
transition area is a good indication of how sharp the chessboard is imaged
and should be below ~3.0 pixels.
@param image Gray image used to find chessboard corners
@param patternSize Size of a found chessboard pattern
@param corners Corners found by #findChessboardCornersSB
@param rise_distance Rise distance 0.8 means 10% ... 90% of the final signal strength
@param vertical By default edge responses for horizontal lines are calculated
@param sharpness Optional output array with a sharpness value for calculated edge responses (see description)
The optional sharpness array is of type CV_32FC1 and has for each calculated
profile one row with the following five entries:
* 0 = x coordinate of the underlying edge in the image
* 1 = y coordinate of the underlying edge in the image
* 2 = width of the transition area (sharpness)
* 3 = signal strength in the black cell (min brightness)
* 4 = signal strength in the white cell (max brightness)
@return Scalar(average sharpness, average min brightness, average max brightness,0)
*/
CV_EXPORTS_W Scalar estimateChessboardSharpness(InputArray image, Size patternSize, InputArray corners,
float rise_distance=0.8F,bool vertical=false,
OutputArray sharpness=noArray());
//! finds subpixel-accurate positions of the chessboard corners
CV_EXPORTS_W bool find4QuadCornerSubpix( InputArray img, InputOutputArray corners, Size region_size );
/** @brief Renders the detected chessboard corners.
@param image Destination image. It must be an 8-bit color image.
@param patternSize Number of inner corners per a chessboard row and column
(patternSize = cv::Size(points_per_row,points_per_column)).
@param corners Array of detected corners, the output of #findChessboardCorners.
@param patternWasFound Parameter indicating whether the complete board was found or not. The
return value of #findChessboardCorners should be passed here.
The function draws individual chessboard corners detected either as red circles if the board was not
found, or as colored corners connected with lines if the board was found.
*/
CV_EXPORTS_W void drawChessboardCorners( InputOutputArray image, Size patternSize,
InputArray corners, bool patternWasFound );
struct CV_EXPORTS_W_SIMPLE CirclesGridFinderParameters
{
CV_WRAP CirclesGridFinderParameters();
CV_PROP_RW cv::Size2f densityNeighborhoodSize;
CV_PROP_RW float minDensity;
CV_PROP_RW int kmeansAttempts;
CV_PROP_RW int minDistanceToAddKeypoint;
CV_PROP_RW int keypointScale;
CV_PROP_RW float minGraphConfidence;
CV_PROP_RW float vertexGain;
CV_PROP_RW float vertexPenalty;
CV_PROP_RW float existingVertexGain;
CV_PROP_RW float edgeGain;
CV_PROP_RW float edgePenalty;
CV_PROP_RW float convexHullFactor;
CV_PROP_RW float minRNGEdgeSwitchDist;
enum GridType
{
SYMMETRIC_GRID, ASYMMETRIC_GRID
};
CV_PROP_RW GridType gridType;
CV_PROP_RW float squareSize; //!< Distance between two adjacent points. Used by CALIB_CB_CLUSTERING.
CV_PROP_RW float maxRectifiedDistance; //!< Max deviation from prediction. Used by CALIB_CB_CLUSTERING.
};
/** @brief Finds centers in the grid of circles.
@param image grid view of input circles; it must be an 8-bit grayscale or color image.
@param patternSize number of circles per row and column
( patternSize = Size(points_per_row, points_per_column) ).
@param centers output array of detected centers.
@param flags various operation flags that can be one of the following values:
- @ref CALIB_CB_SYMMETRIC_GRID uses symmetric pattern of circles.
- @ref CALIB_CB_ASYMMETRIC_GRID uses asymmetric pattern of circles.
- @ref CALIB_CB_CLUSTERING uses a special algorithm for grid detection. It is more robust to
perspective distortions but much more sensitive to background clutter.
@param blobDetector feature detector that finds blobs like dark circles on light background.
If `blobDetector` is NULL then `image` represents Point2f array of candidates.
@param parameters struct for finding circles in a grid pattern.
return True if all of the centers have been found and they have been placed in a certain order
(row by row, left to right in every row). Otherwise, if the function fails to find all the corners
or reorder them, it returns false.
The function attempts to determine whether the input image contains a grid of circles. If it is, the
function locates centers of the circles.
Sample usage of detecting and drawing the centers of circles: :
@code
Size patternsize(7,7); //number of centers
Mat gray = ...; //source image
vector<Point2f> centers; //this will be filled by the detected centers
bool patternfound = findCirclesGrid(gray, patternsize, centers);
drawChessboardCorners(img, patternsize, Mat(centers), patternfound);
@endcode
@note The function requires white space (like a square-thick border, the wider the better) around
the board to make the detection more robust in various environments.
*/
CV_EXPORTS_W bool findCirclesGrid( InputArray image, Size patternSize,
OutputArray centers, int flags,
const Ptr<FeatureDetector> &blobDetector,
const CirclesGridFinderParameters& parameters);
/** @overload */
CV_EXPORTS_W bool findCirclesGrid( InputArray image, Size patternSize,
OutputArray centers, int flags = CALIB_CB_SYMMETRIC_GRID,
const Ptr<FeatureDetector> &blobDetector = cv::SimpleBlobDetector::create());
//! @}
}
#include "opencv2/objdetect/face.hpp"
#include "opencv2/objdetect/charuco_detector.hpp"
#include "opencv2/objdetect/barcode.hpp"
#endif
@@ -0,0 +1,199 @@
// 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, can be used in solvePnP()
*
* @param detectedCorners List of detected marker corners of the board.
* For cv::Board and cv::GridBoard the method expects std::vector<std::vector<Point2f>> or std::vector<Mat> with Aruco marker corners.
* For cv::CharucoBoard the method expects std::vector<Point2f> or Mat with ChAruco corners (chess board corners matched with Aruco markers).
*
* @param detectedIds List of identifiers for each marker or charuco corner.
* For any Board class the method expects std::vector<int> or Mat.
*
* @param objPoints Vector of marker points in the board coordinate space.
* For any Board class the method expects std::vector<cv::Point3f> objectPoints or cv::Mat
*
* @param imgPoints Vector of marker points in the image coordinate space.
* For any Board class the method expects std::vector<cv::Point2f> objectPoints or cv::Mat
*
* @sa solvePnP
*/
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());
/** @brief set legacy chessboard pattern.
*
* Legacy setting creates chessboard patterns starting with a white box in the upper left corner
* if there is an even row count of chessboard boxes, otherwise it starts with a black box.
* This setting ensures compatibility to patterns created with OpenCV versions prior OpenCV 4.6.0.
* See https://github.com/opencv/opencv/issues/23152.
*
* Default value: false.
*/
CV_WRAP void setLegacyPattern(bool legacyPattern);
CV_WRAP bool getLegacyPattern() const;
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, for each charuco corner, nearest marker index in ids array
*/
CV_PROP std::vector<std::vector<int> > getNearestMarkerIdx() const;
/** @brief get CharucoBoard::nearestMarkerCorners, for each charuco corner, nearest marker corner id of each marker
*/
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,495 @@
// 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
};
static constexpr float DEFAULT_VALID_BIT_ID_THRESHOLD{0.49f};
/** @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.125;
cornerRefinementMethod = (int)CORNER_REFINE_NONE;
cornerRefinementWinSize = 5;
relativeCornerRefinmentWinSize = 0.3f;
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;
validBitIdThreshold = DEFAULT_VALID_BIT_ID_THRESHOLD;
}
/** @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 average distance between the corners of the two markers to be grouped (default 0.125).
*
* The rate is relative to the smaller perimeter of the two markers.
* Two markers are grouped if average distance between the corners of the two markers is less than
* min(MarkerPerimeter1, MarkerPerimeter2)*minMarkerDistanceRate.
*
* default value is 0.125 because 0.125*MarkerPerimeter = (MarkerPerimeter / 4) * 0.5 = half the side of the marker.
*
* @note default value was changed from 0.05 after 4.8.1 release, because the filtering algorithm has been changed.
* Now a few candidates from the same group can be added to the list of candidates if they are far from each other.
* @sa minGroupDistance.
*/
CV_PROP_RW double minMarkerDistanceRate;
/** @brief minimum average distance between the corners of the two markers in group to add them to the list of candidates
*
* The average distance between the corners of the two markers is calculated relative to its module size (default 0.21).
*/
CV_PROP_RW float minGroupDistance = 0.21f;
/** @brief default value CORNER_REFINE_NONE */
CV_PROP_RW int cornerRefinementMethod;
/** @brief maximum window size for the corner refinement process (in pixels) (default 5).
*
* The window size may decrease if the ArUco marker is too small, check relativeCornerRefinmentWinSize.
* The final window size is calculated as:
* min(cornerRefinementWinSize, averageArucoModuleSize*relativeCornerRefinmentWinSize),
* where averageArucoModuleSize is average module size of ArUco marker in pixels.
* (ArUco marker is composed of black and white modules)
*/
CV_PROP_RW int cornerRefinementWinSize;
/** @brief Dynamic window size for corner refinement relative to Aruco module size (default 0.3).
*
* The final window size is calculated as:
* min(cornerRefinementWinSize, averageArucoModuleSize*relativeCornerRefinmentWinSize),
* where averageArucoModuleSize is average module size of ArUco marker in pixels.
* (ArUco marker is composed of black and white modules)
* In the case of markers located far from each other, it may be useful to increase the value of the parameter to 0.4-0.5.
* In the case of markers located close to each other, it may be useful to decrease the parameter value to 0.1-0.2.
*/
CV_PROP_RW float relativeCornerRefinmentWinSize;
/// 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 minimum 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 maximum 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;
/// range [0,1], define the acceptable threshold when comparing the detected marker to the dictionary during marker identification.
CV_PROP_RW float validBitIdThreshold;
};
/** @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 errorCorrectionRate 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 possible 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 ArucoDetector constructor for multiple dictionaries
*
* @param dictionaries indicates the type of markers that will be searched. Empty dictionaries will throw an error.
* @param detectorParams marker detection parameters
* @param refineParams marker refine detection parameters
*/
CV_WRAP ArucoDetector(const std::vector<Dictionary> &dictionaries,
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 first specified 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 corresponding 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 Marker detection with confidence computation
*
* @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 markersConfidence contains the normalized confidence [0;1] of the markers' detection,
* defined as 1 minus the normalized uncertainty (percentage of incorrect pixel detections),
* with 1 describing a pixel perfect detection. The confidence values are of type float
* (e.g. std::vector<float>)
* @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 first specified 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 corresponding camera model, if camera parameters are known
* @sa undistort, estimatePoseSingleMarkers, estimatePoseBoard
*/
CV_WRAP void detectMarkersWithConfidence(InputArray image, OutputArrayOfArrays corners, OutputArray ids, OutputArray markersConfidence,
OutputArrayOfArrays rejectedImgPoints = noArray()) const;
/** @brief Refine 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.
* @note This function assumes that the board only contains markers from one dictionary, so only the
* first configured dictionary is used. It has to match the dictionary of the board to work properly.
*/
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;
/** @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.
* @param dictIndices vector of dictionary indices for each detected marker. Use getDictionaries() to get the
* list of corresponding dictionaries.
*
* Performs marker detection in the input image. Only markers included in the specific dictionaries
* 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 corresponding camera model, if camera parameters are known
* @sa undistort, estimatePoseSingleMarkers, estimatePoseBoard
*/
CV_WRAP void detectMarkersMultiDict(InputArray image, OutputArrayOfArrays corners, OutputArray ids,
OutputArrayOfArrays rejectedImgPoints = noArray(), OutputArray dictIndices = noArray()) const;
/** @brief Returns first dictionary from internal list used for marker detection.
*
* @return The first dictionary from the configured ArucoDetector.
*/
CV_WRAP const Dictionary& getDictionary() const;
/** @brief Sets and replaces the first dictionary in internal list to be used for marker detection.
*
* @param dictionary The new dictionary that will replace the first dictionary in the internal list.
*/
CV_WRAP void setDictionary(const Dictionary& dictionary);
/** @brief Returns all dictionaries currently used for marker detection as a vector.
*
* @return A std::vector<Dictionary> containing all dictionaries used by the ArucoDetector.
*/
CV_WRAP std::vector<Dictionary> getDictionaries() const;
/** @brief Sets the entire collection of dictionaries to be used for marker detection, replacing any existing dictionaries.
*
* @param dictionaries A std::vector<Dictionary> containing the new set of dictionaries to be used.
*
* Configures the ArucoDetector to use the provided vector of dictionaries for marker detection.
* This method replaces any dictionaries that were previously set.
* @note Setting an empty vector of dictionaries will throw an error.
*/
CV_WRAP void setDictionaries(const std::vector<Dictionary>& dictionaries);
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,164 @@
// 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 is a set of unique ArUco markers of the same size
*
* `bytesList` storing as 2-dimensions Mat with 4-th channels (CV_8UC4 type was used) and contains the marker codewords where:
* - bytesList.rows is the dictionary size
* - each marker is encoded using `nbytes = ceil(markerSize*markerSize/8.)` bytes
* - each row contains all 4 rotations of the marker, so its length is `4*nbytes`
* - the byte order in the bytesList[i] row:
* `//bytes without rotation/bytes with rotation 1/bytes with rotation 2/bytes with rotation 3//`
* So `bytesList.ptr(i)[k*nbytes + j]` is the j-th byte of i-th marker, in its k-th rotation.
* @note Python bindings generate matrix with shape of bytesList `dictionary_size x nbytes x 4`,
* but it should be indexed like C++ version. Python example for j-th byte of i-th marker, in its k-th rotation:
* `aruco_dict.bytesList[id].ravel()[k*nbytes + j]`
*/
class CV_EXPORTS_W_SIMPLE Dictionary {
public:
CV_PROP_RW Mat bytesList; ///< marker code information. See class description for more details
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();
/** @brief Basic ArUco dictionary constructor
*
* @param bytesList bits for all ArUco markers in dictionary see memory layout in the class description
* @param _markerSize ArUco marker size in units
* @param maxcorr maximum number of bits that can be corrected
*/
CV_WRAP Dictionary(const Mat &bytesList, int _markerSize, int maxcorr = 0);
/** @brief Read a new dictionary from FileNode.
*
* Dictionary example in YAML 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.
*
* Returns reference to the marker id in the dictionary (if any) and its rotation.
*/
CV_WRAP bool identify(const Mat &onlyBits, CV_OUT int &idx, CV_OUT int &rotation, double maxCorrectionRate) const;
/** @brief Given a matrix of pixel ratio raging from 0 to 1. Returns whether if marker is identified or not.
*
* Returns reference to the marker id in the dictionary (if any) and its rotation.
*/
CV_WRAP bool identify(const Mat &onlyCellPixelRatio, CV_OUT int &idx, CV_OUT int &rotation, double maxCorrectionRate, float validBitIdThreshold) const;
/** @brief Returns Hamming distance of the input bits to the specific id.
*
* If `allRotations` flag is set, the four possible marker rotations 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 with 4 marker 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, int rotationId = 0);
/** @brief Get ground truth bits float
*/
CV_WRAP Mat getMarkerBits(int markerId, int rotationId = 0) const;
};
/** @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
DICT_ARUCO_MIP_36h12 ///< 6x6 bits, minimum hamming distance between any two codes = 12, 250 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,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) 2020-2021 darkliang wangberlinT Certseeds
#ifndef OPENCV_OBJDETECT_BARCODE_HPP
#define OPENCV_OBJDETECT_BARCODE_HPP
#include <opencv2/core.hpp>
#include <opencv2/objdetect/graphical_code_detector.hpp>
namespace cv {
namespace barcode {
//! @addtogroup objdetect_barcode
//! @{
class CV_EXPORTS_W_SIMPLE BarcodeDetector : public cv::GraphicalCodeDetector
{
public:
/** @brief Initialize the BarcodeDetector. Super resolution is disabled.
*/
CV_WRAP BarcodeDetector();
/** @brief Initialize the BarcodeDetector with a Super Resolution model.
*
* Loads a Super Resolution DNN model in ONNX format, used to upscale small/low-quality
* barcode crops before decoding for better quality.
*
* @note Caffe models (`sr.prototxt` / `sr.caffemodel`) are no longer supported; convert
* the model to ONNX (a converted `sr.onnx` is available from
* https://github.com/WeChatCV/opencv_3rdparty/tree/wechat_qrcode).
*
* @param super_resolution_model_path path to a single-file ONNX Super Resolution model.
*/
CV_WRAP BarcodeDetector(CV_WRAP_FILE_PATH const std::string &super_resolution_model_path);
~BarcodeDetector();
/** @brief Decodes barcode in image once it's found by the detect() method.
*
* @param img grayscale or color (BGR) image containing bar code.
* @param points vector of rotated rectangle vertices found by detect() method (or some other algorithm).
* For N detected barcodes, the dimensions of this array should be [N][4].
* Order of four points in vector<Point2f> is bottomLeft, topLeft, topRight, bottomRight.
* @param decoded_info UTF8-encoded output vector of string or empty vector of string if the codes cannot be decoded.
* @param decoded_type vector strings, specifies the type of these barcodes
* @return true if at least one valid barcode have been found
*/
CV_WRAP bool decodeWithType(InputArray img,
InputArray points,
CV_OUT std::vector<std::string> &decoded_info,
CV_OUT std::vector<std::string> &decoded_type) const;
/** @brief Both detects and decodes barcode
* @param img grayscale or color (BGR) image containing barcode.
* @param decoded_info UTF8-encoded output vector of string(s) or empty vector of string if the codes cannot be decoded.
* @param decoded_type vector of strings, specifies the type of these barcodes
* @param points optional output vector of vertices of the found barcode rectangle. Will be empty if not found.
* @return true if at least one valid barcode have been found
*/
CV_WRAP bool detectAndDecodeWithType(InputArray img,
CV_OUT std::vector<std::string> &decoded_info,
CV_OUT std::vector<std::string> &decoded_type,
OutputArray points = noArray()) const;
/** @brief Get detector downsampling threshold.
*
* @return detector downsampling threshold
*/
CV_WRAP double getDownsamplingThreshold() const;
/** @brief Set detector downsampling threshold.
*
* By default, the detect method resizes the input image to this limit if the smallest image size is is greater than the threshold.
* Increasing this value can improve detection accuracy and the number of results at the expense of performance.
* Correlates with detector scales. Setting this to a large value will disable downsampling.
* @param thresh downsampling limit to apply (default 512)
* @see setDetectorScales
*/
CV_WRAP BarcodeDetector& setDownsamplingThreshold(double thresh);
/** @brief Returns detector box filter sizes.
*
* @param sizes output parameter for returning the sizes.
*/
CV_WRAP void getDetectorScales(CV_OUT std::vector<float>& sizes) const;
/** @brief Set detector box filter sizes.
*
* Adjusts the value and the number of box filters used in the detect step.
* The filter sizes directly correlate with the expected line widths for a barcode. Corresponds to expected barcode distance.
* If the downsampling limit is increased, filter sizes need to be adjusted in an inversely proportional way.
* @param sizes box filter sizes, relative to minimum dimension of the image (default [0.01, 0.03, 0.06, 0.08])
*/
CV_WRAP BarcodeDetector& setDetectorScales(const std::vector<float>& sizes);
/** @brief Get detector gradient magnitude threshold.
*
* @return detector gradient magnitude threshold.
*/
CV_WRAP double getGradientThreshold() const;
/** @brief Set detector gradient magnitude threshold.
*
* Sets the coherence threshold for detected bounding boxes.
* Increasing this value will generate a closer fitted bounding box width and can reduce false-positives.
* Values between 16 and 1024 generally work, while too high of a value will remove valid detections.
* @param thresh gradient magnitude threshold (default 64).
*/
CV_WRAP BarcodeDetector& setGradientThreshold(double thresh);
};
//! @}
}} // cv::barcode::
#endif // OPENCV_OBJDETECT_BARCODE_HPP
@@ -0,0 +1,161 @@
// 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 {
CV_WRAP CharucoParameters() {
minMarkers = 2;
tryRefineMarkers = false;
checkMarkers = true;
}
/// 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;
/// run check to verify that markers belong to the same board, default true
CV_PROP_RW bool checkMarkers;
};
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 necessary 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
* @note After OpenCV 4.6.0, there was an incompatible change in the ChArUco pattern generation algorithm for even row counts.
* Use cv::aruco::CharucoBoard::setLegacyPattern() to ensure compatibility with patterns created using OpenCV versions prior to 4.6.0.
* For more information, see the issue: https://github.com/opencv/opencv/issues/23152
*/
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(),
InputOutputArray 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,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_FACE_HPP
#define OPENCV_OBJDETECT_FACE_HPP
#include <opencv2/core.hpp>
namespace cv
{
//! @addtogroup objdetect_dnn_face
//! @{
/** @brief DNN-based face detector
model download link: https://github.com/opencv/opencv_zoo/tree/master/models/face_detection_yunet
*/
class CV_EXPORTS_W FaceDetectorYN
{
public:
virtual ~FaceDetectorYN() {}
/** @brief Set the size for the network input, which overwrites the input size of creating model. Call this method when the size of input image does not match the input size when creating model
*
* @param input_size the size of the input image
*/
CV_WRAP virtual void setInputSize(const Size& input_size) = 0;
CV_WRAP virtual Size getInputSize() = 0;
/** @brief Set the score threshold to filter out bounding boxes of score less than the given value
*
* @param score_threshold threshold for filtering out bounding boxes
*/
CV_WRAP virtual void setScoreThreshold(float score_threshold) = 0;
CV_WRAP virtual float getScoreThreshold() = 0;
/** @brief Set the Non-maximum-suppression threshold to suppress bounding boxes that have IoU greater than the given value
*
* @param nms_threshold threshold for NMS operation
*/
CV_WRAP virtual void setNMSThreshold(float nms_threshold) = 0;
CV_WRAP virtual float getNMSThreshold() = 0;
/** @brief Set the number of bounding boxes preserved before NMS
*
* @param top_k the number of bounding boxes to preserve from top rank based on score
*/
CV_WRAP virtual void setTopK(int top_k) = 0;
CV_WRAP virtual int getTopK() = 0;
/** @brief Detects faces in the input image. Following is an example output.
* ![image](pics/lena-face-detection.jpg)
* @param image an image to detect
* @param faces detection results stored in a 2D cv::Mat of shape [num_faces, 15]
* - 0-1: x, y of bbox top left corner
* - 2-3: width, height of bbox
* - 4-5: x, y of right eye (blue point in the example image)
* - 6-7: x, y of left eye (red point in the example image)
* - 8-9: x, y of nose tip (green point in the example image)
* - 10-11: x, y of right corner of mouth (pink point in the example image)
* - 12-13: x, y of left corner of mouth (yellow point in the example image)
* - 14: face score
*/
CV_WRAP virtual int detect(InputArray image, OutputArray faces) = 0;
/** @brief Creates an instance of face detector class with given parameters
*
* @param model the path to the requested model
* @param config the path to the config file for compatibility, which is not requested for ONNX models
* @param input_size the size of the input image
* @param score_threshold the threshold to filter out bounding boxes of score smaller than the given value
* @param nms_threshold the threshold to suppress bounding boxes of IoU bigger than the given value
* @param top_k keep top K bboxes before NMS
* @param backend_id the id of backend
* @param target_id the id of target device
*/
CV_WRAP static Ptr<FaceDetectorYN> create(CV_WRAP_FILE_PATH const String& model,
CV_WRAP_FILE_PATH const String& config,
const Size& input_size,
float score_threshold = 0.9f,
float nms_threshold = 0.3f,
int top_k = 5000,
int backend_id = 0,
int target_id = 0);
/** @overload
*
* @param framework Name of origin framework
* @param bufferModel A buffer with a content of binary file with weights
* @param bufferConfig A buffer with a content of text file contains network configuration
* @param input_size the size of the input image
* @param score_threshold the threshold to filter out bounding boxes of score smaller than the given value
* @param nms_threshold the threshold to suppress bounding boxes of IoU bigger than the given value
* @param top_k keep top K bboxes before NMS
* @param backend_id the id of backend
* @param target_id the id of target device
*/
CV_WRAP static Ptr<FaceDetectorYN> create(const String& framework,
const std::vector<uchar>& bufferModel,
const std::vector<uchar>& bufferConfig,
const Size& input_size,
float score_threshold = 0.9f,
float nms_threshold = 0.3f,
int top_k = 5000,
int backend_id = 0,
int target_id = 0);
};
/** @brief DNN-based face recognizer
model download link: https://github.com/opencv/opencv_zoo/tree/master/models/face_recognition_sface
*/
class CV_EXPORTS_W FaceRecognizerSF
{
public:
virtual ~FaceRecognizerSF() {}
/** @brief Definition of distance used for calculating the distance between two face features
*/
enum DisType { FR_COSINE=0, FR_NORM_L2=1 };
/** @brief Aligns detected face with the source input image and crops it
* @param src_img input image
* @param face_box the detected face result from the input image
* @param aligned_img output aligned image
*/
CV_WRAP virtual void alignCrop(InputArray src_img, InputArray face_box, OutputArray aligned_img) const = 0;
/** @brief Extracts face feature from aligned image
* @param aligned_img input aligned image
* @param face_feature output face feature
*/
CV_WRAP virtual void feature(InputArray aligned_img, OutputArray face_feature) = 0;
/** @brief Calculates the distance between two face features
* @param face_feature1 the first input feature
* @param face_feature2 the second input feature of the same size and the same type as face_feature1
* @param dis_type defines how to calculate the distance between two face features with optional values "FR_COSINE" or "FR_NORM_L2"
*/
CV_WRAP virtual double match(InputArray face_feature1, InputArray face_feature2, int dis_type = FaceRecognizerSF::FR_COSINE) const = 0;
/** @brief Creates an instance of this class with given parameters
* @param model the path of the onnx model used for face recognition
* @param config the path to the config file for compatibility, which is not requested for ONNX models
* @param backend_id the id of backend
* @param target_id the id of target device
*/
CV_WRAP static Ptr<FaceRecognizerSF> create(CV_WRAP_FILE_PATH const String& model, CV_WRAP_FILE_PATH const String& config, int backend_id = 0, int target_id = 0);
/**
* @brief Creates an instance of this class from a buffer containing the model weights and configuration.
* @param framework Name of the framework (ONNX, etc.)
* @param bufferModel A buffer containing the binary model weights.
* @param bufferConfig A buffer containing the network configuration.
* @param backend_id The id of the backend.
* @param target_id The id of the target device.
*
* @return A pointer to the created instance of FaceRecognizerSF.
*/
CV_WRAP static Ptr<FaceRecognizerSF> create(const String& framework,
const std::vector<uchar>& bufferModel,
const std::vector<uchar>& bufferConfig,
int backend_id = 0,
int target_id = 0);
};
//! @}
} // namespace cv
#endif
@@ -0,0 +1,96 @@
// 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_GRAPHICAL_CODE_DETECTOR_HPP
#define OPENCV_OBJDETECT_GRAPHICAL_CODE_DETECTOR_HPP
#include <opencv2/core.hpp>
namespace cv {
//! @addtogroup objdetect_common
//! @{
class CV_EXPORTS_W_SIMPLE GraphicalCodeDetector {
public:
CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to "protected" (need to fix bindings first)
GraphicalCodeDetector();
GraphicalCodeDetector(const GraphicalCodeDetector&) = default;
GraphicalCodeDetector(GraphicalCodeDetector&&) = default;
GraphicalCodeDetector& operator=(const GraphicalCodeDetector&) = default;
GraphicalCodeDetector& operator=(GraphicalCodeDetector&&) = default;
/** @brief Detects graphical code in image and returns the quadrangle containing the code.
@param img grayscale or color (BGR) image containing (or not) graphical code.
@param points Output vector of vertices of the minimum-area quadrangle containing the code.
*/
CV_WRAP bool detect(InputArray img, OutputArray points) const;
/** @brief Decodes graphical code in image once it's found by the detect() method.
Returns UTF8-encoded output string or empty string if the code cannot be decoded.
@param img grayscale or color (BGR) image containing graphical code.
@param points Quadrangle vertices found by detect() method (or some other algorithm).
@param straight_code The optional output image containing binarized code, will be empty if not found.
*/
CV_WRAP std::string decode(InputArray img, InputArray points, OutputArray straight_code = noArray()) const;
/** @brief Both detects and decodes graphical code
@param img grayscale or color (BGR) image containing graphical code.
@param points optional output array of vertices of the found graphical code quadrangle, will be empty if not found.
@param straight_code The optional output image containing binarized code
*/
CV_WRAP std::string detectAndDecode(InputArray img, OutputArray points = noArray(),
OutputArray straight_code = noArray()) const;
/** @brief Detects graphical codes in image and returns the vector of the quadrangles containing the codes.
@param img grayscale or color (BGR) image containing (or not) graphical codes.
@param points Output vector of vector of vertices of the minimum-area quadrangle containing the codes.
*/
CV_WRAP bool detectMulti(InputArray img, OutputArray points) const;
/** @brief Decodes graphical codes in image once it's found by the detect() method.
@param img grayscale or color (BGR) image containing graphical codes.
@param decoded_info UTF8-encoded output vector of string or empty vector of string if the codes cannot be decoded.
@param points vector of Quadrangle vertices found by detect() method (or some other algorithm).
@param straight_code The optional output vector of images containing binarized codes
*/
CV_WRAP bool decodeMulti(InputArray img, InputArray points, CV_OUT std::vector<std::string>& decoded_info,
OutputArrayOfArrays straight_code = noArray()) const;
/** @brief Both detects and decodes graphical codes
@param img grayscale or color (BGR) image containing graphical codes.
@param decoded_info UTF8-encoded output vector of string or empty vector of string if the codes cannot be decoded.
@param points optional output vector of vertices of the found graphical code quadrangles. Will be empty if not found.
@param straight_code The optional vector of images containing binarized codes
- If there are QR codes encoded with a Structured Append mode on the image and all of them detected and decoded correctly,
method writes a full message to position corresponds to 0-th code in a sequence. The rest of QR codes from the same sequence
have empty string.
*/
CV_WRAP bool detectAndDecodeMulti(InputArray img, CV_OUT std::vector<std::string>& decoded_info, OutputArray points = noArray(),
OutputArrayOfArrays straight_code = noArray()) const;
#ifdef OPENCV_BINDINGS_PARSER
CV_WRAP_AS(detectAndDecodeBytes) NativeByteArray detectAndDecode(InputArray img, OutputArray points = noArray(),
OutputArray straight_code = noArray()) const;
CV_WRAP_AS(decodeBytes) NativeByteArray decode(InputArray img, InputArray points, OutputArray straight_code = noArray()) const;
CV_WRAP_AS(decodeBytesMulti) bool decodeMulti(InputArray img, InputArray points, CV_OUT std::vector<NativeByteArray>& decoded_info,
OutputArrayOfArrays straight_code = noArray()) const;
CV_WRAP_AS(detectAndDecodeBytesMulti) bool detectAndDecodeMulti(InputArray img, CV_OUT std::vector<NativeByteArray>& decoded_info, OutputArray points = noArray(),
OutputArrayOfArrays straight_code = noArray()) const;
#endif
struct Impl;
protected:
Ptr<Impl> p;
};
//! @}
}
#endif
@@ -0,0 +1,307 @@
// 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.
/*
* MIT License
*
* Copyright (c) 2018 Pedro Diamel Marrero Fernández
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef OPENCV_OBJDETECT_MCC_CHECKER_DETECTOR_HPP
#define OPENCV_OBJDETECT_MCC_CHECKER_DETECTOR_HPP
#include <opencv2/core.hpp>
#ifdef HAVE_OPENCV_DNN
#include <opencv2/dnn.hpp>
#endif
#include <opencv2/imgproc.hpp>
//---------------------------------------------------------------
// #define MCC_DEBUG
//---------------------------------------------------------------
namespace cv
{
namespace mcc
{
//! @addtogroup mcc
//! @{
/** ColorChart
*
* @brief enum to hold the type of the checker
*/
enum ColorChart
{
MCC24 = 0, ///< Standard Macbeth Chart with 24 squares
SG140, ///< DigitalSG with 140 squares
VINYL18, ///< DKK color chart with 12 squares and 6 rectangle
};
/** CChecker
*
* @brief checker object
*
* This class contains the information about the detected checkers,i.e, their
* type, the corners of the chart, the color profile, the cost, centers chart,
* etc.
*/
class CV_EXPORTS_W CChecker: public Algorithm
{
public:
CChecker() {}
virtual ~CChecker() {}
/** @brief Create a new CChecker object.
*
* @return A pointer to the implementation of the CChecker
*/
CV_WRAP static Ptr<CChecker> create();
public:
CV_WRAP virtual void setTarget(ColorChart _target) = 0;
CV_WRAP virtual void setBox(std::vector<Point2f> _box) = 0;
CV_WRAP virtual void setChartsRGB(Mat _chartsRGB) = 0;
CV_WRAP virtual void setChartsYCbCr(Mat _chartsYCbCr) = 0;
CV_WRAP virtual void setCost(float _cost) = 0;
CV_WRAP virtual void setCenter(Point2f _center) = 0;
CV_WRAP virtual ColorChart getTarget() = 0;
CV_WRAP virtual std::vector<Point2f> getBox() = 0;
/** @brief Computes and returns the coordinates of the central parts of the charts modules.
*
* This method computes transformation matrix from the checkers's coordinates (`CChecker::getBox()`)
* and find by this the coordinates of the central parts of the charts modules.
* It is used in `CCheckerDetector::draw()` and in `ChartsRGB` calculation.
*/
CV_WRAP virtual std::vector<Point2f> getColorCharts() = 0;
CV_WRAP virtual Mat getChartsRGB(bool getStats = true) = 0;
CV_WRAP virtual Mat getChartsYCbCr() = 0;
CV_WRAP virtual float getCost() = 0;
CV_WRAP virtual Point2f getCenter() = 0;
};
/** @brief struct DetectorParametersMCC is used by CCheckerDetector
*/
struct CV_EXPORTS_W_SIMPLE DetectorParametersMCC
{
CV_WRAP DetectorParametersMCC(){
adaptiveThreshWinSizeMin=23;
adaptiveThreshWinSizeMax=153;
adaptiveThreshWinSizeStep=16;
adaptiveThreshConstant=7;
minContoursAreaRate=0.003;
minContoursArea=100;
confidenceThreshold=0.5;
minContourSolidity=0.9;
findCandidatesApproxPolyDPEpsMultiplier=0.05;
borderWidth=0;
B0factor=1.25f;
maxError=0.1f;
minContourPointsAllowed=4;
minContourLengthAllowed=100;
minInterContourDistance=100;
minInterCheckerDistance=10000;
minImageSize=1000;
minGroupSize=4;
}
/// minimum window size for adaptive thresholding before finding contours (default 23).
CV_PROP_RW int adaptiveThreshWinSizeMin;
/// maximum window size for adaptive thresholding before finding contours (default 153).
CV_PROP_RW int adaptiveThreshWinSizeMax;
/// increments from adaptiveThreshWinSizeMin to adaptiveThreshWinSizeMax during the thresholding (default 16).
CV_PROP_RW int adaptiveThreshWinSizeStep;
/// constant for adaptive thresholding before finding contours (default 7)
CV_PROP_RW double adaptiveThreshConstant;
/** @brief determine minimum area for marker contour to be detected
*
* This is defined as a rate respect to the area of the input image. Used only if neural network is used (default 0.03).
*/
CV_PROP_RW double minContoursAreaRate;
/** @brief determine minimum area for marker contour to be detected
*
* This is defined as the actual area. Used only if neural network is used (default 100).
*/
CV_PROP_RW double minContoursArea;
/// minimum confidence for a bounding box detected by neural network to classify as detection.(default 0.5) (0<=confidenceThreshold<=1)
CV_PROP_RW double confidenceThreshold;
/// minimum solidity of a contour for it be detected as a square in the chart. (default 0.9).
CV_PROP_RW double minContourSolidity;
/// multipler to be used in ApproxPolyDP function (default 0.05)
CV_PROP_RW double findCandidatesApproxPolyDPEpsMultiplier;
/// width of the padding used to pass the inital neural network detection in the succeeding system.(default 0)
CV_PROP_RW int borderWidth;
/// distance between two neighboring squares of the same chart as a ratio of the large dimension of a square (default 1.25).
CV_PROP_RW float B0factor;
/// maximum allowed error in the detection of a chart (default 0.1).
CV_PROP_RW float maxError;
/// minimum points in a detected contour (default 4).
CV_PROP_RW int minContourPointsAllowed;
/// minimum length of a contour (default 100).
CV_PROP_RW int minContourLengthAllowed;
/// minimum distance between two contours (default 100).
CV_PROP_RW int minInterContourDistance;
/// minimum distance between two checkers (default 10000).
CV_PROP_RW int minInterCheckerDistance;
/// minimum size of the smaller dimension of the image (default 1000).
CV_PROP_RW int minImageSize;
/// minimum number of squares in a chart that must be detected (default 4).
CV_PROP_RW int minGroupSize;
};
/** @brief A class to find the positions of the ColorCharts in the image.
*/
class CV_EXPORTS_W CCheckerDetector : public Algorithm
{
public:
/** @brief Find the ColorCharts in the given image.
*
* The found charts are not returned but instead stored in the
* detector, these can be accessed later on using getBestColorChecker()
* and getListColorChecker()
* @param image image in color space BGR
* @param regionsOfInterest regions of image to look for the chart, if
* it is empty, charts are looked for in the
* entire image
* @param nc number of charts in the image, if you don't know the exact
* then keeping this number high helps.
* @return true if atleast one chart is detected otherwise false
*/
CV_WRAP_AS(processWithROI) virtual bool
process(InputArray image, const std::vector<Rect> &regionsOfInterest,
const int nc = 1) = 0;
/** @brief Find the ColorCharts in the given image.
*
* Differs from the above one only in the arguments.
*
* This version searches for the chart in the full image.
*
* The found charts are not returned but instead stored in the
* detector, these can be accessed later on using getBestColorChecker()
* and getListColorChecker()
* @param image image in color space BGR
* @param nc number of charts in the image, if you don't know the exact
* then keeping this number high helps.
* @return true if atleast one chart is detected otherwise false
*/
CV_WRAP virtual bool
process(InputArray image, const int nc = 1) = 0;
/** @brief Get the best color checker. By the best it means the one
* detected with the highest confidence.
* @return checker A single colorchecker, if atleast one colorchecker
* was detected, 'nullptr' otherwise.
*/
CV_WRAP virtual Ptr<mcc::CChecker> getBestColorChecker() = 0;
/** @brief Get the list of all detected colorcheckers
* @return checkers vector of colorcheckers
*/
CV_WRAP virtual std::vector<Ptr<CChecker>> getListColorChecker() = 0;
/** @brief Returns the implementation of the CCheckerDetector.
*
*/
CV_WRAP static Ptr<CCheckerDetector> create();
#ifdef HAVE_OPENCV_DNN
/** @brief Set the net which will be used to find the approximate
* bounding boxes for the color charts. And returns the implementation of the CCheckerDetector.
*
* It is not necessary to use this, but this usually results in
* better detection rate.
*
* @param net the neural network, if the network in empty, then
* the function will return false.
*/
CV_WRAP static Ptr<CCheckerDetector> create(const dnn::Net &net);
#endif
/** @brief Draws the checker to the given image.
* @param img image in color space BGR
* @param checkers The checkers which will be drawn by this object.
* @param color The color by with which the squares of the checker
* will be drawn
* @param thickness The thickness with which the sqaures will be
* drawn
*/
CV_WRAP virtual void draw(std::vector<Ptr<CChecker>>& checkers, InputOutputArray img, const Scalar color = CV_RGB(0,250,0), const int thickness = 2) = 0;
/** @brief Gets the reference color for chart.
*/
CV_WRAP virtual Mat getRefColors() = 0;
/** @brief Sets the detection paramaters for mcc.
* @param params DetectorParametersMCC structure containing detection configuration parameters.
*/
CV_WRAP virtual void setDetectionParams(const DetectorParametersMCC &params) = 0;
/** @brief Sets the color chart type for MCC detection.
* @param chartType ColorChart enum specifying the type of color chart to detect.
*/
CV_WRAP virtual void setColorChartType(ColorChart chartType) = 0;
#ifdef HAVE_OPENCV_DNN
/** @brief Enables or disables the use of the neural network for detection.
* @param useDnn Boolean flag to indicate whether to use neural network (true) or not (false).
*/
CV_WRAP virtual void setUseDnnModel(bool useDnn) = 0;
CV_WRAP virtual bool getUseDnnModel() const = 0;
#endif
CV_WRAP virtual const DetectorParametersMCC& getDetectionParams() const = 0;
CV_WRAP virtual ColorChart getColorChartType() const = 0;
};
//! @} mcc
} // namespace mcc
} // namespace cv
#endif
@@ -0,0 +1,48 @@
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
// Copyright (C) 2013, OpenCV Foundation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#ifdef __OPENCV_BUILD
#error this is a compatibility header which should not be used inside the OpenCV library
#endif
#include "opencv2/objdetect.hpp"
@@ -0,0 +1 @@
misc/java/src/cpp/objdetect_converters.hpp
+73
View File
@@ -0,0 +1,73 @@
{
"ManualFuncs" : {
"QRCodeEncoder" : {
"QRCodeEncoder" : {
"j_code" : [
"\n",
"/** Generates QR code from input string.",
"@param encoded_info Input bytes to encode.",
"@param qrcode Generated QR code.",
"*/",
"public void encode(byte[] encoded_info, Mat qrcode) {",
" encode_1(nativeObj, encoded_info, qrcode.nativeObj);",
"}",
"\n"
],
"jn_code": [
"\n",
"private static native void encode_1(long nativeObj, byte[] encoded_info, long qrcode_nativeObj);",
"\n"
],
"cpp_code": [
"//",
"// void cv::QRCodeEncoder::encode(String encoded_info, Mat& qrcode)",
"//",
"\n",
"JNIEXPORT void JNICALL Java_org_opencv_objdetect_QRCodeEncoder_encode_11 (JNIEnv*, jclass, jlong, jbyteArray, jlong);",
"\n",
"JNIEXPORT void JNICALL Java_org_opencv_objdetect_QRCodeEncoder_encode_11",
"(JNIEnv* env, jclass , jlong self, jbyteArray encoded_info, jlong qrcode_nativeObj)",
"{",
"",
" static const char method_name[] = \"objdetect::encode_11()\";",
" try {",
" LOGD(\"%s\", method_name);",
" Ptr<cv::QRCodeEncoder>* me = (Ptr<cv::QRCodeEncoder>*) self; //TODO: check for NULL",
" const char* n_encoded_info = reinterpret_cast<char*>(env->GetByteArrayElements(encoded_info, NULL));",
" const jsize n_encoded_info_size = env->GetArrayLength(encoded_info);",
" Mat& qrcode = *((Mat*)qrcode_nativeObj);",
" (*me)->encode( std::string(n_encoded_info, n_encoded_info_size), qrcode );",
" } catch(const std::exception &e) {",
" throwJavaException(env, &e, method_name);",
" } catch (...) {",
" throwJavaException(env, 0, method_name);",
" }",
"}",
"\n"
]
}
}
},
"type_dict": {
"NativeByteArray": {
"j_type" : "byte[]",
"jn_type": "byte[]",
"jni_type": "jbyteArray",
"jni_name": "n_%(n)s",
"jni_var": "jbyteArray n_%(n)s = env->NewByteArray(static_cast<jsize>(%(n)s.size())); env->SetByteArrayRegion(n_%(n)s, 0, static_cast<jsize>(%(n)s.size()), reinterpret_cast<const jbyte*>(%(n)s.c_str()));",
"cast_from": "std::string"
},
"vector_NativeByteArray": {
"j_type": "List<byte[]>",
"jn_type": "List<byte[]>",
"jni_type": "jobject",
"jni_var": "std::vector< std::string > %(n)s",
"suffix": "Ljava_util_List",
"v_type": "vector_NativeByteArray"
}
},
"func_arg_fix" : {
"findChessboardCorners" : { "corners" : {"ctype" : "vector_Point2f"} },
"drawChessboardCorners" : { "corners" : {"ctype" : "vector_Point2f"} }
}
}
@@ -0,0 +1,20 @@
#include "objdetect_converters.hpp"
#define LOG_TAG "org.opencv.objdetect"
void Copy_vector_NativeByteArray_to_List(JNIEnv* env, std::vector<std::string>& vs, jobject list)
{
static jclass juArrayList = ARRAYLIST(env);
jmethodID m_clear = LIST_CLEAR(env, juArrayList);
jmethodID m_add = LIST_ADD(env, juArrayList);
env->CallVoidMethod(list, m_clear);
for (std::vector<std::string>::iterator it = vs.begin(); it != vs.end(); ++it)
{
jsize sz = static_cast<jsize>((*it).size());
jbyteArray element = env->NewByteArray(sz);
env->SetByteArrayRegion(element, 0, sz, reinterpret_cast<const jbyte*>((*it).c_str()));
env->CallBooleanMethod(list, m_add, element);
env->DeleteLocalRef(element);
}
}
@@ -0,0 +1,14 @@
// 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 OBJDETECT_CONVERTERS_HPP
#define OBJDETECT_CONVERTERS_HPP
#include <jni.h>
#include "opencv_java.hpp"
#include "opencv2/core.hpp"
void Copy_vector_NativeByteArray_to_List(JNIEnv* env, std::vector<std::string>& vs, jobject list);
#endif /* OBJDETECT_CONVERTERS_HPP */
@@ -0,0 +1,115 @@
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);
// Note: Expected values adjusted by -0.5px after fixing the systematic offset bug in charuco_detector.cpp
// The fix removes the incorrect +0.5 offset that was added after cornerSubPix
double eps = 0.2;
assertArrayEquals(new double[]{cellSize - 0.5, cellSize - 0.5}, charucoCorners.get(0, 0), eps);
assertArrayEquals(new double[]{2*cellSize - 0.5, cellSize - 0.5}, charucoCorners.get(0,1), eps);
assertArrayEquals(new double[]{cellSize - 0.5, 2*cellSize - 0.5}, charucoCorners.get(0,2), eps);
assertArrayEquals(new double[]{2*cellSize - 0.5, 2*cellSize - 0.5}, charucoCorners.get(0,3), eps);
}
}
@@ -0,0 +1,55 @@
package org.opencv.test.barcode;
import java.util.List;
import org.opencv.core.Mat;
import org.opencv.objdetect.BarcodeDetector;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.test.OpenCVTestCase;
import java.util.ArrayList;
public class BarcodeDetectorTest extends OpenCVTestCase {
private final static String ENV_OPENCV_TEST_DATA_PATH = "OPENCV_TEST_DATA_PATH";
private String testDataPath;
@Override
protected void setUp() throws Exception {
super.setUp();
// relys on https://developer.android.com/reference/java/lang/System
isTestCaseEnabled = System.getProperties().getProperty("java.vm.name") != "Dalvik";
if (isTestCaseEnabled) {
testDataPath = System.getenv(ENV_OPENCV_TEST_DATA_PATH);
if (testDataPath == null)
throw new Exception(ENV_OPENCV_TEST_DATA_PATH + " has to be defined!");
}
}
public void testDetectAndDecode() {
Mat img = Imgcodecs.imread(testDataPath + "/cv/barcode/multiple/4_barcodes.jpg");
assertFalse(img.empty());
BarcodeDetector detector = new BarcodeDetector();
assertNotNull(detector);
List < String > infos = new ArrayList< String >();
List < String > types = new ArrayList< String >();
boolean result = detector.detectAndDecodeWithType(img, infos, types);
assertTrue(result);
assertEquals(infos.size(), 4);
assertEquals(types.size(), 4);
final String[] correctResults = {"9787122276124", "9787118081473", "9787564350840", "9783319200064"};
for (int i = 0; i < 4; i++) {
assertEquals(types.get(i), "EAN_13");
result = false;
for (int j = 0; j < 4; j++) {
if (correctResults[j].equals(infos.get(i))) {
result = true;
break;
}
}
assertTrue(result);
}
}
}
@@ -0,0 +1,99 @@
package org.opencv.test.objdetect;
import java.util.ArrayList;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfDouble;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.MatOfPoint3f;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.objdetect.Objdetect;
import org.opencv.test.OpenCVTestCase;
import org.opencv.imgproc.Imgproc;
public class ObjdetectTest extends OpenCVTestCase {
Size size;
@Override
protected void setUp() throws Exception {
super.setUp();
size = new Size(3, 3);
}
public void testFindChessboardCornersMatSizeMat() {
Size patternSize = new Size(9, 6);
MatOfPoint2f corners = new MatOfPoint2f();
Objdetect.findChessboardCorners(grayChess, patternSize, corners);
assertFalse(corners.empty());
}
public void testFindChessboardCornersMatSizeMatInt() {
Size patternSize = new Size(9, 6);
MatOfPoint2f corners = new MatOfPoint2f();
Objdetect.findChessboardCorners(grayChess, patternSize, corners, Objdetect.CALIB_CB_ADAPTIVE_THRESH + Objdetect.CALIB_CB_NORMALIZE_IMAGE
+ Objdetect.CALIB_CB_FAST_CHECK);
assertFalse(corners.empty());
}
public void testFind4QuadCornerSubpix() {
Size patternSize = new Size(9, 6);
MatOfPoint2f corners = new MatOfPoint2f();
Size region_size = new Size(5, 5);
Objdetect.findChessboardCorners(grayChess, patternSize, corners);
Objdetect.find4QuadCornerSubpix(grayChess, corners, region_size);
assertFalse(corners.empty());
}
public void testFindCirclesGridMatSizeMat() {
int size = 300;
Mat img = new Mat(size, size, CvType.CV_8U);
img.setTo(new Scalar(255));
Mat centers = new Mat();
assertFalse(Objdetect.findCirclesGrid(img, new Size(5, 5), centers));
for (int i = 0; i < 5; i++)
for (int j = 0; j < 5; j++) {
Point pt = new Point(size * (2 * i + 1) / 10, size * (2 * j + 1) / 10);
Imgproc.circle(img, pt, 10, new Scalar(0), -1);
}
assertTrue(Objdetect.findCirclesGrid(img, new Size(5, 5), centers));
assertEquals(1, centers.rows());
assertEquals(25, centers.cols());
assertEquals(CvType.CV_32FC2, centers.type());
}
public void testFindCirclesGridMatSizeMatInt() {
int size = 300;
Mat img = new Mat(size, size, CvType.CV_8U);
img.setTo(new Scalar(255));
Mat centers = new Mat();
assertFalse(Objdetect.findCirclesGrid(img, new Size(3, 5), centers, Objdetect.CALIB_CB_CLUSTERING
| Objdetect.CALIB_CB_ASYMMETRIC_GRID));
int step = size * 2 / 15;
int offsetx = size / 6;
int offsety = (size - 4 * step) / 2;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 5; j++) {
Point pt = new Point(offsetx + (2 * i + j % 2) * step, offsety + step * j);
Imgproc.circle(img, pt, 10, new Scalar(0), -1);
}
assertTrue(Objdetect.findCirclesGrid(img, new Size(3, 5), centers, Objdetect.CALIB_CB_CLUSTERING
| Objdetect.CALIB_CB_ASYMMETRIC_GRID));
assertEquals(1, centers.rows());
assertEquals(15, centers.cols());
assertEquals(CvType.CV_32FC2, centers.type());
}
}
@@ -0,0 +1,81 @@
package org.opencv.test.objdetect;
import java.util.List;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.objdetect.QRCodeDetector;
import org.opencv.objdetect.QRCodeEncoder;
import org.opencv.objdetect.QRCodeEncoder_Params;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.test.OpenCVTestCase;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
public class QRCodeDetectorTest extends OpenCVTestCase {
private final static String ENV_OPENCV_TEST_DATA_PATH = "OPENCV_TEST_DATA_PATH";
private String testDataPath;
@Override
protected void setUp() throws Exception {
super.setUp();
// relys on https://developer.android.com/reference/java/lang/System
isTestCaseEnabled = System.getProperties().getProperty("java.vm.name") != "Dalvik";
if (isTestCaseEnabled) {
testDataPath = System.getenv(ENV_OPENCV_TEST_DATA_PATH);
if (testDataPath == null)
throw new Exception(ENV_OPENCV_TEST_DATA_PATH + " has to be defined!");
}
}
public void testDetectAndDecode() {
Mat img = Imgcodecs.imread(testDataPath + "/cv/qrcode/link_ocv.jpg");
assertFalse(img.empty());
QRCodeDetector detector = new QRCodeDetector();
assertNotNull(detector);
String output = detector.detectAndDecode(img);
assertEquals(output, "https://opencv.org/");
}
public void testDetectAndDecodeMulti() {
Mat img = Imgcodecs.imread(testDataPath + "/cv/qrcode/multiple/6_qrcodes.png");
assertFalse(img.empty());
QRCodeDetector detector = new QRCodeDetector();
assertNotNull(detector);
List < String > output = new ArrayList< String >();
boolean result = detector.detectAndDecodeMulti(img, output);
assertTrue(result);
assertEquals(output.size(), 6);
List < String > expectedResults = Arrays.asList("SKIP", "EXTRA", "TWO STEPS FORWARD", "STEP BACK", "QUESTION", "STEP FORWARD");
assertEquals(new HashSet<String>(output), new HashSet<String>(expectedResults));
}
public void testKanji() {
byte[] inp = new byte[]{(byte)0x82, (byte)0xb1, (byte)0x82, (byte)0xf1, (byte)0x82, (byte)0xc9, (byte)0x82,
(byte)0xbf, (byte)0x82, (byte)0xcd, (byte)0x90, (byte)0xa2, (byte)0x8a, (byte)0x45};
QRCodeEncoder_Params params = new QRCodeEncoder_Params();
params.set_mode(QRCodeEncoder.MODE_KANJI);
QRCodeEncoder encoder = QRCodeEncoder.create(params);
Mat qrcode = new Mat();
encoder.encode(inp, qrcode);
Imgproc.resize(qrcode, qrcode, new Size(0, 0), 2, 2, Imgproc.INTER_NEAREST);
QRCodeDetector detector = new QRCodeDetector();
byte[] output = detector.detectAndDecodeBytes(qrcode);
assertEquals(detector.getEncoding(), QRCodeEncoder.ECI_SHIFT_JIS);
assertArrayEquals(inp, output);
List < byte[] > outputs = new ArrayList< byte[] >();
assertTrue(detector.detectAndDecodeBytesMulti(qrcode, outputs));
assertEquals(detector.getEncoding(0), QRCodeEncoder.ECI_SHIFT_JIS);
assertArrayEquals(inp, outputs.get(0));
}
}
+31
View File
@@ -0,0 +1,31 @@
{
"whitelist":
{
"": ["groupRectangles", "getPredefinedDictionary", "extendDictionary", "drawDetectedMarkers", "generateImageMarker", "drawDetectedCornersCharuco", "drawDetectedDiamonds"],
"HOGDescriptor": ["load", "HOGDescriptor", "getDefaultPeopleDetector", "getDaimlerPeopleDetector", "setSVMDetector", "detectMultiScale"],
"CascadeClassifier": ["load", "detectMultiScale2", "CascadeClassifier", "detectMultiScale3", "empty", "detectMultiScale"],
"GraphicalCodeDetector": ["decode", "detect", "detectAndDecode", "detectMulti", "decodeMulti", "detectAndDecodeMulti"],
"QRCodeDetector": ["QRCodeDetector", "decode", "detect", "detectAndDecode", "detectMulti", "decodeMulti", "detectAndDecodeMulti", "decodeCurved", "detectAndDecodeCurved", "setEpsX", "setEpsY"],
"aruco_PredefinedDictionaryType": [],
"aruco_Dictionary": ["Dictionary", "getDistanceToId", "generateImageMarker", "getByteListFromBits", "getBitsFromByteList"],
"aruco_Board": ["Board", "matchImagePoints", "generateImage"],
"aruco_GridBoard": ["GridBoard", "generateImage", "getGridSize", "getMarkerLength", "getMarkerSeparation", "matchImagePoints"],
"aruco_CharucoParameters": ["CharucoParameters"],
"aruco_CharucoBoard": ["CharucoBoard", "generateImage", "getChessboardCorners", "getNearestMarkerCorners", "checkCharucoCornersCollinear", "matchImagePoints", "getLegacyPattern", "setLegacyPattern"],
"aruco_DetectorParameters": ["DetectorParameters"],
"aruco_RefineParameters": ["RefineParameters"],
"aruco_ArucoDetector": ["ArucoDetector", "detectMarkers", "refineDetectedMarkers", "setDictionary", "setDetectorParameters", "setRefineParameters"],
"aruco_CharucoDetector": ["CharucoDetector", "setBoard", "setCharucoParameters", "setDetectorParameters", "setRefineParameters", "detectBoard", "detectDiamonds"],
"QRCodeDetectorAruco_Params": ["Params"],
"QRCodeDetectorAruco": ["QRCodeDetectorAruco", "decode", "detect", "detectAndDecode", "detectMulti", "decodeMulti", "detectAndDecodeMulti", "setDetectorParameters", "setArucoParameters"],
"barcode_BarcodeDetector": ["BarcodeDetector", "decode", "detect", "detectAndDecode", "detectMulti", "decodeMulti", "detectAndDecodeMulti", "decodeWithType", "detectAndDecodeWithType"],
"mcc_CheckerDetector": ["process", "getBestColorChecker", "getListColorChecker", "create", "draw", "getRefColors", "setDetectionParams", "getDetectionParams", "setColorChartType", "getColorChartType", "setUseDnnModel", "getUseDnnModel"],
"mcc_DetectorParameters": ["DetectorParametersMCC"],
"mcc_Checker": ["setTarget", "setBox", "setChartsRGB", "setChartsYCbCr", "setCost", "setCenter", "getTarget", "getBox", "getColorCharts", "getChartsRGB", "getChartsYCbCr", "getCost", "getCenter"],
"FaceDetectorYN": ["setInputSize", "getInputSize", "setScoreThreshold", "getScoreThreshold", "setNMSThreshold", "getNMSThreshold", "setTopK", "getTopK", "detect", "create"]
},
"namespace_prefix_override":
{
"aruco": ""
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"ManualFuncs" : {
"QRCodeDetectorAruco": {
"getDetectorParameters": { "declaration" : [""], "implementation" : [""] }
}
},
"func_arg_fix" : {
"Calib" : {
"findCirclesGrid" : { "blobDetector" : {"defval" : "cv::SimpleBlobDetector::create()"} }
}
}
}
@@ -0,0 +1,88 @@
import XCTest
import OpenCV
class ObjdetectTest: OpenCVTestCase {
var size = Size()
override func setUp() {
super.setUp()
size = Size(width: 3, height: 3)
}
override func tearDown() {
super.tearDown()
}
func testFindChessboardCornersMatSizeMat() {
let patternSize = Size(width: 9, height: 6)
let corners = MatOfPoint2f()
Calib.findChessboardCorners(image: grayChess, patternSize: patternSize, corners: corners)
XCTAssertFalse(corners.empty())
}
func testFindChessboardCornersMatSizeMatInt() {
let patternSize = Size(width: 9, height: 6)
let corners = MatOfPoint2f()
Calib.findChessboardCorners(image: grayChess, patternSize: patternSize, corners: corners, flags: Calib.CALIB_CB_ADAPTIVE_THRESH + Calib.CALIB_CB_NORMALIZE_IMAGE + Calib.CALIB_CB_FAST_CHECK)
XCTAssertFalse(corners.empty())
}
func testFind4QuadCornerSubpix() {
let patternSize = Size(width: 9, height: 6)
let corners = MatOfPoint2f()
let region_size = Size(width: 5, height: 5)
Calib.findChessboardCorners(image: grayChess, patternSize: patternSize, corners: corners)
Calib.find4QuadCornerSubpix(img: grayChess, corners: corners, region_size: region_size)
XCTAssertFalse(corners.empty())
}
func testFindCirclesGridMatSizeMat() {
let size = 300
let img = Mat(rows:Int32(size), cols:Int32(size), type:CvType.CV_8U)
img.setTo(scalar: Scalar(255))
let centers = Mat()
XCTAssertFalse(Calib.findCirclesGrid(image: img, patternSize: Size(width: 5, height: 5), centers: centers))
for i in 0..<5 {
for j in 0..<5 {
let x = Int32(size * (2 * i + 1) / 10)
let y = Int32(size * (2 * j + 1) / 10)
let pt = Point(x: x, y: y)
Imgproc.circle(img: img, center: pt, radius: 10, color: Scalar(0), thickness: -1)
}
}
XCTAssert(Calib.findCirclesGrid(image: img, patternSize:Size(width:5, height:5), centers:centers))
XCTAssertEqual(25, centers.rows())
XCTAssertEqual(1, centers.cols())
XCTAssertEqual(CvType.CV_32FC2, centers.type())
}
func testFindCirclesGridMatSizeMatInt() {
let size:Int32 = 300
let img = Mat(rows:size, cols: size, type: CvType.CV_8U)
img.setTo(scalar: Scalar(255))
let centers = Mat()
XCTAssertFalse(Calib.findCirclesGrid(image: img, patternSize: Size(width: 3, height: 5), centers: centers, flags: Calib.CALIB_CB_CLUSTERING | Calib.CALIB_CB_ASYMMETRIC_GRID))
let step = size * 2 / 15
let offsetx = size / 6
let offsety = (size - 4 * step) / 2
for i:Int32 in 0...2 {
for j:Int32 in 0...4 {
let pt = Point(x: offsetx + (2 * i + j % 2) * step, y: offsety + step * j)
Imgproc.circle(img: img, center: pt, radius: 10, color: Scalar(0), thickness: -1)
}
}
XCTAssert(Calib.findCirclesGrid(image: img, patternSize: Size(width: 3, height: 5), centers: centers, flags: Calib.CALIB_CB_CLUSTERING | Calib.CALIB_CB_ASYMMETRIC_GRID))
XCTAssertEqual(15, centers.rows())
XCTAssertEqual(1, centers.cols())
XCTAssertEqual(CvType.CV_32FC2, centers.type())
}
}
@@ -0,0 +1,38 @@
#ifdef HAVE_OPENCV_OBJDETECT
#include "opencv2/objdetect.hpp"
typedef QRCodeEncoder::Params QRCodeEncoder_Params;
typedef std::vector<cv::Ptr<mcc::CChecker>> vector_Ptr_CChecker;
#ifdef HAVE_OPENCV_DNN
typedef dnn::Net dnn_Net;
#endif
class NativeByteArray
{
public:
inline NativeByteArray& operator=(const std::string& from) {
val = from;
return *this;
}
std::string val;
};
class vector_NativeByteArray : public std::vector<std::string> {};
template<>
PyObject* pyopencv_from(const NativeByteArray& from)
{
return PyBytes_FromStringAndSize(from.val.c_str(), from.val.size());
}
template<>
PyObject* pyopencv_from(const vector_NativeByteArray& results)
{
PyObject* list = PyList_New(results.size());
for(size_t i = 0; i < results.size(); ++i)
PyList_SetItem(list, i, PyBytes_FromStringAndSize(results[i].c_str(), results[i].size()));
return list;
}
#endif
@@ -0,0 +1,33 @@
#!/usr/bin/env python
'''
===============================================================================
Barcode detect and decode pipeline.
===============================================================================
'''
import os
import numpy as np
import cv2 as cv
from tests_common import NewOpenCVTests
class barcode_detector_test(NewOpenCVTests):
def test_detect(self):
img = cv.imread(os.path.join(self.extraTestDataPath, 'cv/barcode/multiple/4_barcodes.jpg'))
self.assertFalse(img is None)
detector = cv.barcode_BarcodeDetector()
retval, corners = detector.detect(img)
self.assertTrue(retval)
self.assertEqual(corners.shape, (4, 4, 2))
def test_detect_and_decode(self):
img = cv.imread(os.path.join(self.extraTestDataPath, 'cv/barcode/single/book.jpg'))
self.assertFalse(img is None)
detector = cv.barcode_BarcodeDetector()
retval, decoded_info, decoded_type, corners = detector.detectAndDecodeWithType(img)
self.assertTrue(retval)
self.assertTrue(len(decoded_info) > 0)
self.assertTrue(len(decoded_type) > 0)
self.assertEqual(decoded_info[0], "9787115279460")
self.assertEqual(decoded_type[0], "EAN_13")
self.assertEqual(corners.shape, (1, 4, 2))
@@ -0,0 +1,494 @@
#!/usr/bin/env python
# Python 2/3 compatibility
from __future__ import print_function
import os, tempfile, numpy as np
from math import pi
import cv2 as cv
from tests_common import NewOpenCVTests
def getSyntheticRT(yaw, pitch, distance):
rvec = np.zeros((3, 1), np.float64)
tvec = np.zeros((3, 1), np.float64)
rotPitch = np.array([[-pitch], [0], [0]])
rotYaw = np.array([[0], [yaw], [0]])
rvec, tvec = cv.composeRT(rotPitch, np.zeros((3, 1), np.float64),
rotYaw, np.zeros((3, 1), np.float64))[:2]
tvec = np.array([[0], [0], [distance]])
return rvec, tvec
# see test_aruco_utils.cpp
def projectMarker(img, board, markerIndex, cameraMatrix, rvec, tvec, markerBorder):
markerSizePixels = 100
markerImg = cv.aruco.generateImageMarker(board.getDictionary(), board.getIds()[markerIndex], markerSizePixels, borderBits=markerBorder)
distCoeffs = np.zeros((5, 1), np.float64)
maxCoord = board.getRightBottomCorner()
objPoints = board.getObjPoints()[markerIndex]
for i in range(len(objPoints)):
objPoints[i][0] -= maxCoord[0] / 2
objPoints[i][1] -= maxCoord[1] / 2
objPoints[i][2] -= maxCoord[2] / 2
corners, _ = cv.projectPoints(objPoints, rvec, tvec, cameraMatrix, distCoeffs)
originalCorners = np.array([
[0, 0],
[markerSizePixels, 0],
[markerSizePixels, markerSizePixels],
[0, markerSizePixels],
], np.float32)
transformation = cv.getPerspectiveTransform(originalCorners, corners)
borderValue = 127
aux = cv.warpPerspective(markerImg, transformation, img.shape, None, cv.INTER_NEAREST, cv.BORDER_CONSTANT, borderValue)
assert(img.shape == aux.shape)
mask = (aux == borderValue).astype(np.uint8)
img = img * mask + aux * (1 - mask)
return img
def projectChessboard(squaresX, squaresY, squareSize, imageSize, cameraMatrix, rvec, tvec):
img = np.ones(imageSize, np.uint8) * 255
distCoeffs = np.zeros((5, 1), np.float64)
for y in range(squaresY):
startY = y * squareSize
for x in range(squaresX):
if (y % 2 != x % 2):
continue
startX = x * squareSize
squareCorners = np.array([[startX - squaresX*squareSize/2,
startY - squaresY*squareSize/2,
0]], np.float32)
squareCorners = np.stack((squareCorners[0],
squareCorners[0] + [squareSize, 0, 0],
squareCorners[0] + [squareSize, squareSize, 0],
squareCorners[0] + [0, squareSize, 0]))
projectedCorners, _ = cv.projectPoints(squareCorners, rvec, tvec, cameraMatrix, distCoeffs)
projectedCorners = projectedCorners.astype(np.int64)
projectedCorners = projectedCorners.reshape(1, 4, 2)
img = cv.fillPoly(img, [projectedCorners], 0)
return img
def projectCharucoBoard(board, cameraMatrix, yaw, pitch, distance, imageSize, markerBorder):
rvec, tvec = getSyntheticRT(yaw, pitch, distance)
img = np.ones(imageSize, np.uint8) * 255
for indexMarker in range(len(board.getIds())):
img = projectMarker(img, board, indexMarker, cameraMatrix, rvec, tvec, markerBorder)
chessboard = projectChessboard(board.getChessboardSize()[0], board.getChessboardSize()[1],
board.getSquareLength(), imageSize, cameraMatrix, rvec, tvec)
chessboard = (chessboard != 0).astype(np.uint8)
img = img * chessboard
return img, rvec, tvec
class aruco_objdetect_test(NewOpenCVTests):
def test_board(self):
p1 = np.array([[0, 0, 0], [0, 1, 0], [1, 1, 0], [1, 0, 0]], dtype=np.float32)
p2 = np.array([[1, 0, 0], [1, 1, 0], [2, 1, 0], [2, 0, 0]], dtype=np.float32)
objPoints = np.array([p1, p2])
dictionary = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_50)
ids = np.array([0, 1])
board = cv.aruco.Board(objPoints, dictionary, ids)
np.testing.assert_array_equal(board.getIds().squeeze(), ids)
np.testing.assert_array_equal(np.ravel(np.array(board.getObjPoints())), np.ravel(np.concatenate([p1, p2])))
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_charuco_refine(self):
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_6X6_50)
board_size = (3, 4)
board = cv.aruco.CharucoBoard(board_size, 1., .7, aruco_dict)
aruco_detector = cv.aruco.ArucoDetector(aruco_dict)
charuco_detector = cv.aruco.CharucoDetector(board)
cell_size = 100
image = board.generateImage((cell_size*board_size[0], cell_size*board_size[1]))
camera = np.array([[1, 0, 0.5],
[0, 1, 0.5],
[0, 0, 1]])
dist = np.array([0, 0, 0, 0, 0], dtype=np.float32).reshape(1, -1)
# generate gold corners of the ArUco markers for the test
gold_corners = np.array(board.getObjPoints())[:, :, 0:2]*cell_size
# detect corners
markerCorners, markerIds, _ = aruco_detector.detectMarkers(image)
# test refine
rejected = [markerCorners[-1]]
markerCorners, markerIds = markerCorners[:-1], markerIds[:-1]
markerCorners, markerIds, _, _ = aruco_detector.refineDetectedMarkers(image, board, markerCorners, markerIds,
rejected, cameraMatrix=camera, distCoeffs=dist)
charucoCorners, charucoIds, _, _ = charuco_detector.detectBoard(image, markerCorners=markerCorners,
markerIds=markerIds)
self.assertEqual(len(charucoIds), 6)
self.assertEqual(len(markerIds), 6)
for i, id in enumerate(markerIds.reshape(-1)):
np.testing.assert_allclose(gold_corners[id], markerCorners[i].reshape(4, 2), 0.01, 1.)
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]))
# Note: Expected values adjusted by -0.5px after fixing the systematic offset bug in charuco_detector.cpp
# The fix removes the incorrect +0.5 offset that was added after cornerSubPix
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 - 0.5, i*cell_size - 0.5))
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)
def test_detect_diamonds(self):
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_6X6_250)
board_size = (3, 3)
board = cv.aruco.CharucoBoard(board_size, 1.0, .8, aruco_dict)
charuco_detector = cv.aruco.CharucoDetector(board)
cell_size = 120
image = board.generateImage((cell_size*board_size[0], cell_size*board_size[1]))
# Note: Expected values adjusted by -0.5px after fixing the systematic offset bug in charuco_detector.cpp
# The fix removes the incorrect +0.5 offset that was added after cornerSubPix
list_gold_corners = [(cell_size - 0.5, cell_size - 0.5), (2*cell_size - 0.5, cell_size - 0.5),
(2*cell_size - 0.5, 2*cell_size - 0.5), (cell_size - 0.5, 2*cell_size - 0.5)]
gold_corners = np.array(list_gold_corners, dtype=np.float32)
diamond_corners, diamond_ids, marker_corners, marker_ids = charuco_detector.detectDiamonds(image)
self.assertEqual(diamond_ids.size, 4)
self.assertEqual(marker_ids.size, 4)
for i in range(0, 4):
self.assertEqual(diamond_ids[0][i], i)
np.testing.assert_allclose(gold_corners, np.array(diamond_corners, dtype=np.float32).reshape(-1, 2), 0.01, 0.1)
# check no segfault when cameraMatrix or distCoeffs are not initialized
def test_charuco_no_segfault_params(self):
dictionary = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_1000)
board = cv.aruco.CharucoBoard((10, 10), 0.019, 0.015, dictionary)
charuco_parameters = cv.aruco.CharucoParameters()
detector = cv.aruco.CharucoDetector(board)
detector.setCharucoParameters(charuco_parameters)
self.assertIsNone(detector.getCharucoParameters().cameraMatrix)
self.assertIsNone(detector.getCharucoParameters().distCoeffs)
def test_charuco_no_segfault_params_constructor(self):
dictionary = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_1000)
board = cv.aruco.CharucoBoard((10, 10), 0.019, 0.015, dictionary)
charuco_parameters = cv.aruco.CharucoParameters()
detector = cv.aruco.CharucoDetector(board, charucoParams=charuco_parameters)
self.assertIsNone(detector.getCharucoParameters().cameraMatrix)
self.assertIsNone(detector.getCharucoParameters().distCoeffs)
# similar to C++ test CV_CharucoDetection.accuracy
def test_charuco_detector_accuracy(self):
iteration = 0
cameraMatrix = np.eye(3, 3, dtype=np.float64)
imgSize = (500, 500)
params = cv.aruco.DetectorParameters()
params.minDistanceToBorder = 3
params.validBitIdThreshold = 0.5
board = cv.aruco.CharucoBoard((4, 4), 0.03, 0.015, cv.aruco.getPredefinedDictionary(cv.aruco.DICT_6X6_250))
detector = cv.aruco.CharucoDetector(board, detectorParams=params)
cameraMatrix[0, 0] = cameraMatrix[1, 1] = 600
cameraMatrix[0, 2] = imgSize[0] / 2
cameraMatrix[1, 2] = imgSize[1] / 2
# for different perspectives
distCoeffs = np.zeros((5, 1), dtype=np.float64)
for distance in [0.2, 0.4]:
for yaw in range(-55, 51, 25):
for pitch in range(-55, 51, 25):
markerBorder = iteration % 2 + 1
iteration += 1
# create synthetic image
img, rvec, tvec = projectCharucoBoard(board, cameraMatrix, yaw * pi / 180, pitch * pi / 180, distance, imgSize, markerBorder)
params.markerBorderBits = markerBorder
detector.setDetectorParameters(params)
if (iteration % 2 != 0):
charucoParameters = cv.aruco.CharucoParameters()
charucoParameters.cameraMatrix = cameraMatrix
charucoParameters.distCoeffs = distCoeffs
detector.setCharucoParameters(charucoParameters)
charucoCorners, charucoIds, corners, ids = detector.detectBoard(img)
self.assertGreater(len(ids), 0)
copyChessboardCorners = board.getChessboardCorners()
copyChessboardCorners -= np.array(board.getRightBottomCorner()) / 2
projectedCharucoCorners, _ = cv.projectPoints(copyChessboardCorners, rvec, tvec, cameraMatrix, distCoeffs)
if charucoIds is None:
# Detection can fail at extreme viewing angles
self.assertTrue(abs(yaw) >= 45 or abs(pitch) >= 45,
f"Detection failed unexpectedly at yaw={yaw}, pitch={pitch}")
continue
for i in range(len(charucoIds)):
currentId = charucoIds[i]
self.assertLess(currentId, len(board.getChessboardCorners()))
reprErr = cv.norm(charucoCorners[i] - projectedCharucoCorners[currentId])
self.assertLessEqual(reprErr, 5)
def test_aruco_match_image_points(self):
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_50)
board_size = (3, 4)
board = cv.aruco.GridBoard(board_size, 5.0, 1.0, aruco_dict)
aruco_corners = np.array(board.getObjPoints())[:, :, :2]
aruco_ids = board.getIds()
obj_points, img_points = board.matchImagePoints(aruco_corners, aruco_ids)
aruco_corners = aruco_corners.reshape(-1, 2)
self.assertEqual(aruco_corners.shape[0], obj_points.shape[0])
self.assertEqual(img_points.shape[0], obj_points.shape[0])
self.assertEqual(2, img_points.shape[1])
np.testing.assert_array_equal(aruco_corners, obj_points[:, :2].reshape(-1, 2))
def test_charuco_match_image_points(self):
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_50)
board_size = (3, 4)
board = cv.aruco.CharucoBoard(board_size, 5.0, 1.0, aruco_dict)
chessboard_corners = np.array(board.getChessboardCorners())[:, :2]
chessboard_ids = board.getIds()
obj_points, img_points = board.matchImagePoints(chessboard_corners, chessboard_ids)
self.assertEqual(chessboard_corners.shape[0], obj_points.shape[0])
self.assertEqual(img_points.shape[0], obj_points.shape[0])
self.assertEqual(2, img_points.shape[1])
np.testing.assert_array_equal(chessboard_corners, obj_points[:, :2].reshape(-1, 2))
def test_draw_detected_markers(self):
detected_points = [[[10, 10], [50, 10], [50, 50], [10, 50]]]
img = np.zeros((60, 60), dtype=np.uint8)
# add extra dimension in Python to create Nx4 Mat with 2 channels
points1 = np.array(detected_points).reshape(-1, 4, 1, 2)
img = cv.aruco.drawDetectedMarkers(img, points1, borderColor=255)
# check that the marker borders are painted
contours, _ = cv.findContours(img, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
self.assertEqual(len(contours), 1)
self.assertEqual(img[10, 10], 255)
self.assertEqual(img[50, 10], 255)
self.assertEqual(img[50, 50], 255)
self.assertEqual(img[10, 50], 255)
# must throw Exception without extra dimension
points2 = np.array(detected_points)
with self.assertRaises(Exception):
img = cv.aruco.drawDetectedMarkers(img, points2, borderColor=255)
def test_draw_detected_charuco(self):
detected_points = [[[10, 10], [50, 10], [50, 50], [10, 50]]]
img = np.zeros((60, 60), dtype=np.uint8)
# add extra dimension in Python to create Nx1 Mat with 2 channels
points = np.array(detected_points).reshape(-1, 1, 2)
img = cv.aruco.drawDetectedCornersCharuco(img, points, cornerColor=255)
# check that the 4 charuco corners are painted
contours, _ = cv.findContours(img, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
self.assertEqual(len(contours), 4)
for contour in contours:
center_x = round(np.average(contour[:, 0, 0]))
center_y = round(np.average(contour[:, 0, 1]))
center = [center_x, center_y]
self.assertTrue(center in detected_points[0])
# must throw Exception without extra dimension
points2 = np.array(detected_points)
with self.assertRaises(Exception):
img = cv.aruco.drawDetectedCornersCharuco(img, points2, borderColor=255)
def test_draw_detected_diamonds(self):
detected_points = [[[10, 10], [50, 10], [50, 50], [10, 50]]]
img = np.zeros((60, 60), dtype=np.uint8)
# add extra dimension in Python to create Nx4 Mat with 2 channels
points = np.array(detected_points).reshape(-1, 4, 1, 2)
img = cv.aruco.drawDetectedDiamonds(img, points, borderColor=255)
# check that the diamonds borders are painted
contours, _ = cv.findContours(img, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
self.assertEqual(len(contours), 1)
self.assertEqual(img[10, 10], 255)
self.assertEqual(img[50, 10], 255)
self.assertEqual(img[50, 50], 255)
self.assertEqual(img[10, 50], 255)
# must throw Exception without extra dimension
points2 = np.array(detected_points)
with self.assertRaises(Exception):
img = cv.aruco.drawDetectedDiamonds(img, points2, borderColor=255)
def test_multi_dict_arucodetector(self):
aruco_params = cv.aruco.DetectorParameters()
aruco_dicts = [
cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_250),
cv.aruco.getPredefinedDictionary(cv.aruco.DICT_5X5_250)
]
aruco_detector = cv.aruco.ArucoDetector(aruco_dicts, aruco_params)
id = 2
marker_size = 100
offset = 10
img_marker1 = cv.aruco.generateImageMarker(aruco_dicts[0], id, marker_size, aruco_params.markerBorderBits)
img_marker1 = np.pad(img_marker1, pad_width=offset, mode='constant', constant_values=255)
img_marker2 = cv.aruco.generateImageMarker(aruco_dicts[1], id, marker_size, aruco_params.markerBorderBits)
img_marker2 = np.pad(img_marker2, pad_width=offset, mode='constant', constant_values=255)
img_markers = np.concatenate((img_marker1, img_marker2), axis=1)
corners, ids, rejected, dictIndices = aruco_detector.detectMarkersMultiDict(img_markers)
self.assertEqual(2, len(ids))
self.assertEqual(id, ids[0])
self.assertEqual(id, ids[1])
self.assertEqual(2, len(dictIndices))
self.assertEqual(0, dictIndices[0])
self.assertEqual(1, dictIndices[1])
if __name__ == '__main__':
NewOpenCVTests.bootstrap()
@@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
'''
===============================================================================
QR code detect and decode pipeline.
===============================================================================
'''
import os
import numpy as np
import cv2 as cv
from tests_common import NewOpenCVTests, unittest
class qrcode_detector_test(NewOpenCVTests):
def test_detect(self):
img = cv.imread(os.path.join(self.extraTestDataPath, 'cv/qrcode/link_ocv.jpg'))
self.assertFalse(img is None)
detector = cv.QRCodeDetector()
retval, points = detector.detect(img)
self.assertTrue(retval)
self.assertEqual(points.shape, (1, 4, 2))
def test_detect_and_decode(self):
img = cv.imread(os.path.join(self.extraTestDataPath, 'cv/qrcode/link_ocv.jpg'))
self.assertFalse(img is None)
detector = cv.QRCodeDetector()
retval, points, straight_qrcode = detector.detectAndDecode(img)
self.assertEqual(retval, "https://opencv.org/")
self.assertEqual(points.shape, (1, 4, 2))
def test_detect_multi(self):
img = cv.imread(os.path.join(self.extraTestDataPath, 'cv/qrcode/multiple/6_qrcodes.png'))
self.assertFalse(img is None)
detector = cv.QRCodeDetector()
retval, points = detector.detectMulti(img)
self.assertTrue(retval)
self.assertEqual(points.shape, (6, 4, 2))
def test_detect_and_decode_multi(self):
img = cv.imread(os.path.join(self.extraTestDataPath, 'cv/qrcode/multiple/6_qrcodes.png'))
self.assertFalse(img is None)
detector = cv.QRCodeDetector()
retval, decoded_data, points, straight_qrcode = detector.detectAndDecodeMulti(img)
self.assertTrue(retval)
self.assertEqual(len(decoded_data), 6)
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))
def test_decode_non_ascii(self):
import sys
if sys.version_info[0] < 3:
raise unittest.SkipTest('Python 2.x is not supported')
img = cv.imread(os.path.join(self.extraTestDataPath, 'cv/qrcode/umlaut.png'))
self.assertFalse(img is None)
detector = cv.QRCodeDetector()
decoded_data, _, _ = detector.detectAndDecode(img)
self.assertTrue(isinstance(decoded_data, str))
self.assertTrue("Müllheimstrasse" in decoded_data)
def test_kanji(self):
inp = "こんにちは世界"
inp_bytes = inp.encode("shift-jis")
params = cv.QRCodeEncoder_Params()
params.mode = cv.QRCodeEncoder_MODE_KANJI
encoder = cv.QRCodeEncoder_create(params)
qrcode = encoder.encode(inp_bytes)
qrcode = cv.resize(qrcode, (0, 0), fx=2, fy=2, interpolation=cv.INTER_NEAREST)
detector = cv.QRCodeDetector()
data, _, _ = detector.detectAndDecodeBytes(qrcode)
self.assertEqual(data, inp_bytes)
self.assertEqual(detector.getEncoding(), cv.QRCodeEncoder_ECI_SHIFT_JIS)
self.assertEqual(data.decode("shift-jis"), inp)
_, data, _, _ = detector.detectAndDecodeBytesMulti(qrcode)
self.assertEqual(data[0], inp_bytes)
self.assertEqual(detector.getEncoding(0), cv.QRCodeEncoder_ECI_SHIFT_JIS)
self.assertEqual(data[0].decode("shift-jis"), inp)
+285
View File
@@ -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/geometry.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 = (int)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 = (int)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 = (int)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();
}
}
+120
View File
@@ -0,0 +1,120 @@
// 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/objdetect/barcode.hpp"
namespace opencv_test{namespace{
typedef ::perf::TestBaseWithParam< tuple<string, cv::Size> > Perf_Barcode_multi;
typedef ::perf::TestBaseWithParam< tuple<string, cv::Size> > Perf_Barcode_single;
PERF_TEST_P_(Perf_Barcode_multi, detect)
{
const string root = "cv/barcode/multiple/";
const string name_current_image = get<0>(GetParam());
const cv::Size sz = get<1>(GetParam());
const string image_path = findDataFile(root + name_current_image);
Mat src = imread(image_path);
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
cv::resize(src, src, sz);
vector< Point > corners;
auto bardet = barcode::BarcodeDetector();
bool res = false;
TEST_CYCLE()
{
res = bardet.detectMulti(src, corners);
}
SANITY_CHECK_NOTHING();
ASSERT_TRUE(res);
ASSERT_EQ(16ull, corners.size());
}
PERF_TEST_P_(Perf_Barcode_multi, detect_decode)
{
const string root = "cv/barcode/multiple/";
const string name_current_image = get<0>(GetParam());
const cv::Size sz = get<1>(GetParam());
const string image_path = findDataFile(root + name_current_image);
Mat src = imread(image_path);
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
cv::resize(src, src, sz);
vector<std::string> decoded_info;
vector<std::string> decoded_type;
vector< Point > corners;
auto bardet = barcode::BarcodeDetector();
bool res = false;
TEST_CYCLE()
{
res = bardet.detectAndDecodeWithType(src, decoded_info, decoded_type, corners);
}
SANITY_CHECK_NOTHING();
ASSERT_TRUE(res);
ASSERT_EQ(16ull, corners.size());
ASSERT_EQ(4ull, decoded_info.size());
}
PERF_TEST_P_(Perf_Barcode_single, detect)
{
const string root = "cv/barcode/single/";
const string name_current_image = get<0>(GetParam());
const cv::Size sz = get<1>(GetParam());
const string image_path = findDataFile(root + name_current_image);
Mat src = imread(image_path);
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
cv::resize(src, src, sz);
vector< Point > corners;
auto bardet = barcode::BarcodeDetector();
bool res = false;
TEST_CYCLE()
{
res = bardet.detectMulti(src, corners);
}
SANITY_CHECK_NOTHING();
ASSERT_TRUE(res);
ASSERT_EQ(4ull, corners.size());
}
PERF_TEST_P_(Perf_Barcode_single, detect_decode)
{
const string root = "cv/barcode/single/";
const string name_current_image = get<0>(GetParam());
const cv::Size sz = get<1>(GetParam());
const string image_path = findDataFile(root + name_current_image);
Mat src = imread(image_path);
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
cv::resize(src, src, sz);
vector<std::string> decoded_info;
vector<std::string> decoded_type;
vector< Point > corners;
auto bardet = barcode::BarcodeDetector();
bool res = false;
TEST_CYCLE()
{
res = bardet.detectAndDecodeWithType(src, decoded_info, decoded_type, corners);
}
SANITY_CHECK_NOTHING();
ASSERT_TRUE(res);
ASSERT_EQ(4ull, corners.size());
ASSERT_EQ(1ull, decoded_info.size());
}
INSTANTIATE_TEST_CASE_P(/*nothing*/, Perf_Barcode_multi,
testing::Combine(
testing::Values("4_barcodes.jpg"),
testing::Values(cv::Size(2041, 2722), cv::Size(1361, 1815), cv::Size(680, 907))));
INSTANTIATE_TEST_CASE_P(/*nothing*/, Perf_Barcode_single,
testing::Combine(
testing::Values("book.jpg", "bottle_1.jpg", "bottle_2.jpg"),
testing::Values(cv::Size(480, 360), cv::Size(640, 480), cv::Size(800, 600))));
}} //namespace
@@ -0,0 +1,42 @@
#include "perf_precomp.hpp"
namespace opencv_test
{
using namespace perf;
typedef tuple<std::string, cv::Size> String_Size_t;
typedef perf::TestBaseWithParam<String_Size_t> String_Size;
PERF_TEST_P(String_Size, asymm_circles_grid, testing::Values(
String_Size_t("cv/cameracalibration/asymmetric_circles/acircles1.png", Size(7,13)),
String_Size_t("cv/cameracalibration/asymmetric_circles/acircles2.png", Size(7,13)),
String_Size_t("cv/cameracalibration/asymmetric_circles/acircles3.png", Size(7,13)),
String_Size_t("cv/cameracalibration/asymmetric_circles/acircles4.png", Size(5,5)),
String_Size_t("cv/cameracalibration/asymmetric_circles/acircles5.png", Size(5,5)),
String_Size_t("cv/cameracalibration/asymmetric_circles/acircles6.png", Size(5,5)),
String_Size_t("cv/cameracalibration/asymmetric_circles/acircles7.png", Size(3,9)),
String_Size_t("cv/cameracalibration/asymmetric_circles/acircles8.png", Size(3,9)),
String_Size_t("cv/cameracalibration/asymmetric_circles/acircles9.png", Size(3,9))
)
)
{
string filename = getDataPath(get<0>(GetParam()));
Size gridSize = get<1>(GetParam());
Mat frame = imread(filename);
if (frame.empty())
FAIL() << "Unable to load source image " << filename;
vector<Point2f> ptvec;
ptvec.resize(gridSize.area());
cvtColor(frame, frame, COLOR_BGR2GRAY);
declare.in(frame).out(ptvec);
TEST_CYCLE() ASSERT_TRUE(findCirclesGrid(frame, gridSize, ptvec, CALIB_CB_CLUSTERING | CALIB_CB_ASYMMETRIC_GRID));
SANITY_CHECK(ptvec, 2);
}
} // namespace
+7
View File
@@ -0,0 +1,7 @@
#include "perf_precomp.hpp"
#if defined(HAVE_HPX)
#include <hpx/hpx_main.hpp>
#endif
CV_PERF_TEST_MAIN(objdetect)
+29
View File
@@ -0,0 +1,29 @@
// 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"
namespace opencv_test
{
namespace
{
using namespace std;
using namespace cv::mcc;
PERF_TEST(CV_mcc_perf, detect) {
string path = cvtest::findDataFile("cv/mcc/mcc_ccm_test.jpg");
Mat img = imread(path, IMREAD_COLOR);
Ptr<CCheckerDetector> detector = CCheckerDetector::create();
detector->setColorChartType(MCC24);
// detect MCC24 board
TEST_CYCLE() {
ASSERT_TRUE(detector->process(img, 1));
}
SANITY_CHECK_NOTHING();
}
} // namespace
} // namespace opencv_test
+7
View File
@@ -0,0 +1,7 @@
#ifndef __OPENCV_PERF_PRECOMP_HPP__
#define __OPENCV_PERF_PRECOMP_HPP__
#include "opencv2/ts.hpp"
#include "opencv2/objdetect.hpp"
#endif
@@ -0,0 +1,202 @@
// 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 "../test/test_qr_utils.hpp"
namespace opencv_test
{
namespace
{
typedef ::perf::TestBaseWithParam< std::string > Perf_Objdetect_QRCode;
PERF_TEST_P_(Perf_Objdetect_QRCode, detect)
{
const std::string name_current_image = GetParam();
const std::string root = "cv/qrcode/";
std::string image_path = findDataFile(root + name_current_image);
Mat src = imread(image_path, IMREAD_GRAYSCALE), straight_barcode;
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
std::vector< Point > corners;
QRCodeDetector qrcode;
TEST_CYCLE() ASSERT_TRUE(qrcode.detect(src, corners));
const int pixels_error = 3;
check_qr(root, name_current_image, "test_images", corners, {}, pixels_error);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(Perf_Objdetect_QRCode, decode)
{
const std::string name_current_image = GetParam();
const std::string root = "cv/qrcode/";
std::string image_path = findDataFile(root + name_current_image);
Mat src = imread(image_path, IMREAD_GRAYSCALE), straight_barcode;
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
std::vector< Point > corners;
std::string decoded_info;
QRCodeDetector qrcode;
ASSERT_TRUE(qrcode.detect(src, corners));
TEST_CYCLE()
{
decoded_info = qrcode.decode(src, corners, straight_barcode);
ASSERT_FALSE(decoded_info.empty());
}
const int pixels_error = 3;
check_qr(root, name_current_image, "test_images", corners, {decoded_info}, pixels_error);
SANITY_CHECK_NOTHING();
}
typedef ::perf::TestBaseWithParam<std::tuple<std::string, std::string>> Perf_Objdetect_QRCode_Multi;
static std::set<std::pair<std::string, std::string>> disabled_samples = {{"5_qrcodes.png", "aruco_based"}};
PERF_TEST_P_(Perf_Objdetect_QRCode_Multi, detectMulti)
{
const std::string name_current_image = get<0>(GetParam());
const std::string method = get<1>(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;
std::vector<Point> corners;
GraphicalCodeDetector qrcode = QRCodeDetector();
if (method == "aruco_based") {
qrcode = QRCodeDetectorAruco();
}
TEST_CYCLE() ASSERT_TRUE(qrcode.detectMulti(src, corners));
const int pixels_error = 7;
check_qr(root, name_current_image, "multiple_images", corners, {}, pixels_error, true);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(Perf_Objdetect_QRCode_Multi, decodeMulti)
{
const std::string name_current_image = get<0>(GetParam());
std::string method = get<1>(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;
if (disabled_samples.find({name_current_image, method}) != disabled_samples.end()) {
throw SkipTestException(name_current_image + " is disabled sample for method " + method);
}
GraphicalCodeDetector qrcode = QRCodeDetector();
if (method == "aruco_based") {
qrcode = QRCodeDetectorAruco();
}
std::vector<Point2f> corners;
ASSERT_TRUE(qrcode.detectMulti(src, corners));
std::vector<Mat> straight_barcode;
std::vector< cv::String > decoded_info;
TEST_CYCLE()
{
ASSERT_TRUE(qrcode.decodeMulti(src, corners, decoded_info, straight_barcode));
}
ASSERT_TRUE(decoded_info.size() > 0ull);
for(size_t i = 0; i < decoded_info.size(); i++) {
ASSERT_FALSE(decoded_info[i].empty());
}
ASSERT_EQ(decoded_info.size(), straight_barcode.size());
vector<Point> corners_result(corners.size());
for (size_t i = 0ull; i < corners_result.size(); i++) {
corners_result[i] = corners[i];
}
const int pixels_error = 7;
check_qr(root, name_current_image, "multiple_images", corners_result, decoded_info, pixels_error, true);
SANITY_CHECK_NOTHING();
}
INSTANTIATE_TEST_CASE_P(/*nothing*/, Perf_Objdetect_QRCode,
::testing::Values(
"version_1_down.jpg", "version_1_left.jpg", "version_1_right.jpg", "version_1_up.jpg", "version_1_top.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
INSTANTIATE_TEST_CASE_P(/*nothing*/, Perf_Objdetect_QRCode_Multi,
testing::Combine(testing::Values("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"),
testing::Values("contours_based", "aruco_based")));
typedef ::perf::TestBaseWithParam< tuple< std::string, Size > > Perf_Objdetect_Not_QRCode;
PERF_TEST_P_(Perf_Objdetect_Not_QRCode, detect)
{
std::vector<Point> corners;
std::string type_gen = get<0>(GetParam());
Size resolution = get<1>(GetParam());
Mat not_qr_code(resolution, CV_8UC1, Scalar(0));
if (type_gen == "random")
{
RNG rng;
rng.fill(not_qr_code, RNG::UNIFORM, Scalar(0), Scalar(1));
}
if (type_gen == "chessboard")
{
uint8_t next_pixel = 0;
for (int r = 0; r < not_qr_code.rows * not_qr_code.cols; r++)
{
int i = r / not_qr_code.cols;
int j = r % not_qr_code.cols;
not_qr_code.ptr<uchar>(i)[j] = next_pixel;
next_pixel = 255 - next_pixel;
}
}
QRCodeDetector qrcode;
TEST_CYCLE() ASSERT_FALSE(qrcode.detect(not_qr_code, corners));
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(Perf_Objdetect_Not_QRCode, decode)
{
Mat straight_barcode;
std::vector< Point > corners;
corners.push_back(Point( 0, 0)); corners.push_back(Point( 0, 5));
corners.push_back(Point(10, 0)); corners.push_back(Point(15, 15));
std::string type_gen = get<0>(GetParam());
Size resolution = get<1>(GetParam());
Mat not_qr_code(resolution, CV_8UC1, Scalar(0));
if (type_gen == "random")
{
RNG rng;
rng.fill(not_qr_code, RNG::UNIFORM, Scalar(0), Scalar(1));
}
if (type_gen == "chessboard")
{
uint8_t next_pixel = 0;
for (int r = 0; r < not_qr_code.rows * not_qr_code.cols; r++)
{
int i = r / not_qr_code.cols;
int j = r % not_qr_code.cols;
not_qr_code.ptr<uchar>(i)[j] = next_pixel;
next_pixel = 255 - next_pixel;
}
}
QRCodeDetector qrcode;
TEST_CYCLE() ASSERT_TRUE(qrcode.decode(not_qr_code, corners, straight_barcode).empty());
SANITY_CHECK_NOTHING();
}
INSTANTIATE_TEST_CASE_P(/*nothing*/, Perf_Objdetect_Not_QRCode,
::testing::Combine(
::testing::Values("zero", "random", "chessboard"),
::testing::Values(Size(640, 480), Size(1280, 720),
Size(1920, 1080), Size(3840, 2160))
));
}
} // namespace
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,116 @@
// 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 "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 &parameters, Mat& mThresh);
zarray_t *apriltag_quad_thresh(const DetectorParameters &parameters, 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 {
struct UnionFind {
UnionFind(uint32_t _maxid)
{
maxid = _maxid;
data.resize(maxid+1);
for (unsigned int i = 0; i <= maxid; i++) {
data[i].size = 1;
data[i].parent = i;
}
};
inline uint32_t get_representative(uint32_t id) {
uint32_t root = id;
// chase down the root
while (data[root].parent != root) {
root = 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 (data[id].parent != root) {
uint32_t tmp = data[id].parent;
data[id].parent = root;
id = tmp;
}
return root;
}
inline uint32_t get_set_size(uint32_t id) {
uint32_t repid = get_representative(id);
return data[repid].size;
}
inline uint32_t connect(uint32_t aid, uint32_t bid) {
uint32_t aroot = get_representative(aid);
uint32_t broot = get_representative(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 = data[aroot].size;
uint32_t bsize = 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) {
data[broot].parent = aroot;
data[aroot].size += bsize;
return aroot;
} else {
data[aroot].parent = broot;
data[broot].size += asize;
return broot;
}
}
#define DO_UNIONFIND(dx, dy) if (im.data[y*s + dy*s + x + dx] == v) connect(y*w + x, y*w + dy*w + x + dx);
void do_line(Mat &im, int w, int s, int y) {
CV_Assert(y+1 < im.rows);
CV_Assert(!im.empty());
for (int x = 1; x < w - 1; x++) {
uint8_t v = im.data[y*s + x];
if (v == 127)
continue;
// (dx,dy) pairs for 8 connectivity:
// (REFERENCE) (1, 0)
// (-1, 1) (0, 1) (1, 1)
//
DO_UNIONFIND(1, 0);
DO_UNIONFIND(0, 1);
if (v == 255) {
DO_UNIONFIND(-1, 1);
DO_UNIONFIND(1, 1);
}
}
}
#undef DO_UNIONFIND
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;
};
uint32_t maxid;
std::vector<ufrec> data;
};
}}
#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,206 @@
// 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);
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
+644
View File
@@ -0,0 +1,644 @@
// 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/utils/logger.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(InputArrayOfArrays detectedCorners, InputArray detectedIds, OutputArray _objPoints,
OutputArray imgPoints) const;
virtual void generateImage(Size outSize, OutputArray img, int marginSize, int borderBits) const;
};
void Board::Impl::matchImagePoints(InputArrayOfArrays detectedCorners, InputArray detectedIds, OutputArray _objPoints,
OutputArray imgPoints) const {
CV_Assert(detectedIds.total() == detectedCorners.total());
CV_Assert(detectedIds.total() > 0ull);
CV_Assert(detectedCorners.depth() == CV_32F);
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
Mat detectedIdsMat = detectedIds.getMat();
vector<Mat> detectedCornersVecMat;
detectedCorners.getMatVector(detectedCornersVecMat);
CV_Assert((int)detectedCornersVecMat.front().total()*detectedCornersVecMat.front().channels() == 8);
for(unsigned int i = 0; i < nDetectedMarkers; i++) {
int currentId = detectedIdsMat.at<int>(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(detectedCornersVecMat[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;
// 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
Point2f vecWidth = outCorners[1] - outCorners[0];
float width = (float)cv::norm(vecWidth);
Point2f vecHeight = outCorners[2] - outCorners[0];
float height = (float)cv::norm(vecHeight);
Size dst_sz(cvRound(width), cvRound(height));
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(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(InputArrayOfArrays 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),
legacyPattern(false)
{
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;
// set pre4.6.0 chessboard pattern behavior (even row count patterns have a white box in the upper left corner)
bool legacyPattern;
};
GridBoard::GridBoard() {}
GridBoard::GridBoard(const Size& size, float markerLength, float markerSeparation,
const Dictionary &dictionary, InputArray ids):
Board(new GridBoardImpl(dictionary, size, markerLength, markerSeparation)) {
float onePin = markerLength / ((float)(dictionary.markerSize+2));
if (markerSeparation < onePin*.7f) {
CV_LOG_WARNING(NULL, "Marker border " << markerSeparation << " is less than 70% of ArUco pin size "
<< onePin << ". Please increase markerSeparation or decrease markerLength for stable board detection");
}
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),
legacyPattern(false)
{}
// chessboard size
Size size;
// Physical size of chessboard squares side (normally in meters)
float squareLength;
// Physical marker side length (normally in meters)
float markerLength;
// set pre4.6.0 chessboard pattern behavior (even row count patterns have a white box in the upper left corner)
bool legacyPattern;
// vector of chessboard 3D corners precalculated
std::vector<Point3f> chessboardCorners;
// for each charuco corner, nearest marker index in ids array
std::vector<std::vector<int> > nearestMarkerIdx;
// for each charuco corner, nearest marker corner id of each marker
std::vector<std::vector<int> > nearestMarkerCorners;
void createCharucoBoard();
void calcNearestMarkerCorners();
void matchImagePoints(InputArrayOfArrays detectedCharuco, InputArray detectedIds,
OutputArray objPoints, OutputArray imgPoints) const override;
void generateImage(Size outSize, OutputArray img, int marginSize, int borderBits) const override;
};
void CharucoBoardImpl::createCharucoBoard() {
float diffSquareMarkerLength = (squareLength - markerLength) / 2;
int totalMarkers = (int)(ids.size());
// calculate Board objPoints
int nextId = 0;
objPoints.clear();
for(int y = 0; y < size.height; y++) {
for(int x = 0; x < size.width; x++) {
if(legacyPattern && (size.height % 2 == 0)) { // legacy behavior only for even row count patterns
if((y + 1) % 2 == x % 2) continue; // black corner, no marker here
} else {
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)
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));
// now fill chessboardCorners
chessboardCorners.clear();
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;
chessboardCorners.push_back(corner);
}
}
rightBottomBorder = Point3f(size.width * squareLength, size.height * squareLength, 0.f);
calcNearestMarkerCorners();
}
/** Fill nearestMarkerIdx and nearestMarkerCorners arrays */
void CharucoBoardImpl::calcNearestMarkerCorners() {
nearestMarkerIdx.clear();
nearestMarkerCorners.clear();
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 detectedCharuco, InputArray detectedIds,
OutputArray outObjPoints, OutputArray outImgPoints) const {
CV_CheckEQ(detectedIds.total(), detectedCharuco.total(), "Number of corners and ids must be equal");
CV_Assert(detectedIds.total() > 0ull);
CV_Assert(detectedCharuco.depth() == CV_32F);
// detectedCharuco includes charuco corners as vector<Point2f> or Mat.
// Python bindings could add extra dimension to detectedCharuco and therefore vector<Mat> case is additionally processed.
CV_Assert((detectedCharuco.isMat() || detectedCharuco.isVector() || detectedCharuco.isMatVector() || detectedCharuco.isUMatVector())
&& detectedCharuco.depth() == CV_32F);
size_t nDetected = detectedCharuco.total();
vector<Point3f> objPnts(nDetected);
vector<Point2f> imgPnts(nDetected);
Mat detectedCharucoMat, detectedIdsMat = detectedIds.getMat();
if (!detectedCharuco.isMatVector()) {
detectedCharucoMat = detectedCharuco.getMat();
CV_Assert(detectedCharucoMat.checkVector(2));
}
std::vector<Mat> detectedCharucoVecMat;
if (detectedCharuco.isMatVector()) {
detectedCharuco.getMatVector(detectedCharucoVecMat);
}
for(size_t i = 0ull; i < nDetected; i++) {
int pointId = detectedIdsMat.at<int>((int)i);
CV_Assert(pointId >= 0 && pointId < (int)chessboardCorners.size());
objPnts[i] = chessboardCorners[pointId];
if (detectedCharuco.isMatVector()) {
CV_Assert((int)detectedCharucoVecMat[i].total() * detectedCharucoVecMat[i].channels() == 2);
imgPnts[i] = detectedCharucoVecMat[i].ptr<Point2f>(0)[0];
}
else
imgPnts[i] = detectedCharucoMat.ptr<Point2f>(0)[i];
}
Mat(objPnts).copyTo(outObjPoints);
Mat(imgPnts).copyTo(outImgPoints);
}
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);
// the size of the chessboard square depends on the location of the chessboard
float pixInSquare = 0.f;
// the size of the chessboard in pixels
Size pixInChessboard(noMarginsImg.cols, noMarginsImg.rows);
// determine the zone where the chessboard is placed
float pixInSquareX = (float)noMarginsImg.cols / (float)size.width;
float pixInSquareY = (float)noMarginsImg.rows / (float)size.height;
Point startChessboard(0, 0);
if (pixInSquareX <= pixInSquareY) {
// the width of "noMarginsImg" image determines the dimensions of the chessboard
pixInSquare = pixInSquareX;
pixInChessboard.height = cvRound(pixInSquare*size.height);
int rowsMargin = (noMarginsImg.rows - pixInChessboard.height) / 2;
startChessboard.y = rowsMargin;
}
else {
// the height of "noMarginsImg" image determines the dimensions of the chessboard
pixInSquare = pixInSquareY;
pixInChessboard.width = cvRound(pixInSquare*size.width);
int colsMargin = (noMarginsImg.cols - pixInChessboard.width) / 2;
startChessboard.x = colsMargin;
}
// determine the zone where the chessboard is located
Mat chessboardZoneImg = noMarginsImg(Rect(startChessboard, pixInChessboard));
// marker size in pixels
const float pixInMarker = markerLength/squareLength*pixInSquare;
// the size of the marker margin in pixels
const float pixInMarginMarker = 0.5f*(pixInSquare - pixInMarker);
// determine the zone where the aruco markers are located
int endArucoX = cvRound(pixInSquare*(size.width-1)+pixInMarginMarker+pixInMarker);
int endArucoY = cvRound(pixInSquare*(size.height-1)+pixInMarginMarker+pixInMarker);
Mat arucoZone = chessboardZoneImg(Range(cvRound(pixInMarginMarker), endArucoY), Range(cvRound(pixInMarginMarker), endArucoX));
// draw markers
Board::Impl::generateImage(arucoZone.size(), arucoZone, 0, borderBits);
// now draw black squares
for(int y = 0; y < size.height; y++) {
for(int x = 0; x < size.width; x++) {
if(legacyPattern && (size.height % 2 == 0)) { // legacy behavior only for even row count patterns
if((y + 1) % 2 != x % 2) continue; // white corner, don't do anything
} else {
if(y % 2 != x % 2) continue; // white corner, don't do anything
}
float startX = pixInSquare * float(x);
float startY = pixInSquare * float(y);
Mat squareZone = chessboardZoneImg(Range(cvRound(startY), cvRound(startY + pixInSquare)),
Range(cvRound(startX), cvRound(startX + pixInSquare)));
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);
float onePin = markerLength / ((float)(dictionary.markerSize+2));
float markerSeparation = (squareLength - markerLength)/2.f;
if (markerSeparation < onePin*.7f) {
CV_LOG_WARNING(NULL, "Marker border " << markerSeparation << " is less than 70% of ArUco pin size "
<< onePin <<". Please increase markerSeparation or decrease markerLength for stable board detection");
}
ids.copyTo(impl->ids);
static_pointer_cast<CharucoBoardImpl>(impl)->createCharucoBoard();
}
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;
}
void CharucoBoard::setLegacyPattern(bool legacyPattern) {
CV_Assert(impl);
if (static_pointer_cast<CharucoBoardImpl>(impl)->legacyPattern != legacyPattern)
{
static_pointer_cast<CharucoBoardImpl>(impl)->legacyPattern = legacyPattern;
static_pointer_cast<CharucoBoardImpl>(impl)->createCharucoBoard();
}
}
bool CharucoBoard::getLegacyPattern() const {
CV_Assert(impl);
return static_pointer_cast<CharucoBoardImpl>(impl)->legacyPattern;
}
bool CharucoBoard::checkCharucoCornersCollinear(InputArray charucoIds) const {
CV_Assert(impl);
Mat charucoIdsMat = charucoIds.getMat();
unsigned int nCharucoCorners = (unsigned int)charucoIdsMat.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() >= charucoIdsMat.total());
Vec<double, 3> point0(board->chessboardCorners[charucoIdsMat.at<int>(0)].x,
board->chessboardCorners[charucoIdsMat.at<int>(0)].y, 1);
Vec<double, 3> point1(board->chessboardCorners[charucoIdsMat.at<int>(1)].x,
board->chessboardCorners[charucoIdsMat.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[charucoIdsMat.at<int>(i)].x;
testPoint(1) = board->chessboardCorners[charucoIdsMat.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,490 @@
// 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 &onlyCellPixelRatio, CV_OUT int &idx, CV_OUT int &rotation, double maxCorrectionRate, float validBitIdThreshold) const {
CV_Assert(onlyCellPixelRatio.rows == markerSize && onlyCellPixelRatio.cols == markerSize);
int maxCorrectionRecalculed = int(double(maxCorrectionBits) * maxCorrectionRate);
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(int r = 0; r < 4; r++) {
Mat bitsRot = getBitsFromByteList(bytesList.rowRange(m, m + 1), markerSize, r);
bitsRot.convertTo(bitsRot, CV_32F);
// Loop over all bits dictBitsList [m, markerSize * markerSize, 4]; onlyCellPixelRatio [markerSize, markerSize]
int currentHamming = 0;
for(int i = 0; i < markerSize; i++) {
for(int j = 0; j < markerSize; j++) {
// If detected bit is too far from the ground truth, consider it false.
if(fabs(onlyCellPixelRatio.at<float>(i, j) - static_cast<float>(bitsRot.at<float>(i, j))) > validBitIdThreshold){
currentHamming++;
}
}
}
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;
}
bool Dictionary::identify(const Mat &onlyBits, CV_OUT int &idx, CV_OUT int &rotation, double maxCorrectionRate) const {
CV_Assert(onlyBits.rows == markerSize && onlyBits.cols == markerSize);
Mat candidateBitRatio;
onlyBits.convertTo(candidateBitRatio, CV_32F);
const float validBitIdThreshold = DEFAULT_VALID_BIT_ID_THRESHOLD;
return identify(candidateBitRatio, idx, rotation, maxCorrectionRate, validBitIdThreshold);
}
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 = getMarkerBits(id);
bits.convertTo(bits, CV_8U, 255.0);
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::getMarkerBits(int markerId, int rotationId) const {
const int nbRotations = 4;
CV_Assert(markerId < bytesList.rows);
CV_Assert(rotationId < nbRotations);
Mat bits(markerSize, markerSize, CV_32F, Scalar::all(0));
Mat bitsUints = getBitsFromByteList(bytesList.rowRange(markerId, markerId + 1), markerSize, rotationId);
bitsUints.convertTo(bits, CV_32F);
CV_Assert(bits.rows == markerSize && bits.cols == markerSize);
return bits;
}
Mat Dictionary::getBitsFromByteList(const Mat &byteList, int markerSize, int rotationId) {
CV_Assert(byteList.total() > 0 &&
byteList.total() >= (unsigned int)markerSize * markerSize / 8 &&
byteList.total() <= (unsigned int)markerSize * markerSize / 8 + 1);
CV_Assert(byteList.channels() >= 4);
CV_Assert(rotationId >= 0 && rotationId < 4);
Mat bits = Mat::zeros(markerSize, markerSize, CV_8UC1);
unsigned char *bitsPtr = bits.ptr();
// Use a base offset for the selected rotation
int nbytes = (bits.cols * bits.rows + 8 - 1) / 8; // integer ceil
int base = rotationId * nbytes;
const unsigned char *currentBytePtr = byteList.ptr() + base;
const unsigned char *currentBytePtrEnd = currentBytePtr + bits.total() / 8;
for(;currentBytePtr < currentBytePtrEnd; ++currentBytePtr) {
unsigned char currentByte = *currentBytePtr;
for(int mask = 1 << 7; mask != 0; mask >>= 1) {
if (currentByte & mask) *bitsPtr = 1;
++bitsPtr;
}
}
// if not enough bits for one more byte, we are in the end
// update bit position accordingly
if (bits.total() % 8 != 0) {
unsigned char currentByte = *currentBytePtrEnd;
int mask = 1 << ((bits.total() % 8) - 1);
for(; mask != 0; mask >>= 1) {
if (currentByte & mask) *bitsPtr = 1;
++bitsPtr;
}
}
return bits;
}
Dictionary getPredefinedDictionary(PredefinedDictionaryType name) {
// The maximum number of bits that can be corrected is theoretically (d-1)/2,
// where d is the minimum Hamming distance between any two codes in the dictionary.
// However, we use a more conservative limit (d/2)-1 to reduce the probability
// of false positives during detection. This formula is equivalent to the
// theoretical limit for even distances and stricter for odd distances.
// 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, (3-1)/2);
static const Dictionary DICT_4X4_50_DATA = Dictionary(Mat(50, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, (4-1)/2);
static const Dictionary DICT_4X4_100_DATA = Dictionary(Mat(100, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, (3-1)/2);
static const Dictionary DICT_4X4_250_DATA = Dictionary(Mat(250, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, (3-1)/2);
static const Dictionary DICT_4X4_1000_DATA = Dictionary(Mat(1000, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, (2-1)/2);
static const Dictionary DICT_5X5_50_DATA = Dictionary(Mat(50, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, (8-1)/2);
static const Dictionary DICT_5X5_100_DATA = Dictionary(Mat(100, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, (7-1)/2);
static const Dictionary DICT_5X5_250_DATA = Dictionary(Mat(250, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, (6-1)/2);
static const Dictionary DICT_5X5_1000_DATA = Dictionary(Mat(1000, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, (5-1)/2);
static const Dictionary DICT_6X6_50_DATA = Dictionary(Mat(50, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, (13-1)/2);
static const Dictionary DICT_6X6_100_DATA = Dictionary(Mat(100, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, (12-1)/2);
static const Dictionary DICT_6X6_250_DATA = Dictionary(Mat(250, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, (11-1)/2);
static const Dictionary DICT_6X6_1000_DATA = Dictionary(Mat(1000, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, (9-1)/2);
static const Dictionary DICT_7X7_50_DATA = Dictionary(Mat(50, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, (19-1)/2);
static const Dictionary DICT_7X7_100_DATA = Dictionary(Mat(100, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, (18-1)/2);
static const Dictionary DICT_7X7_250_DATA = Dictionary(Mat(250, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, (17-1)/2);
static const Dictionary DICT_7X7_1000_DATA = Dictionary(Mat(1000, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, (14-1)/2);
static const Dictionary DICT_APRILTAG_16h5_DATA = Dictionary(Mat(30, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_16h5_BYTES), 4, (5-1)/2);
static const Dictionary DICT_APRILTAG_25h9_DATA = Dictionary(Mat(35, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_25h9_BYTES), 5, (9-1)/2);
static const Dictionary DICT_APRILTAG_36h10_DATA = Dictionary(Mat(2320, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h10_BYTES), 6, (10-1)/2);
static const Dictionary DICT_APRILTAG_36h11_DATA = Dictionary(Mat(587, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h11_BYTES), 6, (11-1)/2);
static const Dictionary DICT_ARUCO_MIP_36h12_DATA = Dictionary(Mat(250, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_ARUCO_MIP_36h12_BYTES), 6, (12-1)/2);
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);
case DICT_ARUCO_MIP_36h12:
return Dictionary(DICT_ARUCO_MIP_36h12_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) {
CV_Assert(nMarkers > 0);
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.rowRange(0, min(nMarkers, baseDictionary.bytesList.rows)).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,87 @@
// 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 _copyInput2Vector(InputArrayOfArrays inp, vector<vector<Point2f> > &vec)
{
size_t i, nvecs = inp.size().area();
int inpdepth = inp.depth();
CV_Assert(inpdepth == CV_32F);
vec.resize(nvecs);
if(inp.isMatVector() || inp.kind() == _InputArray::STD_VECTOR_VECTOR)
{
for (i = 0; i < nvecs; i++)
{
Mat inp_i = inp.getMat((int)i);
int j, npoints = inp_i.checkVector(2, inpdepth, true);
CV_Assert(npoints >= 0);
const Point2f* inpptr = inp_i.ptr<Point2f>();
vector<Point2f>& vec_i = vec[i];
vec_i.resize(npoints);
for (j = 0; j < npoints; j++)
vec_i[j] = inpptr[j];
}
}
else {
CV_Error(cv::Error::StsNotImplemented,
"Only Mat vector, UMat vector, and vector<vector> OutputArrays are currently supported.");
}
}
void _copyVector2Output(vector<vector<Point2f> > &vec, OutputArrayOfArrays out, const float scale) {
size_t i, j, nvecs = vec.size();
if(out.isMatVector()) {
vector<Mat>& out_ = out.getMatVecRef();
out_.resize(nvecs);
for (i = 0; i < nvecs; i++) {
const vector<Point2f>& vec_i = vec[i];
Mat& out_i = out_[i];
Mat(vec_i).reshape(2, 1).convertTo(out_i, CV_32F, scale);
}
}
else if(out.isUMatVector()) {
vector<UMat>& out_ = out.getUMatVecRef();
out_.resize(nvecs);
for (i = 0; i < nvecs; i++) {
const vector<Point2f>& vec_i = vec[i];
UMat& out_i = out_[i];
Mat(vec_i).reshape(2, 1).convertTo(out_i, CV_32F, scale);
}
}
else if(out.kind() == _OutputArray::STD_VECTOR_VECTOR &&
out.type() == CV_32FC2){
vector<vector<Point2f>>& out_ = out.getVecVecRef<Point2f>();
out_.resize(nvecs);
for (i = 0; i < nvecs; i++) {
const vector<Point2f>& vec_i = vec[i];
size_t npoints_i = vec_i.size();
vector<Point2f>& out_i = out_[i];
out_i.resize(npoints_i);
for (j = 0; j < npoints_i; j++) {
out_i[j] = vec_i[j]*scale;
}
}
}
else {
CV_Error(cv::Error::StsNotImplemented,
"Only Mat vector, UMat vector, and vector<vector> OutputArrays are currently supported.");
}
}
void _convertToGrey(InputArray _in, Mat& _out) {
CV_Assert(_in.type() == CV_8UC1 || _in.type() == CV_8UC3 || _in.type() == CV_8UC4);
if(_in.type() != CV_8UC1)
cvtColor(_in, _out, COLOR_BGR2GRAY);
else
_out = _in.getMat();
}
}
}
@@ -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
#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 Copy the contents of InputArray to a corners vector.
*/
void _copyInput2Vector(InputArrayOfArrays inp, std::vector<std::vector<Point2f> > &vec);
/**
* @brief Convert input image to gray if it is a BGR or BGRA image
*/
void _convertToGrey(InputArray _in, Mat& _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,627 @@
// 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/geometry.hpp>
#include <opencv2/core/utils/logger.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)
{}
bool checkBoard(InputArrayOfArrays markerCorners, InputArray markerIds, InputArray charucoCorners, InputArray charucoIds) {
vector<Mat> mCorners;
markerCorners.getMatVector(mCorners);
const Mat mIds = markerIds.getMat();
const Mat chCorners = charucoCorners.getMat();
const Mat chIds = charucoIds.getMat();
const vector<int>& boardIds = board.getIds();
const vector<vector<int> > nearestMarkerIdx = board.getNearestMarkerIdx(); // only copy the vectors once
const vector<vector<int> > nearestMarkerCorners = board.getNearestMarkerCorners();
vector<Point2f> distance(nearestMarkerIdx.size(), Point2f(0.f, std::numeric_limits<float>::max()));
// distance[i].x: max distance from the i-th charuco corner to charuco corner-forming markers.
// The two charuco corner-forming markers of i-th charuco corner are defined in getNearestMarkerIdx()[i]
// distance[i].y: min distance from the charuco corner to other markers.
for (size_t i = 0ull; i < chIds.total(); i++) {
int chId = chIds.ptr<int>(0)[i];
Point2f charucoCorner(chCorners.ptr<Point2f>(0)[i]);
for (size_t j = 0ull; j < mIds.total(); j++) {
int idMaker = mIds.ptr<int>(0)[j];
// skip the check if the marker is not in the current board.
if (find(boardIds.begin(), boardIds.end(), idMaker) == boardIds.end())
continue;
Point2f centerMarker((mCorners[j].ptr<Point2f>(0)[0] + mCorners[j].ptr<Point2f>(0)[1] +
mCorners[j].ptr<Point2f>(0)[2] + mCorners[j].ptr<Point2f>(0)[3]) / 4.f);
float dist = sqrt(normL2Sqr<float>(centerMarker - charucoCorner));
// nearestMarkerIdx contains for each charuco corner, nearest marker index in ids array
const int nearestMarkerId1 = boardIds[nearestMarkerIdx[chId][0]];
const int nearestMarkerId2 = boardIds[nearestMarkerIdx[chId][1]];
if (nearestMarkerId1 == idMaker || nearestMarkerId2 == idMaker) {
int nearestCornerId = nearestMarkerId1 == idMaker ? nearestMarkerCorners[chId][0] : nearestMarkerCorners[chId][1];
Point2f nearestCorner = mCorners[j].ptr<Point2f>(0)[nearestCornerId];
// distToNearest: distance from the charuco corner to charuco corner-forming markers
float distToNearest = sqrt(normL2Sqr<float>(nearestCorner - charucoCorner));
distance[chId].x = max(distance[chId].x, distToNearest);
// check that nearestCorner is nearest point
{
Point2f mid1 = (mCorners[j].ptr<Point2f>(0)[(nearestCornerId + 1) % 4]+nearestCorner)*0.5f;
Point2f mid2 = (mCorners[j].ptr<Point2f>(0)[(nearestCornerId + 3) % 4]+nearestCorner)*0.5f;
float tmpDist = min(sqrt(normL2Sqr<float>(mid1 - charucoCorner)), sqrt(normL2Sqr<float>(mid2 - charucoCorner)));
if (tmpDist < distToNearest)
return false;
}
}
// check distance from the charuco corner to other markers
else
distance[chId].y = min(distance[chId].y, dist);
}
// if distance from the charuco corner to charuco corner-forming markers more then distance from the charuco corner to other markers,
// then a false board is found.
if (distance[chId].x > 0.f && distance[chId].y < std::numeric_limits<float>::max() && distance[chId].x > distance[chId].y)
return false;
}
return true;
}
/** 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();
const vector<vector<int> > nearestMarkerIdx = board.getNearestMarkerIdx(); // only copy the vectors once
const vector<vector<int> > nearestMarkerCorners = board.getNearestMarkerCorners();
CV_Assert(nearestMarkerIdx.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(nearestMarkerIdx[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 < nearestMarkerIdx[i].size(); j++) {
// find marker
int markerId = board.getIds()[nearestMarkerIdx[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>(nearestMarkerCorners[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, don't 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]);
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];
}
});
// 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
Board simpleBoard(board.getObjPoints(), board.getDictionary(), board.getIds());
simpleBoard.matchImagePoints(markerCorners, markerIds, objPoints, imgPoints);
if (objPoints.total() < 4ull) // need, at least, 4 corners
return;
solvePnP(objPoints, imgPoints, charucoParameters.cameraMatrix, charucoParameters.distCoeffs, approximatedRvec, approximatedTvec);
// project chessboard corners
vector<Point2f> allChessboardImgPoints;
projectPoints(board.getChessboardCorners(), approximatedRvec, approximatedTvec, charucoParameters.cameraMatrix,
charucoParameters.distCoeffs, allChessboardImgPoints);
// 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);
}
/** 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();
const vector<vector<int> > nearestMarkerIdx = board.getNearestMarkerIdx(); // only copy the vectors once
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;
}
const vector<Point3f> chessboardCorners = board.getChessboardCorners();
size_t nCharucoCorners = (size_t)chessboardCorners.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(chessboardCorners[i].x, chessboardCorners[i].y);
vector<Point2f> interpolatedPositions;
for(size_t j = 0ull; j < nearestMarkerIdx[i].size(); j++) {
int markerId = board.getIds()[nearestMarkerIdx[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;
const vector<vector<int> > nearestMarkerIdx = board.getNearestMarkerIdx(); // only copy the vectors once
// 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 < nearestMarkerIdx[currentCharucoId].size(); m++) {
int markerId = board.getIds()[nearestMarkerIdx[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();
}
void detectBoard(InputArray image, OutputArray charucoCorners, OutputArray charucoIds,
InputOutputArrayOfArrays markerCorners, InputOutputArray markerIds) {
CV_Assert((markerCorners.empty() && markerIds.empty() && !image.empty()) || (markerCorners.total() == markerIds.total()));
vector<vector<Point2f>> tmpMarkerCorners;
vector<int> tmpMarkerIds;
InputOutputArrayOfArrays _markerCorners = markerCorners.needed() ? markerCorners : _InputOutputArray(tmpMarkerCorners);
InputOutputArray _markerIds = markerIds.needed() ? markerIds : _InputOutputArray(tmpMarkerIds);
if (markerCorners.empty() && markerIds.empty()) {
vector<vector<Point2f> > rejectedMarkers;
arucoDetector.detectMarkers(image, _markerCorners, _markerIds, rejectedMarkers);
if (charucoParameters.tryRefineMarkers)
arucoDetector.refineDetectedMarkers(image, board, _markerCorners, _markerIds, rejectedMarkers);
if (_markerCorners.empty() && _markerIds.empty())
return;
}
// if camera parameters are avaible, use approximated calibration
if(!charucoParameters.cameraMatrix.empty())
interpolateCornersCharucoApproxCalib(_markerCorners, _markerIds, image, charucoCorners, charucoIds);
// else use local homography
else
interpolateCornersCharucoLocalHom(_markerCorners, _markerIds, image, charucoCorners, charucoIds);
// to return a charuco corner, its closest aruco markers should have been detected
filterCornersWithoutMinMarkers(charucoCorners, charucoIds, _markerIds, charucoCorners, charucoIds);
}
void detectBoardWithCheck(InputArray image, OutputArray charucoCorners, OutputArray charucoIds,
InputOutputArrayOfArrays markerCorners, InputOutputArray markerIds) {
vector<vector<Point2f>> tmpMarkerCorners;
vector<int> tmpMarkerIds;
InputOutputArrayOfArrays _markerCorners = markerCorners.needed() ? markerCorners : _InputOutputArray(tmpMarkerCorners);
InputOutputArray _markerIds = markerIds.needed() ? markerIds : _InputOutputArray(tmpMarkerIds);
detectBoard(image, charucoCorners, charucoIds, _markerCorners, _markerIds);
if (charucoParameters.checkMarkers && checkBoard(_markerCorners, _markerIds, charucoCorners, charucoIds) == false) {
CV_LOG_DEBUG(NULL, "ChArUco board is built incorrectly");
charucoCorners.release();
charucoIds.release();
}
}
};
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 {
charucoDetectorImpl->detectBoardWithCheck(image, charucoCorners, charucoIds, markerCorners, markerIds);
}
void CharucoDetector::detectDiamonds(InputArray image, OutputArrayOfArrays _diamondCorners, OutputArray _diamondIds,
InputOutputArrayOfArrays inMarkerCorners, InputOutputArray inMarkerIds) const {
CV_Assert(getBoard().getChessboardSize() == Size(3, 3));
CV_Assert((inMarkerCorners.empty() && inMarkerIds.empty() && !image.empty()) || (inMarkerCorners.total() == inMarkerIds.total()));
vector<vector<Point2f>> tmpMarkerCorners;
vector<int> tmpMarkerIds;
InputOutputArrayOfArrays _markerCorners = inMarkerCorners.needed() ? inMarkerCorners : _InputOutputArray(tmpMarkerCorners);
InputOutputArray _markerIds = inMarkerIds.needed() ? inMarkerIds : _InputOutputArray(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)
{
if (_diamondCorners.needed()) _diamondCorners.release();
if (_diamondIds.needed()) _diamondIds.release();
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();
unsigned int nmarkers = (unsigned int)_markerCorners.total();
std::vector<std::vector<Point2f>> markerCorners(nmarkers);
for(unsigned int i = 0; i < nmarkers; i++)
_markerCorners.getMat((int)i).copyTo(markerCorners[i]);
// 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;
for(int c = 0; c < 4; c++) {
Point2f edge = markerCorners[i][c] - markerCorners[i][(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<vector<Point2f> > currentMarker;
vector<int> currentMarkerId;
currentMarker.push_back(markerCorners[i]);
currentMarkerId.push_back(currentId);
// marker candidates (the rest of markers if they have not been assigned)
vector<vector<Point2f> > candidates;
vector<int> candidatesIdxs;
for(unsigned int k = 0; k < assigned.size(); k++) {
if(k == i) continue;
if(!assigned[k]) {
candidates.push_back(markerCorners[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;
charucoDetectorImpl->detectBoardWithCheck(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
if (_diamondIds.needed())
{
Mat(diamondIds).copyTo(_diamondIds);
}
if (_diamondCorners.needed())
{
_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];
}
}
}
}
else
{
if (_diamondCorners.needed()) _diamondCorners.release();
if (_diamondIds.needed()) _diamondIds.release();
}
}
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.total() == _charucoIds.total()) ||
_charucoIds.total() == 0);
CV_Assert(_charucoCorners.channels() == 2);
Mat charucoCorners = _charucoCorners.getMat();
if (charucoCorners.type() != CV_32SC2)
charucoCorners.convertTo(charucoCorners, CV_32SC2);
Mat charucoIds;
if (!_charucoIds.empty())
charucoIds = _charucoIds.getMat();
size_t nCorners = charucoCorners.total();
for(size_t i = 0; i < nCorners; i++) {
Point corner = charucoCorners.at<Point>((int)i);
// draw first corner mark
rectangle(_image, corner - Point(3, 3), corner + Point(3, 3), cornerColor, 1, LINE_AA);
// draw ID
if(!_charucoIds.empty()) {
int id = charucoIds.at<int>((int)i);
stringstream s;
s << "id=" << id;
putText(_image, s.str(), corner + Point(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.channels() == 2);
if (currentMarker.type() != CV_32SC2)
currentMarker.convertTo(currentMarker, CV_32SC2);
// draw marker sides
for(int j = 0; j < 4; j++) {
Point p0, p1;
p0 = currentMarker.at<Point>(j);
p1 = currentMarker.at<Point>((j + 1) % 4);
line(_image, p0, p1, borderColor, 1);
}
// draw first corner mark
rectangle(_image, currentMarker.at<Point>(0) - Point(3, 3),
currentMarker.at<Point>(0) + Point(3, 3), cornerColor, 1, LINE_AA);
// draw id composed by four numbers
if(_ids.total() != 0) {
Point cent(0, 0);
for(int p = 0; p < 4; p++)
cent += currentMarker.at<Point>(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 one or more lines are too long
+438
View File
@@ -0,0 +1,438 @@
// 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) 2020-2021 darkliang wangberlinT Certseeds
#include "precomp.hpp"
#include <opencv2/objdetect/barcode.hpp>
#include <opencv2/core/utils/filesystem.hpp>
#include "barcode_decoder/ean13_decoder.hpp"
#include "barcode_decoder/ean8_decoder.hpp"
#include "barcode_detector/bardetect.hpp"
#include "barcode_decoder/common/super_scale.hpp"
#include "barcode_decoder/common/utils.hpp"
#include "graphical_code_detector_impl.hpp"
using std::string;
using std::vector;
using std::make_shared;
using std::array;
using std::shared_ptr;
using std::dynamic_pointer_cast;
namespace cv {
namespace barcode {
//==================================================================================================
static bool checkBarInputImage(InputArray img, Mat &gray)
{
CV_Assert(!img.empty());
CV_CheckDepthEQ(img.depth(), CV_8U, "");
if (img.cols() <= 40 || img.rows() <= 40)
{
return false; // image data is not enough for providing reliable results
}
int incn = img.channels();
CV_Check(incn, incn == 1 || incn == 3 || incn == 4, "");
if (incn == 3 || incn == 4)
{
cvtColor(img, gray, COLOR_BGR2GRAY);
}
else
{
gray = img.getMat();
}
return true;
}
static void updatePointsResult(OutputArray points_, const vector<Point2f> &points)
{
if (points_.needed())
{
int N = int(points.size() / 4);
if (N > 0)
{
Mat m_p(N, 4, CV_32FC2, (void *) &points[0]);
int points_type = points_.fixedType() ? points_.type() : CV_32FC2;
m_p.reshape(2, points_.rows()).convertTo(points_, points_type); // Mat layout: N x 4 x 2cn
}
else
{
points_.release();
}
}
}
inline const array<shared_ptr<AbsDecoder>, 2> &getDecoders()
{
//indicate Decoder
static const array<shared_ptr<AbsDecoder>, 2> decoders{
shared_ptr<AbsDecoder>(new Ean13Decoder()), shared_ptr<AbsDecoder>(new Ean8Decoder())};
return decoders;
}
//==================================================================================================
class BarDecode
{
public:
void init(const vector<Mat> &bar_imgs_);
const vector<Result> &getDecodeInformation()
{ return result_info; }
bool decodeMultiplyProcess();
private:
vector<Mat> bar_imgs;
vector<Result> result_info;
};
void BarDecode::init(const vector<Mat> &bar_imgs_)
{
bar_imgs = bar_imgs_;
}
bool BarDecode::decodeMultiplyProcess()
{
static float constexpr THRESHOLD_CONF = 0.6f;
result_info.clear();
result_info.resize(bar_imgs.size());
parallel_for_(Range(0, int(bar_imgs.size())), [&](const Range &range) {
for (int i = range.start; i < range.end; i++)
{
Mat bin_bar;
Result max_res;
float max_conf = -1.f;
bool decoded = false;
for (const auto &decoder:getDecoders())
{
if (decoded)
{ break; }
for (const auto binary_type : binary_types)
{
binarize(bar_imgs[i], bin_bar, binary_type);
auto cur_res = decoder->decodeROI(bin_bar);
if (cur_res.second > max_conf)
{
max_res = cur_res.first;
max_conf = cur_res.second;
if (max_conf > THRESHOLD_CONF)
{
// code decoded
decoded = true;
break;
}
}
} //binary types
} //decoder types
result_info[i] = max_res;
}
});
return !result_info.empty();
}
//==================================================================================================
// Private class definition and implementation (pimpl)
struct BarcodeImpl : public GraphicalCodeDetector::Impl
{
public:
shared_ptr<SuperScale> sr;
bool use_nn_sr = false;
double detectorThrDownSample = 512.f;
vector<float> detectorWindowSizes = {0.01f, 0.03f, 0.06f, 0.08f};
double detectorThrGradMagnitude = 64.f;
public:
//=================
// own methods
BarcodeImpl() {}
vector<Mat> initDecode(const Mat &src, const vector<vector<Point2f>> &points) const;
bool decodeWithType(InputArray img,
InputArray points,
vector<string> &decoded_info,
vector<string> &decoded_type) const;
bool detectAndDecodeWithType(InputArray img,
vector<string> &decoded_info,
vector<string> &decoded_type,
OutputArray points_) const;
//=================
// implement interface
~BarcodeImpl() CV_OVERRIDE {}
bool detect(InputArray img, OutputArray points) const CV_OVERRIDE;
string decode(InputArray img, InputArray points, OutputArray straight_code) const CV_OVERRIDE;
string detectAndDecode(InputArray img, OutputArray points, OutputArray straight_code) const CV_OVERRIDE;
bool detectMulti(InputArray img, OutputArray points) const CV_OVERRIDE;
bool decodeMulti(InputArray img, InputArray points, vector<string>& decoded_info, OutputArrayOfArrays straight_code) const CV_OVERRIDE;
bool detectAndDecodeMulti(InputArray img, vector<string>& decoded_info, OutputArray points, OutputArrayOfArrays straight_code) const CV_OVERRIDE;
};
// return cropped and scaled bar img
vector<Mat> BarcodeImpl::initDecode(const Mat &src, const vector<vector<Point2f>> &points) const
{
vector<Mat> bar_imgs;
for (auto &corners : points)
{
Mat bar_img;
cropROI(src, bar_img, corners);
// sharpen(bar_img, bar_img);
// empirical settings
if (bar_img.cols < 320 || bar_img.cols > 640)
{
float scale = 560.0f / static_cast<float>(bar_img.cols);
sr->processImageScale(bar_img, bar_img, scale, use_nn_sr);
}
bar_imgs.emplace_back(bar_img);
}
return bar_imgs;
}
bool BarcodeImpl::decodeWithType(InputArray img,
InputArray points,
vector<string> &decoded_info,
vector<string> &decoded_type) const
{
Mat inarr;
if (!checkBarInputImage(img, inarr))
{
return false;
}
CV_Assert(points.size().width > 0);
CV_Assert((points.size().width % 4) == 0);
vector<vector<Point2f>> src_points;
Mat bar_points = points.getMat();
bar_points = bar_points.reshape(2, 1);
for (int i = 0; i < bar_points.size().width; i += 4)
{
vector<Point2f> tempMat = bar_points.colRange(i, i + 4);
if (contourArea(tempMat) > 0.0)
{
src_points.push_back(tempMat);
}
}
CV_Assert(!src_points.empty());
vector<Mat> bar_imgs = initDecode(inarr, src_points);
BarDecode bardec;
bardec.init(bar_imgs);
bardec.decodeMultiplyProcess();
const vector<Result> info = bardec.getDecodeInformation();
decoded_info.clear();
decoded_type.clear();
bool ok = false;
for (const auto &res : info)
{
if (res.isValid())
{
ok = true;
}
decoded_info.emplace_back(res.result);
decoded_type.emplace_back(res.typeString());
}
return ok;
}
bool BarcodeImpl::detectAndDecodeWithType(InputArray img,
vector<string> &decoded_info,
vector<string> &decoded_type,
OutputArray points_) const
{
Mat inarr;
if (!checkBarInputImage(img, inarr))
{
points_.release();
return false;
}
vector<Point2f> points;
bool ok = this->detect(inarr, points);
if (!ok)
{
points_.release();
return false;
}
updatePointsResult(points_, points);
decoded_info.clear();
decoded_type.clear();
ok = decodeWithType(inarr, points, decoded_info, decoded_type);
return ok;
}
bool BarcodeImpl::detect(InputArray img, OutputArray points) const
{
Mat inarr;
if (!checkBarInputImage(img, inarr))
{
points.release();
return false;
}
Detect bardet;
bardet.init(inarr, detectorThrDownSample);
bardet.localization(detectorWindowSizes, detectorThrGradMagnitude);
if (!bardet.computeTransformationPoints())
{ return false; }
vector<vector<Point2f>> pnts2f = bardet.getTransformationPoints();
vector<Point2f> trans_points;
for (auto &i : pnts2f)
{
for (const auto &j : i)
{
trans_points.push_back(j);
}
}
updatePointsResult(points, trans_points);
return true;
}
string BarcodeImpl::decode(InputArray img, InputArray points, OutputArray straight_code) const
{
CV_UNUSED(straight_code);
vector<string> decoded_info;
vector<string> decoded_type;
if (!decodeWithType(img, points, decoded_info, decoded_type))
return string();
if (decoded_info.size() < 1)
return string();
return decoded_info[0];
}
string BarcodeImpl::detectAndDecode(InputArray img, OutputArray points, OutputArray straight_code) const
{
CV_UNUSED(straight_code);
vector<string> decoded_info;
vector<string> decoded_type;
vector<Point2f> points_;
if (!detectAndDecodeWithType(img, decoded_info, decoded_type, points_))
return string();
if (points_.size() < 4 || decoded_info.size() < 1)
return string();
points_.resize(4);
updatePointsResult(points, points_);
return decoded_info[0];
}
bool BarcodeImpl::detectMulti(InputArray img, OutputArray points) const
{
return detect(img, points);
}
bool BarcodeImpl::decodeMulti(InputArray img, InputArray points, vector<string> &decoded_info, OutputArrayOfArrays straight_code) const
{
CV_UNUSED(straight_code);
vector<string> decoded_type;
return decodeWithType(img, points, decoded_info, decoded_type);
}
bool BarcodeImpl::detectAndDecodeMulti(InputArray img, vector<string> &decoded_info, OutputArray points, OutputArrayOfArrays straight_code) const
{
CV_UNUSED(straight_code);
vector<string> decoded_type;
return detectAndDecodeWithType(img, decoded_info, decoded_type, points);
}
//==================================================================================================
// Public class implementation
BarcodeDetector::BarcodeDetector()
: BarcodeDetector(std::string())
{
}
BarcodeDetector::BarcodeDetector(const string &super_resolution_model_path)
{
Ptr<BarcodeImpl> p_ = new BarcodeImpl();
p = p_;
p_->sr = make_shared<SuperScale>();
// The optional Super Resolution model is a single-file ONNX network (Caffe support
// has been removed). An empty path simply disables super resolution.
if (!super_resolution_model_path.empty())
{
CV_Assert(utils::fs::exists(super_resolution_model_path));
int res = p_->sr->init(super_resolution_model_path);
CV_Assert(res == 0);
p_->use_nn_sr = true;
}
}
BarcodeDetector::~BarcodeDetector() = default;
bool BarcodeDetector::decodeWithType(InputArray img, InputArray points, vector<string> &decoded_info, vector<string> &decoded_type) const
{
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p);
CV_Assert(p_);
return p_->decodeWithType(img, points, decoded_info, decoded_type);
}
bool BarcodeDetector::detectAndDecodeWithType(InputArray img, vector<string> &decoded_info, vector<string> &decoded_type, OutputArray points_) const
{
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p);
CV_Assert(p_);
return p_->detectAndDecodeWithType(img, decoded_info, decoded_type, points_);
}
double BarcodeDetector::getDownsamplingThreshold() const
{
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p);
CV_Assert(p_);
return p_->detectorThrDownSample;
}
BarcodeDetector& BarcodeDetector::setDownsamplingThreshold(double thresh)
{
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p);
CV_Assert(p_);
CV_Assert(thresh >= 64);
p_->detectorThrDownSample = thresh;
return *this;
}
void BarcodeDetector::getDetectorScales(CV_OUT std::vector<float>& sizes) const
{
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p);
CV_Assert(p_);
sizes = p_->detectorWindowSizes;
}
BarcodeDetector& BarcodeDetector::setDetectorScales(const std::vector<float>& sizes)
{
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p);
CV_Assert(p_);
CV_Assert(sizes.size() > 0 && sizes.size() <= 16);
for (const float &size : sizes) {
CV_Assert(size > 0 && size < 1);
}
p_->detectorWindowSizes = sizes;
return *this;
}
double BarcodeDetector::getGradientThreshold() const
{
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p);
CV_Assert(p_);
return p_->detectorThrGradMagnitude;
}
BarcodeDetector& BarcodeDetector::setGradientThreshold(double thresh)
{
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p);
CV_Assert(p_);
CV_Assert(thresh >= 0 && thresh < 1e4);
p_->detectorThrGradMagnitude = thresh;
return *this;
}
}// namespace barcode
} // namespace cv
@@ -0,0 +1,118 @@
// 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) 2020-2021 darkliang wangberlinT Certseeds
#include "../precomp.hpp"
#include "abs_decoder.hpp"
namespace cv {
namespace barcode {
void cropROI(const Mat &src, Mat &dst, const std::vector<Point2f> &rects)
{
std::vector<Point2f> vertices = rects;
int height = cvRound(norm(vertices[0] - vertices[1]));
int width = cvRound(norm(vertices[1] - vertices[2]));
if (height > width)
{
std::swap(height, width);
Point2f v0 = vertices[0];
vertices.erase(vertices.begin());
vertices.push_back(v0);
}
std::vector<Point2f> dst_vertices{
Point2f(0, (float) (height - 1)), Point2f(0, 0), Point2f((float) (width - 1), 0),
Point2f((float) (width - 1), (float) (height - 1))};
dst.create(Size(width, height), CV_8UC1);
Mat M = getPerspectiveTransform(vertices, dst_vertices);
warpPerspective(src, dst, M, dst.size(), cv::INTER_LINEAR, BORDER_CONSTANT, Scalar(255));
}
void fillCounter(const std::vector<uchar> &row, uint start, Counter &counter)
{
size_t counter_length = counter.pattern.size();
std::fill(counter.pattern.begin(), counter.pattern.end(), 0);
counter.sum = 0;
size_t end = row.size();
uchar color = row[start];
uint counterPosition = 0;
while (start < end)
{
if (row[start] == color)
{ // that is, exactly one is true
counter.pattern[counterPosition]++;
counter.sum++;
}
else
{
counterPosition++;
if (counterPosition == counter_length)
{
break;
}
else
{
counter.pattern[counterPosition] = 1;
counter.sum++;
color = 255 - color;
}
}
++start;
}
}
static inline uint
patternMatchVariance(const Counter &counter, const std::vector<int> &pattern, uint maxIndividualVariance)
{
size_t numCounters = counter.pattern.size();
int total = static_cast<int>(counter.sum);
int patternLength = std::accumulate(pattern.cbegin(), pattern.cend(), 0);
if (total < patternLength)
{
// If we don't even have one pixel per unit of bar width, assume this is too small
// to reliably match, so fail:
// and use constexpr functions
return WHITE;// max
}
// We're going to fake floating-point math in integers. We just need to use more bits.
// Scale up patternLength so that intermediate values below like scaledCounter will have
// more "significant digits"
int unitBarWidth = (total << INTEGER_MATH_SHIFT) / patternLength;
maxIndividualVariance = (maxIndividualVariance * unitBarWidth) >> INTEGER_MATH_SHIFT;
uint totalVariance = 0;
for (uint x = 0; x < numCounters; x++)
{
int cnt = counter.pattern[x] << INTEGER_MATH_SHIFT;
int scaledPattern = pattern[x] * unitBarWidth;
uint variance = std::abs(cnt - scaledPattern);
if (variance > maxIndividualVariance)
{
return WHITE;
}
totalVariance += variance;
}
return totalVariance / total;
}
/**
* Determines how closely a set of observed counts of runs of black/white values matches a given
* target pattern. This is reported as the ratio of the total variance from the expected pattern
* proportions across all pattern elements, to the length of the pattern.
*
* @param counters observed counters
* @param pattern expected pattern
* @param maxIndividualVariance The most any counter can differ before we give up
* @return ratio of total variance between counters and pattern compared to total pattern size,
* where the ratio has been multiplied by 256. So, 0 means no variance (perfect match); 256 means
* the total variance between counters and patterns equals the pattern length, higher values mean
* even more variance
*/
uint patternMatch(const Counter &counters, const std::vector<int> &pattern, uint maxIndividual)
{
CV_Assert(counters.pattern.size() == pattern.size());
return patternMatchVariance(counters, pattern, maxIndividual);
}
}
}
@@ -0,0 +1,99 @@
// 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) 2020-2021 darkliang wangberlinT Certseeds
#ifndef OPENCV_BARCODE_ABS_DECODER_HPP
#define OPENCV_BARCODE_ABS_DECODER_HPP
#include "opencv2/objdetect/barcode.hpp"
namespace cv {
namespace barcode {
using std::string;
using std::vector;
constexpr static uchar BLACK = std::numeric_limits<uchar>::min();
// WHITE elemental area is 0xff
constexpr static uchar WHITE = std::numeric_limits<uchar>::max();
struct Result
{
enum BarcodeType
{
BARCODE_NONE,
BARCODE_EAN_8,
BARCODE_EAN_13,
BARCODE_UPC_A,
BARCODE_UPC_E,
BARCODE_UPC_EAN_EXTENSION
};
std::string result;
BarcodeType format = Result::BARCODE_NONE;
Result() = default;
Result(const std::string &_result, BarcodeType _format)
{
result = _result;
format = _format;
}
string typeString() const
{
switch (format)
{
case Result::BARCODE_EAN_8: return "EAN_8";
case Result::BARCODE_EAN_13: return "EAN_13";
case Result::BARCODE_UPC_E: return "UPC_E";
case Result::BARCODE_UPC_A: return "UPC_A";
case Result::BARCODE_UPC_EAN_EXTENSION: return "UPC_EAN_EXTENSION";
default: return string();
}
}
bool isValid() const
{
return format != BARCODE_NONE;
}
};
struct Counter
{
std::vector<int> pattern;
uint sum;
explicit Counter(const vector<int> &_pattern)
{
pattern = _pattern;
sum = 0;
}
};
class AbsDecoder
{
public:
virtual std::pair<Result, float> decodeROI(const Mat &bar_img) const = 0;
virtual ~AbsDecoder() = default;
protected:
virtual Result decode(const vector<uchar> &data) const = 0;
virtual bool isValid(const string &result) const = 0;
size_t bits_num{};
size_t digit_number{};
};
void cropROI(const Mat &_src, Mat &_dst, const std::vector<Point2f> &rect);
void fillCounter(const std::vector<uchar> &row, uint start, Counter &counter);
constexpr static uint INTEGER_MATH_SHIFT = 8;
constexpr static uint PATTERN_MATCH_RESULT_SCALE_FACTOR = 1 << INTEGER_MATH_SHIFT;
uint patternMatch(const Counter &counters, const std::vector<int> &pattern, uint maxIndividual);
}
} // namespace cv
#endif // OPENCV_BARCODE_ABS_DECODER_HPP
@@ -0,0 +1,195 @@
// 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.
// Modified from ZXing. Copyright ZXing authors.
// Licensed under the Apache License, Version 2.0 (the "License").
#include "../../precomp.hpp"
#include "hybrid_binarizer.hpp"
namespace cv {
namespace barcode {
#define CLAMP(x, x1, x2) x < (x1) ? (x1) : ((x) > (x2) ? (x2) : (x))
// This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels.
// So this is the smallest dimension in each axis we can accept.
constexpr static int BLOCK_SIZE_POWER = 3;
constexpr static int BLOCK_SIZE = 1 << BLOCK_SIZE_POWER; // ...0100...00
constexpr static int BLOCK_SIZE_MASK = BLOCK_SIZE - 1; // ...0011...11
constexpr static int MINIMUM_DIMENSION = BLOCK_SIZE * 5;
constexpr static int MIN_DYNAMIC_RANGE = 24;
void
calculateThresholdForBlock(const std::vector<uchar> &luminances, int sub_width, int sub_height, int width, int height,
const Mat &black_points, Mat &dst)
{
int maxYOffset = height - BLOCK_SIZE;
int maxXOffset = width - BLOCK_SIZE;
for (int y = 0; y < sub_height; y++)
{
int yoffset = y << BLOCK_SIZE_POWER;
if (yoffset > maxYOffset)
{
yoffset = maxYOffset;
}
int top = CLAMP(y, 2, sub_height - 3);
for (int x = 0; x < sub_width; x++)
{
int xoffset = x << BLOCK_SIZE_POWER;
if (xoffset > maxXOffset)
{
xoffset = maxXOffset;
}
int left = CLAMP(x, 2, sub_width - 3);
int sum = 0;
const auto *black_row = black_points.ptr<uchar>(top - 2);
for (int z = 0; z <= 4; z++)
{
sum += black_row[left - 2] + black_row[left - 1] + black_row[left] + black_row[left + 1] +
black_row[left + 2];
black_row += black_points.cols;
}
int average = sum / 25;
int temp_y = 0;
auto *ptr = dst.ptr<uchar>(yoffset, xoffset);
for (int offset = yoffset * width + xoffset; temp_y < 8; offset += width)
{
for (int temp_x = 0; temp_x < 8; ++temp_x)
{
*(ptr + temp_x) = (luminances[offset + temp_x] & 255) <= average ? 0 : 255;
}
++temp_y;
ptr += width;
}
}
}
}
Mat calculateBlackPoints(std::vector<uchar> luminances, int sub_width, int sub_height, int width, int height)
{
int maxYOffset = height - BLOCK_SIZE;
int maxXOffset = width - BLOCK_SIZE;
Mat black_points(Size(sub_width, sub_height), CV_8UC1);
for (int y = 0; y < sub_height; y++)
{
int yoffset = y << BLOCK_SIZE_POWER;
if (yoffset > maxYOffset)
{
yoffset = maxYOffset;
}
for (int x = 0; x < sub_width; x++)
{
int xoffset = x << BLOCK_SIZE_POWER;
if (xoffset > maxXOffset)
{
xoffset = maxXOffset;
}
int sum = 0;
int min = 0xFF;
int max = 0;
for (int yy = 0, offset = yoffset * width + xoffset; yy < BLOCK_SIZE; yy++, offset += width)
{
for (int xx = 0; xx < BLOCK_SIZE; xx++)
{
int pixel = luminances[offset + xx] & 0xFF;
sum += pixel;
// still looking for good contrast
if (pixel < min)
{
min = pixel;
}
if (pixel > max)
{
max = pixel;
}
}
// short-circuit min/max tests once dynamic range is met
if (max - min > MIN_DYNAMIC_RANGE)
{
// finish the rest of the rows quickly
for (yy++, offset += width; yy < BLOCK_SIZE; yy++, offset += width)
{
for (int xx = 0; xx < BLOCK_SIZE; xx++)
{
sum += luminances[offset + xx] & 0xFF;
}
}
}
}
// The default estimate is the average of the values in the block.
int average = sum >> (BLOCK_SIZE_POWER * 2);
if (max - min <= MIN_DYNAMIC_RANGE)
{
// If variation within the block is low, assume this is a block with only light or only
// dark pixels. In that case we do not want to use the average, as it would divide this
// low contrast area into black and white pixels, essentially creating data out of noise.
//
// The default assumption is that the block is light/background. Since no estimate for
// the level of dark pixels exists locally, use half the min for the block.
average = min / 2;
if (y > 0 && x > 0)
{
// Correct the "white background" assumption for blocks that have neighbors by comparing
// the pixels in this block to the previously calculated black points. This is based on
// the fact that dark barcode symbology is always surrounded by some amount of light
// background for which reasonable black point estimates were made. The bp estimated at
// the boundaries is used for the interior.
// The (min < bp) is arbitrary but works better than other heuristics that were tried.
int averageNeighborBlackPoint =
(black_points.at<uchar>(y - 1, x) + (2 * black_points.at<uchar>(y, x - 1)) +
black_points.at<uchar>(y - 1, x - 1)) / 4;
if (min < averageNeighborBlackPoint)
{
average = averageNeighborBlackPoint;
}
}
}
black_points.at<uchar>(y, x) = (uchar) average;
}
}
return black_points;
}
void hybridBinarization(const Mat &src, Mat &dst)
{
int width = src.cols;
int height = src.rows;
if (width >= MINIMUM_DIMENSION && height >= MINIMUM_DIMENSION)
{
std::vector<uchar> luminances(src.begin<uchar>(), src.end<uchar>());
int sub_width = width >> BLOCK_SIZE_POWER;
if ((width & BLOCK_SIZE_MASK) != 0)
{
sub_width++;
}
int sub_height = height >> BLOCK_SIZE_POWER;
if ((height & BLOCK_SIZE_MASK) != 0)
{
sub_height++;
}
Mat black_points = calculateBlackPoints(luminances, sub_width, sub_height, width, height);
dst.create(src.size(), src.type());
calculateThresholdForBlock(luminances, sub_width, sub_height, width, height, black_points, dst);
}
else
{
threshold(src, dst, 155, 255, THRESH_OTSU + THRESH_BINARY);
}
}
}
}
@@ -0,0 +1,22 @@
// 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.
// Modified from ZXing. Copyright ZXing authors.
// Licensed under the Apache License, Version 2.0 (the "License").
#ifndef OPENCV_BARCODE_HYBRID_BINARIZER_HPP
#define OPENCV_BARCODE_HYBRID_BINARIZER_HPP
namespace cv {
namespace barcode {
void hybridBinarization(const Mat &src, Mat &dst);
void
calculateThresholdForBlock(const std::vector<uchar> &luminances, int sub_width, int sub_height, int width, int height,
const Mat &black_points, Mat &dst);
Mat calculateBlackPoints(std::vector<uchar> luminances, int sub_width, int sub_height, int width, int height);
}
}
#endif // OPENCV_BARCODE_HYBRID_BINARIZER_HPP
@@ -0,0 +1,99 @@
// 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.
//
// Tencent is pleased to support the open source community by making WeChat QRCode available.
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
// Modified by darkliang wangberlinT
#include "../../precomp.hpp"
#include "super_scale.hpp"
#include "opencv2/core.hpp"
#include "opencv2/core/utils/logger.hpp"
namespace cv {
namespace barcode {
#ifdef HAVE_OPENCV_DNN
constexpr static float MAX_SCALE = 4.0f;
int SuperScale::init(const std::string &model_path)
{
// Super Resolution is loaded from a single-file ONNX model (e.g. dnn/wechat_2021-01/sr.onnx).
srnet_ = dnn::readNetFromONNX(model_path);
net_loaded_ = true;
return 0;
}
void SuperScale::processImageScale(const Mat &src, Mat &dst, float scale, const bool &use_sr, int sr_max_size)
{
scale = min(scale, MAX_SCALE);
if (scale > .0 && scale < 1.0)
{ // down sample
resize(src, dst, Size(), scale, scale, INTER_AREA);
}
else if (scale > 1.5 && scale < 2.0)
{
resize(src, dst, Size(), scale, scale, INTER_CUBIC);
}
else if (scale >= 2.0)
{
int width = src.cols;
int height = src.rows;
if (use_sr && (int) sqrt(width * height * 1.0) < sr_max_size && net_loaded_)
{
superResolutionScale(src, dst);
if (scale > 2.0)
{
processImageScale(dst, dst, scale / 2.0f, use_sr);
}
}
else
{ resize(src, dst, Size(), scale, scale, INTER_CUBIC); }
}
}
int SuperScale::superResolutionScale(const Mat &src, Mat &dst)
{
Mat blob;
dnn::blobFromImage(src, blob, 1.0 / 255, Size(src.cols, src.rows), {0.0f}, false, false);
srnet_.setInput(blob);
auto prob = srnet_.forward();
dst = Mat(prob.size[2], prob.size[3], CV_8UC1);
for (int row = 0; row < prob.size[2]; row++)
{
const float *prob_score = prob.ptr<float>(0, 0, row);
auto *dst_row = dst.ptr<uchar>(row);
for (int col = 0; col < prob.size[3]; col++)
{
dst_row[col] = saturate_cast<uchar>(prob_score[col] * 255.0f);
}
}
return 0;
}
#else // HAVE_OPENCV_DNN
int SuperScale::init(const std::string &model_path)
{
CV_UNUSED(model_path);
return 0;
}
void SuperScale::processImageScale(const Mat &src, Mat &dst, float scale, const bool & isEnabled, int sr_max_size)
{
CV_UNUSED(sr_max_size);
if (isEnabled)
{
CV_LOG_WARNING(NULL, "objdetect/barcode: SuperScaling disabled - OpenCV has been built without DNN support");
}
resize(src, dst, Size(), scale, scale, INTER_CUBIC);
}
#endif // HAVE_OPENCV_DNN
} // namespace barcode
} // namespace cv
@@ -0,0 +1,41 @@
/// 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.
//
// Tencent is pleased to support the open source community by making WeChat QRCode available.
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
#ifndef OPENCV_BARCODE_SUPER_SCALE_HPP
#define OPENCV_BARCODE_SUPER_SCALE_HPP
#ifdef HAVE_OPENCV_DNN
# include "opencv2/dnn.hpp"
#endif
namespace cv {
namespace barcode {
class SuperScale
{
public:
SuperScale() = default;
~SuperScale() = default;
int init(const std::string &model_path);
void processImageScale(const Mat &src, Mat &dst, float scale, const bool &use_sr, int sr_max_size = 160);
#ifdef HAVE_OPENCV_DNN
private:
dnn::Net srnet_;
bool net_loaded_ = false;
int superResolutionScale(const cv::Mat &src, cv::Mat &dst);
#endif
};
} // namespace barcode
} // namespace cv
#endif // OPENCV_BARCODE_SUPER_SCALE_HPP
@@ -0,0 +1,36 @@
// 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) 2020-2021 darkliang wangberlinT Certseeds
#include "../../precomp.hpp"
#include "utils.hpp"
#include "hybrid_binarizer.hpp"
namespace cv {
namespace barcode {
void sharpen(const Mat &src, const Mat &dst)
{
Mat blur;
GaussianBlur(src, blur, Size(0, 0), 25);
addWeighted(src, 2, blur, -1, -20, dst);
}
void binarize(const Mat &src, Mat &dst, BinaryType mode)
{
switch (mode)
{
case OTSU:
threshold(src, dst, 155, 255, THRESH_OTSU + THRESH_BINARY);
break;
case HYBRID:
hybridBinarization(src, dst);
break;
default:
CV_Error(Error::StsNotImplemented, "This binary type is not yet implemented");
}
}
}
}
@@ -0,0 +1,26 @@
// 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) 2020-2021 darkliang wangberlinT Certseeds
#ifndef OPENCV_BARCODE_UTILS_HPP
#define OPENCV_BARCODE_UTILS_HPP
namespace cv {
namespace barcode {
enum BinaryType
{
OTSU = 0, HYBRID = 1
};
static constexpr BinaryType binary_types[] = {OTSU, HYBRID};
void sharpen(const Mat &src, const Mat &dst);
void binarize(const Mat &src, Mat &dst, BinaryType mode);
}
}
#endif // OPENCV_BARCODE_UTILS_HPP
@@ -0,0 +1,92 @@
// 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) 2020-2021 darkliang wangberlinT Certseeds
#include "../precomp.hpp"
#include "ean13_decoder.hpp"
// three digit decode method from https://baike.baidu.com/item/EAN-13
namespace cv {
namespace barcode {
static constexpr size_t EAN13BITS_NUM = 95;
static constexpr size_t EAN13DIGIT_NUM = 13;
// default thought that mat is a matrix after binary-transfer.
/**
* decode EAN-13
* @prama: data: the input array,
* @prama: start: the index of start order, begin at 0, max-value is data.size()-1
* it scan begin at the data[start]
*/
Result Ean13Decoder::decode(const vector<uchar> &data) const
{
string result;
char decode_result[EAN13DIGIT_NUM + 1]{'\0'};
if (data.size() < EAN13BITS_NUM)
{
return Result("Wrong Size", Result::BARCODE_NONE);
}
pair<uint, uint> pattern;
if (!findStartGuardPatterns(data, pattern))
{
return Result("Begin Pattern Not Found", Result::BARCODE_NONE);
}
uint start = pattern.second;
Counter counter(vector<int>{0, 0, 0, 0});
size_t end = data.size();
int first_char_bit = 0;
// [1,6] are left part of EAN, [7,12] are right part, index 0 is calculated by left part
for (int i = 1; i < 7 && start < end; ++i)
{
int bestMatch = decodeDigit(data, counter, start, get_AB_Patterns());
if (bestMatch == -1)
{
return Result("Decode Error", Result::BARCODE_NONE);
}
decode_result[i] = static_cast<char>('0' + bestMatch % 10);
start = counter.sum + start;
first_char_bit += (bestMatch >= 10) << i;
}
decode_result[0] = static_cast<char>(FIRST_CHAR_ARRAY()[first_char_bit >> 2] + '0');
// why there need >> 2?
// first, the i in for-cycle is begin in 1
// second, the first i = 1 is always
Counter middle_counter(vector<int>(MIDDLE_PATTERN().size()));
if (!findGuardPatterns(data, start, true, MIDDLE_PATTERN(), middle_counter, pattern))
{
return Result("Middle Pattern Not Found", Result::BARCODE_NONE);
}
start = pattern.second;
for (int i = 0; i < 6 && start < end; ++i)
{
int bestMatch = decodeDigit(data, counter, start, get_A_or_C_Patterns());
if (bestMatch == -1)
{
return Result("Decode Error", Result::BARCODE_NONE);
}
decode_result[i + 7] = static_cast<char>('0' + bestMatch);
start = counter.sum + start;
}
Counter end_counter(vector<int>(BEGIN_PATTERN().size()));
if (!findGuardPatterns(data, start, false, BEGIN_PATTERN(), end_counter, pattern))
{
return Result("End Pattern Not Found", Result::BARCODE_NONE);
}
result = string(decode_result);
if (!isValid(result))
{
return Result("Wrong: " + result.append(string(EAN13DIGIT_NUM - result.size(), ' ')), Result::BARCODE_NONE);
}
return Result(result, Result::BARCODE_EAN_13);
}
Ean13Decoder::Ean13Decoder()
{
this->bits_num = EAN13BITS_NUM;
this->digit_number = EAN13DIGIT_NUM;
}
}
}
@@ -0,0 +1,31 @@
// 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) 2020-2021 darkliang wangberlinT Certseeds
#ifndef OPENCV_BARCODE_EAN13_DECODER_HPP
#define OPENCV_BARCODE_EAN13_DECODER_HPP
#include "upcean_decoder.hpp"
namespace cv {
namespace barcode {
//extern struct EncodePair;
using std::string;
using std::vector;
using std::pair;
class Ean13Decoder : public UPCEANDecoder
{
public:
Ean13Decoder();
~Ean13Decoder() override = default;
protected:
Result decode(const vector<uchar> &data) const override;
};
}
} // namespace cv
#endif // OPENCV_BARCODE_EAN13_DECODER_HPP
@@ -0,0 +1,79 @@
// 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) 2020-2021 darkliang wangberlinT Certseeds
#include "../precomp.hpp"
#include "ean8_decoder.hpp"
namespace cv {
namespace barcode {
static constexpr size_t EAN8BITS_NUM = 70;
static constexpr size_t EAN8DIGIT_NUM = 8;
Result Ean8Decoder::decode(const vector<uchar> &data) const
{
std::string result;
char decode_result[EAN8DIGIT_NUM + 1]{'\0'};
if (data.size() < EAN8BITS_NUM)
{
return Result("Wrong Size", Result::BARCODE_NONE);
}
pair<uint, uint> pattern;
if (!findStartGuardPatterns(data, pattern))
{
return Result("Begin Pattern Not Found", Result::BARCODE_NONE);
}
uint start = pattern.second;
Counter counter(vector<int>{0, 0, 0, 0});
size_t end = data.size();
for (int i = 0; i < 4 && start < end; ++i)
{
int bestMatch = decodeDigit(data, counter, start, get_A_or_C_Patterns());
if (bestMatch == -1)
{
return Result("Decode Error", Result::BARCODE_NONE);
}
decode_result[i] = static_cast<char>('0' + bestMatch % 10);
start = counter.sum + start;
}
Counter middle_counter(vector<int>(MIDDLE_PATTERN().size()));
if (!findGuardPatterns(data, start, true, MIDDLE_PATTERN(), middle_counter, pattern))
{
return Result("Middle Pattern Not Found", Result::BARCODE_NONE);
}
start = pattern.second;
for (int i = 0; i < 4 && start < end; ++i)
{
int bestMatch = decodeDigit(data, counter, start, get_A_or_C_Patterns());
if (bestMatch == -1)
{
return Result("Decode Error", Result::BARCODE_NONE);
}
decode_result[i + 4] = static_cast<char>('0' + bestMatch);
start = counter.sum + start;
}
Counter end_counter(vector<int>(BEGIN_PATTERN().size()));
if (!findGuardPatterns(data, start, false, BEGIN_PATTERN(), end_counter, pattern))
{
return Result("End Pattern Not Found", Result::BARCODE_NONE);
}
result = string(decode_result);
if (!isValid(result))
{
return Result("Wrong: " + result.append(string(EAN8DIGIT_NUM - result.size(), ' ')), Result::BARCODE_NONE);
}
return Result(result, Result::BARCODE_EAN_8);
}
Ean8Decoder::Ean8Decoder()
{
this->digit_number = EAN8DIGIT_NUM;
this->bits_num = EAN8BITS_NUM;
}
}
}
@@ -0,0 +1,32 @@
// 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) 2020-2021 darkliang wangberlinT Certseeds
#ifndef OPENCV_BARCODE_EAN8_DECODER_HPP
#define OPENCV_BARCODE_EAN8_DECODER_HPP
#include "upcean_decoder.hpp"
namespace cv {
namespace barcode {
using std::string;
using std::vector;
using std::pair;
class Ean8Decoder : public UPCEANDecoder
{
public:
Ean8Decoder();
~Ean8Decoder() override = default;
protected:
Result decode(const vector<uchar> &data) const override;
};
}
}
#endif // OPENCV_BARCODE_EAN8_DECODER_HPP
@@ -0,0 +1,290 @@
// 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) 2020-2021 darkliang wangberlinT Certseeds
#include "../precomp.hpp"
#include "upcean_decoder.hpp"
#include <map>
namespace cv {
namespace barcode {
static constexpr int DIVIDE_PART = 15;
static constexpr int BIAS_PART = 2;
#if 0
void UPCEANDecoder::drawDebugLine(Mat &debug_img, const Point2i &begin, const Point2i &end) const
{
Result result;
std::vector<uchar> middle;
LineIterator line = LineIterator(debug_img, begin, end);
middle.reserve(line.count);
for (int cnt = 0; cnt < line.count; cnt++, line++)
{
middle.push_back(debug_img.at<uchar>(line.pos()));
}
std::pair<int, int> start_range;
if (findStartGuardPatterns(middle, start_range))
{
circle(debug_img, Point2i(begin.x + start_range.second, begin.y), 2, Scalar(0), 2);
}
result = this->decode(middle);
if (result.format == Result::BARCODE_NONE)
{
result = this->decode(std::vector<uchar>(middle.crbegin(), middle.crend()));
}
if (result.format == Result::BARCODE_NONE)
{
cv::line(debug_img, begin, end, Scalar(0), 2);
cv::putText(debug_img, result.result, begin, cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar(0, 0, 255), 1);
}
}
#endif
bool UPCEANDecoder::findGuardPatterns(const std::vector<uchar> &row, uint rowOffset, uchar whiteFirst,
const std::vector<int> &pattern, Counter &counter, std::pair<uint, uint> &result)
{
size_t patternLength = pattern.size();
size_t width = row.size();
uchar color = whiteFirst ? WHITE : BLACK;
rowOffset = (int) (std::find(row.cbegin() + rowOffset, row.cend(), color) - row.cbegin());
uint counterPosition = 0;
uint patternStart = rowOffset;
for (uint x = rowOffset; x < width; x++)
{
if (row[x] == color)
{
counter.pattern[counterPosition]++;
counter.sum++;
}
else
{
if (counterPosition == patternLength - 1)
{
if (patternMatch(counter, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE)
{
result.first = patternStart;
result.second = x;
return true;
}
patternStart += counter.pattern[0] + counter.pattern[1];
counter.sum -= counter.pattern[0] + counter.pattern[1];
std::copy(counter.pattern.begin() + 2, counter.pattern.end(), counter.pattern.begin());
counter.pattern[patternLength - 2] = 0;
counter.pattern[patternLength - 1] = 0;
counterPosition--;
}
else
{
counterPosition++;
}
counter.pattern[counterPosition] = 1;
counter.sum++;
color = (std::numeric_limits<uchar>::max() - color);
}
}
return false;
}
bool UPCEANDecoder::findStartGuardPatterns(const std::vector<uchar> &row, std::pair<uint, uint> &start_range)
{
bool is_find = false;
int next_start = 0;
while (!is_find)
{
Counter guard_counters(std::vector<int>{0, 0, 0});
if (!findGuardPatterns(row, next_start, BLACK, BEGIN_PATTERN(), guard_counters, start_range))
{
return false;
}
int start = static_cast<int>(start_range.first);
next_start = static_cast<int>(start_range.second);
int quiet_start = max(start - (next_start - start), 0);
is_find = (quiet_start != start) &&
(std::find(std::begin(row) + quiet_start, std::begin(row) + start, BLACK) == std::begin(row) + start);
}
return true;
}
int UPCEANDecoder::decodeDigit(const std::vector<uchar> &row, Counter &counters, uint rowOffset,
const std::vector<std::vector<int>> &patterns)
{
fillCounter(row, rowOffset, counters);
int bestMatch = -1;
uint bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept
int i = 0;
for (const auto &pattern : patterns)
{
uint variance = patternMatch(counters, pattern, MAX_INDIVIDUAL_VARIANCE);
if (variance < bestVariance)
{
bestVariance = variance;
bestMatch = i;
}
i++;
}
return std::max(-1, bestMatch);
// -1 is Mismatch or means error.
}
/*Input a ROI mat return result */
std::pair<Result, float> UPCEANDecoder::decodeROI(const Mat &bar_img) const
{
if ((size_t) bar_img.cols < this->bits_num)
{
return std::make_pair(Result{string(), Result::BARCODE_NONE}, 0.0F);
}
std::map<std::string, int> result_vote;
std::map<Result::BarcodeType, int> format_vote;
int vote_cnt = 0;
int total_vote = 0;
std::string max_result;
Result::BarcodeType max_type = Result::BARCODE_NONE;
const int step = bar_img.rows / (DIVIDE_PART + BIAS_PART);
Result result;
int row_num;
for (int i = 0; i < DIVIDE_PART; ++i)
{
row_num = (i + BIAS_PART / 2) * step;
if (row_num < 0 || row_num > bar_img.rows)
{
continue;
}
const auto *ptr = bar_img.ptr<uchar>(row_num);
vector<uchar> line(ptr, ptr + bar_img.cols);
result = decodeLine(line);
if (result.format != Result::BARCODE_NONE)
{
total_vote++;
result_vote[result.result] += 1;
if (result_vote[result.result] > vote_cnt)
{
vote_cnt = result_vote[result.result];
max_result = result.result;
max_type = result.format;
}
}
}
if (total_vote == 0 || (vote_cnt << 2) < total_vote)
{
return std::make_pair(Result(string(), Result::BARCODE_NONE), 0.0f);
}
float confidence = (float) vote_cnt / (float) DIVIDE_PART;
//Check if it is UPC-A format
if (max_type == Result::BARCODE_EAN_13 && max_result[0] == '0')
{
max_result = max_result.substr(1, 12); //UPC-A length 12
max_type = Result::BARCODE_UPC_A;
}
return std::make_pair(Result(max_result, max_type), confidence);
}
Result UPCEANDecoder::decodeLine(const vector<uchar> &line) const
{
Result result = this->decode(line);
if (result.format == Result::BARCODE_NONE)
{
result = this->decode(std::vector<uchar>(line.crbegin(), line.crend()));
}
return result;
}
bool UPCEANDecoder::isValid(const string &result) const
{
if (result.size() != digit_number)
{
return false;
}
int sum = 0;
for (int index = (int) result.size() - 2, i = 1; index >= 0; index--, i++)
{
int temp = result[index] - '0';
sum += (temp + ((i & 1) != 0 ? temp << 1 : 0));
}
return (result.back() - '0') == ((10 - (sum % 10)) % 10);
}
// right for A
const std::vector<std::vector<int>> &get_A_or_C_Patterns()
{
static const std::vector<std::vector<int>> A_or_C_Patterns{{3, 2, 1, 1}, // 0
{2, 2, 2, 1}, // 1
{2, 1, 2, 2}, // 2
{1, 4, 1, 1}, // 3
{1, 1, 3, 2}, // 4
{1, 2, 3, 1}, // 5
{1, 1, 1, 4}, // 6
{1, 3, 1, 2}, // 7
{1, 2, 1, 3}, // 8
{3, 1, 1, 2} // 9
};
return A_or_C_Patterns;
}
const std::vector<std::vector<int>> &get_AB_Patterns()
{
static const std::vector<std::vector<int>> AB_Patterns = [] {
constexpr uint offset = 10;
auto AB_Patterns_inited = std::vector<std::vector<int>>(offset << 1, std::vector<int>(PATTERN_LENGTH, 0));
std::copy(get_A_or_C_Patterns().cbegin(), get_A_or_C_Patterns().cend(), AB_Patterns_inited.begin());
//AB pattern is
for (uint i = 0; i < offset; ++i)
{
for (uint j = 0; j < PATTERN_LENGTH; ++j)
{
AB_Patterns_inited[i + offset][j] = AB_Patterns_inited[i][PATTERN_LENGTH - j - 1];
}
}
return AB_Patterns_inited;
}();
return AB_Patterns;
}
const std::vector<int> &BEGIN_PATTERN()
{
// it just need it's 1:1:1(black:white:black)
static const std::vector<int> BEGIN_PATTERN_(3, 1);
return BEGIN_PATTERN_;
}
const std::vector<int> &MIDDLE_PATTERN()
{
// it just need it's 1:1:1:1:1(white:black:white:black:white)
static const std::vector<int> MIDDLE_PATTERN_(5, 1);
return MIDDLE_PATTERN_;
}
const std::array<char, 32> &FIRST_CHAR_ARRAY()
{
// use array to simulation a Hashmap,
// because the data's size is small,
// use a hashmap or brute-force search 10 times both can not accept
static const std::array<char, 32> pattern{
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x06', '\x00', '\x00', '\x00', '\x09', '\x00',
'\x08', '\x03', '\x00', '\x00', '\x00', '\x00', '\x05', '\x00', '\x07', '\x02', '\x00', '\x00', '\x04',
'\x01', '\x00', '\x00', '\x00', '\x00', '\x00'};
// length is 32 to ensure the security
// 0x00000 -> 0 -> 0
// 0x11010 -> 26 -> 1
// 0x10110 -> 22 -> 2
// 0x01110 -> 14 -> 3
// 0x11001 -> 25 -> 4
// 0x10011 -> 19 -> 5
// 0x00111 -> 7 -> 6
// 0x10101 -> 21 -> 7
// 0x01101 -> 13 -> 8
// 0x01011 -> 11 -> 9
// delete the 1-13's 2 number's bit,
// it always be A which do not need to count.
return pattern;
}
}
} // namespace cv
@@ -0,0 +1,67 @@
// 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) 2020-2021 darkliang wangberlinT Certseeds
#ifndef OPENCV_BARCODE_UPCEAN_DECODER_HPP
#define OPENCV_BARCODE_UPCEAN_DECODER_HPP
#include "abs_decoder.hpp"
/**
* upcean_decoder the abstract basic class for decode formats,
* it will have ean13/8,upc_a,upc_e , etc.. class extend this class
*/
namespace cv {
namespace barcode {
using std::string;
using std::vector;
class UPCEANDecoder : public AbsDecoder
{
public:
~UPCEANDecoder() override = default;
std::pair<Result, float> decodeROI(const Mat &bar_img) const override;
protected:
static int decodeDigit(const std::vector<uchar> &row, Counter &counters, uint rowOffset,
const std::vector<std::vector<int>> &patterns);
static bool
findGuardPatterns(const std::vector<uchar> &row, uint rowOffset, uchar whiteFirst, const std::vector<int> &pattern,
Counter &counter, std::pair<uint, uint> &result);
static bool findStartGuardPatterns(const std::vector<uchar> &row, std::pair<uint, uint> &start_range);
Result decodeLine(const vector<uchar> &line) const;
Result decode(const vector<uchar> &bar) const override = 0;
bool isValid(const string &result) const override;
private:
#if 0
void drawDebugLine(Mat &debug_img, const Point2i &begin, const Point2i &end) const;
#endif
};
const std::vector<std::vector<int>> &get_A_or_C_Patterns();
const std::vector<std::vector<int>> &get_AB_Patterns();
const std::vector<int> &BEGIN_PATTERN();
const std::vector<int> &MIDDLE_PATTERN();
const std::array<char, 32> &FIRST_CHAR_ARRAY();
constexpr static uint PATTERN_LENGTH = 4;
constexpr static uint MAX_AVG_VARIANCE = static_cast<uint>(PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.48f);
constexpr static uint MAX_INDIVIDUAL_VARIANCE = static_cast<uint>(PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.7f);
}
} // namespace cv
#endif // OPENCV_BARCODE_UPCEAN_DECODER_HPP
@@ -0,0 +1,522 @@
// 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) 2020-2021 darkliang wangberlinT Certseeds
#include "../precomp.hpp"
#include "bardetect.hpp"
#include "opencv2/geometry.hpp"
namespace cv {
namespace barcode {
static constexpr float PI = static_cast<float>(CV_PI);
static constexpr float HALF_PI = static_cast<float>(CV_PI / 2);
#define CALCULATE_SUM(ptr, result) \
top_left = static_cast<float>(*((ptr) + left_col + integral_cols * top_row));\
top_right = static_cast<float>(*((ptr) + integral_cols * top_row + right_col));\
bottom_right = static_cast<float>(*((ptr) + right_col + bottom_row * integral_cols));\
bottom_left = static_cast<float>(*((ptr) + bottom_row * integral_cols + left_col));\
(result) = (bottom_right - bottom_left - top_right + top_left);
inline bool Detect::isValidCoord(const Point &coord, const Size &limit)
{
if ((coord.x < 0) || (coord.y < 0))
{
return false;
}
if ((unsigned) coord.x > (unsigned) (limit.width - 1) || ((unsigned) coord.y > (unsigned) (limit.height - 1)))
{
return false;
}
return true;
}
//==============================================================================
// NMSBoxes copied from modules/dnn/src/nms.inl.hpp
// TODO: move NMSBoxes outside the dnn module to allow other modules use it
namespace
{
template <typename T>
static inline bool SortScorePairDescend(const std::pair<float, T>& pair1,
const std::pair<float, T>& pair2)
{
return pair1.first > pair2.first;
}
inline void GetMaxScoreIndex(const std::vector<float>& scores, const float threshold, const int top_k,
std::vector<std::pair<float, int> >& score_index_vec)
{
CV_DbgAssert(score_index_vec.empty());
// Generate index score pairs.
for (size_t i = 0; i < scores.size(); ++i)
{
if (scores[i] > threshold)
{
score_index_vec.push_back(std::make_pair(scores[i], (int)i));
}
}
// Sort the score pair according to the scores in descending order
std::stable_sort(score_index_vec.begin(), score_index_vec.end(),
SortScorePairDescend<int>);
// Keep top_k scores if needed.
if (top_k > 0 && top_k < (int)score_index_vec.size())
{
score_index_vec.resize(top_k);
}
}
template <typename BoxType>
inline void NMSFast_(const std::vector<BoxType>& bboxes,
const std::vector<float>& scores, const float score_threshold,
const float nms_threshold, const float eta, const int top_k,
std::vector<int>& indices,
float (*computeOverlap)(const BoxType&, const BoxType&),
size_t limit = std::numeric_limits<int>::max())
{
CV_Assert(bboxes.size() == scores.size());
// Get top_k scores (with corresponding indices).
std::vector<std::pair<float, int> > score_index_vec;
GetMaxScoreIndex(scores, score_threshold, top_k, score_index_vec);
// Do nms.
float adaptive_threshold = nms_threshold;
indices.clear();
for (size_t i = 0; i < score_index_vec.size(); ++i) {
const int idx = score_index_vec[i].second;
bool keep = true;
for (int k = 0; k < (int)indices.size() && keep; ++k) {
const int kept_idx = indices[k];
float overlap = computeOverlap(bboxes[idx], bboxes[kept_idx]);
keep = overlap <= adaptive_threshold;
}
if (keep) {
indices.push_back(idx);
if (indices.size() >= limit) {
break;
}
}
if (keep && eta < 1 && adaptive_threshold > 0.5) {
adaptive_threshold *= eta;
}
}
}
static inline float rotatedRectIOU(const RotatedRect& a, const RotatedRect& b)
{
std::vector<Point2f> inter;
int res = rotatedRectangleIntersection(a, b, inter);
if (inter.empty() || res == INTERSECT_NONE)
return 0.0f;
if (res == INTERSECT_FULL)
return 1.0f;
float interArea = (float)contourArea(inter);
return interArea / (a.size.area() + b.size.area() - interArea);
}
static void NMSBoxes(const std::vector<RotatedRect>& bboxes, const std::vector<float>& scores,
const float score_threshold, const float nms_threshold,
std::vector<int>& indices, const float eta = 1.f, const int top_k = 0)
{
CV_Assert_N(bboxes.size() == scores.size(), score_threshold >= 0,
nms_threshold >= 0, eta > 0);
NMSFast_(bboxes, scores, score_threshold, nms_threshold, eta, top_k, indices, rotatedRectIOU);
}
} // namespace <anonymous>::
//==============================================================================
void Detect::init(const Mat &src, double detectorThreshDownSamplingLimit)
{
const double min_side = std::min(src.size().width, src.size().height);
if (min_side > detectorThreshDownSamplingLimit)
{
purpose = SHRINKING;
coeff_expansion = min_side / detectorThreshDownSamplingLimit;
width = cvRound(src.size().width / coeff_expansion);
height = cvRound(src.size().height / coeff_expansion);
Size new_size(width, height);
resize(src, resized_barcode, new_size, 0, 0, INTER_AREA);
}
// else if (min_side < 512.0)
// {
// purpose = ZOOMING;
// coeff_expansion = 512.0 / min_side;
// width = cvRound(src.size().width * coeff_expansion);
// height = cvRound(src.size().height * coeff_expansion);
// Size new_size(width, height);
// resize(src, resized_barcode, new_size, 0, 0, INTER_CUBIC);
// }
else
{
purpose = UNCHANGED;
coeff_expansion = 1.0;
width = src.size().width;
height = src.size().height;
resized_barcode = src.clone();
}
// median blur: sometimes it reduces the noise, but also reduces the recall
// medianBlur(resized_barcode, resized_barcode, 3);
}
void Detect::localization(const std::vector<float>& detectorWindowSizes, double detectorThreshGradientMagnitude)
{
localization_bbox.clear();
bbox_scores.clear();
// get integral image
preprocess(detectorThreshGradientMagnitude);
// empirical setting
//static constexpr float SCALE_LIST[] = {0.01f, 0.03f, 0.06f, 0.08f};
const auto min_side = static_cast<float>(std::min(width, height));
int window_size;
for (const float scale: detectorWindowSizes)
{
window_size = cvRound(min_side * scale);
if(window_size == 0) {
window_size = 1;
}
calCoherence(window_size);
barcodeErode();
regionGrowing(window_size);
}
}
bool Detect::computeTransformationPoints()
{
bbox_indices.clear();
transformation_points.clear();
transformation_points.reserve(bbox_indices.size());
RotatedRect rect;
Point2f temp[4];
/**
* #24902 resolution invariant barcode detector
*
* refactor of THRESHOLD_SCORE = float(width * height) / 300.f
* wrt to rescaled input size - 300 value needs factorization
* only one factor pair matches a common aspect ratio of 4:3 ~ 20x15
* decomposing this yields THRESHOLD_SCORE = (width / 20) * (height / 15)
* therefore each factor was rescaled based by purpose (refsize was 512)
*/
const float THRESHOLD_WSCALE = (purpose != UNCHANGED) ? 20 : (20 * width / 512.f);
const float THRESHOLD_HSCALE = (purpose != UNCHANGED) ? 15 : (15 * height / 512.f);
const float THRESHOLD_SCORE = (width / THRESHOLD_WSCALE) * (height / THRESHOLD_HSCALE);
NMSBoxes(localization_bbox, bbox_scores, THRESHOLD_SCORE, 0.1f, bbox_indices);
for (const auto &bbox_index : bbox_indices)
{
rect = localization_bbox[bbox_index];
if (purpose == ZOOMING)
{
rect.center /= coeff_expansion;
rect.size.height /= static_cast<float>(coeff_expansion);
rect.size.width /= static_cast<float>(coeff_expansion);
}
else if (purpose == SHRINKING)
{
rect.center *= coeff_expansion;
rect.size.height *= static_cast<float>(coeff_expansion);
rect.size.width *= static_cast<float>(coeff_expansion);
}
rect.points(temp);
transformation_points.emplace_back(vector<Point2f>{temp[0], temp[1], temp[2], temp[3]});
}
return !transformation_points.empty();
}
void Detect::preprocess(double detectorGradientMagnitudeThresh)
{
Mat scharr_x, scharr_y, temp;
Scharr(resized_barcode, scharr_x, CV_32F, 1, 0);
Scharr(resized_barcode, scharr_y, CV_32F, 0, 1);
// calculate magnitude of gradient and truncate
magnitude(scharr_x, scharr_y, temp);
threshold(temp, temp, detectorGradientMagnitudeThresh, 1, THRESH_BINARY);
temp.convertTo(gradient_magnitude, CV_8U);
integral(gradient_magnitude, integral_edges, CV_32F);
for (int y = 0; y < height; y++)
{
auto *const x_row = scharr_x.ptr<float_t>(y);
auto *const y_row = scharr_y.ptr<float_t>(y);
auto *const magnitude_row = gradient_magnitude.ptr<uint8_t>(y);
for (int pos = 0; pos < width; pos++)
{
if (magnitude_row[pos] == 0)
{
x_row[pos] = 0;
y_row[pos] = 0;
continue;
}
if (x_row[pos] < 0)
{
x_row[pos] *= -1;
y_row[pos] *= -1;
}
}
}
integral(scharr_x, temp, integral_x_sq, CV_32F, CV_32F);
integral(scharr_y, temp, integral_y_sq, CV_32F, CV_32F);
integral(scharr_x.mul(scharr_y), integral_xy, temp, CV_32F, CV_32F);
}
// Change coherence orientation edge_nums
// depend on width height integral_edges integral_x_sq integral_y_sq integral_xy
void Detect::calCoherence(int window_size)
{
static constexpr float THRESHOLD_COHERENCE = 0.9f;
int right_col, left_col, top_row, bottom_row;
float xy, x_sq, y_sq, d, rect_area;
const float THRESHOLD_AREA = float(window_size * window_size) * 0.42f;
Size new_size(width / window_size, height / window_size);
coherence = Mat(new_size, CV_8U), orientation = Mat(new_size, CV_32F), edge_nums = Mat(new_size, CV_32F);
float top_left, top_right, bottom_left, bottom_right;
int integral_cols = width + 1;
const auto *edges_ptr = integral_edges.ptr<float_t>(), *x_sq_ptr = integral_x_sq.ptr<float_t>(), *y_sq_ptr = integral_y_sq.ptr<float_t>(), *xy_ptr = integral_xy.ptr<float_t>();
for (int y = 0; y < new_size.height; y++)
{
auto *coherence_row = coherence.ptr<uint8_t>(y);
auto *orientation_row = orientation.ptr<float_t>(y);
auto *edge_nums_row = edge_nums.ptr<float_t>(y);
if (y * window_size >= height)
{
continue;
}
top_row = y * window_size;
bottom_row = min(height, (y + 1) * window_size);
for (int pos = 0; pos < new_size.width; pos++)
{
// then calculate the column locations of the rectangle and set them to -1
// if they are outside the matrix bounds
if (pos * window_size >= width)
{
continue;
}
left_col = pos * window_size;
right_col = min(width, (pos + 1) * window_size);
//we had an integral image to count non-zero elements
CALCULATE_SUM(edges_ptr, rect_area)
if (rect_area < THRESHOLD_AREA)
{
// smooth region
coherence_row[pos] = 0;
continue;
}
CALCULATE_SUM(x_sq_ptr, x_sq)
CALCULATE_SUM(y_sq_ptr, y_sq)
CALCULATE_SUM(xy_ptr, xy)
// get the values of the rectangle corners from the integral image - 0 if outside bounds
d = sqrt((x_sq - y_sq) * (x_sq - y_sq) + 4 * xy * xy) / (x_sq + y_sq);
if (d > THRESHOLD_COHERENCE)
{
coherence_row[pos] = 255;
orientation_row[pos] = atan2(x_sq - y_sq, 2 * xy) / 2.0f;
edge_nums_row[pos] = rect_area;
}
else
{
coherence_row[pos] = 0;
}
}
}
}
// will change localization_bbox bbox_scores
// will change coherence,
// depend on coherence orientation edge_nums
void Detect::regionGrowing(int window_size)
{
static constexpr float LOCAL_THRESHOLD_COHERENCE = 0.95f, THRESHOLD_RADIAN =
PI / 30, LOCAL_RATIO = 0.5f, EXPANSION_FACTOR = 1.2f;
static constexpr uint THRESHOLD_BLOCK_NUM = 35;
Point pt_to_grow, pt; //point to grow
float src_value;
float cur_value;
float edge_num;
float rect_orientation;
float sin_sum, cos_sum;
uint counter;
//grow direction
static constexpr int DIR[8][2] = {{-1, -1},
{0, -1},
{1, -1},
{1, 0},
{1, 1},
{0, 1},
{-1, 1},
{-1, 0}};
vector<Point2f> growingPoints, growingImgPoints;
for (int y = 0; y < coherence.rows; y++)
{
auto *coherence_row = coherence.ptr<uint8_t>(y);
for (int x = 0; x < coherence.cols; x++)
{
if (coherence_row[x] == 0)
{
continue;
}
// flag
coherence_row[x] = 0;
growingPoints.clear();
growingImgPoints.clear();
pt = Point(x, y);
cur_value = orientation.at<float_t>(pt);
sin_sum = sin(2 * cur_value);
cos_sum = cos(2 * cur_value);
counter = 1;
edge_num = edge_nums.at<float_t>(pt);
growingPoints.push_back(pt);
growingImgPoints.push_back(Point(pt));
while (!growingPoints.empty())
{
pt = growingPoints.back();
growingPoints.pop_back();
src_value = orientation.at<float_t>(pt);
//growing in eight directions
for (auto i : DIR)
{
pt_to_grow = Point(pt.x + i[0], pt.y + i[1]);
//check if out of boundary
if (!isValidCoord(pt_to_grow, coherence.size()))
{
continue;
}
if (coherence.at<uint8_t>(pt_to_grow) == 0)
{
continue;
}
cur_value = orientation.at<float_t>(pt_to_grow);
if (abs(cur_value - src_value) < THRESHOLD_RADIAN ||
abs(cur_value - src_value) > PI - THRESHOLD_RADIAN)
{
coherence.at<uint8_t>(pt_to_grow) = 0;
sin_sum += sin(2 * cur_value);
cos_sum += cos(2 * cur_value);
counter += 1;
edge_num += edge_nums.at<float_t>(pt_to_grow);
growingPoints.push_back(pt_to_grow); //push next point to grow back to stack
growingImgPoints.push_back(pt_to_grow);
}
}
}
//minimum block num
if (counter < THRESHOLD_BLOCK_NUM)
{
continue;
}
float local_coherence = (sin_sum * sin_sum + cos_sum * cos_sum) / static_cast<float>(counter * counter);
// minimum local gradient orientation_arg coherence_arg
if (local_coherence < LOCAL_THRESHOLD_COHERENCE)
{
continue;
}
RotatedRect minRect = minAreaRect(growingImgPoints);
if (edge_num < minRect.size.area() * float(window_size * window_size) * LOCAL_RATIO ||
static_cast<float>(counter) < minRect.size.area() * LOCAL_RATIO)
{
continue;
}
const float local_orientation = atan2(cos_sum, sin_sum) / 2.0f;
// only orientation_arg is approximately equal to the rectangle orientation_arg
rect_orientation = (minRect.angle) * PI / 180.f;
if (minRect.size.width < minRect.size.height)
{
rect_orientation += (rect_orientation <= 0.f ? HALF_PI : -HALF_PI);
std::swap(minRect.size.width, minRect.size.height);
}
if (abs(local_orientation - rect_orientation) > THRESHOLD_RADIAN &&
abs(local_orientation - rect_orientation) < PI - THRESHOLD_RADIAN)
{
continue;
}
minRect.angle = local_orientation * 180.f / PI;
minRect.size.width *= static_cast<float>(window_size) * EXPANSION_FACTOR;
minRect.size.height *= static_cast<float>(window_size);
minRect.center.x = (minRect.center.x + 0.5f) * static_cast<float>(window_size);
minRect.center.y = (minRect.center.y + 0.5f) * static_cast<float>(window_size);
localization_bbox.push_back(minRect);
bbox_scores.push_back(edge_num);
}
}
}
inline const std::array<Mat, 4> &getStructuringElement()
{
static const std::array<Mat, 4> structuringElement{
Mat_<uint8_t>{{3, 3},
{255, 0, 0, 0, 0, 0, 0, 0, 255}}, Mat_<uint8_t>{{3, 3},
{0, 0, 255, 0, 0, 0, 255, 0, 0}},
Mat_<uint8_t>{{3, 3},
{0, 0, 0, 255, 0, 255, 0, 0, 0}}, Mat_<uint8_t>{{3, 3},
{0, 255, 0, 0, 0, 0, 0, 255, 0}}};
return structuringElement;
}
// Change mat
void Detect::barcodeErode()
{
static const std::array<Mat, 4> &structuringElement = getStructuringElement();
Mat m0, m1, m2, m3;
dilate(coherence, m0, structuringElement[0]);
dilate(coherence, m1, structuringElement[1]);
dilate(coherence, m2, structuringElement[2]);
dilate(coherence, m3, structuringElement[3]);
int sum;
for (int y = 0; y < coherence.rows; y++)
{
auto coherence_row = coherence.ptr<uint8_t>(y);
auto m0_row = m0.ptr<uint8_t>(y);
auto m1_row = m1.ptr<uint8_t>(y);
auto m2_row = m2.ptr<uint8_t>(y);
auto m3_row = m3.ptr<uint8_t>(y);
for (int pos = 0; pos < coherence.cols; pos++)
{
if (coherence_row[pos] != 0)
{
sum = m0_row[pos] + m1_row[pos] + m2_row[pos] + m3_row[pos];
//more than 2 group
coherence_row[pos] = sum > 600 ? 255 : 0;
}
}
}
}
}
}
@@ -0,0 +1,62 @@
// 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) 2020-2021 darkliang wangberlinT Certseeds
#ifndef OPENCV_BARCODE_BARDETECT_HPP
#define OPENCV_BARCODE_BARDETECT_HPP
#include <opencv2/core.hpp>
namespace cv {
namespace barcode {
using std::vector;
class Detect
{
private:
vector<RotatedRect> localization_rects;
vector<RotatedRect> localization_bbox;
vector<float> bbox_scores;
vector<int> bbox_indices;
vector<vector<Point2f>> transformation_points;
public:
void init(const Mat &src, double detectorThreshDownSamplingLimit);
void localization(const vector<float>& detectorWindowSizes, double detectorGradientMagnitudeThresh);
vector<vector<Point2f>> getTransformationPoints()
{ return transformation_points; }
bool computeTransformationPoints();
protected:
enum resize_direction
{
ZOOMING, SHRINKING, UNCHANGED
} purpose = UNCHANGED;
double coeff_expansion = 1.0;
int height, width;
Mat resized_barcode, gradient_magnitude, coherence, orientation, edge_nums, integral_x_sq, integral_y_sq, integral_xy, integral_edges;
void preprocess(double detectorThreshGradientMagnitude);
void calCoherence(int window_size);
static inline bool isValidCoord(const Point &coord, const Size &limit);
void regionGrowing(int window_size);
void barcodeErode();
};
}
}
#endif // OPENCV_BARCODE_BARDETECT_HPP
File diff suppressed because it is too large Load Diff
+211
View File
@@ -0,0 +1,211 @@
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// Intel License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2000, Intel Corporation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of Intel Corporation may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#include "precomp.hpp"
#include <vector>
#include <algorithm>
namespace cv {
using namespace std;
static void icvGetQuadrangleHypotheses(const std::vector<std::vector< cv::Point > > & contours, const std::vector< cv::Vec4i > & hierarchy, std::vector<std::pair<float, int> >& quads, int class_id)
{
const float min_aspect_ratio = 0.3f;
const float max_aspect_ratio = 3.0f;
const float min_box_size = 10.0f;
for (size_t i = 0; i < contours.size(); ++i)
{
if (hierarchy.at(i)[3] != -1)
continue; // skip holes
const std::vector< cv::Point > & c = contours[i];
cv::RotatedRect box = cv::minAreaRect(c);
float box_size = MAX(box.size.width, box.size.height);
if(box_size < min_box_size)
{
continue;
}
float aspect_ratio = box.size.width/MAX(box.size.height, 1);
if(aspect_ratio < min_aspect_ratio || aspect_ratio > max_aspect_ratio)
{
continue;
}
quads.emplace_back(box_size, class_id);
}
}
static void countClasses(const std::vector<std::pair<float, int> >& pairs, size_t idx1, size_t idx2, std::vector<int>& counts)
{
counts.assign(2, 0);
for(size_t i = idx1; i != idx2; i++)
{
counts[pairs[i].second]++;
}
}
inline bool less_pred(const std::pair<float, int>& p1, const std::pair<float, int>& p2)
{
return p1.first < p2.first;
}
static void fillQuads(Mat & white, Mat & black, double white_thresh, double black_thresh, vector<pair<float, int> > & quads)
{
Mat thresh;
{
vector< vector<Point> > contours;
vector< Vec4i > hierarchy;
threshold(white, thresh, white_thresh, 255, THRESH_BINARY);
findContours(thresh, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
icvGetQuadrangleHypotheses(contours, hierarchy, quads, 1);
}
{
vector< vector<Point> > contours;
vector< Vec4i > hierarchy;
threshold(black, thresh, black_thresh, 255, THRESH_BINARY_INV);
findContours(thresh, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
icvGetQuadrangleHypotheses(contours, hierarchy, quads, 0);
}
}
static bool checkQuads(vector<pair<float, int> > & quads, const cv::Size & size)
{
const size_t min_quads_count = size.width*size.height/2;
std::sort(quads.begin(), quads.end(), less_pred);
// now check if there are many hypotheses with similar sizes
// do this by floodfill-style algorithm
const float size_rel_dev = 0.4f;
for(size_t i = 0; i < quads.size(); i++)
{
size_t j = i + 1;
for(; j < quads.size(); j++)
{
if(quads[j].first/quads[i].first > 1.0f + size_rel_dev)
{
break;
}
}
if(j + 1 > min_quads_count + i)
{
// check the number of black and white squares
std::vector<int> counts;
countClasses(quads, i, j, counts);
const int black_count = cvRound(ceil(size.width/2.0)*ceil(size.height/2.0));
const int white_count = cvRound(floor(size.width/2.0)*floor(size.height/2.0));
if(counts[0] < black_count*0.75 ||
counts[1] < white_count*0.75)
{
continue;
}
return true;
}
}
return false;
}
bool checkChessboard(InputArray _img, Size size)
{
Mat img = _img.getMat();
CV_Assert(img.channels() == 1 && img.depth() == CV_8U);
const int erosion_count = 1;
const float black_level = 20.f;
const float white_level = 130.f;
const float black_white_gap = 70.f;
Mat white;
Mat black;
erode(img, white, Mat(), Point(-1, -1), erosion_count);
dilate(img, black, Mat(), Point(-1, -1), erosion_count);
bool result = false;
for(float thresh_level = black_level; thresh_level < white_level && !result; thresh_level += 20.0f)
{
vector<pair<float, int> > quads;
fillQuads(white, black, thresh_level + black_white_gap, thresh_level, quads);
if (checkQuads(quads, size))
result = true;
}
return result;
}
// does a fast check if a chessboard is in the input image. This is a workaround to
// a problem of cvFindChessboardCorners being slow on images with no chessboard
// - src: input binary image
// - size: chessboard size
// Returns 1 if a chessboard can be in this image and findChessboardCorners should be called,
// 0 if there is no chessboard, -1 in case of error
int checkChessboardBinary(const cv::Mat & img, const cv::Size & size)
{
CV_Assert(img.channels() == 1 && img.depth() == CV_8U);
Mat white = img.clone();
Mat black = img.clone();
int result = 0;
for ( int erosion_count = 0; erosion_count <= 3; erosion_count++ )
{
if ( 1 == result )
break;
if ( 0 != erosion_count ) // first iteration keeps original images
{
erode(white, white, Mat(), Point(-1, -1), 1);
dilate(black, black, Mat(), Point(-1, -1), 1);
}
vector<pair<float, int> > quads;
fillQuads(white, black, 128, 128, quads);
if (checkQuads(quads, size))
result = 1;
}
return result;
}
}
File diff suppressed because it is too large Load Diff
+872
View File
@@ -0,0 +1,872 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#ifndef CHESSBOARD_HPP_
#define CHESSBOARD_HPP_
#include "opencv2/core.hpp"
#include "opencv2/features.hpp"
#include <vector>
#include <set>
#include <map>
namespace cv {
namespace details{
/**
* \brief Fast point sysmetric cross detector based on a localized radon transformation
*/
class FastX : public cv::Feature2D
{
public:
struct Parameters
{
float strength; //!< minimal strength of a valid junction in dB
float resolution; //!< angle resolution in radians
int branches; //!< the number of branches
int min_scale; //!< scale level [0..8]
int max_scale; //!< scale level [0..8]
bool filter; //!< post filter feature map to improve impulse response
bool super_resolution; //!< up-sample
Parameters()
{
strength = 40;
resolution = float(CV_PI*0.25);
branches = 2;
min_scale = 2;
max_scale = 5;
super_resolution = true;
filter = true;
}
};
public:
FastX(const Parameters &config = Parameters());
virtual ~FastX(){}
void reconfigure(const Parameters &para);
//declaration to be wrapped by rbind
void detect(cv::InputArray image,std::vector<cv::KeyPoint>& keypoints, cv::InputArray mask=cv::Mat())override
{cv::Feature2D::detect(image.getMat(),keypoints,mask.getMat());}
virtual void detectAndCompute(cv::InputArray image,
cv::InputArray mask,
std::vector<cv::KeyPoint>& keypoints,
cv::OutputArray descriptors,
bool useProvidedKeyPoints = false)override;
void detectImpl(const cv::Mat& image,
std::vector<cv::KeyPoint>& keypoints,
std::vector<cv::Mat> &feature_maps,
const cv::Mat& mask=cv::Mat())const;
void detectImpl(const cv::Mat& image,
std::vector<cv::Mat> &rotated_images,
std::vector<cv::Mat> &feature_maps,
const cv::Mat& mask=cv::Mat())const;
void findKeyPoints(const std::vector<cv::Mat> &feature_map,
std::vector<cv::KeyPoint>& keypoints,
const cv::Mat& mask = cv::Mat())const;
std::vector<std::vector<float> > calcAngles(const std::vector<cv::Mat> &rotated_images,
std::vector<cv::KeyPoint> &keypoints)const;
// define pure virtual methods
virtual int descriptorSize()const override{return 0;}
virtual int descriptorType()const override{return 0;}
virtual void operator()( cv::InputArray image, cv::InputArray mask, std::vector<cv::KeyPoint>& keypoints, cv::OutputArray descriptors, bool useProvidedKeypoints=false )const
{
descriptors.clear();
detectImpl(image.getMat(),keypoints,mask);
if(!useProvidedKeypoints) // suppress compiler warning
return;
return;
}
protected:
virtual void computeImpl( const cv::Mat& image, std::vector<cv::KeyPoint>& keypoints, cv::Mat& descriptors)const
{
descriptors = cv::Mat();
detectImpl(image,keypoints);
}
private:
void detectImpl(const cv::Mat& _src, std::vector<cv::KeyPoint>& keypoints, const cv::Mat& mask)const;
virtual void detectImpl(cv::InputArray image, std::vector<cv::KeyPoint>& keypoints, cv::InputArray mask=cv::noArray())const;
void rotate(float angle,cv::InputArray img,cv::Size size,cv::OutputArray out)const;
void calcFeatureMap(const cv::Mat &images,cv::Mat& out)const;
private:
Parameters parameters;
};
/**
* \brief Ellipse class
*/
class Ellipse
{
public:
Ellipse();
Ellipse(const cv::Point2f &center, const cv::Size2f &axes, float angle);
void draw(cv::InputOutputArray img,const cv::Scalar &color = cv::Scalar::all(120))const;
bool contains(const cv::Point2f &pt)const;
cv::Point2f getCenter()const;
const cv::Size2f &getAxes()const;
private:
cv::Point2f center;
cv::Size2f axes;
float angle,cosf,sinf;
};
/**
* \brief Chessboard corner detector
*
* The detectors tries to find all chessboard corners of an imaged
* chessboard and returns them as an ordered vector of KeyPoints.
* Thereby, the left top corner has index 0 and the bottom right
* corner n*m-1.
*/
class Chessboard: public cv::Feature2D
{
public:
static const int DUMMY_FIELD_SIZE = 100; // in pixel
/**
* \brief Configuration of a chessboard corner detector
*
*/
struct Parameters
{
cv::Size chessboard_size; //!< size of the chessboard
int min_scale; //!< scale level [0..8]
int max_scale; //!< scale level [0..8]
int max_points; //!< maximal number of points regarded
int max_tests; //!< maximal number of tested hypothesis
bool super_resolution; //!< use super-repsolution for chessboard detection
bool larger; //!< indicates if larger boards should be returned
bool marker; //!< indicates that valid boards must have a white and black circle marker used for orientation
Parameters()
{
chessboard_size = cv::Size(9,6);
min_scale = 3;
max_scale = 4;
super_resolution = true;
max_points = 200;
max_tests = 50;
larger = false;
marker = false;
}
Parameters(int scale,int _max_points):
min_scale(scale),
max_scale(scale),
max_points(_max_points)
{
chessboard_size = cv::Size(9,6);
}
};
/**
* \brief Gets the 3D objects points for the chessboard assuming the
* left top corner is located at the origin.
*
* \param[in] pattern_size Number of rows and cols of the pattern
* \param[in] cell_size Size of one cell
*
* \returns Returns the object points as CV_32FC3
*/
static cv::Mat getObjectPoints(const cv::Size &pattern_size,float cell_size);
/**
* \brief Class for searching and storing chessboard corners.
*
* The search is based on a feature map having strong pixel
* values at positions where a chessboard corner is located.
*
* The board must be rectangular but supports empty cells
*
*/
class Board
{
public:
/**
* \brief Estimates the position of the next point on a line using cross ratio constrain
*
* cross ratio:
* d12/d34 = d13/d24
*
* point order on the line:
* p0 --> p1 --> p2 --> p3
*
* \param[in] p0 First point coordinate
* \param[in] p1 Second point coordinate
* \param[in] p2 Third point coordinate
* \param[out] p3 Forth point coordinate
*
*/
static bool estimatePoint(const cv::Point2f &p0,const cv::Point2f &p1,const cv::Point2f &p2,cv::Point2f &p3);
// using 1D homography
static bool estimatePoint(const cv::Point2f &p0,const cv::Point2f &p1,const cv::Point2f &p2,const cv::Point2f &p3, cv::Point2f &p4);
/**
* \brief Checks if all points of a row or column have a valid cross ratio constraint
*
* cross ratio:
* d12/d34 = d13/d24
*
* point order on the row/column:
* pt1 --> pt2 --> pt3 --> pt4
*
* \param[in] points THe points of the row/column
*
*/
static bool checkRowColumn(const std::vector<cv::Point2f> &points);
/**
* \brief Estimates the search area for the next point on the line using cross ratio
*
* point order on the line:
* (p0) --> p1 --> p2 --> p3 --> search area
*
* \param[in] p1 First point coordinate
* \param[in] p2 Second point coordinate
* \param[in] p3 Third point coordinate
* \param[in] p Percentage of d34 used for the search area width and height [0..1]
* \param[out] ellipse The search area
* \param[in] p0 optional point to improve accuracy
*
* \return Returns false if no search area can be calculated
*
*/
static bool estimateSearchArea(const cv::Point2f &p1,const cv::Point2f &p2,const cv::Point2f &p3,float p,
Ellipse &ellipse,const cv::Point2f *p0 =NULL);
/**
* \brief Estimates the search area for a specific point based on the given homography
*
* \param[in] H homography describing the transformation from ideal board to real one
* \param[in] row Row of the point
* \param[in] col Col of the point
* \param[in] p Percentage [0..1]
*
* \return Returns false if no search area can be calculated
*
*/
static Ellipse estimateSearchArea(cv::Mat H,int row, int col,float p,int field_size = DUMMY_FIELD_SIZE);
/**
* \brief Searches for the maximum in a given search area
*
* \param[in] map feature map
* \param[in] ellipse search area
* \param[in] min_val Minimum value of the maximum to be accepted as maximum
*
* \return Returns a negative value if all points are outside the ellipse
*
*/
static float findMaxPoint(cv::flann::Index &index,const cv::Mat &data,const Ellipse &ellipse,float white_angle,float black_angle,cv::Point2f &pt);
/**
* \brief Searches for the next point using cross ratio constrain
*
* \param[in] index flann index
* \param[in] data extended flann data
* \param[in] pt1
* \param[in] pt2
* \param[in] pt3
* \param[in] white_angle
* \param[in] black_angle
* \param[in] min_response
* \param[out] point The resulting point
*
* \return Returns false if no point could be found
*
*/
static bool findNextPoint(cv::flann::Index &index,const cv::Mat &data,
const cv::Point2f &pt1,const cv::Point2f &pt2, const cv::Point2f &pt3,
float white_angle,float black_angle,float min_response,cv::Point2f &point);
/**
* \brief Creates a new Board object
*
*/
Board(float white_angle=0,float black_angle=0);
Board(const cv::Size &size, const std::vector<cv::Point2f> &points,float white_angle=0,float black_angle=0);
Board(const Chessboard::Board &other);
virtual ~Board();
Board& operator=(const Chessboard::Board &other);
/**
* \brief Draws the corners into the given image
*
* \param[in] m The image
* \param[out] out The resulting image
* \param[in] H optional homography to calculate search area
*
*/
void draw(cv::InputArray m,cv::OutputArray out,cv::InputArray H=cv::Mat())const;
/**
* \brief Estimates the pose of the chessboard
*
*/
bool estimatePose(const cv::Size2f &real_size,cv::InputArray _K,cv::OutputArray rvec,cv::OutputArray tvec)const;
/**
* \brief Clears all internal data of the object
*
*/
void clear();
/**
* \brief Returns the angle of the black diagnonale
*
*/
float getBlackAngle()const;
/**
* \brief Returns the angle of the black diagnonale
*
*/
float getWhiteAngle()const;
/**
* \brief Initializes a 3x3 grid from 9 corner coordinates
*
* All points must be ordered:
* p0 p1 p2
* p3 p4 p5
* p6 p7 p8
*
* \param[in] points vector of points
*
* \return Returns false if the grid could not be initialized
*/
bool init(const std::vector<cv::Point2f> points);
/**
* \brief Returns true if the board is empty
*
*/
bool isEmpty() const;
/**
* \brief Returns all board corners as ordered vector
*
* The left top corner has index 0 and the bottom right
* corner rows*cols-1. All corners which only belong to
* empty cells are returned as NaN.
*/
std::vector<cv::Point2f> getCorners(bool ball=true) const;
/**
* \brief Returns all board corners as ordered vector of KeyPoints
*
* The left top corner has index 0 and the bottom right
* corner rows*cols-1.
*
* \param[in] ball if set to false only non empty points are returned
*
*/
std::vector<cv::KeyPoint> getKeyPoints(bool ball=true) const;
/**
* \brief Returns the centers of the chessboard cells
*
* The left top corner has index 0 and the bottom right
* corner (rows-1)*(cols-1)-1.
*
*/
std::vector<cv::Point2f> getCellCenters() const;
/**
* \brief Returns all cells as mats of four points each describing their corners.
*
* The left top cell has index 0
*
*/
std::vector<cv::Mat> getCells(float shrink_factor = 1.0,bool bwhite=true,bool bblack = true) const;
/**
* \brief Estimates the homography between an ideal board
* and reality based on the already recovered points
*
* \param[in] rect selecting a subset of the already recovered points
* \param[in] field_size The field size of the ideal board
*
*/
cv::Mat estimateHomography(cv::Rect rect,int field_size = DUMMY_FIELD_SIZE)const;
/**
* \brief Estimates the homography between an ideal board
* and reality based on the already recovered points
*
* \param[in] field_size The field size of the ideal board
*
*/
cv::Mat estimateHomography(int field_size = DUMMY_FIELD_SIZE)const;
/**
* \brief Warp image to match ideal checkerboard
*
*/
cv::Mat warpImage(cv::InputArray image)const;
/**
* \brief Returns the size of the board
*
*/
cv::Size getSize() const;
/**
* \brief Returns the number of cols
*
*/
size_t colCount() const;
/**
* \brief Returns the number of rows
*
*/
size_t rowCount() const;
/**
* \brief Returns the inner contour of the board including only valid corners
*
* \info the contour might be non squared if not all points of the board are defined
*
*/
std::vector<cv::Point2f> getContour()const;
/**
* \brief Masks the found board in the given image
*
*/
void maskImage(cv::InputOutputArray img,const cv::Scalar &color=cv::Scalar::all(0))const;
/**
* \brief Grows the board in all direction until no more corners are found in the feature map
*
* \param[in] data CV_32FC1 data of the flann index
* \param[in] flann_index flann index
*
* \returns the number of grows
*/
int grow(const cv::Mat &data,cv::flann::Index &flann_index);
/**
* \brief Validates all corners using guided search based on the given homography
*
* \param[in] data CV_32FC1 data of the flann index
* \param[in] flann_index flann index
* \param[in] h Homography describing the transformation from ideal board to the real one
* \param[in] min_response Min response
*
* \returns the number of valid corners
*/
int validateCorners(const cv::Mat &data,cv::flann::Index &flann_index,const cv::Mat &h,float min_response=0);
/**
* \brief check that no corner is used more than once
*
* \returns Returns false if a corner is used more than once
*/
bool checkUnique()const;
/**
* \brief Returns false if the angles of the contour are smaller than 35°
*
*/
bool validateContour()const;
/**
\brief delete left column of the board
*/
bool shrinkLeft();
/**
\brief delete right column of the board
*/
bool shrinkRight();
/**
\brief shrink first row of the board
*/
bool shrinkTop();
/**
\brief delete last row of the board
*/
bool shrinkBottom();
/**
* \brief Grows the board to the left by adding one column.
*
* \param[in] map CV_32FC1 feature map
*
* \returns Returns false if the feature map has no maxima at the requested positions
*/
bool growLeft(const cv::Mat &map,cv::flann::Index &flann_index);
void growLeft();
/**
* \brief Grows the board to the top by adding one row.
*
* \param[in] map CV_32FC1 feature map
*
* \returns Returns false if the feature map has no maxima at the requested positions
*/
bool growTop(const cv::Mat &map,cv::flann::Index &flann_index);
void growTop();
/**
* \brief Grows the board to the right by adding one column.
*
* \param[in] map CV_32FC1 feature map
*
* \returns Returns false if the feature map has no maxima at the requested positions
*/
bool growRight(const cv::Mat &map,cv::flann::Index &flann_index);
void growRight();
/**
* \brief Grows the board to the bottom by adding one row.
*
* \param[in] map CV_32FC1 feature map
*
* \returns Returns false if the feature map has no maxima at the requested positions
*/
bool growBottom(const cv::Mat &map,cv::flann::Index &flann_index);
void growBottom();
/**
* \brief Adds one column on the left side
*
* \param[in] points The corner coordinates
*
*/
void addColumnLeft(const std::vector<cv::Point2f> &points);
/**
* \brief Adds one column at the top
*
* \param[in] points The corner coordinates
*
*/
void addRowTop(const std::vector<cv::Point2f> &points);
/**
* \brief Adds one column on the right side
*
* \param[in] points The corner coordinates
*
*/
void addColumnRight(const std::vector<cv::Point2f> &points);
/**
* \brief Adds one row at the bottom
*
* \param[in] points The corner coordinates
*
*/
void addRowBottom(const std::vector<cv::Point2f> &points);
/**
* \brief Rotates the board 90° degrees to the left
*/
void rotateLeft();
/**
* \brief Rotates the board 90° degrees to the right
*/
void rotateRight();
/**
* \brief Flips the board along its local x(width) coordinate direction
*/
void flipVertical();
/**
* \brief Flips the board along its local y(height) coordinate direction
*/
void flipHorizontal();
/**
* \brief Flips and rotates the board so that the angle of
* either the black or white diagonal is bigger than the x
* and y axis of the board and from a right handed
* coordinate system
*/
void normalizeOrientation(bool bblack=true);
/**
* \brief Flips and rotates the board so that the marker
* is normalized
*/
bool normalizeMarkerOrientation();
/**
* \brief Exchanges the stored board with the board stored in other
*/
void swap(Chessboard::Board &other);
bool operator==(const Chessboard::Board& other) const {return rows*cols == other.rows*other.cols;}
bool operator< (const Chessboard::Board& other) const {return rows*cols < other.rows*other.cols;}
bool operator> (const Chessboard::Board& other) const {return rows*cols > other.rows*other.cols;}
bool operator>= (const cv::Size& size)const { return rows*cols >= size.width*size.height; }
/**
* \brief Returns a specific corner
*
* \info raises runtime_error if row col does not exists
*/
cv::Point2f& getCorner(int row,int col);
/**
* \brief Returns true if the cell is empty meaning at least one corner is NaN
*/
bool isCellEmpty(int row,int col);
/**
* \brief Returns the mapping from all corners idx to only valid corners idx
*/
std::map<int,int> getMapping()const;
/**
* \brief Returns true if the cell is black
*
*/
bool isCellBlack(int row,int col)const;
/**
* \brief Returns true if the cell has a round marker at its
* center
*
*/
bool hasCellMarker(int row,int col);
/**
* \brief Detects round markers in the chessboard fields based
* on the given image and the already recoverd board corners
*
* \returns Returns the number of found markes
*
*/
int detectMarkers(cv::InputArray image);
/**
* \brief Calculates the average edge sharpness for the chessboard
*
* \param[in] image The image where the chessboard was detected
* \param[in] rise_distance Rise distance 0.8 means 10% ... 90%
* \param[in] vertical by default only edge response for horiontal lines are calculated
*
* \returns Scalar(sharpness, average min_val, average max_val)
*
* \author aduda@krakenrobotik.de
*/
cv::Scalar calcEdgeSharpness(cv::InputArray image,float rise_distance=0.8,bool vertical=false,cv::OutputArray sharpness=cv::noArray());
/**
* \brief Gets the 3D objects points for the chessboard
* assuming the left top corner is located at the origin. In
* case the board as a marker, the white marker cell is at position zero
*
* \param[in] cell_size Size of one cell
*
* \returns Returns the object points as CV_32FC3
*/
cv::Mat getObjectPoints(float cell_size)const;
/**
* \brief Returns the angle the board is rotated agains the x-axis of the image plane
* \returns Returns the object points as CV_32FC3
*/
float getAngle()const;
/**
* \brief Returns true if the main direction of the board is close to the image x-axis than y-axis
*/
bool isHorizontal()const;
/**
* \brief Updates the search angles
*/
void setAngles(float white,float black);
private:
// stores one cell
// in general a cell is initialized by the Board so that:
// * all corners are always pointing to a valid cv::Point2f
// * depending on the position left,top,right and bottom might be set to NaN
// * A cell is empty if at least one corner is NaN
struct Cell
{
cv::Point2f *top_left,*top_right,*bottom_right,*bottom_left; // corners
Cell *left,*top,*right,*bottom; // neighbouring cells
bool black; // set to true if cell is black
bool marker; // set to true if cell has a round marker in its center
Cell();
bool empty()const; // indicates if the cell is empty (one of its corners has NaN)
int getRow()const;
int getCol()const;
cv::Point2f getCenter()const;
bool isInside(const cv::Point2f &pt)const; // check if point is inside the cell
};
// corners
enum CornerIndex
{
TOP_LEFT,
TOP_RIGHT,
BOTTOM_RIGHT,
BOTTOM_LEFT
};
Cell* getCell(int row,int column); // returns a specific cell
const Cell* getCell(int row,int column)const; // returns a specific cell
void drawEllipses(const std::vector<Ellipse> &ellipses);
// Iterator for iterating over board corners
class PointIter
{
public:
PointIter(Cell *cell,CornerIndex corner_index);
PointIter(const PointIter &other);
void operator=(const PointIter &other);
bool valid() const; // returns if the pointer is pointing to a cell
bool left(bool check_empty=false); // moves one corner to the left or returns false
bool right(bool check_empty=false); // moves one corner to the right or returns false
bool bottom(bool check_empty=false); // moves one corner to the bottom or returns false
bool top(bool check_empty=false); // moves one corner to the top or returns false
bool checkCorner()const; // returns true if the current corner belongs to at least one
// none empty cell
bool isNaN()const; // returns true if the current corner is NaN
const cv::Point2f* operator*() const; // current corner coordinate
cv::Point2f* operator*(); // current corner coordinate
const cv::Point2f* operator->() const; // current corner coordinate
cv::Point2f* operator->(); // current corner coordinate
Cell *getCell(); // current cell
private:
CornerIndex corner_index;
Cell *cell;
};
std::vector<Cell*> cells; // storage for all board cells
std::vector<cv::Point2f*> corners; // storage for all corners
Cell *top_left; // pointer to the top left corner of the board in its local coordinate system
int rows; // number of inner pattern rows
int cols; // number of inner pattern cols
float white_angle,black_angle;
};
public:
/**
* \brief Creates a chessboard corner detectors
*
* \param[in] config Configuration used to detect chessboard corners
*
*/
Chessboard(const Parameters &config = Parameters());
virtual ~Chessboard();
void reconfigure(const Parameters &config = Parameters());
Parameters getPara()const;
/*
* \brief Detects chessboard corners in the given image.
*
* The detectors tries to find all chessboard corners of an imaged
* chessboard and returns them as an ordered vector of KeyPoints.
* Thereby, the left top corner has index 0 and the bottom right
* corner n*m-1.
*
* \param[in] image The image
* \param[out] keypoints The detected corners as a vector of ordered KeyPoints
* \param[in] mask Currently not supported
*
*/
void detect(cv::InputArray image,std::vector<cv::KeyPoint>& keypoints, cv::InputArray mask=cv::Mat())override
{cv::Feature2D::detect(image.getMat(),keypoints,mask.getMat());}
virtual void detectAndCompute(cv::InputArray image,cv::InputArray mask, std::vector<cv::KeyPoint>& keypoints,cv::OutputArray descriptors,
bool useProvidedKeyPoints = false)override;
/*
* \brief Detects chessboard corners in the given image.
*
* The detectors tries to find all chessboard corners of an imaged
* chessboard and returns them as an ordered vector of KeyPoints.
* Thereby, the left top corner has index 0 and the bottom right
* corner n*m-1.
*
* \param[in] image The image
* \param[out] keypoints The detected corners as a vector of ordered KeyPoints
* \param[out] feature_maps The feature map generated by LRJT and used to find the corners
* \param[in] mask Currently not supported
*
*/
void detectImpl(const cv::Mat& image, std::vector<cv::KeyPoint>& keypoints,std::vector<cv::Mat> &feature_maps,const cv::Mat& mask)const;
Chessboard::Board detectImpl(const cv::Mat& image,std::vector<cv::Mat> &feature_maps,const cv::Mat& mask)const;
// define pure virtual methods
virtual int descriptorSize()const override{return 0;}
virtual int descriptorType()const override{return 0;}
virtual void operator()( cv::InputArray image, cv::InputArray mask, std::vector<cv::KeyPoint>& keypoints, cv::OutputArray descriptors, bool useProvidedKeypoints=false )const
{
descriptors.clear();
detectImpl(image.getMat(),keypoints,mask);
if(!useProvidedKeypoints) // suppress compiler warning
return;
return;
}
protected:
virtual void computeImpl( const cv::Mat& image, std::vector<cv::KeyPoint>& keypoints, cv::Mat& descriptors)const
{
descriptors = cv::Mat();
detectImpl(image,keypoints);
}
// indicates why a board could not be initialized for a certain keypoint
enum BState
{
MISSING_POINTS = 0, // at least 5 points are needed
MISSING_PAIRS = 1, // at least two pairs are needed
WRONG_PAIR_ANGLE = 2, // angle between pairs is too small
WRONG_CONFIGURATION = 3, // point configuration is wrong and does not belong to a board
FOUND_BOARD = 4 // board was found
};
void findKeyPoints(const cv::Mat& image, std::vector<cv::KeyPoint>& keypoints,std::vector<cv::Mat> &feature_maps,
std::vector<std::vector<float> > &angles ,const cv::Mat& mask)const;
cv::Mat buildData(const std::vector<cv::KeyPoint>& keypoints)const;
std::vector<cv::KeyPoint> getInitialPoints(cv::flann::Index &flann_index,const cv::Mat &data,const cv::KeyPoint &center,float white_angle,float black_angle, float min_response = 0)const;
BState generateBoards(cv::flann::Index &flann_index,const cv::Mat &data, const cv::KeyPoint &center,
float white_angle,float black_angle,float min_response,const cv::Mat &img,
std::vector<Chessboard::Board> &boards)const;
private:
void detectImpl(const cv::Mat&,std::vector<cv::KeyPoint>&, const cv::Mat& mast =cv::Mat())const;
virtual void detectImpl(cv::InputArray image, std::vector<cv::KeyPoint>& keypoints, cv::InputArray mask=cv::noArray())const;
private:
Parameters parameters; // storing the configuration of the detector
};
}} // end namespace details and cv
#endif
File diff suppressed because it is too large Load Diff
+199
View File
@@ -0,0 +1,199 @@
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#ifndef CIRCLESGRID_HPP_
#define CIRCLESGRID_HPP_
#include <fstream>
#include <set>
#include <list>
#include <numeric>
#include <map>
namespace cv {
class CirclesGridClusterFinder
{
CirclesGridClusterFinder& operator=(const CirclesGridClusterFinder&);
CirclesGridClusterFinder(const CirclesGridClusterFinder&);
public:
CirclesGridClusterFinder(const CirclesGridFinderParameters &parameters)
{
isAsymmetricGrid = parameters.gridType == CirclesGridFinderParameters::ASYMMETRIC_GRID;
squareSize = parameters.squareSize;
maxRectifiedDistance = parameters.maxRectifiedDistance;
}
void findGrid(const std::vector<Point2f> &points, Size patternSize, std::vector<Point2f>& centers);
//cluster 2d points by geometric coordinates
void hierarchicalClustering(const std::vector<Point2f> &points, const Size &patternSize, std::vector<Point2f> &patternPoints);
private:
void findCorners(const std::vector<Point2f> &hull2f, std::vector<Point2f> &corners);
void findOutsideCorners(const std::vector<Point2f> &corners, std::vector<Point2f> &outsideCorners);
void getSortedCorners(const std::vector<Point2f> &hull2f, const std::vector<Point2f> &patternPoints, const std::vector<Point2f> &corners, const std::vector<Point2f> &outsideCorners, std::vector<Point2f> &sortedCorners);
void rectifyPatternPoints(const std::vector<Point2f> &patternPoints, const std::vector<Point2f> &sortedCorners, std::vector<Point2f> &rectifiedPatternPoints);
void parsePatternPoints(const std::vector<Point2f> &patternPoints, const std::vector<Point2f> &rectifiedPatternPoints, std::vector<Point2f> &centers);
float squareSize, maxRectifiedDistance;
bool isAsymmetricGrid;
Size patternSize;
};
class Graph
{
public:
typedef std::set<size_t> Neighbors;
struct Vertex
{
Neighbors neighbors;
};
typedef std::map<size_t, Vertex> Vertices;
Graph(size_t n);
void addVertex(size_t id);
void addEdge(size_t id1, size_t id2);
void removeEdge(size_t id1, size_t id2);
bool doesVertexExist(size_t id) const;
bool areVerticesAdjacent(size_t id1, size_t id2) const;
size_t getVerticesCount() const;
size_t getDegree(size_t id) const;
const Neighbors& getNeighbors(size_t id) const;
void floydWarshall(Mat &distanceMatrix, int infinity = -1) const;
private:
Vertices vertices;
};
struct Path
{
int firstVertex;
int lastVertex;
int length;
std::vector<size_t> vertices;
Path(int first = -1, int last = -1, int len = -1)
{
firstVertex = first;
lastVertex = last;
length = len;
}
};
class CirclesGridFinder
{
public:
CirclesGridFinder(Size patternSize, const std::vector<Point2f> &testKeypoints,
const CirclesGridFinderParameters &parameters = CirclesGridFinderParameters());
bool findHoles();
static Mat rectifyGrid(Size detectedGridSize, const std::vector<Point2f>& centers, const std::vector<
Point2f> &keypoint, std::vector<Point2f> &warpedKeypoints);
void getHoles(std::vector<Point2f> &holes) const;
void getAsymmetricHoles(std::vector<Point2f> &holes) const;
Size getDetectedGridSize() const;
void drawBasis(const std::vector<Point2f> &basis, Point2f origin, Mat &drawImg) const;
void drawBasisGraphs(const std::vector<Graph> &basisGraphs, Mat &drawImg, bool drawEdges = true,
bool drawVertices = true) const;
void drawHoles(const Mat &srcImage, Mat &drawImage) const;
private:
void computeRNG(Graph &rng, std::vector<Point2f> &vectors, Mat *drawImage = 0) const;
void rng2gridGraph(Graph &rng, std::vector<Point2f> &vectors) const;
void eraseUsedGraph(std::vector<Graph> &basisGraphs) const;
void filterOutliersByDensity(const std::vector<Point2f> &samples, std::vector<Point2f> &filteredSamples);
void findBasis(const std::vector<Point2f> &samples, std::vector<Point2f> &basis,
std::vector<Graph> &basisGraphs);
void findMCS(const std::vector<Point2f> &basis, std::vector<Graph> &basisGraphs);
size_t findLongestPath(std::vector<Graph> &basisGraphs, Path &bestPath);
float computeGraphConfidence(const std::vector<Graph> &basisGraphs, bool addRow, const std::vector<size_t> &points,
const std::vector<size_t> &seeds);
void addHolesByGraph(const std::vector<Graph> &basisGraphs, bool addRow, Point2f basisVec);
size_t findNearestKeypoint(Point2f pt) const;
void addPoint(Point2f pt, std::vector<size_t> &points);
void findCandidateLine(std::vector<size_t> &line, size_t seedLineIdx, bool addRow, Point2f basisVec, std::vector<
size_t> &seeds);
void findCandidateHoles(std::vector<size_t> &above, std::vector<size_t> &below, bool addRow, Point2f basisVec,
std::vector<size_t> &aboveSeeds, std::vector<size_t> &belowSeeds);
static bool areCentersNew(const std::vector<size_t> &newCenters, const std::vector<std::vector<size_t> > &holes);
bool isDetectionCorrect();
static void insertWinner(float aboveConfidence, float belowConfidence, float minConfidence, bool addRow,
const std::vector<size_t> &above, const std::vector<size_t> &below, std::vector<std::vector<
size_t> > &holes);
struct Segment
{
Point2f s;
Point2f e;
Segment(Point2f _s, Point2f _e);
};
//if endpoint is on a segment then function return false
static bool areSegmentsIntersecting(Segment seg1, Segment seg2);
static bool doesIntersectionExist(const std::vector<Segment> &corner, const std::vector<std::vector<Segment> > &segments);
void getCornerSegments(const std::vector<std::vector<size_t> > &points, std::vector<std::vector<Segment> > &segments,
std::vector<Point> &cornerIndices, std::vector<Point> &firstSteps,
std::vector<Point> &secondSteps) const;
size_t getFirstCorner(std::vector<Point> &largeCornerIndices, std::vector<Point> &smallCornerIndices,
std::vector<Point> &firstSteps, std::vector<Point> &secondSteps) const;
static double getDirection(Point2f p1, Point2f p2, Point2f p3);
std::vector<Point2f> keypoints;
std::vector<std::vector<size_t> > holes;
std::vector<std::vector<size_t> > holes2;
std::vector<std::vector<size_t> > *largeHoles;
std::vector<std::vector<size_t> > *smallHoles;
const Size_<size_t> patternSize;
CirclesGridFinderParameters parameters;
bool rotatedGrid = false;
CirclesGridFinder& operator=(const CirclesGridFinder&);
CirclesGridFinder(const CirclesGridFinder&);
};
}
#endif /* CIRCLESGRID_HPP_ */
+325
View File
@@ -0,0 +1,325 @@
// 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/imgproc.hpp"
#include "opencv2/core.hpp"
#ifdef HAVE_OPENCV_DNN
#include "opencv2/dnn.hpp"
#endif
#include <algorithm>
namespace cv
{
#ifdef HAVE_OPENCV_DNN
class FaceDetectorYNImpl : public FaceDetectorYN
{
public:
FaceDetectorYNImpl(const String& model,
const String& config,
const Size& input_size,
float score_threshold,
float nms_threshold,
int top_k,
int backend_id,
int target_id)
:divisor(32),
strides({8, 16, 32})
{
net = dnn::readNet(model, config);
CV_Assert(!net.empty());
net.setPreferableBackend(backend_id);
net.setPreferableTarget(target_id);
inputW = input_size.width;
inputH = input_size.height;
padW = (int((inputW - 1) / divisor) + 1) * divisor;
padH = (int((inputH - 1) / divisor) + 1) * divisor;
if (!net.getMainGraph())
net.setInputShape("input", MatShape({1, 3, padH, padW}));
scoreThreshold = score_threshold;
nmsThreshold = nms_threshold;
topK = top_k;
}
FaceDetectorYNImpl(const String& framework,
const std::vector<uchar>& bufferModel,
const std::vector<uchar>& bufferConfig,
const Size& input_size,
float score_threshold,
float nms_threshold,
int top_k,
int backend_id,
int target_id)
:divisor(32),
strides({8, 16, 32})
{
net = dnn::readNet(framework, bufferModel, bufferConfig);
CV_Assert(!net.empty());
net.setPreferableBackend(backend_id);
net.setPreferableTarget(target_id);
inputW = input_size.width;
inputH = input_size.height;
padW = (int((inputW - 1) / divisor) + 1) * divisor;
padH = (int((inputH - 1) / divisor) + 1) * divisor;
if (!net.getMainGraph())
net.setInputShape("input", MatShape({1, 3, padH, padW}));
scoreThreshold = score_threshold;
nmsThreshold = nms_threshold;
topK = top_k;
}
void setInputSize(const Size& input_size) override
{
inputW = input_size.width;
inputH = input_size.height;
padW = ((inputW - 1) / divisor + 1) * divisor;
padH = ((inputH - 1) / divisor + 1) * divisor;
if (!net.getMainGraph())
net.setInputShape("input", MatShape({1, 3, padH, padW}));
}
Size getInputSize() override
{
Size input_size;
input_size.width = inputW;
input_size.height = inputH;
return input_size;
}
void setScoreThreshold(float score_threshold) override
{
scoreThreshold = score_threshold;
}
float getScoreThreshold() override
{
return scoreThreshold;
}
void setNMSThreshold(float nms_threshold) override
{
nmsThreshold = nms_threshold;
}
float getNMSThreshold() override
{
return nmsThreshold;
}
void setTopK(int top_k) override
{
topK = top_k;
}
int getTopK() override
{
return topK;
}
int detect(InputArray input_image, OutputArray faces) override
{
// TODO: more checkings should be done?
if (input_image.empty())
{
return 0;
}
CV_CheckEQ(input_image.size(), Size(inputW, inputH), "Size does not match. Call setInputSize(size) if input size does not match the preset size");
Mat input_blob;
if(input_image.kind() == _InputArray::UMAT) {
// Pad input_image with divisor 32
UMat pad_image;
padWithDivisor(input_image, pad_image);
// Build blob from input image
input_blob = dnn::blobFromImage(pad_image);
} else {
// Pad input_image with divisor 32
Mat pad_image;
padWithDivisor(input_image, pad_image);
// Build blob from input image
input_blob = dnn::blobFromImage(pad_image);
}
// Forward
std::vector<String> output_names = { "cls_8", "cls_16", "cls_32", "obj_8", "obj_16", "obj_32", "bbox_8", "bbox_16", "bbox_32", "kps_8", "kps_16", "kps_32" };
std::vector<Mat> output_blobs;
net.setInput(input_blob);
net.forward(output_blobs, output_names);
// Post process
Mat results = postProcess(output_blobs);
results.convertTo(faces, CV_32FC1);
return 1;
}
private:
Mat postProcess(const std::vector<Mat>& output_blobs)
{
Mat faces;
for (size_t i = 0; i < strides.size(); ++i) {
int cols = int(padW / strides[i]);
int rows = int(padH / strides[i]);
// Extract from output_blobs
Mat cls = output_blobs[i];
Mat obj = output_blobs[i + strides.size() * 1];
Mat bbox = output_blobs[i + strides.size() * 2];
Mat kps = output_blobs[i + strides.size() * 3];
// Decode from predictions
float* cls_v = (float*)(cls.data);
float* obj_v = (float*)(obj.data);
float* bbox_v = (float*)(bbox.data);
float* kps_v = (float*)(kps.data);
// (tl_x, tl_y, w, h, re_x, re_y, le_x, le_y, nt_x, nt_y, rcm_x, rcm_y, lcm_x, lcm_y, score)
// 'tl': top left point of the bounding box
// 're': right eye, 'le': left eye
// 'nt': nose tip
// 'rcm': right corner of mouth, 'lcm': left corner of mouth
Mat face(1, 15, CV_32FC1);
for(int r = 0; r < rows; ++r) {
for(int c = 0; c < cols; ++c) {
size_t idx = r * cols + c;
// Get score
float cls_score = cls_v[idx];
float obj_score = obj_v[idx];
// Clamp
cls_score = MIN(cls_score, 1.f);
cls_score = MAX(cls_score, 0.f);
obj_score = MIN(obj_score, 1.f);
obj_score = MAX(obj_score, 0.f);
float score = std::sqrt(cls_score * obj_score);
face.at<float>(0, 14) = score;
// Checking if the score meets the threshold before adding the face
if (score < scoreThreshold)
continue;
// Get bounding box
float cx = ((c + bbox_v[idx * 4 + 0]) * strides[i]);
float cy = ((r + bbox_v[idx * 4 + 1]) * strides[i]);
float w = exp(bbox_v[idx * 4 + 2]) * strides[i];
float h = exp(bbox_v[idx * 4 + 3]) * strides[i];
float x1 = cx - w / 2.f;
float y1 = cy - h / 2.f;
face.at<float>(0, 0) = x1;
face.at<float>(0, 1) = y1;
face.at<float>(0, 2) = w;
face.at<float>(0, 3) = h;
// Get landmarks
for(int n = 0; n < 5; ++n) {
face.at<float>(0, 4 + 2 * n) = (kps_v[idx * 10 + 2 * n] + c) * strides[i];
face.at<float>(0, 4 + 2 * n + 1) = (kps_v[idx * 10 + 2 * n + 1]+ r) * strides[i];
}
faces.push_back(face);
}
}
}
if (faces.rows > 1)
{
// Retrieve boxes and scores
std::vector<Rect2i> faceBoxes;
std::vector<float> faceScores;
for (int rIdx = 0; rIdx < faces.rows; rIdx++)
{
faceBoxes.push_back(Rect2i(int(faces.at<float>(rIdx, 0)),
int(faces.at<float>(rIdx, 1)),
int(faces.at<float>(rIdx, 2)),
int(faces.at<float>(rIdx, 3))));
faceScores.push_back(faces.at<float>(rIdx, 14));
}
std::vector<int> keepIdx;
dnn::NMSBoxes(faceBoxes, faceScores, scoreThreshold, nmsThreshold, keepIdx, 1.f, topK);
// Get NMS results
Mat nms_faces;
for (int idx: keepIdx)
{
nms_faces.push_back(faces.row(idx));
}
return nms_faces;
}
else
{
return faces;
}
}
void padWithDivisor(InputArray input_image, OutputArray pad_image)
{
int bottom = padH - inputH;
int right = padW - inputW;
copyMakeBorder(input_image, pad_image, 0, bottom, 0, right, BORDER_CONSTANT, 0);
}
private:
dnn::Net net;
int inputW;
int inputH;
int padW;
int padH;
const int divisor;
int topK;
float scoreThreshold;
float nmsThreshold;
const std::vector<int> strides;
};
#endif
Ptr<FaceDetectorYN> FaceDetectorYN::create(const String& model,
const String& config,
const Size& input_size,
const float score_threshold,
const float nms_threshold,
const int top_k,
const int backend_id,
const int target_id)
{
#ifdef HAVE_OPENCV_DNN
return makePtr<FaceDetectorYNImpl>(model, config, input_size, score_threshold, nms_threshold, top_k, backend_id, target_id);
#else
CV_UNUSED(model); CV_UNUSED(config); CV_UNUSED(input_size); CV_UNUSED(score_threshold); CV_UNUSED(nms_threshold); CV_UNUSED(top_k); CV_UNUSED(backend_id); CV_UNUSED(target_id);
CV_Error(cv::Error::StsNotImplemented, "cv::FaceDetectorYN requires enabled 'dnn' module.");
#endif
}
Ptr<FaceDetectorYN> FaceDetectorYN::create(const String& framework,
const std::vector<uchar>& bufferModel,
const std::vector<uchar>& bufferConfig,
const Size& input_size,
const float score_threshold,
const float nms_threshold,
const int top_k,
const int backend_id,
const int target_id)
{
#ifdef HAVE_OPENCV_DNN
return makePtr<FaceDetectorYNImpl>(framework, bufferModel, bufferConfig, input_size, score_threshold, nms_threshold, top_k, backend_id, target_id);
#else
CV_UNUSED(framework); CV_UNUSED(bufferModel); CV_UNUSED(bufferConfig); CV_UNUSED(input_size); CV_UNUSED(score_threshold); CV_UNUSED(nms_threshold); CV_UNUSED(top_k); CV_UNUSED(backend_id); CV_UNUSED(target_id);
CV_Error(cv::Error::StsNotImplemented, "cv::FaceDetectorYN requires enabled 'dnn' module.");
#endif
}
} // namespace cv
+218
View File
@@ -0,0 +1,218 @@
// 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.hpp"
#ifdef HAVE_OPENCV_DNN
#include "opencv2/dnn.hpp"
#endif
#include <algorithm>
namespace cv
{
#ifdef HAVE_OPENCV_DNN
class FaceRecognizerSFImpl : public FaceRecognizerSF
{
public:
FaceRecognizerSFImpl(const String& model, const String& config, int backend_id, int target_id)
{
net = dnn::readNet(model, config);
CV_Assert(!net.empty());
net.setPreferableBackend(backend_id);
net.setPreferableTarget(target_id);
}
FaceRecognizerSFImpl(const String& framework,
const std::vector<uchar>& bufferModel,
const std::vector<uchar>& bufferConfig,
int backend_id, int target_id)
{
net = dnn::readNet(framework, bufferModel, bufferConfig);
CV_Assert(!net.empty());
net.setPreferableBackend(backend_id);
net.setPreferableTarget(target_id);
}
void alignCrop(InputArray _src_img, InputArray _face_mat, OutputArray _aligned_img) const override
{
Mat face_mat = _face_mat.getMat();
float src_point[5][2];
for (int row = 0; row < 5; ++row)
{
for(int col = 0; col < 2; ++col)
{
src_point[row][col] = face_mat.at<float>(0, row*2+col+4);
}
}
Mat warp_mat = getSimilarityTransformMatrix(src_point);
warpAffine(_src_img, _aligned_img, warp_mat, Size(112, 112), INTER_LINEAR);
}
void feature(InputArray _aligned_img, OutputArray _face_feature) override
{
Mat inputBolb = dnn::blobFromImage(_aligned_img, 1, Size(112, 112), Scalar(0, 0, 0), true, false);
net.setInput(inputBolb);
net.forward(_face_feature);
}
double match(InputArray _face_feature1, InputArray _face_feature2, int dis_type) const override
{
Mat face_feature1 = _face_feature1.getMat(), face_feature2 = _face_feature2.getMat();
normalize(face_feature1, face_feature1);
normalize(face_feature2, face_feature2);
if(dis_type == DisType::FR_COSINE){
return sum(face_feature1.mul(face_feature2))[0];
}else if(dis_type == DisType::FR_NORM_L2){
return norm(face_feature1, face_feature2);
}else{
throw std::invalid_argument("invalid parameter " + std::to_string(dis_type));
}
}
private:
Mat getSimilarityTransformMatrix(float src[5][2]) const {
float dst[5][2] = { {38.2946f, 51.6963f}, {73.5318f, 51.5014f}, {56.0252f, 71.7366f}, {41.5493f, 92.3655f}, {70.7299f, 92.2041f} };
float avg0 = (src[0][0] + src[1][0] + src[2][0] + src[3][0] + src[4][0]) / 5;
float avg1 = (src[0][1] + src[1][1] + src[2][1] + src[3][1] + src[4][1]) / 5;
//Compute mean of src and dst.
float src_mean[2] = { avg0, avg1 };
float dst_mean[2] = { 56.0262f, 71.9008f };
//Subtract mean from src and dst.
float src_demean[5][2];
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 5; j++)
{
src_demean[j][i] = src[j][i] - src_mean[i];
}
}
float dst_demean[5][2];
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 5; j++)
{
dst_demean[j][i] = dst[j][i] - dst_mean[i];
}
}
double A00 = 0.0, A01 = 0.0, A10 = 0.0, A11 = 0.0;
for (int i = 0; i < 5; i++)
A00 += dst_demean[i][0] * src_demean[i][0];
A00 = A00 / 5;
for (int i = 0; i < 5; i++)
A01 += dst_demean[i][0] * src_demean[i][1];
A01 = A01 / 5;
for (int i = 0; i < 5; i++)
A10 += dst_demean[i][1] * src_demean[i][0];
A10 = A10 / 5;
for (int i = 0; i < 5; i++)
A11 += dst_demean[i][1] * src_demean[i][1];
A11 = A11 / 5;
Mat A = (Mat_<double>(2, 2) << A00, A01, A10, A11);
double d[2] = { 1.0, 1.0 };
double detA = A00 * A11 - A01 * A10;
if (detA < 0)
d[1] = -1;
double T[3][3] = { {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0} };
Mat s, u, vt, v;
SVD::compute(A, s, u, vt);
double smax = s.ptr<double>(0)[0]>s.ptr<double>(1)[0] ? s.ptr<double>(0)[0] : s.ptr<double>(1)[0];
double tol = smax * 2 * FLT_MIN;
int rank = 0;
if (s.ptr<double>(0)[0]>tol)
rank += 1;
if (s.ptr<double>(1)[0]>tol)
rank += 1;
double arr_u[2][2] = { {u.ptr<double>(0)[0], u.ptr<double>(0)[1]}, {u.ptr<double>(1)[0], u.ptr<double>(1)[1]} };
double arr_vt[2][2] = { {vt.ptr<double>(0)[0], vt.ptr<double>(0)[1]}, {vt.ptr<double>(1)[0], vt.ptr<double>(1)[1]} };
double det_u = arr_u[0][0] * arr_u[1][1] - arr_u[0][1] * arr_u[1][0];
double det_vt = arr_vt[0][0] * arr_vt[1][1] - arr_vt[0][1] * arr_vt[1][0];
if (rank == 1)
{
if ((det_u*det_vt) > 0)
{
Mat uvt = u*vt;
T[0][0] = uvt.ptr<double>(0)[0];
T[0][1] = uvt.ptr<double>(0)[1];
T[1][0] = uvt.ptr<double>(1)[0];
T[1][1] = uvt.ptr<double>(1)[1];
}
else
{
double temp = d[1];
d[1] = -1;
Mat D = (Mat_<double>(2, 2) << d[0], 0.0, 0.0, d[1]);
Mat Dvt = D*vt;
Mat uDvt = u*Dvt;
T[0][0] = uDvt.ptr<double>(0)[0];
T[0][1] = uDvt.ptr<double>(0)[1];
T[1][0] = uDvt.ptr<double>(1)[0];
T[1][1] = uDvt.ptr<double>(1)[1];
d[1] = temp;
}
}
else
{
Mat D = (Mat_<double>(2, 2) << d[0], 0.0, 0.0, d[1]);
Mat Dvt = D*vt;
Mat uDvt = u*Dvt;
T[0][0] = uDvt.ptr<double>(0)[0];
T[0][1] = uDvt.ptr<double>(0)[1];
T[1][0] = uDvt.ptr<double>(1)[0];
T[1][1] = uDvt.ptr<double>(1)[1];
}
double var1 = 0.0;
for (int i = 0; i < 5; i++)
var1 += src_demean[i][0] * src_demean[i][0];
var1 = var1 / 5;
double var2 = 0.0;
for (int i = 0; i < 5; i++)
var2 += src_demean[i][1] * src_demean[i][1];
var2 = var2 / 5;
double scale = 1.0 / (var1 + var2)* (s.ptr<double>(0)[0] * d[0] + s.ptr<double>(1)[0] * d[1]);
double TS[2];
TS[0] = T[0][0] * src_mean[0] + T[0][1] * src_mean[1];
TS[1] = T[1][0] * src_mean[0] + T[1][1] * src_mean[1];
T[0][2] = dst_mean[0] - scale*TS[0];
T[1][2] = dst_mean[1] - scale*TS[1];
T[0][0] *= scale;
T[0][1] *= scale;
T[1][0] *= scale;
T[1][1] *= scale;
Mat transform_mat = (Mat_<double>(2, 3) << T[0][0], T[0][1], T[0][2], T[1][0], T[1][1], T[1][2]);
return transform_mat;
}
private:
dnn::Net net;
};
#endif
Ptr<FaceRecognizerSF> FaceRecognizerSF::create(const String& model, const String& config, int backend_id, int target_id)
{
#ifdef HAVE_OPENCV_DNN
return makePtr<FaceRecognizerSFImpl>(model, config, backend_id, target_id);
#else
CV_UNUSED(model); CV_UNUSED(config); CV_UNUSED(backend_id); CV_UNUSED(target_id);
CV_Error(cv::Error::StsNotImplemented, "cv::FaceRecognizerSF requires enabled 'dnn' module");
#endif
}
Ptr<FaceRecognizerSF> FaceRecognizerSF::create(const String& framework,
const std::vector<uchar>& bufferModel,
const std::vector<uchar>& bufferConfig,
int backend_id, int target_id)
{
#ifdef HAVE_OPENCV_DNN
return makePtr<FaceRecognizerSFImpl>(framework, bufferModel, bufferConfig, backend_id, target_id);
#else
CV_UNUSED(framework); CV_UNUSED(bufferModel); CV_UNUSED(bufferConfig); CV_UNUSED(backend_id); CV_UNUSED(target_id);
CV_Error(cv::Error::StsNotImplemented, "cv::FaceRecognizerSF requires enabled 'dnn' module");
#endif
}
} // namespace cv
@@ -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
#include "precomp.hpp"
#include "opencv2/objdetect/graphical_code_detector.hpp"
#include "graphical_code_detector_impl.hpp"
namespace cv {
GraphicalCodeDetector::GraphicalCodeDetector() {}
bool GraphicalCodeDetector::detect(InputArray img, OutputArray points) const {
CV_Assert(p);
return p->detect(img, points);
}
std::string GraphicalCodeDetector::decode(InputArray img, InputArray points, OutputArray straight_code) const {
CV_Assert(p);
return p->decode(img, points, straight_code);
}
std::string GraphicalCodeDetector::detectAndDecode(InputArray img, OutputArray points, OutputArray straight_code) const {
CV_Assert(p);
return p->detectAndDecode(img, points, straight_code);
}
bool GraphicalCodeDetector::detectMulti(InputArray img, OutputArray points) const {
CV_Assert(p);
return p->detectMulti(img, points);
}
bool GraphicalCodeDetector::decodeMulti(InputArray img, InputArray points, std::vector<std::string>& decoded_info,
OutputArrayOfArrays straight_code) const {
CV_Assert(p);
return p->decodeMulti(img, points, decoded_info, straight_code);
}
bool GraphicalCodeDetector::detectAndDecodeMulti(InputArray img, std::vector<std::string>& decoded_info, OutputArray points,
OutputArrayOfArrays straight_code) const {
CV_Assert(p);
return p->detectAndDecodeMulti(img, decoded_info, points, straight_code);
}
}
@@ -0,0 +1,40 @@
// 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_GRAPHICAL_CODE_DETECTOR_IMPL_HPP
#define OPENCV_OBJDETECT_GRAPHICAL_CODE_DETECTOR_IMPL_HPP
#include <opencv2/core.hpp>
namespace cv {
struct GraphicalCodeDetector::Impl {
virtual ~Impl() {}
virtual bool detect(InputArray img, OutputArray points) const = 0;
virtual std::string decode(InputArray img, InputArray points, OutputArray straight_code) const = 0;
virtual std::string detectAndDecode(InputArray img, OutputArray points, OutputArray straight_code) const = 0;
virtual bool detectMulti(InputArray img, OutputArray points) const = 0;
virtual bool decodeMulti(InputArray img, InputArray points, std::vector<std::string>& decoded_info,
OutputArrayOfArrays straight_code) const = 0;
virtual bool detectAndDecodeMulti(InputArray img, std::vector<std::string>& decoded_info,
OutputArray points, OutputArrayOfArrays straight_code) const = 0;
};
class QRCodeDecoder {
public:
virtual ~QRCodeDecoder();
static Ptr<QRCodeDecoder> create();
virtual bool decode(const Mat& straight, String& decoded_info) = 0;
QRCodeEncoder::EncodeMode mode;
QRCodeEncoder::ECIEncodings eci;
uint8_t parity = 0;
uint8_t sequence_num = 0;
uint8_t total_num = 1;
};
}
#endif
+52
View File
@@ -0,0 +1,52 @@
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
// Copyright (C) 2015, Itseez Inc., all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
//
// Library initialization file
//
#include "precomp.hpp"
IPP_INITIALIZER_AUTO
/* End of file. */
+180
View File
@@ -0,0 +1,180 @@
// 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.
/*
* MIT License
*
* Copyright (c) 2018 Pedro Diamel Marrero Fernández
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "precomp.hpp"
#include "bound_min.hpp"
namespace cv
{
namespace mcc
{
CBoundMin::CBoundMin()
{
}
CBoundMin::~CBoundMin()
{
}
void CBoundMin::calculate()
{
corners.clear();
size_t N = chart.size();
if (!N)
return;
std::vector<Point2f> X(4 * N);
for (size_t i = 0; i < N; i++)
{
mcc::CChart cc = chart[i];
for (size_t j = 0; j < 4; j++)
{
X[i * 4 + j] = cc.corners[j];
}
}
// media
Point2f mu(0, 0);
for (size_t i = 0; i < 4 * N; i++)
mu += X[i];
mu /= (4 * (int)N);
for (size_t i = 0; i < 4 * N; i++)
X[i] -= mu;
// calculate all line
std::vector<Point3f> L;
L.resize(4 * N);
for (size_t i = 0; i < N; i++)
{
Point3f v0, v1, v2, v3;
v0.x = X[4 * i + 0].x;
v0.y = X[4 * i + 0].y;
v0.z = 1;
v1.x = X[4 * i + 1].x;
v1.y = X[4 * i + 1].y;
v1.z = 1;
v2.x = X[4 * i + 2].x;
v2.y = X[4 * i + 2].y;
v2.z = 1;
v3.x = X[4 * i + 3].x;
v3.y = X[4 * i + 3].y;
v3.z = 1;
L[4 * i + 0] = v0.cross(v1);
L[4 * i + 1] = v1.cross(v2);
L[4 * i + 2] = v2.cross(v3);
L[4 * i + 3] = v3.cross(v0);
}
// line convex hull
std::vector<int> dist;
dist.resize(4 * N);
Point2f n;
float d;
for (size_t i = 0; i < 4 * N; i++)
{
n.x = L[i].x;
n.y = L[i].y;
d = L[i].z;
int s = 0;
for (size_t j = 0; j < N; j++)
s += (X[j].dot(n) + d) <= 0;
dist[i] = s;
}
// sort
std::vector<int> idx;
std::vector<Point3f> Ls;
Ls.resize(4 * N);
mcc::sort(dist, idx);
for (size_t i = 0; i < 4 * N; i++)
Ls[i] = L[idx[i]];
std::vector<Point3f> Lc;
Lc.resize(4 * N);
Lc[0] = Ls[0];
Point3f ln;
int j, k = 0;
for (size_t i = 0; i < 4 * N; i++)
{
ln = Ls[i]; //current line
if (!validateLine(Lc, ln, k, j))
{
Lc[k] = ln;
k++;
}
else if ((abs(Lc[j].z) < abs(ln.z)) && (abs(dist[i] - dist[j]) < 2))
{
Lc[j] = ln;
}
if (k == 4 && abs(dist[i] - dist[k - 1]) > 2)
break;
}
if (k < 4)
return;
std::vector<float> thetas;
thetas.resize(4);
for (size_t i = 0; i < 4; i++)
thetas[i] = atan2(Lc[i].y / Lc[i].z, Lc[i].x / Lc[i].z);
sort(thetas, idx, false);
std::vector<Point3f> lines;
lines.resize(4);
for (size_t i = 0; i < 4; i++)
lines[i] = Lc[idx[i]];
Point3f Vcart;
Point2f Vhom;
std::vector<Point2f> V;
V.resize(4);
for (size_t i = 0; i < 4; i++)
{
j = (i + 1) % 4;
Vcart = lines[i].cross(lines[j]);
if (fabs(Vcart.z) <= 1e-6){
continue;
}
Vhom.x = Vcart.x / Vcart.z;
Vhom.y = Vcart.y / Vcart.z;
V[i] = Vhom + mu;
}
corners = V;
}
} // namespace mcc
} // namespace cv
+83
View File
@@ -0,0 +1,83 @@
// 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.
/*
* MIT License
*
* Copyright (c) 2018 Pedro Diamel Marrero Fernández
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _MCC_BOUND_MIN_HPP
#define _MCC_BOUND_MIN_HPP
#include "charts.hpp"
namespace cv
{
namespace mcc
{
class CBoundMin
{
public:
CBoundMin();
~CBoundMin();
void setCharts(const std::vector<CChart> &chartIn) { chart = chartIn; }
void getCorners(std::vector<Point2f> &cornersOut) { cornersOut = corners; }
void calculate();
private:
std::vector<CChart> chart;
std::vector<Point2f> corners;
private:
bool validateLine(const std::vector<Point3f> &Lc, Point3f ln,
int k, int &j)
{
double theta;
Point2d v0, v1;
for (j = 0; j < k; j++)
{
v0.x = Lc[j].x;
v0.y = Lc[j].y;
v1.x = ln.x;
v1.y = ln.y;
theta = v0.dot(v1) / (norm(v0) * norm(v1));
theta = acos(theta);
if (theta < 0.5)
return true;
}
return false;
}
};
} // namespace mcc
} // namespace cv
#endif //_MCC_BOUND_MIN_HPP
+96
View File
@@ -0,0 +1,96 @@
// 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.
/*
* MIT License
*
* Copyright (c) 2018 Pedro Diamel Marrero Fernández
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "precomp.hpp"
#include "charts.hpp"
namespace cv
{
namespace mcc
{
CChart::CChart()
: perimetro(0), area(0), large_side(0)
{
}
CChart::~CChart()
{
}
void CChart::
setCorners(std::vector<Point2f> p)
{
Point v1, v2;
if (p.empty())
return;
// copy
corners = p;
// Sort the corners in anti-clockwise order
polyanticlockwise(corners);
// Properties
area = contourArea(corners);
perimetro = perimeter(corners);
center = mace_center(corners);
v1 = corners[2] - corners[0];
v2 = corners[3] - corners[1];
large_side = std::max(norm(v1), norm(v2));
}
//////////////////////////////////////////////////////////////////////////////////////////////
CChartDraw::
CChartDraw(CChart &pChart, InputOutputArray image)
: m_pChart(pChart), m_image(image.getMat())
{
}
void CChartDraw::
drawContour(Scalar color /*= CV_RGB(0, 250, 0)*/) const
{
//Draw lines
int thickness = 2;
line(m_image, (m_pChart).corners[0], (m_pChart).corners[1], color, thickness, LINE_AA);
line(m_image, (m_pChart).corners[1], (m_pChart).corners[2], color, thickness, LINE_AA);
line(m_image, (m_pChart).corners[2], (m_pChart).corners[3], color, thickness, LINE_AA);
line(m_image, (m_pChart).corners[3], (m_pChart).corners[0], color, thickness, LINE_AA);
}
void CChartDraw::
drawCenter(Scalar color /*= CV_RGB(0, 0, 255)*/) const
{
int radius = 3;
int thickness = 2;
circle(m_image, (m_pChart).center, radius, color, thickness);
}
} // namespace mcc
} // namespace cv
+90
View File
@@ -0,0 +1,90 @@
// 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.
/*
* MIT License
*
* Copyright (c) 2018 Pedro Diamel Marrero Fernández
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _MCC_CHARTS_HPP
#define _MCC_CHARTS_HPP
namespace cv
{
namespace mcc
{
/** @brief Chart model
*
* .--------. <- px,py
* | | one chart for checker color
* | RGB |
* | Lab |
* | |
* .--------.
*
* @author Pedro Marrero Fernndez
*/
class CChart
{
public:
CChart();
~CChart();
/** @brief set corners
*@param p[in] new corners
*/
void setCorners(std::vector<Point2f> p);
public:
std::vector<Point2f> corners;
Point2f center;
double perimetro;
double area;
double large_side;
};
/** @brief Chart draw */
class CChartDraw
{
public:
/** @brief contructor */
CChartDraw(CChart &pChart, InputOutputArray image);
/** @brief draw the chart contour over the image */
void drawContour(Scalar color = CV_RGB(0, 250, 0)) const;
/** @brief draw the chart center over the image */
void drawCenter(Scalar color = CV_RGB(0, 0, 255)) const;
private:
CChart &m_pChart;
Mat m_image;
};
} // namespace mcc
} // namespace cv
#endif //_MCC_CHARTS_HPP
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,216 @@
// 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.
/*
* MIT License
*
* Copyright (c) 2018 Pedro Diamel Marrero Fernández
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _MCC_CHECKER_DETECTOR_HPP
#define _MCC_CHECKER_DETECTOR_HPP
#include "opencv2/objdetect.hpp"
#include "charts.hpp"
namespace cv
{
namespace mcc
{
class CCheckerDetectorImpl : public CCheckerDetector
{
typedef std::vector<Point> PointsVector;
typedef std::vector<PointsVector> ContoursVector;
public:
CCheckerDetectorImpl();
#ifdef HAVE_OPENCV_DNN
CCheckerDetectorImpl(const dnn::Net& _net){
net = _net;
}
#endif
virtual ~CCheckerDetectorImpl();
bool process(InputArray image, const std::vector<Rect> &regionsOfInterest,
const int nc = 1) CV_OVERRIDE;
bool process(InputArray image, const int nc = 1) CV_OVERRIDE;
Ptr<CChecker> getBestColorChecker() CV_OVERRIDE
{
if (m_checkers.size())
return m_checkers[0];
return nullptr;
}
std::vector<Ptr<CChecker>> getListColorChecker() CV_OVERRIDE
{
return m_checkers;
}
virtual Mat getRefColors() CV_OVERRIDE;
virtual void setDetectionParams(const DetectorParametersMCC &params) CV_OVERRIDE;
virtual void setColorChartType(ColorChart chartType) CV_OVERRIDE;
#ifdef HAVE_OPENCV_DNN
virtual void setUseDnnModel(bool useDnn) CV_OVERRIDE;
virtual bool getUseDnnModel() const CV_OVERRIDE;
#endif
virtual const DetectorParametersMCC& getDetectionParams() const CV_OVERRIDE;
virtual ColorChart getColorChartType() const CV_OVERRIDE;
virtual void draw(std::vector<Ptr<CChecker>>& checkers, InputOutputArray img, const Scalar color = CV_RGB(0,250,0), const int thickness = 2) CV_OVERRIDE;
protected: // methods pipeline
bool _no_net_process(InputArray image, const int nc, std::vector<Rect> regionsOfInterest);
/// prepareImage
/** @brief Prepare Image
* @param bgrMat image in color space BGR
* @param grayOut gray scale
* @param bgrOut rescale image
* @param aspOut aspect ratio
* @param max_size rescale factor in max dim
*/
virtual void
prepareImage(InputArray bgr, OutputArray grayOut, OutputArray bgrOut, float &aspOut) const;
/// performThreshold
/** @brief Adaptative threshold
* @param grayscaleImg gray scale image
* @param thresholdImg binary image
* @param wndx, wndy windows size
* @param step
*/
virtual void
performThreshold(InputArray grayscaleImg, OutputArrayOfArrays thresholdImg) const;
/// findContours
/** @brief find contour in the image
* @param srcImg binary imagen
* @param contours
* @param minContourPointsAllowed
*/
virtual void
findContours(InputArray srcImg, ContoursVector &contours) const;
/// findCandidates
/** @brief find posibel candidates
* @param contours
* @param detectedCharts
* @param minContourLengthAllowed
*/
virtual void
findCandidates(const ContoursVector &contours, std::vector<CChart> &detectedCharts);
/// clustersAnalysis
/** @brief clusters charts analysis
* @param detectedCharts
* @param groups
*/
virtual void
clustersAnalysis(const std::vector<CChart> &detectedCharts, std::vector<int> &groups);
/// checkerRecognize
/** @brief checker color recognition
* @param img
* @param detectedCharts
* @param G
* @param colorChartsOut
*/
virtual void
checkerRecognize(InputArray img, const std::vector<CChart> &detectedCharts, const std::vector<int> &G,
std::vector<std::vector<Point2f>> &colorChartsOut);
/// checkerAnalysis
/** @brief evaluate checker
* @param img
* @param img_org
* @param colorCharts
* @param checker
* @param asp
*/
virtual void
checkerAnalysis(InputArray img_rgb_f, const unsigned int nc,
const std::vector<std::vector<Point2f>> &colorCharts,
std::vector<Ptr<CChecker>> &checkers, float asp,
const Mat &img_rgb_org,
const Mat &img_ycbcr_org,
std::vector<Mat> &rgb_planes,
std::vector<Mat> &ycbcr_planes,
const Point2f& offset);
virtual void
removeTooCloseDetections();
protected:
std::vector<Ptr<CChecker>> m_checkers;
#ifdef HAVE_OPENCV_DNN
dnn::Net net;
bool m_useDnn = true;
#else
bool m_useDnn = false;
#endif
DetectorParametersMCC m_params = DetectorParametersMCC();
ColorChart m_chartType;
private: // methods aux
void get_subbox_chart_physical(
const std::vector<Point2f> &points,
std::vector<Point2f> &chartPhy);
void reduce_array(
const std::vector<float> &x,
std::vector<float> &x_new,
float tol);
void transform_points_inverse(
InputArray T,
const std::vector<Point2f> &X,
std::vector<Point2f> &Xt);
void get_profile(
const std::vector<Point2f> &ibox,
OutputArray charts_rgb,
OutputArray charts_ycbcr,
InputArray im_rgb,
InputArray im_ycbcr,
std::vector<Mat> &rgb_planes,
std::vector<Mat> &ycbcr_planes);
/** @brief cost function
* e(p) = ||f(p)||^2 = \sum_k (mu_{k,p}*r_k')/||mu_{k,p}||||r_k|| + ...
* + \sum_k || \sigma_{k,p} ||^2
*/
float cost_function(InputArray img, InputOutputArray mask, InputArray lab,
const std::vector<Point2f> &ibox);
};
} // namespace mcc
} // namespace cv
#endif //_MCC_CHECKER_DETECTOR_HPP
+487
View File
@@ -0,0 +1,487 @@
// 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.
/*
* MIT License
*
* Copyright (c) 2018 Pedro Diamel Marrero Fernández
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "precomp.hpp"
#include "checker_model.hpp"
#include "dictionary.hpp"
namespace cv
{
namespace mcc
{
///////////////////////////////////////////////////////////////////////////////
/// CChartModel
CChartModel::CChartModel(const ColorChart chartType)
{
switch (chartType)
{
case MCC24: //Standard
size = Size2i(4, 6);
boxsize = Size2f(11.25, 16.75);
box.resize(4);
box[0] = Point2f(0.00, 0.00);
box[1] = Point2f(16.75, 0.00);
box[2] = Point2f(16.75, 11.25);
box[3] = Point2f(0.00, 11.25);
cellchart.assign(CChartClassicModelCellchart, CChartClassicModelCellchart + 4 * 24);
center.assign(CChartClassicModelCenter, CChartClassicModelCenter + 24);
chart.resize(size.area(), std::vector<float>(9));
for(int color = 0 ; color < (int)chart.size() ; color++)
chart[color].assign(CChartClassicModelColors[color] , CChartClassicModelColors[color] + 9 );
break;
case SG140: //DigitalSG
size = Size2i(10, 14);
boxsize = Size2f(27.75, 38.75);
box.resize(4);
box[0] = Point2f(0.00, 0.00);
box[1] = Point2f(38.75, 0.00);
box[2] = Point2f(38.75, 27.75);
box[3] = Point2f(0.00, 27.75);
cellchart.assign(CChartDigitalSGCellchart, CChartDigitalSGCellchart + 4 * 140);
center.assign(CChartDigitalSGCenter, CChartDigitalSGCenter + 140);
chart.resize(size.area(), std::vector<float>(9));
for(int color = 0 ; color < (int)chart.size() ; color++)
chart[color].assign(CChartDigitalSGColors[color] , CChartDigitalSGColors[color] + 9 );
break;
case VINYL18: //Vinyl
size = Size2i(3, 6);
boxsize = Size2f(12.50, 18.25);
box.resize(4);
box[0] = Point2f(0.00, 0.00);
box[1] = Point2f(18.25, 0.00);
box[2] = Point2f(18.25, 12.50);
box[3] = Point2f(0.00, 12.50);
cellchart.assign(CChartVinylCellchart, CChartVinylCellchart + 4 * 18);
center.assign(CChartVinylCenter, CChartVinylCenter + 18);
chart.resize(size.area(), std::vector<float>(9));
for(int color = 0 ; color < (int)chart.size() ; color++)
chart[color].assign(CChartVinylColors[color] , CChartVinylColors[color] + 9 );
break;
}
}
CChartModel::~CChartModel()
{
}
bool CChartModel::
evaluate(const SUBCCMModel &subModel, int &offset, int &iTheta, float &error)
{
float tError;
int tTheta, tOffset;
error = INFINITY;
bool beval = false;
// para todas las orientaciones
// min_{ theta,dt } | CC_e - CC |
for (tTheta = 0; tTheta < 8; tTheta++)
{
if (match(subModel, tTheta, tError, tOffset) && tError < error)
{
error = tError;
iTheta = tTheta;
offset = tOffset;
beval = true;
}
}
return beval;
}
void CChartModel::
copyToColorMat(OutputArray lab, int cs)
{
size_t N, M, k;
N = size.width;
M = size.height;
Mat im_lab_org((int)N, (int)M, CV_32FC3);
int type_color = 3 * cs;
k = 0;
for (size_t i = 0; i < N; i++)
{
for (size_t j = 0; j < M; j++)
{
Vec3f &lab_values = im_lab_org.at<Vec3f>((int)i, (int)j);
lab_values[0] = chart[k][type_color + 0];
lab_values[1] = chart[k][type_color + 1];
lab_values[2] = chart[k][type_color + 2];
k++;
}
}
lab.assign(im_lab_org);
}
void mcc::CChartModel::
rotate90()
{
size = Size2i(size.height, size.width);
//the matrix is roated clockwise 90 degree, so first row will become last column, second row second last column and so on
//doing this inplace will make the code a bit hard to read, so creating a temporary array
std::vector<Point2f> _cellchart(cellchart.size());
std::vector<Point2f> _center(center.size());
int k = 0;
for (int i = 0; i < size.width; i++)
{
for (int j = 0; j < size.height; j++)
{
//k contains the new coordintes,
int old_i = size.height - j - 1;
int old_j = i;
int old_k = (old_i)*size.width + old_j;
_cellchart[4 * k + 0] = cellchart[4 * old_k + 3];
_cellchart[4 * k + 1] = cellchart[4 * old_k + 0];
_cellchart[4 * k + 2] = cellchart[4 * old_k + 1];
_cellchart[4 * k + 3] = cellchart[4 * old_k + 2];
// center
_center[k] = center[old_k];
k++;
}
}
cellchart = _cellchart;
center = _center;
boxsize = Size2f(boxsize.height, boxsize.width);
}
void mcc::CChartModel::
flip()
{
std::vector<Point2f> _cellchart(cellchart.size());
std::vector<Point2f> _center(center.size());
int k = 0;
for (int i = 0; i < size.width; i++)
{
for (int j = 0; j < size.height; j++)
{
//k contains the new coordintes,
int old_i = i;
int old_j = size.height - j - 1;
int old_k = (old_i)*size.height + old_j;
_cellchart[4 * k + 0] = cellchart[4 * old_k + 1];
_cellchart[4 * k + 1] = cellchart[4 * old_k + 0];
_cellchart[4 * k + 2] = cellchart[4 * old_k + 3];
_cellchart[4 * k + 3] = cellchart[4 * old_k + 2];
// center
_center[k] = center[old_k];
k++;
}
}
cellchart = _cellchart;
center = _center;
}
float CChartModel::
dist_color_lab(InputArray lab1, InputArray lab2)
{
int N = lab1.rows();
float dist = 0, dist_i;
Mat _lab1 = lab1.getMat(), _lab2 = lab2.getMat();
for (int i = 0; i < N; i++)
{
Vec3f v1 = _lab1.at<Vec3f>(i, 0);
Vec3f v2 = _lab2.at<Vec3f>(i, 0);
// v1[0] = 1;
// v2[0] = 1; // L <- 0
// euclidean
Vec3f v = v1 - v2;
dist_i = v.dot(v);
dist += sqrt(dist_i);
// cosine
//float costh = v1.dot(v2) / (norm(v1)*norm(v2));
//dist += 1 - (1 + costh) / 2;
}
dist /= N;
return dist;
}
bool CChartModel::
match(const SUBCCMModel &subModel, int iTheta, float &error, int &ierror)
{
size_t N, M, k;
N = size.width;
M = size.height;
Mat im_lab_org((int)N, (int)M, CV_32FC3);
int type_color = 3;
k = 0;
for (size_t i = 0; i < N; i++)
{
for (size_t j = 0; j < M; j++)
{
Vec3f &lab = im_lab_org.at<Vec3f>((int)i, (int)j);
lab[0] = chart[k][type_color + 0];
lab[1] = chart[k][type_color + 1];
lab[2] = chart[k][type_color + 2];
k++;
}
}
rot90(im_lab_org, iTheta);
N = im_lab_org.rows;
M = im_lab_org.cols;
size_t n, m;
n = subModel.color_size.height;
m = subModel.color_size.width;
// boundary condition
if (N < n || M < m)
return false;
// rgb to La*b*
Mat rgb_est = subModel.sub_chart;
Mat lab_est;
cvtColor(rgb_est, lab_est, COLOR_BGR2Lab);
size_t nN, mM;
nN = N - n + 1;
mM = M - m + 1;
std::vector<float> lEcm(nN * mM);
k = 0;
for (size_t i = 0; i < nN; i++)
{
for (size_t j = 0; j < mM; j++)
{
Mat lab_curr, lab_roi;
lab_roi = im_lab_org(Rect((int)j, (int)i, (int)m, (int)n));
lab_roi.copyTo(lab_curr);
lab_curr = lab_curr.t();
lab_curr = lab_curr.reshape(3, (int)n * (int)m);
// Mean squared error
// ECM = 1 / N sum_i(Y - Yp) ^ 2
lEcm[k] = dist_color_lab(lab_curr, lab_est) / (M * N);
k++;
}
}
// minimo
error = lEcm[0];
ierror = 0;
for (int i = 1; i < (int)lEcm.size(); i++)
if (error > lEcm[i])
{
error = lEcm[i];
ierror = i;
}
return true;
}
void CChartModel::
rot90(InputOutputArray mat, int itheta)
{
//1=CW, 2=CCW, 3=180
switch (itheta)
{
case 1: //transpose+flip(1)=CW
transpose(mat, mat);
cv::flip(mat, mat, 1);
break;
case 2: //flip(-1)=180
cv::flip(mat, mat, -1);
break;
case 3: //transpose+flip(0)=CCW
transpose(mat, mat);
cv::flip(mat, mat, 0);
break;
//flipped images start here
case 4: //flip(1)=no rotation, just flipped
cv::flip(mat, mat, 1);
break;
case 5: //flip(1)+transpose + flip(1)=CW
cv::flip(mat, mat, 1);
transpose(mat, mat);
cv::flip(mat, mat, 1);
break;
case 6: //flip(1)+flip(-1)=180
cv::flip(mat, mat, 1);
cv::flip(mat, mat, -1);
break;
case 7: //flip(1)+transpose+flip(0)=CCW
cv::flip(mat, mat, 1);
transpose(mat, mat);
cv::flip(mat, mat, 0);
break;
}
}
//////////////////////////////////////////////////////////////////////////////////////////////
// // CChecker
Ptr<CChecker> CChecker::create()
{
return makePtr<CCheckerImpl>();
}
void CCheckerImpl::setTarget(ColorChart _target)
{
target = _target;
}
void CCheckerImpl::setBox(std::vector<Point2f> _box)
{
box = _box;
}
void CCheckerImpl::setChartsRGB(Mat _chartsRGB)
{
chartsRGB = _chartsRGB;
}
void CCheckerImpl::setChartsYCbCr(Mat _chartsYCbCr)
{
chartsYCbCr = _chartsYCbCr;
}
void CCheckerImpl::setCost(float _cost)
{
cost = _cost;
}
void CCheckerImpl::setCenter(Point2f _center)
{
center = _center;
}
ColorChart CCheckerImpl::getTarget()
{
return target;
}
std::vector<Point2f> CCheckerImpl::getBox()
{
return box;
}
std::vector<Point2f> CCheckerImpl::getColorCharts()
{
// color chart classic model
CChartModel cccm(getTarget());
Mat lab;
size_t N;
std::vector<Point2f> fbox = cccm.box;
std::vector<Point2f> cellchart = cccm.cellchart;
std::vector<Point2f> charts(cellchart.size());
// tranformation
Matx33f ccT = getPerspectiveTransform(fbox, getBox());
std::vector<Point2f> bch(4), bcht(4);
N = cellchart.size() / 4;
for (size_t i = 0, k; i < N; i++)
{
k = 4 * i;
for (size_t j = 0ull; j < 4ull; j++)
bch[j] = cellchart[k + j];
polyanticlockwise(bch);
transform_points_forward(ccT, bch, bcht);
Point2f c(0, 0);
for (size_t j = 0; j < 4; j++)
c += bcht[j];
c /= 4;
for (size_t j = 0ull; j < 4ull; j++)
charts[k+j] = ((bcht[j] - c) * 0.50) + c;
}
return charts;
}
Mat CCheckerImpl::getChartsRGB(bool getStats /* = true */)
{
if (!getStats){
Mat src = chartsRGB.col(1).clone().reshape(3, chartsRGB.rows/3);
return src;
}
return chartsRGB;
}
Mat CCheckerImpl::getChartsYCbCr()
{
return chartsYCbCr;
}
float CCheckerImpl::getCost()
{
return cost;
}
Point2f CCheckerImpl::getCenter()
{
return center;
}
void transform_points_forward(const Matx33f& T, const std::vector<Point2f> &X, std::vector<Point2f> &Xt)
{
size_t N = X.size();
if (Xt.size() != N)
Xt.resize(N);
std::fill(Xt.begin(), Xt.end(), Point2f(0.f, 0.f));
if (N == 0)
return;
Matx31f p, xt;
Point2f pt;
for (size_t i = 0; i < N; i++)
{
p(0, 0) = X[i].x;
p(1, 0) = X[i].y;
p(2, 0) = 1;
xt = T * p;
pt.x = xt(0, 0) / xt(2, 0);
pt.y = xt(1, 0) / xt(2, 0);
Xt[i] = pt;
}
}
} // namespace mcc
} // namespace cv
+161
View File
@@ -0,0 +1,161 @@
// 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.
/*
* MIT License
*
* Copyright (c) 2018 Pedro Diamel Marrero Fernández
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _MCC_CHECKER_MODEL_HPP
#define _MCC_CHECKER_MODEL_HPP
namespace cv
{
namespace mcc
{
//! @addtogroup mcc
//! @{
/** CChartModel
*
* @brief Class for handing the different chart models.
*
* This class contains the variables and functions which are specific
* to a given chart type and will be used for it detections.
*/
class CChartModel
{
public:
/** SUBCCMModel
*
* @brief Information about a continuous subregion of the chart.
*
* Usually not all the cells of the chart can be detected by the
* detector. This submodel contains the information about the
* detected squares.
*/
typedef struct
_SUBCCMModel
{
Mat sub_chart;
Size2i color_size;
std::vector<Point2f> centers;
} SUBCCMModel;
public:
CChartModel(const ColorChart chartType);
~CChartModel();
/** @brief evaluate submodel in this checker type*/
bool evaluate(const SUBCCMModel &subModel, int &offset, int &iTheta, float &error);
// function utils
void copyToColorMat(OutputArray lab, int cs = 0);
void rotate90();
void flip();
public:
// Cie L*a*b* values use illuminant D50 2 degree observer sRGB values for
// for iluminante D65.
Size2i size;
Size2f boxsize;
std::vector<Point2f> box;
std::vector<Point2f> cellchart;
std::vector<Point2f> center;
std::vector<std::vector<float>> chart;
protected:
/** \brief match checker color
* \param[in] subModel sub-checker
* \param[in] iTheta angle
* \param[out] error
* \param[out] ierror
* \return state
*/
bool match(const SUBCCMModel &subModel, int iTheta, float &error, int &ierror);
/** \brief euclidian dist L2 for Lab space
* \note
* \f$ \sum_i \sqrt (\sum_k (ab1-ab2)_k.^2) \f$
* \param[in] lab1
* \param[in] lab2
* \return distance
*/
float dist_color_lab(InputArray lab1, InputArray lab2);
/** \brief rotate matrix 90 degree */
void rot90(InputOutputArray mat, int itheta);
};
/** CChecker
*
* \brief checker model
* \author Pedro Marrero Fernandez
*
*/
class CCheckerImpl : public CChecker
{
public:
public:
CCheckerImpl() {}
~CCheckerImpl() {}
void setTarget(ColorChart _target) CV_OVERRIDE;
void setBox(std::vector<Point2f> _box) CV_OVERRIDE;
void setChartsRGB(Mat _chartsRGB) CV_OVERRIDE;
void setChartsYCbCr(Mat _chartsYCbCr) CV_OVERRIDE;
void setCost(float _cost) CV_OVERRIDE;
void setCenter(Point2f _center) CV_OVERRIDE;
ColorChart getTarget() CV_OVERRIDE;
std::vector<Point2f> getBox() CV_OVERRIDE;
std::vector<Point2f> getColorCharts() CV_OVERRIDE;
Mat getChartsRGB(bool getStats = true) CV_OVERRIDE;
Mat getChartsYCbCr() CV_OVERRIDE;
float getCost() CV_OVERRIDE;
Point2f getCenter() CV_OVERRIDE;
private:
ColorChart target; ///< type of checkercolor
std::vector<Point2f> box; ///< positions of the corners
Mat chartsRGB; ///< charts profile in rgb color space
Mat chartsYCbCr; ///< charts profile in YCbCr color space
float cost; ///< cost to aproximate
Point2f center; ///< center of the chart.
};
// @}
void transform_points_forward(const Matx33f& T, const std::vector<Point2f> &X, std::vector<Point2f> &Xt);
} // namespace mcc
} // namespace cv
#endif //_MCC_CHECKER_MODEL_HPP
+111
View File
@@ -0,0 +1,111 @@
// 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.
/*
* MIT License
*
* Copyright (c) 2018 Pedro Diamel Marrero Fernández
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "precomp.hpp"
#include "common.hpp"
namespace cv
{
namespace mcc
{
Rect poly2mask(const std::vector<Point2f> &poly, Size size, InputOutputArray mask)
{
// Create Polygon from vertices
std::vector<Point> roi_poly;
approxPolyDP(poly, roi_poly, 1.0, true);
Rect roi = boundingRect(roi_poly);
// Fill polygon white
fillConvexPoly(mask, &roi_poly[0], (int)roi_poly.size(), 1, 8, 0);
roi &= Rect(0, 0, size.width, size.height);
if (roi.empty())
roi = Rect(0, 0, 1, 1);
return roi;
}
float perimeter(const std::vector<Point2f> &ps)
{
float sum = 0, dx, dy;
for (size_t i = 0; i < ps.size(); i++)
{
int i2 = (i + 1) % (int)ps.size();
dx = ps[i].x - ps[i2].x;
dy = ps[i].y - ps[i2].y;
sum += sqrt(dx * dx + dy * dy);
}
return sum;
}
Point2f
mace_center(const std::vector<Point2f> &ps)
{
Point2f center;
int n;
center = Point2f(0);
n = (int)ps.size();
for (int i = 0; i < n; i++)
center += ps[i];
center /= n;
return center;
}
void polyanticlockwise(std::vector<Point2f> &points)
{
// Sort the points in anti-clockwise order
// Trace a line between the first and second point.
// If the third point is at the right side, then the points are anti-clockwise
Point2f v1 = points[1] - points[0];
Point2f v2 = points[2] - points[0];
//if the third point is in the left side, then sort in anti-clockwise order
if ((v1.x * v2.y) - (v1.y * v2.x) < 0.0)
std::swap(points[1], points[3]);
}
void polyclockwise(std::vector<Point2f> &points)
{
// Sort the points in clockwise order
// Trace a line between the first and second point.
// If the third point is at the right side, then the points are clockwise
Point2f v1 = points[1] - points[0];
Point2f v2 = points[2] - points[0];
//if the third point is in the left side, then sort in clockwise order
if ((v1.x * v2.y) - (v1.y * v2.x) > 0.0)
std::swap(points[1], points[3]);
}
} // namespace mcc
} // namespace cv
+133
View File
@@ -0,0 +1,133 @@
// 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.
/*
* MIT License
*
* Copyright (c) 2018 Pedro Diamel Marrero Fernández
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _MCC_COMMON_HPP
#define _MCC_COMMON_HPP
namespace cv
{
namespace mcc
{
Rect poly2mask(const std::vector<Point2f> &poly, Size size, InputOutputArray mask);
template <typename T>
void circshift(std::vector<T> &A, int shiff)
{
if (A.empty() || shiff < 1)
return;
int n = A.size();
if (shiff >= n)
return;
std::vector<T> Tmp(n);
for (int i = shiff; i < n + shiff; i++)
Tmp[(i % n)] = A[i - shiff];
A = Tmp;
}
float perimeter(const std::vector<Point2f> &ps);
Point2f mace_center(const std::vector<Point2f> &ps);
template <typename T>
void unique(const std::vector<T> &A, std::vector<T> &U)
{
int n = (int)A.size();
std::vector<T> Tm = A;
std::sort(Tm.begin(), Tm.end());
U.clear();
U.push_back(Tm[0]);
for (int i = 1; i < n; i++)
if (Tm[i] != Tm[i - 1])
U.push_back(Tm[i]);
}
void polyanticlockwise(std::vector<Point2f> &points);
void polyclockwise(std::vector<Point2f> &points);
// Does lexical cast of the input argument to string
template <typename T>
std::string ToString(const T &value)
{
std::ostringstream stream;
stream << value;
return stream.str();
}
template <typename T>
void change(T &a, T &b)
{
T c = a;
a = b;
b = c;
}
template <typename T>
void sort(std::vector<T> &A, std::vector<int> &idx, bool ord = true)
{
size_t N = A.size();
if (N == 0)
return;
idx.clear();
idx.resize(N);
for (size_t i = 0; i < N; i++)
idx[i] = (int)i;
for (size_t i = 0; i < N - 1; i++)
{
size_t k = i;
T valor = A[i];
for (size_t j = i + 1; j < N; j++)
{
if ((A[j] < valor && ord) || (A[j] > valor && !ord))
{
valor = A[j];
k = j;
}
}
if (k == i)
continue;
change(A[i], A[k]);
change(idx[i], idx[k]);
}
}
} // namespace mcc
} // namespace cv
#endif //_MCC_COMMON_HPP
+68
View File
@@ -0,0 +1,68 @@
// 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.
/*
* MIT License
*
* Copyright (c) 2018 Pedro Diamel Marrero Fernández
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "precomp.hpp"
#include "debug.hpp"
#ifdef MCC_DEBUG
#include <opencv2/highgui.hpp>
namespace cv
{
namespace mcc
{
Scalar randomcolor(RNG &rng)
{
int icolor = (unsigned)rng;
return Scalar(icolor & 255, (icolor >> 8) & 255, (icolor >> 16) & 255);
}
void imshow_250xN(const std::string &name_, InputArray patch)
{
Mat bigpatch;
Size size = patch.size();
float asp = (float)size.height / size.width;
int new_size = 550;
resize(patch, bigpatch, Size((int)new_size, int(new_size * asp)));
imshow(name_, bigpatch);
}
void showAndSave(std::string name, InputArray m, std::string path)
{
imshow_250xN(name, m);
imwrite(path + "/" + name + ".png", m);
if (waitKey(0) == 'q')
return;
}
} // namespace mcc
} // namespace cv
#endif
+50
View File
@@ -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.
/*
* MIT License
*
* Copyright (c) 2018 Pedro Diamel Marrero Fernández
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _MCC_DEBUG_HPP
#define _MCC_DEBUG_HPP
#include "opencv2/objdetect/mcc_checker_detector.hpp"
#ifdef MCC_DEBUG
namespace cv
{
namespace mcc
{
Scalar randomcolor(RNG &rng);
void imshow_250xN(const std::string &name_, InputArray patch);
void showAndSave(std::string name, InputArray m, std::string path);
} // namespace mcc
} // namespace cv
#endif
#endif //_MCC_DEBUG_HPP
File diff suppressed because it is too large Load Diff
+139
View File
@@ -0,0 +1,139 @@
// 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.
/*
* MIT License
*
* Copyright (c) 2018 Pedro Diamel Marrero Fernández
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "precomp.hpp"
#include "graph_cluster.hpp"
namespace cv
{
namespace mcc
{
CB0cluster::CB0cluster()
{
}
CB0cluster::~CB0cluster()
{
}
void CB0cluster::
group()
{
size_t n = X.size();
G.clear();
G.resize(n);
for (int i = 0; i < (int)n - 1; i++)
{
std::vector<double> Y;
Y.clear();
Y.resize(n - i);
Y[0] = 0;
// 1. group similar blobs
double dist, w, y;
for (int j = i + 1, k = 1; j < (int)n; j++, k++)
{
//dist(X_i,X_j)
dist = norm(X[i] - X[j]);
//heuristic to combine two sub charts, This is pretty ugly at the moment,
//Looking for somthing better
w = min(abs(W[i] - W[j]) / (W[i] + W[j]),
abs(max(W[i], W[j]) - 24 / 11.0f * min(W[i], W[j])) / (max(W[i], W[j]) + 24 / 11.0f * min(W[i], W[j])));
w = (w < 0.1);
y = w * dist;
Y[k] = (y < B0[i]) * y;
;
}
if (!G[i])
G[i] = i + 1;
std::vector<int> pos_b0;
find(Y, pos_b0);
size_t m = pos_b0.size();
if (!m)
continue;
std::vector<int> pos_nz, pos_z;
for (int j = 0; j < (int)m; j++)
{
pos_b0[j] = pos_b0[j] + i;
if (G[pos_b0[j]])
pos_nz.push_back(j);
else
pos_z.push_back(j);
}
for (int j = 0; j < (int)pos_z.size(); j++)
{
pos_z[j] = pos_b0[pos_z[j]];
G[pos_z[j]] = G[i];
}
if (!pos_nz.size())
continue;
std::vector<int> g;
for (size_t j = 0; j < pos_nz.size(); j++)
{
pos_nz[j] = pos_b0[pos_nz[j]];
g.push_back(G[pos_nz[j]]);
}
unique(g, g);
for (size_t k = 0; k < g.size(); k++)
{
int gk = g[k];
for (size_t j = 0; j < G.size(); j++)
if (G[j] == gk)
G[j] = G[i];
}
}
if (!G[n - 1])
G[n - 1] = (int)n;
std::vector<int> S;
S = G;
unique(S, S);
for (int k = 0; k < (int)S.size(); k++)
{
int gk = S[k];
for (int j = 0; j < (int)G.size(); j++)
if (G[j] == gk)
G[j] = k;
}
}
} // namespace mcc
} // namespace cv
@@ -0,0 +1,74 @@
// 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.
/*
* MIT License
*
* Copyright (c) 2018 Pedro Diamel Marrero Fernández
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _MCC_GRAPH_CLUSTERS_HPP
#define _MCC_GRAPH_CLUSTERS_HPP
namespace cv
{
namespace mcc
{
class CB0cluster
{
public:
CB0cluster();
~CB0cluster();
inline void setVertex(const std::vector<Point> &V) { X = V; }
inline void setB0(const std::vector<double> &b0) { B0 = b0; }
inline void setWeight(const std::vector<double> &Weight) { W = Weight; }
void group();
void getGroup(std::vector<int> &g) { g = G; }
private:
//entrada
std::vector<Point> X;
std::vector<double> B0;
std::vector<double> W;
//salida
std::vector<int> G;
private:
template <typename T>
void find(const std::vector<T> &A, std::vector<int> &indx)
{
indx.clear();
for (int i = 0; i < (int)A.size(); i++)
if (A[i])
indx.push_back(i);
}
};
} // namespace mcc
} // namespace cv
#endif //_MCC_GRAPH_CLUSTERS_HPP
+48
View File
@@ -0,0 +1,48 @@
// 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.
/*
* MIT License
*
* Copyright (c) 2018 Pedro Diamel Marrero Fernández
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _MCC_PRECOMP_HPP
#define _MCC_PRECOMP_HPP
#include <limits>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/geometry.hpp>
#ifdef HAVE_OPENCV_DNN
#include <opencv2/dnn.hpp>
#endif
#include <vector>
#include <string>
#include "opencv2/objdetect.hpp"
#include "common.hpp"
#endif //_MCC_PRECOMP_HPP
@@ -0,0 +1,98 @@
// 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.
/*
* MIT License
*
* Copyright (c) 2018 Pedro Diamel Marrero Fernández
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "wiener_filter.hpp"
namespace cv
{
namespace mcc
{
CWienerFilter::CWienerFilter()
{
}
CWienerFilter::~CWienerFilter()
{
}
void CWienerFilter::
wiener2(InputArray _src, OutputArray _dst, int szWindowX, int szWindowY)
{
CV_Assert(szWindowX > 0 && szWindowY > 0);
Mat src = _src.getMat();
int nRows;
int nCols;
Scalar v = 0;
Mat p_kernel;
Mat srcStub;
//Now create a temporary holding matrix
Mat p_tmpMat1, p_tmpMat2, p_tmpMat3, p_tmpMat4;
double noise_power;
nRows = szWindowY;
nCols = szWindowX;
p_kernel = Mat(nRows, nCols, CV_32F, Scalar(1.0 / (double)(nRows * nCols)));
//Local mean of input
filter2D(src, p_tmpMat1, -1, p_kernel, Point(nCols / 2, nRows / 2)); //localMean
//Local variance of input
p_tmpMat2 = src.mul(src);
filter2D(p_tmpMat2, p_tmpMat3, -1, p_kernel, Point(nCols / 2, nRows / 2));
//Subtract off local_mean^2 from local variance
p_tmpMat4 = p_tmpMat1.mul(p_tmpMat1); //localMean^2
p_tmpMat3 = p_tmpMat3 - p_tmpMat4;
// Sub(p_tmpMat3, p_tmpMat4, p_tmpMat3); //filter(in^2) - localMean^2 ==> localVariance
//Estimate noise power
v = mean(p_tmpMat3);
noise_power = v.val[0];
// result = local_mean + ( max(0, localVar - noise) ./ max(localVar, noise)) .* (in - local_mean)
p_tmpMat4 = src - p_tmpMat1; //in - local_mean
p_tmpMat2 = max(p_tmpMat3, noise_power); //max(localVar, noise)
add(p_tmpMat3, Scalar(-noise_power), p_tmpMat3); //localVar - noise
p_tmpMat3 = max(p_tmpMat3, 0); // max(0, localVar - noise)
p_tmpMat3 = (p_tmpMat3 / p_tmpMat2); //max(0, localVar-noise) / max(localVar, noise)
Mat dst = p_tmpMat3.mul(p_tmpMat4);
dst = dst + p_tmpMat1;
_dst.assign(dst);
}
} // namespace mcc
} // namespace cv
@@ -0,0 +1,68 @@
// 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.
/*
* MIT License
*
* Copyright (c) 2018 Pedro Diamel Marrero Fernández
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* @file wiener_filter
* @brief filter wiener for denoise
* @author: Pedro D. Marrero Fernandez
* @data: 17/05/2016
*/
#ifndef _WIENER_FILTER_HPP
#define _WIENER_FILTER_HPP
#include "precomp.hpp"
namespace cv
{
namespace mcc
{
/// CWienerFilter
/** @brief wiener class filter for denoise
* @author: Pedro D. Marrero Fernandez
* @data: 17/05/2016
*/
class CWienerFilter
{
public:
CWienerFilter();
~CWienerFilter();
/** cvWiener2
* @brief A Wiener 2D Filter implementation for OpenCV
* @author: Ray Juang / rayver{ _at_ } hkn{ / _dot_ / } berkeley(_dot_) edu
* @date : 12.1.2006
*/
void wiener2(InputArray _src, OutputArray _dst, int szWindowX, int szWindowY);
};
} // namespace mcc
} // namespace cv
#endif //_WIENER_FILTER_HPP
+67
View File
@@ -0,0 +1,67 @@
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#ifndef __OPENCV_PRECOMP_H__
#define __OPENCV_PRECOMP_H__
#include "opencv2/core.hpp"
#include "opencv2/objdetect.hpp"
#include "opencv2/objdetect/barcode.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/geometry.hpp"
#include <opencv2/core/utils/logger.hpp>
#include "opencv2/core/utility.hpp"
#include "opencv2/core/ocl.hpp"
#include "opencv2/core/private.hpp"
#include <numeric>
#include <array>
#include <vector>
namespace cv {
int checkChessboardBinary(const Mat & img, const Size & size);
}
#endif
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More