bubble-detector / inference.py
VaiiZ's picture
Upload 4 files
b48ed2c verified
Raw History Blame Contribute Delete
2.66 kB
import io
from PIL import Image
MODEL_ID = "ogkalu/comic-text-and-bubble-detector"
CONFIDENCE_THRESHOLD = 0.3
# Classes du modèle (voir sa fiche HuggingFace) :
# 0 = bubble (contour de bulle vide, sans texte à l'intérieur)
# 1 = text_bubble (texte à l'intérieur d'une bulle)
# 2 = text_free (texte hors bulle : onomatopées, cartouches, etc.)
# On ne garde que les zones contenant du texte : c'est ce qui doit devenir
# cliquable pour l'utilisateur. Les bulles vides (classe 0) sont ignorées.
RELEVANT_LABELS = {"text_bubble", "text_free"}
class BubbleDetector:
"""
Détecte les zones de texte d'une page de manga avec le modèle
open-source `ogkalu/comic-text-and-bubble-detector` (RT-DETR-v2, licence
Apache 2.0), entraîné sur manga/webtoon/manhua/comics occidentaux.
Utilise le pipeline `object-detection` standard de la librairie
`transformers` : le pré-traitement (redimensionnement) et le
post-traitement (décodage des boîtes, seuil de confiance) sont gérés en
interne par HuggingFace, aucune logique de décodage écrite à la main ici
— contrairement à des modèles YOLO bruts, le contrat d'API est stable et
documenté.
Le modèle (~170 Mo) se télécharge automatiquement depuis HuggingFace au
premier appel, puis reste en cache — comme pour manga-ocr, aucune étape
manuelle, aucune clé API.
"""
def __init__(self) -> None:
self._pipeline = None
def _ensure_loaded(self):
if self._pipeline is None:
from transformers import pipeline
import torch
# GPU si disponible (RTX 4060), sinon CPU. device=0 = premier GPU.
device = 0 if torch.cuda.is_available() else -1
self._pipeline = pipeline(
"object-detection", model=MODEL_ID, device=device
)
return self._pipeline
def detect(self, image_bytes: bytes) -> list[dict]:
detector = self._ensure_loaded()
image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
predictions = detector(image, threshold=CONFIDENCE_THRESHOLD)
boxes: list[dict] = []
for pred in predictions:
if pred["label"] not in RELEVANT_LABELS:
continue
box = pred["box"] # {xmin, ymin, xmax, ymax} déjà en pixels réels
boxes.append(
{
"x": int(box["xmin"]),
"y": int(box["ymin"]),
"width": int(box["xmax"] - box["xmin"]),
"height": int(box["ymax"] - box["ymin"]),
}
)
return boxes