Files
Geo1230 ca8e48fb7a Initial commit
ComfyUI-DeepseekOCR plugin v1.0.0
2025-11-10 15:18:14 +08:00

127 lines
4.3 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.
"""
ComfyUI 图像张量与 PIL 互转、输出目录生成等小工具。
尽量保持“纯工具”,方便单测。
"""
from pathlib import Path
from datetime import datetime
from typing import Tuple
import numpy as np
from PIL import Image
import torch
def tensor_to_pil(image_tensor: torch.Tensor) -> Image.Image:
"""
输入:ComfyUI IMAGE(float32,0~1,可能是 [B,H,W,C] 或 [H,W,C])
输出:PIL.Image(RGB)
"""
# 处理批次维度:取第一张
if image_tensor.ndim == 4:
image_tensor = image_tensor[0]
# 转换为 numpy: [H,W,C]
img_np = image_tensor.cpu().numpy()
# 从 [0,1] float32 转换为 [0,255] uint8
img_np = (img_np * 255).clip(0, 255).astype(np.uint8)
# 转换为 PIL
return Image.fromarray(img_np, mode='RGB')
def pil_to_tensor(img: Image.Image) -> torch.Tensor:
"""
可选:将 PIL 转回 Comfy 规范张量(float32, 0~1, [H,W,C])
"""
# 转换为 RGB(防止 RGBA 等)
img = img.convert('RGB')
# 转换为 numpy: [H,W,C]
img_np = np.array(img, dtype=np.float32)
# 从 [0,255] 转换为 [0,1]
img_np = img_np / 255.0
# 转换为 tensor
return torch.from_numpy(img_np)
def make_output_dir(root: Path) -> Path:
"""
在 root 下创建时间戳目录,返回路径。
示例:ComfyUI/output/DeepSeekOCR/2025-11-05_20-31-00/
"""
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
output_dir = root / timestamp
output_dir.mkdir(parents=True, exist_ok=True)
return output_dir
def redraw_boxes_on_image(image: Image.Image, text_output: str, color=(255, 0, 0), width=2) -> Image.Image:
"""
在图像上重新绘制检测框(自定义颜色和宽度)
Args:
image: PIL Image
text_output: DeepSeek-OCR 的文本输出,包含 <|ref|>...<|det|>[[x1,y1,x2,y2]]<|/det|> 标记
color: 边框颜色 RGB,默认红色 (255, 0, 0)
width: 边框宽度,默认 2px
Returns:
带标注框的 PIL Image
"""
import re
from PIL import ImageDraw, ImageFont
# 复制图像
img_with_boxes = image.copy()
draw = ImageDraw.Draw(img_with_boxes)
# 解析文本中的检测框坐标
# 格式:<|ref|>标签<|/ref|><|det|>[[x1,y1,x2,y2], ...]<|/det|>
pattern = r'<\|ref\|>(.*?)<\|/ref\|><\|det\|>(.*?)<\|/det\|>'
matches = re.findall(pattern, text_output, re.DOTALL)
image_width, image_height = image.size
for label, coords_str in matches:
try:
# 解析坐标
coords_list = eval(coords_str) # [[x1,y1,x2,y2], ...]
for coords in coords_list:
x1, y1, x2, y2 = coords
# 坐标归一化:DeepSeek-OCR 使用 0-999 范围
x1 = int(x1 / 999 * image_width)
y1 = int(y1 / 999 * image_height)
x2 = int(x2 / 999 * image_width)
y2 = int(y2 / 999 * image_height)
# 绘制边框
draw.rectangle([x1, y1, x2, y2], outline=color, width=width)
# 绘制标签(可选)
if label.strip():
try:
font = ImageFont.load_default()
text_bbox = draw.textbbox((0, 0), label, font=font)
text_width = text_bbox[2] - text_bbox[0]
text_height = text_bbox[3] - text_bbox[1]
text_x = x1
text_y = max(0, y1 - text_height - 2)
# 白色背景
draw.rectangle(
[text_x, text_y, text_x + text_width + 4, text_y + text_height + 2],
fill=(255, 255, 255)
)
# 文字
draw.text((text_x + 2, text_y), label, font=font, fill=color)
except:
pass
except Exception as e:
print(f"[DeepseekOCR] 绘制边框失败: {e}")
continue
return img_with_boxes