1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-31 08:13:04 +04:00

Merge branch '4.x' into '5.x'

This commit is contained in:
Maksim Shabunin
2024-06-11 19:38:59 +03:00
573 changed files with 72922 additions and 7355 deletions
@@ -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);
};
//! @}
@@ -128,23 +128,23 @@ public:
*/
enum DisType { FR_COSINE=0, FR_NORM_L2=1 };
/** @brief Aligning image to put face on the standard position
/** @brief Aligns detected face with the source input image and crops it
* @param src_img input image
* @param face_box the detection result used for indicate face in input image
* @param face_box the detected face result from the input image
* @param aligned_img output aligned image
*/
CV_WRAP virtual void alignCrop(InputArray src_img, InputArray face_box, OutputArray aligned_img) const = 0;
/** @brief Extracting face feature from aligned image
/** @brief Extracts face feature from aligned image
* @param aligned_img input aligned image
* @param face_feature output face feature
*/
CV_WRAP virtual void feature(InputArray aligned_img, OutputArray face_feature) = 0;
/** @brief Calculating the distance between two face features
/** @brief Calculates the distance between two face features
* @param face_feature1 the first input feature
* @param face_feature2 the second input feature of the same size and the same type as face_feature1
* @param dis_type defining the similarity with optional values "FR_OSINE" or "FR_NORM_L2"
* @param dis_type defines how to calculate the distance between two face features with optional values "FR_COSINE" or "FR_NORM_L2"
*/
CV_WRAP virtual double match(InputArray face_feature1, InputArray face_feature2, int dis_type = FaceRecognizerSF::FR_COSINE) const = 0;
@@ -99,21 +99,6 @@ void Board::Impl::generateImage(Size outSize, OutputArray img, int marginSize, i
float sizeX = maxX - minX;
float sizeY = maxY - minY;
// proportion transformations
float xReduction = sizeX / float(out.cols);
float yReduction = sizeY / float(out.rows);
// determine the zone where the markers are placed
if(xReduction > yReduction) {
int nRows = int(sizeY / xReduction);
int rowsMargins = (out.rows - nRows) / 2;
out.adjustROI(-rowsMargins, -rowsMargins, 0, 0);
} else {
int nCols = int(sizeX / yReduction);
int colsMargins = (out.cols - nCols) / 2;
out.adjustROI(0, 0, -colsMargins, -colsMargins);
}
// now paint each marker
Mat marker;
Point2f outCorners[3];
+66 -3
View File
@@ -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);
+84 -1
View File
@@ -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>::
@@ -81,6 +81,18 @@ static Mat projectCharucoBoard(aruco::CharucoBoard& board, Mat cameraMatrix, dou
return img;
}
static bool borderPixelsHaveSameColor(const Mat& image, uint8_t color) {
for (int j = 0; j < image.cols; j++) {
if (image.at<uint8_t>(0, j) != color || image.at<uint8_t>(image.rows-1, j) != color)
return false;
}
for (int i = 0; i < image.rows; i++) {
if (image.at<uint8_t>(i, 0) != color || image.at<uint8_t>(i, image.cols-1) != color)
return false;
}
return true;
}
/**
* @brief Check Charuco detection
*/
@@ -771,17 +783,24 @@ TEST_P(CharucoBoard, testWrongSizeDetection)
ASSERT_TRUE(detectedCharucoIds.empty());
}
TEST(CharucoBoardGenerate, issue_24806)
typedef testing::TestWithParam<std::tuple<cv::Size, float, cv::Size, int>> CharucoBoardGenerate;
INSTANTIATE_TEST_CASE_P(/**/, CharucoBoardGenerate, testing::Values(make_tuple(Size(7, 4), 13.f, Size(400, 300), 24),
make_tuple(Size(12, 2), 13.f, Size(200, 150), 1),
make_tuple(Size(12, 2), 13.1f, Size(400, 300), 1)));
TEST_P(CharucoBoardGenerate, issue_24806)
{
aruco::Dictionary dict = aruco::getPredefinedDictionary(aruco::DICT_4X4_1000);
const float squareLength = 13.f, markerLength = 10.f;
const Size boardSize(7ull, 4ull);
auto params = GetParam();
const Size boardSize = std::get<0>(params);
const float squareLength = std::get<1>(params), markerLength = 10.f;
Size imgSize = std::get<2>(params);
const aruco::CharucoBoard board(boardSize, squareLength, markerLength, dict);
const int marginSize = 24;
const int marginSize = std::get<3>(params);
Mat boardImg;
// generate chessboard image
board.generateImage(Size(400, 300), boardImg, marginSize);
board.generateImage(imgSize, boardImg, marginSize);
// This condition checks that the width of the image determines the dimensions of the chessboard in this test
CV_Assert((float)(boardImg.cols) / (float)boardSize.width <=
(float)(boardImg.rows) / (float)boardSize.height);
@@ -819,7 +838,54 @@ TEST(CharucoBoardGenerate, issue_24806)
bool eq = (cv::countNonZero(goldCorner1 != winCorner) == 0) || (cv::countNonZero(goldCorner2 != winCorner) == 0);
ASSERT_TRUE(eq);
}
// TODO: fix aruco generateImage and add test aruco corners for generated image
// marker size in pixels
const float pixInMarker = markerLength/squareLength*pixInSquare;
// the size of the marker margin in pixels
const float pixInMarginMarker = 0.5f*(pixInSquare - pixInMarker);
// determine the zone where the aruco markers are located
int endArucoX = cvRound(pixInSquare*(boardSize.width-1)+pixInMarginMarker+pixInMarker);
int endArucoY = cvRound(pixInSquare*(boardSize.height-1)+pixInMarginMarker+pixInMarker);
Mat arucoZone = chessboardZoneImg(Range(cvRound(pixInMarginMarker), endArucoY), Range(cvRound(pixInMarginMarker), endArucoX));
const auto& markerCorners = board.getObjPoints();
float minX, maxX, minY, maxY;
minX = maxX = markerCorners[0][0].x;
minY = maxY = markerCorners[0][0].y;
for (const auto& marker : markerCorners) {
for (const Point3f& objCorner : marker) {
minX = min(minX, objCorner.x);
maxX = max(maxX, objCorner.x);
minY = min(minY, objCorner.y);
maxY = max(maxY, objCorner.y);
}
}
Point2f outCorners[3];
for (const auto& marker : markerCorners) {
for (int i = 0; i < 3; i++) {
outCorners[i] = Point2f(marker[i].x, marker[i].y) - Point2f(minX, minY);
outCorners[i].x = outCorners[i].x / (maxX - minX) * float(arucoZone.cols);
outCorners[i].y = outCorners[i].y / (maxY - minY) * float(arucoZone.rows);
}
Size dst_sz(outCorners[2] - outCorners[0]); // assuming CCW order
dst_sz.width = dst_sz.height = std::min(dst_sz.width, dst_sz.height);
Rect borderRect = Rect(outCorners[0], dst_sz);
//The test checks the inner and outer borders of the Aruco markers.
//In the inner border of Aruco marker, all pixels should be black.
//In the outer border of Aruco marker, all pixels should be white.
Mat markerImg = arucoZone(borderRect);
bool markerBorderIsBlack = borderPixelsHaveSameColor(markerImg, 0);
ASSERT_EQ(markerBorderIsBlack, true);
Mat markerOuterBorder = markerImg;
markerOuterBorder.adjustROI(1, 1, 1, 1);
bool markerOuterBorderIsWhite = borderPixelsHaveSameColor(markerOuterBorder, 255);
ASSERT_EQ(markerOuterBorderIsWhite, true);
}
}
// Temporary disabled in https://github.com/opencv/opencv/pull/24338
@@ -870,12 +936,14 @@ TEST(Charuco, DISABLED_testSeveralBoardsWithCustomIds)
detector2.detectBoard(gray, c_corners2, c_ids2, corners, ids);
ASSERT_EQ(ids.size(), size_t(16));
ASSERT_EQ(c_corners1.rows, expected_corners.rows);
EXPECT_NEAR(0, cvtest::norm(expected_corners, c_corners1.reshape(1), NORM_INF), 3e-1);
// In 4.x detectBoard() returns the charuco corners in a 2D Mat with shape (N_corners, 1)
// In 5.x, after PR #23473, detectBoard() returns the charuco corners in a 1D Mat with shape (1, N_corners)
ASSERT_EQ(expected_corners.total(), c_corners1.total()*c_corners1.channels());
EXPECT_NEAR(0., cvtest::norm(expected_corners.reshape(1, 1), c_corners1.reshape(1, 1), NORM_INF), 3e-1);
ASSERT_EQ(c_corners2.rows, expected_corners.rows);
ASSERT_EQ(expected_corners.total(), c_corners2.total()*c_corners2.channels());
expected_corners.col(0) += 500;
EXPECT_NEAR(0, cvtest::norm(expected_corners, c_corners2.reshape(1), NORM_INF), 3e-1);
EXPECT_NEAR(0., cvtest::norm(expected_corners.reshape(1, 1), c_corners2.reshape(1, 1), NORM_INF), 3e-1);
}
}} // namespace