SoM / utils.py
SkalskiP's picture
Deleted 'gpt4v.py' and moved its functionalities to 'utils.py' and 'app.py'.
017684f
import cv2
import som
import numpy as np
import supervision as sv
class Visualizer:
def __init__(
self,
line_thickness: int = 2,
mask_opacity: float = 0.1,
text_scale: float = 0.6
) -> None:
self.box_annotator = sv.BoundingBoxAnnotator(
color_lookup=sv.ColorLookup.INDEX,
thickness=line_thickness)
self.mask_annotator = sv.MaskAnnotator(
color_lookup=sv.ColorLookup.INDEX,
opacity=mask_opacity)
self.polygon_annotator = sv.PolygonAnnotator(
color_lookup=sv.ColorLookup.INDEX,
thickness=line_thickness)
self.label_annotator = sv.LabelAnnotator(
color=sv.Color.black(),
text_color=sv.Color.white(),
color_lookup=sv.ColorLookup.INDEX,
text_position=sv.Position.CENTER_OF_MASS,
text_scale=text_scale)
def visualize(
self,
image: np.ndarray,
detections: sv.Detections,
with_box: bool,
with_mask: bool,
with_polygon: bool,
with_label: bool
) -> np.ndarray:
annotated_image = image.copy()
if with_box:
annotated_image = self.box_annotator.annotate(
scene=annotated_image, detections=detections)
if with_mask:
annotated_image = self.mask_annotator.annotate(
scene=annotated_image, detections=detections)
if with_polygon:
annotated_image = self.polygon_annotator.annotate(
scene=annotated_image, detections=detections)
if with_label:
labels = list(map(str, range(len(detections))))
annotated_image = self.label_annotator.annotate(
scene=annotated_image, detections=detections, labels=labels)
return annotated_image
def refine_mask(
mask: np.ndarray,
area_threshold: float,
mode: str = 'islands'
) -> np.ndarray:
"""
Refines a mask by removing small islands or filling small holes based on area
threshold.
Parameters:
mask (np.ndarray): Input binary mask.
area_threshold (float): Threshold for relative area to remove or fill features.
mode (str): Operation mode ('islands' for removing islands, 'holes' for filling
holes).
Returns:
np.ndarray: Refined binary mask.
"""
mask = np.uint8(mask * 255)
operation = cv2.RETR_EXTERNAL if mode == 'islands' else cv2.RETR_CCOMP
contours, _ = cv2.findContours(
mask, operation, cv2.CHAIN_APPROX_SIMPLE
)
total_area = cv2.countNonZero(mask) if mode == 'islands' else mask.size
for contour in contours:
area = cv2.contourArea(contour)
relative_area = area / total_area
if relative_area < area_threshold:
cv2.drawContours(
image=mask,
contours=[contour],
contourIdx=-1,
color=(0 if mode == 'islands' else 255),
thickness=-1
)
return np.where(mask > 0, 1, 0).astype(bool)
def filter_masks_by_relative_area(
masks: np.ndarray,
min_relative_area: float = 0.02,
max_relative_area: float = 1.0
) -> np.ndarray:
"""
Filters out masks based on their relative area.
Parameters:
masks (np.ndarray): A 3D numpy array where each slice along the third dimension
represents a mask.
min_relative_area (float): Minimum relative area threshold for keeping a mask.
max_relative_area (float): Maximum relative area threshold for keeping a mask.
Returns:
np.ndarray: A 3D numpy array of filtered masks.
"""
mask_areas = masks.sum(axis=(1, 2))
total_area = masks.shape[1] * masks.shape[2]
relative_areas = mask_areas / total_area
min_area_filter = relative_areas >= min_relative_area
max_area_filter = relative_areas <= max_relative_area
return masks[min_area_filter & max_area_filter]
def postprocess_masks(
detections: sv.Detections,
area_threshold: float = 0.01,
min_relative_area: float = 0.01,
max_relative_area: float = 1.0,
iou_threshold: float = 0.9
) -> sv.Detections:
"""
Post-processes the masks of detection objects by removing small islands and filling
small holes.
Parameters:
detections (sv.Detections): Detection objects to be filtered.
area_threshold (float): Threshold for relative area to remove or fill features.
min_relative_area (float): Minimum relative area threshold for detections.
max_relative_area (float): Maximum relative area threshold for detections.
iou_threshold (float): The IoU threshold above which masks will be considered as
overlapping.
Returns:
np.ndarray: Post-processed masks.
"""
masks = detections.mask.copy()
for i in range(len(masks)):
masks[i] = refine_mask(
mask=masks[i],
area_threshold=area_threshold,
mode='islands'
)
masks[i] = refine_mask(
mask=masks[i],
area_threshold=area_threshold,
mode='holes'
)
masks = filter_masks_by_relative_area(
masks=masks,
min_relative_area=min_relative_area,
max_relative_area=max_relative_area)
masks = som.mask_non_max_suppression(
masks=masks,
iou_threshold=iou_threshold)
return sv.Detections(
xyxy=sv.mask_to_xyxy(masks),
mask=masks
)