Skip to content

Fix bug 16410 and add test#16415

Merged
alalek merged 5 commits intoopencv:3.4from
arnaudbrejeon:bug_fix_16410
Jan 29, 2020
Merged

Fix bug 16410 and add test#16415
alalek merged 5 commits intoopencv:3.4from
arnaudbrejeon:bug_fix_16410

Conversation

@arnaudbrejeon
Copy link
Copy Markdown
Contributor

resolves #16410

Here is what is happening. In order to do the parallelization, the image is split in stripes and labelling is done on each stripe. In order for stripes to be independent, each stripe starting at height y and with width w gets a startLabel defined as:
startLabel = (y+1)/2 * (w+1)/2 + 1

The formula comes from the fact that for an image of height h and width w, an upper bound of the number of labels is: (h+1)/2 * (w+1)/2 + 1

The startLabels are too close to each other in some cases and create labels overlapping. Here is why:
let's say a stripe starts in y0 and finishes in y1, we can write
y0 = 2*y + 1 and y1 = 2*y + 1 + h
The maximum number of labels is (h+1)/2 * (w+1)/2 + 1
The number of labels for the stripe is:
( (2*y+1+h+1)/2 * (w+1)/2 + 1 ) - ( (2*y+1+1)/2 * (w+1)/2 + 1 ) which is equal to
((h+2)/2 - 1) * (w+1)/2 = (h/2) * (w+1)/2

If h is odd, then the number of allowed labels in the stripe is not large enough.

In order to make sure there is enough labels for each stripe, I suggest we use the formula:
startLabel = (y+2)/2 * (w+1)/2 + 1

@asmorkalov
Copy link
Copy Markdown
Contributor

Hello @arnaudbrejeon thanks for the bug report and fix!

@alalek
Copy link
Copy Markdown
Member

alalek commented Jan 28, 2020

@arnaudbrejeon Well done! Thanks for the reproducer!

Reference on original patch: #8789 (#7705).

If h is odd

Your guess seems correct.

Perhaps "odd" case is not expected at all by processing code:

  • chunksSizeAndLabels_[r] = range.end;
  • chunksSizeAndLabels_[startR + 1] = label - firstLabel;

and finally:

  • const double nParallelStripes = std::max(1, std::min(h / 2, getNumThreads()*4));
  • with L.rows / nThreads >= 2 limit:
    //Run parallel labeling only if the rows of the image are at least twice the number of available threads
    const bool is_parallel = currentParallelFramework != NULL && nThreads > 1 && L.rows / nThreads >= 2;

Last statements trying to guarantee that subtask size is not less than 2 rows (or may be even "even").
Anyway this approach is wrong because nStripes parameter of parallel_for() is just a hint and it is not accurate at all (like any result of floating-point calculations).

There is similar problem with 4 connectivity stripes: (r * imgLabels_.cols + 1) / 2 + 1

  • r=3, cols=3 => 6
  • r=4, cols=3 => 7
  • r=6, cols=3 => 10
  • 1 0 1 => 2 labels are required for 3 cols x 1 row
  • 1 0 1 | 0 1 0 | 1 0 1 => up to 5 labels for 3 x 3 stripe (but 10-6=4)

Q: Why are we observing crash instead of incorrect result?

A: Uninitialized array with garbage after the first element:

            //Tree of labels
            LabelT *P = (LabelT *)cv::fastMalloc(Plength * sizeof(LabelT));
            //First label is for background
            P[0] = 0;

There is one more similar place in LabelingGranaParallel (8 connectivity):

LabelT label = LabelT((r + 1) / 2)  * LabelT((imgLabels_.cols + 1) / 2) + 1;

Additional commits will be pushed shortly.

Copy link
Copy Markdown
Member

@alalek alalek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@alalek alalek merged commit ecbba85 into opencv:3.4 Jan 29, 2020
@alalek alalek mentioned this pull request Feb 1, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants