mirror of
https://github.com/opencv/opencv.git
synced 2025-06-11 20:09:23 +08:00
Merge pull request #24903 from cristidbr-adapta:feature-barcode-detector-parameters
Feature barcode detector parameters #24903 Attempt to solve #24902 without changing the default detector behaviour. Megre with extra: https://github.com/opencv/opencv_extra/pull/1150 **Introduces new parameters and methods to `cv::barcode::BarcodeDetector`**. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
parent
6350bfbf79
commit
e05ad56f6e
@ -57,6 +57,52 @@ public:
|
||||
CV_OUT std::vector<std::string> &decoded_info,
|
||||
CV_OUT std::vector<std::string> &decoded_type,
|
||||
OutputArray points = noArray()) const;
|
||||
|
||||
/** @brief Get detector downsampling threshold.
|
||||
*
|
||||
* @return detector downsampling threshold
|
||||
*/
|
||||
CV_WRAP double getDownsamplingThreshold() const;
|
||||
|
||||
/** @brief Set detector downsampling threshold.
|
||||
*
|
||||
* By default, the detect method resizes the input image to this limit if the smallest image size is is greater than the threshold.
|
||||
* Increasing this value can improve detection accuracy and the number of results at the expense of performance.
|
||||
* Correlates with detector scales. Setting this to a large value will disable downsampling.
|
||||
* @param thresh downsampling limit to apply (default 512)
|
||||
* @see setDetectorScales
|
||||
*/
|
||||
CV_WRAP BarcodeDetector& setDownsamplingThreshold(double thresh);
|
||||
|
||||
/** @brief Returns detector box filter sizes.
|
||||
*
|
||||
* @param sizes output parameter for returning the sizes.
|
||||
*/
|
||||
CV_WRAP void getDetectorScales(CV_OUT std::vector<float>& sizes) const;
|
||||
|
||||
/** @brief Set detector box filter sizes.
|
||||
*
|
||||
* Adjusts the value and the number of box filters used in the detect step.
|
||||
* The filter sizes directly correlate with the expected line widths for a barcode. Corresponds to expected barcode distance.
|
||||
* If the downsampling limit is increased, filter sizes need to be adjusted in an inversely proportional way.
|
||||
* @param sizes box filter sizes, relative to minimum dimension of the image (default [0.01, 0.03, 0.06, 0.08])
|
||||
*/
|
||||
CV_WRAP BarcodeDetector& setDetectorScales(const std::vector<float>& sizes);
|
||||
|
||||
/** @brief Get detector gradient magnitude threshold.
|
||||
*
|
||||
* @return detector gradient magnitude threshold.
|
||||
*/
|
||||
CV_WRAP double getGradientThreshold() const;
|
||||
|
||||
/** @brief Set detector gradient magnitude threshold.
|
||||
*
|
||||
* Sets the coherence threshold for detected bounding boxes.
|
||||
* Increasing this value will generate a closer fitted bounding box width and can reduce false-positives.
|
||||
* Values between 16 and 1024 generally work, while too high of a value will remove valid detections.
|
||||
* @param thresh gradient magnitude threshold (default 64).
|
||||
*/
|
||||
CV_WRAP BarcodeDetector& setGradientThreshold(double thresh);
|
||||
};
|
||||
//! @}
|
||||
|
||||
|
@ -142,11 +142,15 @@ struct BarcodeImpl : public GraphicalCodeDetector::Impl
|
||||
public:
|
||||
shared_ptr<SuperScale> sr;
|
||||
bool use_nn_sr = false;
|
||||
double detectorThrDownSample = 512.f;
|
||||
vector<float> detectorWindowSizes = {0.01f, 0.03f, 0.06f, 0.08f};
|
||||
double detectorThrGradMagnitude = 64.f;
|
||||
|
||||
public:
|
||||
//=================
|
||||
// own methods
|
||||
BarcodeImpl() = default;
|
||||
BarcodeImpl() {}
|
||||
|
||||
vector<Mat> initDecode(const Mat &src, const vector<vector<Point2f>> &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<vector<Point2f>> pnts2f = bardet.getTransformationPoints();
|
||||
@ -370,5 +374,64 @@ bool BarcodeDetector::detectAndDecodeWithType(InputArray img, vector<string> &de
|
||||
return p_->detectAndDecodeWithType(img, decoded_info, decoded_type, points_);
|
||||
}
|
||||
|
||||
double BarcodeDetector::getDownsamplingThreshold() const
|
||||
{
|
||||
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p);
|
||||
CV_Assert(p_);
|
||||
|
||||
return p_->detectorThrDownSample;
|
||||
}
|
||||
|
||||
BarcodeDetector& BarcodeDetector::setDownsamplingThreshold(double thresh)
|
||||
{
|
||||
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p);
|
||||
CV_Assert(p_);
|
||||
CV_Assert(thresh >= 64);
|
||||
|
||||
p_->detectorThrDownSample = thresh;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void BarcodeDetector::getDetectorScales(CV_OUT std::vector<float>& sizes) const
|
||||
{
|
||||
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p);
|
||||
CV_Assert(p_);
|
||||
|
||||
sizes = p_->detectorWindowSizes;
|
||||
}
|
||||
|
||||
BarcodeDetector& BarcodeDetector::setDetectorScales(const std::vector<float>& sizes)
|
||||
{
|
||||
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p);
|
||||
CV_Assert(p_);
|
||||
CV_Assert(sizes.size() > 0 && sizes.size() <= 16);
|
||||
|
||||
for (const float &size : sizes) {
|
||||
CV_Assert(size > 0 && size < 1);
|
||||
}
|
||||
|
||||
p_->detectorWindowSizes = sizes;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
double BarcodeDetector::getGradientThreshold() const
|
||||
{
|
||||
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p);
|
||||
CV_Assert(p_);
|
||||
|
||||
return p_->detectorThrGradMagnitude;
|
||||
}
|
||||
|
||||
BarcodeDetector& BarcodeDetector::setGradientThreshold(double thresh)
|
||||
{
|
||||
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p);
|
||||
CV_Assert(p_);
|
||||
CV_Assert(thresh >= 0 && thresh < 1e4);
|
||||
|
||||
p_->detectorThrGradMagnitude = thresh;
|
||||
return *this;
|
||||
}
|
||||
|
||||
}// namespace barcode
|
||||
} // namespace cv
|
||||
|
@ -136,13 +136,13 @@ static void NMSBoxes(const std::vector<RotatedRect>& bboxes, const std::vector<f
|
||||
|
||||
//==============================================================================
|
||||
|
||||
void Detect::init(const Mat &src)
|
||||
void Detect::init(const Mat &src, double detectorThreshDownSamplingLimit)
|
||||
{
|
||||
const double min_side = std::min(src.size().width, src.size().height);
|
||||
if (min_side > 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<float>& 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<float>(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);
|
||||
|
||||
|
@ -24,9 +24,9 @@ private:
|
||||
|
||||
|
||||
public:
|
||||
void init(const Mat &src);
|
||||
void init(const Mat &src, double detectorThreshDownSamplingLimit);
|
||||
|
||||
void localization();
|
||||
void localization(const vector<float>& detectorWindowSizes, double detectorGradientMagnitudeThresh);
|
||||
|
||||
vector<vector<Point2f>> 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);
|
||||
|
||||
|
@ -60,7 +60,7 @@ 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"} }
|
||||
{ "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<float> 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<ParamStruct> 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<Point2f> 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<float> expected_ds = {0.1f};
|
||||
vector<float> 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<float> {}));
|
||||
EXPECT_ANY_THROW(bardet.setDetectorScales(vector<float> {-1}));
|
||||
EXPECT_ANY_THROW(bardet.setDetectorScales(vector<float> {1.5}));
|
||||
EXPECT_ANY_THROW(bardet.setDetectorScales(vector<float> (17, 0.5)));
|
||||
EXPECT_ANY_THROW(bardet.setGradientThreshold(-0.1));
|
||||
}
|
||||
|
||||
}} // opencv_test::<anonymous>::
|
||||
|
Loading…
Reference in New Issue
Block a user