yomitoku 0.4.0.post1.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- yomitoku/__init__.py +20 -0
- yomitoku/base.py +136 -0
- yomitoku/cli/__init__.py +0 -0
- yomitoku/cli/main.py +230 -0
- yomitoku/configs/__init__.py +13 -0
- yomitoku/configs/cfg_layout_parser_rtdtrv2.py +89 -0
- yomitoku/configs/cfg_table_structure_recognizer_rtdtrv2.py +80 -0
- yomitoku/configs/cfg_text_detector_dbnet.py +49 -0
- yomitoku/configs/cfg_text_recognizer_parseq.py +51 -0
- yomitoku/constants.py +32 -0
- yomitoku/data/__init__.py +3 -0
- yomitoku/data/dataset.py +40 -0
- yomitoku/data/functions.py +279 -0
- yomitoku/document_analyzer.py +315 -0
- yomitoku/export/__init__.py +6 -0
- yomitoku/export/export_csv.py +71 -0
- yomitoku/export/export_html.py +188 -0
- yomitoku/export/export_json.py +34 -0
- yomitoku/export/export_markdown.py +145 -0
- yomitoku/layout_analyzer.py +66 -0
- yomitoku/layout_parser.py +189 -0
- yomitoku/models/__init__.py +9 -0
- yomitoku/models/dbnet_plus.py +272 -0
- yomitoku/models/layers/__init__.py +0 -0
- yomitoku/models/layers/activate.py +38 -0
- yomitoku/models/layers/dbnet_feature_attention.py +160 -0
- yomitoku/models/layers/parseq_transformer.py +218 -0
- yomitoku/models/layers/rtdetr_backbone.py +333 -0
- yomitoku/models/layers/rtdetr_hybrid_encoder.py +433 -0
- yomitoku/models/layers/rtdetrv2_decoder.py +811 -0
- yomitoku/models/parseq.py +243 -0
- yomitoku/models/rtdetr.py +22 -0
- yomitoku/ocr.py +87 -0
- yomitoku/postprocessor/__init__.py +9 -0
- yomitoku/postprocessor/dbnet_postporcessor.py +137 -0
- yomitoku/postprocessor/parseq_tokenizer.py +128 -0
- yomitoku/postprocessor/rtdetr_postprocessor.py +107 -0
- yomitoku/reading_order.py +214 -0
- yomitoku/resource/MPLUS1p-Medium.ttf +0 -0
- yomitoku/resource/charset.txt +1 -0
- yomitoku/table_structure_recognizer.py +244 -0
- yomitoku/text_detector.py +103 -0
- yomitoku/text_recognizer.py +128 -0
- yomitoku/utils/__init__.py +0 -0
- yomitoku/utils/graph.py +20 -0
- yomitoku/utils/logger.py +15 -0
- yomitoku/utils/misc.py +102 -0
- yomitoku/utils/visualizer.py +179 -0
- yomitoku-0.4.0.post1.dev0.dist-info/METADATA +127 -0
- yomitoku-0.4.0.post1.dev0.dist-info/RECORD +52 -0
- yomitoku-0.4.0.post1.dev0.dist-info/WHEEL +4 -0
- yomitoku-0.4.0.post1.dev0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
"""Copyright(c) 2023 lyuwenyu. All Rights Reserved."""
|
2
|
+
|
3
|
+
import torch
|
4
|
+
import torch.nn as nn
|
5
|
+
import torch.nn.functional as F
|
6
|
+
import torchvision
|
7
|
+
|
8
|
+
|
9
|
+
def mod(a, b):
|
10
|
+
out = a - a // b * b
|
11
|
+
return out
|
12
|
+
|
13
|
+
|
14
|
+
class RTDETRPostProcessor(nn.Module):
|
15
|
+
__share__ = [
|
16
|
+
"num_classes",
|
17
|
+
"use_focal_loss",
|
18
|
+
"num_top_queries",
|
19
|
+
"remap_mscoco_category",
|
20
|
+
]
|
21
|
+
|
22
|
+
def __init__(
|
23
|
+
self,
|
24
|
+
num_classes=80,
|
25
|
+
use_focal_loss=True,
|
26
|
+
num_top_queries=300,
|
27
|
+
remap_mscoco_category=False,
|
28
|
+
) -> None:
|
29
|
+
super().__init__()
|
30
|
+
self.use_focal_loss = use_focal_loss
|
31
|
+
self.num_top_queries = num_top_queries
|
32
|
+
self.num_classes = int(num_classes)
|
33
|
+
self.remap_mscoco_category = remap_mscoco_category
|
34
|
+
self.deploy_mode = False
|
35
|
+
|
36
|
+
def extra_repr(self) -> str:
|
37
|
+
return f"use_focal_loss={self.use_focal_loss}, num_classes={self.num_classes}, num_top_queries={self.num_top_queries}"
|
38
|
+
|
39
|
+
# def forward(self, outputs, orig_target_sizes):
|
40
|
+
def forward(self, outputs, orig_target_sizes: torch.Tensor, threshold):
|
41
|
+
logits, boxes = outputs["pred_logits"], outputs["pred_boxes"]
|
42
|
+
# orig_target_sizes = torch.stack([t["orig_size"] for t in targets], dim=0)
|
43
|
+
|
44
|
+
bbox_pred = torchvision.ops.box_convert(boxes, in_fmt="cxcywh", out_fmt="xyxy")
|
45
|
+
bbox_pred *= orig_target_sizes.repeat(1, 2).unsqueeze(1)
|
46
|
+
|
47
|
+
if self.use_focal_loss:
|
48
|
+
scores = F.sigmoid(logits)
|
49
|
+
scores, index = torch.topk(scores.flatten(1), self.num_top_queries, dim=-1)
|
50
|
+
# TODO for older tensorrt
|
51
|
+
# labels = index % self.num_classes
|
52
|
+
labels = mod(index, self.num_classes)
|
53
|
+
index = index // self.num_classes
|
54
|
+
boxes = bbox_pred.gather(
|
55
|
+
dim=1,
|
56
|
+
index=index.unsqueeze(-1).repeat(1, 1, bbox_pred.shape[-1]),
|
57
|
+
)
|
58
|
+
|
59
|
+
else:
|
60
|
+
scores = F.softmax(logits)[:, :, :-1]
|
61
|
+
scores, labels = scores.max(dim=-1)
|
62
|
+
if scores.shape[1] > self.num_top_queries:
|
63
|
+
scores, index = torch.topk(scores, self.num_top_queries, dim=-1)
|
64
|
+
labels = torch.gather(labels, dim=1, index=index)
|
65
|
+
boxes = torch.gather(
|
66
|
+
boxes,
|
67
|
+
dim=1,
|
68
|
+
index=index.unsqueeze(-1).tile(1, 1, boxes.shape[-1]),
|
69
|
+
)
|
70
|
+
|
71
|
+
# TODO for onnx export
|
72
|
+
if self.deploy_mode:
|
73
|
+
return labels, boxes, scores
|
74
|
+
|
75
|
+
# TODO
|
76
|
+
if self.remap_mscoco_category:
|
77
|
+
from ...data.dataset import mscoco_label2category
|
78
|
+
|
79
|
+
labels = (
|
80
|
+
torch.tensor(
|
81
|
+
[mscoco_label2category[int(x.item())] for x in labels.flatten()]
|
82
|
+
)
|
83
|
+
.to(boxes.device)
|
84
|
+
.reshape(labels.shape)
|
85
|
+
)
|
86
|
+
|
87
|
+
results = []
|
88
|
+
for lab, box, sco in zip(labels, boxes, scores):
|
89
|
+
lab = lab[sco > threshold]
|
90
|
+
box = box[sco > threshold]
|
91
|
+
sco = sco[sco > threshold]
|
92
|
+
|
93
|
+
lab = lab.cpu().numpy()
|
94
|
+
box = box.cpu().numpy()
|
95
|
+
sco = sco.cpu().numpy()
|
96
|
+
|
97
|
+
result = dict(labels=lab, boxes=box, scores=sco)
|
98
|
+
results.append(result)
|
99
|
+
|
100
|
+
return results
|
101
|
+
|
102
|
+
def deploy(
|
103
|
+
self,
|
104
|
+
):
|
105
|
+
self.eval()
|
106
|
+
self.deploy_mode = True
|
107
|
+
return self
|
@@ -0,0 +1,214 @@
|
|
1
|
+
import cv2
|
2
|
+
|
3
|
+
from .utils.graph import Node
|
4
|
+
from .utils.misc import (
|
5
|
+
is_intersected_vertical,
|
6
|
+
is_intersected_horizontal,
|
7
|
+
)
|
8
|
+
|
9
|
+
|
10
|
+
def is_locked_node(node):
|
11
|
+
return all([child.is_locked for child in node.children])
|
12
|
+
|
13
|
+
|
14
|
+
def _priority_dfs(nodes, direction):
|
15
|
+
if len(nodes) == 0:
|
16
|
+
return []
|
17
|
+
|
18
|
+
pending_nodes = sorted(nodes, key=lambda x: x.prop["distance"])
|
19
|
+
visited = [False] * len(nodes)
|
20
|
+
|
21
|
+
start = pending_nodes.pop(0)
|
22
|
+
stack = [start]
|
23
|
+
|
24
|
+
order = []
|
25
|
+
open_list = []
|
26
|
+
|
27
|
+
while not all(visited):
|
28
|
+
while stack:
|
29
|
+
is_updated = False
|
30
|
+
current = stack.pop()
|
31
|
+
if not visited[current.id]:
|
32
|
+
parents = current.parents
|
33
|
+
if all([visited[parent.id] for parent in parents]) or len(parents) == 0:
|
34
|
+
visited[current.id] = True
|
35
|
+
order.append(current.id)
|
36
|
+
is_updated = True
|
37
|
+
else:
|
38
|
+
if current not in open_list:
|
39
|
+
open_list.append(current)
|
40
|
+
|
41
|
+
if is_updated:
|
42
|
+
for open_node in reversed(open_list):
|
43
|
+
stack.append(open_node)
|
44
|
+
open_list.remove(open_node)
|
45
|
+
|
46
|
+
if len(current.children) > 0:
|
47
|
+
stack.append(current)
|
48
|
+
|
49
|
+
if len(current.children) == 0:
|
50
|
+
children = []
|
51
|
+
for node in stack:
|
52
|
+
if current in node.parents:
|
53
|
+
children.append(node)
|
54
|
+
stack.remove(node)
|
55
|
+
|
56
|
+
if direction == "horizontal":
|
57
|
+
children = sorted(
|
58
|
+
children, key=lambda x: x.prop["box"][0], reverse=True
|
59
|
+
)
|
60
|
+
else:
|
61
|
+
children = sorted(
|
62
|
+
children, key=lambda x: x.prop["box"][1], reverse=True
|
63
|
+
)
|
64
|
+
|
65
|
+
stack.extend(children)
|
66
|
+
continue
|
67
|
+
|
68
|
+
child = current.children.pop(0)
|
69
|
+
stack.append(child)
|
70
|
+
|
71
|
+
for node in pending_nodes:
|
72
|
+
if node in open_list:
|
73
|
+
continue
|
74
|
+
stack.append(node)
|
75
|
+
pending_nodes.remove(node)
|
76
|
+
break
|
77
|
+
else:
|
78
|
+
if not all(visited) and len(open_list) != 0:
|
79
|
+
node = open_list.pop(0)
|
80
|
+
visited[node.id] = True
|
81
|
+
order.append(node.id)
|
82
|
+
|
83
|
+
return order
|
84
|
+
|
85
|
+
|
86
|
+
def _exist_other_node_between_vertical(node, other_node, nodes):
|
87
|
+
for search_node in nodes:
|
88
|
+
if search_node == node or search_node == other_node:
|
89
|
+
continue
|
90
|
+
|
91
|
+
_, sy1, _, sy2 = search_node.prop["box"]
|
92
|
+
_, oy1, _, oy2 = other_node.prop["box"]
|
93
|
+
_, ny1, _, ny2 = node.prop["box"]
|
94
|
+
|
95
|
+
if is_intersected_vertical(search_node.prop["box"], node.prop["box"]):
|
96
|
+
if ny2 < sy1 < oy1 and ny2 < sy2 < oy1:
|
97
|
+
return True
|
98
|
+
|
99
|
+
if oy2 < sy1 < ny1 and oy2 < sy2 < ny1:
|
100
|
+
return True
|
101
|
+
|
102
|
+
return False
|
103
|
+
|
104
|
+
|
105
|
+
def _exist_other_node_between_horizontal(node, other_node, nodes):
|
106
|
+
for search_node in nodes:
|
107
|
+
if search_node == node or search_node == other_node:
|
108
|
+
continue
|
109
|
+
|
110
|
+
sx1, _, sx2, _ = search_node.prop["box"]
|
111
|
+
ox1, _, ox2, _ = other_node.prop["box"]
|
112
|
+
nx1, _, nx2, _ = node.prop["box"]
|
113
|
+
|
114
|
+
if is_intersected_horizontal(search_node.prop["box"], node.prop["box"]):
|
115
|
+
if nx2 < sx1 < ox1 and nx2 < sx2 < ox1:
|
116
|
+
return True
|
117
|
+
|
118
|
+
if ox2 < sx1 < nx1 and ox2 < sx2 < nx1:
|
119
|
+
return True
|
120
|
+
|
121
|
+
return False
|
122
|
+
|
123
|
+
|
124
|
+
def _create_graph_horizontal(nodes):
|
125
|
+
for i, node in enumerate(nodes):
|
126
|
+
for j, other_node in enumerate(nodes):
|
127
|
+
if i == j:
|
128
|
+
continue
|
129
|
+
|
130
|
+
if is_intersected_vertical(node.prop["box"], other_node.prop["box"]):
|
131
|
+
ty = node.prop["box"][1]
|
132
|
+
oy = other_node.prop["box"][1]
|
133
|
+
|
134
|
+
if _exist_other_node_between_vertical(node, other_node, nodes):
|
135
|
+
continue
|
136
|
+
|
137
|
+
if ty < oy:
|
138
|
+
node.add_link(other_node)
|
139
|
+
else:
|
140
|
+
other_node.add_link(node)
|
141
|
+
|
142
|
+
node_distance = node.prop["box"][0] + node.prop["box"][1]
|
143
|
+
node.prop["distance"] = node_distance
|
144
|
+
|
145
|
+
for node in nodes:
|
146
|
+
node.children = sorted(node.children, key=lambda x: x.prop["box"][0])
|
147
|
+
|
148
|
+
|
149
|
+
def _create_graph_vertical(nodes):
|
150
|
+
max_x = max([node.prop["box"][2] for node in nodes])
|
151
|
+
|
152
|
+
for i, node in enumerate(nodes):
|
153
|
+
for j, other_node in enumerate(nodes):
|
154
|
+
if i == j:
|
155
|
+
continue
|
156
|
+
|
157
|
+
if is_intersected_horizontal(node.prop["box"], other_node.prop["box"]):
|
158
|
+
tx = node.prop["box"][2]
|
159
|
+
ox = other_node.prop["box"][2]
|
160
|
+
|
161
|
+
if _exist_other_node_between_horizontal(node, other_node, nodes):
|
162
|
+
continue
|
163
|
+
|
164
|
+
if tx < ox:
|
165
|
+
other_node.add_link(node)
|
166
|
+
else:
|
167
|
+
node.add_link(other_node)
|
168
|
+
|
169
|
+
node.prop["distance"] = (max_x - node.prop["box"][2]) + node.prop["box"][1]
|
170
|
+
|
171
|
+
for node in nodes:
|
172
|
+
node.children = sorted(node.children, key=lambda x: x.prop["box"][1])
|
173
|
+
|
174
|
+
|
175
|
+
def prediction_reading_order(elements, direction, img=None):
|
176
|
+
if len(elements) < 2:
|
177
|
+
return elements
|
178
|
+
|
179
|
+
nodes = [Node(i, element.dict()) for i, element in enumerate(elements)]
|
180
|
+
if direction == "horizontal":
|
181
|
+
_create_graph_horizontal(nodes)
|
182
|
+
else:
|
183
|
+
_create_graph_vertical(nodes)
|
184
|
+
|
185
|
+
# For debugging
|
186
|
+
# if img is not None:
|
187
|
+
# visualize_graph(img, nodes)
|
188
|
+
|
189
|
+
order = _priority_dfs(nodes, direction)
|
190
|
+
for i, index in enumerate(order):
|
191
|
+
elements[index].order = i
|
192
|
+
|
193
|
+
return elements
|
194
|
+
|
195
|
+
|
196
|
+
def visualize_graph(img, nodes):
|
197
|
+
out = img.copy()
|
198
|
+
for node in nodes:
|
199
|
+
for child in node.children:
|
200
|
+
nx1, ny1, nx2, ny2 = node.prop["box"]
|
201
|
+
cx1, cy1, cx2, cy2 = child.prop["box"]
|
202
|
+
|
203
|
+
node_center = nx1 + (nx2 - nx1) // 2, ny1 + (ny2 - ny1) // 2
|
204
|
+
child_center = cx1 + (cx2 - cx1) // 2, cy1 + (cy2 - cy1) // 2
|
205
|
+
|
206
|
+
cv2.arrowedLine(
|
207
|
+
out,
|
208
|
+
(int(node_center[0]), int(node_center[1])),
|
209
|
+
(int(child_center[0]), int(child_center[1])),
|
210
|
+
(0, 0, 255),
|
211
|
+
2,
|
212
|
+
)
|
213
|
+
|
214
|
+
cv2.imwrite("graph.jpg", out)
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~¡¢£¤¥¦§©«¬®°±¶·»¿Å×ð÷ÿħʒ̧́̅̈̋ΓΔΘΛΞΠΣΦΨΩαβγδεζηθικλμνξοπρστυφχψωДЩджзщъёאิლ‐–—‖‘’“”†‡•‰′※‿⁂⁄⁑€℧←↑→↓↔↖↗↘↙⇄⇒⇔⇦⇧⇨⇩∀∂∃∅∇∈∉∋−∓√∝∞∟∠∦∧∨∩∪∫∮∴∵∽≃≅≈≒≠≡≢≦≧≪≫≶≷⊂⊃⊄⊅⊆⊇⊊⊋⊕⊖⊗⊥⊿⋚⋛⌅⌆⌒⌘⎾⎿⏀⏁⏂⏃⏄⏅⏆⏇⏈⏉⏊⏋⏌⏎⓫⓬⓭⓮⓯⓰⓱⓲⓳⓴⓵⓶⓷⓸⓹⓺⓻⓼⓽⓾─━┌┐┘├╹■□▱▲△▶▷▼▽◀◁◆◇◉○◎●◐◑◒◓◕◡◦◯☀☁☂☃★☆☎☖☗☞♀♂♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯✓❖❶❷❸❹❺❻❼❽❾❿⤴⤵⦅⦆⦿⧺⧻、。〃〄々〆〇〈〉《》「」『』【】〒〓〔〕〖〗〘〙〜〝〟〠〳〴〵〻〼〽ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをん゙゚ゝゞ゠ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶヷヸヹ・ーヽヾㇲㇵㇶㇷㇻ㒵㓛㕘㗂㩁㮶㷀㷔䑓䒾䠖䰗一丁七万丈三上下不与丐丑且丕世丘丙丞両並个中丰串丸丹主丼丿乂乃久之乍乎乏乕乖乗乘乙九乞也乢乱乳乾亀亂了予争亊事二于云互五井亘亙些亜亞亟亡亢交亥亦亨享京亭亮亳亶亷亹人亻什仁仄仆仇今介仍从仏仔仕他仗付仙仝仞代令以仭仮仰仲仵件价任份仿企伉伊伋伍伎伏伐休会伜伝伯估伴伶伸伺似伽佃但佇位低住佐佑体何佗余佚佛作佝佞佟佤佩佯佰佳併佶佻佼佾使侃來侈例侍侏侑侖侗侘供依侠価侫侭侮侯侵侶便係促俄俅俊俎俐俑俔俗俘俚俛保俟俠信俣俤俥修俯俱俳俵俶俸俺俾倂倅倆倉個倍倏們倒倔倖候倚倞借倣値倥倦倨倩倪倫倭倶倹倻偃假偈偉偏偐偕偖做停健偬偲側偵偶偸偽傀傅傍傑傕傘備傚傪催傭傲傳傴債傷傾僂僄僅僉僊働像僑僔僕僖僙僚僞僣僥僧僭僮價僻儀儁儂億儉儋儒儔儕儘儚儛儞償儡優儲儷儺儻儼兀允元兄充兆兇先光兊克兌免兎児兒兔兕兗党兜兢入內全兩兪八公六兮共兵其具典兼冀内円冉冊册再冐冑冒冕冗写冠冢冤冥冦冨冩冪冬冰冱冲决冴况冶冷冼冽凄准凉凋凌凍凖凛凜凝凞几凡処凧凩凪凭凮凰凱凶凸凹出函凾刀刃刄分切刈刊刋刎刑刕列初判別利刪刮到刳制刷券刹刺刻剃剄則剉削剋剌前剔剖剛剜剝剣剤剥剩剪副剰剱割剴創剽剿劃劇劈劉劍劒劔力功加劣助努劫劬劭励労劵効劻劾勁勃勅勇勉勒動勘務勛勝勞募勢勣勤勦勧勰勲勳勵勸勺勾勿匀匁匂包匆匈匍匏匐匕化北匙匝匠匡匣匪匯匱匹区医匾匿區十千卅卆升午卉半卍卑卒卓協南単博卜卞占卣卦卮卯印危即却卵卷卸卻卽卿厄厓厖厘厚原厠厥厦厨厩厭厰厲厳厷去参參又叉及友双反収叔取受叙叛叟叡叢口古句叩只叫召叭叮可台叱史右叶号司叺吁吃各合吉吊同名后吏吐向君吝吞吟吠否吧吩含听吶吸吹吻吼吽吾呀呂呆呈呉告呍呎呑呕呟周呪呰呱味呵呶呷呻呼命咀咄咆咈咋和咎咏咐咒咖咜咢咤咥咨咩咫咬咯咲咳咸咽哀品哂哄哆哇哈哉員哥哨哩哭哮哲哺哿唄唆唇唎唐唖售唯唱唳唸唹唼唾啀啄商問啐啓啖啗啜啞啡啻啼啾喀喃善喆喇喉喊喋喎喘喙喚喜喝喟喧喨喩喪喫喬單喰喱営嗄嗅嗇嗒嗔嗚嗜嗟嗣嗤嗧嗽嗾嘆嘈嘉嘎嘔嘖嘗嘘嘩嘯嘰嘱嘲嘴嘶嘸嘻噂噉噌噎噓噛噤噦器噪噫噬噯噲噴噶噸噺嚀嚆嚇嚊嚏嚔嚙嚝嚠嚢嚥嚬嚭嚮嚴嚶嚼囀囁囂囃囅囈囊囎囑囓囗囘囚四回因団囮困囲図囹固国囿圀圃圄國圍圏園圓圖團圜土圦圧在圭地圳圷圻址坂均坊坌坍坎坏坐坑坡坤坦坨坩坪垂垈型垓垜垠垢垣垤垰垸埃埋城埒埓埔埜域埠埣埤埴埵執培基埼堀堂堅堆堊堕堙堝堡堤堪堯堰報場堵堺塀塁塊塋塌塑塒塔塗塘塙塚塞塡塢塩填塰塲塵塹塼塾境墅墉墓増墜增墟墨墮墳墺墻墾壁壅壇壊壌壑壒壓壕壘壙壜壞壟壤士壬壮壯声壱売壷壹壺壻壼壽変夋夏夔夕外夘夙多夛夜夢夤夥大天太夫夬夭央失夲夷夸夾奄奇奈奉奎奏奐契奔奕套奘奚奝奠奢奥奧奨奪奬奭奮女奴奶奸好妁如妃妄妊妍妓妖妙妝妣妤妥妨妬妮妲妹妺妻妾姆姉始姐姑姒姓委姙姚姜姣姥姦姧姨姪姫姮姶姸姻姿威娃娉娍娑娘娜娟娠娥娩娯娵娶娼婀婁婆婉婕婚婢婣婦婪婬婿媄媒媚媛媧媱媳媼媽媾嫁嫂嫄嫉嫋嫌嫖嫗嫡嫣嫦嫩嫺嬉嬋嬌嬖嬢嬥嬪嬬嬰嬲嬴嬶孀孁孃孅孌子孑孔孕字存孚孛孜孝孟季孤学孩孫孰孵學孺孽宀宅宇守安宋完宍宏宓宕宗官宙定宛宜宝実客宣室宥宦宮宰害宴宵家宸容宿寂寃寄寅密寇富寐寒寓寔寛寝寞察寡寢寤寥實寧寨審寫寮寰寳寵寶寸寺対寿封専射尅将將專尉尊尋對導小少尓尖尚尠尢尤尨尩尫尭就尸尹尺尻尼尽尾尿局屁居屆屈届屋屍屎屏屐屑屓展屛属屠屡屢層履屬屮屯山屹岌岐岑岡岨岩岫岬岱岳岷岸岺岻峅峙峠峡峨峩峪峭峯峰峴島峻峽崇崋崎崐崑崔崕崖崗崘崙崚崛崟崢崧崩嵆嵋嵌嵎嵐嵜嵩嵬嵯嶂嶄嶇嶋嶌嶠嶬嶮嶲嶷嶸嶺嶼嶽巉巌巍巎巒巓巖川州巡巢巣工左巧巨巫差己已巳巴巵巷巻巽巾市布帆帋希帑帒帕帖帙帚帛帝帥師席帮帯帰帳帶帷常帽幀幄幅幇幌幎幔幕幖幗幘幟幡幢幣幤幫幮干平年并幷幸幹幻幼幽幾庁広庄庇床序底庖店庚府庠度座庫庭庵庶康庸庾廁廂廃廈廉廊廋廏廐廓廖廚廛廟廠廡廢廣廨廩廬廰廳廴延廷廸建廻廼廿弁弄弉弊弋弌弍式弐弑弓弔引弖弗弘弛弟弥弦弧弩弭弯弱弴張強强弻弼弾彅彈彊彌彎当彗彙彝彡形彣彤彦彧彩彪彫彬彭彰影彳彷役彼彽彿往征徂徃径待徇徊律後徐徑徒従得徘徙從徠御徧徨復循徭微徳徴徵德徹徼徽心必忉忌忍忖志忘忙応忝忠忡忤忩快忯忰忱念忸忻忽忿怏怐怒怕怖怙怛怜思怠怡急性怨怩怪怫怯怱怵怺恁恂恃恆恊恋恍恐恒恕恙恚恟恠恢恣恤恥恨恩恪恫恬恭息恰恵悃悄悅悉悋悌悍悔悖悚悛悝悟悠患悦悧悩悪悰悲悳悴悵悶悸悼悽情惇惋惑惓惔惕惘惚惜惟惠惡惣惧惨惰惱想惴惶惹惺惻愁愆愈愉愍愎意愔愕愚愛感愡愧愨愬愴愷愼愽愾愿慂慄慇慈慊態慌慍慎慓慕慘慙慚慟慢慣慥慧慨慫慮慰慳慴慶慷慾憂憇憊憍憎憐憑憔憖憙憚憤憧憩憫憬憮憲憶憺憾懃懆懇懈應懊懋懌懐懣懦懲懴懶懷懸懺懼懽懾懿戀戈戊戌戍戎成我戒戔戕或戚戛戞戟戡戦戩截戮戯戰戲戴戸戻房所扁扇扈扉手才扎打払托扛扞扠扣扨扭扮扱扶批扼承技抂抄抉把抑抒抓抔投抖抗折抛抜択披抬抱抵抹抻押抽拂担拆拇拈拉拋拌拍拏拐拒拓拔拕拗拘拙招拜拝拠拡括拭拮拯拱拳拵拶拷拼拾拿持挂指挈按挊挌挍挐挑挘挙挟挨挫振挵挹挺挽挾挿捉捌捍捏捐捕捗捜捥捧捨捩据捲捶捷捺捻掃授掉掌掏排掖掘掛掟掠採探掣接控推掩措掬掲掴掻掾揀揃揄揆揉描提插揖揚換握揣揩揭揮援揶揷揺搆損搏搔搖搗搜搤搦搨搩搬搭搶携搾摂摑摘摠摧摩摭摯摶摸摹摺撃撈撑撒撓撕撚撝撞撤撥撩撫播撮撰撲撹撻撼撾撿擁擂擅擇擉操擒擔擘據擠擡擢擣擤擦擧擬擯擱擲擴擻擽擾攀攅攘攝攢攣攪攫攬支攴攵收攷攸改攻放政故效敍敎敏救敕敖敗教敝敢散敦敧敬数敲整敵敷數斂斃文斉斌斎斐斑斗料斛斜斟斡斤斥斧斫斬断斯新斷方於施旁旄旅旆旋旌族旒旗旛无既日旦旧旨早旬旭旱旺旻旼昂昃昆昇昉昊昌明昏易昔昕星映春昧昨昭是昱昴昵昶昺昻昼晁時晃晄晉晋晏晒晝晟晡晢晤晦晧晨晩普景晰晴晶晷智暁暇暈暉暎暑暖暗暘暝暠暢暦暫暮暲暴暸暹暻暼暾曁曄曇曈曉曖曙曜曝曠曦曩曰曲曳更曵曷書曹曺曻曼曽曾替最會朅月有朋服朏朔朕朖朗望朝期朦朧木未末本札朮朱朴朶朸机朽杁杆杉李杏材村杓杖杙杜杞束杠条杢杣杤来杦杭杮杯杰東杲杳杵杷杼松板枇枉枋枌析枒枕林枘枚果枝枠枡枢枯枲枳枴架枷枸枹枻柁柄柊柏某柑染柔柘柚柝柞柢柩柬柯柱柳柴柵査柾柿栂栃栄栓栖栗栞校栢栩株栫栱栲栴核根格栽栾桀桁桂桃桅框案桎桐桑桒桓桔桙桛桜桝桟档桧桴桶桾桿梁梃梅梏梓梔梗梛條梟梠梢梦梧梨梭梯械梱梲梳梵梶梼棃棄棈棉棊棋棍棐棒棕棖棗棘棚棟棠棡棣棧棭森棰棱棲棹棺椀椅椋植椎椏椒椙椚椛検椥椨椰椴椵椹椽椿楂楊楓楔楕楚楝楞楠楡楢楣楤楨楪楫業楮楯楳極楷楸楹楼楽榀榁概榊榎榑榔榕榛榜榧榮榲榴榻榾槁槃槇槊構槌槍槎槐槓槖様槙槧槨槫槲槵槹槻槽槿樂樅樊樋樒樓樗標樝樞樟模樣権横樫樵樸樹樺樽橄橅橇橈橋橐橘橙橛機橡橢橫橳橿檀檄檍檎檐檔檗檜檞檠檢檣檪檫檬檮檳檸檻櫂櫃櫑櫓櫚櫛櫞櫟櫨櫪櫬櫺櫻欄欅權欒欖欝欠次欣欧欲欵欷欹欺欽款歃歇歉歌歎歐歓歔歙歛歟歡止正此步武歩歪歯歳歴歷歸死歿殂殃殄殆殉殊残殖殘殞殤殪殯殱殲殳殴段殷殺殻殼殿毀毅毆毉毋毌母毎每毒毓比毖毗毘毛毟毫毬毮毯毱毳氈氏氐民氓气気氣氤氳水氵氷永氾汀汁求汎汐汕汗汙汚汜汝汞江池汨汪汰汲汳汴汶決汽汾沁沂沃沅沈沌沍沐沒沓沖沙沚沛没沢沪沫沮沱河沸油治沼沽沾沿況泄泉泊泌泓泔法泗泛泝泡波泣泥注泪泮泯泰泳洋洌洎洒洗洙洛洞洟津洩洪洫洮洱洲洵洶洸洹活洽派流浄浅浙浚浜浣浦浩浪浬浮浴海浸涂涅涇消涌涎涓涕涙涛涜涪涯液涵涸涼涿淀淄淅淆淇淋淑淒淖淘淙淚淛淝淞淡淤淦淨淪淫淮深淳淵混淹淺添清渇済渉渋渓渕渗渙渚減渝渟渠渡渣渤渥渦渧温渫測渭港游渺渾湃湄湊湍湎湑湖湘湛湣湧湫湮湯湲湾湿満溂溌源準溘溜溝溟溢溥溪溫溯溲溶溷溺滁滂滄滅滇滉滋滌滎滑滓滔滕滙滝滞滬滯滲滴滸滾滿漁漂漆漉漏漑漓演漕漠漢漣漪漫漬漱漲漳漸漾漿潁潅潑潔潘潙潛潜潞潟潢潤潦潭潮潯潰潴潸潺潼潾澀澁澂澄澆澈澎澗澠澡澣澤澪澱澳澶澹激濁濂濃濊濕濘濛濟濠濡濤濫濮濯濱濳濵濶濹濾瀆瀉瀋瀏瀑瀕瀘瀚瀛瀝瀞瀟瀦瀧瀨瀬瀰瀲瀾灌灑灘灝灣灤火灯灰灸灼災炉炊炎炒炕炙炫炬炭炮炯炳炷炸点為炻烈烏烔烘烙烝烟烤烬烱烹烽焉焔焙焚焜無焦焮焰焱然焼煆煇煉煊煌煎煕煖煙煞煠煢煤煥煦照煨煩煬煮煽熄熇熈熊熏熒熔熕熖熙熟熨熬熱熹熾燁燃燄燈燎燐燒燔燕燗營燠燥燦燧燬燭燮燵燹燻燼燾燿爆爍爐爛爨爪爬爭爰爲爵父爸爹爺爻爼爽爾牀牆片版牋牌牒牓牖牘牙牛牝牟牡牢牧物牯牲牴特牽牾犀犁犂犇犍犒犠犢犧犬犯状狀狂狃狄狆狎狐狒狗狙狛狠狡狢狩独狭狷狸狹狼狽猊猖猗猙猛猜猝猟猥猨猩猪猫献猯猴猶猷猹猽猾猿獄獅獏獐獒獗獣獦獨獪獰獱獲獵獸獺獻玄玅率玉王玕玖玘玟玩玫玲玳玵玻珀珂珈珉珊珍珎珞珠珢珥珩珪班珮現球琅理琉琊琛琢琥琦琨琪琫琬琮琰琲琳琴琵琶琺瑀瑁瑄瑇瑋瑕瑗瑙瑚瑛瑜瑞瑟瑠瑣瑤瑩瑪瑫瑯瑰瑳瑶瑾璀璃璇璈璉璋璐璘璜璞璟璣璧璨璫環璽璿瓊瓏瓔瓘瓚瓜瓠瓢瓣瓦瓩瓮瓱瓲瓶瓷甃甄甌甍甎甑甕甗甘甚甜甞生産甥甦用甪甫甬田由甲申男甸町画畊界畏畑畔留畚畜畝畠畢畤略畦畧番畫畭畯異畳畴當畷畸畿疆疇疊疋疎疏疑疔疚疝疣疥疫疱疲疳疵疸疹疼疽疾痂痃病症痍痎痒痔痕痘痙痛痞痢痣痤痩痰痲痳痴痺痼痾痿瘀瘁瘉瘋瘍瘖瘙瘞瘠瘡瘢瘤瘦瘧瘭瘰瘳瘴瘻療癆癇癈癋癌癒癖癘癜癡癢癧癩癪癬癭癲癸発登發白百皀皃的皆皇皈皋皎皐皓皕皖皙皚皮皰皴皷皸皹皺皿盂盃盆盈盉益盍盎盒盔盖盗盙盛盜盞盟盡監盤盥盦盧盪盬目盱盲直相盻盼盾省眄眇眈眉看県眙眛眞真眠眥眦眩眶眷眸眺眼着睇睛睜睡睢督睥睦睨睫睹睺睾睿瞀瞋瞑瞞瞠瞥瞬瞭瞰瞳瞶瞻瞼瞽瞿矍矗矚矛矜矢矣知矧矩短矮矯石砂砉砌砒研砕砖砥砦砧砭砲破砺硅硎硏硝硨硫硬确硯硴硼碁碆碇碌碍碎碑碓碕碗碣碧碩碪確碼碾磁磅磊磋磌磐磔磚磧磨磬磯磲磴磽礀礁礎礑礒礙礦礪礫礬示礼社祀祁祅祆祇祈祉祐祓祕祖祗祚祛祜祝神祟祠祢祥票祭祷祺祿禀禁禄禅禊禍禎福禖禛禝禦禧禪禮禰禱禳禴禹禺禽禾禿秀私秉秋种科秒秕秘秞租秡秣秤秦秧秩称移稀稈程稍税稔稗稙稚稜稟稠種稱稲稷稹稻稼稽稿穀穂穆穉穌積穎穏穐穗穙穡穢穣穩穫穰穴穵究穹空穽穿突窃窄窈窒窓窕窖窗窘窟窠窣窩窪窬窮窯窰窶窺窻窿竃竄竅竇竈竊立竎站竜竝竟章竢竣童竦竪竫竭端競竸竹竺竿笄笈笊笋笏笑笒笘笙笛笞笠笥符笨第笮笳笵笹笻筅筆筇筈等筋筌筍筏筐筑筒筓答策筝筠筥筧筬筭筮筰筱筵筺筿箆箇箋箍箏箒箔箕算箙箚箝箞管箪箭箯箱箴箸節篁範篆篇築篊篋篏篙篝篠篤篥篦篩篭篳篶篷簀簁簃簇簋簍簏簑簒簓簔簗簞簟簠簡簣簧簪簫簳簶簷簸簽簾簿籀籃籌籍籏籐籔籖籙籟籠籤籩籬米籵籾粁粂粃粉粋粍粏粒粕粗粘粛粟粡粢粤粥粧粮粱粲粳粹粼粽精糀糂糅糊糎糒糕糖糜糝糞糟糠糢糧糯糲糴糶糸糺系糾紀紂約紅紆紇紈紊紋納紐紒純紕紗紘紙級紛紜素紡索紫紬紮累細紱紲紳紹紺絁終絃組絅絆絋経絎絏結絓絖絛絜絞絡絢絣給絨絪絮統絲絳絵絶絹絽綉綏經継続綛綜綝綟綠綢綣綦綫綬維綮綯綰綱網綴綵綸綺綻綽綾綿緇緊緋総緑緒緖緘線緜緝緞締緡緣編緩緬緯緲練緻縁縄縅縉縊縋縒縕縛縝縞縟縢縣縦縫縬縮縱縲縵縷縹縺縻總績繁繃繅繆繇繊繋繍織繕繙繚繝繞繡繦繧繩繪繫繭繰繹繻繼繽繿纂纈纉纊續纍纎纏纐纒纓纔纜缶缸缺罄罅罌罎罐罔罕罘罠罡罣罨罩罪罫置罰署罵罷罸罹羂羅羆羇羈羊羌美羚羞羣群羨義羮羯羲羸羹羽羿翁翅翆翊翌翎習翔翕翛翟翠翡翦翩翫翰翳翹翻翼耀老考耄者耆耋而耐耒耕耗耘耙耜耡耦耨耳耶耻耽耿聃聆聊聖聘聚聞聟聡聢聨聯聰聱聲聳聴聶職聽聾聿肄肅肆肇肉肋肌肓肖肘肚肛肝股肢肥肩肪肭肯肱育肴肺胃胄胆背胎胖胚胛胝胞胡胤胥胯胱胳胴胸胼能脂脅脆脇脈脉脊脘脚脛脞脣脩脯脱脳脹脾腅腆腊腋腎腐腑腓腔腕腟腥腦腫腮腰腱腴腸腹腺腿膀膂膃膅膈膊膏膓膕膚膜膝膠膣膨膩膰膳膵膸膺膻膽膾膿臀臂臆臈臉臍臏臑臓臘臙臚臟臠臣臥臧臨自臬臭至致臺臼臾舁舂舅與興舉舊舌舍舎舐舒舖舗舘舛舜舞舟舩航舫般舲舳舵舶舷舸船艀艇艘艙艚艜艝艟艣艤艦艨艪艫艮良艱色艶艷艸艾芋芍芎芒芙芝芟芥芦芨芩芬芭芮芯花芳芷芸芹芻芽芾芿苅苑苒苓苔苕苗苛苜苞苟苡苣若苦苧苫英苳苴苹苺苻茂范茄茅茆茉茎茗茘茜茣茨茫茯茱茲茴茵茶茸茹荀荃荆草荊荏荒荔荘荳荵荷荻荼莅莆莉莊莎莒莓莕莖莘莚莞莟莠莢莧莨莩莪莫莱莵莽菀菁菅菇菊菌菎菓菖菘菜菟菠菩菫華菰菱菲菴菹菽萃萄萇萊萌萍萎萓萠萢萩萪萬萱萵萸萼落葆葈葉葎著葛葡葢董葦葩葫葬葭葯葱葵葷葺蒂蒅蒋蒐蒔蒙蒜蒟蒡蒯蒲蒸蒻蒼蒾蒿蓀蓁蓂蓄蓆蓉蓊蓋蓍蓐蓑蓓蓖蓙蓚蓬蓮蓯蓴蓼蓿蔀蔑蔓蔔蔕蔗蔘蔚蔞蔟蔡蔣蔦蔬蔭蔲蔵蔽蕁蕃蕈蕉蕊蕋蕎蕓蕗蕙蕚蕝蕞蕣蕨蕩蕪蕭蕷蕾薀薄薇薈薊薏薐薑薔薗薙薛薜薟薤薦薨薩薪薫薬薭薮薯薹薺藁藉藍藏藐藕藜藝藤藥藦藩藪藷藹藺藻藿蘂蘄蘆蘇蘊蘋蘐蘑蘖蘗蘚蘞蘩蘭蘯蘸蘿虎虐虔處虗虚虜虞號虢虧虫虬虱虹虻蚊蚋蚌蚓蚕蚣蚤蚩蚪蚫蚯蚰蛄蛆蛇蛉蛋蛍蛎蛔蛙蛛蛞蛟蛤蛩蛭蛮蛯蛸蛹蛻蛼蛾蜀蜂蜃蜆蜈蜉蜊蜍蜑蜒蜓蜘蜚蜜蜥蜩蜱蜴蜷蜺蜻蜾蜿蝀蝃蝉蝋蝌蝎蝓蝕蝗蝘蝙蝟蝠蝣蝦蝨蝪蝮蝯蝴蝶蝸蝿螂融螟螢螫螭螳螺螻螽蟀蟄蟆蟇蟋蟎蟐蟒蟖蟠蟥蟬蟯蟲蟷蟹蟻蟾蠃蠅蠍蠑蠓蠕蠖蠟蠡蠢蠣蠧蠱蠲蠶蠹蠻血衄衆行衍衒術街衙衛衝衞衡衢衣衤表衫衰衲衵衷衽衾衿袁袂袈袋袍袒袖袗袘袙袞袢袤袪被袰袱袴袵袷袿裀裁裂裃裄装裎裏裒裔裕裘裙補裝裟裡裨裰裱裲裳裴裵裸裹裼製裾褂褄複褊褌褐褒褓褘褙褚褜褥褪褫褰褲褶褸褻襁襄襖襞襟襠襤襦襯襲襴襷襾西要覃覆覇覈覊見規視覗覘覚覡覦覧覩親覬覯覲観覺覽覿觀角觔觚觜觝解触觫觳觴觸觿言訂訃計訊訌討訓託記訛訝訟訢訣訥訪設許訳訴訶診註証詁詆詈詐詑詒詔評詛詞詠詡詢詣試詩詫詬詭詮詰話該詳詵詹詼誂誄誅誇誉誌認誑誓誕誘語誠誡誣誤誥誦誨説読誰課誹誼誾調諂諄談請諌諍諏諒論諗諚諛諜諡諢諤諦諧諫諭諮諱諳諶諷諸諺諾謀謁謂謄謇謌謎謐謔謖謗謙謚講謝謠謡謦謨謫謬謳謹謾譃證譌譎譏譔識譙譚譜譞譟警譫譬譯議譲譴護譽讀讃變讌讎讐讒讓讖讙讚讜谷谺谿豁豆豇豈豉豊豌豎豐豕豚象豨豪豫豬豳豸豹豺豼貂貅貉貌貎貔貘貝貞負財貢貧貨販貪貫責貮貯貰貲貴貶買貸費貼貽貿賀賁賂賃賄資賈賊賍賎賑賓賚賛賜賞賠賡賢賣賤賦質賭賴賺購賽贄贅贇贈贊贋贍贏贐贓贔贖贛赤赦赧赫赭走赳赴起趁超越趙趣趨足趺趾跋跌跎跏跑跖跚跛距跟跡跣跨跪跫路跳践跼踈踉踊踏踐踔踝踞踠踣踪踰踴踵蹂蹄蹇蹈蹉蹊蹋蹌蹐蹕蹙蹟蹠蹣蹤蹬蹰蹲蹴蹶蹼躁躄躅躇躊躋躍躑躓躔躙躡躪身躬躯躰躱躶躾軀軈車軋軌軍軒軛軟転軫軸軺軻軼軽軾較載輊輌輒輓輔輕輙輛輜輝輞輟輦輩輪輯輳輸輻輾輿轀轂轄轅轆轉轌轍轎轔轗轜轟轡轢轤辛辜辞辟辣辦辧辨辭辮辯辰辱農辶辷辺辻込辿迂迄迅迎近返迚迢迤迦迩迪迫迭迯述迴迷迸迹迺追退送适逃逅逆逈逋逍透逐逑逓途逕逗這通逝逞速造逡逢連逮週進逵逶逸逹逼遁遂遅遇遉遊運遍過遏遐遑道達違遙遜遞遠遡遣遥適遭遮遯遲遵遶遷選遺遼遽避邀邁邂邃還邅邇邈邉邊邏邑邕邗邙邢那邦邨邪邯邱邳邵邶邸邾郁郃郈郊郎郗郝郞郡郢部郭郯郵郷都郿鄂鄄鄒鄕鄘鄙鄧鄭鄯鄰鄲鄴鄽酈酉酊酋酌配酎酒酔酖酗酘酛酡酢酣酤酥酩酪酬酲酵酷酸醂醇醉醋醍醐醒醗醜醢醤醪醫醬醱醴醵醸醺釀釁采釈釉釋里重野量釐金釗釘釜針釟釡釣釦釧釭釵釼鈍鈎鈐鈑鈔鈕鈞鈴鈷鈸鈺鈿鉀鉄鉅鉇鉈鉉鉊鉋鉏鉗鉛鉞鉢鉤鉦鉧鉱鉾銀銃銅銈銍銑銓銕銖銘銙銚銛銜銭銷銹鋏鋒鋤鋧鋩鋪鋭鋲鋳鋸鋹鋺鋼錄錆錐錕錘錙錚錠錡錢錣錥錦錨錫錬錮錯録錵錺錻鍈鍋鍍鍑鍔鍛鍜鍬鍮鍵鍼鍾鎌鎔鎖鎗鎚鎛鎧鎬鎭鎮鎰鎹鏃鏈鏑鏖鏗鏘鏝鏞鏟鏡鏢鏤鏧鏨鏱鏻鏽鐃鐐鐓鐔鐗鐘鐙鐚鐡鐫鐵鐶鐸鐺鑁鑄鑅鑑鑒鑓鑚鑛鑞鑠鑢鑫鑰鑵鑷鑼鑽鑾鑿長镹門閂閃閇閉閊開閎閏閑閒間閔閖閘閙関閣閤閥閦閧閨閩閫閭閲閹閻閼閽閾闇闈闊闌闍闐闔闕闖闘關闡闢闥阜阡阨阪阮阯防阳阻阿陀陂附陋陌降限陖陘陛陜陝陞陟院陣除陥陪陬陰陲陳陵陶陷陸険陽隂隄隅隆隈隊隋隍階随隔隕隗隘隙際障隠隣隧隨險隱隲隴隷隸隹隺隻隼雀雁雄雅集雇雉雋雌雍雎雑雒雕雖雙雛雜雝雞離難雧雨雩雪雫雯雰雲零雷雹電需霄霆震霊霍霏霑霓霖霙霜霞霤霧霰露霸霹霽霾靁靂靄靆靈靉靏靑青靖静靚靛靜非靠靡面靤靨革靫靭靱靳靴靹靺靼鞁鞄鞅鞆鞋鞍鞏鞘鞚鞜鞠鞢鞣鞦鞨鞭鞮鞳鞴鞺韃韆韋韓韜韞韮音韴韵韶韻響頁頂頃項順須頊頌頎頏預頑頒頓頗領頚頞頡頣頤頫頬頭頰頴頷頸頻頼頽顆顋題額顎顏顓顔顕顗願顚顛類顥顧顫顬顯顰顱顳顴風颪颯颱颶颷颺颼飃飄飆飛飜食飡飢飩飫飭飮飯飲飴飼飽飾餃餅餉養餌餐餒餓餘餝餞餠餡餤館餬餮餼饂饅饉饋饌饐饑饒饕饗饜首馗馘香馞馥馨馬馭馮馳馴駁駄駅駆駈駐駑駒駕駘駛駝駟駢駭駮駱駸駻駿騁騅騎騏騒験騖騙騨騫騰騷騾驀驃驅驍驒驕驗驚驛驟驢驤驥驩驪骨骰骶骸骼髀髄髏髑髓體高髙髞髠髢髣髦髪髫髭髮髯髱髴髷髻鬂鬆鬐鬒鬘鬚鬟鬠鬢鬣鬧鬨鬩鬪鬮鬱鬲鬳鬻鬼魁魂魃魄魅魋魍魎魏魑魔魘魚魯魳魴魹鮃鮄鮎鮐鮑鮒鮓鮞鮟鮠鮧鮨鮪鮫鮭鮮鮴鮹鯆鯉鯊鯎鯒鯔鯖鯛鯡鯣鯥鯨鯯鯰鯱鯵鯷鯸鯺鰆鰈鰉鰊鰌鰍鰐鰒鰓鰕鰡鰣鰤鰥鰩鰭鰮鰯鰰鰱鰲鰶鰹鰺鰻鱁鱇鱈鱉鱏鱒鱗鱚鱠鱧鱮鱲鱶鱷鱸鳥鳧鳩鳬鳰鳳鳴鳶鴃鴇鴈鴉鴎鴒鴗鴛鴝鴟鴣鴦鴨鴫鴬鴻鴾鴿鵃鵄鵈鵐鵑鵒鵙鵜鵝鵞鵟鵠鵡鵤鵬鵯鵲鵺鵼鵾鶉鶏鶚鶩鶫鶯鶲鶴鶸鶺鶿鷀鷁鷄鷉鷓鷗鷙鷦鷧鷭鷯鷲鷸鷹鷺鷽鸊鸕鸚鸛鸝鸞鹵鹸鹹鹼鹽鹿麁麈麋麐麒麓麕麗麝麟麤麥麦麩麪麭麴麵麸麹麺麻麼麽麾麿黃黄黌黍黎黐黑黒黔默黙黛黜黝點黠黥黨黯黴黷黻黽鼇鼈鼎鼓鼕鼙鼠鼬鼯鼹鼻鼽鼾齅齊齋齎齒齕齝齟齡齢齣齦齧齪齬齲齶齷龍龐龔龕龗龜龝龠龢글한﨑﨟﹅﹆𠮟𡌛𡢳𢌞𢪸𥝱𧘔𩹉
|
@@ -0,0 +1,244 @@
|
|
1
|
+
from typing import List, Union
|
2
|
+
|
3
|
+
import cv2
|
4
|
+
import torch
|
5
|
+
import torchvision.transforms as T
|
6
|
+
from PIL import Image
|
7
|
+
from pydantic import conlist
|
8
|
+
|
9
|
+
from .base import BaseModelCatalog, BaseModule, BaseSchema
|
10
|
+
from .configs import TableStructureRecognizerRTDETRv2Config
|
11
|
+
from .layout_parser import filter_contained_rectangles_within_category
|
12
|
+
from .models import RTDETRv2
|
13
|
+
from .postprocessor import RTDETRPostProcessor
|
14
|
+
from .utils.misc import calc_intersection, filter_by_flag, is_contained
|
15
|
+
from .utils.visualizer import table_visualizer
|
16
|
+
|
17
|
+
|
18
|
+
class TableStructureRecognizerModelCatalog(BaseModelCatalog):
|
19
|
+
def __init__(self):
|
20
|
+
super().__init__()
|
21
|
+
self.register("rtdetrv2", TableStructureRecognizerRTDETRv2Config, RTDETRv2)
|
22
|
+
|
23
|
+
|
24
|
+
class TableCellSchema(BaseSchema):
|
25
|
+
col: int
|
26
|
+
row: int
|
27
|
+
col_span: int
|
28
|
+
row_span: int
|
29
|
+
box: conlist(int, min_length=4, max_length=4)
|
30
|
+
contents: Union[str, None]
|
31
|
+
|
32
|
+
|
33
|
+
class TableStructureRecognizerSchema(BaseSchema):
|
34
|
+
box: conlist(int, min_length=4, max_length=4)
|
35
|
+
n_row: int
|
36
|
+
n_col: int
|
37
|
+
cells: List[TableCellSchema]
|
38
|
+
order: int
|
39
|
+
|
40
|
+
|
41
|
+
def extract_cells(row_boxes, col_boxes):
|
42
|
+
cells = []
|
43
|
+
for i, row_box in enumerate(row_boxes):
|
44
|
+
for j, col_box in enumerate(col_boxes):
|
45
|
+
intersection = calc_intersection(row_box, col_box)
|
46
|
+
if intersection is None:
|
47
|
+
continue
|
48
|
+
|
49
|
+
cells.append(
|
50
|
+
{
|
51
|
+
"col": j + 1,
|
52
|
+
"row": i + 1,
|
53
|
+
"col_span": 1,
|
54
|
+
"row_span": 1,
|
55
|
+
"box": intersection,
|
56
|
+
"contents": None,
|
57
|
+
}
|
58
|
+
)
|
59
|
+
|
60
|
+
return cells
|
61
|
+
|
62
|
+
|
63
|
+
def filter_contained_cells_within_spancell(cells, span_boxes):
|
64
|
+
check_list = [True] * len(cells)
|
65
|
+
child_boxes = [[] for _ in range(len(span_boxes))]
|
66
|
+
for i, span_box in enumerate(span_boxes):
|
67
|
+
for j, sub_cell in enumerate(cells):
|
68
|
+
if is_contained(span_box, sub_cell["box"]):
|
69
|
+
check_list[j] = False
|
70
|
+
child_boxes[i].append(sub_cell)
|
71
|
+
|
72
|
+
cells = filter_by_flag(cells, check_list)
|
73
|
+
|
74
|
+
for i, span_box in enumerate(span_boxes):
|
75
|
+
child_box = child_boxes[i]
|
76
|
+
|
77
|
+
if len(child_box) == 0:
|
78
|
+
continue
|
79
|
+
|
80
|
+
row = min([box["row"] for box in child_box])
|
81
|
+
col = min([box["col"] for box in child_box])
|
82
|
+
row_span = max([box["row"] for box in child_box]) - row + 1
|
83
|
+
col_span = max([box["col"] for box in child_box]) - col + 1
|
84
|
+
|
85
|
+
span_box = list(map(int, span_box))
|
86
|
+
|
87
|
+
cells.append(
|
88
|
+
{
|
89
|
+
"col": col,
|
90
|
+
"row": row,
|
91
|
+
"col_span": col_span,
|
92
|
+
"row_span": row_span,
|
93
|
+
"box": span_box,
|
94
|
+
"contents": None,
|
95
|
+
}
|
96
|
+
)
|
97
|
+
|
98
|
+
cells = sorted(cells, key=lambda x: (x["row"], x["col"]))
|
99
|
+
return cells
|
100
|
+
|
101
|
+
|
102
|
+
class TableStructureRecognizer(BaseModule):
|
103
|
+
model_catalog = TableStructureRecognizerModelCatalog()
|
104
|
+
|
105
|
+
def __init__(
|
106
|
+
self,
|
107
|
+
model_name="rtdetrv2",
|
108
|
+
path_cfg=None,
|
109
|
+
device="cuda",
|
110
|
+
visualize=False,
|
111
|
+
from_pretrained=True,
|
112
|
+
):
|
113
|
+
super().__init__()
|
114
|
+
self.load_model(
|
115
|
+
model_name,
|
116
|
+
path_cfg,
|
117
|
+
from_pretrained=True,
|
118
|
+
)
|
119
|
+
self.device = device
|
120
|
+
self.visualize = visualize
|
121
|
+
|
122
|
+
self.model.eval()
|
123
|
+
self.model.to(self.device)
|
124
|
+
|
125
|
+
self.postprocessor = RTDETRPostProcessor(
|
126
|
+
num_classes=self._cfg.RTDETRTransformerv2.num_classes,
|
127
|
+
num_top_queries=self._cfg.RTDETRTransformerv2.num_queries,
|
128
|
+
)
|
129
|
+
|
130
|
+
self.transforms = T.Compose(
|
131
|
+
[
|
132
|
+
T.Resize(self._cfg.data.img_size),
|
133
|
+
T.ToTensor(),
|
134
|
+
]
|
135
|
+
)
|
136
|
+
|
137
|
+
self.thresh_score = self._cfg.thresh_score
|
138
|
+
|
139
|
+
self.label_mapper = {
|
140
|
+
id: category for id, category in enumerate(self._cfg.category)
|
141
|
+
}
|
142
|
+
|
143
|
+
def preprocess(self, img, boxes):
|
144
|
+
cv_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
145
|
+
|
146
|
+
table_imgs = []
|
147
|
+
for box in boxes:
|
148
|
+
x1, y1, x2, y2 = map(int, box)
|
149
|
+
table_img = cv_img[y1:y2, x1:x2, :]
|
150
|
+
th, hw = table_img.shape[:2]
|
151
|
+
table_img = Image.fromarray(table_img)
|
152
|
+
img_tensor = self.transforms(table_img)[None].to(self.device)
|
153
|
+
table_imgs.append(
|
154
|
+
{
|
155
|
+
"tensor": img_tensor,
|
156
|
+
"size": (th, hw),
|
157
|
+
"offset": (x1, y1),
|
158
|
+
}
|
159
|
+
)
|
160
|
+
return table_imgs
|
161
|
+
|
162
|
+
def postprocess(self, preds, data):
|
163
|
+
h, w = data["size"]
|
164
|
+
orig_size = torch.tensor([w, h])[None].to(self.device)
|
165
|
+
outputs = self.postprocessor(preds, orig_size, self.thresh_score)
|
166
|
+
|
167
|
+
preds = outputs[0]
|
168
|
+
scores = preds["scores"]
|
169
|
+
boxes = preds["boxes"]
|
170
|
+
labels = preds["labels"]
|
171
|
+
|
172
|
+
category_elements = {category: [] for category in self.label_mapper.values()}
|
173
|
+
for box, score, label in zip(boxes, scores, labels):
|
174
|
+
category = self.label_mapper[label.item()]
|
175
|
+
box = box.astype(int).tolist()
|
176
|
+
|
177
|
+
box[0] += data["offset"][0]
|
178
|
+
box[1] += data["offset"][1]
|
179
|
+
box[2] += data["offset"][0]
|
180
|
+
box[3] += data["offset"][1]
|
181
|
+
|
182
|
+
category_elements[category].append(
|
183
|
+
{
|
184
|
+
"box": box,
|
185
|
+
"score": float(score),
|
186
|
+
}
|
187
|
+
)
|
188
|
+
|
189
|
+
category_elements = filter_contained_rectangles_within_category(
|
190
|
+
category_elements
|
191
|
+
)
|
192
|
+
|
193
|
+
cells, n_row, n_col = self.extract_cell_elements(category_elements)
|
194
|
+
|
195
|
+
table_x, table_y = data["offset"]
|
196
|
+
table_x2 = table_x + data["size"][1]
|
197
|
+
table_y2 = table_y + data["size"][0]
|
198
|
+
table_box = [table_x, table_y, table_x2, table_y2]
|
199
|
+
|
200
|
+
table = {
|
201
|
+
"box": table_box,
|
202
|
+
"n_row": n_row,
|
203
|
+
"n_col": n_col,
|
204
|
+
"cells": cells,
|
205
|
+
"order": 0,
|
206
|
+
}
|
207
|
+
|
208
|
+
results = TableStructureRecognizerSchema(**table)
|
209
|
+
|
210
|
+
return results
|
211
|
+
|
212
|
+
def extract_cell_elements(self, elements):
|
213
|
+
row_boxes = [element["box"] for element in elements["row"]]
|
214
|
+
col_boxes = [element["box"] for element in elements["col"]]
|
215
|
+
span_boxes = [element["box"] for element in elements["span"]]
|
216
|
+
|
217
|
+
row_boxes = sorted(row_boxes, key=lambda x: x[1])
|
218
|
+
col_boxes = sorted(col_boxes, key=lambda x: x[0])
|
219
|
+
|
220
|
+
cells = extract_cells(row_boxes, col_boxes)
|
221
|
+
cells = filter_contained_cells_within_spancell(cells, span_boxes)
|
222
|
+
|
223
|
+
return cells, len(row_boxes), len(col_boxes)
|
224
|
+
|
225
|
+
def __call__(self, img, table_boxes, vis=None):
|
226
|
+
img_tensors = self.preprocess(img, table_boxes)
|
227
|
+
outputs = []
|
228
|
+
for data in img_tensors:
|
229
|
+
with torch.inference_mode():
|
230
|
+
pred = self.model(data["tensor"])
|
231
|
+
table = self.postprocess(pred, data)
|
232
|
+
outputs.append(table)
|
233
|
+
|
234
|
+
if vis is None and self.visualize:
|
235
|
+
vis = img.copy()
|
236
|
+
|
237
|
+
if self.visualize:
|
238
|
+
for table in outputs:
|
239
|
+
vis = table_visualizer(
|
240
|
+
vis,
|
241
|
+
table,
|
242
|
+
)
|
243
|
+
|
244
|
+
return outputs, vis
|