mirror of
https://github.com/opencv/opencv.git
synced 2026-07-31 00:03:03 +04:00
Merge branch 4.x
This commit is contained in:
@@ -18,3 +18,32 @@
|
||||
year = {2016},
|
||||
month = {October}
|
||||
}
|
||||
|
||||
@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}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@
|
||||
#define OPENCV_OBJDETECT_HPP
|
||||
|
||||
#include "opencv2/core.hpp"
|
||||
#include "opencv2/objdetect/aruco_detector.hpp"
|
||||
#include "opencv2/objdetect/graphical_code_detector.hpp"
|
||||
|
||||
/**
|
||||
@defgroup objdetect Object Detection
|
||||
@@ -101,6 +103,7 @@ using a Boosted Cascade of Simple Features. IEEE CVPR, 2001. The paper is availa
|
||||
<https://github.com/SvHey/thesis/blob/master/Literature/ObjectDetection/violaJones_CVPR2001.pdf>
|
||||
|
||||
@defgroup objdetect_hog HOG (Histogram of Oriented Gradients) descriptor and object detector
|
||||
@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.
|
||||
@@ -753,44 +756,27 @@ public:
|
||||
CV_WRAP virtual void encodeStructuredAppend(const String& encoded_info, OutputArrayOfArrays qrcodes) = 0;
|
||||
|
||||
};
|
||||
|
||||
class CV_EXPORTS_W QRCodeDetector
|
||||
class CV_EXPORTS_W_SIMPLE QRCodeDetector : public GraphicalCodeDetector
|
||||
{
|
||||
public:
|
||||
CV_WRAP QRCodeDetector();
|
||||
~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 void setEpsX(double epsX);
|
||||
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 void setEpsY(double epsY);
|
||||
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 void setUseAlignmentMarkers(bool useAlignmentMarkers);
|
||||
|
||||
/** @brief Detects QR code in image and returns the quadrangle containing the code.
|
||||
@param img grayscale or color (BGR) image containing (or not) QR code.
|
||||
@param points Output vector of vertices of the minimum-area quadrangle containing the code.
|
||||
*/
|
||||
CV_WRAP bool detect(InputArray img, OutputArray points) const;
|
||||
|
||||
/** @brief Decodes QR 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 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 std::string decode(InputArray img, InputArray points, OutputArray straight_qrcode = noArray());
|
||||
CV_WRAP QRCodeDetector& setUseAlignmentMarkers(bool useAlignmentMarkers);
|
||||
|
||||
/** @brief Decodes QR code on a curved surface in image once it's found by the detect() method.
|
||||
|
||||
@@ -801,15 +787,6 @@ public:
|
||||
*/
|
||||
CV_WRAP cv::String decodeCurved(InputArray img, InputArray points, OutputArray straight_qrcode = noArray());
|
||||
|
||||
/** @brief Both detects and decodes QR code
|
||||
|
||||
@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 detectAndDecode(InputArray img, OutputArray points=noArray(),
|
||||
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.
|
||||
@@ -818,43 +795,58 @@ public:
|
||||
*/
|
||||
CV_WRAP std::string detectAndDecodeCurved(InputArray img, OutputArray points=noArray(),
|
||||
OutputArray straight_qrcode = noArray());
|
||||
};
|
||||
|
||||
/** @brief Detects QR codes in image and returns the vector of the quadrangles containing the codes.
|
||||
@param img grayscale or color (BGR) image containing (or not) QR 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;
|
||||
class CV_EXPORTS_W_SIMPLE QRCodeDetectorAruco : public GraphicalCodeDetector {
|
||||
public:
|
||||
CV_WRAP QRCodeDetectorAruco();
|
||||
|
||||
/** @brief Decodes QR codes in image once it's found by the detect() method.
|
||||
@param img grayscale or color (BGR) image containing QR 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_qrcode The optional output vector of images containing rectified and binarized QR codes
|
||||
*/
|
||||
CV_WRAP
|
||||
bool decodeMulti(
|
||||
InputArray img, InputArray points,
|
||||
CV_OUT std::vector<std::string>& decoded_info,
|
||||
OutputArrayOfArrays straight_qrcode = noArray()
|
||||
) const;
|
||||
struct CV_EXPORTS_W_SIMPLE Params {
|
||||
CV_WRAP Params();
|
||||
|
||||
/** @brief Both detects and decodes QR codes
|
||||
@param img grayscale or color (BGR) image containing QR 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 QR code quadrangles. Will be empty if not found.
|
||||
@param straight_qrcode The optional output vector of images containing rectified and binarized QR codes
|
||||
*/
|
||||
CV_WRAP
|
||||
bool detectAndDecodeMulti(
|
||||
InputArray img, CV_OUT std::vector<std::string>& decoded_info,
|
||||
OutputArray points = noArray(),
|
||||
OutputArrayOfArrays straight_qrcode = noArray()
|
||||
) const;
|
||||
/** @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;
|
||||
|
||||
protected:
|
||||
struct Impl;
|
||||
Ptr<Impl> p;
|
||||
/** @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 aruco::DetectorParameters getArucoParameters();
|
||||
|
||||
/** @brief Aruco detector parameters are used to search for the finder patterns. */
|
||||
CV_WRAP void setArucoParameters(const aruco::DetectorParameters& params);
|
||||
};
|
||||
|
||||
//! @}
|
||||
@@ -862,7 +854,7 @@ protected:
|
||||
|
||||
#include "opencv2/objdetect/detection_based_tracker.hpp"
|
||||
#include "opencv2/objdetect/face.hpp"
|
||||
#include "opencv2/objdetect/aruco_detector.hpp"
|
||||
#include "opencv2/objdetect/charuco_detector.hpp"
|
||||
#include "opencv2/objdetect/barcode.hpp"
|
||||
|
||||
#endif
|
||||
|
||||
@@ -90,7 +90,7 @@ public:
|
||||
*/
|
||||
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)
|
||||
CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to "protected" (need to fix bindings first)
|
||||
Board();
|
||||
|
||||
struct Impl;
|
||||
@@ -122,7 +122,7 @@ public:
|
||||
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)
|
||||
CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to "protected" (need to fix bindings first)
|
||||
GridBoard();
|
||||
};
|
||||
|
||||
@@ -187,7 +187,7 @@ public:
|
||||
*/
|
||||
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)
|
||||
CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to "protected" (need to fix bindings first)
|
||||
CharucoBoard();
|
||||
};
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ struct CV_EXPORTS_W_SIMPLE DetectorParameters {
|
||||
minCornerDistanceRate = 0.05;
|
||||
minDistanceToBorder = 3;
|
||||
minMarkerDistanceRate = 0.05;
|
||||
cornerRefinementMethod = CORNER_REFINE_NONE;
|
||||
cornerRefinementMethod = (int)CORNER_REFINE_NONE;
|
||||
cornerRefinementWinSize = 5;
|
||||
cornerRefinementMaxIterations = 30;
|
||||
cornerRefinementMinAccuracy = 0.1;
|
||||
@@ -106,7 +106,7 @@ struct CV_EXPORTS_W_SIMPLE DetectorParameters {
|
||||
CV_PROP_RW double minMarkerDistanceRate;
|
||||
|
||||
/** @brief default value CORNER_REFINE_NONE */
|
||||
CV_PROP_RW CornerRefineMethod cornerRefinementMethod;
|
||||
CV_PROP_RW int cornerRefinementMethod;
|
||||
|
||||
/// window size for the corner refinement process (in pixels) (default 5).
|
||||
CV_PROP_RW int cornerRefinementWinSize;
|
||||
|
||||
@@ -110,7 +110,8 @@ enum PredefinedDictionaryType {
|
||||
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_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
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// 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.
|
||||
*/
|
||||
CV_WRAP BarcodeDetector();
|
||||
/** @brief Initialize the BarcodeDetector.
|
||||
*
|
||||
* Parameters allow to load _optional_ Super Resolution DNN model for better quality.
|
||||
* @param prototxt_path prototxt file path for the super resolution model
|
||||
* @param model_path model file path for the super resolution model
|
||||
*/
|
||||
CV_WRAP BarcodeDetector(const std::string &prototxt_path, const std::string &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;
|
||||
};
|
||||
//! @}
|
||||
|
||||
}} // cv::barcode::
|
||||
|
||||
#endif // OPENCV_OBJDETECT_BARCODE_HPP
|
||||
@@ -0,0 +1,81 @@
|
||||
// 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
|
||||
*/
|
||||
CV_WRAP bool detectAndDecodeMulti(InputArray img, CV_OUT std::vector<std::string>& decoded_info, OutputArray points = noArray(),
|
||||
OutputArrayOfArrays straight_code = noArray()) const;
|
||||
struct Impl;
|
||||
protected:
|
||||
Ptr<Impl> p;
|
||||
};
|
||||
|
||||
//! @}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,50 @@
|
||||
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();
|
||||
|
||||
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,7 @@
|
||||
{
|
||||
"ManualFuncs" : {
|
||||
"QRCodeDetectorAruco": {
|
||||
"getDetectorParameters": { "declaration" : [""], "implementation" : [""] }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
@@ -171,7 +171,7 @@ PERF_TEST_P(EstimateAruco, ArucoFirst, ESTIMATE_PARAMS) {
|
||||
aruco::DetectorParameters detectorParams;
|
||||
detectorParams.minDistanceToBorder = 1;
|
||||
detectorParams.markerBorderBits = 1;
|
||||
detectorParams.cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
|
||||
detectorParams.cornerRefinementMethod = (int)cv::aruco::CORNER_REFINE_SUBPIX;
|
||||
|
||||
const int markerSize = 100;
|
||||
const int numMarkersInRow = 9;
|
||||
@@ -203,7 +203,7 @@ PERF_TEST_P(EstimateAruco, ArucoSecond, ESTIMATE_PARAMS) {
|
||||
aruco::DetectorParameters detectorParams;
|
||||
detectorParams.minDistanceToBorder = 1;
|
||||
detectorParams.markerBorderBits = 1;
|
||||
detectorParams.cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
|
||||
detectorParams.cornerRefinementMethod = (int)cv::aruco::CORNER_REFINE_SUBPIX;
|
||||
|
||||
//USE_ARUCO3
|
||||
detectorParams.useAruco3Detection = get<0>(testParams);
|
||||
@@ -255,7 +255,7 @@ PERF_TEST_P(EstimateLargeAruco, ArucoFHD, ESTIMATE_FHD_PARAMS) {
|
||||
aruco::DetectorParameters detectorParams;
|
||||
detectorParams.minDistanceToBorder = 1;
|
||||
detectorParams.markerBorderBits = 1;
|
||||
detectorParams.cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
|
||||
detectorParams.cornerRefinementMethod = (int)cv::aruco::CORNER_REFINE_SUBPIX;
|
||||
|
||||
//USE_ARUCO3
|
||||
detectorParams.useAruco3Detection = get<0>(testParams).useAruco3Detection;
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
// 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
@@ -3,6 +3,7 @@
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "perf_precomp.hpp"
|
||||
#include "../test/test_qr_utils.hpp"
|
||||
|
||||
namespace opencv_test
|
||||
{
|
||||
@@ -23,7 +24,9 @@ PERF_TEST_P_(Perf_Objdetect_QRCode, detect)
|
||||
std::vector< Point > corners;
|
||||
QRCodeDetector qrcode;
|
||||
TEST_CYCLE() ASSERT_TRUE(qrcode.detect(src, corners));
|
||||
SANITY_CHECK(corners);
|
||||
const int pixels_error = 3;
|
||||
check_qr(root, name_current_image, "test_images", corners, {}, pixels_error);
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
#ifdef HAVE_QUIRC
|
||||
@@ -45,48 +48,52 @@ PERF_TEST_P_(Perf_Objdetect_QRCode, decode)
|
||||
decoded_info = qrcode.decode(src, corners, straight_barcode);
|
||||
ASSERT_FALSE(decoded_info.empty());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> decoded_info_uint8_t(decoded_info.begin(), decoded_info.end());
|
||||
SANITY_CHECK(decoded_info_uint8_t);
|
||||
SANITY_CHECK(straight_barcode);
|
||||
|
||||
const int pixels_error = 3;
|
||||
check_qr(root, name_current_image, "test_images", corners, {decoded_info}, pixels_error);
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
#endif
|
||||
|
||||
typedef ::perf::TestBaseWithParam< std::string > Perf_Objdetect_QRCode_Multi;
|
||||
typedef ::perf::TestBaseWithParam<std::tuple<std::string, std::string>> Perf_Objdetect_QRCode_Multi;
|
||||
|
||||
static inline bool compareCorners(const Point2f& corner1, const Point2f& corner2) {
|
||||
return corner1.x == corner2.x ? corner1.y < corner2.y : corner1.x < corner2.x;
|
||||
}
|
||||
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 = GetParam();
|
||||
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<Point2f> corners;
|
||||
QRCodeDetector qrcode;
|
||||
std::vector<Point> corners;
|
||||
GraphicalCodeDetector qrcode = QRCodeDetector();
|
||||
if (method == "aruco_based") {
|
||||
qrcode = QRCodeDetectorAruco();
|
||||
}
|
||||
TEST_CYCLE() ASSERT_TRUE(qrcode.detectMulti(src, corners));
|
||||
sort(corners.begin(), corners.end(), compareCorners);
|
||||
SANITY_CHECK(corners);
|
||||
}
|
||||
|
||||
static inline bool compareQR(const pair<string, Mat>& v1, const pair<string, Mat>& v2) {
|
||||
return v1.first < v2.first;
|
||||
const int pixels_error = 7;
|
||||
check_qr(root, name_current_image, "multiple_images", corners, {}, pixels_error, true);
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
#ifdef HAVE_QUIRC
|
||||
PERF_TEST_P_(Perf_Objdetect_QRCode_Multi, decodeMulti)
|
||||
{
|
||||
const std::string name_current_image = GetParam();
|
||||
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;
|
||||
QRCodeDetector qrcode;
|
||||
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;
|
||||
@@ -94,26 +101,20 @@ PERF_TEST_P_(Perf_Objdetect_QRCode_Multi, decodeMulti)
|
||||
TEST_CYCLE()
|
||||
{
|
||||
ASSERT_TRUE(qrcode.decodeMulti(src, corners, decoded_info, straight_barcode));
|
||||
for(size_t i = 0; i < decoded_info.size(); i++)
|
||||
{
|
||||
ASSERT_FALSE(decoded_info[i].empty());
|
||||
}
|
||||
}
|
||||
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<pair<string, Mat> > result;
|
||||
for (size_t i = 0ull; i < decoded_info.size(); i++) {
|
||||
result.push_back(make_pair(decoded_info[i], straight_barcode[i]));
|
||||
vector<Point> corners_result(corners.size());
|
||||
for (size_t i = 0ull; i < corners_result.size(); i++) {
|
||||
corners_result[i] = corners[i];
|
||||
}
|
||||
|
||||
sort(result.begin(), result.end(), compareQR);
|
||||
vector<vector<uint8_t> > decoded_info_sort;
|
||||
vector<Mat> straight_barcode_sort;
|
||||
for (size_t i = 0ull; i < result.size(); i++) {
|
||||
vector<uint8_t> tmp(result[i].first.begin(), result[i].first.end());
|
||||
decoded_info_sort.push_back(tmp);
|
||||
straight_barcode_sort.push_back(result[i].second);
|
||||
}
|
||||
SANITY_CHECK(decoded_info_sort);
|
||||
const int pixels_error = 7;
|
||||
check_qr(root, name_current_image, "multiple_images", corners_result, decoded_info, pixels_error, true);
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -127,11 +128,10 @@ INSTANTIATE_TEST_CASE_P(/*nothing*/, Perf_Objdetect_QRCode,
|
||||
// version_5_right.jpg DISABLED after tile fix, PR #22025
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Perf_Objdetect_QRCode_Multi,
|
||||
::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::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;
|
||||
|
||||
|
||||
@@ -881,7 +881,7 @@ void ArucoDetector::detectMarkers(InputArray _image, OutputArrayOfArrays _corner
|
||||
}
|
||||
else {
|
||||
// always turn on corner refinement in case of Aruco3, due to upsampling
|
||||
detectorParams.cornerRefinementMethod = CORNER_REFINE_SUBPIX;
|
||||
detectorParams.cornerRefinementMethod = (int)CORNER_REFINE_SUBPIX;
|
||||
// only CORNER_REFINE_SUBPIX implement correctly for useAruco3Detection
|
||||
// Todo: update other CORNER_REFINE methods
|
||||
}
|
||||
@@ -923,7 +923,7 @@ void ArucoDetector::detectMarkers(InputArray _image, OutputArrayOfArrays _corner
|
||||
vector<vector<vector<Point> > > contoursSet;
|
||||
|
||||
/// STEP 2.a Detect marker candidates :: using AprilTag
|
||||
if(detectorParams.cornerRefinementMethod == CORNER_REFINE_APRILTAG){
|
||||
if(detectorParams.cornerRefinementMethod == (int)CORNER_REFINE_APRILTAG){
|
||||
_apriltag(grey, detectorParams, candidates, contours);
|
||||
|
||||
candidatesSet.push_back(candidates);
|
||||
@@ -938,7 +938,7 @@ void ArucoDetector::detectMarkers(InputArray _image, OutputArrayOfArrays _corner
|
||||
candidates, contours, ids, detectorParams, _rejectedImgPoints);
|
||||
|
||||
/// STEP 3: Corner refinement :: use corner subpix
|
||||
if (detectorParams.cornerRefinementMethod == CORNER_REFINE_SUBPIX) {
|
||||
if (detectorParams.cornerRefinementMethod == (int)CORNER_REFINE_SUBPIX) {
|
||||
CV_Assert(detectorParams.cornerRefinementWinSize > 0 && detectorParams.cornerRefinementMaxIterations > 0 &&
|
||||
detectorParams.cornerRefinementMinAccuracy > 0);
|
||||
// Do subpixel estimation. In Aruco3 start on the lowest pyramid level and upscale the corners
|
||||
@@ -963,7 +963,7 @@ void ArucoDetector::detectMarkers(InputArray _image, OutputArrayOfArrays _corner
|
||||
}
|
||||
|
||||
/// STEP 3, Optional : Corner refinement :: use contour container
|
||||
if (detectorParams.cornerRefinementMethod == CORNER_REFINE_CONTOUR){
|
||||
if (detectorParams.cornerRefinementMethod == (int)CORNER_REFINE_CONTOUR){
|
||||
|
||||
if (!ids.empty()) {
|
||||
|
||||
@@ -976,7 +976,7 @@ void ArucoDetector::detectMarkers(InputArray _image, OutputArrayOfArrays _corner
|
||||
}
|
||||
}
|
||||
|
||||
if (detectorParams.cornerRefinementMethod != CORNER_REFINE_SUBPIX && fxfy != 1.f) {
|
||||
if (detectorParams.cornerRefinementMethod != (int)CORNER_REFINE_SUBPIX && fxfy != 1.f) {
|
||||
// only CORNER_REFINE_SUBPIX implement correctly for useAruco3Detection
|
||||
// Todo: update other CORNER_REFINE methods
|
||||
|
||||
@@ -1213,7 +1213,7 @@ void ArucoDetector::refineDetectedMarkers(InputArray _image, const Board& _board
|
||||
if(closestCandidateIdx >= 0) {
|
||||
|
||||
// subpixel refinement
|
||||
if(detectorParams.cornerRefinementMethod == CORNER_REFINE_SUBPIX) {
|
||||
if(detectorParams.cornerRefinementMethod == (int)CORNER_REFINE_SUBPIX) {
|
||||
CV_Assert(detectorParams.cornerRefinementWinSize > 0 &&
|
||||
detectorParams.cornerRefinementMaxIterations > 0 &&
|
||||
detectorParams.cornerRefinementMinAccuracy > 0);
|
||||
|
||||
@@ -258,6 +258,8 @@ Dictionary getPredefinedDictionary(PredefinedDictionaryType name) {
|
||||
static const Dictionary DICT_APRILTAG_36h10_DATA = Dictionary(Mat(2320, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h10_BYTES), 6, 0);
|
||||
static const Dictionary DICT_APRILTAG_36h11_DATA = Dictionary(Mat(587, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h11_BYTES), 6, 0);
|
||||
|
||||
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);
|
||||
|
||||
switch(name) {
|
||||
|
||||
case DICT_ARUCO_ORIGINAL:
|
||||
@@ -308,6 +310,8 @@ Dictionary getPredefinedDictionary(PredefinedDictionaryType name) {
|
||||
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);
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,374 @@
|
||||
// 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;
|
||||
|
||||
public:
|
||||
//=================
|
||||
// own methods
|
||||
BarcodeImpl() = default;
|
||||
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);
|
||||
bardet.localization();
|
||||
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<Point> points_;
|
||||
if (!detectAndDecodeWithType(img, decoded_info, decoded_type, points_))
|
||||
return string();
|
||||
if (points_.size() < 4 || decoded_info.size() < 1)
|
||||
return string();
|
||||
points_.resize(4);
|
||||
points.setTo(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(string(), string())
|
||||
{
|
||||
}
|
||||
|
||||
BarcodeDetector::BarcodeDetector(const string &prototxt_path, const string &model_path)
|
||||
{
|
||||
Ptr<BarcodeImpl> p_ = new BarcodeImpl();
|
||||
p = p_;
|
||||
if (!prototxt_path.empty() && !model_path.empty())
|
||||
{
|
||||
CV_Assert(utils::fs::exists(prototxt_path));
|
||||
CV_Assert(utils::fs::exists(model_path));
|
||||
p_->sr = make_shared<SuperScale>();
|
||||
int res = p_->sr->init(prototxt_path, 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_);
|
||||
}
|
||||
|
||||
}// 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,77 @@
|
||||
// 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"
|
||||
|
||||
#ifdef HAVE_OPENCV_DNN
|
||||
|
||||
namespace cv {
|
||||
namespace barcode {
|
||||
constexpr static float MAX_SCALE = 4.0f;
|
||||
|
||||
int SuperScale::init(const std::string &proto_path, const std::string &model_path)
|
||||
{
|
||||
srnet_ = dnn::readNetFromCaffe(proto_path, 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;
|
||||
}
|
||||
} // namespace barcode
|
||||
} // namespace cv
|
||||
|
||||
#endif // HAVE_OPENCV_DNN
|
||||
@@ -0,0 +1,69 @@
|
||||
/// 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"
|
||||
|
||||
namespace cv {
|
||||
namespace barcode {
|
||||
|
||||
class SuperScale
|
||||
{
|
||||
public:
|
||||
SuperScale() = default;
|
||||
|
||||
~SuperScale() = default;
|
||||
|
||||
int init(const std::string &proto_path, const std::string &model_path);
|
||||
|
||||
void processImageScale(const Mat &src, Mat &dst, float scale, const bool &use_sr, int sr_max_size = 160);
|
||||
|
||||
private:
|
||||
dnn::Net srnet_;
|
||||
bool net_loaded_ = false;
|
||||
|
||||
int superResolutionScale(const cv::Mat &src, cv::Mat &dst);
|
||||
};
|
||||
|
||||
} // namespace barcode
|
||||
} // namespace cv
|
||||
|
||||
#else // HAVE_OPENCV_DNN
|
||||
|
||||
#include "opencv2/core.hpp"
|
||||
#include "opencv2/core/utils/logger.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace barcode {
|
||||
|
||||
class SuperScale
|
||||
{
|
||||
public:
|
||||
int init(const std::string &, const std::string &)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
void processImageScale(const Mat &src, Mat &dst, float scale, const bool & isEnabled, int)
|
||||
{
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace barcode
|
||||
} // namespace cv
|
||||
|
||||
#endif // !HAVE_OPENCV_DNN
|
||||
|
||||
#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,510 @@
|
||||
// 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"
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
const double min_side = std::min(src.size().width, src.size().height);
|
||||
if (min_side > 512.0)
|
||||
{
|
||||
purpose = SHRINKING;
|
||||
coeff_expansion = min_side / 512.0;
|
||||
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()
|
||||
{
|
||||
|
||||
localization_bbox.clear();
|
||||
bbox_scores.clear();
|
||||
|
||||
// get integral image
|
||||
preprocess();
|
||||
// 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:SCALE_LIST)
|
||||
{
|
||||
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];
|
||||
const float THRESHOLD_SCORE = float(width * height) / 300.f;
|
||||
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()
|
||||
{
|
||||
Mat scharr_x, scharr_y, temp;
|
||||
static constexpr double THRESHOLD_MAGNITUDE = 64.;
|
||||
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, THRESHOLD_MAGNITUDE, 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);
|
||||
|
||||
void localization();
|
||||
|
||||
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();
|
||||
|
||||
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
|
||||
@@ -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,25 @@
|
||||
// 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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -44,10 +44,13 @@
|
||||
#define __OPENCV_PRECOMP_H__
|
||||
|
||||
#include "opencv2/objdetect.hpp"
|
||||
#include "opencv2/objdetect/barcode.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
|
||||
#include "opencv2/core/utility.hpp"
|
||||
#include "opencv2/core/ocl.hpp"
|
||||
#include "opencv2/core/private.hpp"
|
||||
|
||||
#include <numeric>
|
||||
|
||||
#endif
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "opencv2/objdetect.hpp"
|
||||
#include "opencv2/3d.hpp"
|
||||
#include <opencv2/core/utils/logger.hpp>
|
||||
#include "graphical_code_detector_impl.hpp"
|
||||
|
||||
#ifdef HAVE_QUIRC
|
||||
#include "quirc.h"
|
||||
@@ -950,34 +951,53 @@ vector<Point2f> QRDetect::getQuadrilateral(vector<Point2f> angle_list)
|
||||
return result_angle_list;
|
||||
}
|
||||
|
||||
|
||||
struct QRCodeDetector::Impl
|
||||
struct ImplContour : public GraphicalCodeDetector::Impl
|
||||
{
|
||||
public:
|
||||
Impl() { epsX = 0.2; epsY = 0.1; }
|
||||
~Impl() {}
|
||||
ImplContour(): epsX(0.2), epsY(0.1) {}
|
||||
|
||||
double epsX, epsY;
|
||||
vector<vector<Point2f>> alignmentMarkers;
|
||||
vector<Point2f> updateQrCorners;
|
||||
mutable vector<vector<Point2f>> alignmentMarkers;
|
||||
mutable vector<Point2f> updateQrCorners;
|
||||
bool useAlignmentMarkers = true;
|
||||
|
||||
bool detect(InputArray in, OutputArray points) const override;
|
||||
std::string decode(InputArray img, InputArray points, OutputArray straight_qrcode) const override;
|
||||
std::string detectAndDecode(InputArray img, OutputArray points, OutputArray straight_qrcode) const override;
|
||||
|
||||
bool detectMulti(InputArray img, OutputArray points) const override;
|
||||
bool decodeMulti(InputArray img, InputArray points, std::vector<cv::String>& decoded_info,
|
||||
OutputArrayOfArrays straight_qrcode) const override;
|
||||
bool detectAndDecodeMulti(InputArray img, std::vector<cv::String>& decoded_info, OutputArray points,
|
||||
OutputArrayOfArrays straight_qrcode) const override;
|
||||
|
||||
String decodeCurved(InputArray in, InputArray points, OutputArray straight_qrcode);
|
||||
|
||||
std::string detectAndDecodeCurved(InputArray in, OutputArray points, OutputArray straight_qrcode);
|
||||
};
|
||||
|
||||
QRCodeDetector::QRCodeDetector() : p(new Impl) {}
|
||||
QRCodeDetector::QRCodeDetector() {
|
||||
p = makePtr<ImplContour>();
|
||||
}
|
||||
|
||||
QRCodeDetector::~QRCodeDetector() {}
|
||||
QRCodeDetector& QRCodeDetector::setEpsX(double epsX) {
|
||||
std::dynamic_pointer_cast<ImplContour>(p)->epsX = epsX;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void QRCodeDetector::setEpsX(double epsX) { p->epsX = epsX; }
|
||||
void QRCodeDetector::setEpsY(double epsY) { p->epsY = epsY; }
|
||||
QRCodeDetector& QRCodeDetector::setEpsY(double epsY) {
|
||||
std::dynamic_pointer_cast<ImplContour>(p)->epsY = epsY;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool QRCodeDetector::detect(InputArray in, OutputArray points) const
|
||||
bool ImplContour::detect(InputArray in, OutputArray points) const
|
||||
{
|
||||
Mat inarr;
|
||||
if (!checkQRInputImage(in, inarr))
|
||||
return false;
|
||||
|
||||
QRDetect qrdet;
|
||||
qrdet.init(inarr, p->epsX, p->epsY);
|
||||
qrdet.init(inarr, epsX, epsY);
|
||||
if (!qrdet.localization()) { return false; }
|
||||
if (!qrdet.computeTransformationPoints()) { return false; }
|
||||
vector<Point2f> pnts2f = qrdet.getTransformationPoints();
|
||||
@@ -2789,9 +2809,7 @@ QRDecode::QRDecode(bool _useAlignmentMarkers):
|
||||
test_perspective_size(0.f)
|
||||
{}
|
||||
|
||||
std::string QRCodeDetector::decode(InputArray in, InputArray points,
|
||||
OutputArray straight_qrcode)
|
||||
{
|
||||
std::string ImplContour::decode(InputArray in, InputArray points, OutputArray straight_qrcode) const {
|
||||
Mat inarr;
|
||||
if (!checkQRInputImage(in, inarr))
|
||||
return std::string();
|
||||
@@ -2801,7 +2819,7 @@ std::string QRCodeDetector::decode(InputArray in, InputArray points,
|
||||
CV_Assert(src_points.size() == 4);
|
||||
CV_CheckGT(contourArea(src_points), 0.0, "Invalid QR code source points");
|
||||
|
||||
QRDecode qrdec(p->useAlignmentMarkers);
|
||||
QRDecode qrdec(useAlignmentMarkers);
|
||||
qrdec.init(inarr, src_points);
|
||||
bool ok = qrdec.straightDecodingProcess();
|
||||
|
||||
@@ -2815,14 +2833,18 @@ std::string QRCodeDetector::decode(InputArray in, InputArray points,
|
||||
qrdec.getStraightBarcode().convertTo(straight_qrcode, CV_8UC1);
|
||||
}
|
||||
if (ok && !decoded_info.empty()) {
|
||||
p->alignmentMarkers = {qrdec.alignment_coords};
|
||||
p->updateQrCorners = qrdec.getOriginalPoints();
|
||||
alignmentMarkers = {qrdec.alignment_coords};
|
||||
updateQrCorners = qrdec.getOriginalPoints();
|
||||
}
|
||||
return ok ? decoded_info : std::string();
|
||||
}
|
||||
|
||||
cv::String QRCodeDetector::decodeCurved(InputArray in, InputArray points,
|
||||
OutputArray straight_qrcode)
|
||||
String QRCodeDetector::decodeCurved(InputArray in, InputArray points, OutputArray straight_qrcode) {
|
||||
CV_Assert(p);
|
||||
return std::dynamic_pointer_cast<ImplContour>(p)->decodeCurved(in, points, straight_qrcode);
|
||||
}
|
||||
|
||||
String ImplContour::decodeCurved(InputArray in, InputArray points, OutputArray straight_qrcode)
|
||||
{
|
||||
Mat inarr;
|
||||
if (!checkQRInputImage(in, inarr))
|
||||
@@ -2833,7 +2855,7 @@ cv::String QRCodeDetector::decodeCurved(InputArray in, InputArray points,
|
||||
CV_Assert(src_points.size() == 4);
|
||||
CV_CheckGT(contourArea(src_points), 0.0, "Invalid QR code source points");
|
||||
|
||||
QRDecode qrdec(p->useAlignmentMarkers);
|
||||
QRDecode qrdec(useAlignmentMarkers);
|
||||
qrdec.init(inarr, src_points);
|
||||
bool ok = qrdec.curvedDecodingProcess();
|
||||
|
||||
@@ -2851,10 +2873,7 @@ cv::String QRCodeDetector::decodeCurved(InputArray in, InputArray points,
|
||||
return ok ? decoded_info : std::string();
|
||||
}
|
||||
|
||||
std::string QRCodeDetector::detectAndDecode(InputArray in,
|
||||
OutputArray points_,
|
||||
OutputArray straight_qrcode)
|
||||
{
|
||||
std::string ImplContour::detectAndDecode(InputArray in, OutputArray points_, OutputArray straight_qrcode) const {
|
||||
Mat inarr;
|
||||
if (!checkQRInputImage(in, inarr))
|
||||
{
|
||||
@@ -2874,9 +2893,14 @@ std::string QRCodeDetector::detectAndDecode(InputArray in,
|
||||
return decoded_info;
|
||||
}
|
||||
|
||||
std::string QRCodeDetector::detectAndDecodeCurved(InputArray in,
|
||||
OutputArray points_,
|
||||
OutputArray straight_qrcode)
|
||||
std::string QRCodeDetector::detectAndDecodeCurved(InputArray in, OutputArray points,
|
||||
OutputArray straight_qrcode) {
|
||||
CV_Assert(p);
|
||||
return std::dynamic_pointer_cast<ImplContour>(p)->detectAndDecodeCurved(in, points, straight_qrcode);
|
||||
}
|
||||
|
||||
std::string ImplContour::detectAndDecodeCurved(InputArray in, OutputArray points_,
|
||||
OutputArray straight_qrcode)
|
||||
{
|
||||
Mat inarr;
|
||||
if (!checkQRInputImage(in, inarr))
|
||||
@@ -3817,31 +3841,28 @@ bool QRDetectMulti::computeTransformationPoints(const size_t cur_ind)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QRCodeDetector::detectMulti(InputArray in, OutputArray points) const
|
||||
{
|
||||
Mat inarr;
|
||||
if (!checkQRInputImage(in, inarr))
|
||||
{
|
||||
bool ImplContour::detectMulti(InputArray in, OutputArray points) const {
|
||||
Mat gray;
|
||||
if (!checkQRInputImage(in, gray)) {
|
||||
points.release();
|
||||
return false;
|
||||
}
|
||||
|
||||
vector<Point2f> result;
|
||||
QRDetectMulti qrdet;
|
||||
qrdet.init(inarr, p->epsX, p->epsY);
|
||||
if (!qrdet.localization())
|
||||
{
|
||||
qrdet.init(gray, epsX, epsY);
|
||||
if (!qrdet.localization()) {
|
||||
points.release();
|
||||
return false;
|
||||
}
|
||||
vector< vector< Point2f > > pnts2f = qrdet.getTransformationPoints();
|
||||
vector<Point2f> trans_points;
|
||||
vector<vector<Point2f> > pnts2f = qrdet.getTransformationPoints();
|
||||
for(size_t i = 0; i < pnts2f.size(); i++)
|
||||
for(size_t j = 0; j < pnts2f[i].size(); j++)
|
||||
trans_points.push_back(pnts2f[i][j]);
|
||||
|
||||
updatePointsResult(points, trans_points);
|
||||
|
||||
return true;
|
||||
result.push_back(pnts2f[i][j]);
|
||||
if (result.size() >= 4) {
|
||||
updatePointsResult(points, result);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
class ParallelDecodeProcess : public ParallelLoopBody
|
||||
@@ -3902,7 +3923,7 @@ private:
|
||||
|
||||
};
|
||||
|
||||
bool QRCodeDetector::decodeMulti(
|
||||
bool ImplContour::decodeMulti(
|
||||
InputArray img,
|
||||
InputArray points,
|
||||
CV_OUT std::vector<cv::String>& decoded_info,
|
||||
@@ -3926,7 +3947,7 @@ bool QRCodeDetector::decodeMulti(
|
||||
}
|
||||
}
|
||||
CV_Assert(src_points.size() > 0);
|
||||
vector<QRDecode> qrdec(src_points.size(), p->useAlignmentMarkers);
|
||||
vector<QRDecode> qrdec(src_points.size(), useAlignmentMarkers);
|
||||
vector<Mat> straight_barcode(src_points.size());
|
||||
vector<std::string> info(src_points.size());
|
||||
ParallelDecodeProcess parallelDecodeProcess(inarr, qrdec, info, straight_barcode, src_points);
|
||||
@@ -3957,12 +3978,12 @@ bool QRCodeDetector::decodeMulti(
|
||||
{
|
||||
decoded_info.push_back(info[i]);
|
||||
}
|
||||
p->alignmentMarkers.resize(src_points.size());
|
||||
p->updateQrCorners.resize(src_points.size()*4ull);
|
||||
alignmentMarkers.resize(src_points.size());
|
||||
updateQrCorners.resize(src_points.size()*4ull);
|
||||
for (size_t i = 0ull; i < src_points.size(); i++) {
|
||||
p->alignmentMarkers[i] = qrdec[i].alignment_coords;
|
||||
alignmentMarkers[i] = qrdec[i].alignment_coords;
|
||||
for (size_t j = 0ull; j < 4ull; j++)
|
||||
p->updateQrCorners[i*4ull+j] = qrdec[i].getOriginalPoints()[j] * qrdec[i].coeff_expansion;
|
||||
updateQrCorners[i*4ull+j] = qrdec[i].getOriginalPoints()[j] * qrdec[i].coeff_expansion;
|
||||
}
|
||||
if (!decoded_info.empty())
|
||||
return true;
|
||||
@@ -3970,7 +3991,7 @@ bool QRCodeDetector::decodeMulti(
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QRCodeDetector::detectAndDecodeMulti(
|
||||
bool ImplContour::detectAndDecodeMulti(
|
||||
InputArray img,
|
||||
CV_OUT std::vector<cv::String>& decoded_info,
|
||||
OutputArray points_,
|
||||
@@ -3994,13 +4015,537 @@ bool QRCodeDetector::detectAndDecodeMulti(
|
||||
updatePointsResult(points_, points);
|
||||
decoded_info.clear();
|
||||
ok = decodeMulti(inarr, points, decoded_info, straight_qrcode);
|
||||
updatePointsResult(points_, p->updateQrCorners);
|
||||
updatePointsResult(points_, updateQrCorners);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void QRCodeDetector::setUseAlignmentMarkers(bool useAlignmentMarkers) {
|
||||
p->useAlignmentMarkers = useAlignmentMarkers;
|
||||
QRCodeDetector& QRCodeDetector::setUseAlignmentMarkers(bool useAlignmentMarkers) {
|
||||
(std::dynamic_pointer_cast<ImplContour>)(p)->useAlignmentMarkers = useAlignmentMarkers;
|
||||
return *this;
|
||||
}
|
||||
|
||||
QRCodeDetectorAruco::Params::Params() {
|
||||
minModuleSizeInPyramid = 4.f;
|
||||
maxRotation = (float)CV_PI/12.f;
|
||||
maxModuleSizeMismatch = 1.75f;
|
||||
maxTimingPatternMismatch = 2.f;
|
||||
maxPenalties = 0.4f;
|
||||
maxColorsMismatch = 0.2f;
|
||||
scaleTimingPatternScore = 0.9f;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct FinderPatternInfo {
|
||||
FinderPatternInfo() {}
|
||||
|
||||
FinderPatternInfo(const vector<Point2f>& patternPoints): points(patternPoints) {
|
||||
float minSin = 1.f;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
center += points[i];
|
||||
const Point2f side = points[i]-points[(i+1) % 4];
|
||||
const float lenSide = sqrt(normL2Sqr<float>(side));
|
||||
minSin = min(minSin, abs(side.y) / lenSide);
|
||||
moduleSize += lenSide;
|
||||
}
|
||||
moduleSize /= (4.f * 7.f); // 4 sides, 7 modules in one side
|
||||
center /= 4.f;
|
||||
minQrAngle = asin(minSin);
|
||||
}
|
||||
|
||||
enum TypePattern {
|
||||
CENTER,
|
||||
RIGHT,
|
||||
BOTTOM,
|
||||
NONE
|
||||
};
|
||||
|
||||
void setType(const TypePattern& _typePattern, const Point2f& centerQR) {
|
||||
typePattern = _typePattern;
|
||||
float bestLen = normL2Sqr<float>(centerQR - points[0]);
|
||||
int id = 0;
|
||||
for (int i = 1; i < 4; i++) {
|
||||
float len = normL2Sqr<float>(centerQR - points[i]);
|
||||
if (len < bestLen) {
|
||||
bestLen = len;
|
||||
id = i;
|
||||
}
|
||||
}
|
||||
innerCornerId = id;
|
||||
}
|
||||
|
||||
Point2f getDirectionTo(const TypePattern& other) const {
|
||||
Point2f res = points[innerCornerId];
|
||||
if (typePattern == TypePattern::CENTER) {
|
||||
if (other == TypePattern::RIGHT) {
|
||||
res -= points[(innerCornerId + 1) % 4];
|
||||
res = 0.5f*(res + points[(innerCornerId + 3) % 4] - points[(innerCornerId + 2) % 4]);
|
||||
}
|
||||
else if (other == TypePattern::BOTTOM) {
|
||||
res -= points[(innerCornerId + 3) % 4];
|
||||
res = 0.5f*(res + points[(innerCornerId + 1) % 4] - points[(innerCornerId + 2) % 4]);
|
||||
}
|
||||
}
|
||||
else if (typePattern == TypePattern::RIGHT && other == TypePattern::CENTER) {
|
||||
res = res - points[(innerCornerId + 3) % 4];
|
||||
res = 0.5f*(res + points[(innerCornerId + 1) % 4] - points[(innerCornerId + 2) % 4]);
|
||||
}
|
||||
else if (typePattern == TypePattern::BOTTOM && other == TypePattern::CENTER) {
|
||||
res = res - points[(innerCornerId + 1) % 4];
|
||||
res = 0.5f*(res + points[(innerCornerId + 3) % 4] - points[(innerCornerId + 2) % 4]);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool checkTriangleAngle(const FinderPatternInfo& patternRight, const FinderPatternInfo& patternBottom, const float length2Vec) {
|
||||
// check the triangle angle btw right & center & bootom sides of QR code
|
||||
// the triangle angle shoud be between 30 and 150 degrees
|
||||
// abs(pi/2 - triangle_angle) should be less 60 degrees
|
||||
const float angle = abs((float)CV_PI/2.f - acos((center - patternRight.center).dot((center - patternBottom.center)) / length2Vec));
|
||||
|
||||
const float maxTriangleDeltaAngle = (float)CV_PI / 3.f;
|
||||
if (angle > maxTriangleDeltaAngle) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool checkAngle(const FinderPatternInfo& other, const float maxRotation) {
|
||||
Point2f toOther = getDirectionTo(other.typePattern);
|
||||
Point2f toThis = other.getDirectionTo(typePattern);
|
||||
const float cosAngle = getCosAngle(toOther, toThis);
|
||||
if (cosAngle < 0.f && (CV_PI - acos(cosAngle)) / 2.f < maxRotation) {
|
||||
const float angleCenter = max(acos(getCosAngle(toOther, other.center - center)), acos(getCosAngle(toThis, center - other.center)));
|
||||
if (angleCenter < maxRotation)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static float getCosAngle(const Point2f& vec1, const Point2f& vec2) {
|
||||
float cosAngle = vec1.dot(vec2) / (sqrt(normL2Sqr<float>(vec1)) * sqrt(normL2Sqr<float>(vec2)));
|
||||
cosAngle = std::max(-1.f, cosAngle);
|
||||
cosAngle = std::min(1.f, cosAngle);
|
||||
return cosAngle;
|
||||
}
|
||||
|
||||
pair<int, Point2f> getQRCorner() const {
|
||||
if (typePattern == TypePattern::CENTER) {
|
||||
int id = (innerCornerId + 2) % 4;
|
||||
return std::make_pair(id, points[id]);
|
||||
}
|
||||
else if (typePattern != TypePattern::NONE) {
|
||||
int id = (innerCornerId + 2) % 4;
|
||||
return std::make_pair(id, points[id]);
|
||||
}
|
||||
return std::make_pair(-1, Point2f());
|
||||
}
|
||||
|
||||
pair<int, Point2f> getCornerForIntersection() const {
|
||||
if (typePattern == TypePattern::RIGHT) {
|
||||
int id = (innerCornerId + 3) % 4;
|
||||
return std::make_pair(id, points[id]);
|
||||
}
|
||||
else if (typePattern == TypePattern::BOTTOM) {
|
||||
int id = (innerCornerId + 1) % 4;
|
||||
return std::make_pair(id, points[id]);
|
||||
}
|
||||
return std::make_pair(-1, Point2f());
|
||||
}
|
||||
|
||||
Point2f getTimingStart(TypePattern direction) const {
|
||||
const float timingStartPosition = .5f;
|
||||
const float patternLength = 7.f;
|
||||
Point2f start = points[innerCornerId]*((patternLength - timingStartPosition)/patternLength);
|
||||
if (typePattern == TypePattern::CENTER && direction == TypePattern::RIGHT) {
|
||||
start += points[(innerCornerId + 3) % 4]*(timingStartPosition/patternLength);
|
||||
}
|
||||
else if (typePattern == TypePattern::CENTER && direction == TypePattern::BOTTOM) {
|
||||
start += points[(innerCornerId + 1) % 4]*(timingStartPosition/patternLength);
|
||||
}
|
||||
else if (typePattern == TypePattern::RIGHT && direction == TypePattern::CENTER) {
|
||||
start += points[(innerCornerId + 1) % 4]*(timingStartPosition/patternLength);
|
||||
}
|
||||
else if (typePattern == TypePattern::BOTTOM && direction == TypePattern::CENTER) {
|
||||
start += points[(innerCornerId + 3) % 4]*(timingStartPosition/patternLength);
|
||||
}
|
||||
return start + getDirectionTo(direction)/(patternLength*2.f);
|
||||
}
|
||||
|
||||
// return total white+black modules in timing pattern, total white modules, penaltyPoints
|
||||
Point3i getTimingPatternScore(const Point2f& start, const Point2f& end, Mat &img, const float maxTimingPatternMismatch) const {
|
||||
Rect imageRect(Point(), img.size());
|
||||
int penaltyPoints = 0;
|
||||
int colorCounters[2] = {0, 0};
|
||||
if (imageRect.contains(Point(cvRound(end.x), cvRound(end.y)))) {
|
||||
LineIterator lineIterator(start, end);
|
||||
uint8_t prevValue = img.at<uint8_t>(lineIterator.pos());
|
||||
|
||||
vector<Point> vec = {lineIterator.pos()};
|
||||
|
||||
// the starting position in the timing pattern is the white module white module next to the finder pattern.
|
||||
bool whiteColor = true;
|
||||
lineIterator++;
|
||||
colorCounters[whiteColor]++;
|
||||
|
||||
for(int i = 1; i < lineIterator.count; i++, ++lineIterator) {
|
||||
const uint8_t value = img.at<uint8_t>(lineIterator.pos());
|
||||
if (prevValue != value) {
|
||||
const float dist = sqrt(normL2Sqr<float>((Point2f)(vec.back()-lineIterator.pos())));
|
||||
// check long and short lines in timing pattern
|
||||
const float relativeDiff = max(moduleSize, dist)/min(moduleSize, dist);
|
||||
if (relativeDiff > maxTimingPatternMismatch) {
|
||||
if (dist < moduleSize || relativeDiff < maxTimingPatternMismatch*8.f)
|
||||
penaltyPoints++;
|
||||
else
|
||||
penaltyPoints += cvRound(relativeDiff);
|
||||
}
|
||||
vec.push_back(lineIterator.pos());
|
||||
prevValue = value;
|
||||
whiteColor ^= true;
|
||||
colorCounters[whiteColor]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Point3i(colorCounters[0] + colorCounters[1], colorCounters[1], penaltyPoints);
|
||||
}
|
||||
|
||||
FinderPatternInfo& operator*=(const float scale) {
|
||||
moduleSize *= scale;
|
||||
center *= scale;
|
||||
for (auto& point: points)
|
||||
point *= scale;
|
||||
return *this;
|
||||
}
|
||||
|
||||
float moduleSize = 0.f;
|
||||
|
||||
// Index of inner QR corner.
|
||||
// The inner corner is the corner closest to the center of the QR code.
|
||||
int innerCornerId = 0;
|
||||
|
||||
float minQrAngle = 0.f;
|
||||
TypePattern typePattern = NONE;
|
||||
|
||||
Point2f center;
|
||||
vector<Point2f> points;
|
||||
};
|
||||
|
||||
struct QRCode {
|
||||
QRCode() {}
|
||||
|
||||
QRCode(const FinderPatternInfo& _centerPattern, const FinderPatternInfo& _rightPattern, const FinderPatternInfo& _bottomPattern,
|
||||
Point2f _center, float dist): centerPattern(_centerPattern), rightPattern(_rightPattern), bottomPattern(_bottomPattern),
|
||||
center(_center), distance(dist) {
|
||||
moduleSize = (centerPattern.moduleSize + rightPattern.moduleSize + bottomPattern.moduleSize) / 3.f;
|
||||
}
|
||||
|
||||
vector<Point2f> getQRCorners() const {
|
||||
Point2f a1 = rightPattern.getQRCorner().second;
|
||||
Point2f a2 = rightPattern.getCornerForIntersection().second;
|
||||
|
||||
Point2f b1 = bottomPattern.getQRCorner().second;
|
||||
Point2f b2 = bottomPattern.getCornerForIntersection().second;
|
||||
|
||||
Point2f rightBottom = intersectionLines(a1, a2, b1, b2);
|
||||
|
||||
return {centerPattern.getQRCorner().second, rightPattern.getQRCorner().second, rightBottom, bottomPattern.getQRCorner().second};
|
||||
}
|
||||
|
||||
static QRCode checkCompatibilityPattern(const FinderPatternInfo &_pattern1, const FinderPatternInfo& _pattern2, const FinderPatternInfo& _pattern3,
|
||||
Point3i& index, const QRCodeDetectorAruco::Params& qrDetectorParameters) {
|
||||
FinderPatternInfo pattern1 = _pattern1, pattern2 = _pattern2, pattern3 = _pattern3;
|
||||
Point2f centerQR;
|
||||
float distance = std::numeric_limits<float>::max();
|
||||
|
||||
if (abs(pattern1.minQrAngle - pattern2.minQrAngle) > qrDetectorParameters.maxRotation ||
|
||||
abs(pattern1.minQrAngle - pattern3.minQrAngle) > qrDetectorParameters.maxRotation) // check maxRotation
|
||||
return QRCode(pattern1, pattern2, pattern3, centerQR, distance);
|
||||
if (max(pattern1.moduleSize, pattern2.moduleSize) / min(pattern1.moduleSize, pattern2.moduleSize) > qrDetectorParameters.maxModuleSizeMismatch ||
|
||||
max(pattern1.moduleSize, pattern3.moduleSize) / min(pattern1.moduleSize, pattern3.moduleSize) > qrDetectorParameters.maxModuleSizeMismatch)
|
||||
return QRCode(pattern1, pattern2, pattern3, centerQR, distance);
|
||||
// QR code:
|
||||
// center right
|
||||
// 1 ________ 2
|
||||
// |_| |_|
|
||||
// | / |
|
||||
// | / |
|
||||
// | / |
|
||||
// |_ / |
|
||||
// |_|______|
|
||||
// 4
|
||||
// bottom
|
||||
|
||||
// sides length check
|
||||
const float side1 = sqrt(normL2Sqr<float>(pattern1.center - pattern2.center));
|
||||
const float side2 = sqrt(normL2Sqr<float>(pattern1.center - pattern3.center));
|
||||
const float side3 = sqrt(normL2Sqr<float>(pattern2.center - pattern3.center));
|
||||
std::array<float, 3> sides = {side1, side2, side3};
|
||||
std::sort(sides.begin(), sides.end());
|
||||
// check sides diff
|
||||
if (sides[1] / sides[0] < qrDetectorParameters.maxModuleSizeMismatch) {
|
||||
// find center pattern
|
||||
if (side1 > side2 && side1 > side3) { // centerPattern is pattern3
|
||||
std::swap(pattern3, pattern1); // now pattern1 is centerPattern
|
||||
std::swap(index.x, index.z);
|
||||
}
|
||||
else if (side2 > side1 && side2 > side3) { // centerPattern is pattern2
|
||||
std::swap(pattern2, pattern1); // now pattern1 is centerPattern
|
||||
std::swap(index.x, index.y);
|
||||
}
|
||||
// now pattern1 is centerPattern
|
||||
centerQR = (pattern2.center + pattern3.center) / 2.f;
|
||||
pattern1.setType(FinderPatternInfo::TypePattern::CENTER, centerQR);
|
||||
// check triangle angle
|
||||
if (pattern1.checkTriangleAngle(pattern2, pattern3, sides[0]*sides[1]) == false)
|
||||
return QRCode(pattern1, pattern2, pattern3, centerQR, distance);
|
||||
// check that pattern2 is right
|
||||
pattern2.setType(FinderPatternInfo::TypePattern::RIGHT, centerQR);
|
||||
bool ok = pattern1.checkAngle(pattern2, qrDetectorParameters.maxRotation);
|
||||
if (!ok) {
|
||||
// check that pattern3 is right
|
||||
pattern3.setType(FinderPatternInfo::TypePattern::RIGHT, centerQR);
|
||||
ok = pattern1.checkAngle(pattern3, qrDetectorParameters.maxRotation);
|
||||
if (ok) {
|
||||
std::swap(pattern3, pattern2); // now pattern2 is rightPattern
|
||||
std::swap(index.y, index.z);
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
// check that pattern3 is bottom
|
||||
pattern3.setType(FinderPatternInfo::TypePattern::BOTTOM, centerQR);
|
||||
ok = pattern1.checkAngle(pattern3, qrDetectorParameters.maxRotation);
|
||||
if (ok) {
|
||||
// intersection check
|
||||
Point2f c1 = intersectionLines(pattern1.getQRCorner().second, pattern1.points[pattern1.innerCornerId],
|
||||
pattern2.getQRCorner().second, pattern2.points[pattern2.innerCornerId]);
|
||||
Point2f c2 = intersectionLines(pattern1.getQRCorner().second, pattern1.points[pattern1.innerCornerId],
|
||||
pattern3.getQRCorner().second, pattern3.points[pattern3.innerCornerId]);
|
||||
const float centerDistance = sqrt(normL2Sqr<float>(c1 - c2));
|
||||
distance = (sides[0] + sides[1] + centerDistance)*(sides[1] / sides[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
QRCode qrcode(pattern1, pattern2, pattern3, centerQR, distance);
|
||||
return qrcode;
|
||||
}
|
||||
|
||||
int calculateScoreByTimingPattern(Mat &img, const QRCodeDetectorAruco::Params& params) {
|
||||
const int minModulesInTimingPattern = 4;
|
||||
|
||||
const Point3i v1 = centerPattern.getTimingPatternScore(rightPattern.getTimingStart(FinderPatternInfo::CENTER),
|
||||
centerPattern.getTimingStart(FinderPatternInfo::RIGHT), img,
|
||||
params.maxTimingPatternMismatch);
|
||||
|
||||
if ((float)v1.z > params.maxPenalties*v1.x || v1.x <= minModulesInTimingPattern || abs(v1.y / (float)v1.x - 0.5f) > params.maxColorsMismatch)
|
||||
return std::numeric_limits<int>::max();
|
||||
|
||||
const Point3i v2 = centerPattern.getTimingPatternScore(bottomPattern.getTimingStart(FinderPatternInfo::CENTER),
|
||||
centerPattern.getTimingStart(FinderPatternInfo::BOTTOM), img,
|
||||
params.maxTimingPatternMismatch);
|
||||
|
||||
|
||||
if ((float)v2.z > params.maxPenalties*v2.x || v2.x <= minModulesInTimingPattern || abs(v2.y / (float)v2.x - 0.5f) > params.maxColorsMismatch)
|
||||
return std::numeric_limits<int>::max();
|
||||
|
||||
// TODO: add v1, v2 check, add "y" checks
|
||||
float numModules = (sqrt(normL2Sqr<float>((centerPattern.getQRCorner().second - rightPattern.getQRCorner().second)))*0.5f +
|
||||
sqrt(normL2Sqr<float>((centerPattern.getQRCorner().second - bottomPattern.getQRCorner().second))*0.5f)) / moduleSize;
|
||||
|
||||
const int sizeDelta = abs(cvRound(numModules) - (14 + v1.z < v2.z ? v1.x : v2.x));
|
||||
const int colorDelta = abs(v1.x - v1.y - v1.y) + abs(v2.x - v2.y - v2.y);
|
||||
const int score = v1.z + v2.z + sizeDelta + colorDelta;
|
||||
return score;
|
||||
}
|
||||
|
||||
QRCode& operator*=(const float scale) {
|
||||
centerPattern *= scale;
|
||||
rightPattern *= scale;
|
||||
bottomPattern *= scale;
|
||||
center *= scale;
|
||||
moduleSize *= scale;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FinderPatternInfo centerPattern;
|
||||
FinderPatternInfo rightPattern;
|
||||
FinderPatternInfo bottomPattern;
|
||||
Point2f center;
|
||||
float distance = std::numeric_limits<float>::max();
|
||||
int timingPatternScore = std::numeric_limits<int>::max();
|
||||
float moduleSize = 0.f;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
static
|
||||
vector<QRCode> analyzeFinderPatterns(const vector<vector<Point2f> > &corners, const Mat& img,
|
||||
const QRCodeDetectorAruco::Params& qrDetectorParameters) {
|
||||
vector<QRCode> qrCodes;
|
||||
vector<FinderPatternInfo> patterns;
|
||||
if (img.empty())
|
||||
return qrCodes;
|
||||
float maxModuleSize = 0.f;
|
||||
for (size_t i = 0ull; i < corners.size(); i++) {
|
||||
FinderPatternInfo pattern = FinderPatternInfo(corners[i]);
|
||||
// TODO: improve thinning Aruco markers
|
||||
bool isUniq = true;
|
||||
for (const FinderPatternInfo& tmp : patterns) {
|
||||
Point2f dist = pattern.center - tmp.center;
|
||||
if (max(abs(dist.x), abs(dist.y)) < 3.f * tmp.moduleSize) {
|
||||
isUniq = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isUniq) {
|
||||
patterns.push_back(pattern);
|
||||
maxModuleSize = max(maxModuleSize, patterns.back().moduleSize);
|
||||
}
|
||||
}
|
||||
const int threshold = cvRound(qrDetectorParameters.minModuleSizeInPyramid * 12.5f) +
|
||||
(cvRound(qrDetectorParameters.minModuleSizeInPyramid * 12.5f) % 2 ? 0 : 1);
|
||||
int maxLevelPyramid = 0;
|
||||
while (maxModuleSize / 2.f > qrDetectorParameters.minModuleSizeInPyramid) {
|
||||
maxLevelPyramid++;
|
||||
maxModuleSize /= 2.f;
|
||||
}
|
||||
vector<Mat> pyramid;
|
||||
buildPyramid(img, pyramid, maxLevelPyramid);
|
||||
// TODO: ADAPTIVE_THRESH_GAUSSIAN_C vs ADAPTIVE_THRESH_MEAN_C
|
||||
for (Mat& pyr: pyramid) {
|
||||
adaptiveThreshold(pyr, pyr, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, threshold, -1);
|
||||
}
|
||||
|
||||
for (size_t i = 0ull; i < patterns.size(); i++) {
|
||||
QRCode qrCode;
|
||||
int indexes[3] = {0};
|
||||
for (size_t j = i + 1ull; j < patterns.size(); j++) {
|
||||
for (size_t k = j + 1ull; k < patterns.size(); k++) {
|
||||
Point3i index((int)i, (int)j, (int)k);
|
||||
QRCode tmp = QRCode::checkCompatibilityPattern(patterns[i], patterns[j], patterns[k], index, qrDetectorParameters);
|
||||
if (tmp.distance != std::numeric_limits<float>::max()) {
|
||||
int levelPyramid = 0;
|
||||
QRCode qrCopy = tmp;
|
||||
while (tmp.moduleSize / 2.f > qrDetectorParameters.minModuleSizeInPyramid) {
|
||||
tmp *= 0.5f;
|
||||
levelPyramid++;
|
||||
}
|
||||
qrCopy.timingPatternScore = tmp.calculateScoreByTimingPattern(pyramid[levelPyramid], qrDetectorParameters);
|
||||
if (qrCopy.timingPatternScore != std::numeric_limits<int>::max() &&
|
||||
qrCopy.timingPatternScore * qrDetectorParameters.scaleTimingPatternScore < (float)qrCode.timingPatternScore
|
||||
&& qrCopy.distance < qrCode.distance)
|
||||
{
|
||||
qrCode = qrCopy;
|
||||
indexes[0] = (int)i;
|
||||
indexes[1] = (int)j;
|
||||
indexes[2] = (int)k;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (qrCode.distance != std::numeric_limits<float>::max()) {
|
||||
qrCodes.push_back(qrCode);
|
||||
std::swap(patterns[indexes[2]], patterns.back());
|
||||
patterns.pop_back();
|
||||
std::swap(patterns[indexes[1]], patterns.back());
|
||||
patterns.pop_back();
|
||||
std::swap(patterns[indexes[0]], patterns.back());
|
||||
patterns.pop_back();
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return qrCodes;
|
||||
}
|
||||
|
||||
struct PimplQRAruco : public ImplContour {
|
||||
QRCodeDetectorAruco::Params qrParams;
|
||||
aruco::ArucoDetector arucoDetector;
|
||||
aruco::DetectorParameters arucoParams;
|
||||
|
||||
PimplQRAruco() {
|
||||
Mat bits = Mat::ones(Size(5, 5), CV_8UC1);
|
||||
Mat(bits, Rect(1, 1, 3, 3)).setTo(Scalar(0));
|
||||
Mat byteList = aruco::Dictionary::getByteListFromBits(bits);
|
||||
aruco::Dictionary dictionary = aruco::Dictionary(byteList, 5, 4);
|
||||
arucoParams.minMarkerPerimeterRate = 0.02;
|
||||
arucoDetector = aruco::ArucoDetector(dictionary, arucoParams);
|
||||
}
|
||||
|
||||
bool detectMulti(InputArray in, OutputArray points) const override {
|
||||
Mat gray;
|
||||
if (!checkQRInputImage(in, gray)) {
|
||||
points.release();
|
||||
return false;
|
||||
}
|
||||
vector<Point2f> result;
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
arucoDetector.detectMarkers(gray, corners, ids);
|
||||
if (corners.size() >= 3ull) {
|
||||
vector<QRCode> qrCodes = analyzeFinderPatterns(corners, gray.clone(), qrParams);
|
||||
if (qrCodes.size() == 0ull)
|
||||
return false;
|
||||
for (auto& qr : qrCodes) {
|
||||
for (Point2f& corner : qr.getQRCorners()) {
|
||||
result.push_back(corner);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.size() >= 4) {
|
||||
updatePointsResult(points, result);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool detect(InputArray img, OutputArray points) const override {
|
||||
vector<Point2f> corners, result;
|
||||
bool flag = detectMulti(img, corners);
|
||||
CV_Assert((int)corners.size() % 4 == 0);
|
||||
|
||||
Point2f imageCenter(((float)img.cols())/2.f, ((float)img.rows())/2.f);
|
||||
size_t minQrId = 0ull;
|
||||
float minDist = std::numeric_limits<float>::max();
|
||||
for (size_t i = 0ull; i < corners.size(); i += 4ull) {
|
||||
Point2f qrCenter((corners[i] + corners[i+1ull] + corners[i+2ull] + corners[i+3ull]) / 4.f);
|
||||
float dist = sqrt(normL2Sqr<float>(qrCenter - imageCenter));
|
||||
if (dist < minDist) {
|
||||
minQrId = i;
|
||||
minDist = dist;
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
result = {corners[minQrId], corners[minQrId+1ull], corners[minQrId+2ull], corners[minQrId+3ull]};
|
||||
updatePointsResult(points, result);
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
};
|
||||
|
||||
QRCodeDetectorAruco::QRCodeDetectorAruco() {
|
||||
p = makePtr<PimplQRAruco>();
|
||||
}
|
||||
|
||||
QRCodeDetectorAruco::QRCodeDetectorAruco(const QRCodeDetectorAruco::Params& params) {
|
||||
p = makePtr<PimplQRAruco>();
|
||||
std::dynamic_pointer_cast<PimplQRAruco>(p)->qrParams = params;
|
||||
}
|
||||
|
||||
const QRCodeDetectorAruco::Params& QRCodeDetectorAruco::getDetectorParameters() const {
|
||||
return std::dynamic_pointer_cast<PimplQRAruco>(p)->qrParams;
|
||||
}
|
||||
|
||||
QRCodeDetectorAruco& QRCodeDetectorAruco::setDetectorParameters(const QRCodeDetectorAruco::Params& params) {
|
||||
std::dynamic_pointer_cast<PimplQRAruco>(p)->qrParams = params;
|
||||
return *this;
|
||||
}
|
||||
|
||||
aruco::DetectorParameters QRCodeDetectorAruco::getArucoParameters() {
|
||||
return std::dynamic_pointer_cast<PimplQRAruco>(p)->arucoParams;
|
||||
}
|
||||
|
||||
void QRCodeDetectorAruco::setArucoParameters(const aruco::DetectorParameters& params) {
|
||||
std::dynamic_pointer_cast<PimplQRAruco>(p)->arucoParams = params;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -268,12 +268,12 @@ void CV_ArucoDetectionPerspective::run(int) {
|
||||
}
|
||||
|
||||
if(ArucoAlgParams::USE_APRILTAG == arucoAlgParams){
|
||||
detectorParameters.cornerRefinementMethod = aruco::CORNER_REFINE_APRILTAG;
|
||||
detectorParameters.cornerRefinementMethod = (int)aruco::CORNER_REFINE_APRILTAG;
|
||||
}
|
||||
|
||||
if (ArucoAlgParams::USE_ARUCO3 == arucoAlgParams) {
|
||||
detectorParameters.useAruco3Detection = true;
|
||||
detectorParameters.cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX;
|
||||
detectorParameters.cornerRefinementMethod = (int)aruco::CORNER_REFINE_SUBPIX;
|
||||
}
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
|
||||
@@ -653,7 +653,7 @@ TEST_P(ArucoThreading, number_of_threads_does_not_change_results)
|
||||
img_marker.copyTo(img(Rect(shift, shift, height_marker, height_marker)));
|
||||
|
||||
aruco::DetectorParameters detectorParameters = detector.getDetectorParameters();
|
||||
detectorParameters.cornerRefinementMethod = GetParam();
|
||||
detectorParameters.cornerRefinementMethod = (int)GetParam();
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
|
||||
vector<vector<Point2f> > original_corners;
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "opencv2/objdetect/barcode.hpp"
|
||||
#include <set>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace opencv_test{namespace{
|
||||
|
||||
typedef std::set<string> StringSet;
|
||||
|
||||
// Convert ';'-separated strings to a set
|
||||
inline static StringSet toSet(const string &line)
|
||||
{
|
||||
StringSet res;
|
||||
string::size_type it = 0, ti;
|
||||
while (true)
|
||||
{
|
||||
ti = line.find(';', it);
|
||||
if (ti == string::npos)
|
||||
{
|
||||
res.insert(string(line, it, line.size() - it));
|
||||
break;
|
||||
}
|
||||
res.insert(string(line, it, ti - it));
|
||||
it = ti + 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// Convert vector of strings to a set
|
||||
inline static StringSet toSet(const vector<string> &lines)
|
||||
{
|
||||
StringSet res;
|
||||
for (const string & line : lines)
|
||||
res.insert(line);
|
||||
return res;
|
||||
}
|
||||
|
||||
// Get all keys of a map in a vector
|
||||
template<typename T, typename V>
|
||||
inline static vector<T> getKeys(const map<T, V> &m)
|
||||
{
|
||||
vector<T> res;
|
||||
for (const auto & it : m)
|
||||
res.push_back(it.first);
|
||||
return res;
|
||||
}
|
||||
|
||||
struct BarcodeResult
|
||||
{
|
||||
string type;
|
||||
string data;
|
||||
};
|
||||
|
||||
map<string, BarcodeResult> testResults {
|
||||
{ "single/book.jpg", {"EAN_13", "9787115279460"} },
|
||||
{ "single/bottle_1.jpg", {"EAN_13", "6922255451427"} },
|
||||
{ "single/bottle_2.jpg", {"EAN_13", "6921168509256"} },
|
||||
{ "multiple/4_barcodes.jpg", {"EAN_13;EAN_13;EAN_13;EAN_13", "9787564350840;9783319200064;9787118081473;9787122276124"} }
|
||||
};
|
||||
|
||||
typedef testing::TestWithParam< string > BarcodeDetector_main;
|
||||
|
||||
TEST_P(BarcodeDetector_main, interface)
|
||||
{
|
||||
const string fname = GetParam();
|
||||
const string image_path = findDataFile(string("barcode/") + fname);
|
||||
const StringSet expected_lines = toSet(testResults[fname].data);
|
||||
const StringSet expected_types = toSet(testResults[fname].type);
|
||||
const size_t expected_count = expected_lines.size(); // assume codes are unique
|
||||
// TODO: verify points location
|
||||
|
||||
Mat img = imread(image_path);
|
||||
ASSERT_FALSE(img.empty()) << "Can't read image: " << image_path;
|
||||
|
||||
barcode::BarcodeDetector det;
|
||||
vector<Point2f> points;
|
||||
vector<string> types;
|
||||
vector<string> lines;
|
||||
|
||||
// common interface (single)
|
||||
{
|
||||
bool res = det.detect(img, points);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(expected_count * 4, points.size());
|
||||
}
|
||||
|
||||
{
|
||||
string res = det.decode(img, points);
|
||||
ASSERT_FALSE(res.empty());
|
||||
EXPECT_EQ(1u, expected_lines.count(res));
|
||||
}
|
||||
|
||||
// common interface (multi)
|
||||
{
|
||||
bool res = det.detectMulti(img, points);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(expected_count * 4, points.size());
|
||||
}
|
||||
|
||||
{
|
||||
bool res = det.decodeMulti(img, points, lines);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(expected_lines, toSet(lines));
|
||||
}
|
||||
|
||||
// specific interface
|
||||
{
|
||||
bool res = det.decodeWithType(img, points, lines, types);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(expected_types, toSet(types));
|
||||
EXPECT_EQ(expected_lines, toSet(lines));
|
||||
}
|
||||
|
||||
{
|
||||
bool res = det.detectAndDecodeWithType(img, lines, types, points);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(expected_types, toSet(types));
|
||||
EXPECT_EQ(expected_lines, toSet(lines));
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/**/, BarcodeDetector_main, testing::ValuesIn(getKeys(testResults)));
|
||||
|
||||
TEST(BarcodeDetector_base, invalid)
|
||||
{
|
||||
auto bardet = barcode::BarcodeDetector();
|
||||
std::vector<Point> corners;
|
||||
vector<cv::String> decoded_info;
|
||||
Mat zero_image = Mat::zeros(256, 256, CV_8UC1);
|
||||
EXPECT_FALSE(bardet.detectMulti(zero_image, corners));
|
||||
corners = std::vector<Point>(4);
|
||||
EXPECT_ANY_THROW(bardet.decodeMulti(zero_image, corners, decoded_info));
|
||||
}
|
||||
|
||||
}} // opencv_test::<anonymous>::
|
||||
@@ -26,7 +26,7 @@ class CV_ArucoBoardPose : public cvtest::BaseTest {
|
||||
params.minDistanceToBorder = 3;
|
||||
if (arucoAlgParams == ArucoAlgParams::USE_ARUCO3) {
|
||||
params.useAruco3Detection = true;
|
||||
params.cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX;
|
||||
params.cornerRefinementMethod = (int)aruco::CORNER_REFINE_SUBPIX;
|
||||
params.minSideLengthCanonicalImg = 16;
|
||||
params.errorCorrectionRate = 0.8;
|
||||
}
|
||||
@@ -137,7 +137,7 @@ class CV_ArucoRefine : public cvtest::BaseTest {
|
||||
aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
|
||||
aruco::DetectorParameters params;
|
||||
params.minDistanceToBorder = 3;
|
||||
params.cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX;
|
||||
params.cornerRefinementMethod = (int)aruco::CORNER_REFINE_SUBPIX;
|
||||
if (arucoAlgParams == ArucoAlgParams::USE_ARUCO3)
|
||||
params.useAruco3Detection = true;
|
||||
aruco::RefineParameters refineParams(10.f, 3.f, true);
|
||||
|
||||
@@ -612,7 +612,7 @@ TEST(Charuco, testBoardSubpixelCoords)
|
||||
cv::GaussianBlur(gray, gray, Size(5, 5), 1.0);
|
||||
|
||||
aruco::DetectorParameters params;
|
||||
params.cornerRefinementMethod = cv::aruco::CORNER_REFINE_APRILTAG;
|
||||
params.cornerRefinementMethod = (int)cv::aruco::CORNER_REFINE_APRILTAG;
|
||||
|
||||
aruco::CharucoParameters charucoParameters;
|
||||
charucoParameters.cameraMatrix = K;
|
||||
@@ -636,7 +636,7 @@ TEST(Charuco, issue_14014)
|
||||
Mat img = imread(imgPath);
|
||||
|
||||
aruco::DetectorParameters detectorParams;
|
||||
detectorParams.cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX;
|
||||
detectorParams.cornerRefinementMethod = (int)aruco::CORNER_REFINE_SUBPIX;
|
||||
detectorParams.cornerRefinementMinAccuracy = 0.01;
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_7X7_250), detectorParams);
|
||||
aruco::CharucoBoard board(Size(8, 5), 0.03455f, 0.02164f, detector.getDictionary());
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
namespace opencv_test {
|
||||
|
||||
static inline
|
||||
void check_qr(const string& root, const string& name_current_image, const string& config_name,
|
||||
const std::vector<Point>& corners,
|
||||
const std::vector<string>& decoded_info, const int max_pixel_error,
|
||||
bool isMulti = false) {
|
||||
const std::string dataset_config = findDataFile(root + "dataset_config.json");
|
||||
FileStorage file_config(dataset_config, FileStorage::READ);
|
||||
ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config;
|
||||
FileNode images_list = file_config[config_name];
|
||||
size_t images_count = static_cast<size_t>(images_list.size());
|
||||
ASSERT_GT(images_count, 0u) << "Can't find validation data entries in 'test_images': " << dataset_config;
|
||||
for (size_t index = 0; index < images_count; index++) {
|
||||
FileNode config = images_list[(int)index];
|
||||
std::string name_test_image = config["image_name"];
|
||||
if (name_test_image == name_current_image) {
|
||||
if (isMulti) {
|
||||
for(int j = 0; j < int(corners.size()); j += 4) {
|
||||
bool ok = false;
|
||||
for (int k = 0; k < int(corners.size() / 4); k++) {
|
||||
int count_eq_points = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int x = config["x"][k][i];
|
||||
int y = config["y"][k][i];
|
||||
if(((abs(corners[j + i].x - x)) <= max_pixel_error) && ((abs(corners[j + i].y - y)) <= max_pixel_error))
|
||||
count_eq_points++;
|
||||
}
|
||||
if (count_eq_points == 4) {
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(ok);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < (int)corners.size(); i++) {
|
||||
int x = config["x"][i];
|
||||
int y = config["y"][i];
|
||||
EXPECT_NEAR(x, corners[i].x, max_pixel_error);
|
||||
EXPECT_NEAR(y, corners[i].y, max_pixel_error);
|
||||
}
|
||||
}
|
||||
#ifdef HAVE_QUIRC
|
||||
if (decoded_info.size() == 0ull)
|
||||
return;
|
||||
if (isMulti) {
|
||||
size_t count_eq_info = 0;
|
||||
for(int i = 0; i < int(decoded_info.size()); i++) {
|
||||
for(int j = 0; j < int(decoded_info.size()); j++) {
|
||||
std::string original_info = config["info"][j];
|
||||
if(original_info == decoded_info[i]) {
|
||||
count_eq_info++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(decoded_info.size(), count_eq_info);
|
||||
}
|
||||
else {
|
||||
std::string original_info = config["info"];
|
||||
EXPECT_EQ(decoded_info[0], original_info);
|
||||
}
|
||||
#endif
|
||||
return; // done
|
||||
}
|
||||
}
|
||||
FAIL() << "Not found results for '" << name_current_image << "' image in config file:" << dataset_config <<
|
||||
"Re-run tests with enabled UPDATE_QRCODE_TEST_DATA macro to update test data.\n";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_qr_utils.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
@@ -33,6 +34,8 @@ std::string qrcode_images_multiple[] = {
|
||||
"5_qrcodes.png", "6_qrcodes.png", "7_qrcodes.png", "8_close_qrcodes.png"
|
||||
};
|
||||
|
||||
static std::set<std::pair<std::string, std::string>> disabled_samples = {{"5_qrcodes.png", "aruco_based"}};
|
||||
|
||||
//#define UPDATE_QRCODE_TEST_DATA
|
||||
#ifdef UPDATE_QRCODE_TEST_DATA
|
||||
|
||||
@@ -262,43 +265,7 @@ TEST_P(Objdetect_QRCode, regression)
|
||||
#else
|
||||
ASSERT_TRUE(qrcode.detect(src, corners));
|
||||
#endif
|
||||
|
||||
const std::string dataset_config = findDataFile(root + "dataset_config.json");
|
||||
FileStorage file_config(dataset_config, FileStorage::READ);
|
||||
ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config;
|
||||
{
|
||||
FileNode images_list = file_config["test_images"];
|
||||
size_t images_count = static_cast<size_t>(images_list.size());
|
||||
ASSERT_GT(images_count, 0u) << "Can't find validation data entries in 'test_images': " << dataset_config;
|
||||
|
||||
for (size_t index = 0; index < images_count; index++)
|
||||
{
|
||||
FileNode config = images_list[(int)index];
|
||||
std::string name_test_image = config["image_name"];
|
||||
if (name_test_image == name_current_image)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int x = config["x"][i];
|
||||
int y = config["y"][i];
|
||||
EXPECT_NEAR(x, corners[i].x, pixels_error);
|
||||
EXPECT_NEAR(y, corners[i].y, pixels_error);
|
||||
}
|
||||
|
||||
#ifdef HAVE_QUIRC
|
||||
std::string original_info = config["info"];
|
||||
EXPECT_EQ(decoded_info, original_info);
|
||||
#endif
|
||||
|
||||
return; // done
|
||||
}
|
||||
}
|
||||
std::cerr
|
||||
<< "Not found results for '" << name_current_image
|
||||
<< "' image in config file:" << dataset_config << std::endl
|
||||
<< "Re-run tests with enabled UPDATE_QRCODE_TEST_DATA macro to update test data."
|
||||
<< std::endl;
|
||||
}
|
||||
check_qr(root, name_current_image, "test_images", corners, {decoded_info}, pixels_error);
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam< std::string > Objdetect_QRCode_Close;
|
||||
@@ -329,43 +296,7 @@ TEST_P(Objdetect_QRCode_Close, regression)
|
||||
#else
|
||||
ASSERT_TRUE(qrcode.detect(barcode, corners));
|
||||
#endif
|
||||
|
||||
const std::string dataset_config = findDataFile(root + "dataset_config.json");
|
||||
FileStorage file_config(dataset_config, FileStorage::READ);
|
||||
ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config;
|
||||
{
|
||||
FileNode images_list = file_config["close_images"];
|
||||
size_t images_count = static_cast<size_t>(images_list.size());
|
||||
ASSERT_GT(images_count, 0u) << "Can't find validation data entries in 'test_images': " << dataset_config;
|
||||
|
||||
for (size_t index = 0; index < images_count; index++)
|
||||
{
|
||||
FileNode config = images_list[(int)index];
|
||||
std::string name_test_image = config["image_name"];
|
||||
if (name_test_image == name_current_image)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int x = config["x"][i];
|
||||
int y = config["y"][i];
|
||||
EXPECT_NEAR(x, corners[i].x, pixels_error);
|
||||
EXPECT_NEAR(y, corners[i].y, pixels_error);
|
||||
}
|
||||
|
||||
#ifdef HAVE_QUIRC
|
||||
std::string original_info = config["info"];
|
||||
EXPECT_EQ(decoded_info, original_info);
|
||||
#endif
|
||||
|
||||
return; // done
|
||||
}
|
||||
}
|
||||
std::cerr
|
||||
<< "Not found results for '" << name_current_image
|
||||
<< "' image in config file:" << dataset_config << std::endl
|
||||
<< "Re-run tests with enabled UPDATE_QRCODE_TEST_DATA macro to update test data."
|
||||
<< std::endl;
|
||||
}
|
||||
check_qr(root, name_current_image, "close_images", corners, {decoded_info}, pixels_error);
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam< std::string > Objdetect_QRCode_Monitor;
|
||||
@@ -396,43 +327,7 @@ TEST_P(Objdetect_QRCode_Monitor, regression)
|
||||
#else
|
||||
ASSERT_TRUE(qrcode.detect(barcode, corners));
|
||||
#endif
|
||||
|
||||
const std::string dataset_config = findDataFile(root + "dataset_config.json");
|
||||
FileStorage file_config(dataset_config, FileStorage::READ);
|
||||
ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config;
|
||||
{
|
||||
FileNode images_list = file_config["monitor_images"];
|
||||
size_t images_count = static_cast<size_t>(images_list.size());
|
||||
ASSERT_GT(images_count, 0u) << "Can't find validation data entries in 'test_images': " << dataset_config;
|
||||
|
||||
for (size_t index = 0; index < images_count; index++)
|
||||
{
|
||||
FileNode config = images_list[(int)index];
|
||||
std::string name_test_image = config["image_name"];
|
||||
if (name_test_image == name_current_image)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int x = config["x"][i];
|
||||
int y = config["y"][i];
|
||||
EXPECT_NEAR(x, corners[i].x, pixels_error);
|
||||
EXPECT_NEAR(y, corners[i].y, pixels_error);
|
||||
}
|
||||
|
||||
#ifdef HAVE_QUIRC
|
||||
std::string original_info = config["info"];
|
||||
EXPECT_EQ(decoded_info, original_info);
|
||||
#endif
|
||||
|
||||
return; // done
|
||||
}
|
||||
}
|
||||
std::cerr
|
||||
<< "Not found results for '" << name_current_image
|
||||
<< "' image in config file:" << dataset_config << std::endl
|
||||
<< "Re-run tests with enabled UPDATE_QRCODE_TEST_DATA macro to update test data."
|
||||
<< std::endl;
|
||||
}
|
||||
check_qr(root, name_current_image, "monitor_images", corners, {decoded_info}, pixels_error);
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam< std::string > Objdetect_QRCode_Curved;
|
||||
@@ -458,56 +353,26 @@ TEST_P(Objdetect_QRCode_Curved, regression)
|
||||
#else
|
||||
ASSERT_TRUE(qrcode.detect(src, corners));
|
||||
#endif
|
||||
|
||||
const std::string dataset_config = findDataFile(root + "dataset_config.json");
|
||||
FileStorage file_config(dataset_config, FileStorage::READ);
|
||||
ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config;
|
||||
{
|
||||
FileNode images_list = file_config["test_images"];
|
||||
size_t images_count = static_cast<size_t>(images_list.size());
|
||||
ASSERT_GT(images_count, 0u) << "Can't find validation data entries in 'test_images': " << dataset_config;
|
||||
|
||||
for (size_t index = 0; index < images_count; index++)
|
||||
{
|
||||
FileNode config = images_list[(int)index];
|
||||
std::string name_test_image = config["image_name"];
|
||||
if (name_test_image == name_current_image)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int x = config["x"][i];
|
||||
int y = config["y"][i];
|
||||
EXPECT_NEAR(x, corners[i].x, pixels_error);
|
||||
EXPECT_NEAR(y, corners[i].y, pixels_error);
|
||||
}
|
||||
|
||||
#ifdef HAVE_QUIRC
|
||||
std::string original_info = config["info"];
|
||||
EXPECT_EQ(decoded_info, original_info);
|
||||
#endif
|
||||
|
||||
return; // done
|
||||
}
|
||||
}
|
||||
std::cerr
|
||||
<< "Not found results for '" << name_current_image
|
||||
<< "' image in config file:" << dataset_config << std::endl
|
||||
<< "Re-run tests with enabled UPDATE_QRCODE_TEST_DATA macro to update test data."
|
||||
<< std::endl;
|
||||
}
|
||||
check_qr(root, name_current_image, "test_images", corners, {decoded_info}, pixels_error);
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam < std::string > Objdetect_QRCode_Multi;
|
||||
typedef testing::TestWithParam<std::tuple<std::string, std::string>> Objdetect_QRCode_Multi;
|
||||
TEST_P(Objdetect_QRCode_Multi, regression)
|
||||
{
|
||||
const std::string name_current_image = GetParam();
|
||||
const std::string name_current_image = get<0>(GetParam());
|
||||
const std::string root = "qrcode/multiple/";
|
||||
const std::string method = get<1>(GetParam());
|
||||
const int pixels_error = 4;
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat src = imread(image_path);
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
QRCodeDetector qrcode;
|
||||
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<Point> corners;
|
||||
#ifdef HAVE_QUIRC
|
||||
std::vector<cv::String> decoded_info;
|
||||
@@ -521,75 +386,15 @@ TEST_P(Objdetect_QRCode_Multi, regression)
|
||||
#else
|
||||
ASSERT_TRUE(qrcode.detectMulti(src, corners));
|
||||
#endif
|
||||
|
||||
const std::string dataset_config = findDataFile(root + "dataset_config.json");
|
||||
FileStorage file_config(dataset_config, FileStorage::READ);
|
||||
ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config;
|
||||
{
|
||||
FileNode images_list = file_config["multiple_images"];
|
||||
size_t images_count = static_cast<size_t>(images_list.size());
|
||||
ASSERT_GT(images_count, 0u) << "Can't find validation data entries in 'test_images': " << dataset_config;
|
||||
for (size_t index = 0; index < images_count; index++)
|
||||
{
|
||||
FileNode config = images_list[(int)index];
|
||||
std::string name_test_image = config["image_name"];
|
||||
if (name_test_image == name_current_image)
|
||||
{
|
||||
for(int j = 0; j < int(corners.size()); j += 4)
|
||||
{
|
||||
bool ok = false;
|
||||
for (int k = 0; k < int(corners.size() / 4); k++)
|
||||
{
|
||||
int count_eq_points = 0;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int x = config["x"][k][i];
|
||||
int y = config["y"][k][i];
|
||||
if(((abs(corners[j + i].x - x)) <= pixels_error) && ((abs(corners[j + i].y - y)) <= pixels_error))
|
||||
count_eq_points++;
|
||||
}
|
||||
if (count_eq_points == 4)
|
||||
{
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(ok);
|
||||
}
|
||||
|
||||
#ifdef HAVE_QUIRC
|
||||
size_t count_eq_info = 0;
|
||||
for(int i = 0; i < int(decoded_info.size()); i++)
|
||||
{
|
||||
for(int j = 0; j < int(decoded_info.size()); j++)
|
||||
{
|
||||
std::string original_info = config["info"][j];
|
||||
if(original_info == decoded_info[i])
|
||||
{
|
||||
count_eq_info++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(decoded_info.size(), count_eq_info);
|
||||
#endif
|
||||
|
||||
return; // done
|
||||
}
|
||||
}
|
||||
std::cerr
|
||||
<< "Not found results for '" << name_current_image
|
||||
<< "' image in config file:" << dataset_config << std::endl
|
||||
<< "Re-run tests with enabled UPDATE_QRCODE_TEST_DATA macro to update test data."
|
||||
<< std::endl;
|
||||
}
|
||||
check_qr(root, name_current_image, "multiple_images", corners, decoded_info, pixels_error, true);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode, testing::ValuesIn(qrcode_images_name));
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Close, testing::ValuesIn(qrcode_images_close));
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Monitor, testing::ValuesIn(qrcode_images_monitor));
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Curved, testing::ValuesIn(qrcode_images_curved));
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Multi, testing::ValuesIn(qrcode_images_multiple));
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Multi, testing::Combine(testing::ValuesIn(qrcode_images_multiple),
|
||||
testing::Values("contours_based", "aruco_based")));
|
||||
|
||||
TEST(Objdetect_QRCode_decodeMulti, decode_regression_16491)
|
||||
{
|
||||
@@ -611,8 +416,10 @@ TEST(Objdetect_QRCode_decodeMulti, decode_regression_16491)
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(Objdetect_QRCode_detectMulti, detect_regression_16961)
|
||||
typedef testing::TestWithParam<std::string> Objdetect_QRCode_detectMulti;
|
||||
TEST_P(Objdetect_QRCode_detectMulti, detect_regression_16961)
|
||||
{
|
||||
const std::string method = GetParam();
|
||||
const std::string name_current_image = "9_qrcodes.jpg";
|
||||
const std::string root = "qrcode/multiple/";
|
||||
|
||||
@@ -620,7 +427,10 @@ TEST(Objdetect_QRCode_detectMulti, detect_regression_16961)
|
||||
Mat src = imread(image_path);
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
|
||||
QRCodeDetector qrcode;
|
||||
GraphicalCodeDetector qrcode = QRCodeDetector();
|
||||
if (method == "aruco_based") {
|
||||
qrcode = QRCodeDetectorAruco();
|
||||
}
|
||||
std::vector<Point> corners;
|
||||
EXPECT_TRUE(qrcode.detectMulti(src, corners));
|
||||
ASSERT_FALSE(corners.empty());
|
||||
@@ -628,21 +438,27 @@ TEST(Objdetect_QRCode_detectMulti, detect_regression_16961)
|
||||
EXPECT_EQ(corners.size(), expect_corners_size);
|
||||
}
|
||||
|
||||
TEST(Objdetect_QRCode_decodeMulti, check_output_parameters_type_19363)
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_detectMulti, testing::Values("contours_based", "aruco_based"));
|
||||
typedef testing::TestWithParam<std::string> Objdetect_QRCode_detectAndDecodeMulti;
|
||||
TEST_P(Objdetect_QRCode_detectAndDecodeMulti, check_output_parameters_type_19363)
|
||||
{
|
||||
const std::string name_current_image = "9_qrcodes.jpg";
|
||||
const std::string root = "qrcode/multiple/";
|
||||
const std::string method = GetParam();
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat src = imread(image_path);
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
#ifdef HAVE_QUIRC
|
||||
QRCodeDetector qrcode;
|
||||
GraphicalCodeDetector qrcode = QRCodeDetector();
|
||||
if (method == "aruco_based") {
|
||||
qrcode = QRCodeDetectorAruco();
|
||||
}
|
||||
std::vector<Point> corners;
|
||||
std::vector<cv::String> decoded_info;
|
||||
#if 0 // FIXIT: OutputArray::create() type check
|
||||
std::vector<Mat2b> straight_barcode_nchannels;
|
||||
EXPECT_ANY_THROW(qrcode.detectAndDecodeMulti(src, decoded_info, corners, straight_barcode_nchannels));
|
||||
EXPECT_ANY_THROW(qrcode->detectAndDecodeMulti(src, decoded_info, corners, straight_barcode_nchannels));
|
||||
#endif
|
||||
|
||||
int expected_barcode_type = CV_8UC1;
|
||||
@@ -653,6 +469,8 @@ TEST(Objdetect_QRCode_decodeMulti, check_output_parameters_type_19363)
|
||||
EXPECT_EQ(expected_barcode_type, straight_barcode[i].type());
|
||||
#endif
|
||||
}
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_detectAndDecodeMulti, testing::Values("contours_based", "aruco_based"));
|
||||
|
||||
|
||||
TEST(Objdetect_QRCode_detect, detect_regression_20882)
|
||||
{
|
||||
@@ -793,14 +611,18 @@ TEST(Objdetect_QRCode_decode, decode_regression_version_25)
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(Objdetect_QRCode_decodeMulti, decode_9_qrcodes_version7)
|
||||
TEST_P(Objdetect_QRCode_detectAndDecodeMulti, decode_9_qrcodes_version7)
|
||||
{
|
||||
const std::string name_current_image = "9_qrcodes_version7.jpg";
|
||||
const std::string root = "qrcode/multiple/";
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat src = imread(image_path);
|
||||
QRCodeDetector qrcode;
|
||||
const std::string method = GetParam();
|
||||
GraphicalCodeDetector qrcode = QRCodeDetector();
|
||||
if (method == "aruco_based") {
|
||||
qrcode = QRCodeDetectorAruco();
|
||||
}
|
||||
std::vector<Point> corners;
|
||||
std::vector<cv::String> decoded_info;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user