roboflow / supervision

We write your reusable computer vision tools. 💜
https://supervision.roboflow.com
MIT License
17.14k stars 1.31k forks source link

To get the count of objects (in/out) in the polygon area, like we have it for line_zone using the supervision library for yolo-v8 #54

Open DhanapriyaJ opened 1 year ago

DhanapriyaJ commented 1 year ago

Search before asking

Description

Count of object that goes in or goes out of the polygon area. Also, the count(in/out) by each class. The yolo model can be either a detection or a segmentation model.

Use case

No response

Additional

No response

Are you willing to submit a PR?

github-actions[bot] commented 1 year ago

Hello there, thank you for opening an Issue ! 🙏🏻 The team was notified and they will get back to you asap.

Jipr commented 1 year ago

@DhanapriyaJ I have found a way to count whether specific objects are going in or out, thereby providing an answer to your question regarding the count (in/out) for each class.

import cv2
from ultralytics import YOLO
import supervision as sv

#Global variables#--------------------------------------
LINE_START = sv.Point(320, 0)
LINE_END = sv.Point(320, 480)
object1_id = 41 # Object ID of: cup
object2_id = 39 # Object ID of: bottle

def create_labels(model, detections):
    labels = [
        f"# {class_id}{model.model.names[class_id]} {confidence:0.2f}"
        for _, confidence, class_id, tracker_id
        in detections 
    ]
    return labels

class object_line_counter():
    def __init__(self,class_id_number,line_counter):
        self.class_id_number = class_id_number
        self.line_counter = line_counter

    def detections(self,result):
        detections = sv.Detections.from_yolov8(result)
        detections = detections[detections.class_id == self.class_id_number]

        if result.boxes.id is not None:
            detections.tracker_id = result.boxes.id.cpu().numpy().astype(int)  

        self.line_counter.trigger(detections=detections)
        count_in = self.line_counter.in_count
        count_out = self.line_counter.out_count

        return count_in,count_out

def main():
    line_counter = sv.LineZone(start=LINE_START, end=LINE_END)
    line_counter1 = sv.LineZone(start=LINE_START, end=LINE_END)
    line_counter2 = sv.LineZone(start=LINE_START, end=LINE_END)

    line_annotator = sv.LineZoneAnnotator(thickness=2, text_thickness=1, text_scale=0.5)
    box_annotator = sv.BoxAnnotator(thickness=2,text_thickness=1,text_scale=0.5)

    # Create class objects
    object1 = object_line_counter(object1_id, line_counter1)
    object2 = object_line_counter(object2_id, line_counter2)

    model = YOLO("yolov8l.pt")

    for result in model.track(source=0, show=True, stream=True, agnostic_nms=True):

        frame = result.orig_img

        #Detect al objects
        detections = sv.Detections.from_yolov8(result)
        detections = detections[detections.class_id != 0]
        if result.boxes.id is not None:
            detections.tracker_id = result.boxes.id.cpu().numpy().astype(int)
        labels = create_labels(model,detections)

        #Detect specific objects
        object1_in,object1_out = object1.detections(result)
        object2_in,object2_out = object2.detections(result)

        frame = box_annotator.annotate(scene=frame, detections=detections, labels=labels)

        line_counter.trigger(detections=detections)
        line_annotator.annotate(frame=frame, line_counter=line_counter)

        print('object1_in',object1_in, 'object1_out',object1_out)
        print('object2_in',object2_in, 'object2_out',object2_out)

        cv2.imshow("yolov8", frame)

        if (cv2.waitKey(30) == 27):
            break

if __name__ == "__main__":
    main()
SkalskiP commented 1 year ago

@DhanapriyaJ can I close this issue given @Jipr came up with this solution?

DhanapriyaJ commented 1 year ago

My issues is regarding the polygon area. @Jipr has given the solutions to get the in/out counts for the object which is required using the line zone .Is it possible to get the in/out counts in the polygon area @SkalskiP.

SkalskiP commented 1 year ago

Ah... not yet. We have that on our road map. But it won't be anything we add very soon unfortunately :/

maddust commented 1 year ago

@DhanapriyaJ

im checking if a object is inside a polygon with the following code . disregard the frame_check if you want to track every frame . im skiping frames to check every second instead of every frame.

With this you can determine how long the object stayed inside the polygon and when was seen for the first time and the last time (in/out)

hope it helps .

Polygon Tigger and Annotation

    polygon_zone_1.trigger(detections=detections)
    mask=polygon_zone_1.trigger(detections=detections)
    in_zone_detections=detections[mask]
    if frame_count % fame_check == 0:
        for i, detection in enumerate(in_zone_detections):
            tracker_id = detection[4]
            class_id = int(detection[3])
            class_names = CLASS_NAMES_DICT.get(class_id)
            confidence = round(float(detection[2]) * 100)
            print(f"Object Inside Polygon: Frame: {frame_count}, Tracker ID: {tracker_id}, Class ID: {class_id}, Class Name: {class_names}, Confidence: {confidence}")

    polygon_zone_1_annotator.annotate(scene=frame)
SkalskiP commented 1 year ago

Yes @maddust! 🔥

This is more or less what I expect. To solve the issue end to end you would most likely need to have Python dictionary that would track the frame_id of the first appearance of a given track_id in the zone and that's it.

Newthin commented 10 months ago

my problem is how do i print the Zone Current_count

SkalskiP commented 10 months ago

Hi @Newthin 👋🏻 Do You mean print in the terminal or draw on the frame?

Newthin commented 10 months ago

I mean print it on terminal

On Fri, Aug 18, 2023, 10:23 AM Piotr Skalski @.***> wrote:

Hi @Newthin https://github.com/Newthin 👋🏻 Do You mean print in the terminal or draw on the frame?

— Reply to this email directly, view it on GitHub https://github.com/roboflow/supervision/issues/54#issuecomment-1683699997, or unsubscribe https://github.com/notifications/unsubscribe-auth/AXNF47COSTEO3JLLPB4ALFLXV47CZANCNFSM6AAAAAAWLXHKIE . You are receiving this because you were mentioned.Message ID: @.***>

mahmad096 commented 8 months ago

Checkout supervision by roboflow: https://supervision.roboflow.com/how_to/filter_detections/

See PolygonZone

JSNN170 commented 7 months ago

Hi,

Did you find a solution for it? I am also trying to print the current_count on the terminal using the PolygonZone. There isn't anything related to it in the link you provided...

But it is mentioned in the API, but when I add the line of code 'print(sv.PolygonZone.current_count)' it says the PolygonZone object doesn't have this attribute...

Could you provided some help?

Thanks,

image

Newthin commented 7 months ago

I didn't get a solution to that problem

On Thu, Nov 23, 2023, 8:35 PM JSNN170 @.***> wrote:

Hi,

Did you find a solution for it? I am also trying to print the current_count on the terminal using the PolygonZone. There isn't anything related to it in the link you provided...

But it is mentioned in the API, but when I add the line of code 'print(sv.PolygonZone.current_count)' it says the PolygonZone object doesn't have this attribute...

Could you provided some help?

Thanks,

[image: image] https://user-images.githubusercontent.com/151624537/285302990-ae02923d-2f68-4bb2-9051-b9c62043cc45.png

— Reply to this email directly, view it on GitHub https://github.com/roboflow/supervision/issues/54#issuecomment-1824883326, or unsubscribe https://github.com/notifications/unsubscribe-auth/AXNF47EG3YYQSO7GV5ACHVDYF6XSNAVCNFSM6AAAAAAWLXHKIGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQMRUHA4DGMZSGY . You are receiving this because you were mentioned.Message ID: @.***>

mahmad096 commented 7 months ago

FABE0B0E-3357-4A83-BFBC-461A55BB2B2F

See if that helps

JSNN170 commented 7 months ago

Hi,

Could you provide me with an example?

I'm not sure that I fully understand it.

Regards,

Jason T.

On Fri, 24 Nov 2023 at 03:35, Evan Shlom @.***> wrote:

You have to deconstruct the premade class(es). Use the opencv method of doing a for loop and blend the supervision api's class with how OpenCV generally works when you run a for loop to run through the frames. Supervision classes are sometimes abstracted just a little too much so you have to remake them if you want to get analytics

— Reply to this email directly, view it on GitHub https://github.com/roboflow/supervision/issues/54#issuecomment-1825108681, or unsubscribe https://github.com/notifications/unsubscribe-auth/BEEZWWO42ZXN4IOSSHLKZITYGAIYVAVCNFSM6AAAAAAWLXHKIGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQMRVGEYDQNRYGE . You are receiving this because you commented.Message ID: @.***>

JSNN170 commented 7 months ago

Which variable is holding the current count within the polygon zone?

JSNN170 commented 7 months ago

I didn't get a solution to that problem On Thu, Nov 23, 2023, 8:35 PM JSNN170 @.> wrote: Hi, Did you find a solution for it? I am also trying to print the current_count on the terminal using the PolygonZone. There isn't anything related to it in the link you provided... But it is mentioned in the API, but when I add the line of code 'print(sv.PolygonZone.current_count)' it says the PolygonZone object doesn't have this attribute... Could you provided some help? Thanks, [image: image] https://user-images.githubusercontent.com/151624537/285302990-ae02923d-2f68-4bb2-9051-b9c62043cc45.png — Reply to this email directly, view it on GitHub <#54 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AXNF47EG3YYQSO7GV5ACHVDYF6XSNAVCNFSM6AAAAAAWLXHKIGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQMRUHA4DGMZSGY . You are receiving this because you were mentioned.Message ID: @.>

Hi Newthin,

I found a way to do it!

I have accessed the script under the PolygonZone tool by holding the cmd key over it. (on Visual Studio Code)

image

Then under the PolygonZone class, under the trigger function there is variable called "self.current_count = np.sum(is_in_zone)" and I used that variable to print the number of objects detected in the zone.

I defined a variable called "count_in_zone" in the PolygonZone class, initial value = 0 and then updated the class attribute count_in_zone in the trigger function.

I could then called this new variable from my separate script by typing "print(PolygonZone.count_in_zone)"

There may be a better way of doing it, but I have an intermediate level in coding... but it should work.

Screenshot 2023-11-28 at 13 47 45

Screenshot 2023-11-28 at 13 48 51

zijun3 commented 6 months ago

I didn't get a solution to that problem On Thu, Nov 23, 2023, 8:35 PM JSNN170 @._> wrote: Hi, Did you find a solution for it? I am also trying to print the current_count on the terminal using the PolygonZone. There isn't anything related to it in the link you provided... But it is mentioned in the API, but when I add the line of code 'print(sv.PolygonZone.currentcount)' it says the PolygonZone object doesn't have this attribute... Could you provided some help? Thanks, [image: image] https://user-images.githubusercontent.com/151624537/285302990-ae02923d-2f68-4bb2-9051-b9c62043cc45.png — Reply to this email directly, view it on GitHub <#54 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AXNF47EG3YYQSO7GV5ACHVDYF6XSNAVCNFSM6AAAAAAWLXHKIGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQMRUHA4DGMZSGY . You are receiving this because you were mentioned.Message ID: @_._>

Hi Newthin,

I found a way to do it!

I have accessed the script under the PolygonZone tool by holding the cmd key over it. (on Visual Studio Code)

image

Then under the PolygonZone class, under the trigger function there is variable called "self.current_count = np.sum(is_in_zone)" and I used that variable to print the number of objects detected in the zone.

I defined a variable called "count_in_zone" in the PolygonZone class, initial value = 0 and then updated the class attribute count_in_zone in the trigger function.

I could then called this new variable from my separate script by typing "print(PolygonZone.count_in_zone)"

There may be a better way of doing it, but I have an intermediate level in coding... but it should work.

Screenshot 2023-11-28 at 13 47 45

Screenshot 2023-11-28 at 13 48 51

Which variable is holding the current count within the polygon zone?

I didn't get a solution to that problem On Thu, Nov 23, 2023, 8:35 PM JSNN170 @._> wrote: Hi, Did you find a solution for it? I am also trying to print the current_count on the terminal using the PolygonZone. There isn't anything related to it in the link you provided... But it is mentioned in the API, but when I add the line of code 'print(sv.PolygonZone.currentcount)' it says the PolygonZone object doesn't have this attribute... Could you provided some help? Thanks, [image: image] https://user-images.githubusercontent.com/151624537/285302990-ae02923d-2f68-4bb2-9051-b9c62043cc45.png — Reply to this email directly, view it on GitHub <#54 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AXNF47EG3YYQSO7GV5ACHVDYF6XSNAVCNFSM6AAAAAAWLXHKIGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQMRUHA4DGMZSGY . You are receiving this because you were mentioned.Message ID: @_._>

Hi Newthin,

I found a way to do it!

I have accessed the script under the PolygonZone tool by holding the cmd key over it. (on Visual Studio Code)

image

Then under the PolygonZone class, under the trigger function there is variable called "self.current_count = np.sum(is_in_zone)" and I used that variable to print the number of objects detected in the zone.

I defined a variable called "count_in_zone" in the PolygonZone class, initial value = 0 and then updated the class attribute count_in_zone in the trigger function.

I could then called this new variable from my separate script by typing "print(PolygonZone.count_in_zone)"

There may be a better way of doing it, but I have an intermediate level in coding... but it should work.

Screenshot 2023-11-28 at 13 47 45

Screenshot 2023-11-28 at 13 48 51

Brilliant solution! It worked for me perfectly thank you very much!

nlitz88 commented 5 months ago

Yes @maddust! 🔥

This is more or less what I expect. To solve the issue end to end you would most likely need to have Python dictionary that would track the frame_id of the first appearance of a given track_id in the zone and that's it.

I just started using supervision and stumbled into this issue when I was looking for the same kind of functionality. I.e., I wanted to be able to count how many detections had entered and/or exited a PolygonZone, ultimately with the goal of tallying up the total number of detections in that zone over a period of time.

Because this isn't baked into the PolygonZone just yet, I wrote this little helper class ZoneMonitor that aims to do just that--it takes the detections from a particular zone for each frame, and it keeps track of when each detection "enters" and "exits" a zone.

"""Contains ZoneMonitor implementation. This class is meant to serve as a helper
class to be used alongside a Supervision PolygonZone for counting the number of
objects that enter and exit a zone.
"""

from typing import List, Optional, Tuple

import supervision as sv

class ZoneMonitor:
    """Class to keep track of the detections that enter and exit a zone."""

    def __init__(self,
                 in_threshold: Optional[int] = 15,
                 out_timeout: Optional[int] = 30) -> None:
        """Creates a new ZoneMonitor instance to track when detections enter and
        leave a zone.

        Args:
            in_threshold (Optional[int], optional): Number of frames that a
            detection must be tracked for before it is considered "in" the zone.
            Defaults to 15.
            out_timeout (Optional[int], optional): Number of frames that a
            detection considered to be "in" the zone can be absent for before it
            is deemed as "exited." This should probably match the tracker's
            "track_thresh" so that objects picked back up by tracker aren't
            double counted by this monitor.
        """
        # Grab constructor parameters.
        self._in_threshold = in_threshold
        self._out_timeout = out_timeout
        # Create dictionary for monitored detections. Maps a detection's
        # tracker_id to the number of times it has been seen and its timeout.
        # TODO: There should be an additional class made specifically just for
        # handling this dictionary and its entries.
        self._monitored_detections = {}

    def update(self, 
               detections_in_zone: sv.Detections) -> Tuple[List, List]:
        """Takes the detections from a zone and figures out which tracked
        detections have newly entered the zone and those that have exited the
        zone since the last call to update.

        Args:
            detections_in_zone (sv.Detections): Supervision Detections instance
            containing the detections from a zone.

        Returns:
            Tuple[List, List]: Returns a list of detection vectors that have
            recently entered the zone (been present for at least in_threshold
            frames) and the list of detection vectors that haven't been seen in
            out_timeout frames.
        """

        # Create list for detections that have been present in the video
        # sequence for at least in_threshold frames.
        entered_detections = []
        # Create list for detections that have not been present for at least
        # out_timeout frames.
        exited_detections = []

        # Add any new detections to the monitored list. Increment the number of
        # frames present for detections already in the monitored list.
        for detection in list(detections_in_zone):
            # Accessing elements of each detection according to
            # https://supervision.roboflow.com/detection/core/#detections
            tracker_id = detection[4]

            # If this tracked detection is already being monitored, then just
            # increments its frames_since_added count. Also refresh its out
            # timeout counter.
            if tracker_id in list(self._monitored_detections.keys()):
                if self._monitored_detections[tracker_id]["frames_present"] < self._in_threshold:
                    self._monitored_detections[tracker_id]["frames_present"] += 1
                    if self._monitored_detections[tracker_id]["frames_present"] == self._in_threshold:
                        entered_detections.append(detection)
                self._monitored_detections[tracker_id]["last_detection"] = detection
                self._monitored_detections[tracker_id]["out_timeout_counter"] = self._out_timeout

            # If not, this is a new detection. Add a new entry to the monitored
            # detections dictionary.
            else:
                self._monitored_detections[tracker_id] = {
                    "last_detection": detection,
                    "frames_present": 1,
                    "out_timeout_counter": self._out_timeout
                }

        # Remove any detections that haven't been present for out_timeout
        # frames. Decrement out_timeout_counter for all other missing
        # detections.
        for tracker_id in list(self._monitored_detections.keys()):
            if tracker_id not in detections_in_zone.tracker_id:
                self._monitored_detections[tracker_id]["out_timeout_counter"] -= 1
                if self._monitored_detections[tracker_id]["out_timeout_counter"] == 0:
                    if self._monitored_detections[tracker_id]["frames_present"] == self._in_threshold:
                        exited_detections.append(self._monitored_detections[tracker_id]["last_detection"])
                    del(self._monitored_detections[tracker_id])

        return entered_detections, exited_detections

To use it, I just create a ZoneMonitor instance right alongside the PolygonZone instance it is meant to monitor:

full_zone = sv.PolygonZone(polygon=full_zone_polygon,
                           frame_resolution_wh=(video_width, video_height),
                           triggering_position=sv.Position.CENTER)
TRACK_BUFFER_S = 8
full_zone_monitor = ZoneMonitor(in_threshold=int(video_framerate) // 2,
                                out_timeout=int(video_framerate)*TRACK_BUFFER_S)

And then, for each frame that you process, you can just pass it the detections from the zone and it will return the detections that "entered" or "exited" the zone since the last frame. As a rough example:

result = model(frame, verbose=False)[0]
detections = sv.Detections.from_ultralytics(ultralytics_results=result)
detections = tracker.update_with_detections(detections=detections)
zone_detections_mask = full_zone.trigger(detections=detections)
detections_in_zone = detections[zone_detections_mask]
entered_detections, exited_detections = full_zone_monitor.update(detections_in_zone=detections_in_zone)
# Can count total detections in zone, for example.
total_detections += len(entered_detections)

Not sure it's the most elegant approach, but I figured I'd share in case someone else could use it as a starting point for their own projects.