1
0
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:
Alexander Smorkalov
2023-07-12 13:42:01 +03:00
128 changed files with 9936 additions and 932 deletions
+29
View File
@@ -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}
}
+56 -64
View File
@@ -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))
+3 -3
View File
@@ -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;
+114
View File
@@ -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
+42 -42
View File
@@ -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
+374
View File
@@ -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
+3
View File
@@ -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
+599 -54
View File
@@ -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;
+140
View File
@@ -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());
+78
View File
@@ -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";
}
}
+43 -221
View File
@@ -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;