supervision icon indicating copy to clipboard operation
supervision copied to clipboard

line counter is not working anymore?

Open uzumakinaruto19 opened this issue 1 year ago • 22 comments

Search before asking

  • [X] I have searched the Supervision issues and found no similar feature requests.

Question

this is my code I wanted to add multiple lines and count the objects, but the line counter is not working anymore with the latest supervision.

import os
import torch
os.environ["CUDA_DEVICE_ORDER"]="PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"]="0"
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
import numpy as np
import json
from ultralytics import YOLO
import supervision as sv
# from supervision.tools.line_counter import LineCounter
# from supervision.geometry.dataclasses import Point


# LINE_START = Point(50, 1500)
# LINE_END = Point(3790, 1500)

# line_counter = LineCounter(start=LINE_START, end=LINE_END)

model = YOLO('yolov8n.pt')

def main_func(input_data,frame):

    global model

    if 'ignore' in input_data:
        return "ignore"
    else:

        input_data = json.loads(input_data)

        results= model.track(frame, imgsz=640, classes=0,tracker="bytetrack.yaml")[0]
        detections = sv.Detections.from_yolov8(results)

        if results.boxes.id is not None:
            detections.tracker_id = results.boxes.id.cpu().numpy().astype(int)
            detections.conf = results.boxes.conf.cpu().numpy().astype(float)
            detections.xyxy = results.boxes.xyxy.cpu().numpy().astype(int)
            
            # print(detections.tracker_id)
            # print(detections.conf)
            # print(detections.xyxy)
        else:
            detections.tracker_id = np.array([])
            detections.conf = np.array([])
            detections.xyxy = np.array([])
 
        new_box=detections.xyxy
        new_box = new_box.astype("int").tolist()
        new_ids = detections.tracker_id.astype("int").tolist()
        new_confs = detections.conf.astype("float").tolist()    



        print("count",count)

        output = {
                  'frame_index':input_data['frame_index'],
                  'time_stamp':input_data['time_stamp'],
                  'detections':new_box,
                  'demo_run':input_data['demo_run'],
                  'ids':new_ids,
                  'confs':new_confs
                  }
        
        print(output)
        return output

except for the polygon , how to have multiple lines and update the counts?

Additional

No response

uzumakinaruto19 avatar Jun 08 '23 11:06 uzumakinaruto19

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

github-actions[bot] avatar Jun 08 '23 11:06 github-actions[bot]

Hi, @uzumakinaruto19 👋🏻! Could you be a bit more specific? What do you mean by "but the line counter is not working anymore with the latest supervision"?

SkalskiP avatar Jun 08 '23 20:06 SkalskiP

@uzumakinaruto19 did you use trigger() yet? I don't see it in your code.

sceddd avatar Jun 09 '23 04:06 sceddd

Hi, @uzumakinaruto19 👋🏻! Could you be a bit more specific? What do you mean by "but the line counter is not working anymore with the latest supervision"?

hey sorry I'll provide the updated code - this my function for detection and tracking

def process_frame(input_data, frame, line_counter_1, line_counter_2, line_counter_3, line_annotator, box_annotator):

    
    results = model.track(frame, imgsz=640, classes=0, tracker="bytetrack.yaml")[0]
    detections = sv.Detections.from_yolov8(results)

    if results.boxes.id is not None:
        detections.tracker_id = results.boxes.id.cpu().numpy().astype(int)
        detections.conf = results.boxes.conf.cpu().numpy().astype(float)
        detections.xyxy = results.boxes.xyxy.cpu().numpy().astype(int)
    else:
        detections.tracker_id = np.array([])
        detections.conf = np.array([])
        detections.xyxy = np.array([])

    frame = box_annotator.annotate(scene=frame, detections=detections, labels=[str(id) for id in detections.tracker_id])
    line_counter_1.trigger(detections=detections)
    line_counter_2.trigger(detections=detections)
    line_counter_3.trigger(detections=detections)

    line_annotator.annotate(frame=frame, line_counter=line_counter_1)
    line_annotator.annotate(frame=frame, line_counter=line_counter_2)
    line_annotator.annotate(frame=frame, line_counter=line_counter_3)

    new_ids = detections.tracker_id.astype("int").tolist()
    new_box = detections.xyxy.astype("int").tolist()
    new_confs = detections.conf.astype("float").tolist()

    output = {
        'frame_index': input_data['frame_index'],
        'time_stamp': input_data['time_stamp'],
        'detections': new_box,
        'demo_run': input_data['demo_run'],
        'ids': new_ids,
        'confs': new_confs,
        'line_1_out_count': line_counter_1.out_count,
        'line_2_out_count': line_counter_2.out_count,
        'line_3_out_count': line_counter_3.out_count
    }

    print(output)
    return output




def display_frame(output, frame):
    cv2.imshow("Output", frame)
    cv2.waitKey(1)


# Provide the path to your video file
video_path = './test.mp4'
main_func(video_path)

even after using the trigger and the object crosses the line its not getting counted?

uzumakinaruto19 avatar Jun 09 '23 13:06 uzumakinaruto19

@uzumakinaruto19 did you use trigger() yet? I don't see it in your code.

i have used , I provided the wrong code, my bad

I think it had to do something with my tracking? well the IDs getting generated are not updating, I have some issue with the tracker so the ID is getting resued every time, ie, when an object enters and that is assigned with an ID and and when that leaves the frame and a new object comes it is reusing that the ID it should assign a new ID but it uses the number 1 again and start from there

i know tracking doesn't comes under this repo still , may that be an issue and how to tackle that, because the video might be an RTSP stream and id shouldn't be repeated just continuously and resued for at least sometime say like after 10 mins or something

@SkalskiP any help will be really appreciated

uzumakinaruto19 avatar Jun 09 '23 13:06 uzumakinaruto19

Hi, @uzumakinaruto19 👋🏻

I'm not sure you can use YOLOv8 ByteTrack this way. Here is a snippet of code I used in the past, and it worked. Important note supervision API may differ a bit. It is pretty old code, but the YOLOv8 tracking part worked 100%.

import cv2

from ultralytics import YOLO
import supervision as sv
import numpy as np


LINE_START = sv.Point(320, 0)
LINE_END = sv.Point(320, 480)


def main():
    line_counter = 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
    )

    model = YOLO("yolov8l.pt")
    for result in model.track(source=0, show=True, stream=True, agnostic_nms=True):
        
        frame = result.orig_img
        detections = sv.Detections.from_yolov8(result)

        if result.boxes.id is not None:
            detections.tracker_id = result.boxes.id.cpu().numpy().astype(int)
        
        detections = detections[(detections.class_id != 60) & (detections.class_id != 0)]

        labels = [
            f"{tracker_id} {model.model.names[class_id]} {confidence:0.2f}"
            for _, confidence, class_id, tracker_id
            in detections
        ]

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

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

        cv2.imshow("yolov8", frame)

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


if __name__ == "__main__":
    main()

SkalskiP avatar Jun 09 '23 15:06 SkalskiP

Hi, @uzumakinaruto19 👋🏻

I'm not sure you can use YOLOv8 ByteTrack this way. Here is a snippet of code I used in the past, and it worked. Important note supervision API may differ a bit. It is pretty old code, but the YOLOv8 tracking part worked 100%.

import cv2

from ultralytics import YOLO
import supervision as sv
import numpy as np


LINE_START = sv.Point(320, 0)
LINE_END = sv.Point(320, 480)


def main():
    line_counter = 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
    )

    model = YOLO("yolov8l.pt")
    for result in model.track(source=0, show=True, stream=True, agnostic_nms=True):
        
        frame = result.orig_img
        detections = sv.Detections.from_yolov8(result)

        if result.boxes.id is not None:
            detections.tracker_id = result.boxes.id.cpu().numpy().astype(int)
        
        detections = detections[(detections.class_id != 60) & (detections.class_id != 0)]

        labels = [
            f"{tracker_id} {model.model.names[class_id]} {confidence:0.2f}"
            for _, confidence, class_id, tracker_id
            in detections
        ]

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

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

        cv2.imshow("yolov8", frame)

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


if __name__ == "__main__":
    main()

@SkalskiP thanks, I've referred while doing it

well the detection is working fine aswell as the tracking

below shown is the output

WARNING ⚠️ imgsz=[740] must be multiple of max stride 32, updating to [768] {'frame_index': 10, 'time_stamp': 0, 'detections': [[275, 147, 317, 271], [330, 162, 347, 221]], 'demo_run': False, 'ids': [1, 2], 'confs': [0.8728579878807068, 0.731636643409729], 'line_1_out_count': 0} Line 1 Out Count: 0

I'm having issues while counting alone ids what I'm guessing

I have drawn the line but even after the object crossing it is not getting counted

uzumakinaruto19 avatar Jun 09 '23 15:06 uzumakinaruto19

With the current implementation, for the object to be counted, all four corners of the bounding box must cross the line.

SkalskiP avatar Jun 09 '23 16:06 SkalskiP

So u are suggesting extending the line more (I guess I tried that aswell, it is not getting counted at all, anyway I'll try once more), should a go with a polygon(idk whether that will make any sense with that)

uzumakinaruto19 avatar Jun 09 '23 16:06 uzumakinaruto19

I actually suggest putting the camera further away.

SkalskiP avatar Jun 09 '23 16:06 SkalskiP

With the current implementation, for the object to be counted, all four corners of the bounding box must cross the line.

Instead of that can't u use the centroid of the Box, more accurate maybe?

uzumakinaruto19 avatar Jun 09 '23 17:06 uzumakinaruto19

Using single point with models like YOLOv8 is not reliable solution unfortunately.

SkalskiP avatar Jun 09 '23 17:06 SkalskiP

Maybe you put the line in the wrong position. The logic is that if all the bounding boxes cross the line, the count will increase. In your case, you should either turn the camera to a higher position or detect the shoes and count if those shoes cross the line or you could take a look at #107 with the @maddust issue by choose the point at bottom center of the bounding box for counting.

sceddd avatar Jun 09 '23 17:06 sceddd

Maybe you put the line in the wrong position. The logic is that if all the bounding boxes cross the line, the count will increase. In your case, you should either turn the camera to a higher position or detect the shoes and count if those shoes cross the line or you could take a look at #107 with the @maddust issue by choose the point at bottom center of the bounding box for counting.

This actually worked in my case with @maddust suggestion, thanks a ton !! @SkalskiP and @sceddd

when using the polygon zone I'm getting this error, it's running for some seconds and throwing this error

result[:, [0, 2]] = result[:, [0, 2]].clip(0, width)
IndexError: too many indices for array: array is 1-dimensional, but 2 were indexed

And is it possible to get how much time an object spends in that zone alone?

Thanks in advance

uzumakinaruto19 avatar Jun 12 '23 09:06 uzumakinaruto19

This actually worked in my case

Awesome! :tada:

when using the polygon zone I'm getting this error, it's running for some seconds and throwing this error

Could you create a separate bug report where we could address that issue? Please make sure to provide the following:

  1. Version of supervision you use.
  2. Minimal reproducible example.
  3. Full stack trace.
  4. If you can video/image you use for tests.

And is it possible to get how much time an object spends in that zone alone?

It is but you would need a tracker for that. We also do not have ready-to-use tools you could use, but we can guide you and help you build your code.

SkalskiP avatar Jun 12 '23 11:06 SkalskiP

Could you create a separate bug report where we could address that issue?

I have raised issue #131 for that

It is but you would need a tracker for that. We also do not have ready-to-use tools you could use, but we can guide you and help you build your code.

I'm already having a tracker(ByteTrack) with yolov8

Really appreciate this repo and the help you guys giving!!

uzumakinaruto19 avatar Jun 12 '23 11:06 uzumakinaruto19

@uzumakinaruto19 As for your in-zone time counter. Here is a sketch of the python logic that you will need.

# dictionary that stores information when given detection starts to be in the zone
in_zone_state: Dict[int, int] = {}

# loop over frames
for frame_index, frame in ...:

    # trigger your zone with detections
    in_zone = polygon_zone.trigger(detections)
    # filter detections to get only those in the zone
    detections_in_zone = detections[in_zone]
    
    # tracker ids that were in the zone in the current frame
    curr_in_zone = set(detections_in_zone.tracker_id)
    # tracker ids that were in the zone in the previous frame
    prev_in_zone = set(in_zone_state.keys())
    # tracker ids that just entered the zone
    new = curr_in_zone.difference(prev_in_zone)
    # tracker ids that just left the zone
    old = prev_in_zone.difference(curr_in_zone)

    # adding new tracker ids to in_zone_state with current frame_index
    for tracker_id in new:
        in_zone_state[tracker_id] = frame_index

    # removing old tracker ids from in_zone_state and calculating how long it was in zone
    for tracker_id in old:
        in_zone_frame_count = frame_index - in_zone_state[tracker_id]
        in_zone_time = in_zone_frame_count * 1 / VIDEO_FPS
        del in_zone_state[tracker_id]

SkalskiP avatar Jun 12 '23 12:06 SkalskiP

One more doubt!

If I want to exclusively call out each of the in/out count values, how do I do that? like I wanted to store it in an output dictionary like this for an example output = { 'frame_index': frame_index, 'time_stamp': input_data['time_stamp'], 'detections': new_boxes, 'demo_run': input_data['demo_run'], 'ids': new_ids, 'in_zone_time': in_zone_time, 'avg_wait_time': avg_wait_time, 'l1_in': l1, 'l1_out': l1 }

below is my trigger function

l1 =line_counter_1.trigger(detections=detections)
l2 =line_counter_2.trigger(detections=detections)
l3 =line_counter_3.trigger(detections=detections)

like I need to get whatever that's getting printed on the UI as in/out for each line

uzumakinaruto19 avatar Jun 13 '23 12:06 uzumakinaruto19

@uzumakinaruto19 In this case, I recommend overriding the linezone class. You'll need to add in, and out dicts to store the value you want to take. Take a look at #87 for my issue, for taking the frame index, use VideoInfo to take fps info for taking the sec. more info in #116.

sceddd avatar Jun 13 '23 13:06 sceddd

@uzumakinaruto19 In this case, I recommend overriding the linezone class. You'll need to add in, and out dicts to store the value you want to take. Take a look at #87 for my issue, for taking the frame index, use VideoInfo to take fps info for taking the sec. more info in #116.

Done!! Thanks

uzumakinaruto19 avatar Jun 13 '23 13:06 uzumakinaruto19

Hey, hope everyone is doing good, is there a way I can reset the counters say after 20 mins? i tried the below code but not really working

def reset_counters():
    global line_counter_values, in_zone_time, frame_index, model, start_time
    print("Resetting counters...")
    model = YOLO("yolov8n.pt")
    in_zone_time = {}
    frame_index = 0
    start_time = time.time()  # Reset the start time
    return {
        "line_counter_1": {"in": 0, "out": 0},
        "line_counter_2": {"in": 0, "out": 0},
        "line_counter_3": {"in": 0, "out": 0}
    }, {}, 0



def main(input_data, frame, line_counter_1, line_counter_2, line_counter_3, zone):
    global model, frame_index, in_zone_state, in_zone_time, start_time, reset_interval
    
    line_annotator = sv.LineZoneAnnotator(thickness=2, text_thickness=1, text_scale=0.5)
    zone_annotator = sv.PolygonZoneAnnotator(zone=zone, color=sv.Color.white(), thickness=1, text_thickness=1, text_scale=0.5)

    # Get the current time
    current_time = time.time()

    # Calculate the elapsed time in seconds
    elapsed_time = current_time - start_time
    print(elapsed_time)

    # Check if the reset interval has passed
    if elapsed_time >= reset_interval:
        #print("Resetting counters...")
        reset_counters()
        print("Resetting counters...")
        # Reset the tracking IDs by setting persist=False
        persist = False
        result = model.track(frame, classes=0, imgsz=480, agnostic_nms=True, tracker="bytetrack.yaml", persist=persist)
        #print("Resetting counters...")


        # Reset the start time for the next interval
        start_time = current_time
    else:
        # Keep the tracking IDs by setting persist=True (default)       
        
        persist = True
        print("Keeping counters...")
        

        result = model.track(frame, classes=0, imgsz=480, agnostic_nms=True ,tracker="bytetrack.yaml", persist=persist)
    result = result[0]
    
 
    detections = sv.Detections.from_yolov8(result)

Thanks in advance!!!

uzumakinaruto19 avatar Jun 25 '23 09:06 uzumakinaruto19

Hi @uzumakinaruto19 👋🏻 ! If you want to reset line counter counts, all you need to do is:

# line counter
line_zone = sv.LineZone(...)

# line counter reset
counter.tracker_state = {}
counter.in_count = 0
counter.out_count = 0

SkalskiP avatar Jun 26 '23 10:06 SkalskiP

Closing the issue.

SkalskiP avatar Mar 25 '24 15:03 SkalskiP