File size: 4,277 Bytes
c8cdee4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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