-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Add Non-Maximum Merging (NMM) to Detections #500
Conversation
…e large object detection accuracy
Hi @mario-dg 👋🏻! Sorry for lagging with the review. I will take a look at it on Monday. |
Hi @SkalskiP👋, no worries! |
This is great feature. When will it be merged? @SkalskiP |
@kadirnar, I need to review it first. I didn't have time to work on it yet. |
Hi @mario-dg 👋🏻 We aim to significantly enhance our |
Hey @SkalskiP, thanks for coming back to this PR! |
Hi @mario-dg 👋🏻 that's awesome! We are planning a major wave of updates in InferenceSlicer that includes:
This PR looks like the perfect candidate to achieve the last of these goals. |
Thats great! Supervision might become a much more suitable lightweight alternative to SAHI, once we've improved its capabilities. Anything specific I could work on? |
Alright @SkalskiP, will get to it this evening. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @mario-dg,
I've reviewed the PR. Frankly, I think it's a really well structured, well-documented contribution. I especially like how you managed to match the function API and documentation to what we have in with_nms
🙂
I've noted down the changes that we'd need. Some extra tests would be good to do as well.
- You can find one here: https://storage.googleapis.com/com-roboflow-marketing/supervision/video-examples/beach-1.mp4
- There's some code to run the models here. We're especially interested in the case of
from_inference
as that may produce thedata
object I mentioned in a few comments. https://supervision.roboflow.com/develop/how_to/detect_small_objects/
Let me know if you have any questions.
Hey @SkalskiP, as mentioned in one of the comments - you were right about data
missing in __setitem__
.
The broader question is: what's contained inside it? Do we have a list of possible dict entries? We would want to unify into a single object, so it's a different 'merge' than what we had before in Detections.merge
.
Thank you @LinasKo for this in depth review. I think I got most of what you've commented. |
The whole point of the |
Got it; then there's no ideal solution. In light of that - more confidence is a nice tie-breaker. @mario-dg, can I request a change? If we're relying on confidence as a tie-breaker, let's make the |
@LinasKo yes sure! Was about to start with your first comments, so I will keep that in mind and use the confidences as a base to determine which ids will be picked in the merging process. |
Hey @mario-dg, Good work! I'm reviewing it now. For testing, you might find something useful in this Colab. If you remove the inference slicer and its callbacks, you should be able to evaluate the runtime of your functions on an image with many realistic detections. If you do use it, I suggest you remove surplus tests - Ultralytics runs quickest and is easy to install. Also, I don't think you'll need |
Other than that, I like how this looks. |
Upon closer inspection, Specifically, many of the variables we have such as We'd like to merge today, so I'll see if I can fix it. |
Hey @LinasKo, sorry for being absent lately. Due to being pretty busy with work and my masters thesis, I'm currently not able to test or benchmark. |
It's alright; we've all been through that 😉 Thank you very much for your contribution. I'll make sure it gets verified and merged soon! |
Hey @SkalskiP, I pushed an update; here's some questions:
|
* Ruff complains when `== True` is used * Different behaviour with `is True`
fe11936
to
559ef90
Compare
Awesome! Less is more!
No. That's okey.
Yup. Let's move it and use
I'm really sorry but I'm not sure what you are asking. Sorry once again. |
Thanks, that's helpful :)
|
Added I believe I've addressed the change requests. |
supervision/detection/core.py
Outdated
return Detections.merge(result) | ||
|
||
|
||
def merge_object_detection_pair(det1: Detections, det2: Detections) -> Detections: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please rename the arguments to detections_1
and detections_2
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, I think we have a naming problem. Our current marge
should be called concatenate
, and this should be just merge.
But as long as merge_object_detection_pair
is not part of public API we don't need to overthink it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made an attempt to improve the naming:
- box_non_max_merge -> _box_non_max_merge_all (open to name ideas)
- box_non_max_merge_batch -> box_non_max_merge. Now it's the main method, exactly like * box_non_max_suppression
- merge_object_detection_pair -> _merge_inner_detection_object_pair
- new method: _merge_inner_detections_objects
supervision/detection/core.py
Outdated
@@ -1066,6 +1068,33 @@ def __setitem__(self, key: str, value: Union[np.ndarray, List]): | |||
|
|||
self.data[key] = value | |||
|
|||
def _set_at_index(self, index: int, other: Detections): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't placing this code as part of the setitem method makes more sense? The flow below feels quite natural to me.
detections_1 = sv.Detections(...)
detections_2 = sv.Detections(...)
detections_1[0] = detections_2[0]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__setitam__
detections_2[0]
detections_2["class_name"]
__getitam__
detections_2[0]
detections_2[1:3]
detections_2[[1, 2, 3]]
detections_2[[False, True, False]]
detections_2["class_name"]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_set_at_index was not required. I removed it entirely, and did not add any logic to __setitem__
.
supervision/detection/utils.py
Outdated
@@ -274,6 +275,81 @@ def box_non_max_suppression( | |||
return keep[sort_index.argsort()] | |||
|
|||
|
|||
def box_non_max_merge( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one seems like a helper function that does not need to be exposed in the API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renamed to _box_non_max_merge_all
, removed from init.py.
Note: you'll see box_non_max_merge
which is the prior batch function. Now it's the main one.
* Reintroduced iou check before response - necessary for algorithm
@SkalskiP, Here's some updates. I've addressed every comment you had, with the major changes being as follows:
Unexpected changes:
Same colab as before, verified to work as the original author intended: https://colab.research.google.com/drive/1v0MPlG1tQctX5-koh0l6h1NcB6eWJ_YY#scrollTo=hObeS7Dg8_AP Let me know what you think. |
Raises: | ||
ValueError: If one field is None and the other is not, for any of the fields. | ||
""" | ||
attributes = ["mask", "confidence", "class_id", "tracker_id"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we try to get that list automatically?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried it, it's cumbersome, I'll add the code + tests in a separate PR and we can choose whether to keep it.
|
||
result = [] | ||
for merge_group in merge_groups: | ||
unmerged_detections = [self[i] for i in merge_group] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we don't need that list comprehension, just use detections[indexes]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My explanation was wrong.
We're doing this not to copy the result (that's in another case), but to create a list of single-object detections. [Detections, Detections, Detections, ...]
.
I believe this is the most concise way.
Hi @SkalskiP, I've tidied this up - I believe it can now be merged. |
That's awesome! Didn't image that this PR would turn out as big as it got. Thanks guys!🚀 |
Description
Currently it is only possible to apply Non-Maximum Suppression (NMS) to Detections.
In an attempt to increase the accuracy of object detection and segmentation, especially when using the InferenceSlicer,
I added the ability to apply NMM instead of NMS to the merged Detections merge_detections.
NMM merges bounding boxes and masks, that would have originally been discarded, when applying NMS.
This results in more accurate detections/segmentations.
The implementation of this algorithm closely follows the one from SAHI.
In addition, also inspired by SAHI, the InferenceSlicer can increase the detection accuracy of larger objects, by running inference
on the whole image on top of all slices perform_standard_pred. The whole image detections will be merged with the sliced image detections.
I would be happy to discuss implementation changes and design choices.
This PR should serve as a first implementation and is far from perfect.
Any ideas, critic or comments are more than welcome :)
Type of change
Please delete options that are not relevant.
How has this change been tested, please provide a testcase or example of how you tested the change?
NMS:
NMM:
SAHI:
The code sample that was used to create these two images:
Any specific deployment considerations
The two additional parameters that were added to InferenceSlicer are optional and default to
False
, thus neither breaking, nor changing the results of existing implementations.Docs