Files
Eduardo Ottone ee92581211 Initial release - YOLOv8 object detection node for ComfyUI
- Implements YOLOv8-based object detection with batch output
- Supports multiple YOLOv8 model sizes (nano to extra-large)
- Configurable confidence threshold, IoU threshold, and padding
- Outputs separate images for each detected object
- Includes installation scripts for Windows, Linux, and Mac
- Complete documentation and example workflow
- MIT license for open source distribution
2025-08-22 12:04:30 -07:00

163 lines
5.2 KiB
Python

#!/usr/bin/env python3
"""
Quick segmentation script for extracting isolated objects
Simple interface for production use
"""
import os
import sys
from pathlib import Path
import cv2
import numpy as np
from PIL import Image
import torch
from ultralytics import YOLO
from typing import List, Optional
def quick_segment(
image_path: str,
output_dir: str = 'quick_output',
model_name: str = 'yolov8m-seg.pt',
conf_threshold: float = 0.25,
save_transparent: bool = True,
save_masks: bool = True
) -> int:
"""
Quick segmentation of an image
Args:
image_path: Path to input image
output_dir: Output directory
model_name: YOLO model to use
conf_threshold: Confidence threshold
save_transparent: Save objects with transparent background
save_masks: Save binary masks
Returns:
Number of objects detected
"""
# Auto-detect device
if torch.cuda.is_available():
device = 'cuda'
elif torch.backends.mps.is_available():
device = 'mps'
else:
device = 'cpu'
print(f"Using {device.upper()}")
# Load model
print("Loading model...")
model = YOLO(model_name)
model.to(device)
# Read image
img = cv2.imread(image_path)
if img is None:
print(f"Error: Could not read {image_path}")
return 0
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
height, width = img.shape[:2]
# Run inference
print(f"Processing {Path(image_path).name}...")
results = model(img_rgb, conf=conf_threshold)
if len(results) == 0 or results[0].masks is None:
print("No objects detected")
return 0
# Create output directory
output_path = Path(output_dir)
output_path.mkdir(exist_ok=True)
base_name = Path(image_path).stem
# Process detections
result = results[0]
masks = result.masks.data.cpu().numpy()
boxes = result.boxes.xyxy.cpu().numpy()
classes = result.boxes.cls.cpu().numpy().astype(int)
confidences = result.boxes.conf.cpu().numpy()
n_objects = len(masks)
print(f"Found {n_objects} object(s)")
for i in range(n_objects):
# Get class name
class_name = model.names[classes[i]].replace(' ', '_')
conf = confidences[i]
# Get mask
mask = masks[i]
mask_resized = cv2.resize(mask, (width, height), interpolation=cv2.INTER_NEAREST)
mask_binary = (mask_resized > 0.5).astype(np.uint8)
# Get bounding box
x1, y1, x2, y2 = boxes[i].astype(int)
# Save mask if requested
if save_masks:
mask_path = output_path / f"{base_name}_{i:02d}_{class_name}_mask.png"
cv2.imwrite(str(mask_path), mask_binary * 255)
# Extract cropped object
cropped_img = img_rgb[y1:y2, x1:x2]
cropped_mask = mask_binary[y1:y2, x1:x2]
# Save with transparent background if requested
if save_transparent:
cropped_rgba = np.zeros((*cropped_img.shape[:2], 4), dtype=np.uint8)
cropped_rgba[:, :, :3] = cropped_img
cropped_rgba[:, :, 3] = cropped_mask * 255
alpha_path = output_path / f"{base_name}_{i:02d}_{class_name}.png"
Image.fromarray(cropped_rgba).save(str(alpha_path))
print(f" [{i:2d}] {class_name} ({conf:.1%}) -> {alpha_path.name}")
else:
# Save with black background
isolated = cropped_img.copy()
isolated[cropped_mask == 0] = 0
isolated_path = output_path / f"{base_name}_{i:02d}_{class_name}.jpg"
cv2.imwrite(str(isolated_path), cv2.cvtColor(isolated, cv2.COLOR_RGB2BGR))
print(f" [{i:2d}] {class_name} ({conf:.1%}) -> {isolated_path.name}")
return n_objects
def main():
"""Main function for command-line usage"""
import argparse
parser = argparse.ArgumentParser(description='Quick object segmentation')
parser.add_argument('images', nargs='+', help='Image file(s) to process')
parser.add_argument('-o', '--output', default='quick_output', help='Output directory')
parser.add_argument('-m', '--model', default='yolov8m-seg.pt', help='Model to use')
parser.add_argument('-c', '--conf', type=float, default=0.25, help='Confidence threshold')
parser.add_argument('--no-transparent', action='store_true', help='Save without transparency')
parser.add_argument('--no-masks', action='store_true', help='Skip saving masks')
args = parser.parse_args()
# Process each image
total_objects = 0
for image_path in args.images:
if Path(image_path).is_file():
n = quick_segment(
image_path,
args.output,
args.model,
args.conf,
save_transparent=not args.no_transparent,
save_masks=not args.no_masks
)
total_objects += n
print()
print(f"Total: {total_objects} objects extracted from {len(args.images)} image(s)")
print(f"Output saved to: {args.output}/")
if __name__ == '__main__':
main()