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

Merge branch 4.x

This commit is contained in:
Alexander Smorkalov
2026-02-11 13:54:39 +03:00
committed by Alexander Smorkalov
468 changed files with 16647 additions and 11284 deletions
+13
View File
@@ -76,6 +76,19 @@ TEST(Imgproc_ApproxPoly, bad_epsilon)
ASSERT_ANY_THROW(approxPolyDP(inputPoints, outputPoints, eps, false));
}
TEST(Imgproc_ApproxPoly, distace_between_point_and_segment)
{
vector<Point2f> inputPoints = {
{ {0.f, 0.f}, {4.f, 2.f}, {11.f, 1.f}, {8.f, 0.f} }
};
std::vector<Point2f> result;
approxPolyDP(inputPoints, result, 1.9, false);
vector<Point2f> expectedResult = {
{ {0.f, 0.f}, {11.f, 1.f}, {8.f, 0.f} }
};
ASSERT_EQ(result, expectedResult);
}
struct ApproxPolyN: public testing::Test
{
void SetUp()
@@ -291,4 +291,33 @@ namespace opencv_test { namespace {
test.safe_run();
}
// Regression test for issue #28254
// Out-of-bounds read in AVX2 bilateralFilter 32f path with BORDER_CONSTANT
TEST(Imgproc_BilateralFilter, regression_28254_oob_read)
{
// Create a 64x64 CV_32FC1 image with values in range [100, 200]
// Image must be large enough (width >= 32) to trigger SIMD/AVX2 code path.
// Values are set so BORDER_CONSTANT padding (default 0) is outside the range,
// which triggers the out-of-bounds condition in the LUT access.
cv::Mat src(64, 64, CV_32FC1);
cv::randu(src, 100.0f, 200.0f);
cv::Mat dst;
// Parameters that trigger the bug
int d = -1;
double sigmaColor = 2.7;
double sigmaSpace = 44.5;
int borderType = cv::BORDER_CONSTANT;
// This should not crash or trigger AddressSanitizer
EXPECT_NO_THROW(
cv::bilateralFilter(src, dst, d, sigmaColor, sigmaSpace, borderType)
);
// Verify output is valid
EXPECT_FALSE(dst.empty());
EXPECT_EQ(dst.size(), src.size());
EXPECT_EQ(dst.type(), src.type());
}
}} // namespace
+15
View File
@@ -233,5 +233,20 @@ TEST(Imgproc_DrawContours, MatListOfMatIntScalarInt)
EXPECT_EQ(nz, 0);
}
TEST(Imgproc_Moments, degenerateContours)
{
std::vector<cv::Point> c1;
c1.push_back(cv::Point(10,10));
cv::Moments m1 = cv::moments(c1, false);
EXPECT_EQ(m1.m00, 0);
std::vector<cv::Point> c2;
c2.push_back(cv::Point(0,0));
c2.push_back(cv::Point(5,5));
c2.push_back(cv::Point(10,10));
cv::Moments m2 = cv::moments(c2, false);
EXPECT_EQ(m2.m00, 0);
}
}} // namespace
/* End of file. */
+56 -2
View File
@@ -304,9 +304,11 @@ TEST(Imgproc_ConvexityDefects, ordering_4539)
vector<int> hull_ind;
vector<Vec4i> defects;
#if 0 // deprecated behavior
// first, check the original contour as-is, without intermediate fillPoly/drawContours.
convexHull(contour_, hull_ind, false, false);
EXPECT_THROW( convexityDefects(contour_, hull_ind, defects), cv::Exception );
#endif
int scale = 20;
contour_ *= (double)scale;
@@ -319,10 +321,12 @@ TEST(Imgproc_ConvexityDefects, ordering_4539)
findContours(canvas_gray, contours, noArray(), RETR_LIST, CHAIN_APPROX_SIMPLE);
convexHull(contours[0], hull_ind, false, false);
#if 0 // deprecated behavior
// the original contour contains self-intersections,
// therefore convexHull does not return a monotonous sequence of points
// and therefore convexityDefects throws an exception
EXPECT_THROW( convexityDefects(contours[0], hull_ind, defects), cv::Exception );
#endif
#if 1
// one way to eliminate the contour self-intersection in this particular case is to apply dilate(),
@@ -1049,7 +1053,7 @@ TEST_P(minEnclosingTriangle_Modes, accuracy)
const Mat midPoint = (cur + next) / 2;
EXPECT_TRUE(isPointOnHull(hull, midPoint));
// at least one of hull edges must be on tirangle edge
// at least one of hull edges must be on triangle edge
hasEdgeOnHull = hasEdgeOnHull || isEdgeOnHull(hull, cur, next);
}
EXPECT_TRUE(hasEdgeOnHull);
@@ -1279,10 +1283,60 @@ INSTANTIATE_TEST_CASE_P(Imgproc, minAreaRect_of_line,
testing::Values(
std::make_tuple(Point2f(10, 15), Point2f(10, 25), Point2f(10, 20), Size2f(10, 0), -90.f),
std::make_tuple(Point2f(450, 500), Point2f(508, 500), Point2f(479, 500), Size2f(0, 58), -90.f),
std::make_tuple(Point2f(10, 20), Point2f(13, 16), Point2f(11.5, 18), Size2f(5, 0), -53.1301002f),
std::make_tuple(Point2f(10, 20), Point2f(13, 16), Point2f(11.5, 18), Size2f(5, 0), -53.1301041f),
std::make_tuple(Point2f(9, 19), Point2f(4, 7), Point2f(6.5, 13), Size2f(0, 13), -22.6198654f)
));
typedef testing::TestWithParam<tuple<tuple<std::vector<Point>, Mat>, bool> > convexHull_monotonous;
TEST_P(convexHull_monotonous, self_intersecting_contour)
{
std::vector<Point> contour = get<0>(get<0>(GetParam()));
Mat ref = get<1>(get<0>(GetParam())).clone();
bool clockwise = get<1>(GetParam());
if (!clockwise)
{
std::reverse(ref.begin<int>(), ref.end<int>());
}
Mat indices;
convexHull(contour, indices, clockwise, false);
Point minLoc;
minMaxLoc(indices, nullptr, nullptr, &minLoc);
std::rotate(indices.begin<int>(), indices.begin<int>() + minLoc.y, indices.end<int>());
minMaxLoc(ref, nullptr, nullptr, &minLoc);
std::rotate(ref.begin<int>(), ref.begin<int>() + minLoc.y, ref.end<int>());
ASSERT_EQ( cvtest::norm(indices, ref, NORM_INF), 0) << indices;
}
INSTANTIATE_TEST_CASE_P(Imgproc, convexHull_monotonous,
testing::Combine(
testing::Values(
std::make_tuple(
std::vector<Point>{
Point(3, 2), Point(3, 4), Point(2, 5), Point(1, 5),
Point(2, 5), Point(3, 4), Point(6, 4), Point(6, 2)
},
(Mat_<int>(5, 1) << 0, 3, 4, 6, 7)
),
std::make_tuple(
std::vector<Point>{
Point(3, -2), Point(3, -4), Point(2, -5), Point(1, -5),
Point(2, -5), Point(3, -4), Point(6, -4), Point(6, -2)
},
(Mat_<int>(5, 1) << 3, 0, 7, 6, 4)
),
std::make_tuple(
std::vector<Point>{
Point(1, 1), Point(1, 0), Point(0, 0), Point(1, 0), Point(0, 1)
},
(Mat_<int>(4, 1) << 0, 1, 2, 4)
)
),
testing::Bool()
));
}} // namespace
/* End of file. */
+24
View File
@@ -1281,4 +1281,28 @@ TEST(Drawing, contours_filled)
}
}
// Test for LINE_4 vs LINE_8 connectivity behavior
// Regression test for issue #26413
TEST(Drawing, line_connectivity_regression_26413)
{
Mat img4(10, 10, CV_8UC1, Scalar(0));
Mat img8(10, 10, CV_8UC1, Scalar(0));
// Draw a diagonal line from (0,0) to (9,9)
// LINE_4 (4-connected) should produce staircase pattern (no diagonals)
// LINE_8 (8-connected) should produce diagonal steps
line(img4, Point(0, 0), Point(9, 9), Scalar(255), 1, LINE_4);
line(img8, Point(0, 0), Point(9, 9), Scalar(255), 1, LINE_8);
int count4 = countNonZero(img4);
int count8 = countNonZero(img8);
// LINE_8 for a 10-pixel diagonal should have exactly 10 pixels
EXPECT_EQ(10, count8) << "LINE_8 diagonal from (0,0) to (9,9) should have 10 pixels";
// LINE_4 for a 10-pixel diagonal should have approximately 19 pixels
// (needs both horizontal and vertical steps)
EXPECT_GT(count4, 15) << "LINE_4 diagonal should have significantly more pixels due to staircase";
}
}} // namespace
@@ -509,7 +509,8 @@ int CV_GoodFeatureToTTest::validate_test_results( int test_case_idx )
EXPECT_LE(e, eps); // never true
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
for(int i = 0; i < (int)std::min((unsigned int)(cornersQuality.size()), (unsigned int)(cornersQuality.size())); i++) {
int min_size = (int)std::min(cornersQuality.size(), RefcornersQuality.size());
for(int i = 0; i < min_size; i++) {
if (std::abs(cornersQuality[i] - RefcornersQuality[i]) > eps * std::max(cornersQuality[i], RefcornersQuality[i]))
printf("i = %i Quality %2.6f Quality ref %2.6f\n", i, cornersQuality[i], RefcornersQuality[i]);
}
+117
View File
@@ -0,0 +1,117 @@
// 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 <vector>
namespace opencv_test { namespace {
Mat CropMid(InputArray src, int w, int h)
{
Mat mat = src.getMat();
return mat(Rect(mat.cols / 2 - w / 2, mat.rows / 2 - h / 2, w, h));
}
Mat GenerateTestImage(Size size)
{
Mat image = Mat::zeros(size.height * 2, size.width * 2, CV_32F);
rectangle(image,
Point(static_cast<int>(size.width * 0.1), static_cast<int>(size.height * 0.1)),
Point(static_cast<int>(size.width * 0.9), static_cast<int>(size.height * 0.9)),
Scalar(1),
-1);
return image;
}
void TestPhaseCorrelationIterative(const Size& size, const double maxShift)
{
const auto iters = std::max(201., maxShift * 10 + 1);
const Point2d shiftOffset(-maxShift * 0.5, -maxShift * 0.5);
Mat image1 = GenerateTestImage(size);
Mat crop1 = CropMid(image1, size.width, size.height);
Mat image2 = image1.clone();
std::vector<double> pcErrors;
std::vector<double> ipcErrors;
for (int i = 0; i < iters; ++i)
{
const auto shift =
Point2d(maxShift * i / (iters - 1), maxShift * i / (iters - 1)) + shiftOffset;
const Mat Tmat = (Mat_<double>(2, 3) << 1., 0., shift.x, 0., 1., shift.y);
warpAffine(image1, image2, Tmat, image2.size());
Mat crop2 = CropMid(image2, size.width, size.height);
const auto ipcshift = phaseCorrelateIterative(crop1, crop2);
const auto pcshift = phaseCorrelate(crop1, crop2);
pcErrors.push_back(
0.5 * std::abs(pcshift.x - shift.y) + 0.5 * std::abs(pcshift.y - shift.x));
ipcErrors.push_back(
0.5 * std::abs(ipcshift.x - shift.y) + 0.5 * std::abs(ipcshift.y - shift.x));
// error should be low
EXPECT_NEAR(ipcshift.x - shift.x, 0.0, 0.1);
EXPECT_NEAR(ipcshift.y - shift.y, 0.0, 0.1);
}
cv::Scalar pcMean, pcStddev, ipcMean, ipcStddev;
meanStdDev(ipcErrors, ipcMean, ipcStddev);
meanStdDev(pcErrors, pcMean, pcStddev);
// average error should be low
ASSERT_LT(ipcMean[0], 0.03);
// average error should be less than non-iterative average error
ASSERT_LT(ipcMean[0], pcMean[0]);
// error stddev should be less than non-iterative error stddev
ASSERT_LT(ipcStddev[0], pcStddev[0]);
}
TEST(Imgproc_PhaseCorrelationIterative, 256x128_accuracy)
{
TestPhaseCorrelationIterative(Size(256, 128), 1);
}
TEST(Imgproc_PhaseCorrelationIterative, 64x64_accuracy_shift_1)
{
TestPhaseCorrelationIterative(Size(64, 64), 1);
}
TEST(Imgproc_PhaseCorrelationIterative, 64x64_accuracy_shift_16)
{
TestPhaseCorrelationIterative(Size(64, 64), 16);
}
TEST(Imgproc_PhaseCorrelationIterative, 0x0_image)
{
ASSERT_ANY_THROW(TestPhaseCorrelationIterative(Size(0, 0), 1));
}
TEST(Imgproc_PhaseCorrelationIterative, 1x1_image)
{
ASSERT_ANY_THROW(TestPhaseCorrelationIterative(Size(1, 1), 1));
}
TEST(Imgproc_PhaseCorrelationIterative, accuracy_real_img)
{
Mat img = imread(cvtest::TS::ptr()->get_data_path() + "shared/airplane.png", IMREAD_GRAYSCALE);
if (img.empty())
return;
img.convertTo(img, CV_64FC1);
const int xLen = 256;
const int yLen = 256;
const int xShift = 40;
const int yShift = 14;
Mat roi1 = img(Rect(xShift, yShift, xLen, yLen));
Mat roi2 = img(Rect(0, 0, xLen, yLen));
const Point2d ipcShift = phaseCorrelateIterative(roi1, roi2);
ASSERT_NEAR(ipcShift.x, (double)xShift, 1.);
ASSERT_NEAR(ipcShift.y, (double)yShift, 1.);
}
}} // namespace opencv_test
+13
View File
@@ -311,5 +311,18 @@ TEST_P(StackBlur_GaussianBlur, compare)
}
INSTANTIATE_TEST_CASE_P(Imgproc, StackBlur_GaussianBlur, testing::Values(CV_8U, CV_16S, CV_16U, CV_32F));
TEST(Imgproc_StackBlur, regression_28233)
{
Mat src1(1, 1, CV_8UC1, Scalar(123));
Mat dst1;
EXPECT_NO_THROW(stackBlur(src1, dst1, Size(9, 1)));
EXPECT_EQ(dst1.at<uchar>(0, 0), 123);
Mat src2(3, 3, CV_8UC1, Scalar(50));
Mat dst2;
EXPECT_NO_THROW(stackBlur(src2, dst2, Size(11, 11)));
EXPECT_EQ(dst2.at<uchar>(1, 1), 50);
}
}
}