"""Отрисовка bbox и текста распознанного номера на изображении.""" from pathlib import Path import cv2 import numpy as np from PIL import Image, ImageDraw, ImageFont def _find_font(size: int) -> ImageFont.FreeTypeFont: """Ищет шрифт с поддержкой кириллицы. Fallback на default.""" # Стандартные пути на Windows candidates = [ "C:/Windows/Fonts/arial.ttf", "C:/Windows/Fonts/arialbd.ttf", # Linux (для Docker) "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", ] for path in candidates: if Path(path).exists(): return ImageFont.truetype(path, size) return ImageFont.load_default() def annotate_image( image_bgr: np.ndarray, detections: list[dict], output_path: str | Path, ) -> None: """ Рисует bbox + текст номера для каждой детекции и сохраняет результат. detections: список dict от pipeline.process() """ # Конверт BGR (OpenCV) -> RGB (PIL) image_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB) pil_img = Image.fromarray(image_rgb) draw = ImageDraw.Draw(pil_img) # Размер шрифта пропорционально картинке h = pil_img.height font_size = max(16, h // 30) font = _find_font(font_size) for d in detections: if d.get("is_fallback"): continue # для fallback рисовать bbox по всему изображению не имеет смысла x1, y1, x2, y2 = d["bbox"] color = (0, 200, 0) if d.get("is_valid_gost") else (255, 140, 0) # Рамка draw.rectangle([x1, y1, x2, y2], outline=color, width=3) # Подпись над рамкой label = d["plate_text"] or "?" bbox_text = draw.textbbox((0, 0), label, font=font) text_w = bbox_text[2] - bbox_text[0] text_h = bbox_text[3] - bbox_text[1] # Фон под текст ty = max(0, y1 - text_h - 6) draw.rectangle([x1, ty, x1 + text_w + 8, ty + text_h + 6], fill=color) draw.text((x1 + 4, ty + 2), label, fill="white", font=font) pil_img.save(str(output_path), "JPEG", quality=90)