bashkir-trocr-base / runtime /line_merger.py
failed09's picture
Initial public release of BashkirTrOCR v3 (ONNX FP16 CUDA & INT8 CPU)
c8cdee4 verified
Raw History Blame Contribute Delete
4.28 kB
import numpy as np
def merge_line_boxes(det_res, x_margin_ratio=2.5, y_overlap_thresh=0.45):
"""
Умное объединение разрезанных боксов одной строки (Horizontal Line Merging).
:param det_res: список кортежей (box, text, score) от PaddleOCR/RapidOCR
:param x_margin_ratio: максимальное расстояние между словами одной строки (в долях высоты)
:param y_overlap_thresh: минимальный коэффициент вертикального перекрытия для принадлежности к одной строке
"""
if det_res is None or len(det_res) == 0:
return []
# Преобразуем в структурированные прямоугольники [xmin, ymin, xmax, ymax]
rects = []
for item in det_res:
if isinstance(item, (list, tuple)) and len(item) > 0 and isinstance(item[0], (list, tuple, np.ndarray)):
box = np.array(item[0]).astype(int)
else:
box = np.array(item).astype(int)
if box.ndim == 2 and box.shape[0] >= 4:
xmin = int(np.min(box[:, 0]))
xmax = int(np.max(box[:, 0]))
ymin = int(np.min(box[:, 1]))
ymax = int(np.max(box[:, 1]))
elif len(box) == 4:
xmin, ymin, xmax, ymax = int(box[0]), int(box[1]), int(box[2]), int(box[3])
else:
continue
h = max(1, ymax - ymin)
w = max(1, xmax - xmin)
rects.append({
"box": [xmin, ymin, xmax, ymax],
"xmin": xmin, "xmax": xmax,
"ymin": ymin, "ymax": ymax,
"h": h, "w": w,
"cy": (ymin + ymax) / 2.0
})
if not rects:
return []
# Сортируем сначала по вертикали Y
rects.sort(key=lambda r: (r["ymin"], r["xmin"]))
# Группируем боксы по строкам с проверкой близости вертикальных центров
lines = []
for r in rects:
placed = False
for line in lines:
line_ymin = min(x["ymin"] for x in line)
line_ymax = max(x["ymax"] for x in line)
line_cy = (line_ymin + line_ymax) / 2.0
line_h = max(1, line_ymax - line_ymin)
# Проверяем расстояние между центрами строк
center_diff = abs(r["cy"] - line_cy)
if center_diff <= min(r["h"], line_h) * 0.45:
line.append(r)
placed = True
break
if not placed:
lines.append([r])
# Внутри каждой строки сортируем слева направо и объединяем
merged_boxes = []
for line in lines:
line.sort(key=lambda r: r["xmin"])
cur_xmin = line[0]["xmin"]
cur_xmax = line[0]["xmax"]
cur_ymin = line[0]["ymin"]
cur_ymax = line[0]["ymax"]
for next_r in line[1:]:
avg_h = ((cur_ymax - cur_ymin) + next_r["h"]) / 2.0
dist_x = next_r["xmin"] - cur_xmax
# Если боксы достаточно близко по горизонтали — объединяем в один
if dist_x <= avg_h * x_margin_ratio:
cur_xmax = max(cur_xmax, next_r["xmax"])
cur_ymin = min(cur_ymin, next_r["ymin"])
cur_ymax = max(cur_ymax, next_r["ymax"])
else:
# Слишком далеко (например, две колонки текста) — сохраняем отдельно
merged_boxes.append([cur_xmin, cur_ymin, cur_xmax, cur_ymax])
cur_xmin = next_r["xmin"]
cur_xmax = next_r["xmax"]
cur_ymin = next_r["ymin"]
cur_ymax = next_r["ymax"]
merged_boxes.append([cur_xmin, cur_ymin, cur_xmax, cur_ymax])
# Сортируем итоговые строки строго сверху вниз
merged_boxes.sort(key=lambda b: (b[1], b[0]))
return merged_boxes