diff --git a/modules/objdetect/include/opencv2/objdetect/barcode.hpp b/modules/objdetect/include/opencv2/objdetect/barcode.hpp index 788889ad40..c20b67c0b2 100644 --- a/modules/objdetect/include/opencv2/objdetect/barcode.hpp +++ b/modules/objdetect/include/opencv2/objdetect/barcode.hpp @@ -57,6 +57,52 @@ public: CV_OUT std::vector &decoded_info, CV_OUT std::vector &decoded_type, OutputArray points = noArray()) const; + + /** @brief Get detector downsampling threshold. + * + * @return detector downsampling threshold + */ + CV_WRAP double getDownsamplingThreshold() const; + + /** @brief Set detector downsampling threshold. + * + * By default, the detect method resizes the input image to this limit if the smallest image size is is greater than the threshold. + * Increasing this value can improve detection accuracy and the number of results at the expense of performance. + * Correlates with detector scales. Setting this to a large value will disable downsampling. + * @param thresh downsampling limit to apply (default 512) + * @see setDetectorScales + */ + CV_WRAP BarcodeDetector& setDownsamplingThreshold(double thresh); + + /** @brief Returns detector box filter sizes. + * + * @param sizes output parameter for returning the sizes. + */ + CV_WRAP void getDetectorScales(CV_OUT std::vector& sizes) const; + + /** @brief Set detector box filter sizes. + * + * Adjusts the value and the number of box filters used in the detect step. + * The filter sizes directly correlate with the expected line widths for a barcode. Corresponds to expected barcode distance. + * If the downsampling limit is increased, filter sizes need to be adjusted in an inversely proportional way. + * @param sizes box filter sizes, relative to minimum dimension of the image (default [0.01, 0.03, 0.06, 0.08]) + */ + CV_WRAP BarcodeDetector& setDetectorScales(const std::vector& sizes); + + /** @brief Get detector gradient magnitude threshold. + * + * @return detector gradient magnitude threshold. + */ + CV_WRAP double getGradientThreshold() const; + + /** @brief Set detector gradient magnitude threshold. + * + * Sets the coherence threshold for detected bounding boxes. + * Increasing this value will generate a closer fitted bounding box width and can reduce false-positives. + * Values between 16 and 1024 generally work, while too high of a value will remove valid detections. + * @param thresh gradient magnitude threshold (default 64). + */ + CV_WRAP BarcodeDetector& setGradientThreshold(double thresh); }; //! @} diff --git a/modules/objdetect/src/barcode.cpp b/modules/objdetect/src/barcode.cpp index 172fb5bd77..1b963e4242 100644 --- a/modules/objdetect/src/barcode.cpp +++ b/modules/objdetect/src/barcode.cpp @@ -142,11 +142,15 @@ struct BarcodeImpl : public GraphicalCodeDetector::Impl public: shared_ptr sr; bool use_nn_sr = false; + double detectorThrDownSample = 512.f; + vector detectorWindowSizes = {0.01f, 0.03f, 0.06f, 0.08f}; + double detectorThrGradMagnitude = 64.f; public: //================= // own methods - BarcodeImpl() = default; + BarcodeImpl() {} + vector initDecode(const Mat &src, const vector> &points) const; bool decodeWithType(InputArray img, InputArray points, @@ -268,8 +272,8 @@ bool BarcodeImpl::detect(InputArray img, OutputArray points) const } Detect bardet; - bardet.init(inarr); - bardet.localization(); + bardet.init(inarr, detectorThrDownSample); + bardet.localization(detectorWindowSizes, detectorThrGradMagnitude); if (!bardet.computeTransformationPoints()) { return false; } vector> pnts2f = bardet.getTransformationPoints(); @@ -370,5 +374,64 @@ bool BarcodeDetector::detectAndDecodeWithType(InputArray img, vector &de return p_->detectAndDecodeWithType(img, decoded_info, decoded_type, points_); } +double BarcodeDetector::getDownsamplingThreshold() const +{ + Ptr p_ = dynamic_pointer_cast(p); + CV_Assert(p_); + + return p_->detectorThrDownSample; +} + +BarcodeDetector& BarcodeDetector::setDownsamplingThreshold(double thresh) +{ + Ptr p_ = dynamic_pointer_cast(p); + CV_Assert(p_); + CV_Assert(thresh >= 64); + + p_->detectorThrDownSample = thresh; + return *this; +} + +void BarcodeDetector::getDetectorScales(CV_OUT std::vector& sizes) const +{ + Ptr p_ = dynamic_pointer_cast(p); + CV_Assert(p_); + + sizes = p_->detectorWindowSizes; +} + +BarcodeDetector& BarcodeDetector::setDetectorScales(const std::vector& sizes) +{ + Ptr p_ = dynamic_pointer_cast(p); + CV_Assert(p_); + CV_Assert(sizes.size() > 0 && sizes.size() <= 16); + + for (const float &size : sizes) { + CV_Assert(size > 0 && size < 1); + } + + p_->detectorWindowSizes = sizes; + + return *this; +} + +double BarcodeDetector::getGradientThreshold() const +{ + Ptr p_ = dynamic_pointer_cast(p); + CV_Assert(p_); + + return p_->detectorThrGradMagnitude; +} + +BarcodeDetector& BarcodeDetector::setGradientThreshold(double thresh) +{ + Ptr p_ = dynamic_pointer_cast(p); + CV_Assert(p_); + CV_Assert(thresh >= 0 && thresh < 1e4); + + p_->detectorThrGradMagnitude = thresh; + return *this; +} + }// namespace barcode } // namespace cv diff --git a/modules/objdetect/src/barcode_detector/bardetect.cpp b/modules/objdetect/src/barcode_detector/bardetect.cpp index b156d1b25d..abb30bf547 100644 --- a/modules/objdetect/src/barcode_detector/bardetect.cpp +++ b/modules/objdetect/src/barcode_detector/bardetect.cpp @@ -136,13 +136,13 @@ static void NMSBoxes(const std::vector& bboxes, const std::vector 512.0) + if (min_side > detectorThreshDownSamplingLimit) { purpose = SHRINKING; - coeff_expansion = min_side / 512.0; + coeff_expansion = min_side / detectorThreshDownSamplingLimit; width = cvRound(src.size().width / coeff_expansion); height = cvRound(src.size().height / coeff_expansion); Size new_size(width, height); @@ -171,19 +171,19 @@ void Detect::init(const Mat &src) } -void Detect::localization() +void Detect::localization(const std::vector& detectorWindowSizes, double detectorThreshGradientMagnitude) { localization_bbox.clear(); bbox_scores.clear(); // get integral image - preprocess(); + preprocess(detectorThreshGradientMagnitude); // empirical setting - static constexpr float SCALE_LIST[] = {0.01f, 0.03f, 0.06f, 0.08f}; + //static constexpr float SCALE_LIST[] = {0.01f, 0.03f, 0.06f, 0.08f}; const auto min_side = static_cast(std::min(width, height)); int window_size; - for (const float scale:SCALE_LIST) + for (const float scale: detectorWindowSizes) { window_size = cvRound(min_side * scale); if(window_size == 0) { @@ -205,7 +205,20 @@ bool Detect::computeTransformationPoints() transformation_points.reserve(bbox_indices.size()); RotatedRect rect; Point2f temp[4]; - const float THRESHOLD_SCORE = float(width * height) / 300.f; + + /** + * #24902 resolution invariant barcode detector + * + * refactor of THRESHOLD_SCORE = float(width * height) / 300.f + * wrt to rescaled input size - 300 value needs factorization + * only one factor pair matches a common aspect ratio of 4:3 ~ 20x15 + * decomposing this yields THRESHOLD_SCORE = (width / 20) * (height / 15) + * therefore each factor was rescaled based by purpose (refsize was 512) + */ + const float THRESHOLD_WSCALE = (purpose != UNCHANGED) ? 20 : (20 * width / 512.f); + const float THRESHOLD_HSCALE = (purpose != UNCHANGED) ? 15 : (15 * height / 512.f); + const float THRESHOLD_SCORE = (width / THRESHOLD_WSCALE) * (height / THRESHOLD_HSCALE); + NMSBoxes(localization_bbox, bbox_scores, THRESHOLD_SCORE, 0.1f, bbox_indices); for (const auto &bbox_index : bbox_indices) @@ -231,15 +244,14 @@ bool Detect::computeTransformationPoints() } -void Detect::preprocess() +void Detect::preprocess(double detectorGradientMagnitudeThresh) { 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); + threshold(temp, temp, detectorGradientMagnitudeThresh, 1, THRESH_BINARY); temp.convertTo(gradient_magnitude, CV_8U); integral(gradient_magnitude, integral_edges, CV_32F); diff --git a/modules/objdetect/src/barcode_detector/bardetect.hpp b/modules/objdetect/src/barcode_detector/bardetect.hpp index 9f084d20aa..178fded36f 100644 --- a/modules/objdetect/src/barcode_detector/bardetect.hpp +++ b/modules/objdetect/src/barcode_detector/bardetect.hpp @@ -24,9 +24,9 @@ private: public: - void init(const Mat &src); + void init(const Mat &src, double detectorThreshDownSamplingLimit); - void localization(); + void localization(const vector& detectorWindowSizes, double detectorGradientMagnitudeThresh); vector> getTransformationPoints() { return transformation_points; } @@ -44,7 +44,7 @@ protected: 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 preprocess(double detectorThreshGradientMagnitude); void calCoherence(int window_size); diff --git a/modules/objdetect/test/test_barcode.cpp b/modules/objdetect/test/test_barcode.cpp index 7e295fafa3..94542ca39b 100644 --- a/modules/objdetect/test/test_barcode.cpp +++ b/modules/objdetect/test/test_barcode.cpp @@ -60,7 +60,7 @@ map 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"} } + { "multiple/4_barcodes.jpg", {"EAN_13;EAN_13;EAN_13;EAN_13", "9787564350840;9783319200064;9787118081473;9787122276124"} }, }; typedef testing::TestWithParam< string > BarcodeDetector_main; @@ -144,4 +144,87 @@ TEST(BarcodeDetector_base, invalid) EXPECT_ANY_THROW(bardet.decodeMulti(zero_image, corners, decoded_info)); } +struct ParamStruct +{ + double down_thresh; + vector scales; + double grad_thresh; + unsigned res_count; +}; + +inline static std::ostream &operator<<(std::ostream &out, const ParamStruct &p) +{ + out << "(" << p.down_thresh << ", "; + for(float val : p.scales) + out << val << ", "; + out << p.grad_thresh << ")"; + return out; +} + +ParamStruct param_list[] = { + { 512, {0.01f, 0.03f, 0.06f, 0.08f}, 64, 4 }, // default values -> 4 codes + { 512, {0.01f, 0.03f, 0.06f, 0.08f}, 1024, 2 }, + { 512, {0.01f, 0.03f, 0.06f, 0.08f}, 2048, 0 }, + { 128, {0.01f, 0.03f, 0.06f, 0.08f}, 64, 3 }, + { 64, {0.01f, 0.03f, 0.06f, 0.08f}, 64, 2 }, + { 128, {0.0000001f}, 64, 1 }, + { 128, {0.0000001f, 0.0001f}, 64, 1 }, + { 128, {0.0000001f, 0.1f}, 64, 1 }, + { 512, {0.1f}, 64, 0 }, +}; + +typedef testing::TestWithParam BarcodeDetector_parameters_tune; + +TEST_P(BarcodeDetector_parameters_tune, accuracy) +{ + const ParamStruct param = GetParam(); + + const string fname = "multiple/4_barcodes.jpg"; + const string image_path = findDataFile(string("barcode/") + fname); + + const Mat img = imread(image_path); + ASSERT_FALSE(img.empty()) << "Can't read image: " << image_path; + + auto bardet = barcode::BarcodeDetector(); + bardet.setDownsamplingThreshold(param.down_thresh); + bardet.setDetectorScales(param.scales); + bardet.setGradientThreshold(param.grad_thresh); + vector points; + bardet.detectMulti(img, points); + EXPECT_EQ(points.size() / 4, param.res_count); +} + +INSTANTIATE_TEST_CASE_P(/**/, BarcodeDetector_parameters_tune, testing::ValuesIn(param_list)); + +TEST(BarcodeDetector_parameters, regression) +{ + const double expected_dt = 1024, expected_gt = 256; + const vector expected_ds = {0.1f}; + vector ds_value = {0.0f}; + + auto bardet = barcode::BarcodeDetector(); + + bardet.setDownsamplingThreshold(expected_dt).setDetectorScales(expected_ds).setGradientThreshold(expected_gt); + + double dt_value = bardet.getDownsamplingThreshold(); + bardet.getDetectorScales(ds_value); + double gt_value = bardet.getGradientThreshold(); + + EXPECT_EQ(expected_dt, dt_value); + EXPECT_EQ(expected_ds, ds_value); + EXPECT_EQ(expected_gt, gt_value); +} + +TEST(BarcodeDetector_parameters, invalid) +{ + auto bardet = barcode::BarcodeDetector(); + + EXPECT_ANY_THROW(bardet.setDownsamplingThreshold(-1)); + EXPECT_ANY_THROW(bardet.setDetectorScales(vector {})); + EXPECT_ANY_THROW(bardet.setDetectorScales(vector {-1})); + EXPECT_ANY_THROW(bardet.setDetectorScales(vector {1.5})); + EXPECT_ANY_THROW(bardet.setDetectorScales(vector (17, 0.5))); + EXPECT_ANY_THROW(bardet.setGradientThreshold(-0.1)); +} + }} // opencv_test::::