acsaco's picture
download
raw
4.28 kB
"""
Text Detection Module - GPU-optimized.
Uses YOLO (comic-text-detector) for fast manga text detection.
Model stays in GPU memory for zero-copy inference.
"""
import asyncio
from typing import Optional
from pathlib import Path
import numpy as np
from PIL import Image
from app.core.config import Settings
class TextDetector:
"""
GPU-accelerated text detection using YOLO.
Model is loaded once and persists in GPU memory.
"""
def __init__(self, settings: Settings):
self.settings = settings
self.model = None
self.device = settings.CUDA_DEVICE
async def load(self):
"""Load YOLO model into GPU memory."""
try:
from ultralytics import YOLO
from huggingface_hub import hf_hub_download
# Download model if not cached
cache_dir = self.settings.CACHE_DIR / "detection"
cache_dir.mkdir(parents=True, exist_ok=True)
model_path = hf_hub_download(
repo_id=self.settings.DETECTION_MODEL,
filename="comic-text-detector.pt",
cache_dir=str(cache_dir),
)
self.model = YOLO(model_path)
# Move to GPU
self.model.to(self.device)
print(f"[Detector] Loaded {self.settings.DETECTION_MODEL} on {self.device}")
except Exception as e:
print(f"[Detector] Failed to load YOLO: {e}")
print("[Detector] Falling back to contour-based detection")
self.model = None
async def detect(self, image: Image.Image) -> list[dict]:
"""
Detect text regions in image.
Returns list of { x, y, width, height, confidence }.
"""
if self.model is not None:
return await self._detect_yolo(image)
return await self._detect_contour(image)
async def _detect_yolo(self, image: Image.Image) -> list[dict]:
"""YOLO-based detection (GPU-accelerated)."""
img_array = np.array(image)
# Run inference (non-blocking via thread)
loop = asyncio.get_event_loop()
results = await loop.run_in_executor(
None,
lambda: self.model.predict(
img_array,
conf=self.settings.DETECTION_CONF_THRESHOLD,
iou=self.settings.DETECTION_IOU_THRESHOLD,
device=self.device,
verbose=False,
),
)
regions = []
for result in results:
if result.boxes is not None:
for box in result.boxes:
x1, y1, x2, y2 = box.xyxy[0].tolist()
conf = box.conf[0].item()
regions.append({
"x": int(x1),
"y": int(y1),
"width": int(x2 - x1),
"height": int(y2 - y1),
"confidence": conf,
"orientation": "vertical" if (y2 - y1) > (x2 - x1) * 2 else "horizontal",
})
return regions
async def _detect_contour(self, image: Image.Image) -> list[dict]:
"""Fallback: contour-based detection (CPU, no model needed)."""
img_array = np.array(image.convert("L"))
# Adaptive thresholding
binary = (img_array < 128).astype(np.uint8)
# Connected components
from scipy import ndimage
labeled, num_features = ndimage.label(binary)
regions = []
for i in range(1, num_features + 1):
component = (labeled == i)
y_coords, x_coords = np.where(component)
if len(x_coords) < 30:
continue
x_min, x_max = int(x_coords.min()), int(x_coords.max())
y_min, y_max = int(y_coords.min()), int(y_coords.max())
w = x_max - x_min
h = y_max - y_min
if w >= 10 and h >= 10 and w * h >= 100:
regions.append({
"x": x_min,
"y": y_min,
"width": w,
"height": h,
"confidence": 0.5,
"orientation": "vertical" if h > w * 2 else "horizontal",
})
return regions

Xet Storage Details

Size:
4.28 kB
·
Xet hash:
ff291324edcd675e37f9b97bb1f98fea45236537056af85819363a270ee84fb9

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.