Download runtime/line_merger.py from failed09/bashkir-trocr-base: direct link, hf CLI and curl.
- Browser
- Download file 4.28 kB
-
https://huggingface.co/failed09/bashkir-trocr-base/resolve/main/runtime/line_merger.py
- Command line
-
hf download hf://failed09/bashkir-trocr-base/runtime/line_merger.py
-
curl -L -o line_merger.py https://huggingface.co/failed09/bashkir-trocr-base/resolve/main/runtime/line_merger.py
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 | |