Repository navigation
Conversation
… 1.0 When Params::Sigma < 1.0, detectEdges() set `smoothImage = srcImage`, aliasing the caller's input Mat. detectEllipses() then runs an in-place GaussianBlur into smoothImage (the PFmode path in detectEdges() does the same), silently overwriting the user's input image and any later reads of it. Clone the image so the input stays read-only, matching the reference implementation ED_Lib. Adds a regression test that fails before this change (1916 pixels of the input were modified) and passes after it.
imho no need to clone |
|
Thanks for taking a look, @sturkmen72. You're right that nothing writes to The caller's image is modified when all of the following hold:
The problem can be reproduced with the C++ code in #4203, or with the Python code below (tested with the official import numpy as np, cv2
img = np.full((200, 200), 255, np.uint8)
cv2.circle(img, (100, 100), 60, 0, 2)
backup = img.copy()
ed = cv2.ximgproc.createEdgeDrawing()
p = cv2.ximgproc.EdgeDrawing.Params()
p.Sigma = 0.0
ed.setParams(p)
ed.detectEdges(img)
ed.detectEllipses()
print(np.count_nonzero(img != backup)) # 1916
print(np.array_equal(img, cv2.GaussianBlur(backup, (0, 0), 0.5))) # TrueIt also affects the detector's own results. About the fix: |
|
https://github.com/purehol/opencv_contrib/blob/6b5b73a05c01753f6870d4f06b080de647019a6f/modules/ximgproc/src/edge_drawing.cpp#L2718 |
|
@purehol i propose a fix like 5.x...sturkmen72:opencv_contrib:fixes_source_image_changing |
With Sigma < 1.0 smoothImage shared the caller's buffer. opencv#4203 named two writes into that buffer, the GaussianBlur() in detectEllipses() and the one in the PFmode branch of detectEdges(), but only the first had a test. Two more cases turned up while checking the remaining uses of smoothImage: the next detectEdges() with Sigma >= 1.0 on the same detector blurred into the still shared buffer, i.e. into the previous input image, and raising Sigma before detectEllipses() did not prevent the write, since the sharing was created by detectEdges(). Replace the single-case test by one that runs the four cases of the opencv#4203 reproducer, so that a fix has to cover all of them.
|
@sturkmen72 thanks for the review. #4203 and the description name two places that blur into the shared buffer,
Starting from your guard at edge_drawing.cpp:2718, I built the variants below against the same tree (each branch is 4.x plus
A guard keyed on One correction to my comment of 13 October: I wrote that You know this code much better than I do, so before I push the test I would like to ask for your advice: of the variants above, which one would you prefer to see in this PR? Each branch builds and runs the four-case test as it is, so any of them can be checked directly. |
|
@purehol thank you for detailed analysis. i missed blurring when PFmode = true it seems solves all complexity |
|
@sturkmen72 thank you for the review. I pushed the test as a second commit (purehol/opencv_contrib@ba5b7f5): @asmorkalov the new commit needs workflow approval before CI can run. Could you approve it? |
Root cause
On 4.x, with
Params::Sigma < 1.0,detectEdges()setssmoothImage = srcImage(edge_drawing.cpp:411), andsrcImage = src.getMat()(edge_drawing.cpp:396) is a header on the caller's buffer.smoothImagetherefore shares the caller's image until it is reallocated, and every laterGaussianBlur()intosmoothImageblurs that image in place. This happens in four cases:detectEllipses()GaussianBlur(srcImage, smoothImage, Size(), 0.50)— edge_drawing.cpp:2718detectEdges()withPFmode = trueGaussianBlur(srcImage, smoothImage, Size(), 0.4)— edge_drawing.cpp:430detectEdges()withSigma >= 1.0on the same detector and a same-size image — writes into the previous inputGaussianBlur(srcImage, smoothImage, ...)— edge_drawing.cpp:413-415Sigmaraised to>= 1.0beforedetectEllipses()— the sharing was created bydetectEdges()Fix
smoothImage = srcImage.clone()at edge_drawing.cpp:411 givessmoothImageits own buffer where the sharing is created, so all four cases are covered by one line and no later stage has to know about it. Cost is onewidth*heightcopy perdetectEdges()call whenSigma < 1.0.Verification
TEST_F(ximgproc_ED, inputNotModifiedWithSigmaBelowOne)runs the four cases, one detector and oneEXPECTper case. On 4.x it fails in all four cases (1916 / 1356 / 3618 / 1916 modified pixels); with the fix it passes. The otherximgproc_EDandximgproc_FLDtests, includingdetectLinesAndEllipses, pass with and without the fix.Effect on results
Compared with 4.x, results are identical for
Sigma >= 1.0, and forSigma < 1.0withPFmode = false(checked on the test image and on 8 images ofopencv/samples/data). WithSigma < 1.0andPFmode = true,detectLines()/detectEllipses()results change (e.g.fruits.jpg: 351 → 347 lines, 2 → 1 ellipses), because validation now reads the unmodified input instead of the image that edge_drawing.cpp:430 had blurred in place.Changes since the approval
edge_drawing.cpp: unchanged.test_fld.cpp:detectEllipsesDoesNotModifyInput(case 1 only) is replaced withinputNotModifiedWithSigmaBelowOne(all four cases).Fixes #4203