Skip to content

ximgproc: fix uninitialised read of ellipse periphery point in ValidateCircles - #4208

Open
purehol wants to merge 1 commit into
opencv:4.xfrom
purehol:fix-edgedrawing-odd-ellipse-points
Open

ximgproc: fix uninitialised read of ellipse periphery point in ValidateCircles#4208
purehol wants to merge 1 commit into
opencv:4.xfrom
purehol:fix-edgedrawing-odd-ellipse-points

Conversation

@purehol

@purehol purehol commented Sep 2, 2026

Copy link
Copy Markdown

Root cause

ValidateCircles() sets noPoints = computeEllipsePerimeter(...) (odd about half
the time) and calls ComputeEllipsePoints(px, py, noPoints), which writes exactly
2 * (noPoints/2) points. For an odd noPoints the last slot px/py[noPoints-1]
is never written, yet the validation loop for (j = 0; j < noPoints; j++) reads
it — an uninitialised read for the first candidate, or a stale value from a
previous candidate (the buffers are reused). That phantom point is then fed into
checkValidationByNFA(noPeripheryPixels, aligned).

Fix

Drop the odd point before sampling (if (noPoints % 2) noPoints--;), matching the
reference implementation ED_Lib. The OpenCV port had adopted the
points_buffer_size bound from the same upstream fix but missed this odd-count
guard.

Behavior change

This is not a neutral change of output — it removes a phantom sample from the NFA
statistics for odd-perimeter ellipses. The phantom point holds an indeterminate
value (uninitialised for the first candidate, stale for later ones); it is counted
as a periphery sample and, depending on that value, is either skipped by the bounds
check (when it falls outside the image) or fully processed (when it falls inside,
where it can also change aligned), so it perturbs the validation of every
odd-perimeter ellipse in an unpredictable direction. The effect is usually small:
over 93 OpenCV sample images plus a synthetic case, the fix changed the
detected-ellipse count on 6 of them (in both directions) and left the other 87
unchanged. Where an ellipse does change, the post-fix computation uses only the real
periphery points (the never-written slot is no longer read); we did not assess which
output is closer to ground truth. Reading the unwritten slot is also undefined
behaviour, flagged by memory sanitizers.

Verification

On stock OpenCV, valgrind --track-origins on detectEllipses() over an image
with several ellipses reports "uninitialised value" in ValidateCircles
(ERROR SUMMARY: 2 errors) before the change and 0 errors after it. A
deterministic, layout-independent confirmation (temporary source instrumentation,
not part of this change) writes a sentinel to px/py[noPoints-1] before the call
and observes it left unwritten for odd noPoints (e.g. 375, 229). Added
TEST_F(ximgproc_ED, detectEllipsesOddPerimeterNoUninitRead) to exercise the
odd-perimeter path under memory-checking CI. (valgrind flags only the
first-candidate uninitialised reads; the stale-reuse case is covered by the
write-count-vs-read-count argument above.)

Fixes #4207

  • I agree to contribute to the project under Apache 2 License.
  • 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
  • The PR is proposed to the proper branch
  • There is a reference to the original bug report and related work
  • There is accuracy test, performance test and test data in the repository, if applicable
  • The feature is well documented and sample code can be built with the project CMake

…teCircles

ValidateCircles() sets noPoints = computeEllipsePerimeter(...) (odd about half
the time) and calls ComputeEllipsePoints(px, py, noPoints), which writes exactly
2*(noPoints/2) points. For an odd noPoints the last slot px/py[noPoints-1] is
never written, yet the validation loop (j < noPoints) reads it: an uninitialised
read for the first candidate, or a stale value left by a previous candidate (the
buffers are reused), which then feeds the NFA alignment statistics. Drop the odd
point before sampling, matching the reference implementation ED_Lib.

Verified with a deterministic sentinel (px/py[noPoints-1] stayed unwritten for
odd noPoints on a real image) and with valgrind --track-origins (uninitialised
value at the ValidateCircles branches, origin at the px/py allocation); both are
clean after the change. Adds a test that exercises the odd-perimeter validation
path under memory-checking CI.
@purehol
purehol marked this pull request as ready for review September 2, 2026 16:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant