Add bounding box selection option

This commit is contained in:
TTPlanetPig
2025-06-13 11:16:26 +08:00
parent f41dfd20ed
commit ff5dbd9025
2 changed files with 41 additions and 8 deletions
+6 -2
View File
@@ -8,12 +8,16 @@ This repository provides a custom [ComfyUI](https://github.com/comfyanonymous/Co
Downloads a chosen Qwen 2.5-VL model into `models/Qwen` and returns the loaded model and processor. You can also choose which device to load the model onto (e.g. `cuda:1` if you have multiple GPUs).
### `QwenVLDetection`
Runs a detection prompt on an input image using the loaded model. The node outputs the raw text response and the bounding box coordinates.
Runs a detection prompt on an input image using the loaded model. The node outputs the raw text response and the bounding box coordinates. You can specify which boxes to return using the **bbox_selection** parameter:
- `all` – return all boxes (default)
- `merge` – merge all boxes into a single bounding box
- Comma-separated indices such as `0`, `1,2` or `0,2` – return only the selected boxes, sorted by detection confidence
The bounding boxes are converted to absolute pixel coordinates so that they can be directly fed into the SAM2 nodes.
## Usage
1. Place this repository inside your `ComfyUI/custom_nodes` directory.
2. From the **Download and Load Qwen2.5-VL Model** node, select the model you want to use and, if necessary, choose the device (such as `cuda:1`) where it should be loaded. The snapshot download will resume automatically if a previous attempt was interrupted.
3. Connect the output model to **Qwen2.5-VL Object Detection**, provide an image and the object you want to locate (e.g. `cat`). The node will automatically build the detection prompt and return the bounding boxes.
3. Connect the output model to **Qwen2.5-VL Object Detection**, provide an image and the object you want to locate (e.g. `cat`). Optionally set **bbox_selection** to `merge` or specific indices like `0,2` if you only want certain boxes. The node will automatically build the detection prompt and return the selected bounding boxes.
4. Feed the resulting bounding boxes into the SAM2 workflow for further processing.
+35 -6
View File
@@ -2,7 +2,7 @@ import os
import ast
import json
from dataclasses import dataclass
from typing import List, Dict, Any
from typing import List, Dict, Any, Tuple
import torch
from PIL import Image
@@ -22,7 +22,13 @@ def parse_json(json_output: str) -> str:
return json_output
def parse_boxes(text: str, img_width: int, img_height: int, input_w: int, input_h: int) -> List[List[int]]:
def parse_boxes(
text: str,
img_width: int,
img_height: int,
input_w: int,
input_h: int,
) -> List[List[int]]:
text = parse_json(text)
try:
data = ast.literal_eval(text)
@@ -30,9 +36,10 @@ def parse_boxes(text: str, img_width: int, img_height: int, input_w: int, input_
end_idx = text.rfind('"}') + len('"}')
truncated = text[:end_idx] + "]"
data = ast.literal_eval(truncated)
boxes = []
items: List[Tuple[float, List[int]]] = []
for item in data:
box = item.get("bbox_2d") or item.get("bbox") or item
score = float(item.get("score", 0))
y1, x1, y2, x2 = box[1], box[0], box[3], box[2]
abs_y1 = int(y1 / input_h * img_height)
abs_x1 = int(x1 / input_w * img_width)
@@ -42,8 +49,9 @@ def parse_boxes(text: str, img_width: int, img_height: int, input_w: int, input_
abs_x1, abs_x2 = abs_x2, abs_x1
if abs_y1 > abs_y2:
abs_y1, abs_y2 = abs_y2, abs_y1
boxes.append([abs_x1, abs_y1, abs_x2, abs_y2])
return boxes
items.append((score, [abs_x1, abs_y1, abs_x2, abs_y2]))
items.sort(key=lambda x: x[0], reverse=True)
return [box for _, box in items]
@dataclass
@@ -130,6 +138,7 @@ class QwenVLDetection:
"qwen_model": ("QWEN_MODEL",),
"image": ("IMAGE",),
"target": ("STRING", {"default": "object"}),
"bbox_selection": ("STRING", {"default": "all"}),
},
}
@@ -138,7 +147,7 @@ class QwenVLDetection:
FUNCTION = "detect"
CATEGORY = "Qwen2.5-VL"
def detect(self, qwen_model: QwenModel, image, target: str):
def detect(self, qwen_model: QwenModel, image, target: str, bbox_selection: str = "all"):
model = qwen_model.model
processor = qwen_model.processor
device = next(model.parameters()).device
@@ -168,6 +177,26 @@ class QwenVLDetection:
input_h = inputs['image_grid_thw'][0][1] * 14
input_w = inputs['image_grid_thw'][0][2] * 14
boxes = parse_boxes(output_text, image.width, image.height, input_w, input_h)
selection = bbox_selection.strip().lower()
if selection == "merge":
if boxes:
x1 = min(b[0] for b in boxes)
y1 = min(b[1] for b in boxes)
x2 = max(b[2] for b in boxes)
y2 = max(b[3] for b in boxes)
boxes = [[x1, y1, x2, y2]]
else:
boxes = []
elif selection != "all":
idxs = []
for part in selection.replace(",", " ").split():
try:
idxs.append(int(part))
except Exception:
continue
boxes = [boxes[i] for i in idxs if 0 <= i < len(boxes)]
return (output_text, boxes)