florence-forge-webui / florence_forge /deployment /inference_visualization.py
isLinXu
Pack FlorenceForge source for embedded HF Spaces deployment
e40db0e
Raw
History Blame Contribute Delete
11.3 kB
"""推理结果可视化(从 ``inference.py`` 抽出)。"""
from __future__ import annotations
import logging
from pathlib import Path
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
if TYPE_CHECKING: # pragma: no cover - typing-only import
import PIL
from florence_forge.utils.plot_backend import finalize_matplotlib_figure
from .inference_parsing import clean_text_prefix
logger = logging.getLogger(__name__)
def visualize_detections(self, image, detections: List[Dict[str, Any]], save_path: Optional[str] = None):
"""Visualize detection results on the image.
Args:
image: The original image (PIL Image).
detections: A list of detection results.
save_path: The path to save the visualization (optional).
"""
try:
from PIL import Image
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np
except ImportError as e:
logger.error(f"Visualization dependencies are not installed: {e}")
return
# Ensure it's a PIL Image
if not isinstance(image, Image.Image):
if isinstance(image, np.ndarray):
image = Image.fromarray(image)
else:
logger.error("Unsupported image format for visualization.")
return
# Create a matplotlib figure
fig, ax = plt.subplots(1, figsize=(12, 8))
ax.imshow(image)
# Define a list of colors for bounding boxes
colors = ['red', 'blue', 'green', 'yellow', 'purple', 'orange', 'pink', 'brown']
# Draw detection boxes and labels
for i, detection in enumerate(detections):
bbox = detection['bbox']
label = detection['label']
confidence = detection.get('confidence', 1.0)
# Select a color
color = colors[i % len(colors)]
# Create a rectangle patch
x1, y1, x2, y2 = bbox
width = x2 - x1
height = y2 - y1
rect = patches.Rectangle(
(x1, y1), width, height,
linewidth=2, edgecolor=color, facecolor='none'
)
ax.add_patch(rect)
# Add the label
label_text = f"{label} ({confidence:.2f})" if confidence < 1.0 else label
ax.text(
x1, y1 - 5, label_text,
bbox=dict(boxstyle="round,pad=0.3", facecolor=color, alpha=0.7),
fontsize=10, color='white', weight='bold'
)
ax.set_xlim(0, image.width)
ax.set_ylim(image.height, 0)
ax.axis('off')
plt.title(f'Detection Results - Found {len(detections)} objects')
if save_path:
plt.savefig(save_path, bbox_inches='tight', dpi=150)
logger.info(f"Visualization result saved to: {save_path}")
finalize_matplotlib_figure()
def visualize_bboxes(self, image, bboxes, save_path):
from PIL import ImageDraw
if not save_path:
logger.warning("未提供保存路径,无法可视化边界框")
return
draw = ImageDraw.Draw(image)
for bbox in bboxes:
draw.rectangle(bbox, outline="red", width=2)
try:
image.save(save_path)
logger.info(f"边界框可视化结果已保存至: {save_path}")
except Exception as e:
logger.error(f"保存边界框可视化结果失败: {e}")
def visualize_ocr_with_region(self, image, ocr_results: List[Dict[str, Any]], save_path: Optional[str]):
from PIL import ImageDraw, ImageFont
if not save_path:
logger.warning("未提供保存路径,无法可视化OCR结果")
return
draw = ImageDraw.Draw(image, 'RGBA')
# Font selection for Chinese characters
font_size = 15
try:
# Construct path to the font file relative to this file
current_dir = Path(__file__).parent
font_path = current_dir.parent.parent / 'assets' / 'fonts' / 'SourceHanSansSC-Regular.ttf'
font = ImageFont.truetype(str(font_path), font_size)
except IOError:
logger.warning(f"未能加载思源黑体字体,将回退到默认字体。请检查路径:{font_path}")
try:
# Common fonts for Chinese on different OS
font = ImageFont.truetype("SimHei.ttf", font_size) # Windows/Linux
except IOError:
try:
font = ImageFont.truetype("/System/Library/Fonts/STHeitiLight.ttc", font_size) # macOS
except IOError:
try:
font = ImageFont.truetype("arial.ttf", font_size)
except IOError:
font = ImageFont.load_default()
for result in ocr_results:
polygon = result['polygon']
text = result['text']
# Draw polygon with semi-transparent fill
draw.polygon(polygon, outline='lime', fill=(0, 255, 0, 60))
# Position text at the top-left corner of the polygon
text_position = polygon[0]
draw.text(text_position, text, fill='red', font=font)
try:
image.convert('RGB').save(save_path)
logger.info(f"OCR可视化结果已保存至: {save_path}")
except Exception as e:
logger.error(f"保存OCR可视化结果失败: {e}")
def visualize_caption(self, image, caption: str, save_path: Optional[str] = None):
"""Visualize caption on the image.
Args:
image: The original image (PIL Image).
caption: The caption text.
save_path: The path to save the visualization (optional).
"""
try:
from PIL import Image, ImageDraw, ImageFont
import numpy as np
import textwrap
except ImportError as e:
logger.error(f"Visualization dependencies are not installed: {e}")
return
if not isinstance(image, Image.Image):
if isinstance(image, np.ndarray):
image = Image.fromarray(image)
else:
logger.error("Unsupported image format for visualization.")
return
# Clean caption
caption = caption.replace('</s>', '').replace('<s>', '').strip()
caption = clean_text_prefix(caption)
# Create a drawing context, use RGBA for transparency
draw = ImageDraw.Draw(image, 'RGBA')
# Dynamically adjust font size based on image height
font_size = max(36, image.height // 25) # Adjust font size relative to image height, with a minimum
try:
font = ImageFont.truetype("arial.ttf", font_size)
except IOError:
# Fallback to a default font, try to get a font with a size
try:
font = ImageFont.load_default(size=font_size)
except AttributeError:
# Older PIL versions might not support size in load_default
font = ImageFont.load_default()
image_width, image_height = image.size
# Wrap text if it's too long
avg_char_width = font.getlength('a')
wrap_width = int(image_width / avg_char_width * 1.5) if avg_char_width > 0 else 60
wrapped_caption = textwrap.fill(caption, width=wrap_width)
# Calculate text bounding box with wrapped text
text_bbox = draw.textbbox((0, 0), wrapped_caption, font=font)
text_height = text_bbox[3] - text_bbox[1]
# Create a rectangle for the text background at the top
background_color = (255, 255, 0, 180) # Semi-transparent yellow
draw.rectangle(
[(0, 0), (image_width, text_height + 20)],
fill=background_color
)
# Draw the text at the top with a more conspicuous color
draw.text(
(10, 10), # Padding from top-left
wrapped_caption,
font=font,
fill=(0, 0, 0) # Black text for high contrast
)
# Convert back to RGB if saving as a format that doesn't support alpha
if save_path and Path(save_path).suffix.lower() in ['.jpg', '.jpeg']:
image = image.convert('RGB')
if save_path:
image.save(save_path)
logger.info(f"Caption visualization saved to: {save_path}")
else:
image.show()
def visualize_segmentation(
self,
image: 'PIL.Image.Image',
polygons: List[List[Tuple[int, int]]],
save_path: Optional[str] = None,
color: Tuple[int, int, int] = (255, 0, 0), # 默认为红色
alpha: float = 0.5 # 半透明
) -> None:
"""在图像上可视化分割掩码"""
from PIL import Image, ImageDraw
logger.debug(f"接收到 {len(polygons)} 个多边形进行可视化。")
if not polygons:
logger.warning("未提供多边形数据,跳过可视化。")
# Even if no polygons, save the original image if a path is given,
# so the user knows the process ran.
if save_path:
image.convert('RGB').save(save_path)
logger.info(f"已保存原始图像到: {save_path},因为没有分割数据可绘制。")
return
# 确保原始图像是RGBA模式,以便进行alpha合成
base_image = image.convert('RGBA')
# 创建一个透明的覆盖层用于绘制
overlay = Image.new('RGBA', base_image.size, (255, 255, 255, 0))
draw = ImageDraw.Draw(overlay)
polygons_drawn = 0
for i, polygon in enumerate(polygons):
logger.debug(f"正在处理多边形 #{i},该多边形有 {len(polygon)} 个顶点。")
if len(polygon) > 2:
# 在透明覆盖层上绘制半透明多边形
draw.polygon(polygon, fill=color + (int(255 * alpha),), outline=color)
polygons_drawn += 1
else:
logger.warning(f"多边形 #{i} 的顶点数不足({len(polygon)} <= 2),无法绘制。")
if polygons_drawn == 0:
logger.warning("没有绘制任何多边形,因为所有提供的多边形顶点数都不足。")
# 将带有掩码的覆盖层与基础图像混合
blended_image = Image.alpha_composite(base_image, overlay)
final_image = blended_image.convert('RGB')
if save_path:
final_image.save(save_path)
if polygons_drawn > 0:
logger.info(f"分割可视化结果(绘制了 {polygons_drawn} 个多边形)已保存到: {save_path}")
else:
logger.warning(f"可视化图像已保存到 {save_path},但未绘制任何分割掩码。")
else:
try:
final_image.show()
except Exception as e:
logger.warning(f"无法显示图像,请检查显示环境: {e}")