object-detection / utils.py
A-kshar's picture
Upload 3 files
a6ba93e verified
Raw History Blame Contribute Delete
8.18 kB
import cv2
import numpy as np
class_names = [
"person",
"bicycle",
"car",
"motorcycle",
"airplane",
"bus",
"train",
"truck",
"boat",
"traffic light",
"fire hydrant",
"stop sign",
"parking meter",
"bench",
"bird",
"cat",
"dog",
"horse",
"sheep",
"cow",
"elephant",
"bear",
"zebra",
"giraffe",
"backpack",
"umbrella",
"handbag",
"tie",
"suitcase",
"frisbee",
"skis",
"snowboard",
"sports ball",
"kite",
"baseball bat",
"baseball glove",
"skateboard",
"surfboard",
"tennis racket",
"bottle",
"wine glass",
"cup",
"fork",
"knife",
"spoon",
"bowl",
"banana",
"apple",
"sandwich",
"orange",
"broccoli",
"carrot",
"hot dog",
"pizza",
"donut",
"cake",
"chair",
"couch",
"potted plant",
"bed",
"dining table",
"toilet",
"tv",
"laptop",
"mouse",
"remote",
"keyboard",
"cell phone",
"microwave",
"oven",
"toaster",
"sink",
"refrigerator",
"book",
"clock",
"vase",
"scissors",
"teddy bear",
"hair drier",
"toothbrush",
]
# Create a list of colors for each class where each color is a tuple of 3 integer values
rng = np.random.default_rng(3)
colors = rng.uniform(0, 255, size=(len(class_names), 3))
def nms(boxes, scores, iou_threshold):
# Sort by score
sorted_indices = np.argsort(scores)[::-1]
keep_boxes = []
while sorted_indices.size > 0:
# Pick the last box
box_id = sorted_indices[0]
keep_boxes.append(box_id)
# Compute IoU of the picked box with the rest
ious = compute_iou(boxes[box_id, :], boxes[sorted_indices[1:], :])
# Remove boxes with IoU over the threshold
keep_indices = np.where(ious < iou_threshold)[0]
# print(keep_indices.shape, sorted_indices.shape)
sorted_indices = sorted_indices[keep_indices + 1]
return keep_boxes
def multiclass_nms(boxes, scores, class_ids, iou_threshold):
unique_class_ids = np.unique(class_ids)
keep_boxes = []
for class_id in unique_class_ids:
class_indices = np.where(class_ids == class_id)[0]
class_boxes = boxes[class_indices, :]
class_scores = scores[class_indices]
class_keep_boxes = nms(class_boxes, class_scores, iou_threshold)
keep_boxes.extend(class_indices[class_keep_boxes])
return keep_boxes
def compute_iou(box, boxes):
# Compute xmin, ymin, xmax, ymax for both boxes
xmin = np.maximum(box[0], boxes[:, 0])
ymin = np.maximum(box[1], boxes[:, 1])
xmax = np.minimum(box[2], boxes[:, 2])
ymax = np.minimum(box[3], boxes[:, 3])
# Compute intersection area
intersection_area = np.maximum(0, xmax - xmin) * np.maximum(0, ymax - ymin)
# Compute union area
box_area = (box[2] - box[0]) * (box[3] - box[1])
boxes_area = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
union_area = box_area + boxes_area - intersection_area
# Compute IoU
iou = intersection_area / union_area
return iou
def xywh2xyxy(x):
# Convert bounding box (x, y, w, h) to bounding box (x1, y1, x2, y2)
y = np.copy(x)
y[..., 0] = x[..., 0] - x[..., 2] / 2
y[..., 1] = x[..., 1] - x[..., 3] / 2
y[..., 2] = x[..., 0] + x[..., 2] / 2
y[..., 3] = x[..., 1] + x[..., 3] / 2
return y
def draw_seg_masks(image, masks, class_ids, mask_alpha=0.3):
"""Overlay pixel-level segmentation masks on *image* using per-class colours.
Parameters
----------
image : np.ndarray BGR frame (H, W, 3)
masks : np.ndarray Float mask array of shape (N, Hm, Wm). Each slice is
a soft mask (0-1) at the model's internal resolution.
class_ids : array-like Integer class index for each of the N masks.
mask_alpha : float Blend weight for the mask layer (0 = invisible, 1 = opaque).
"""
if masks is None or len(masks) == 0:
return image
img_h, img_w = image.shape[:2]
mask_layer = image.copy()
for mask, class_id in zip(masks, class_ids):
color = colors[int(class_id) % len(colors)]
# Resize soft mask to frame resolution and threshold to binary.
mask_resized = cv2.resize(mask, (img_w, img_h), interpolation=cv2.INTER_LINEAR)
binary = (mask_resized > 0.5).astype(np.uint8)
# Paint the masked pixels with the class colour.
for ch, c_val in enumerate(color):
mask_layer[:, :, ch] = np.where(binary, c_val, mask_layer[:, :, ch])
return cv2.addWeighted(mask_layer, mask_alpha, image, 1 - mask_alpha, 0)
def draw_detections(image, boxes, scores, class_ids, masks=None, mask_alpha=0.3, class_names_list=None):
"""Draw segmentation masks (if any), bounding boxes, and labels on *image*.
Parameters
----------
image : np.ndarray BGR frame.
boxes : np.ndarray Bounding boxes in xyxy format, shape (N, 4).
scores : np.ndarray Confidence scores, shape (N,).
class_ids : np.ndarray Integer class indices, shape (N,).
masks : np.ndarray | None Soft masks (N, Hm, Wm). When provided the
masks are blended onto the frame before boxes/labels are drawn.
mask_alpha: float Opacity of the mask layer.
class_names_list: list[str] | None List of active class names corresponding to class_ids.
"""
if class_names_list is None:
class_names_list = class_names
det_img = image.copy()
img_height, img_width = image.shape[:2]
font_size = min([img_height, img_width]) * 0.0006
text_thickness = int(min([img_height, img_width]) * 0.001)
# 1. Draw segmentation masks first so boxes/labels render on top.
if masks is not None and len(masks) > 0:
det_img = draw_seg_masks(det_img, masks, class_ids, mask_alpha)
# 2. Draw bounding boxes and labels of detections.
for class_id, box, score in zip(class_ids, boxes, scores):
color = colors[int(class_id) % len(colors)]
draw_box(det_img, box, color) # type: ignore
label = class_names_list[int(class_id)] if int(class_id) < len(class_names_list) else str(class_id)
caption = f"{label} {int(score * 100)}%"
draw_text(det_img, caption, box, color, font_size, text_thickness) # type: ignore
return det_img
def draw_box(
image: np.ndarray,
box: np.ndarray,
color: tuple[int, int, int] = (0, 0, 255),
thickness: int = 2,
) -> np.ndarray:
x1, y1, x2, y2 = box.astype(int)
return cv2.rectangle(image, (x1, y1), (x2, y2), color, thickness)
def draw_text(
image: np.ndarray,
text: str,
box: np.ndarray,
color: tuple[int, int, int] = (0, 0, 255),
font_size: float = 0.001,
text_thickness: int = 2,
) -> np.ndarray:
x1, y1, x2, y2 = box.astype(int)
(tw, th), _ = cv2.getTextSize(
text=text,
fontFace=cv2.FONT_HERSHEY_SIMPLEX,
fontScale=font_size,
thickness=text_thickness,
)
th = int(th * 1.2)
cv2.rectangle(image, (x1, y1), (x1 + tw, y1 - th), color, -1)
return cv2.putText(
image,
text,
(x1, y1),
cv2.FONT_HERSHEY_SIMPLEX,
font_size,
(255, 255, 255),
text_thickness,
cv2.LINE_AA,
)
def draw_masks(
image: np.ndarray, boxes: np.ndarray, classes: np.ndarray, mask_alpha: float = 0.3
) -> np.ndarray:
mask_img = image.copy()
# Draw bounding boxes and labels of detections
for box, class_id in zip(boxes, classes):
color = colors[class_id]
x1, y1, x2, y2 = box.astype(int)
# Draw fill rectangle in mask image
cv2.rectangle(mask_img, (x1, y1), (x2, y2), color, -1) # type: ignore
return cv2.addWeighted(mask_img, mask_alpha, image, 1 - mask_alpha, 0)