Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stbt.match: Optimization: Merge regions-of-interest that overlap completely #569

Merged
merged 8 commits into from
Feb 15, 2019

Conversation

drothlis
Copy link
Contributor

@drothlis drothlis commented Feb 14, 2019

Currently we're getting rois like this:

20-rois

Note that cv2.findContours with cv2.RETR_EXTERNAL is working correctly: those internal regions aren't internal contours, they're completely outside the large contour. It's only when we take the bounding rectangle of the large contour, that the smaller ones fall inside it. So we have to manually filter them out. I couldn't find a suitable OpenCV function to do it so I've implemented a simple quadratic loop.

After _merge_regions it looks like this:

9-rois

This is a quadratic implementation, but it shouldn't matter because we don't expect huge numbers of regions. On my laptop it takes:

number of regions ms
20 0.06
200 0.6
2000 9

TODO:

…lass

Note that I use `image[r.slice]` instead of `stbt.crop(image, r)`
because the latter raises "ValueError: frame with dimensions (360, 640,
3) doesn't contain Region(x=0, y=0, right=640, bottom=361)" -- I suppose
it's the pyramid upscaling that is introducing an extra 1 or 2 pixels to
some of the ROIs.
…letely

Currently we're getting rois like this:

![](https://user-images.githubusercontent.com/12424/52785764-b7c26c80-3050-11e9-84ff-acc46480e2ab.png)

Note that `cv2.findContours` with `cv2.RETR_EXTERNAL` is working
correctly: those internal regions aren't internal contours, they're
completely outside the large contour. It's only when we take the
*bounding rectangle* of the large contour, that the smaller ones fall
inside it. So we have to manually filter them out. I couldn't find a
suitable OpenCV function to do it so I've implemented a simple quadratic
loop.

After `_merge_regions` it looks like this:

![](https://user-images.githubusercontent.com/12424/52785909-230c3e80-3051-11e9-93d0-1e41f0cd5c14.png)

This is a simple quadratic implementation, but it shouldn't matter
because we don't expect huge numbers of regions. On my laptop it takes:

| number of regions |  ms |
|-------------------|-----|
|                20 | 0.2 |
|               200 |   4 |
|              2000 |  27 |
So that we only need 1 comparison for each pair of regions (the smaller
region can't possibly contain the larger region).

Also modify the list in-place.

Performance on my laptop:

| number of regions |   ms | previous implementation ms |
|-------------------|------|----------------------------|
|                20 | 0.06 |                        0.2 |
|               200 | 0.6  |                        4   |
|              2000 | 9    |                       27   |
(min(s.w - 1, r.x + r.w + t.w - 1),
min(s.h - 1, r.y + r.h + t.h - 1)),
(min(s.w - 1, r.right + t.w - 1),
min(s.h - 1, r.bottom + t.h - 1)),
Copy link
Contributor

Choose a reason for hiding this comment

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

Right, this is the same as:

d = roi.extend(right=template.shape[1], bottom=template.shape[0])
d = Region.intersect(d, _image_region(source_with_rois))
cv2.rectangle(source_with_rois, (d.x, d.y), (d.right - 1, d.bottom - 1),
              (0, 255, 255), thickness=1)

Maybe we should have our own draw_rectangle function that takes a Region encompassing the last two lines.

@drothlis
Copy link
Contributor Author

I ran it on one of my test-packs. There are some regressions but overall it seems a worthwhile improvement (average 12ms improvement across all 1100 template×screenshot pairs):

screenshot from 2019-02-14 14-14-55

The worst slowdown (see below) had 350 regions!

Reference image Frame ROIs identified at previous pyramid level ROIs in the full sized image
template source level1-source_matchtemplate_threshold level0-source_with_rois

@drothlis drothlis added this to the v30 milestone Feb 14, 2019
@drothlis
Copy link
Contributor Author

drothlis commented Feb 14, 2019

This was the best improvement: 18 largeish regions that are redundant, because we already need to search the entire frame anyway.

level0-source_with_rois

To test the performance impact of various `stbt.match` optimisations.

I have added screenshots of one pathological case for the "merge
regions-of-interest that overlap completely" optimisation, and one case
that justifies it.

(You can test your own images by adding them under
`tests/images/performance/` without committing them to git.)
For more consistent results. Only tested on my laptop.
For more consistent results across different implementations of
`_merge_regions`.
`slice` doesn't feel like a property to me like `width` or `height`.
_stbt/types.py Outdated
@@ -129,6 +129,11 @@ def height(self):
"""The height of the region, measured in pixels."""
return self.bottom - self.y

@property
def slice(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure this makes sense as a property as much as a function. Perhaps as_slice() or to_slice()?

@wmanley wmanley merged commit 56fd3df into master Feb 15, 2019
@wmanley wmanley deleted the merge-nested-rois branch February 15, 2019 12:21
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.

None yet

2 participants