From c90ef3f8bba6aad228c8591cc9c7cc012eff8852 Mon Sep 17 00:00:00 2001 From: Joonas Lipping Date: Fri, 16 Oct 2026 08:11:28 +0300 Subject: [PATCH 1/2] fix(image_transforms): guard against division by zero in create_gaussian_filter() When sigma is very small, gaussian(max_size/2, sigma) can underflow to zero, causing a division by zero in the loop condition. Add epsilon to the denominator. --- dlib/image_transforms/spatial_filtering.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dlib/image_transforms/spatial_filtering.h b/dlib/image_transforms/spatial_filtering.h index 91dcae321b..72ab95cbdd 100644 --- a/dlib/image_transforms/spatial_filtering.h +++ b/dlib/image_transforms/spatial_filtering.h @@ -1191,9 +1191,10 @@ namespace dlib // This only matters when T is an integer type. However, we do it for // all types so that the behavior of this function is always relatively // the same. - while (gaussian(0,sigma)/gaussian(max_size/2,sigma) > 50) + while (gaussian(0,sigma)/(gaussian(max_size/2,sigma) + + std::numeric_limits::epsilon())> 50) { --max_size; - + } matrix f(max_size); for (long i = 0; i < f.size(); ++i) From e5d7b938e8e90e7f5543eca3574c9415a8c4a11b Mon Sep 17 00:00:00 2001 From: Davis King Date: Fri, 16 Oct 2026 22:09:06 -0400 Subject: [PATCH 2/2] Preserve Gaussian filter cutoff without dividing by tails --- dlib/image_transforms/spatial_filtering.h | 4 +- dlib/test/image.cpp | 86 +++++++++++++++++++++++ 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/dlib/image_transforms/spatial_filtering.h b/dlib/image_transforms/spatial_filtering.h index 72ab95cbdd..702b4e94c9 100644 --- a/dlib/image_transforms/spatial_filtering.h +++ b/dlib/image_transforms/spatial_filtering.h @@ -1191,10 +1191,8 @@ namespace dlib // This only matters when T is an integer type. However, we do it for // all types so that the behavior of this function is always relatively // the same. - while (gaussian(0,sigma)/(gaussian(max_size/2,sigma) + - std::numeric_limits::epsilon())> 50) { + while (gaussian(0,sigma) > 50*gaussian(max_size/2,sigma)) --max_size; - } matrix f(max_size); for (long i = 0; i < f.size(); ++i) diff --git a/dlib/test/image.cpp b/dlib/test/image.cpp index 6c6c7bd128..c9b4900f19 100644 --- a/dlib/test/image.cpp +++ b/dlib/test/image.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -12,6 +13,7 @@ #include #include #include +#include #include "tester.h" @@ -939,6 +941,89 @@ namespace } + template + void test_gaussian_blur() + { + array2d img(9,11), blurred; + assign_all_pixels(img, 100); + const double sigmas[] = {1, 0.3575067961838845, 0.357506796183885, 18}; + const int max_sizes[] = {1001, 3, 3, 5}; + const int borders[] = {2, 0, 1, 2}; + for (unsigned long i = 0; i < 4; ++i) + { + const rectangle rect = gaussian_blur(img, blurred, sigmas[i], max_sizes[i]); + DLIB_TEST(rect == shrink_rect(get_rect(img), borders[i])); + DLIB_TEST(have_same_dimensions(img, blurred)); + for (long r = 0; r < img.nr(); ++r) + { + for (long c = 0; c < img.nc(); ++c) + { + if (rect.contains(point(c,r))) + { + // Integer output may truncate a floating-point result at sigma=18. + const double tolerance = is_float_type::value ? 1e-4 : 1; + DLIB_TEST(std::abs(static_cast(blurred[r][c])-100) <= tolerance); + } + else + { + DLIB_TEST(blurred[r][c] == 0); + } + } + } + } + } + + void test_gaussian_filter() + { +#if defined(FE_DIVBYZERO) && defined(FE_OVERFLOW) + std::fenv_t environment; + DLIB_TEST(std::fegetenv(&environment) == 0); + auto restore_environment = make_scope_exit([&] { std::fesetenv(&environment); }); + DLIB_TEST(std::feclearexcept(FE_ALL_EXCEPT) == 0); +#endif + + // At the default size, the Gaussian tail can underflow to zero. Selecting a smaller + // filter must not divide by that tail or overflow a density ratio. + const matrix integer_filter = create_gaussian_filter(1,1001); + const matrix float_filter = create_gaussian_filter(1,1001); + const matrix double_filter = create_gaussian_filter(1,1001); +#if defined(FE_DIVBYZERO) && defined(FE_OVERFLOW) + DLIB_TEST(std::fetestexcept(FE_DIVBYZERO | FE_OVERFLOW) == 0); +#endif + matrix expected_integer; + expected_integer = 1,4,7,4,1; + matrix expected_double; + expected_double = 0.05399096651318806, 0.24197072451914337, + 0.3989422804014327, + 0.24197072451914337, 0.05399096651318806; + DLIB_TEST(integer_filter == expected_integer); + DLIB_TEST(max(abs(double_filter-expected_double)) < 1e-15); + DLIB_TEST(max(abs(matrix_cast(float_filter)-expected_double)) < 1e-7); + + // These lie on opposite sides of the 50:1 cutoff. Adding epsilon to + // the edge density incorrectly retains three taps for the lower sigma. + const double below_cutoff = 0.3575067961838845; + const double above_cutoff = 0.357506796183885; + DLIB_TEST(create_gaussian_filter(below_cutoff,3).size() == 1); + DLIB_TEST(create_gaussian_filter(below_cutoff,3).size() == 1); + DLIB_TEST(create_gaussian_filter(below_cutoff,3).size() == 1); + matrix expected_three; + expected_three = 1,50,1; + DLIB_TEST(create_gaussian_filter(above_cutoff,3) == expected_three); + DLIB_TEST(create_gaussian_filter(above_cutoff,3).size() == 3); + DLIB_TEST(create_gaussian_filter(above_cutoff,3).size() == 3); + DLIB_TEST(create_gaussian_filter(0.1,1001).size() == 1); + DLIB_TEST(create_gaussian_filter(1,1).size() == 1); + + test_gaussian_blur(); + test_gaussian_blur(); + test_gaussian_blur(); +#if defined(FE_DIVBYZERO) && defined(FE_OVERFLOW) + DLIB_TEST(std::fesetenv(&environment) == 0); + restore_environment.release(); +#endif + } + void test_filtering2(int nr, int nc, dlib::rand& rnd) { print_spinner(); @@ -2619,6 +2704,7 @@ namespace test_integral_image(); test_zero_border_pixels(); + test_gaussian_filter(); test_filtering(false,1); test_filtering(true,1);