Files
2024-06-05 14:47:41 +08:00

231 lines
7.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import sys
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
import folder_paths
import json
import cv2
import logging
import numpy as np
import torch
from PIL import Image, ImageDraw, ImageFont
from urllib.parse import urlparse
from torch.hub import download_url_to_file
from omdet.infernece.det_engine import DetEngine
from omdet.utils.plots import Annotator
logger = logging.getLogger("ComfyUI-OmDet")
OmDet_model_list = {
"OmDet-Turbo_tiny_SWIN_T.pth": {
"model_url": "https://huggingface.co/omlab/OmDet-Turbo_tiny_SWIN_T/resolve/main/OmDet-Turbo_tiny_SWIN_T.pth?download=true",
},
"ViT-B-16.pt": {
"model_url": "https://huggingface.co/omlab/OmDet-Turbo_tiny_SWIN_T/resolve/main/ViT-B-16.pt?download=true",
},
}
def download_model(model_url, model_file_path):
try:
torch.hub.download_url_to_file(model_url, model_file_path)
except Exception as e:
print(f"Failed to download model from {model_url}. Error: {e}")
def check_and_download_models():
model_path = os.path.join(folder_paths.models_dir, "OmDet")
if not os.path.exists(model_path):
os.makedirs(model_path)
for model_name, model_info in OmDet_model_list.items():
model_filename = model_name
model_file_path = os.path.join(model_path, model_filename)
if not os.path.exists(model_file_path):
print(f"{model_filename} not found. Downloading from {model_info['model_url']}...")
download_model(model_info["model_url"], model_file_path)
# modified from https://stackoverflow.com/questions/22058048/hashing-a-file-in-python
def calculate_file_hash(filename: str, hash_every_n: int = 1):
# Larger video files were taking >.5 seconds to hash even when cached,
# so instead the modified time from the filesystem is used as a hash
h = hashlib.sha256()
h.update(filename.encode())
h.update(str(os.path.getmtime(filename)).encode())
return h.hexdigest()
def plot_boxes_to_image(image_pil, tgt):
H, W = tgt["size"]
result = tgt["result"]
res_mask = []
res_image = []
box_color = (255, 0, 0) # Red color for the box
text_color = (255, 255, 255) # White color for the text
draw = ImageDraw.Draw(image_pil)
# Get the current file path and use it to create a relative path to the font file
current_file_path = os.path.dirname(os.path.abspath(__file__))
font_path = os.path.join(current_file_path, "docs", "PingFangRegular.ttf")
font_size = 20
font = ImageFont.truetype(font_path, font_size)
labelme_data = {
"version": "4.5.6",
"flags": {},
"shapes": [],
"imagePath": None,
"imageData": None,
"imageHeight": H,
"imageWidth": W,
}
for item in result:
x1,y1,x2,y2 = item['xmin'],item['ymin'],item['xmax'],item['ymax']
label = item["label"]
threshold = round(item["conf"], 2)
x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
points = [[x1, y1], [x2, y2]]
# Save labelme json
shape = {
"label": label,
"points": points,
"group_id": None,
"shape_type": "rectangle",
"flags": {},
}
labelme_data["shapes"].append(shape)
# Change label
label = label + ":" + str(threshold)
shape["threshold"] = str(threshold)
# Draw rectangle on the image using PIL
draw.rectangle([(x1, y1), (x2, y2)], outline=box_color, width=3)
# Draw label on the image using PIL
text_size = draw.textsize(label, font=font)
label_ymin = max(y1, text_size[1] + 10)
draw.rectangle([(x1, y1 - text_size[1] - 10), (x1 + text_size[0], y1)], fill=box_color)
draw.text((x1, y1 - text_size[1] - 10), label, font=font, fill=text_color)
# Draw mask
mask = np.zeros((H, W, 1), dtype=np.uint8)
cv2.rectangle(mask, (int(x1), int(y1)), (int(x2), int(y2)), (255, 255, 255), -1)
mask_tensor = torch.from_numpy(mask).permute(2, 0, 1).float() / 255.0
res_mask.append(mask_tensor)
if len(res_mask) == 0:
mask = np.zeros((H, W, 1), dtype=np.uint8)
mask_tensor = torch.from_numpy(mask).permute(2, 0, 1).float() / 255.0
res_mask.append(mask_tensor)
# Convert the PIL image back to a numpy array
image_with_boxes = np.array(image_pil)
# Convert the modified image to a torch tensor
image_with_boxes_tensor = torch.from_numpy(image_with_boxes.astype(np.float32) / 255.0)
image_with_boxes_tensor = torch.unsqueeze(image_with_boxes_tensor, 0)
res_image.append(image_with_boxes_tensor)
return res_image, res_mask, labelme_data
def OmDet_detect(data, labels, conf_threshold=0.30, nms_threshold=0.5,device="cuda"):
prompt = "Detect {}.".format(",".join(labels))
engine = DetEngine(batch_size=1, device=device)
res = engine.inf_predict(
"OmDet-Turbo_tiny_SWIN_T",
task=prompt,
data=data,
labels=labels,
src_type="pil", # type of the image_paths, "local/tensor/pil/"url"
conf_threshold=conf_threshold,
nms_threshold=nms_threshold,
)
return res
def get_labels(input_string):
modified_string = input_string.replace(", ", ",").replace(",", ",")
return modified_string.split(",")
class ApplyOmDet:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"prompt": (
"STRING",
{"default": "person,cat,dog"},
),
"conf_threshold": (
"FLOAT",
{"default": 0.3, "min": 0, "max": 1.0, "step": 0.01},
),
"nms_threshold": (
"FLOAT",
{"default": 0.5, "min": 0, "max": 1.0, "step": 0.01},
),
"device": (["cuda", "cpu"],),
},
}
CATEGORY = "ComfyUI-OmDet"
FUNCTION = "main"
RETURN_TYPES = (
"IMAGE",
"MASK",
"JSON",
)
def main(
self,
image,
prompt,
conf_threshold,
nms_threshold,
device
):
check_and_download_models()
data = []
labels = get_labels(prompt)
for item in image:
image_pil = Image.fromarray(np.clip(255.0 * item.cpu().numpy(), 0, 255).astype(np.uint8)).convert("RGB")
data.append(image_pil)
results = OmDet_detect(data, labels, conf_threshold, nms_threshold, device)
res_images = []
res_masks = []
res_labels = []
for image_pil, result in zip(data, results):
size = image_pil.size
pred_dict = {
"size": [size[1], size[0]],
"result":result
}
image_tensor, mask_tensor, labelme_data = plot_boxes_to_image(image_pil, pred_dict)
res_images.extend(image_tensor)
res_masks.extend(mask_tensor)
res_labels.append(labelme_data)
if len(res_images) == 0:
res_images.extend(item)
if len(res_masks) == 0:
mask = np.zeros((height, width, 1), dtype=np.uint8)
empty_mask = torch.from_numpy(mask).permute(2, 0, 1).float() / 255.0
res_masks.extend(empty_mask)
return (
torch.cat(res_images, dim=0),
torch.cat(res_masks, dim=0),
res_labels,
)