├── figures ├── CLL.jpg └── insights.jpg ├── demo_image ├── ARA.jpg ├── BEN.jpg ├── CHI.jpg ├── HIN.jpg ├── JPN.jpg ├── KOR.jpg └── LAT.jpg ├── requirements.txt ├── modules ├── sequence_modeling.py ├── prediction.py ├── transformation.py ├── feature_extraction.py └── svtr.py ├── LICENSE ├── .gitignore ├── model.py ├── demo.py ├── utils.py ├── README.md ├── dataset.py ├── test.py ├── train.py └── charset └── MLT19_charset.txt /figures/CLL.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ku21fan/CLL-STR/HEAD/figures/CLL.jpg -------------------------------------------------------------------------------- /demo_image/ARA.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ku21fan/CLL-STR/HEAD/demo_image/ARA.jpg -------------------------------------------------------------------------------- /demo_image/BEN.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ku21fan/CLL-STR/HEAD/demo_image/BEN.jpg -------------------------------------------------------------------------------- /demo_image/CHI.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ku21fan/CLL-STR/HEAD/demo_image/CHI.jpg -------------------------------------------------------------------------------- /demo_image/HIN.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ku21fan/CLL-STR/HEAD/demo_image/HIN.jpg -------------------------------------------------------------------------------- /demo_image/JPN.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ku21fan/CLL-STR/HEAD/demo_image/JPN.jpg -------------------------------------------------------------------------------- /demo_image/KOR.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ku21fan/CLL-STR/HEAD/demo_image/KOR.jpg -------------------------------------------------------------------------------- /demo_image/LAT.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ku21fan/CLL-STR/HEAD/demo_image/LAT.jpg -------------------------------------------------------------------------------- /figures/insights.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ku21fan/CLL-STR/HEAD/figures/insights.jpg -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | natsort 2 | lmdb 3 | pillow 4 | nltk 5 | natsort 6 | fire 7 | tensorboard==2.9.0 8 | tqdm 9 | opencv-python 10 | zhconv 11 | setuptools==65.5.1 12 | protobuf==3.20.2 13 | timm 14 | -------------------------------------------------------------------------------- /modules/sequence_modeling.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | 3 | 4 | class BidirectionalLSTM(nn.Module): 5 | 6 | def __init__(self, input_size, hidden_size, output_size): 7 | super(BidirectionalLSTM, self).__init__() 8 | self.rnn = nn.LSTM(input_size, hidden_size, bidirectional=True, batch_first=True) 9 | self.linear = nn.Linear(hidden_size * 2, output_size) 10 | 11 | def forward(self, input): 12 | """ 13 | input : visual feature [batch_size x T x input_size], T = num_steps. 14 | output : contextual feature [batch_size x T x output_size] 15 | """ 16 | self.rnn.flatten_parameters() 17 | recurrent, _ = self.rnn(input) # batch_size x T x input_size -> batch_size x T x (2*hidden_size) 18 | output = self.linear(recurrent) # batch_size x T x output_size 19 | return output 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jeonghun Baek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/tensorboard/* 2 | **/saved_models/* 3 | **/result/* 4 | **/data/* 5 | *.mdb 6 | *.pth 7 | *.tar 8 | *.txt 9 | *.ipynb 10 | *.zip 11 | *.eps 12 | *.pdf 13 | 14 | ### Linux ### 15 | *~ 16 | 17 | # temporary files which can be created if a process still has a handle open of a deleted file 18 | .fuse_hidden* 19 | 20 | # KDE directory preferences 21 | .directory 22 | 23 | # Linux trash folder which might appear on any partition or disk 24 | .Trash-* 25 | 26 | # .nfs files are created when an open file is removed but is still being accessed 27 | .nfs* 28 | 29 | ### OSX ### 30 | # General 31 | .DS_Store 32 | .AppleDouble 33 | .LSOverride 34 | 35 | # Icon must end with two \r 36 | Icon 37 | 38 | # Thumbnails 39 | ._* 40 | 41 | # Files that might appear in the root of a volume 42 | .DocumentRevisions-V100 43 | .fseventsd 44 | .Spotlight-V100 45 | .TemporaryItems 46 | .Trashes 47 | .VolumeIcon.icns 48 | .com.apple.timemachine.donotpresent 49 | 50 | # Directories potentially created on remote AFP share 51 | .AppleDB 52 | .AppleDesktop 53 | Network Trash Folder 54 | Temporary Items 55 | .apdisk 56 | 57 | ### Python ### 58 | # Byte-compiled / optimized / DLL files 59 | __pycache__/ 60 | *.py[cod] 61 | *$py.class 62 | 63 | # C extensions 64 | *.so 65 | 66 | # Distribution / packaging 67 | .Python 68 | build/ 69 | develop-eggs/ 70 | dist/ 71 | downloads/ 72 | eggs/ 73 | .eggs/ 74 | lib/ 75 | lib64/ 76 | parts/ 77 | sdist/ 78 | var/ 79 | wheels/ 80 | *.egg-info/ 81 | .installed.cfg 82 | *.egg 83 | MANIFEST 84 | 85 | # PyInstaller 86 | # Usually these files are written by a python script from a template 87 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 88 | *.manifest 89 | *.spec 90 | 91 | # Installer logs 92 | pip-log.txt 93 | pip-delete-this-directory.txt 94 | 95 | # Unit test / coverage reports 96 | htmlcov/ 97 | .tox/ 98 | .coverage 99 | .coverage.* 100 | .cache 101 | nosetests.xml 102 | coverage.xml 103 | *.cover 104 | .hypothesis/ 105 | .pytest_cache/ 106 | 107 | # Translations 108 | *.mo 109 | *.pot 110 | 111 | # Django stuff: 112 | *.log 113 | local_settings.py 114 | db.sqlite3 115 | 116 | # Flask stuff: 117 | instance/ 118 | .webassets-cache 119 | 120 | # Scrapy stuff: 121 | .scrapy 122 | 123 | # Sphinx documentation 124 | docs/_build/ 125 | 126 | # PyBuilder 127 | target/ 128 | 129 | # Jupyter Notebook 130 | .ipynb_checkpoints 131 | 132 | # IPython 133 | profile_default/ 134 | ipython_config.py 135 | 136 | # pyenv 137 | .python-version 138 | 139 | # celery beat schedule file 140 | celerybeat-schedule 141 | 142 | # SageMath parsed files 143 | *.sage.py 144 | 145 | # Environments 146 | .env 147 | .venv 148 | env/ 149 | venv/ 150 | ENV/ 151 | env.bak/ 152 | venv.bak/ 153 | 154 | # Spyder project settings 155 | .spyderproject 156 | .spyproject 157 | 158 | # Rope project settings 159 | .ropeproject 160 | 161 | # mkdocs documentation 162 | /site 163 | 164 | # mypy 165 | .mypy_cache/ 166 | .dmypy.json 167 | dmypy.json 168 | 169 | ### Python Patch ### 170 | .venv/ 171 | 172 | ### Python.VirtualEnv Stack ### 173 | # Virtualenv 174 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 175 | [Bb]in 176 | [Ii]nclude 177 | [Ll]ib 178 | [Ll]ib64 179 | [Ll]ocal 180 | [Ss]cripts 181 | pyvenv.cfg 182 | pip-selfcheck.json 183 | 184 | ### Windows ### 185 | # Windows thumbnail cache files 186 | Thumbs.db 187 | ehthumbs.db 188 | ehthumbs_vista.db 189 | 190 | # Dump file 191 | *.stackdump 192 | 193 | # Folder config file 194 | [Dd]esktop.ini 195 | 196 | # Recycle Bin used on file shares 197 | $RECYCLE.BIN/ 198 | 199 | # Windows Installer files 200 | *.cab 201 | *.msi 202 | *.msix 203 | *.msm 204 | *.msp 205 | 206 | # Windows shortcuts 207 | *.lnk 208 | -------------------------------------------------------------------------------- /modules/prediction.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 6 | 7 | 8 | class Attention(nn.Module): 9 | def __init__(self, input_size, hidden_size, num_class, num_char_embeddings=256): 10 | super(Attention, self).__init__() 11 | self.attention_cell = AttentionCell( 12 | input_size, hidden_size, num_char_embeddings 13 | ) 14 | self.hidden_size = hidden_size 15 | self.num_class = num_class 16 | self.generator = nn.Linear(hidden_size, num_class) 17 | self.char_embeddings = nn.Embedding(num_class, num_char_embeddings) 18 | 19 | def forward(self, batch_H, text, is_train=True, batch_max_length=25): 20 | """ 21 | input: 22 | batch_H : contextual_feature H = hidden state of encoder. [batch_size x num_steps x contextual_feature_channels] 23 | text : the text-index of each image. [batch_size x (max_length+1)]. +1 for [SOS] token. text[:, 0] = [SOS]. 24 | output: probability distribution at each step [batch_size x num_steps x num_class] 25 | """ 26 | batch_size = batch_H.size(0) 27 | num_steps = batch_max_length + 1 # +1 for [EOS] at end of sentence. 28 | 29 | output_hiddens = ( 30 | torch.FloatTensor(batch_size, num_steps, self.hidden_size) 31 | .fill_(0) 32 | .to(device) 33 | ) 34 | hidden = ( 35 | torch.FloatTensor(batch_size, self.hidden_size).fill_(0).to(device), 36 | torch.FloatTensor(batch_size, self.hidden_size).fill_(0).to(device), 37 | ) 38 | 39 | if is_train: 40 | for i in range(num_steps): 41 | char_embeddings = self.char_embeddings(text[:, i]) 42 | # hidden : decoder's hidden s_{t-1}, batch_H : encoder's hidden H, char_embeddings : f(y_{t-1}) 43 | hidden, alpha = self.attention_cell(hidden, batch_H, char_embeddings) 44 | output_hiddens[:, i, :] = hidden[ 45 | 0 46 | ] # LSTM hidden index (0: hidden, 1: Cell) 47 | probs = self.generator(output_hiddens) 48 | 49 | else: 50 | targets = text[0].expand(batch_size) # should be fill with [SOS] token 51 | probs = ( 52 | torch.FloatTensor(batch_size, num_steps, self.num_class) 53 | .fill_(0) 54 | .to(device) 55 | ) 56 | 57 | for i in range(num_steps): 58 | char_embeddings = self.char_embeddings(targets) 59 | hidden, alpha = self.attention_cell(hidden, batch_H, char_embeddings) 60 | probs_step = self.generator(hidden[0]) 61 | probs[:, i, :] = probs_step 62 | _, next_input = probs_step.max(1) 63 | targets = next_input 64 | 65 | return probs # batch_size x num_steps x num_class 66 | 67 | 68 | class AttentionCell(nn.Module): 69 | def __init__(self, input_size, hidden_size, num_embeddings): 70 | super(AttentionCell, self).__init__() 71 | self.i2h = nn.Linear(input_size, hidden_size, bias=False) 72 | self.h2h = nn.Linear( 73 | hidden_size, hidden_size 74 | ) # either i2i or h2h should have bias 75 | self.score = nn.Linear(hidden_size, 1, bias=False) 76 | self.rnn = nn.LSTMCell(input_size + num_embeddings, hidden_size) 77 | self.hidden_size = hidden_size 78 | 79 | def forward(self, prev_hidden, batch_H, char_embeddings): 80 | # [batch_size x num_encoder_step x num_channel] -> [batch_size x num_encoder_step x hidden_size] 81 | batch_H_proj = self.i2h(batch_H) 82 | prev_hidden_proj = self.h2h(prev_hidden[0]).unsqueeze(1) 83 | e = self.score( 84 | torch.tanh(batch_H_proj + prev_hidden_proj) 85 | ) # batch_size x num_encoder_step * 1 86 | 87 | alpha = F.softmax(e, dim=1) 88 | context = torch.bmm(alpha.permute(0, 2, 1), batch_H).squeeze( 89 | 1 90 | ) # batch_size x num_channel 91 | concat_context = torch.cat( 92 | [context, char_embeddings], 1 93 | ) # batch_size x (num_channel + num_embedding) 94 | cur_hidden = self.rnn(concat_context, prev_hidden) 95 | return cur_hidden, alpha 96 | -------------------------------------------------------------------------------- /model.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | from modules.transformation import TPS_SpatialTransformerNetwork 5 | from modules.feature_extraction import ( 6 | VGG_FeatureExtractor, 7 | RCNN_FeatureExtractor, 8 | ResNet_FeatureExtractor, 9 | SVTR_FeatureExtractor, 10 | ) 11 | from modules.sequence_modeling import BidirectionalLSTM 12 | from modules.prediction import Attention 13 | 14 | 15 | class Model(nn.Module): 16 | def __init__(self, opt): 17 | super(Model, self).__init__() 18 | self.opt = opt 19 | self.stages = { 20 | "Trans": opt.Transformation, 21 | "Feat": opt.FeatureExtraction, 22 | "Seq": opt.SequenceModeling, 23 | "Pred": opt.Prediction, 24 | } 25 | 26 | """ Transformation """ 27 | if opt.Transformation == "TPS": 28 | self.Transformation = TPS_SpatialTransformerNetwork( 29 | F=opt.num_fiducial, 30 | I_size=(opt.imgH, opt.imgW), 31 | I_r_size=(opt.imgH, opt.imgW), 32 | I_channel_num=opt.input_channel, 33 | ) 34 | else: 35 | print("No Transformation module specified") 36 | 37 | """ FeatureExtraction """ 38 | if opt.FeatureExtraction == "VGG": 39 | self.FeatureExtraction = VGG_FeatureExtractor( 40 | opt.input_channel, opt.output_channel 41 | ) 42 | elif opt.FeatureExtraction == "RCNN": 43 | self.FeatureExtraction = RCNN_FeatureExtractor( 44 | opt.input_channel, opt.output_channel 45 | ) 46 | elif opt.FeatureExtraction == "ResNet": 47 | self.FeatureExtraction = ResNet_FeatureExtractor( 48 | opt.input_channel, opt.output_channel 49 | ) 50 | elif opt.FeatureExtraction == "SVTR": 51 | self.FeatureExtraction = SVTR_FeatureExtractor( 52 | opt.input_channel, opt.output_channel 53 | ) 54 | 55 | else: 56 | raise Exception("No FeatureExtraction module specified") 57 | 58 | self.FeatureExtraction_output = opt.output_channel 59 | self.AdaptiveAvgPool = nn.AdaptiveAvgPool2d( 60 | (None, 1) 61 | ) # Transform final (imgH/16-1) -> 1 62 | 63 | """ Sequence modeling""" 64 | if opt.SequenceModeling == "BiLSTM": 65 | self.SequenceModeling = nn.Sequential( 66 | BidirectionalLSTM( 67 | self.FeatureExtraction_output, opt.hidden_size, opt.hidden_size 68 | ), 69 | BidirectionalLSTM(opt.hidden_size, opt.hidden_size, opt.hidden_size), 70 | ) 71 | self.SequenceModeling_output = opt.hidden_size 72 | else: 73 | print("No SequenceModeling module specified") 74 | self.SequenceModeling_output = self.FeatureExtraction_output 75 | 76 | """ Prediction """ 77 | if opt.Prediction == "CTC": 78 | self.Prediction = nn.Linear(self.SequenceModeling_output, opt.num_class) 79 | elif opt.Prediction == "Attn": 80 | self.Prediction = Attention( 81 | self.SequenceModeling_output, opt.hidden_size, opt.num_class 82 | ) 83 | else: 84 | raise Exception("Prediction is neither CTC or Attn") 85 | 86 | def forward(self, image, text=None, is_train=True): 87 | """ Transformation stage """ 88 | if not self.stages["Trans"] == "None": 89 | image = self.Transformation(image) 90 | 91 | """ Feature extraction stage """ 92 | visual_feature = self.FeatureExtraction(image) 93 | visual_feature = visual_feature.permute( 94 | 0, 3, 1, 2 95 | ) # [b, c, h, w] -> [b, w, c, h] 96 | visual_feature = self.AdaptiveAvgPool( 97 | visual_feature 98 | ) # [b, w, c, h] -> [b, w, c, 1] 99 | visual_feature = visual_feature.squeeze(3) # [b, w, c, 1] -> [b, w, c] 100 | 101 | """ Sequence modeling stage """ 102 | if self.stages["Seq"] == "BiLSTM": 103 | contextual_feature = self.SequenceModeling( 104 | visual_feature 105 | ) # [b, num_steps, opt.hidden_size] 106 | else: 107 | contextual_feature = visual_feature # for convenience. this is NOT contextually modeled by BiLSTM 108 | 109 | """ Prediction stage """ 110 | if self.stages["Pred"] == "CTC": 111 | prediction = self.Prediction(contextual_feature.contiguous()) 112 | else: 113 | prediction = self.Prediction( 114 | contextual_feature.contiguous(), 115 | text, 116 | is_train, 117 | batch_max_length=self.opt.batch_max_length, 118 | ) 119 | 120 | return prediction # [b, num_steps, opt.num_class] 121 | -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import string 3 | import argparse 4 | 5 | import torch 6 | import torch.backends.cudnn as cudnn 7 | import torch.utils.data 8 | import torch.nn.functional as F 9 | 10 | from utils import CTCLabelConverter, AttnLabelConverter 11 | from dataset import RawDataset, AlignCollate 12 | from model import Model 13 | 14 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 15 | 16 | 17 | def demo(opt): 18 | """ model configuration """ 19 | if "CTC" in opt.Prediction: 20 | converter = CTCLabelConverter(opt) 21 | else: 22 | converter = AttnLabelConverter(opt) 23 | opt.sos_token_index = converter.dict["[SOS]"] 24 | opt.eos_token_index = converter.dict["[EOS]"] 25 | opt.num_class = len(converter.character) 26 | 27 | model = Model(opt) 28 | print( 29 | "model input parameters", 30 | opt.imgH, 31 | opt.imgW, 32 | opt.num_fiducial, 33 | opt.input_channel, 34 | opt.output_channel, 35 | opt.hidden_size, 36 | opt.num_class, 37 | opt.batch_max_length, 38 | opt.Transformation, 39 | opt.FeatureExtraction, 40 | opt.SequenceModeling, 41 | opt.Prediction, 42 | ) 43 | model = torch.nn.DataParallel(model).to(device) 44 | 45 | # load model 46 | print("loading pretrained model from %s" % opt.saved_model) 47 | model.load_state_dict(torch.load(opt.saved_model, map_location=device)) 48 | 49 | # prepare data. two demo images from https://github.com/bgshih/crnn#run-demo 50 | AlignCollate_demo = AlignCollate(opt, mode="test") 51 | demo_data = RawDataset(root=opt.image_folder, opt=opt) # use RawDataset 52 | demo_loader = torch.utils.data.DataLoader( 53 | demo_data, 54 | batch_size=opt.batch_size, 55 | shuffle=False, 56 | num_workers=int(opt.workers), 57 | collate_fn=AlignCollate_demo, 58 | pin_memory=True, 59 | ) 60 | 61 | # predict 62 | model.eval() 63 | with torch.no_grad(): 64 | log = open(f"./log_demo_result.txt", "w") 65 | for image_tensors, image_path_list in demo_loader: 66 | batch_size = image_tensors.size(0) 67 | image = image_tensors.to(device) 68 | 69 | if "CTC" in opt.Prediction: 70 | preds = model(image) 71 | else: 72 | # For max length prediction 73 | text_for_pred = ( 74 | torch.LongTensor(batch_size) 75 | .fill_(converter.dict["[SOS]"]) 76 | .to(device) 77 | ) 78 | preds = model(image, text_for_pred, is_train=False) 79 | 80 | # Select max probabilty (greedy decoding) then decode index to character 81 | preds_size = torch.IntTensor([preds.size(1)] * batch_size).to(device) 82 | _, preds_index = preds.max(2) 83 | preds_str = converter.decode(preds_index, preds_size) 84 | 85 | dashed_line = "-" * 80 86 | head = f'{"image_path":25s}\t{"predicted_labels":25s}\tconfidence score' 87 | 88 | print(f"{dashed_line}\n{head}\n{dashed_line}") 89 | log.write(f"{dashed_line}\n{head}\n{dashed_line}\n") 90 | 91 | preds_prob = F.softmax(preds, dim=2) 92 | preds_max_prob, _ = preds_prob.max(dim=2) 93 | for img_name, pred, pred_max_prob in zip( 94 | image_path_list, preds_str, preds_max_prob 95 | ): 96 | if "Attn" in opt.Prediction: 97 | pred_EOS = pred.find("[EOS]") 98 | pred = pred[:pred_EOS] # prune after "end of sentence" token ([s]) 99 | pred_max_prob = pred_max_prob[:pred_EOS] 100 | 101 | # calculate confidence score (= multiply of pred_max_prob) 102 | try: 103 | confidence_score = pred_max_prob.cumprod(dim=0)[-1] 104 | except: 105 | confidence_score = 0 106 | 107 | print(f"{img_name:25s}\t{pred:25s}\t{confidence_score:0.4f}") 108 | log.write(f"{img_name:25s}\t{pred:25s}\t{confidence_score:0.4f}\n") 109 | 110 | log.write("\n") 111 | log.close() 112 | 113 | 114 | if __name__ == "__main__": 115 | parser = argparse.ArgumentParser() 116 | parser.add_argument( 117 | "--image_folder", 118 | required=True, 119 | help="path to image_folder which contains text images", 120 | ) 121 | parser.add_argument( 122 | "--workers", type=int, help="number of data loading workers", default=4 123 | ) 124 | parser.add_argument("--batch_size", type=int, default=192, help="input batch size") 125 | parser.add_argument( 126 | "--saved_model", required=True, help="path to saved_model to evaluation" 127 | ) 128 | """ Data processing """ 129 | parser.add_argument( 130 | "--batch_max_length", type=int, default=25, help="maximum-label-length" 131 | ) 132 | parser.add_argument( 133 | "--imgH", type=int, default=32, help="the height of the input image" 134 | ) 135 | parser.add_argument( 136 | "--imgW", type=int, default=100, help="the width of the input image" 137 | ) 138 | """ Model Architecture """ 139 | parser.add_argument("--model_name", type=str, required=True, help="CRNN|SVTR") 140 | parser.add_argument( 141 | "--num_fiducial", 142 | type=int, 143 | default=20, 144 | help="number of fiducial points of TPS-STN", 145 | ) 146 | parser.add_argument( 147 | "--input_channel", 148 | type=int, 149 | default=3, 150 | help="the number of input channel of Feature extractor", 151 | ) 152 | parser.add_argument( 153 | "--output_channel", 154 | type=int, 155 | default=512, 156 | help="the number of output channel of Feature extractor", 157 | ) 158 | parser.add_argument( 159 | "--hidden_size", type=int, default=256, help="the size of the LSTM hidden state" 160 | ) 161 | 162 | """ Charcomb """ 163 | parser.add_argument( 164 | "--PAD", 165 | action="store_true", 166 | help="whether to keep ratio then pad for image resize", 167 | ) 168 | parser.add_argument( 169 | "--Aug", 170 | type=str, 171 | default="None", 172 | help="whether to use augmentation |None|Crop|Rot|", 173 | ) 174 | 175 | opt = parser.parse_args() 176 | 177 | with open(f"charset/MLT19_charset.txt", "r", encoding="utf-8") as file: 178 | opt.character = file.read().splitlines() 179 | 180 | cudnn.benchmark = True 181 | cudnn.deterministic = True 182 | opt.num_gpu = torch.cuda.device_count() 183 | 184 | # for convenience 185 | if opt.model_name == "CRNN": # CRNN 186 | opt.Transformation = "None" 187 | opt.FeatureExtraction = "VGG" 188 | opt.SequenceModeling = "BiLSTM" 189 | opt.Prediction = "CTC" 190 | 191 | elif opt.model_name == "SVTR": # SVTR 192 | opt.Transformation = "None" 193 | opt.FeatureExtraction = "SVTR" 194 | opt.SequenceModeling = "None" 195 | opt.Prediction = "CTC" 196 | 197 | if opt.num_gpu > 1: 198 | print( 199 | "For lab setting, check your GPU number, you should be missed CUDA_VISIBLE_DEVICES=0 or typo" 200 | ) 201 | sys.exit() 202 | 203 | demo(opt) 204 | -------------------------------------------------------------------------------- /modules/transformation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | import torch.nn as nn 4 | import torch.nn.functional as F 5 | device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') 6 | 7 | 8 | class TPS_SpatialTransformerNetwork(nn.Module): 9 | """ Rectification Network of RARE, namely TPS based STN """ 10 | 11 | def __init__(self, F, I_size, I_r_size, I_channel_num=1): 12 | """ Based on RARE TPS 13 | input: 14 | batch_I: Batch Input Image [batch_size x I_channel_num x I_height x I_width] 15 | I_size : (height, width) of the input image I 16 | I_r_size : (height, width) of the rectified image I_r 17 | I_channel_num : the number of channels of the input image I 18 | output: 19 | batch_I_r: rectified image [batch_size x I_channel_num x I_r_height x I_r_width] 20 | """ 21 | super(TPS_SpatialTransformerNetwork, self).__init__() 22 | self.F = F 23 | self.I_size = I_size 24 | self.I_r_size = I_r_size # = (I_r_height, I_r_width) 25 | self.I_channel_num = I_channel_num 26 | self.LocalizationNetwork = LocalizationNetwork(self.F, self.I_channel_num) 27 | self.GridGenerator = GridGenerator(self.F, self.I_r_size) 28 | 29 | def forward(self, batch_I): 30 | batch_C_prime = self.LocalizationNetwork(batch_I) # batch_size x K x 2 31 | # batch_size x n (= I_r_width x I_r_height) x 2 32 | build_P_prime = self.GridGenerator.build_P_prime(batch_C_prime) 33 | build_P_prime_reshape = build_P_prime.reshape([build_P_prime.size(0), self.I_r_size[0], self.I_r_size[1], 2]) 34 | 35 | if torch.__version__ > "1.2.0": 36 | batch_I_r = F.grid_sample(batch_I, build_P_prime_reshape, padding_mode='border', align_corners=True) 37 | else: 38 | batch_I_r = F.grid_sample(batch_I, build_P_prime_reshape, padding_mode='border') 39 | 40 | return batch_I_r 41 | 42 | 43 | class LocalizationNetwork(nn.Module): 44 | """ Localization Network of RARE, which predicts C' (K x 2) from I (I_width x I_height) """ 45 | 46 | def __init__(self, F, I_channel_num): 47 | super(LocalizationNetwork, self).__init__() 48 | self.F = F 49 | self.I_channel_num = I_channel_num 50 | self.conv = nn.Sequential( 51 | nn.Conv2d(in_channels=self.I_channel_num, out_channels=64, kernel_size=3, stride=1, padding=1, 52 | bias=False), nn.BatchNorm2d(64), nn.ReLU(True), 53 | nn.MaxPool2d(2, 2), # batch_size x 64 x I_height/2 x I_width/2 54 | nn.Conv2d(64, 128, 3, 1, 1, bias=False), nn.BatchNorm2d(128), nn.ReLU(True), 55 | nn.MaxPool2d(2, 2), # batch_size x 128 x I_height/4 x I_width/4 56 | nn.Conv2d(128, 256, 3, 1, 1, bias=False), nn.BatchNorm2d(256), nn.ReLU(True), 57 | nn.MaxPool2d(2, 2), # batch_size x 256 x I_height/8 x I_width/8 58 | nn.Conv2d(256, 512, 3, 1, 1, bias=False), nn.BatchNorm2d(512), nn.ReLU(True), 59 | nn.AdaptiveAvgPool2d(1) # batch_size x 512 60 | ) 61 | 62 | self.localization_fc1 = nn.Sequential(nn.Linear(512, 256), nn.ReLU(True)) 63 | self.localization_fc2 = nn.Linear(256, self.F * 2) 64 | 65 | # Init fc2 in LocalizationNetwork 66 | self.localization_fc2.weight.data.fill_(0) 67 | """ see RARE paper Fig. 6 (a) """ 68 | ctrl_pts_x = np.linspace(-1.0, 1.0, int(F / 2)) 69 | ctrl_pts_y_top = np.linspace(0.0, -1.0, num=int(F / 2)) 70 | ctrl_pts_y_bottom = np.linspace(1.0, 0.0, num=int(F / 2)) 71 | ctrl_pts_top = np.stack([ctrl_pts_x, ctrl_pts_y_top], axis=1) 72 | ctrl_pts_bottom = np.stack([ctrl_pts_x, ctrl_pts_y_bottom], axis=1) 73 | initial_bias = np.concatenate([ctrl_pts_top, ctrl_pts_bottom], axis=0) 74 | self.localization_fc2.bias.data = torch.from_numpy(initial_bias).float().view(-1) 75 | 76 | def forward(self, batch_I): 77 | """ 78 | input: batch_I : Batch Input Image [batch_size x I_channel_num x I_height x I_width] 79 | output: batch_C_prime : Predicted coordinates of fiducial points for input batch [batch_size x F x 2] 80 | """ 81 | batch_size = batch_I.size(0) 82 | features = self.conv(batch_I).view(batch_size, -1) 83 | batch_C_prime = self.localization_fc2(self.localization_fc1(features)).view(batch_size, self.F, 2) 84 | return batch_C_prime 85 | 86 | 87 | class GridGenerator(nn.Module): 88 | """ Grid Generator of RARE, which produces P_prime by multipling T with P """ 89 | 90 | def __init__(self, F, I_r_size): 91 | """ Generate P_hat and inv_delta_C for later """ 92 | super(GridGenerator, self).__init__() 93 | self.eps = 1e-6 94 | self.I_r_height, self.I_r_width = I_r_size 95 | self.F = F 96 | self.C = self._build_C(self.F) # F x 2 97 | self.P = self._build_P(self.I_r_width, self.I_r_height) 98 | 99 | num_gpu = torch.cuda.device_count() 100 | if num_gpu > 1: 101 | # for multi-gpu, you may need register buffer 102 | self.register_buffer("inv_delta_C", torch.tensor( 103 | self._build_inv_delta_C(self.F, self.C)).float()) # F+3 x F+3 104 | self.register_buffer("P_hat", torch.tensor(self._build_P_hat(self.F, self.C, self.P)).float()) # n x F+3 105 | else: 106 | # for fine-tuning with different image width, you may use below instead of self.register_buffer 107 | self.inv_delta_C = torch.tensor(self._build_inv_delta_C(self.F, self.C)).float().to(device) # F+3 x F+3 108 | self.P_hat = torch.tensor(self._build_P_hat(self.F, self.C, self.P)).float().to(device) # n x F+3 109 | 110 | def _build_C(self, F): 111 | """ Return coordinates of fiducial points in I_r; C """ 112 | ctrl_pts_x = np.linspace(-1.0, 1.0, int(F / 2)) 113 | ctrl_pts_y_top = -1 * np.ones(int(F / 2)) 114 | ctrl_pts_y_bottom = np.ones(int(F / 2)) 115 | ctrl_pts_top = np.stack([ctrl_pts_x, ctrl_pts_y_top], axis=1) 116 | ctrl_pts_bottom = np.stack([ctrl_pts_x, ctrl_pts_y_bottom], axis=1) 117 | C = np.concatenate([ctrl_pts_top, ctrl_pts_bottom], axis=0) 118 | return C # F x 2 119 | 120 | def _build_inv_delta_C(self, F, C): 121 | """ Return inv_delta_C which is needed to calculate T """ 122 | hat_C = np.zeros((F, F), dtype=float) # F x F 123 | for i in range(0, F): 124 | for j in range(i, F): 125 | r = np.linalg.norm(C[i] - C[j]) 126 | hat_C[i, j] = r 127 | hat_C[j, i] = r 128 | np.fill_diagonal(hat_C, 1) 129 | hat_C = (hat_C ** 2) * np.log(hat_C) 130 | # print(C.shape, hat_C.shape) 131 | delta_C = np.concatenate( # F+3 x F+3 132 | [ 133 | np.concatenate([np.ones((F, 1)), C, hat_C], axis=1), # F x F+3 134 | np.concatenate([np.zeros((2, 3)), np.transpose(C)], axis=1), # 2 x F+3 135 | np.concatenate([np.zeros((1, 3)), np.ones((1, F))], axis=1) # 1 x F+3 136 | ], 137 | axis=0 138 | ) 139 | inv_delta_C = np.linalg.inv(delta_C) 140 | return inv_delta_C # F+3 x F+3 141 | 142 | def _build_P(self, I_r_width, I_r_height): 143 | I_r_grid_x = (np.arange(-I_r_width, I_r_width, 2) + 1.0) / I_r_width # self.I_r_width 144 | I_r_grid_y = (np.arange(-I_r_height, I_r_height, 2) + 1.0) / I_r_height # self.I_r_height 145 | P = np.stack( # self.I_r_width x self.I_r_height x 2 146 | np.meshgrid(I_r_grid_x, I_r_grid_y), 147 | axis=2 148 | ) 149 | return P.reshape([-1, 2]) # n (= self.I_r_width x self.I_r_height) x 2 150 | 151 | def _build_P_hat(self, F, C, P): 152 | n = P.shape[0] # n (= self.I_r_width x self.I_r_height) 153 | P_tile = np.tile(np.expand_dims(P, axis=1), (1, F, 1)) # n x 2 -> n x 1 x 2 -> n x F x 2 154 | C_tile = np.expand_dims(C, axis=0) # 1 x F x 2 155 | P_diff = P_tile - C_tile # n x F x 2 156 | rbf_norm = np.linalg.norm(P_diff, ord=2, axis=2, keepdims=False) # n x F 157 | rbf = np.multiply(np.square(rbf_norm), np.log(rbf_norm + self.eps)) # n x F 158 | P_hat = np.concatenate([np.ones((n, 1)), P, rbf], axis=1) 159 | return P_hat # n x F+3 160 | 161 | def build_P_prime(self, batch_C_prime): 162 | """ Generate Grid from batch_C_prime [batch_size x F x 2] """ 163 | batch_size = batch_C_prime.size(0) 164 | batch_inv_delta_C = self.inv_delta_C.repeat(batch_size, 1, 1) 165 | batch_P_hat = self.P_hat.repeat(batch_size, 1, 1) 166 | batch_C_prime_with_zeros = torch.cat((batch_C_prime, torch.zeros( 167 | batch_size, 3, 2).float().to(device)), dim=1) # batch_size x F+3 x 2 168 | batch_T = torch.bmm(batch_inv_delta_C, batch_C_prime_with_zeros) # batch_size x F+3 x 2 169 | batch_P_prime = torch.bmm(batch_P_hat, batch_T) # batch_size x n x 2 170 | return batch_P_prime # batch_size x n x 2 171 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import regex 4 | import PIL 5 | import numpy as np 6 | import torch 7 | 8 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 9 | 10 | 11 | class CTCLabelConverter(object): 12 | """ Convert between text-label and text-index """ 13 | 14 | def __init__(self, opt): 15 | 16 | # character (str): set of the possible characters. 17 | list_special_token = [ 18 | "[PAD]", 19 | "[UNK]", 20 | " ", 21 | ] # [UNK] for unknown character, ' ' for space. 22 | # list_character = list(character) 23 | list_character = ( 24 | opt.character # we give list of character as a default. (especially for Hindi) 25 | ) 26 | dict_character = list_special_token + list_character 27 | 28 | self.dict = {} 29 | for i, char in enumerate(dict_character): 30 | # NOTE: 0 is reserved for 'CTCblank' token required by CTCLoss, not same with space ' '. 31 | # print(i, char) 32 | self.dict[char] = i + 1 33 | 34 | self.character = [ 35 | "[CTCblank]" 36 | ] + dict_character # dummy '[CTCblank]' token for CTCLoss (index 0). 37 | print(f"# of tokens and characters: {len(self.character)}") 38 | 39 | def encode(self, word_string, batch_max_length=25): 40 | """ convert word_list (string) into word_index. 41 | input: 42 | word_string: word labels of each image. [batch_size] 43 | batch_max_length: max length of word in the batch. Default: 25 44 | 45 | output: 46 | batch_word_index: word index list for CTCLoss. [batch_size, batch_max_length] 47 | batch_word_length: length of each word. [batch_size] 48 | """ 49 | # word_length = [len(word) for word in word_string] 50 | batch_word_length = [] 51 | 52 | # The index used for padding (=[PAD]) would not affect the CTC loss calculation. 53 | batch_word_index = torch.LongTensor(len(word_string), batch_max_length).fill_( 54 | self.dict["[PAD]"] 55 | ) 56 | for i, word in enumerate(word_string): 57 | # For HIN/BEN/ARA, HIN/BEN/ARA chars are delimited with \t 58 | if regex.findall("[\p{Arabic}]", word): 59 | if "\t" in word: 60 | word = word.split("\t") 61 | else: 62 | # not to split 1 char into sub-chars. e.g. "list(मं)" splits मं into 2 sub-chars. 63 | word = [word] 64 | # The order of Arabic labels in lmdb is right to left (human reading order), 65 | # so we reverse the label to align with the visual text order (left to right). 66 | word.reverse() 67 | 68 | elif regex.findall("[\p{InDevanagari}\p{InBengali}]", word): 69 | if "\t" in word: 70 | word = word.split("\t") 71 | else: 72 | # not to split 1 char into sub-chars. e.g. "list(मं)" splits मं into 2 sub-chars. 73 | word = [word] 74 | else: 75 | word = list(word) 76 | 77 | # we calculate word_length after handling hindi chars. 78 | word_length = len(word) 79 | batch_word_length.append(word_length) 80 | word_idx = [ 81 | self.dict[char] if char in self.dict else self.dict["[UNK]"] 82 | for char in word 83 | ] 84 | batch_word_index[i][:word_length] = torch.LongTensor(word_idx) 85 | 86 | return ( 87 | batch_word_index.to(device), 88 | torch.IntTensor(batch_word_length).to(device), 89 | ) 90 | 91 | def decode(self, word_index, word_length): 92 | """ convert word_index into word_string """ 93 | word_string = [] 94 | for idx, length in enumerate(word_length): 95 | word_idx = word_index[idx, :] 96 | 97 | char_list = [] 98 | for i in range(length): 99 | # removing repeated characters and blank. 100 | if word_idx[i] != 0 and not (i > 0 and word_idx[i - 1] == word_idx[i]): 101 | char_list.append(self.character[word_idx[i]]) 102 | 103 | word = "".join(char_list) 104 | word_string.append(word) 105 | 106 | return word_string 107 | 108 | def decode_gt(self, word_index, word_length): 109 | """ convert word_index into word_string for gt""" 110 | word_string = [] 111 | for idx, length in enumerate(word_length): 112 | word_idx = word_index[idx, :length] 113 | word = "".join([self.character[i] for i in word_idx]) 114 | word = word.replace("[PAD]", "") 115 | word_string.append(word) 116 | 117 | return word_string 118 | 119 | 120 | class AttnLabelConverter(object): 121 | """ Convert between text-label and text-index """ 122 | 123 | def __init__(self, opt): 124 | 125 | # character (str): set of the possible characters. 126 | # [SOS] (start-of-sentence token) and [EOS] (end-of-sentence token) for the attention decoder. 127 | list_special_token = [ 128 | "[PAD]", 129 | "[UNK]", 130 | " ", 131 | "[SOS]", 132 | "[EOS]", 133 | ] # [UNK] for unknown character, ' ' for space. 134 | # list_character = list(character) 135 | list_character = ( 136 | opt.character # we give list of character as a default. (especially for Hindi) 137 | ) 138 | self.character = list_special_token + list_character 139 | 140 | self.dict = {} 141 | for i, char in enumerate(self.character): 142 | # print(i, char) 143 | self.dict[char] = i 144 | 145 | print(f"# of tokens and characters: {len(self.character)}") 146 | 147 | def encode(self, word_string, batch_max_length=25): 148 | """ convert word_list (string) into word_index. 149 | input: 150 | word_string: word labels of each image. [batch_size] 151 | batch_max_length: max length of word in the batch. Default: 25 152 | 153 | output: 154 | batch_word_index : the input of attention decoder. [batch_size x (max_length+2)] +1 for [SOS] token and +1 for [EOS] token. 155 | batch_word_length : the length of output of attention decoder, which count [EOS] token also. [batch_size] 156 | """ 157 | batch_max_length += 1 158 | 159 | # additional batch_max_length + 1 for [SOS] at first step. 160 | batch_word_index = torch.LongTensor( 161 | len(word_string), batch_max_length + 1 162 | ).fill_(self.dict["[PAD]"]) 163 | batch_word_index[:, 0] = self.dict["[SOS]"] 164 | batch_word_length = [] 165 | 166 | for i, word in enumerate(word_string): 167 | # For HIN/BEN/ARA, HIN/BEN/ARA chars are delimited with \t 168 | if regex.findall("[\p{Arabic}]", word): 169 | if "\t" in word: 170 | word = word.split("\t") 171 | else: 172 | # not to split 1 char into sub-chars. e.g. "list(मं)" splits मं into 2 sub-chars. 173 | word = [word] 174 | # The order of Arabic labels in lmdb is right to left (human reading order), 175 | # so we reverse the label to align with the visual text order (left to right). 176 | word.reverse() 177 | 178 | elif regex.findall("[\p{InDevanagari}\p{InBengali}]", word): 179 | if "\t" in word: 180 | word = word.split("\t") 181 | else: 182 | # not to split 1 char into sub-chars. e.g. "list(मं)" splits मं into 2 sub-chars. 183 | word = [word] 184 | else: 185 | word = list(word) 186 | 187 | # we calculate word_length after handling hindi chars. 188 | word_length = len(word) + 1 # +1 for [EOS] at end of sentence. 189 | batch_word_length.append(word_length) 190 | word.append("[EOS]") 191 | word_idx = [ 192 | self.dict[char] if char in self.dict else self.dict["[UNK]"] 193 | for char in word 194 | ] 195 | batch_word_index[i][1 : 1 + word_length] = torch.LongTensor( 196 | word_idx 197 | ) # word_index[:, 0] = [SOS] token 198 | 199 | return ( 200 | batch_word_index.to(device), 201 | torch.IntTensor(batch_word_length).to(device), 202 | ) 203 | 204 | def decode(self, word_index, word_length): 205 | """ convert word_index into word_string """ 206 | word_string = [] 207 | for idx, length in enumerate(word_length): 208 | word_idx = word_index[idx, :length] 209 | word = "".join([self.character[i] for i in word_idx]) 210 | word_string.append(word) 211 | 212 | return word_string 213 | 214 | 215 | class Averager(object): 216 | """Compute average for torch.Tensor, used for loss average.""" 217 | 218 | def __init__(self): 219 | self.reset() 220 | 221 | def add(self, v): 222 | count = v.data.numel() 223 | v = v.data.sum() 224 | self.n_count += count 225 | self.sum += v 226 | 227 | def reset(self): 228 | self.n_count = 0 229 | self.sum = 0 230 | 231 | def val(self): 232 | res = 0 233 | if self.n_count != 0: 234 | res = self.sum / float(self.n_count) 235 | return res 236 | 237 | 238 | def adjust_learning_rate(optimizer, iteration, opt): 239 | """Decay the learning rate based on schedule""" 240 | lr = opt.lr 241 | # stepwise lr schedule 242 | for milestone in opt.schedule: 243 | lr *= ( 244 | opt.lr_drop_rate if iteration >= (float(milestone) * opt.num_iter) else 1.0 245 | ) 246 | for param_group in optimizer.param_groups: 247 | param_group["lr"] = lr 248 | 249 | 250 | def tensor2im(image_tensor, imtype=np.uint8): 251 | image_numpy = image_tensor.cpu().float().numpy() 252 | if image_numpy.shape[0] == 1: 253 | image_numpy = np.tile(image_numpy, (3, 1, 1)) 254 | image_numpy = (np.transpose(image_numpy, (1, 2, 0)) + 1) / 2.0 * 255.0 255 | return image_numpy.astype(imtype) 256 | 257 | 258 | def save_image(image_numpy, image_path): 259 | image_pil = PIL.Image.fromarray(image_numpy) 260 | image_pil.save(image_path) 261 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cross-Lingual Learning in Multilingual Scene Text Recognition 2 | Official PyTorch implementation | [paper](https://arxiv.org/abs/2312.10806) | [training and evaluation data](#download-preprocessed-lmdb-datasets-for-training-and-evaluation) | [pretrained model](#pretrained_models) |
3 | 4 | **Preliminaries:** 5 | 1. Multilingual STR: A task to recognize text from multiple languages in a word- or line-level scene image. 6 | 2. Cross-Lingual Learning (CLL): A methodology for transferring knowledge from one language to another. 7 | 8 | **What we did:** 9 | 1. Verified two general insights about CLL and showed that _the two general insights may not be applied to multilingual STR._ 10 | 2. Showed that _CLL is simple and highly effective for improving performance in low-resource languages._ 11 | 3. Found the condition where CLL works well: _the crucial condition for CLL is the dataset size of high-resource languages, regardless of the kind of high-resource languages._ 12 | 13 | **More detail:** 14 | 1. In this work, we investigate **CLL for multilingual scene text recognition (STR).**
15 | CLL transfers knowledge from one language to another. 16 | Often, the number of resources in each language is not even. 17 | There are high-resource languages such as English and low-resource languages such as Korean. 18 | Generally, the performance on a low-resource language is low, and improving the performance requires more resources. 19 | In this case, **instead of obtaining more resources, applying CLL from an existing high-resource language to the low-resource language can improve the performance**, as shown in the figure below. 20 | 21 |

22 |
23 | (a) Monolingual training on a low-resource language Korean (KOR) results in incorrect predictions, whereas (b) CLL from a high-resource language English (ENG) to KOR leads to correct predictions. 24 |

25 | 26 | 27 | 2. **We aim to find the condition where CLL works well.** 28 | To do so, we first **verify two general insights about CLL discussed in previous works ([MRN](https://github.com/simplify23/MRN) and [M-BRET](https://arxiv.org/abs/1906.01502))**: 29 | (a) joint learning with high- and low-resource languages may reduce performance on low-resource languages, and (b) CLL works best between similar languages. 30 | By using a representative multilingual scene text dataset [MLT19](https://rrc.cvc.uab.es/?ch=15&com=introduction), we show that **the two general insights may not be applied to multilingual STR.** 31 |

32 |
33 | Two general insights about CLL related to (a) joint learning and (b) cascade learning (CL). 34 |

35 | 36 | 37 | 3. After that, through extensive experiments with additional data [SynthMLT](https://github.com/MichalBusta/E2E-MLT), we empirically show that **the crucial factor is the number of samples in high-resource languages rather than the similarity between languages.** 38 | In other words, CLL works well when we have sufficiently large samples in high-resource languages regardless of the kind of high-resource languages. 39 | We assume this is because **the essential knowledge of STR is distinguishing text in the image and can be learned from any language.** 40 | 41 | We hope this work helps get more insights into multilingual STR. 42 | 43 | 44 | 45 | 47 | 48 | ## Getting Started 49 | ### Installation 50 | This work was tested with PyTorch 1.12.1, CUDA 11.3, Python 3.8, and Linux.
51 | Install conda >= 4.11.0 (python >= 3.8) and then run the following commands.
52 | ``` 53 | conda create -n CLL python=3.8 -y 54 | conda activate CLL 55 | 56 | git clone https://github.com/ku21fan/CLL-STR.git 57 | cd ./CLL-STR 58 | pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 torchaudio==0.12.1 --extra-index-url https://download.pytorch.org/whl/cu113 59 | pip install -r requirements.txt 60 | ``` 61 | 62 |

Run demo with pretrained model Open In Colab

63 | 64 | 1. [Download pretrained model](https://www.dropbox.com/scl/fo/uip1fanfsgu2yxerfofv6/h?rlkey=sd93rztv4gfwmmg7ukzrho0e1&dl=0)
65 | For example, download [SVTR_joint_All.pth](https://www.dropbox.com/scl/fi/q9r2ebbnr94xv4azyl7o1/SVTR_joint_All.pth?rlkey=wsonhqkffwyb4jixva44p0s5y&dl=0) for demo. 66 | ``` 67 | wget -O SVTR_joint_All.pth https://www.dropbox.com/scl/fi/q9r2ebbnr94xv4azyl7o1/SVTR_joint_All.pth?rlkey=wsonhqkffwyb4jixva44p0s5y 68 | ``` 69 | 2. Add image files to test into `demo_image/` 70 | 3. Run demo.py 71 | ``` 72 | CUDA_VISIBLE_DEVICES=0 python3 demo.py --model_name SVTR --image_folder demo_image/ \ 73 | --saved_model SVTR_joint_All.pth 74 | ``` 75 | 76 | #### Prediction on demo images 77 | Here, LAT is considered a high-resource language (28K) compared to other languages. The results below show the effectiveness of CLL. 78 | | demo images | [SVTR_joint_All_but_LAT](https://www.dropbox.com/scl/fi/vh7hadt745e0bkvx3ihzb/SVTR_joint_All_but_LAT.pth?rlkey=756sc51vvp23xl6yh5zx2edmu&dl=0) | [SVTR_joint_All](https://www.dropbox.com/scl/fi/q9r2ebbnr94xv4azyl7o1/SVTR_joint_All.pth?rlkey=wsonhqkffwyb4jixva44p0s5y&dl=0) | Ground Truth | 79 | | --- | --- | --- | --- | 80 | | | اﻟﺤﻢ | اﻟﺒﺎب | الباب | 81 | | | বান | রাখিবেন | রাখিবেন | 82 | | | か本 | 北京西 | 北京西 | 83 | | | ज | मंडप | मंडप | 84 | | | ٥り | くすり | くすり | 85 | | | 상온 | 냉면은 | 냉면은 | 86 | | | 디센다 | CUISINE | CUISINE | 87 | 88 | 89 | ### Download preprocessed lmdb datasets for training and evaluation 90 | We use [MLT19](https://rrc.cvc.uab.es/?ch=15&com=introduction) and [SynthMLT](https://github.com/MichalBusta/E2E-MLT) datasets. 91 | MLT19 and SynthMLT consist of 7 scripts: Arabic (ARA), Bengali (BEN), Chinese (CHI), Hindi (HIN), Japanese (JPN), Korean (KOR), and Latin (LAT). 92 | LAT consists of English, French, German, and Italian. 93 | 94 | Download the preprocessed lmdb datasets from [here](https://www.dropbox.com/scl/fi/js1y8dd6y0sethbuge1zl/CLL-data.zip?rlkey=bca3631p91mw1f98qhh79qprg&dl=0), or use wget as follows. 95 | ``` 96 | wget -O CLL-data.zip https://www.dropbox.com/scl/fi/js1y8dd6y0sethbuge1zl/CLL-data.zip?rlkey=bca3631p91mw1f98qhh79qprg 97 | unzip CLL-data.zip -d data/ 98 | ``` 99 | The structure of `data/` is as follows: 100 | ``` 101 | data 102 | ├── training 103 | │ ├── ARA 104 | │ ├── BEN 105 | │ ├── CHI 106 | │ ├── HIN 107 | │ ├── JPN 108 | │ ├── KOR 109 | │ ├── LAT 110 | │ ├── SynthMLT_ARA 111 | │ ├── SynthMLT_BEN 112 | │ ├── SynthMLT_CHI 113 | │ ├── SynthMLT_HIN 114 | │ ├── SynthMLT_JPN 115 | │ ├── SynthMLT_KOR 116 | │ └── SynthMLT_LAT 117 | ├── validation 118 | │ ├── ARA 119 | │ ├── BEN 120 | │ ├── CHI 121 | │ ├── HIN 122 | │ ├── JPN 123 | │ ├── KOR 124 | │ └── LAT 125 | └── evaluation 126 | ├── ARA 127 | ├── BEN 128 | ├── CHI 129 | ├── HIN 130 | ├── JPN 131 | ├── KOR 132 | └── LAT 133 | ``` 134 | 135 | ### Training 136 | 1. Train SVTR with only one language data (LAT). For detail, check [--select_data part](https://github.com/ku21fan/CLL-STR/blob/e65ac0288ea41c0cba7191141b838e7ab0d1eb4f/train.py#L532-L573) in train.py. 137 | ``` 138 | CUDA_VISIBLE_DEVICES=0 python3 train.py --model_name SVTR --exp_name SVTR_lang_L --select_data lang_L 139 | ``` 140 | 141 | 2. Joint learning with all seven languages (Related to Table 2 in the paper) 142 | ``` 143 | CUDA_VISIBLE_DEVICES=0 python3 train.py --model_name SVTR --exp_name SVTR_joint_All --select_data lang_CBHJAKL 144 | ``` 145 | 146 | 3. Cascase learning: Pre-train SVTR on LAT then HIN (Related to Table 3 in the paper) 147 | ``` 148 | CUDA_VISIBLE_DEVICES=0 python3 train.py --model_name SVTR --exp_name SVTR_L_to_H \ 149 | --num_iter 10000 --saved_model saved_models/SVTR_lang_L/best_score.pth --select_data lang_H 150 | ``` 151 | 152 | 4. Joint learning with all seven languages but using only 2K LAT instead of all LAT = _Base_ setting of Table 4 in the paper. 153 | ``` 154 | CUDA_VISIBLE_DEVICES=0 python3 train.py --model_name SVTR --exp_name SVTR_joint_Base \ 155 | --data_usage_LAT 2000 --select_data lang_CBHJAKL 156 | ``` 157 | 158 | Joint learning with _Base_ setting + 2K SynthMLT KOR 159 | ``` 160 | CUDA_VISIBLE_DEVICES=0 python3 train.py --model_name SVTR --exp_name SVTR_joint_Base_2KSynthMLT_K \ 161 | --data_usage_LAT 2000 --data_usage_SynthMLT 2000 --select_data lang_CBHJAKL-SynthMLT_K 162 | ``` 163 | 164 | Joint learning with _Base_ setting + all SynthMLT KOR 165 | ``` 166 | CUDA_VISIBLE_DEVICES=0 python3 train.py --model_name SVTR --exp_name SVTR_joint_Base_allSynthMLT_K \ 167 | --data_usage_LAT 2000 --select_data lang_CBHJAKL-SynthMLT_K 168 | ``` 169 | 170 | 171 | ### Evaluation 172 | Test SVTR model. 173 | ``` 174 | CUDA_VISIBLE_DEVICES=0 python3 test.py --model_name SVTR --saved_model saved_models/SVTR_joint_All/best_score.pth 175 | ``` 176 | 177 | 178 | ### Main arguments 179 | train.py 180 | * `--train_data`: folder path for training lmdb dataset. default: `data/training/` 181 | * `--valid_data`: folder path for validation lmdb dataset. default: `data/validation/` 182 | * `--eval_data`: folder path for evaluation lmdb dataset. default: `data/evaluation/` 183 | * `--model_name`: select model 'CRNN' or 'SVTR'. 184 | * `--saved_model`: assign the saved model for fine-tuning 185 | 186 | test.py 187 | * `--eval_data`: folder path for evaluation lmdb dataset. default: `data/evaluation/` 188 | * `--model_name`: select model 'CRNN' or 'SVTR'. 189 | * `--saved_model`: assign the saved model for evaluation. 190 | 191 | demo.py 192 | * `--image_folder`: path for image_folder which contains text images. default: `demo_image/` 193 | * `--model_name`: select model 'CRNN' or 'SVTR'. 194 | * `--saved_model`: assign the saved model to use. 195 | 196 | 197 | ## Citation 198 | Please consider citing this work in your publications if it helps your research. 199 | ``` 200 | @inproceedings{baek2024CLLSTR, 201 | title={Cross-Lingual Learning in Multilingual Scene Text Recognition}, 202 | author={Baek, Jeonghun and Matsui, Yusuke and Aizawa, Kiyoharu}, 203 | booktitle={IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP)}, 204 | year={2024} 205 | } 206 | ``` 207 | 208 | ## Contact 209 | Feel free to contact us if there is any question: Jeonghun Baek ku21fang@gmail.com 210 | 211 | ## Acknowledgements 212 | This implementation has been based on the repositories [STR-Fewer-Labels](https://github.com/ku21fan/STR-Fewer-Labels) and [MRN](https://github.com/simplify23/MRN). 213 | 214 | ## License 215 | MIT 216 | -------------------------------------------------------------------------------- /dataset.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import re 4 | import six 5 | import time 6 | import math 7 | import random 8 | 9 | import lmdb 10 | import regex 11 | from tqdm import tqdm 12 | from natsort import natsorted 13 | import PIL 14 | import numpy as np 15 | import torch 16 | from torch.utils.data import Dataset, ConcatDataset, Subset 17 | from torch._utils import _accumulate 18 | import torchvision.transforms as transforms 19 | 20 | 21 | def hierarchical_dataset(root, opt, select_data="/", mode="train", data_type="label"): 22 | """ select_data='/' contains all sub-directory of root directory """ 23 | dataset_list = [] 24 | dataset_log = f"dataset_root: {root}\t dataset: {select_data[0]}" 25 | print(dataset_log) 26 | dataset_log += "\n" 27 | for dirpath, dirnames, filenames in os.walk(root + "/"): 28 | if not dirnames: 29 | select_flag = False 30 | for selected_d in select_data: 31 | if selected_d in dirpath: 32 | select_flag = True 33 | break 34 | 35 | if select_flag: 36 | dataset = LmdbDataset(dirpath, opt, mode=mode) 37 | 38 | sub_dataset_log = f"sub-directory:\t/{os.path.relpath(dirpath, root)}\t num samples: {len(dataset)}" 39 | print(sub_dataset_log) 40 | dataset_log += f"{sub_dataset_log}\n" 41 | dataset_list.append(dataset) 42 | 43 | concatenated_dataset = ConcatDataset(dataset_list) 44 | 45 | return concatenated_dataset, dataset_log 46 | 47 | 48 | def concat_dataset(root, opt, dataset_list, mode="train"): 49 | """ select_data='/' contains all sub-directory of root directory """ 50 | concat_dataset_list = [] 51 | for data in dataset_list: 52 | dataset_log = f"dataset_root: {root}\tsub-directory:\t{data}" 53 | print(dataset_log) 54 | dataset_log += "\n" 55 | 56 | dataset = LmdbDataset(os.path.join(root, data), opt, mode=mode) 57 | tmp_log = f"\t num samples: {len(dataset)}" 58 | print(tmp_log) 59 | dataset_log += f"{tmp_log}\n" 60 | 61 | if mode == "train" and data == "LAT" and opt.data_usage_LAT != "all": 62 | total_number_dataset = len(dataset) 63 | number_dataset = int(opt.data_usage_LAT) 64 | 65 | dataset_split = [number_dataset, total_number_dataset - number_dataset] 66 | indices = range(total_number_dataset) 67 | dataset, _ = [ 68 | Subset(dataset, indices[offset - length : offset]) 69 | for offset, length in zip(_accumulate(dataset_split), dataset_split) 70 | ] 71 | tmp_log = f"{len(dataset)} {data} samples are used for the experiment" 72 | print(tmp_log) 73 | dataset_log += f"{tmp_log}\n" 74 | 75 | elif ( 76 | mode == "train" and "SynthMLT" in data and opt.data_usage_SynthMLT != "all" 77 | ): 78 | total_number_dataset = len(dataset) 79 | number_dataset = int(opt.data_usage_SynthMLT) 80 | 81 | dataset_split = [number_dataset, total_number_dataset - number_dataset] 82 | indices = range(total_number_dataset) 83 | dataset, _ = [ 84 | Subset(dataset, indices[offset - length : offset]) 85 | for offset, length in zip(_accumulate(dataset_split), dataset_split) 86 | ] 87 | tmp_log = f"{len(dataset)} {data} samples are used for the experiment" 88 | print(tmp_log) 89 | dataset_log += f"{tmp_log}\n" 90 | 91 | concat_dataset_list.append(dataset) 92 | 93 | concatenated_dataset = ConcatDataset(concat_dataset_list) 94 | print(f"num samples of concatenated_dataset: {len(concatenated_dataset)}") 95 | 96 | if mode == "train" and opt.data_usage != "all": 97 | total_number_dataset = len(concatenated_dataset) 98 | number_dataset = int(opt.data_usage) 99 | 100 | dataset_split = [number_dataset, total_number_dataset - number_dataset] 101 | indices = range(total_number_dataset) 102 | concatenated_dataset, _ = [ 103 | Subset(concatenated_dataset, indices[offset - length : offset]) 104 | for offset, length in zip(_accumulate(dataset_split), dataset_split) 105 | ] 106 | tmp_log = f"{len(concatenated_dataset)} samples are used for the experiment" 107 | print(tmp_log) 108 | dataset_log += f"{tmp_log}\n" 109 | 110 | # for faster training, we multiply small datasets itself. 111 | if mode == "train" and len(concatenated_dataset) < 100000: 112 | print( 113 | f"{len(concatenated_dataset)} is too small, it is multiplied to over 100K" 114 | ) 115 | print( 116 | "CAUTION: If you use 'epoch' for training, you have to remove this multiplication part" 117 | ) 118 | multiple_times = int(100000 / len(concatenated_dataset)) 119 | dataset_self_multiple = [concatenated_dataset] * multiple_times 120 | concatenated_dataset = ConcatDataset(dataset_self_multiple) 121 | 122 | return concatenated_dataset, dataset_log 123 | 124 | 125 | class LmdbDataset(Dataset): 126 | def __init__(self, root, opt, mode="train"): 127 | 128 | self.root = root 129 | self.opt = opt 130 | self.mode = mode 131 | self.env = lmdb.open( 132 | root, 133 | max_readers=32, 134 | readonly=True, 135 | lock=False, 136 | readahead=False, 137 | meminit=False, 138 | ) 139 | 140 | # print(self.env.info()) 141 | self.txn = self.env.begin(write=False) 142 | 143 | if not self.env: 144 | print("cannot open lmdb from %s" % (root)) 145 | sys.exit(0) 146 | 147 | self.nSamples = int(self.txn.get("num-samples".encode())) 148 | 149 | self.filtered_index_list = [] 150 | for index in tqdm(range(self.nSamples), total=self.nSamples): 151 | index += 1 # lmdb starts with 1 152 | label_key = "label-%09d".encode() % index 153 | label = self.txn.get(label_key).decode("utf-8") 154 | 155 | # length filtering 156 | length_of_label = len(label) 157 | 158 | # For HIN/BEN/ARA, HIN/BEN/ARA chars are delimited with \t 159 | if regex.findall("[\p{InDevanagari}\p{InBengali}\p{Arabic}]", label): 160 | if ( 161 | length_of_label > 2 * opt.batch_max_length - 1 162 | ): # to count "\t" between hindi chars. 163 | # print("length check", label) 164 | continue 165 | 166 | elif length_of_label > opt.batch_max_length: 167 | # print("length check", label) 168 | continue 169 | 170 | self.filtered_index_list.append(index) 171 | 172 | self.nSamples = len(self.filtered_index_list) 173 | 174 | def __len__(self): 175 | return self.nSamples 176 | 177 | def __getitem__(self, index): 178 | assert index <= len(self), "index range error" 179 | index = self.filtered_index_list[index] 180 | 181 | label_key = "label-%09d".encode() % index 182 | label = self.txn.get(label_key).decode("utf-8") 183 | img_key = "image-%09d".encode() % index 184 | imgbuf = self.txn.get(img_key) 185 | 186 | buf = six.BytesIO() 187 | buf.write(imgbuf) 188 | buf.seek(0) 189 | 190 | try: 191 | img = PIL.Image.open(buf).convert("RGB") 192 | 193 | except IOError: 194 | print(f"Corrupted image for {index}") 195 | # make dummy image and dummy label for corrupted image. 196 | img = PIL.Image.new("RGB", (self.opt.imgW, self.opt.imgH)) 197 | label = "[dummy_label]" 198 | 199 | return (img, label) 200 | 201 | 202 | class RawDataset(Dataset): 203 | def __init__(self, root, opt): 204 | self.opt = opt 205 | self.image_path_list = [] 206 | for dirpath, dirnames, filenames in os.walk(root): 207 | for name in filenames: 208 | _, ext = os.path.splitext(name) 209 | ext = ext.lower() 210 | if ext == ".jpg" or ext == ".jpeg" or ext == ".png": 211 | self.image_path_list.append(os.path.join(dirpath, name)) 212 | 213 | self.image_path_list = natsorted(self.image_path_list) 214 | self.nSamples = len(self.image_path_list) 215 | 216 | def __len__(self): 217 | return self.nSamples 218 | 219 | def __getitem__(self, index): 220 | 221 | try: 222 | img = PIL.Image.open(self.image_path_list[index]).convert("RGB") 223 | 224 | except IOError: 225 | print(f"Corrupted image for {index}") 226 | # make dummy image and dummy label for corrupted image. 227 | img = PIL.Image.new("RGB", (self.opt.imgW, self.opt.imgH)) 228 | 229 | return (img, self.image_path_list[index]) 230 | 231 | 232 | class AlignCollate(object): 233 | def __init__(self, opt, mode="train"): 234 | self.opt = opt 235 | self.mode = mode 236 | 237 | if opt.PAD: 238 | self.transform = NormalizePAD(opt) 239 | else: 240 | if opt.Aug == "None" or mode != "train": 241 | self.transform = ResizeNormalize((opt.imgW, opt.imgH)) 242 | else: 243 | self.transform = Text_augment(opt) 244 | 245 | def __call__(self, batch): 246 | images, labels = zip(*batch) 247 | 248 | image_tensors = [self.transform(image) for image in images] 249 | image_tensors = torch.cat([t.unsqueeze(0) for t in image_tensors], 0) 250 | 251 | return image_tensors, labels 252 | 253 | 254 | class ResizeNormalize(object): 255 | def __init__(self, size, interpolation=PIL.Image.BICUBIC): 256 | # CAUTION: it should be (width, height). different from size of transforms.Resize (height, width) 257 | self.size = size 258 | self.interpolation = interpolation 259 | self.toTensor = transforms.ToTensor() 260 | 261 | def __call__(self, image): 262 | image = image.resize(self.size, self.interpolation) 263 | image = self.toTensor(image) 264 | image.sub_(0.5).div_(0.5) 265 | return image 266 | 267 | 268 | class NormalizePAD(object): 269 | def __init__(self, opt, interpolation=PIL.Image.BICUBIC): 270 | self.opt = opt 271 | self.interpolation = interpolation 272 | self.padded_size = (3, self.opt.imgH, self.opt.imgW) # 3 for RGB input channel 273 | self.toTensor = transforms.ToTensor() 274 | 275 | def __call__(self, img): 276 | w, h = img.size 277 | ratio = w / float(h) 278 | if math.ceil(self.opt.imgH * ratio) > self.opt.imgW: 279 | resized_w = self.opt.imgW 280 | else: 281 | resized_w = math.ceil(self.opt.imgH * ratio) 282 | 283 | img = img.resize((resized_w, self.opt.imgH), self.interpolation) 284 | 285 | img = self.toTensor(img) 286 | img.sub_(0.5).div_(0.5) 287 | c, h, w = img.size() 288 | Pad_img = torch.FloatTensor(*self.padded_size).fill_(0) 289 | Pad_img[:, :, :w] = img # right pad 290 | if self.padded_size[2] != w: # add border Pad 291 | Pad_img[:, :, w:] = ( 292 | img[:, :, w - 1].unsqueeze(2).expand(c, h, self.padded_size[2] - w) 293 | ) 294 | 295 | return Pad_img 296 | -------------------------------------------------------------------------------- /modules/feature_extraction.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | import torch.nn.functional as F 3 | 4 | from modules.svtr import SVTR 5 | 6 | 7 | class VGG_FeatureExtractor(nn.Module): 8 | """ FeatureExtractor of CRNN (https://arxiv.org/pdf/1507.05717.pdf) """ 9 | 10 | def __init__(self, input_channel, output_channel=512): 11 | super(VGG_FeatureExtractor, self).__init__() 12 | self.output_channel = [ 13 | int(output_channel / 8), 14 | int(output_channel / 4), 15 | int(output_channel / 2), 16 | output_channel, 17 | ] # [64, 128, 256, 512] 18 | self.ConvNet = nn.Sequential( 19 | nn.Conv2d(input_channel, self.output_channel[0], 3, 1, 1), 20 | nn.ReLU(True), 21 | nn.MaxPool2d(2, 2), # 64x16x50 22 | nn.Conv2d(self.output_channel[0], self.output_channel[1], 3, 1, 1), 23 | nn.ReLU(True), 24 | nn.MaxPool2d(2, 2), # 128x8x25 25 | nn.Conv2d(self.output_channel[1], self.output_channel[2], 3, 1, 1), 26 | nn.ReLU(True), # 256x8x25 27 | nn.Conv2d(self.output_channel[2], self.output_channel[2], 3, 1, 1), 28 | nn.ReLU(True), 29 | nn.MaxPool2d((2, 1), (2, 1)), # 256x4x25 30 | nn.Conv2d( 31 | self.output_channel[2], self.output_channel[3], 3, 1, 1, bias=False 32 | ), 33 | nn.BatchNorm2d(self.output_channel[3]), 34 | nn.ReLU(True), # 512x4x25 35 | nn.Conv2d( 36 | self.output_channel[3], self.output_channel[3], 3, 1, 1, bias=False 37 | ), 38 | nn.BatchNorm2d(self.output_channel[3]), 39 | nn.ReLU(True), 40 | nn.MaxPool2d((2, 1), (2, 1)), # 512x2x25 41 | nn.Conv2d(self.output_channel[3], self.output_channel[3], 2, 1, 0), 42 | nn.ReLU(True), 43 | ) # 512x1x24 44 | 45 | def forward(self, input): 46 | return self.ConvNet(input) 47 | 48 | 49 | class RCNN_FeatureExtractor(nn.Module): 50 | """ FeatureExtractor of GRCNN (https://papers.nips.cc/paper/6637-gated-recurrent-convolution-neural-network-for-ocr.pdf) """ 51 | 52 | def __init__(self, input_channel, output_channel=512): 53 | super(RCNN_FeatureExtractor, self).__init__() 54 | self.output_channel = [ 55 | int(output_channel / 8), 56 | int(output_channel / 4), 57 | int(output_channel / 2), 58 | output_channel, 59 | ] # [64, 128, 256, 512] 60 | self.ConvNet = nn.Sequential( 61 | nn.Conv2d(input_channel, self.output_channel[0], 3, 1, 1), 62 | nn.ReLU(True), 63 | nn.MaxPool2d(2, 2), # 64 x 16 x 50 64 | GRCL( 65 | self.output_channel[0], 66 | self.output_channel[0], 67 | num_iteration=5, 68 | kernel_size=3, 69 | pad=1, 70 | ), 71 | nn.MaxPool2d(2, 2), # 64 x 8 x 25 72 | GRCL( 73 | self.output_channel[0], 74 | self.output_channel[1], 75 | num_iteration=5, 76 | kernel_size=3, 77 | pad=1, 78 | ), 79 | nn.MaxPool2d(2, (2, 1), (0, 1)), # 128 x 4 x 26 80 | GRCL( 81 | self.output_channel[1], 82 | self.output_channel[2], 83 | num_iteration=5, 84 | kernel_size=3, 85 | pad=1, 86 | ), 87 | nn.MaxPool2d(2, (2, 1), (0, 1)), # 256 x 2 x 27 88 | nn.Conv2d( 89 | self.output_channel[2], self.output_channel[3], 2, 1, 0, bias=False 90 | ), 91 | nn.BatchNorm2d(self.output_channel[3]), 92 | nn.ReLU(True), 93 | ) # 512 x 1 x 26 94 | 95 | def forward(self, input): 96 | return self.ConvNet(input) 97 | 98 | 99 | class ResNet_FeatureExtractor(nn.Module): 100 | """ FeatureExtractor of FAN (http://openaccess.thecvf.com/content_ICCV_2017/papers/Cheng_Focusing_Attention_Towards_ICCV_2017_paper.pdf) """ 101 | 102 | def __init__(self, input_channel, output_channel=512): 103 | super(ResNet_FeatureExtractor, self).__init__() 104 | self.ConvNet = ResNet(input_channel, output_channel, BasicBlock, [1, 2, 5, 3]) 105 | 106 | def forward(self, input): 107 | return self.ConvNet(input) 108 | 109 | 110 | # For Gated RCNN 111 | class GRCL(nn.Module): 112 | def __init__(self, input_channel, output_channel, num_iteration, kernel_size, pad): 113 | super(GRCL, self).__init__() 114 | self.wgf_u = nn.Conv2d(input_channel, output_channel, 1, 1, 0, bias=False) 115 | self.wgr_x = nn.Conv2d(output_channel, output_channel, 1, 1, 0, bias=False) 116 | self.wf_u = nn.Conv2d( 117 | input_channel, output_channel, kernel_size, 1, pad, bias=False 118 | ) 119 | self.wr_x = nn.Conv2d( 120 | output_channel, output_channel, kernel_size, 1, pad, bias=False 121 | ) 122 | 123 | self.BN_x_init = nn.BatchNorm2d(output_channel) 124 | 125 | self.num_iteration = num_iteration 126 | self.GRCL = [GRCL_unit(output_channel) for _ in range(num_iteration)] 127 | self.GRCL = nn.Sequential(*self.GRCL) 128 | 129 | def forward(self, input): 130 | """ The input of GRCL is consistant over time t, which is denoted by u(0) 131 | thus wgf_u / wf_u is also consistant over time t. 132 | """ 133 | wgf_u = self.wgf_u(input) 134 | wf_u = self.wf_u(input) 135 | x = F.relu(self.BN_x_init(wf_u)) 136 | 137 | for i in range(self.num_iteration): 138 | x = self.GRCL[i](wgf_u, self.wgr_x(x), wf_u, self.wr_x(x)) 139 | 140 | return x 141 | 142 | 143 | class GRCL_unit(nn.Module): 144 | def __init__(self, output_channel): 145 | super(GRCL_unit, self).__init__() 146 | self.BN_gfu = nn.BatchNorm2d(output_channel) 147 | self.BN_grx = nn.BatchNorm2d(output_channel) 148 | self.BN_fu = nn.BatchNorm2d(output_channel) 149 | self.BN_rx = nn.BatchNorm2d(output_channel) 150 | self.BN_Gx = nn.BatchNorm2d(output_channel) 151 | 152 | def forward(self, wgf_u, wgr_x, wf_u, wr_x): 153 | G_first_term = self.BN_gfu(wgf_u) 154 | G_second_term = self.BN_grx(wgr_x) 155 | G = F.sigmoid(G_first_term + G_second_term) 156 | 157 | x_first_term = self.BN_fu(wf_u) 158 | x_second_term = self.BN_Gx(self.BN_rx(wr_x) * G) 159 | x = F.relu(x_first_term + x_second_term) 160 | 161 | return x 162 | 163 | 164 | class BasicBlock(nn.Module): 165 | expansion = 1 166 | 167 | def __init__(self, inplanes, planes, stride=1, downsample=None): 168 | super(BasicBlock, self).__init__() 169 | self.conv1 = self._conv3x3(inplanes, planes) 170 | self.bn1 = nn.BatchNorm2d(planes) 171 | self.conv2 = self._conv3x3(planes, planes) 172 | self.bn2 = nn.BatchNorm2d(planes) 173 | self.relu = nn.ReLU(inplace=True) 174 | self.downsample = downsample 175 | self.stride = stride 176 | 177 | def _conv3x3(self, in_planes, out_planes, stride=1): 178 | "3x3 convolution with padding" 179 | return nn.Conv2d( 180 | in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False 181 | ) 182 | 183 | def forward(self, x): 184 | residual = x 185 | 186 | out = self.conv1(x) 187 | out = self.bn1(out) 188 | out = self.relu(out) 189 | 190 | out = self.conv2(out) 191 | out = self.bn2(out) 192 | 193 | if self.downsample is not None: 194 | residual = self.downsample(x) 195 | out += residual 196 | out = self.relu(out) 197 | 198 | return out 199 | 200 | 201 | class ResNet(nn.Module): 202 | def __init__(self, input_channel, output_channel, block, layers): 203 | super(ResNet, self).__init__() 204 | 205 | self.output_channel_block = [ 206 | int(output_channel / 4), 207 | int(output_channel / 2), 208 | output_channel, 209 | output_channel, 210 | ] 211 | 212 | self.inplanes = int(output_channel / 8) 213 | self.conv0_1 = nn.Conv2d( 214 | input_channel, 215 | int(output_channel / 16), 216 | kernel_size=3, 217 | stride=1, 218 | padding=1, 219 | bias=False, 220 | ) 221 | self.bn0_1 = nn.BatchNorm2d(int(output_channel / 16)) 222 | self.conv0_2 = nn.Conv2d( 223 | int(output_channel / 16), 224 | self.inplanes, 225 | kernel_size=3, 226 | stride=1, 227 | padding=1, 228 | bias=False, 229 | ) 230 | self.bn0_2 = nn.BatchNorm2d(self.inplanes) 231 | self.relu = nn.ReLU(inplace=True) 232 | 233 | self.maxpool1 = nn.MaxPool2d(kernel_size=2, stride=2, padding=0) 234 | self.layer1 = self._make_layer(block, self.output_channel_block[0], layers[0]) 235 | self.conv1 = nn.Conv2d( 236 | self.output_channel_block[0], 237 | self.output_channel_block[0], 238 | kernel_size=3, 239 | stride=1, 240 | padding=1, 241 | bias=False, 242 | ) 243 | self.bn1 = nn.BatchNorm2d(self.output_channel_block[0]) 244 | 245 | self.maxpool2 = nn.MaxPool2d(kernel_size=2, stride=2, padding=0) 246 | self.layer2 = self._make_layer( 247 | block, self.output_channel_block[1], layers[1], stride=1 248 | ) 249 | self.conv2 = nn.Conv2d( 250 | self.output_channel_block[1], 251 | self.output_channel_block[1], 252 | kernel_size=3, 253 | stride=1, 254 | padding=1, 255 | bias=False, 256 | ) 257 | self.bn2 = nn.BatchNorm2d(self.output_channel_block[1]) 258 | 259 | self.maxpool3 = nn.MaxPool2d(kernel_size=2, stride=(2, 1), padding=(0, 1)) 260 | self.layer3 = self._make_layer( 261 | block, self.output_channel_block[2], layers[2], stride=1 262 | ) 263 | self.conv3 = nn.Conv2d( 264 | self.output_channel_block[2], 265 | self.output_channel_block[2], 266 | kernel_size=3, 267 | stride=1, 268 | padding=1, 269 | bias=False, 270 | ) 271 | self.bn3 = nn.BatchNorm2d(self.output_channel_block[2]) 272 | 273 | self.layer4 = self._make_layer( 274 | block, self.output_channel_block[3], layers[3], stride=1 275 | ) 276 | self.conv4_1 = nn.Conv2d( 277 | self.output_channel_block[3], 278 | self.output_channel_block[3], 279 | kernel_size=2, 280 | stride=(2, 1), 281 | padding=(0, 1), 282 | bias=False, 283 | ) 284 | self.bn4_1 = nn.BatchNorm2d(self.output_channel_block[3]) 285 | self.conv4_2 = nn.Conv2d( 286 | self.output_channel_block[3], 287 | self.output_channel_block[3], 288 | kernel_size=2, 289 | stride=1, 290 | padding=0, 291 | bias=False, 292 | ) 293 | self.bn4_2 = nn.BatchNorm2d(self.output_channel_block[3]) 294 | 295 | def _make_layer(self, block, planes, blocks, stride=1): 296 | downsample = None 297 | if stride != 1 or self.inplanes != planes * block.expansion: 298 | downsample = nn.Sequential( 299 | nn.Conv2d( 300 | self.inplanes, 301 | planes * block.expansion, 302 | kernel_size=1, 303 | stride=stride, 304 | bias=False, 305 | ), 306 | nn.BatchNorm2d(planes * block.expansion), 307 | ) 308 | 309 | layers = [] 310 | layers.append(block(self.inplanes, planes, stride, downsample)) 311 | self.inplanes = planes * block.expansion 312 | for i in range(1, blocks): 313 | layers.append(block(self.inplanes, planes)) 314 | 315 | return nn.Sequential(*layers) 316 | 317 | def forward(self, x): 318 | x = self.conv0_1(x) 319 | x = self.bn0_1(x) 320 | x = self.relu(x) 321 | x = self.conv0_2(x) 322 | x = self.bn0_2(x) 323 | x = self.relu(x) 324 | 325 | x = self.maxpool1(x) 326 | x = self.layer1(x) 327 | x = self.conv1(x) 328 | x = self.bn1(x) 329 | x = self.relu(x) 330 | 331 | x = self.maxpool2(x) 332 | x = self.layer2(x) 333 | x = self.conv2(x) 334 | x = self.bn2(x) 335 | x = self.relu(x) 336 | 337 | x = self.maxpool3(x) 338 | x = self.layer3(x) 339 | x = self.conv3(x) 340 | x = self.bn3(x) 341 | x = self.relu(x) 342 | 343 | x = self.layer4(x) 344 | x = self.conv4_1(x) 345 | x = self.bn4_1(x) 346 | x = self.relu(x) 347 | x = self.conv4_2(x) 348 | x = self.bn4_2(x) 349 | x = self.relu(x) 350 | 351 | return x 352 | 353 | 354 | class SVTR_FeatureExtractor(nn.Module): 355 | def __init__(self, input_channel, output_channel=512): 356 | super(SVTR_FeatureExtractor, self).__init__() 357 | self.ConvNet = SVTR(in_channels=input_channel, out_channels=output_channel) 358 | # self.neck = Im2Seq() 359 | 360 | def forward(self, input): 361 | return self.ConvNet(input) 362 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | import string 5 | import argparse 6 | import re 7 | from datetime import date 8 | 9 | import torch 10 | import torch.backends.cudnn as cudnn 11 | import torch.utils.data 12 | import torch.nn.functional as F 13 | import numpy as np 14 | from nltk.metrics.distance import edit_distance 15 | from tqdm import tqdm 16 | 17 | from utils import CTCLabelConverter, AttnLabelConverter, Averager 18 | from dataset import hierarchical_dataset, AlignCollate 19 | from model import Model 20 | 21 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 22 | 23 | 24 | def benchmark_all_eval(model, criterion, converter, opt, calculate_infer_time=False): 25 | """evaluation on 7 scripts""" 26 | if opt.dataset_name == "MLT19": 27 | eval_data_list = [ 28 | "CHI", 29 | "BEN", 30 | "HIN", 31 | "JPN", 32 | "ARA", 33 | "KOR", 34 | "LAT", 35 | ] 36 | 37 | if calculate_infer_time: 38 | eval_batch_size = ( 39 | 1 # batch_size should be 1 to calculate the GPU inference time per image. 40 | ) 41 | else: 42 | eval_batch_size = opt.batch_size 43 | 44 | accuracy_list = [] 45 | total_forward_time = [] 46 | total_eval_data_number = [] 47 | total_correct_number = [] 48 | log = open(f"./result/{opt.exp_name}/log_all_evaluation.txt", "a") 49 | dashed_line = "-" * 80 50 | print(dashed_line) 51 | log.write(dashed_line + "\n") 52 | for eval_data in eval_data_list: 53 | eval_data_path = os.path.join(opt.eval_data, eval_data) 54 | AlignCollate_eval = AlignCollate(opt, mode="test") 55 | eval_data, eval_data_log = hierarchical_dataset( 56 | root=eval_data_path, opt=opt, mode="test" 57 | ) 58 | eval_loader = torch.utils.data.DataLoader( 59 | eval_data, 60 | batch_size=eval_batch_size, 61 | shuffle=False, 62 | num_workers=int(opt.workers), 63 | collate_fn=AlignCollate_eval, 64 | pin_memory=False, 65 | ) 66 | 67 | (_, accuracy_by_best_model, _, _, _, infer_time, length_of_data,) = validation( 68 | model, criterion, eval_loader, converter, opt, tqdm_position=0 69 | ) 70 | accuracy_list.append(f"{accuracy_by_best_model:0.2f}") 71 | total_forward_time.append(infer_time) 72 | total_eval_data_number.append(len(eval_data)) 73 | total_correct_number.append(accuracy_by_best_model * length_of_data) 74 | log.write(eval_data_log) 75 | print(f"Acc {accuracy_by_best_model:0.2f}") 76 | log.write(f"Acc {accuracy_by_best_model:0.2f}\n") 77 | print(dashed_line) 78 | log.write(dashed_line + "\n") 79 | 80 | averaged_forward_time = sum(total_forward_time) / sum(total_eval_data_number) * 1000 81 | total_accuracy = sum(total_correct_number) / sum(total_eval_data_number) 82 | params_num = sum([np.prod(p.size()) for p in model.parameters()]) 83 | 84 | eval_log = "accuracy: " 85 | for name, accuracy in zip(eval_data_list, accuracy_list): 86 | eval_log += f"{name}: {accuracy}\t" 87 | eval_log += f"total_accuracy: {total_accuracy:0.2f}\t" 88 | eval_log += f"averaged_infer_time: {averaged_forward_time:0.3f}\t# parameters: {params_num/1e6:0.2f}" 89 | print(eval_log) 90 | log.write(eval_log + "\n") 91 | 92 | # for convenience 93 | tab_sep_accuracy_list = "\t".join(accuracy_list) 94 | print(f"{total_accuracy:0.2f}\t{tab_sep_accuracy_list}") 95 | # print(f"Total_accuracy:{total_accuracy:0.2f}") 96 | log.write(f"{tab_sep_accuracy_list}\n") 97 | log.write(f"Total_accuracy:{total_accuracy:0.2f}\n") 98 | log.close() 99 | 100 | # for convenience 101 | today = date.today() 102 | if opt.log_multiple_test: 103 | log_all_model = open(f"./evaluation_log/log_multiple_test_{today}.txt", "a") 104 | log_all_model.write(f"{total_accuracy:0.2f}\t{tab_sep_accuracy_list}\n") 105 | else: 106 | log_all_model = open( 107 | f"./evaluation_log/log_all_model_evaluation_{today}.txt", "a" 108 | ) 109 | log_all_model.write( 110 | f"./result/{opt.exp_name}\tTotal_accuracy:{total_accuracy:0.2f}\n" 111 | ) 112 | log_all_model.write(f"{total_accuracy:0.2f}\t{tab_sep_accuracy_list}\n") 113 | log_all_model.close() 114 | 115 | return total_accuracy, eval_data_list, accuracy_list 116 | 117 | 118 | def validation(model, criterion, eval_loader, converter, opt, tqdm_position=1): 119 | """validation or evaluation""" 120 | n_correct = 0 121 | norm_ED = 0 122 | length_of_data = 0 123 | infer_time = 0 124 | valid_loss_avg = Averager() 125 | 126 | for i, (image_tensors, labels) in tqdm( 127 | enumerate(eval_loader), 128 | total=len(eval_loader), 129 | position=tqdm_position, 130 | leave=False, 131 | ): 132 | batch_size = image_tensors.size(0) 133 | length_of_data = length_of_data + batch_size 134 | image = image_tensors.to(device) 135 | 136 | # For max length prediction 137 | labels_index, labels_length = converter.encode( 138 | labels, batch_max_length=opt.batch_max_length 139 | ) 140 | 141 | if "CTC" in opt.Prediction: 142 | start_time = time.time() 143 | preds = model(image) 144 | forward_time = time.time() - start_time 145 | 146 | # Calculate evaluation loss for CTC deocder. 147 | preds_size = torch.IntTensor([preds.size(1)] * batch_size) 148 | # permute 'preds' to use CTCloss format 149 | cost = criterion( 150 | preds.log_softmax(2).permute(1, 0, 2), 151 | labels_index, 152 | preds_size, 153 | labels_length, 154 | ) 155 | 156 | else: 157 | text_for_pred = ( 158 | torch.LongTensor(batch_size).fill_(converter.dict["[SOS]"]).to(device) 159 | ) 160 | 161 | start_time = time.time() 162 | preds = model(image, text_for_pred, is_train=False) 163 | forward_time = time.time() - start_time 164 | 165 | target = labels_index[:, 1:] # without [SOS] Symbol 166 | cost = criterion( 167 | preds.contiguous().view(-1, preds.shape[-1]), 168 | target.contiguous().view(-1), 169 | ) 170 | 171 | # make unknown chracter to [UNK] token. 172 | if "Attn" in opt.Prediction: 173 | labels_string = converter.decode( 174 | labels_index[:, 1:], labels_length 175 | ) # without [SOS] Symbol 176 | elif "CTC" in opt.Prediction: 177 | labels_string = converter.decode_gt(labels_index, labels_length) 178 | 179 | # select max probabilty (greedy decoding) then decode index to character 180 | _, preds_index = preds.max(2) 181 | preds_size = torch.IntTensor([preds.size(1)] * preds_index.size(0)).to(device) 182 | preds_str = converter.decode(preds_index, preds_size) 183 | 184 | infer_time += forward_time 185 | valid_loss_avg.add(cost) 186 | 187 | # calculate accuracy & confidence score 188 | preds_prob = F.softmax(preds, dim=2) 189 | preds_max_prob, _ = preds_prob.max(dim=2) 190 | confidence_score_list = [] 191 | for gt, prd, prd_max_prob in zip(labels_string, preds_str, preds_max_prob): 192 | 193 | if "Attn" in opt.Prediction: 194 | gt = gt[: gt.find("[EOS]")] 195 | prd_EOS = prd.find("[EOS]") 196 | prd = prd[:prd_EOS] # prune after "end of sentence" token ([EOS]) 197 | prd_max_prob = prd_max_prob[:prd_EOS] 198 | 199 | """we only evaluate the characters included in char_set (=opt.character).""" 200 | gt = gt.replace("[UNK]", "") 201 | prd = prd.replace("[UNK]", "") 202 | 203 | if opt.NED: 204 | # ICDAR2019 Normalized Edit Distance 205 | if len(gt) == 0 or len(prd) == 0: 206 | norm_ED += 0 207 | elif len(gt) > len(prd): 208 | norm_ED += 1 - edit_distance(prd, gt) / len(gt) 209 | else: 210 | norm_ED += 1 - edit_distance(prd, gt) / len(prd) 211 | 212 | else: 213 | if prd == gt: 214 | n_correct += 1 215 | 216 | # calculate confidence score (= multiply of prd_max_prob) 217 | try: 218 | confidence_score = prd_max_prob.cumprod(dim=0)[-1] 219 | except: 220 | confidence_score = 0 # for empty pred case, when prune after "end of sentence" token ([EOS]) 221 | confidence_score_list.append(confidence_score) 222 | 223 | if opt.NED: 224 | # ICDAR2019 Normalized Edit Distance. In web page, they report % of norm_ED (= norm_ED * 100). 225 | score = norm_ED / float(length_of_data) * 100 226 | else: 227 | score = n_correct / float(length_of_data) * 100 # accuracy 228 | 229 | return ( 230 | valid_loss_avg.val(), 231 | score, 232 | preds_str, 233 | confidence_score_list, 234 | labels_string, 235 | infer_time, 236 | length_of_data, 237 | ) 238 | 239 | 240 | def test(opt): 241 | """ model configuration """ 242 | if "CTC" in opt.Prediction: 243 | converter = CTCLabelConverter(opt) 244 | else: 245 | converter = AttnLabelConverter(opt) 246 | opt.sos_token_index = converter.dict["[SOS]"] 247 | opt.eos_token_index = converter.dict["[EOS]"] 248 | opt.num_class = len(converter.character) 249 | 250 | model = Model(opt) 251 | print( 252 | "model input parameters", 253 | opt.imgH, 254 | opt.imgW, 255 | opt.num_fiducial, 256 | opt.input_channel, 257 | opt.output_channel, 258 | opt.hidden_size, 259 | opt.num_class, 260 | opt.batch_max_length, 261 | opt.Transformation, 262 | opt.FeatureExtraction, 263 | opt.SequenceModeling, 264 | opt.Prediction, 265 | ) 266 | model = torch.nn.DataParallel(model).to(device) 267 | 268 | # load model 269 | print("loading pretrained model from %s" % opt.saved_model) 270 | try: 271 | model.load_state_dict(torch.load(opt.saved_model, map_location=device)) 272 | except: 273 | print( 274 | "*** pretrained model not match strictly *** and thus load_state_dict with strict=False mode" 275 | ) 276 | # pretrained_state_dict = torch.load(opt.saved_model) 277 | # for name in pretrained_state_dict: 278 | # print(name) 279 | model.load_state_dict( 280 | torch.load(opt.saved_model, map_location=device), strict=False 281 | ) 282 | 283 | opt.exp_name = "_".join(opt.saved_model.split("/")[1:]) 284 | # print(model) 285 | 286 | """ keep evaluation model and result logs """ 287 | os.makedirs(f"./result/{opt.exp_name}", exist_ok=True) 288 | # os.system(f'cp {opt.saved_model} ./result/{opt.exp_name}/') 289 | 290 | """ setup loss """ 291 | if "CTC" in opt.Prediction: 292 | criterion = torch.nn.CTCLoss(zero_infinity=True).to(device) 293 | else: 294 | # ignore [PAD] token = ignore index 0 295 | criterion = torch.nn.CrossEntropyLoss(ignore_index=converter.dict["[PAD]"]).to( 296 | device 297 | ) 298 | 299 | """ evaluation """ 300 | model.eval() 301 | with torch.no_grad(): 302 | # evaluate 7 benchmark evaluation datasets 303 | benchmark_all_eval(model, criterion, converter, opt) 304 | 305 | 306 | if __name__ == "__main__": 307 | parser = argparse.ArgumentParser() 308 | parser.add_argument( 309 | "--eval_data", default="data/evaluation/", help="path to evaluation dataset" 310 | ) 311 | parser.add_argument( 312 | "--workers", type=int, help="number of data loading workers", default=4 313 | ) 314 | parser.add_argument("--batch_size", type=int, default=512, help="input batch size") 315 | parser.add_argument( 316 | "--saved_model", required=True, help="path to saved_model to evaluation" 317 | ) 318 | parser.add_argument( 319 | "--log_multiple_test", action="store_true", help="log_multiple_test" 320 | ) 321 | """ Data processing """ 322 | parser.add_argument( 323 | "--batch_max_length", type=int, default=25, help="maximum-label-length" 324 | ) 325 | parser.add_argument( 326 | "--imgH", type=int, default=32, help="the height of the input image" 327 | ) 328 | parser.add_argument( 329 | "--imgW", type=int, default=100, help="the width of the input image" 330 | ) 331 | parser.add_argument("--NED", default=True, help="For Normalized edit_distance") 332 | parser.add_argument( 333 | "--PAD", 334 | action="store_true", 335 | help="whether to keep ratio then pad for image resize", 336 | ) 337 | parser.add_argument( 338 | "--Aug", 339 | type=str, 340 | default="None", 341 | help="whether to use augmentation |None|Crop|Rot|", 342 | ) 343 | 344 | """ Model Architecture """ 345 | parser.add_argument("--model_name", type=str, required=True, help="CRNN|SVTR") 346 | parser.add_argument( 347 | "--num_fiducial", 348 | type=int, 349 | default=20, 350 | help="number of fiducial points of TPS-STN", 351 | ) 352 | parser.add_argument( 353 | "--input_channel", 354 | type=int, 355 | default=3, 356 | help="the number of input channel of Feature extractor", 357 | ) 358 | parser.add_argument( 359 | "--output_channel", 360 | type=int, 361 | default=512, 362 | help="the number of output channel of Feature extractor", 363 | ) 364 | parser.add_argument( 365 | "--hidden_size", type=int, default=256, help="the size of the LSTM hidden state" 366 | ) 367 | parser.add_argument( 368 | "--dataset_name", default="MLT19", help="dataset name |MLT19|", 369 | ) 370 | opt = parser.parse_args() 371 | 372 | with open(f"charset/MLT19_charset.txt", "r", encoding="utf-8") as file: 373 | opt.character = file.read().splitlines() 374 | 375 | # for convenience 376 | if opt.model_name == "CRNN": # CRNN 377 | opt.Transformation = "None" 378 | opt.FeatureExtraction = "VGG" 379 | opt.SequenceModeling = "BiLSTM" 380 | opt.Prediction = "CTC" 381 | 382 | elif opt.model_name == "SVTR": # SVTR 383 | opt.Transformation = "None" 384 | opt.FeatureExtraction = "SVTR" 385 | opt.SequenceModeling = "None" 386 | opt.Prediction = "CTC" 387 | 388 | os.makedirs(f"./evaluation_log", exist_ok=True) 389 | 390 | cudnn.benchmark = True 391 | cudnn.deterministic = True 392 | opt.num_gpu = torch.cuda.device_count() 393 | 394 | if opt.num_gpu > 1: 395 | print( 396 | "For lab setting, check your GPU number, you should be missed CUDA_VISIBLE_DEVICES=0 or typo" 397 | ) 398 | sys.exit() 399 | 400 | test(opt) 401 | -------------------------------------------------------------------------------- /modules/svtr.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/simplify23/MRN/blob/23b24806219d66722c916db49c823f96368beb49/modules/svtr.py 2 | import numpy as np 3 | import torch 4 | from torch import nn 5 | from timm.models.layers import trunc_normal_ 6 | from functools import partial 7 | 8 | 9 | def drop_path( 10 | x, drop_prob: float = 0.0, training: bool = False, scale_by_keep: bool = True 11 | ): 12 | """Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks). 13 | This is the same as the DropConnect impl I created for EfficientNet, etc networks, however, 14 | the original name is misleading as 'Drop Connect' is a different form of dropout in a separate paper... 15 | See discussion: https://github.com/tensorflow/tpu/issues/494#issuecomment-532968956 ... I've opted for 16 | changing the layer and argument names to 'drop path' rather than mix DropConnect as a layer name and use 17 | 'survival rate' as the argument. 18 | """ 19 | if drop_prob == 0.0 or not training: 20 | return x 21 | keep_prob = 1 - drop_prob 22 | shape = (x.shape[0],) + (1,) * ( 23 | x.ndim - 1 24 | ) # work with diff dim tensors, not just 2D ConvNets 25 | random_tensor = x.new_empty(shape).bernoulli_(keep_prob) 26 | if keep_prob > 0.0 and scale_by_keep: 27 | random_tensor.div_(keep_prob) 28 | return x * random_tensor 29 | 30 | 31 | class DropPath(nn.Module): 32 | """Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks). 33 | """ 34 | 35 | def __init__(self, drop_prob: float = 0.0, scale_by_keep: bool = True): 36 | super(DropPath, self).__init__() 37 | self.drop_prob = drop_prob 38 | self.scale_by_keep = scale_by_keep 39 | 40 | def forward(self, x): 41 | return drop_path(x, self.drop_prob, self.training, self.scale_by_keep) 42 | 43 | 44 | class Identity(nn.Module): 45 | def __init__(self): 46 | super(Identity, self).__init__() 47 | 48 | def forward(self, input): 49 | return input 50 | 51 | 52 | class Mlp(nn.Module): 53 | def __init__( 54 | self, 55 | in_features, 56 | hidden_features=None, 57 | out_features=None, 58 | act_layer=nn.GELU, 59 | drop=0.0, 60 | ): 61 | super().__init__() 62 | out_features = out_features or in_features 63 | hidden_features = hidden_features or in_features 64 | self.fc1 = nn.Linear(in_features, hidden_features) 65 | self.act = act_layer() 66 | self.fc2 = nn.Linear(hidden_features, out_features) 67 | self.drop = nn.Dropout(drop) 68 | 69 | def forward(self, x): 70 | x = self.fc1(x) 71 | x = self.act(x) 72 | x = self.drop(x) 73 | x = self.fc2(x) 74 | x = self.drop(x) 75 | return x 76 | 77 | 78 | class ConvMixer(nn.Module): 79 | def __init__( 80 | self, dim, num_heads=8, HW=[8, 25], local_k=[3, 3], 81 | ): 82 | super().__init__() 83 | self.HW = HW 84 | self.dim = dim 85 | self.local_mixer = nn.Conv2d( 86 | dim, dim, local_k, 1, [local_k[0] // 2, local_k[1] // 2], groups=num_heads 87 | ) 88 | 89 | def forward(self, x): 90 | h = self.HW[0] 91 | w = self.HW[1] 92 | x = x.transpose(1, 2).reshape(-1, self.dim, h, w) 93 | x = self.local_mixer(x) 94 | x = x.reshape(-1, self.dim, h * w).transpose(1, 2) 95 | return x 96 | 97 | 98 | class Attention(nn.Module): 99 | def __init__( 100 | self, 101 | dim, 102 | num_heads=8, 103 | mixer="Global", 104 | HW=(8, 25), 105 | local_k=[7, 11], 106 | qkv_bias=False, 107 | qk_scale=None, 108 | attn_drop=0.0, 109 | proj_drop=0.0, 110 | ): 111 | super().__init__() 112 | self.num_heads = num_heads 113 | head_dim = dim // num_heads 114 | self.scale = qk_scale or head_dim ** -0.5 115 | 116 | self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias) 117 | self.attn_drop = nn.Dropout(attn_drop) 118 | self.proj = nn.Linear(dim, dim) 119 | self.proj_drop = nn.Dropout(proj_drop) 120 | self.HW = HW 121 | if HW is not None: 122 | H = HW[0] 123 | W = HW[1] 124 | self.N = H * W 125 | self.C = dim 126 | if mixer == "Local" and HW is not None: 127 | hk = local_k[0] 128 | wk = local_k[1] 129 | mask = torch.ones([H * W, H + hk - 1, W + wk - 1], dtype=torch.float32) 130 | for h in range(0, H): 131 | for w in range(0, W): 132 | mask[h * W + w, h : h + hk, w : w + wk] = 0.0 133 | mask_torch = torch.flatten( 134 | mask[:, hk // 2 : H + hk // 2, wk // 2 : W + wk // 2], 1 135 | ) 136 | mask_inf = torch.full([H * W, H * W], -np.inf, dtype=torch.float32) 137 | mask = torch.where(mask_torch < 1, mask_torch, mask_inf) 138 | self.mask = mask.unsqueeze(0) 139 | self.mask = self.mask.unsqueeze(0) 140 | # print(self.mask.size()) 141 | 142 | self.mixer = mixer 143 | 144 | def forward(self, x): 145 | if self.HW is not None: 146 | N = self.N 147 | C = self.C 148 | else: 149 | _, N, C = x.size() 150 | qkv = self.qkv(x) 151 | qkv = qkv.reshape((-1, N, 3, self.num_heads, C // self.num_heads)) 152 | qkv = qkv.permute(2, 0, 3, 1, 4) 153 | q, k, v = qkv[0] * self.scale, qkv[1], qkv[2] 154 | attn = q.matmul(k.permute(0, 1, 3, 2)) 155 | if self.mixer == "Local": 156 | attn += self.mask.to(attn.device) 157 | attn = nn.functional.softmax(attn, dim=-1) 158 | attn = self.attn_drop(attn) 159 | 160 | x = (attn.matmul(v)).permute(0, 2, 1, 3).reshape((-1, N, C)) 161 | x = self.proj(x) 162 | x = self.proj_drop(x) 163 | return x 164 | 165 | 166 | class Block(nn.Module): 167 | def __init__( 168 | self, 169 | dim, 170 | num_heads, 171 | mixer="Global", 172 | local_mixer=[7, 11], 173 | HW=[8, 25], 174 | mlp_ratio=4.0, 175 | qkv_bias=False, 176 | qk_scale=None, 177 | drop=0.0, 178 | attn_drop=0.0, 179 | drop_path=0.0, 180 | act_layer=nn.GELU, 181 | norm_layer="nn.LayerNorm", 182 | epsilon=1e-6, 183 | ): 184 | super().__init__() 185 | 186 | self.norm1 = norm_layer(dim) 187 | 188 | if mixer == "Global" or mixer == "Local": 189 | self.mixer = Attention( 190 | dim, 191 | num_heads=num_heads, 192 | mixer=mixer, 193 | HW=HW, 194 | local_k=local_mixer, 195 | qkv_bias=qkv_bias, 196 | qk_scale=qk_scale, 197 | attn_drop=attn_drop, 198 | proj_drop=drop, 199 | ) 200 | if mixer == "Conv": 201 | self.mixer = ConvMixer(dim, num_heads=num_heads, HW=HW, local_k=local_mixer) 202 | 203 | # NOTE: drop path for stochastic depth, we shall see if this is better than dropout here 204 | self.drop_path = DropPath(drop_path) if drop_path > 0.0 else Identity() 205 | 206 | self.norm2 = norm_layer(dim) 207 | 208 | mlp_hidden_dim = int(dim * mlp_ratio) 209 | self.mlp_ratio = mlp_ratio 210 | self.mlp = Mlp( 211 | in_features=dim, 212 | hidden_features=mlp_hidden_dim, 213 | act_layer=act_layer, 214 | drop=drop, 215 | ) 216 | 217 | def forward(self, x): 218 | self.N, self.C = x.shape[1:] 219 | x = x + self.drop_path(self.mixer(self.norm1(x))) 220 | x = x + self.drop_path(self.mlp(self.norm2(x))) 221 | return x 222 | 223 | def flops(self): 224 | flops = ( 225 | self.attn.flops() 226 | + self.N * self.C * 2 227 | + 2 * self.N * self.C * self.C * self.mlp_ratio 228 | ) 229 | return flops 230 | 231 | 232 | class PatchEmbed(nn.Module): 233 | """ Image to Patch Embedding 234 | """ 235 | 236 | def __init__(self, img_size=[32, 100], in_channels=3, embed_dim=768, sub_num=2): 237 | super().__init__() 238 | # img_size = to_2tuple(img_size) 239 | num_patches = (img_size[1] // (2 ** sub_num)) * (img_size[0] // (2 ** sub_num)) 240 | # num_patches = (img_size[1] ) * \ 241 | # (img_size[0] ) 242 | self.img_size = img_size 243 | self.num_patches = num_patches 244 | self.embed_dim = embed_dim 245 | self.norm = None 246 | if sub_num == 2: 247 | self.proj = nn.Sequential( 248 | nn.Conv2d(in_channels, embed_dim // 2, 3, 2, 1), 249 | nn.BatchNorm2d(embed_dim // 2), 250 | nn.GELU(), 251 | nn.Conv2d(embed_dim // 2, embed_dim, 3, 2, 1), 252 | nn.BatchNorm2d(embed_dim), 253 | nn.GELU(), 254 | ) 255 | if sub_num == 3: 256 | self.proj = nn.Sequential( 257 | nn.Conv2d(in_channels, embed_dim // 4, 3, 2, 1), 258 | nn.BatchNorm2d(embed_dim // 4), 259 | nn.GELU(), 260 | nn.Conv2d(embed_dim // 4, embed_dim // 2, 3, 2, 1), 261 | nn.BatchNorm2d(embed_dim // 2), 262 | nn.GELU(), 263 | nn.Conv2d(embed_dim // 2, embed_dim, 3, 2, 1), 264 | nn.BatchNorm2d(embed_dim), 265 | ) 266 | 267 | def forward(self, x): 268 | B, C, H, W = x.shape 269 | assert ( 270 | H == self.img_size[0] and W == self.img_size[1] 271 | ), f"Input image size ({H}*{W}) doesn't match model ({self.img_size[0]}*{self.img_size[1]})." 272 | # x1 = self.proj1(x) 273 | # x2 = self.proj2(x1).flatten(2).transpose(1, 2) 274 | # x1 = x1.flatten(2).transpose(1, 2) 275 | x = self.proj(x).flatten(2).transpose(1, 2) 276 | return x 277 | # return x1,x2 278 | 279 | def flops(self): 280 | Ho, Wo = self.img_size 281 | flops = ( 282 | Ho // 2 * Wo // 2 * 3 * self.embed_dim // 2 * (3 * 3) 283 | + Ho // 4 * Wo // 4 * self.embed_dim // 2 * self.embed_dim * (3 * 3) 284 | + Ho * Wo * self.embed_dim * 2 285 | ) 286 | return flops 287 | 288 | 289 | class SubSample(nn.Module): 290 | def __init__( 291 | self, 292 | in_channels, 293 | out_channels, 294 | types="Pool", 295 | stride=[2, 1], 296 | sub_norm="nn.LayerNorm", 297 | act=None, 298 | ): 299 | super().__init__() 300 | self.types = types 301 | if types == "Pool": 302 | self.avgpool = nn.AvgPool2d( 303 | kernel_size=[3, 5], stride=stride, padding=[1, 2] 304 | ) 305 | self.maxpool = nn.MaxPool2d( 306 | kernel_size=[3, 5], stride=stride, padding=[1, 2] 307 | ) 308 | self.proj = nn.Linear(in_channels, out_channels) 309 | elif types == "Unet": 310 | # For mini-Unet 311 | attn_mode = "nearest" 312 | else: 313 | self.conv = nn.Conv2d( 314 | in_channels, out_channels, kernel_size=3, stride=stride, padding=1 315 | ) 316 | self.norm = eval(sub_norm)(out_channels) 317 | if act is not None: 318 | self.act = act() 319 | else: 320 | self.act = None 321 | 322 | def forward(self, x): 323 | 324 | if self.types == "Pool": 325 | # x = x.transpose((0, 2, 1)) 326 | x1 = self.avgpool(x) 327 | x2 = self.maxpool(x) 328 | x = (x1 + x2) * 0.5 329 | out = self.proj(x.flatten(2).transpose(1, 2)) 330 | elif self.types == "Unet": 331 | # Apply mini U-Net on k 332 | features = [] 333 | for i in range(len(self.encoder)): 334 | x = self.encoder[i](x) 335 | features.append(x) 336 | x = self.block(x) 337 | for i in range(len(self.decoder) - 1): 338 | x = self.k_decoder[i](x) 339 | x = x + features[len(self.decoder) - 2 - i] 340 | out = self.decoder[-1](x) 341 | else: 342 | # self.H, self.W = x.shape[2:] 343 | x = self.conv(x) 344 | out = x.flatten(2).transpose(1, 2) 345 | out = self.norm(out) 346 | if self.act is not None: 347 | out = self.act(out) 348 | 349 | return out 350 | 351 | 352 | class SVTR(nn.Module): 353 | def __init__( 354 | self, 355 | # img_size=[32, 256], 356 | img_size=[32, 100], 357 | in_channels=3, 358 | embed_dim=[64, 128, 256], 359 | depth=[3, 6, 3], 360 | num_heads=[2, 4, 8], 361 | mixer=[ 362 | "Local", 363 | "Local", 364 | "Local", 365 | "Local", 366 | "Local", 367 | "Local", 368 | "Global", 369 | "Global", 370 | "Global", 371 | "Global", 372 | "Global", 373 | "Global", 374 | ], # Local atten, Global atten, Conv 375 | local_mixer=[[7, 11], [7, 11], [7, 11]], 376 | patch_merging="Conv", # Conv, Pool, None 377 | mlp_ratio=4, 378 | qkv_bias=True, 379 | qk_scale=None, 380 | drop_rate=0.0, 381 | last_drop=0.1, 382 | attn_drop_rate=0.0, 383 | drop_path_rate=0.1, 384 | norm_layer="nn.LayerNorm", 385 | sub_norm="nn.LayerNorm", 386 | epsilon=1e-6, 387 | out_channels=192, 388 | out_char_num=25, 389 | block_unit="Block", 390 | act="nn.GELU", 391 | last_stage=True, 392 | sub_num=2, 393 | **kwargs, 394 | ): 395 | super().__init__() 396 | self.img_size = img_size 397 | self.num_features = self.embed_dim = embed_dim 398 | self.out_channels = out_channels 399 | norm_layer = partial(eval(norm_layer), eps=epsilon) 400 | self.patch_embed = PatchEmbed( 401 | img_size=img_size, 402 | in_channels=in_channels, 403 | embed_dim=embed_dim[0], 404 | sub_num=sub_num, 405 | ) 406 | num_patches = self.patch_embed.num_patches 407 | # self.HW = [img_size[0], img_size[1]] 408 | self.HW = [img_size[0] // (2 ** sub_num), img_size[1] // (2 ** sub_num)] 409 | self.pos_embed = nn.Parameter(torch.zeros(1, num_patches, embed_dim[0])) 410 | # self.add_parameter("pos_embed", self.pos_embed) 411 | # self.cls_token = self.create_parameter( 412 | # shape=(1, 1, embed_dim), default_initializer=zeros_) 413 | # self.add_parameter("cls_token", self.cls_token) 414 | self.pos_drop = nn.Dropout(p=drop_rate) 415 | # self.up_linear = nn.Linear(256, 512) 416 | Block_unit = eval(block_unit) 417 | if block_unit == "CSWinBlock": 418 | split_size_h = [1, 2, 2] 419 | split_size_w = [5, 5, 25] 420 | ex_arg = [ 421 | { 422 | "reso": [img_size[0] // 4, img_size[1] // 4], 423 | "split_size_h": split_size_h[0], 424 | "split_size_w": split_size_w[0], 425 | }, 426 | { 427 | "reso": [img_size[0] // 8, img_size[1] // 4], 428 | "split_size_h": split_size_h[1], 429 | "split_size_w": split_size_w[1], 430 | }, 431 | { 432 | "reso": [img_size[0] // 16, img_size[1] // 4], 433 | "split_size_h": split_size_h[2], 434 | "split_size_w": split_size_w[2], 435 | }, 436 | ] 437 | else: 438 | ex_arg = [{"epsilon": epsilon}, {"epsilon": epsilon}, {"epsilon": epsilon}] 439 | dpr = np.linspace(0, drop_path_rate, sum(depth)) 440 | self.blocks1 = nn.ModuleList( 441 | [ 442 | Block_unit( 443 | dim=embed_dim[0], 444 | num_heads=num_heads[0], 445 | mixer=mixer[0 : depth[0]][i], 446 | HW=self.HW, 447 | local_mixer=local_mixer[0], 448 | mlp_ratio=mlp_ratio, 449 | qkv_bias=qkv_bias, 450 | qk_scale=qk_scale, 451 | drop=drop_rate, 452 | act_layer=eval(act), 453 | attn_drop=attn_drop_rate, 454 | drop_path=dpr[0 : depth[0]][i], 455 | norm_layer=norm_layer, 456 | **ex_arg[0], 457 | ) 458 | for i in range(depth[0]) 459 | ] 460 | ) 461 | if patch_merging is not None: 462 | self.sub_sample1 = SubSample( 463 | embed_dim[0], 464 | embed_dim[1], 465 | sub_norm=sub_norm, 466 | stride=[2, 1], 467 | types=patch_merging, 468 | ) # ConvBNLayer(embed_dim[0], embed_dim[1], kernel_size=3, stride=[2, 1], sub_norm=sub_norm) 469 | HW = [self.HW[0] // 2, self.HW[1]] 470 | # self.sub_sample1_0 = SubSample(embed_dim[0], embed_dim[0], sub_norm=sub_norm, stride=[2, 2], 471 | # types=patch_merging) # ConvBNLayer(embed_dim[0], embed_dim[1], kernel_size=3, stride=[2, 1], sub_norm=sub_norm) 472 | # HW = [self.HW[0] // 2, self.HW[1] // 2] 473 | # self.sub_sample1_1 = SubSample(embed_dim[0], embed_dim[1], sub_norm=sub_norm, stride=[1, 1], 474 | # types=patch_merging) # ConvBNLayer(embed_dim[0], embed_dim[1], kernel_size=3, stride=[2, 1], sub_norm=sub_norm) 475 | 476 | else: 477 | HW = self.HW 478 | self.patch_merging = patch_merging 479 | self.blocks2 = nn.ModuleList( 480 | [ 481 | Block_unit( 482 | dim=embed_dim[1], 483 | num_heads=num_heads[1], 484 | mixer=mixer[depth[0] : depth[0] + depth[1]][i], 485 | HW=HW, 486 | local_mixer=local_mixer[1], 487 | mlp_ratio=mlp_ratio, 488 | qkv_bias=qkv_bias, 489 | qk_scale=qk_scale, 490 | drop=drop_rate, 491 | act_layer=eval(act), 492 | attn_drop=attn_drop_rate, 493 | drop_path=dpr[depth[0] : depth[0] + depth[1]][i], 494 | norm_layer=norm_layer, 495 | **ex_arg[1], 496 | ) 497 | for i in range(depth[1]) 498 | ] 499 | ) 500 | if patch_merging is not None: 501 | self.sub_sample2 = SubSample( 502 | embed_dim[1], 503 | embed_dim[2], 504 | sub_norm=sub_norm, 505 | stride=[2, 1], 506 | types=patch_merging, 507 | ) # ConvBNLayer(embed_dim[1], embed_dim[2], kernel_size=3, stride=[2, 1], sub_norm=sub_norm) 508 | HW = [self.HW[0] // 4, self.HW[1]] 509 | # self.sub_sample2 = SubSample(embed_dim[1], embed_dim[2], sub_norm=sub_norm, stride=[2, 2], 510 | # types=patch_merging) # ConvBNLayer(embed_dim[1], embed_dim[2], kernel_size=3, stride=[2, 1], sub_norm=sub_norm) 511 | # HW = [self.HW[0] // 4, self.HW[1] // 4] 512 | else: 513 | HW = self.HW 514 | self.blocks3 = nn.ModuleList( 515 | [ 516 | Block_unit( 517 | dim=embed_dim[2], 518 | num_heads=num_heads[2], 519 | mixer=mixer[depth[0] + depth[1] :][i], 520 | HW=HW, 521 | local_mixer=local_mixer[2], 522 | mlp_ratio=mlp_ratio, 523 | qkv_bias=qkv_bias, 524 | qk_scale=qk_scale, 525 | drop=drop_rate, 526 | act_layer=eval(act), 527 | attn_drop=attn_drop_rate, 528 | drop_path=dpr[depth[0] + depth[1] :][i], 529 | norm_layer=norm_layer, 530 | **ex_arg[2], 531 | ) 532 | for i in range(depth[2]) 533 | ] 534 | ) 535 | if patch_merging is not None: 536 | # self.sub_sample1 = SubSample(embed_dim[0], embed_dim[1], sub_norm=sub_norm, stride=[2, 1], 537 | # types=patch_merging) # ConvBNLayer(embed_dim[0], embed_dim[1], kernel_size=3, stride=[2, 1], sub_norm=sub_norm) 538 | # HW = [self.HW[0] // 2, self.HW[1]] 539 | self.sub_sample3 = SubSample( 540 | embed_dim[2], 541 | out_channels, 542 | sub_norm=sub_norm, 543 | stride=[2, 1], 544 | types=patch_merging, 545 | ) # ConvBNLayer(embed_dim[0], embed_dim[1], kernel_size=3, stride=[2, 1], sub_norm=sub_norm) 546 | 547 | self.last_stage = last_stage 548 | if last_stage: 549 | self.avg_pool = nn.AdaptiveAvgPool2d([1, out_char_num]) 550 | self.linear = nn.Linear(384, 512) 551 | 552 | self.last_conv = nn.Conv2d( 553 | in_channels=embed_dim[2], 554 | out_channels=self.out_channels, 555 | kernel_size=(1, 1), 556 | stride=(1, 1), 557 | padding=0, 558 | bias=False, 559 | ) 560 | 561 | self.hardswish = nn.Hardswish() 562 | self.dropout = nn.Dropout(p=last_drop) 563 | self.norm = norm_layer(embed_dim[-1]) 564 | 565 | # Classifier head 566 | # self.head = nn.Linear(embed_dim, 567 | # class_num) if class_num > 0 else Identity() 568 | 569 | trunc_normal_(self.pos_embed, std=0.02) 570 | # trunc_normal_(self.cls_token) 571 | self.apply(self._init_weights) 572 | 573 | def _init_weights(self, m): 574 | if isinstance(m, nn.Linear): 575 | trunc_normal_(m.weight, std=0.02) 576 | if isinstance(m, nn.Linear) and m.bias is not None: 577 | nn.init.constant_(m.bias, 0) 578 | elif isinstance(m, nn.LayerNorm): 579 | nn.init.constant_(m.bias, 0) 580 | nn.init.constant_(m.bias, 1.0) 581 | elif isinstance(m, nn.Conv2d): 582 | nn.init.kaiming_normal_(m.weight, mode="fan_in") 583 | 584 | def forward_features(self, x, tpsnet=None): 585 | # B = x.shape[0] 586 | # out = [] 587 | 588 | B = x.shape[0] 589 | x = self.patch_embed(x) 590 | # out.append(rearrange(x1, 'b (h w) c -> b c h w',h=32,w=128)) 591 | # out.append(rearrange(x, 'b (h w) c -> b c h w',h=32,w=128)) 592 | # cls_tokens = self.cls_token.expand((B, -1, -1)) 593 | # x = paddle.concat((cls_tokens, x), axis=1) 594 | x = x + self.pos_embed 595 | x = self.pos_drop(x) 596 | for blk in self.blocks1: 597 | x = blk(x) 598 | if self.patch_merging is not None: 599 | x = self.sub_sample1( 600 | x.transpose(1, 2).reshape(B, self.embed_dim[0], self.HW[0], self.HW[1]) 601 | ) 602 | 603 | for blk in self.blocks2: 604 | x = blk(x) 605 | if self.patch_merging is not None: 606 | x = self.sub_sample2( 607 | x.transpose(1, 2).reshape( 608 | B, self.embed_dim[1], self.HW[0] // 2, self.HW[1] 609 | ) 610 | ) 611 | # x = self.sub_sample2(x.transpose(1, 2).reshape(B, self.embed_dim[1], self.HW[0] // 2, self.HW[1] // 2)) 612 | for blk in self.blocks3: 613 | x = blk(x) 614 | if self.patch_merging is not None: 615 | # x = self.sub_sample2(x.transpose(1, 2).reshape(B, self.embed_dim[1], self.HW[0] // 2, self.HW[1])) 616 | x = self.sub_sample3( 617 | x.transpose(1, 2).reshape( 618 | B, self.embed_dim[2], self.HW[0] // 4, self.HW[1] 619 | ) 620 | ) 621 | x = x.permute(0, 2, 1).reshape( 622 | [-1, self.out_channels, self.HW[0] // 8, self.HW[1]] 623 | ) 624 | return x 625 | 626 | def forward(self, x): 627 | x = self.forward_features(x) 628 | return x 629 | -------------------------------------------------------------------------------- /train.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | import random 5 | import string 6 | import argparse 7 | from datetime import date 8 | 9 | import torch 10 | import torch.backends.cudnn as cudnn 11 | import torch.nn.init as init 12 | import torch.utils.data 13 | import torch.nn.functional as F 14 | import numpy as np 15 | from torch.utils.tensorboard import SummaryWriter 16 | from tqdm import tqdm 17 | 18 | from utils import CTCLabelConverter, AttnLabelConverter, Averager, adjust_learning_rate 19 | from dataset import ( 20 | hierarchical_dataset, 21 | AlignCollate, 22 | concat_dataset, 23 | ) 24 | from model import Model 25 | from test import validation, benchmark_all_eval 26 | 27 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 28 | 29 | 30 | def train(opt, log): 31 | """ model configuration """ 32 | if "CTC" in opt.Prediction: 33 | converter = CTCLabelConverter(opt) 34 | else: 35 | converter = AttnLabelConverter(opt) 36 | opt.sos_token_index = converter.dict["[SOS]"] 37 | opt.eos_token_index = converter.dict["[EOS]"] 38 | opt.num_class = len(converter.character) 39 | 40 | model = Model(opt) 41 | print( 42 | "model input parameters", 43 | opt.imgH, 44 | opt.imgW, 45 | opt.num_fiducial, 46 | opt.input_channel, 47 | opt.output_channel, 48 | opt.hidden_size, 49 | opt.num_class, 50 | opt.batch_max_length, 51 | opt.Transformation, 52 | opt.FeatureExtraction, 53 | opt.SequenceModeling, 54 | opt.Prediction, 55 | ) 56 | 57 | # weight initialization 58 | for name, param in model.named_parameters(): 59 | if "localization_fc2" in name: 60 | print(f"Skip {name} as it is already initialized") 61 | continue 62 | 63 | if opt.FeatureExtraction == "Swin" and "FeatureExtraction" in name: 64 | continue 65 | 66 | try: 67 | if "bias" in name: 68 | init.constant_(param, 0.0) 69 | elif "weight" in name: 70 | init.kaiming_normal_(param) 71 | except Exception as e: # for batchnorm. 72 | if "weight" in name: 73 | param.data.fill_(1) 74 | continue 75 | 76 | # data parallel for multi-GPU 77 | model = torch.nn.DataParallel(model).to(device) 78 | model.train() 79 | if opt.saved_model != "": 80 | fine_tuning_log = f"### loading pretrained model from {opt.saved_model}\n" 81 | pretrained_state_dict = torch.load(opt.saved_model) 82 | 83 | for name, param in model.named_parameters(): 84 | try: 85 | param.data.copy_( 86 | pretrained_state_dict[name].data 87 | ) # load from pretrained model 88 | if opt.FT == "freeze": 89 | param.requires_grad = False # Freeze 90 | fine_tuning_log += f"pretrained layer (freezed): {name}\n" 91 | else: 92 | fine_tuning_log += f"pretrained layer: {name}\n" 93 | except: 94 | fine_tuning_log += f"non-pretrained layer: {name}\n" 95 | 96 | print(fine_tuning_log) 97 | log.write(fine_tuning_log + "\n") 98 | 99 | # print("Model:") 100 | # print(model) 101 | log.write(repr(model) + "\n") 102 | 103 | """ dataset preparation """ # for long data loading time, make model then load dataset! 104 | train_dataset, train_dataset_log = concat_dataset( 105 | root=opt.train_data, opt=opt, dataset_list=opt.select_data, mode="train" 106 | ) 107 | AlignCollate_train = AlignCollate(opt) 108 | train_loader = torch.utils.data.DataLoader( 109 | train_dataset, 110 | batch_size=opt.batch_size, 111 | shuffle=True, 112 | num_workers=int(opt.workers), 113 | collate_fn=AlignCollate_train, 114 | pin_memory=False, 115 | drop_last=False, 116 | ) 117 | train_loader_iter = iter(train_loader) 118 | log.write(train_dataset_log) 119 | print("-" * 80) 120 | log.write("-" * 80 + "\n") 121 | 122 | tmp_valid_data = [] 123 | for val_select_d in opt.select_data: 124 | tmp_valid_data.append(val_select_d.replace("SynthMLT_", "")) 125 | valid_select_data = set(tmp_valid_data) 126 | # print(valid_select_data) 127 | 128 | valid_dataset, valid_dataset_log = concat_dataset( 129 | root=opt.valid_data, opt=opt, dataset_list=valid_select_data, mode="test" 130 | ) 131 | 132 | AlignCollate_valid = AlignCollate(opt, mode="test") 133 | valid_loader = torch.utils.data.DataLoader( 134 | valid_dataset, 135 | batch_size=opt.batch_size, 136 | shuffle=True, # 'True' to check training progress with validation function. 137 | num_workers=int(opt.workers), 138 | collate_fn=AlignCollate_valid, 139 | pin_memory=False, 140 | ) 141 | log.write(valid_dataset_log) 142 | print("-" * 80) 143 | log.write("-" * 80 + "\n") 144 | 145 | """ setup loss """ 146 | if "CTC" in opt.Prediction: 147 | criterion = torch.nn.CTCLoss(zero_infinity=True).to(device) 148 | else: 149 | # ignore [PAD] token = ignore index 0 150 | criterion = torch.nn.CrossEntropyLoss(ignore_index=converter.dict["[PAD]"]).to( 151 | device 152 | ) 153 | 154 | # loss averager 155 | train_loss_avg = Averager() 156 | 157 | # filter that only require gradient descent 158 | filtered_parameters = [] 159 | params_num = [] 160 | for p in filter(lambda p: p.requires_grad, model.parameters()): 161 | filtered_parameters.append(p) 162 | params_num.append(np.prod(p.size())) 163 | print(f"Trainable params num: {sum(params_num)}") 164 | log.write(f"Trainable params num: {sum(params_num)}\n") 165 | # [print(name, p.numel()) for name, p in filter(lambda p: p[1].requires_grad, model.named_parameters())] 166 | 167 | # setup optimizer 168 | if opt.optimizer == "sgd": 169 | optimizer = torch.optim.SGD( 170 | filtered_parameters, 171 | lr=opt.lr, 172 | momentum=opt.sgd_momentum, 173 | weight_decay=opt.sgd_weight_decay, 174 | ) 175 | elif opt.optimizer == "adadelta": 176 | optimizer = torch.optim.Adadelta( 177 | filtered_parameters, lr=opt.lr, rho=opt.rho, eps=opt.eps 178 | ) 179 | elif opt.optimizer == "adam": 180 | optimizer = torch.optim.Adam(filtered_parameters, lr=opt.lr) 181 | 182 | elif opt.optimizer == "adamw": 183 | optimizer = torch.optim.AdamW(filtered_parameters, lr=opt.lr) 184 | print("Optimizer:") 185 | print(optimizer) 186 | log.write(repr(optimizer) + "\n") 187 | 188 | if "super" in opt.schedule: 189 | if opt.optimizer == "sgd": 190 | cycle_momentum = True 191 | else: 192 | cycle_momentum = False 193 | 194 | scheduler = torch.optim.lr_scheduler.OneCycleLR( 195 | optimizer, 196 | max_lr=opt.lr, 197 | cycle_momentum=cycle_momentum, 198 | div_factor=20, 199 | final_div_factor=1000, 200 | total_steps=opt.num_iter, 201 | ) 202 | print("Scheduler:") 203 | print(scheduler) 204 | log.write(repr(scheduler) + "\n") 205 | 206 | """ final options """ 207 | # print(opt) 208 | opt_log = "------------ Options -------------\n" 209 | args = vars(opt) 210 | for k, v in args.items(): 211 | if str(k) == "character" and len(v) > 500: 212 | opt_log += f"{str(k)}: So many characters to show all: number of characters: {len(v)}\n" 213 | else: 214 | opt_log += f"{str(k)}: {str(v)}\n" 215 | opt_log += "---------------------------------------\n" 216 | print(opt_log) 217 | log.write(opt_log) 218 | log.close() 219 | 220 | """ start training """ 221 | start_iter = 0 222 | if opt.saved_model != "": 223 | try: 224 | start_iter = int(opt.saved_model.split("_")[-1].split(".")[0]) 225 | print(f"continue to train, start_iter: {start_iter}") 226 | except: 227 | pass 228 | 229 | start_time = time.time() 230 | best_score = -1 231 | 232 | # training loop 233 | for iteration in tqdm( 234 | range(start_iter + 1, opt.num_iter + 1), 235 | total=opt.num_iter, 236 | position=0, 237 | leave=True, 238 | ): 239 | 240 | try: 241 | image_tensors, labels = train_loader_iter.next() 242 | except StopIteration: 243 | train_loader_iter = iter(train_loader) 244 | image_tensors, labels = train_loader_iter.next() 245 | 246 | image = image_tensors.to(device) 247 | labels_index, labels_length = converter.encode( 248 | labels, batch_max_length=opt.batch_max_length 249 | ) 250 | batch_size = image.size(0) 251 | 252 | # default recognition loss part 253 | if "CTC" in opt.Prediction: 254 | preds = model(image) 255 | preds_size = torch.IntTensor([preds.size(1)] * batch_size) 256 | preds_log_softmax = preds.log_softmax(2).permute(1, 0, 2) 257 | loss = criterion(preds_log_softmax, labels_index, preds_size, labels_length) 258 | else: 259 | preds = model(image, labels_index[:, :-1]) # align with Attention.forward 260 | target = labels_index[:, 1:] # without [SOS] Symbol 261 | loss = criterion( 262 | preds.view(-1, preds.shape[-1]), target.contiguous().view(-1) 263 | ) 264 | 265 | model.zero_grad() 266 | loss.backward() 267 | torch.nn.utils.clip_grad_norm_(model.parameters(), opt.grad_clip) 268 | optimizer.step() 269 | train_loss_avg.add(loss) 270 | 271 | if "super" in opt.schedule: 272 | scheduler.step() 273 | else: 274 | adjust_learning_rate(optimizer, iteration, opt) 275 | 276 | # validation part. 277 | # To see training progress, we also conduct validation when 'iteration == 1' 278 | if iteration % opt.val_interval == 0 or iteration == 1: 279 | # for validation log 280 | with open(f"./saved_models/{opt.exp_name}/log_train.txt", "a") as log: 281 | model.eval() 282 | with torch.no_grad(): 283 | ( 284 | valid_loss, 285 | current_score, 286 | preds, 287 | confidence_score, 288 | labels, 289 | infer_time, 290 | length_of_data, 291 | ) = validation(model, criterion, valid_loader, converter, opt) 292 | model.train() 293 | 294 | # keep best accuracy model (on valid dataset) 295 | # Do not use this on test datasets. It would be an unfair comparison (training should be done without referring test set). 296 | if current_score > best_score: 297 | best_score = current_score 298 | torch.save( 299 | model.state_dict(), 300 | f"./saved_models/{opt.exp_name}/best_score.pth", 301 | ) 302 | 303 | # validation log: loss, lr, accuracy, time. 304 | lr = optimizer.param_groups[0]["lr"] 305 | elapsed_time = time.time() - start_time 306 | valid_log = f"\n[{iteration}/{opt.num_iter}] Train_loss: {train_loss_avg.val():0.5f}, Valid_loss: {valid_loss:0.5f}\n" 307 | valid_log += f'{"Current_score":17s}: {current_score:0.2f}, Current_lr: {lr:0.7f}\n' 308 | valid_log += f'{"Best_score":17s}: {best_score:0.2f}, Infer_time: {infer_time:0.1f}, Elapsed_time: {elapsed_time:0.1f}' 309 | 310 | # show some predicted results 311 | dashed_line = "-" * 80 312 | head = f'{"Ground Truth":25s} | {"Prediction":25s} | Confidence Score & T/F' 313 | predicted_result_log = f"{dashed_line}\n{head}\n{dashed_line}\n" 314 | for gt, pred, confidence in zip( 315 | labels[:5], preds[:5], confidence_score[:5] 316 | ): 317 | if "Attn" in opt.Prediction: 318 | gt = gt[: gt.find("[EOS]")] 319 | pred = pred[: pred.find("[EOS]")] 320 | 321 | predicted_result_log += f"{gt:25s} | {pred:25s} | {confidence:0.4f}\t{str(pred == gt)}\n" 322 | predicted_result_log += f"{dashed_line}" 323 | valid_log = f"{valid_log}\n{predicted_result_log}" 324 | print(valid_log) 325 | log.write(valid_log + "\n") 326 | 327 | opt.writer.add_scalar( 328 | "train/train_loss", float(f"{train_loss_avg.val():0.5f}"), iteration 329 | ) 330 | opt.writer.add_scalar("train/lr", float(f"{lr:0.7f}"), iteration) 331 | opt.writer.add_scalar( 332 | "train/elapsed_time", float(f"{elapsed_time:0.1f}"), iteration 333 | ) 334 | opt.writer.add_scalar( 335 | "valid/valid_loss", float(f"{valid_loss:0.5f}"), iteration 336 | ) 337 | opt.writer.add_scalar( 338 | "valid/current_score", float(f"{current_score:0.2f}"), iteration 339 | ) 340 | opt.writer.add_scalar( 341 | "valid/best_score", float(f"{best_score:0.2f}"), iteration 342 | ) 343 | 344 | train_loss_avg.reset() 345 | 346 | """ Evaluation at the end of training """ 347 | # print("Start evaluation on benchmark testset") 348 | """ keep evaluation model and result logs """ 349 | os.makedirs(f"./result/{opt.exp_name}", exist_ok=True) 350 | os.makedirs(f"./evaluation_log", exist_ok=True) 351 | saved_best_model = f"./saved_models/{opt.exp_name}/best_score.pth" 352 | # os.system(f'cp {saved_best_model} ./result/{opt.exp_name}/') 353 | model.load_state_dict(torch.load(f"{saved_best_model}")) 354 | 355 | model.eval() 356 | with torch.no_grad(): 357 | total_accuracy, eval_data_list, accuracy_list = benchmark_all_eval( 358 | model, criterion, converter, opt 359 | ) 360 | 361 | opt.writer.add_scalar( 362 | "test/total_accuracy", float(f"{total_accuracy:0.2f}"), iteration 363 | ) 364 | for eval_data, accuracy in zip(eval_data_list, accuracy_list): 365 | accuracy = float(accuracy) 366 | opt.writer.add_scalar(f"test/{eval_data}", float(f"{accuracy:0.2f}"), iteration) 367 | 368 | print( 369 | f'finished the experiment: {opt.exp_name}, "CUDA_VISIBLE_DEVICES" was {opt.CUDA_VISIBLE_DEVICES}' 370 | ) 371 | 372 | 373 | if __name__ == "__main__": 374 | parser = argparse.ArgumentParser() 375 | parser.add_argument( 376 | "--train_data", default="data/training", help="path to training dataset", 377 | ) 378 | parser.add_argument( 379 | "--valid_data", default="data/validation", help="path to validation dataset", 380 | ) 381 | parser.add_argument( 382 | "--eval_data", default="data/evaluation/", help="path to evaluation dataset", 383 | ) 384 | parser.add_argument( 385 | "--workers", type=int, default=4, help="number of data loading workers" 386 | ) 387 | parser.add_argument("--batch_size", type=int, default=128, help="input batch size") 388 | parser.add_argument( 389 | "--num_iter", type=int, default=100000, help="number of iterations to train for" 390 | ) 391 | parser.add_argument( 392 | "--val_interval", 393 | type=int, 394 | default=2000, 395 | help="Interval between each validation", 396 | ) 397 | parser.add_argument( 398 | "--log_multiple_test", action="store_true", help="log_multiple_test" 399 | ) 400 | parser.add_argument( 401 | "--FT", type=str, default="init", help="whether to do fine-tuning |init|freeze|" 402 | ) 403 | parser.add_argument( 404 | "--grad_clip", type=float, default=5, help="gradient clipping value. default=5" 405 | ) 406 | """ Optimizer """ 407 | parser.add_argument( 408 | "--optimizer", type=str, default="adamw", help="optimizer |sgd|adadelta|adam|" 409 | ) 410 | parser.add_argument( 411 | "--lr", 412 | type=float, 413 | # default=0.0005, 414 | default=0.001, 415 | help="learning rate, default=1.0 for Adadelta, 0.0005 for Adam", 416 | ) 417 | parser.add_argument( 418 | "--sgd_momentum", default=0.9, type=float, help="momentum for SGD" 419 | ) 420 | parser.add_argument( 421 | "--sgd_weight_decay", default=0.000001, type=float, help="weight decay for SGD" 422 | ) 423 | parser.add_argument( 424 | "--rho", 425 | type=float, 426 | default=0.95, 427 | help="decay rate rho for Adadelta. default=0.95", 428 | ) 429 | parser.add_argument( 430 | "--eps", type=float, default=1e-8, help="eps for Adadelta. default=1e-8" 431 | ) 432 | parser.add_argument( 433 | "--schedule", 434 | default="super", 435 | nargs="*", 436 | help="(learning rate schedule. default is super for super convergence, 1 for None, [0.6, 0.8] for the same setting with ASTER", 437 | ) 438 | parser.add_argument( 439 | "--lr_drop_rate", 440 | type=float, 441 | default=0.1, 442 | help="lr_drop_rate. default is the same setting with ASTER", 443 | ) 444 | """ Model Architecture """ 445 | parser.add_argument("--model_name", type=str, required=True, help="CRNN|SVTR") 446 | parser.add_argument( 447 | "--num_fiducial", 448 | type=int, 449 | default=20, 450 | help="number of fiducial points of TPS-STN", 451 | ) 452 | parser.add_argument( 453 | "--input_channel", 454 | type=int, 455 | default=3, 456 | help="the number of input channel of Feature extractor", 457 | ) 458 | parser.add_argument( 459 | "--output_channel", 460 | type=int, 461 | default=512, 462 | help="the number of output channel of Feature extractor", 463 | ) 464 | parser.add_argument( 465 | "--hidden_size", type=int, default=256, help="the size of the LSTM hidden state" 466 | ) 467 | """ Data processing """ 468 | parser.add_argument( 469 | "--select_data", type=str, default="/", help="select training data", 470 | ) 471 | parser.add_argument( 472 | "--batch_ratio", 473 | type=str, 474 | help="assign ratio for each selected data in the batch", 475 | ) 476 | parser.add_argument( 477 | "--data_usage", 478 | type=str, 479 | default="all", 480 | help="assign number of data for each selected data", 481 | ) 482 | parser.add_argument( 483 | "--batch_max_length", type=int, default=25, help="maximum-label-length" 484 | ) 485 | parser.add_argument( 486 | "--imgH", type=int, default=32, help="the height of the input image" 487 | ) 488 | parser.add_argument( 489 | "--imgW", type=int, default=100, help="the width of the input image" 490 | ) 491 | parser.add_argument("--NED", default=True, help="For Normalized edit_distance") 492 | parser.add_argument( 493 | "--PAD", 494 | action="store_true", 495 | help="whether to keep ratio then pad for image resize", 496 | ) 497 | parser.add_argument( 498 | "--Aug", 499 | type=str, 500 | default="None", 501 | help="whether to use augmentation |None|Crop|Rot|", 502 | ) 503 | parser.add_argument( 504 | "--test", type=str, default="None", help="to verify temporal try" 505 | ) 506 | """ exp_name and etc """ 507 | parser.add_argument("--exp_name", help="Where to store logs and models") 508 | parser.add_argument( 509 | "--manual_seed", type=int, default=21, help="for random seed setting" 510 | ) 511 | parser.add_argument( 512 | "--saved_model", default="", help="path to model to continue training" 513 | ) 514 | parser.add_argument( 515 | "--dataset_name", default="MLT19", help="dataset name |MLT19|", 516 | ) 517 | parser.add_argument( 518 | "--data_usage_LAT", 519 | type=str, 520 | default="all", 521 | help="assign number of data for each selected data", 522 | ) 523 | parser.add_argument( 524 | "--data_usage_SynthMLT", 525 | type=str, 526 | default="all", 527 | help="assign number of data for each selected data", 528 | ) 529 | 530 | opt = parser.parse_args() 531 | 532 | opt.select_data = opt.select_data.split("-") 533 | tmp_select_data = [] 534 | for data_group in opt.select_data: 535 | if "lang_" in data_group: 536 | for lang in list(data_group.replace("lang_", "")): 537 | if lang == "A": 538 | tmp_select_data.append("ARA") 539 | elif lang == "L": 540 | tmp_select_data.append("LAT") 541 | elif lang == "C": 542 | tmp_select_data.append("CHI") 543 | elif lang == "J": 544 | tmp_select_data.append("JPN") 545 | elif lang == "K": 546 | tmp_select_data.append("KOR") 547 | elif lang == "B": 548 | tmp_select_data.append("BEN") 549 | elif lang == "H": 550 | tmp_select_data.append("HIN") 551 | 552 | elif "SynthMLT_" in data_group: 553 | for lang in list(data_group.replace("SynthMLT_", "")): 554 | if lang == "A": 555 | tmp_select_data.append("SynthMLT_ARA") 556 | elif lang == "L": 557 | tmp_select_data.append("SynthMLT_LAT") 558 | elif lang == "C": 559 | tmp_select_data.append("SynthMLT_CHI") 560 | elif lang == "J": 561 | tmp_select_data.append("SynthMLT_JPN") 562 | elif lang == "K": 563 | tmp_select_data.append("SynthMLT_KOR") 564 | elif lang == "B": 565 | tmp_select_data.append("SynthMLT_BEN") 566 | elif lang == "H": 567 | tmp_select_data.append("SynthMLT_HIN") 568 | 569 | else: 570 | tmp_select_data.append(data_group) 571 | 572 | opt.select_data = tmp_select_data 573 | print(opt.select_data) 574 | 575 | with open(f"charset/MLT19_charset.txt", "r", encoding="utf-8") as file: 576 | opt.character = file.read().splitlines() 577 | 578 | # for convenience 579 | if opt.model_name == "CRNN": # CRNN 580 | opt.Transformation = "None" 581 | opt.FeatureExtraction = "VGG" 582 | opt.SequenceModeling = "BiLSTM" 583 | opt.Prediction = "CTC" 584 | 585 | elif opt.model_name == "SVTR": # SVTR 586 | opt.Transformation = "None" 587 | opt.FeatureExtraction = "SVTR" 588 | opt.SequenceModeling = "None" 589 | opt.Prediction = "CTC" 590 | 591 | """ Seed and GPU setting """ 592 | random.seed(opt.manual_seed) 593 | np.random.seed(opt.manual_seed) 594 | torch.manual_seed(opt.manual_seed) 595 | torch.cuda.manual_seed_all(opt.manual_seed) # if you are using multi-GPU. 596 | torch.cuda.manual_seed(opt.manual_seed) 597 | 598 | cudnn.benchmark = True # It fasten training. 599 | cudnn.deterministic = True 600 | 601 | opt.gpu_name = "_".join(torch.cuda.get_device_name().split()) 602 | opt.CUDA_VISIBLE_DEVICES = os.environ["CUDA_VISIBLE_DEVICES"] 603 | opt.num_gpu = torch.cuda.device_count() 604 | if opt.num_gpu > 1: 605 | print( 606 | "We recommend to use 1 GPU, check your GPU number, you would miss CUDA_VISIBLE_DEVICES=0 or typo" 607 | ) 608 | print("To use multi-gpu setting, remove or comment out these lines") 609 | sys.exit() 610 | 611 | """ directory and log setting """ 612 | if not opt.exp_name: 613 | opt.exp_name = f"Seed{opt.manual_seed}-{opt.model_name}-{opt.gpu_name}" 614 | 615 | os.makedirs(f"./saved_models/{opt.exp_name}", exist_ok=True) 616 | log = open(f"./saved_models/{opt.exp_name}/log_train.txt", "a") 617 | command_line_input = " ".join(sys.argv) 618 | print( 619 | f"Command line input: CUDA_VISIBLE_DEVICES={opt.CUDA_VISIBLE_DEVICES} python {command_line_input}" 620 | ) 621 | log.write( 622 | f"Command line input: CUDA_VISIBLE_DEVICES={opt.CUDA_VISIBLE_DEVICES} python {command_line_input}\n" 623 | ) 624 | os.makedirs(f"./tensorboard", exist_ok=True) 625 | opt.writer = SummaryWriter(log_dir=f"./tensorboard/{opt.exp_name}") 626 | 627 | train(opt, log) 628 | -------------------------------------------------------------------------------- /charset/MLT19_charset.txt: -------------------------------------------------------------------------------- 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 | A 32 | À 33 |  34 | Ä 35 | B 36 | C 37 | Ç 38 | D 39 | E 40 | È 41 | É 42 | Ê 43 | F 44 | G 45 | H 46 | I 47 | Ì 48 | J 49 | K 50 | L 51 | M 52 | N 53 | O 54 | Ò 55 | Ô 56 | Ö 57 | P 58 | Q 59 | R 60 | S 61 | T 62 | U 63 | Ù 64 | Ü 65 | V 66 | W 67 | X 68 | Y 69 | Z 70 | a 71 | à 72 | â 73 | ä 74 | b 75 | c 76 | ç 77 | d 78 | e 79 | è 80 | é 81 | ê 82 | f 83 | g 84 | h 85 | i 86 | ì 87 | î 88 | j 89 | k 90 | l 91 | m 92 | n 93 | o 94 | ò 95 | ó 96 | ô 97 | ö 98 | p 99 | q 100 | r 101 | s 102 | t 103 | u 104 | ù 105 | ú 106 | û 107 | ü 108 | v 109 | w 110 | x 111 | y 112 | z 113 | ß 114 | Œ 115 | œ 116 | ء 117 | ا 118 | آ 119 | أ 120 | إ 121 | ب 122 | ة 123 | ت 124 | ث 125 | ج 126 | ح 127 | خ 128 | د 129 | ذ 130 | ر 131 | ز 132 | س 133 | ش 134 | ص 135 | ض 136 | ط 137 | ظ 138 | ع 139 | غ 140 | ف 141 | ق 142 | ك 143 | ل 144 | م 145 | ن 146 | ه 147 | و 148 | ؤ 149 | ى 150 | ي 151 | ئ 152 | ٫ 153 | ڥ 154 | ڨ 155 | अ 156 | अँ 157 | अं 158 | अॅ 159 | अॅँ 160 | आ 161 | आँ 162 | आं 163 | इ 164 | इं 165 | ई 166 | ईं 167 | उ 168 | ऊ 169 | ऊँ 170 | ऊं 171 | ऋ 172 | ए 173 | ऐं 174 | ऑ 175 | ओ 176 | ओं 177 | औ 178 | औं 179 | क 180 | कँ 181 | कं 182 | का 183 | काँ 184 | कां 185 | कि 186 | की 187 | कु 188 | कुं 189 | कू 190 | कृ 191 | कॅ 192 | के 193 | कें 194 | कै 195 | कॉ 196 | को 197 | क्ट 198 | क्टं 199 | क्ट्रि 200 | क्त 201 | क्ति 202 | क्त्दा 203 | क्मि 204 | क्य 205 | क्र 206 | क्रं 207 | क्रा 208 | क्रां 209 | क्रि 210 | क्रिं 211 | क्री 212 | क्रॉ 213 | क्लि 214 | क्ल्प 215 | क्वा 216 | क्श 217 | क्शं 218 | क्ष 219 | क्षा 220 | क्षि 221 | क्षे 222 | क्ष्मी 223 | क्ष्म्ण 224 | क्स 225 | क्स् 226 | ख 227 | खं 228 | खा 229 | खि 230 | खी 231 | खे 232 | ग 233 | गं 234 | गा 235 | गां 236 | गि 237 | गी 238 | गु 239 | गृ 240 | गे 241 | गो 242 | गौ 243 | ग्ण 244 | ग्णे 245 | ग्न 246 | ग्य 247 | ग्या 248 | ग्यु 249 | ग्र 250 | ग्रं 251 | ग्री 252 | ग्रु 253 | ग्रे 254 | ग्लि 255 | ग्लो 256 | घ 257 | घा 258 | घां 259 | घु 260 | घे 261 | घो 262 | घ्या 263 | घ्वा 264 | च 265 | चँ 266 | चं 267 | चा 268 | चि 269 | चिं 270 | ची 271 | चु 272 | चे 273 | चें 274 | चै 275 | चो 276 | चौ 277 | च्छ 278 | च्छ्ता 279 | च्या 280 | च्व्हा 281 | छ 282 | छा 283 | छो 284 | ज 285 | जं 286 | जा 287 | जि 288 | जिं 289 | जी 290 | जु 291 | जे 292 | जें 293 | जॉ 294 | जो 295 | ज् 296 | ज्ज 297 | ज्जा 298 | ज्जी 299 | ज्ञ 300 | ज्ञा 301 | ज्य 302 | ज्या 303 | ज्ये 304 | ज्वे 305 | झ 306 | झं 307 | झा 308 | झे 309 | ट 310 | टं 311 | टा 312 | टि 313 | टी 314 | टीं 315 | टे 316 | टो 317 | ट्ट 318 | ट्टी 319 | ट्टे 320 | ट्य 321 | ट्रा 322 | ट्राँ 323 | ट्री 324 | ट्रे 325 | ट्रो 326 | ट्स 327 | ठ 328 | ठा 329 | ठी 330 | ठे 331 | ठ्ठ 332 | ड 333 | डं 334 | ड़ 335 | ड़ी 336 | डा 337 | डां 338 | डि 339 | डी 340 | डु 341 | डे 342 | डें 343 | डो 344 | ड्डा 345 | ड्र 346 | ड्रे 347 | ड्ला 348 | ढ 349 | ढ़ी 350 | ढा 351 | ढी 352 | ढे 353 | ढो 354 | ण 355 | णा 356 | णि 357 | णी 358 | णू 359 | णे 360 | ण्ड 361 | ण्डा 362 | ण्णा 363 | ण्य 364 | ण्या 365 | त 366 | तं 367 | ता 368 | तां 369 | ति 370 | ती 371 | तु 372 | तू 373 | तृ 374 | ते 375 | तो 376 | तों 377 | त्त 378 | त्ता 379 | त्ते 380 | त्न 381 | त्ना 382 | त्मा 383 | त्य 384 | त्या 385 | त्यि 386 | त्य्मे 387 | त्र 388 | त्रा 389 | त्रि 390 | त्री 391 | त्रे 392 | त्र्य 393 | त्र्या 394 | त्स 395 | त्सं 396 | त्सा 397 | थ 398 | था 399 | थां 400 | थि 401 | थी 402 | थु 403 | थे 404 | थो 405 | थ्वी 406 | द 407 | दा 408 | दां 409 | दि 410 | दी 411 | दु 412 | दुं 413 | दू 414 | दृ 415 | दे 416 | दो 417 | द्गु 418 | द्द 419 | द्दी 420 | द्द् 421 | द्ध 422 | द्धा 423 | द्धि 424 | द्धी 425 | द्ध् 426 | द्य 427 | द्या 428 | द्यो 429 | द्र 430 | द्रा 431 | द्री 432 | द्रे 433 | द्वा 434 | ध 435 | धं 436 | धा 437 | धि 438 | धी 439 | धु 440 | धे 441 | धो 442 | धों 443 | धौ 444 | ध्द 445 | ध्दी 446 | ध्य 447 | ध्या 448 | ध्व 449 | न 450 | नं 451 | ना 452 | नां 453 | नि 454 | निं 455 | नी 456 | नु 457 | नू 458 | नॅ 459 | ने 460 | नै 461 | नॉ 462 | नो 463 | नों 464 | न्ट 465 | न्ट्र 466 | न्ट्री 467 | न्ड 468 | न्ड्री 469 | न्त 470 | न्द्र 471 | न्द्रा 472 | न्न 473 | न्य 474 | न्यः 475 | न्या 476 | न्यु 477 | न्ये 478 | न्स 479 | न्सी 480 | न्स् 481 | न्स्ता 482 | न्ही 483 | न्हे 484 | प 485 | पँ 486 | पं 487 | पा 488 | पाँ 489 | पां 490 | पि 491 | पिं 492 | पी 493 | पीं 494 | पु 495 | पू 496 | पृ 497 | पॅ 498 | पे 499 | पै 500 | पॉ 501 | पो 502 | पौ 503 | प्ट 504 | प्त 505 | प्ती 506 | प्ना 507 | प्प 508 | प्पा 509 | प्यु 510 | प्र 511 | प्रा 512 | प्रि 513 | प्रिं 514 | प्रे 515 | प्रो 516 | प्रौ 517 | प्ला 518 | प्ले 519 | प्लॉ 520 | फ 521 | फँ 522 | फा 523 | फि 524 | फु 525 | फे 526 | फो 527 | फ्स् 528 | ब 529 | बँ 530 | बं 531 | बा 532 | बाँ 533 | बि 534 | बी 535 | बु 536 | बॅ 537 | बे 538 | बै 539 | बैँ 540 | बैं 541 | बो 542 | बौ 543 | ब्ध 544 | ब्धि 545 | ब्ब 546 | ब्या 547 | ब्यु 548 | ब्र 549 | ब्री 550 | ब्ला 551 | ब्ली 552 | ब्ल्यू 553 | भ 554 | भं 555 | भा 556 | भां 557 | भि 558 | भिं 559 | भी 560 | भु 561 | भू 562 | भे 563 | भै 564 | भो 565 | भों 566 | भ्यं 567 | म 568 | मं 569 | मः 570 | मा 571 | मां 572 | मि 573 | मी 574 | मु 575 | मू 576 | मृ 577 | मे 578 | में 579 | मै 580 | मो 581 | म् 582 | म्चं 583 | म्प 584 | म्पो 585 | म्प्ले 586 | म्ब 587 | म्म 588 | म्मा 589 | म्र 590 | म्रा 591 | म्स 592 | म्ह 593 | म्हा 594 | म्ही 595 | य 596 | यं 597 | या 598 | याँ 599 | यां 600 | यु 601 | यू 602 | ये 603 | यो 604 | य्य 605 | र 606 | रं 607 | रा 608 | रां 609 | रि 610 | री 611 | रु 612 | रू 613 | रूं 614 | रे 615 | रें 616 | रॉ 617 | रो 618 | र्क 619 | र्किं 620 | र्कु 621 | र्के 622 | र्ग 623 | र्गा 624 | र्गी 625 | र्च 626 | र्चि 627 | र्जी 628 | र्ट 629 | र्टी 630 | र्ट्मे 631 | र्ट्में 632 | र्ट्स् 633 | र्ड 634 | र्डा 635 | र्ण 636 | र्ती 637 | र्थ 638 | र्थी 639 | र्द 640 | र्दि 641 | र्ध 642 | र्धि 643 | र्नि 644 | र्फ 645 | र्म 646 | र्मा 647 | र्मि 648 | र्य 649 | र्या 650 | र्ल 651 | र्व 652 | र्वां 653 | र्वि 654 | र्वे 655 | र्श 656 | र्श्व 657 | र्ष 658 | र्षा 659 | र्षी 660 | र्स 661 | र्सिं 662 | ल 663 | लँ 664 | लं 665 | ला 666 | लां 667 | लि 668 | ली 669 | लीं 670 | लु 671 | लू 672 | ले 673 | लॉ 674 | लो 675 | लों 676 | ल्टी 677 | ल्ड 678 | ल्डीं 679 | ल्प 680 | ल्म 681 | ल्य 682 | ल्या 683 | ल्रा 684 | ल्ली 685 | ल्व्ह 686 | ल्स 687 | ल्स् 688 | ल्हा 689 | ळ 690 | ळं 691 | ळा 692 | ळी 693 | ळु 694 | ळू 695 | ळे 696 | ळो 697 | व 698 | वं 699 | वा 700 | वां 701 | वि 702 | विं 703 | वी 704 | वु 705 | वू 706 | वृ 707 | वे 708 | वें 709 | वै 710 | वैं 711 | व्दै 712 | व्य 713 | व्यं 714 | व्या 715 | व्ह 716 | व्हा 717 | व्हे 718 | व्हो 719 | श 720 | शं 721 | शा 722 | शां 723 | शि 724 | शिं 725 | शी 726 | शु 727 | शे 728 | शें 729 | शै 730 | शॉ 731 | शो 732 | शौ 733 | श्चं 734 | श्पां 735 | श्मा 736 | श्य 737 | श्या 738 | श्र 739 | श्रा 740 | श्रां 741 | श्री 742 | श्रृं 743 | श्रे 744 | श्व 745 | श्व्र 746 | श्ह 747 | ष 748 | षां 749 | षि 750 | षे 751 | ष्ट 752 | ष्टी 753 | ष्टृ 754 | ष्ट्र 755 | ष्ट्रा 756 | ष्ट्री 757 | ष्ठ 758 | ष्ठा 759 | ष्ण 760 | ष्णा 761 | ष्णु 762 | ष्णू 763 | ष्प 764 | ष्य 765 | स 766 | सं 767 | सा 768 | सां 769 | सि 770 | सिं 771 | सी 772 | सु 773 | सुं 774 | सू 775 | सृ 776 | से 777 | सें 778 | सै 779 | सो 780 | सौ 781 | स् 782 | स्क 783 | स्कू 784 | स्कृ 785 | स्जि 786 | स्ट 787 | स्टँ 788 | स्टा 789 | स्टि 790 | स्टी 791 | स्टु 792 | स्टे 793 | स्टॉ 794 | स्टो 795 | स्ट्री 796 | स्ठ 797 | स्त 798 | स्तं 799 | स्ता 800 | स्ति 801 | स्तु 802 | स्त्या 803 | स्त्र 804 | स्त्री 805 | स्थ 806 | स्था 807 | स्थे 808 | स्नँ 809 | स्नॅ 810 | स्पा 811 | स्पि 812 | स्पी 813 | स्पे 814 | स्म 815 | स्मा 816 | स्मृ 817 | स्मे 818 | स्य 819 | स्या 820 | स्री 821 | स्व 822 | स्वा 823 | स्वी 824 | ह 825 | हं 826 | हा 827 | हाँ 828 | हां 829 | हि 830 | हिं 831 | ही 832 | हु 833 | हू 834 | हे 835 | है 836 | हॉ 837 | हो 838 | हौ 839 | ह्वा 840 | ॐ 841 | ঃ 842 | অ 843 | অা 844 | অ্যা 845 | আ 846 | আা 847 | ই 848 | ইং 849 | ঈ 850 | উ 851 | ঋ 852 | এ 853 | এী 854 | এ্য 855 | এ্যা 856 | ও 857 | ওঁ 858 | ঔ 859 | ঔঁ 860 | ক 861 | কং 862 | কা 863 | কাঁ 864 | কি 865 | কিং 866 | কী 867 | কু 868 | কৃ 869 | কৃা 870 | কৄ 871 | কে 872 | কো 873 | কোং 874 | ক্ট 875 | ক্ত 876 | ক্তি 877 | ক্যা 878 | ক্র 879 | ক্রঃ 880 | ক্রা 881 | ক্রি 882 | ক্রী 883 | ক্রে 884 | ক্লা 885 | ক্লি 886 | ক্ল্ু 887 | ক্ষ 888 | ক্ষা 889 | ক্ষী 890 | ক্ষু 891 | ক্ষে 892 | ক্ষ্মী 893 | ক্স 894 | খ 895 | খা 896 | খাঁ 897 | খি 898 | খু 899 | খূ 900 | খে 901 | খো 902 | খ্রীঃ 903 | গ 904 | গা 905 | গাঁ 906 | গি 907 | গিং 908 | গী 909 | গু 910 | গুঁ 911 | গৃ 912 | গৄ 913 | গে 914 | গো 915 | গৌ 916 | গ্য 917 | গ্যা 918 | গ্র 919 | গ্রা 920 | গ্রী 921 | গ্রে 922 | গ্লা 923 | ঘ 924 | ঘা 925 | ঘো 926 | ঙি 927 | ঙ্ক 928 | ঙ্কে 929 | ঙ্খ 930 | ঙ্গ 931 | ঙ্গী 932 | চ 933 | চা 934 | চাঁ 935 | চি 936 | চিং 937 | চী 938 | চু 939 | চে 940 | চৌ 941 | চৈ 942 | চ্ 943 | চ্চ 944 | চ্ছ 945 | চ্ছা 946 | চ্ছে 947 | ছ 948 | ছা 949 | ছি 950 | ছু 951 | ছে 952 | ছো 953 | জ 954 | জং 955 | জা 956 | জি 957 | জী 958 | জু 959 | জে 960 | জো 961 | জ্ 962 | জ্ঞ 963 | জ্ঞা 964 | জ্য 965 | জ্যো 966 | ঝি 967 | ঞ্চ 968 | ঞ্চা 969 | ঞ্জ 970 | ঞ্জা 971 | ঞ্জি 972 | ঞ্জু 973 | ট 974 | টা 975 | টি 976 | টিং 977 | টী 978 | টু 979 | টে 980 | টো 981 | ট্ 982 | ট্টা 983 | ট্যা 984 | ট্র 985 | ট্রা 986 | ট্রি 987 | ট্রে 988 | ঠ 989 | ঠা 990 | ঠি 991 | ড 992 | ডঃ 993 | ড় 994 | ড় 995 | ড়া 996 | ড়া 997 | ড়ি 998 | ড়ি 999 | ড়ী 1000 | ড়ী 1001 | ড়ীূূ 1002 | ড়ে 1003 | ড়ো 1004 | ডা 1005 | ডাঃ 1006 | ডি 1007 | ডিং 1008 | ডু 1009 | ডে 1010 | ডো 1011 | ড্ডা 1012 | ড্রা 1013 | ড্রে 1014 | ঢ 1015 | ঢা 1016 | ণ 1017 | ণা 1018 | ণি 1019 | ণী 1020 | ণে 1021 | ণ্জ 1022 | ণ্ড 1023 | ণ্ডা 1024 | ণ্ডি 1025 | ণ্ডিং 1026 | ণ্যা 1027 | ত 1028 | তা 1029 | তি 1030 | তী 1031 | তু 1032 | তৃ 1033 | তে 1034 | তো 1035 | তৈ 1036 | ত্ত 1037 | ত্ন 1038 | ত্বে 1039 | ত্য 1040 | ত্যা 1041 | ত্যে 1042 | ত্র 1043 | ত্রি 1044 | ত্রী 1045 | ত্ল 1046 | থ 1047 | থা 1048 | থি 1049 | থী 1050 | থু 1051 | থে 1052 | দ 1053 | দা 1054 | দাঁ 1055 | দি 1056 | দী 1057 | দু 1058 | দে 1059 | দো 1060 | দ্বা 1061 | দ্বি 1062 | দ্মা 1063 | দ্যা 1064 | দ্র 1065 | দ্রু 1066 | ধ 1067 | ধা 1068 | ধি 1069 | ধী 1070 | ধু 1071 | ধূ 1072 | ধে 1073 | ধো 1074 | ধ্য 1075 | ধ্যা 1076 | ন 1077 | নং 1078 | নঃ 1079 | না 1080 | নি 1081 | নিং 1082 | নী 1083 | নু 1084 | নে 1085 | নো 1086 | ন্ 1087 | ন্ট 1088 | ন্টা 1089 | ন্টি 1090 | ন্টিং 1091 | ন্টী 1092 | ন্টে 1093 | ন্ট্রা 1094 | ন্ড 1095 | ন্ডা 1096 | ন্ডি 1097 | ন্ডু 1098 | ন্ডূ 1099 | ন্ত 1100 | ন্তা 1101 | ন্তি 1102 | ন্তু 1103 | ন্ত্রি 1104 | ন্ত্ু 1105 | ন্দ 1106 | ন্দা 1107 | ন্দি 1108 | ন্দী 1109 | ন্দু 1110 | ন্দ্য 1111 | ন্দ্যো 1112 | ন্দ্র 1113 | ন্দ্রে 1114 | ন্ধ 1115 | ন্ধা 1116 | ন্ধী 1117 | ন্ধু 1118 | ন্ধ্যা 1119 | ন্ন 1120 | ন্ব 1121 | ন্ম 1122 | ন্য 1123 | ন্যা 1124 | ন্স 1125 | ন্সা 1126 | ন্সি 1127 | প 1128 | পঃ 1129 | পা 1130 | পি 1131 | পী 1132 | পু 1133 | পূ 1134 | পে 1135 | পো 1136 | পৌ 1137 | প্ 1138 | প্ত 1139 | প্যা 1140 | প্র 1141 | প্র়় 1142 | প্রা 1143 | প্রাঃ 1144 | প্রি 1145 | প্রী 1146 | প্রে 1147 | প্রো 1148 | প্রোঃ 1149 | প্ল 1150 | প্লা 1151 | প্লে 1152 | প্স 1153 | ফ 1154 | ফা 1155 | ফি 1156 | ফু 1157 | ফে 1158 | ফো 1159 | ফ্ট 1160 | ফ্যা 1161 | ফ্র 1162 | ফ্রা 1163 | ফ্রি 1164 | ফ্রী 1165 | ব 1166 | বঁা 1167 | বং 1168 | বা 1169 | বাঁ 1170 | বাং 1171 | বি 1172 | বী 1173 | বু 1174 | বৃ 1175 | বে 1176 | বো 1177 | বৈঃ 1178 | ব্দী 1179 | ব্ধ 1180 | ব্য 1181 | ব্যা 1182 | ব্যে 1183 | ব্রি 1184 | ব্রী 1185 | ব্রু 1186 | ব্ল 1187 | ব্লা 1188 | ব্লি 1189 | ব্লু 1190 | ভ 1191 | ভা 1192 | ভি 1193 | ভু 1194 | ভুঁ 1195 | ভূ 1196 | ভে 1197 | ভো 1198 | ভ্য 1199 | ভ্যা 1200 | ম 1201 | মা 1202 | মি 1203 | মী 1204 | মু 1205 | মূ 1206 | মে 1207 | মো 1208 | মোঃ 1209 | মৌ 1210 | মৈ 1211 | ম্ 1212 | ম্প 1213 | ম্পা 1214 | ম্পি 1215 | ম্প্র 1216 | ম্প্রী 1217 | ম্প্রো 1218 | ম্ব 1219 | ম্বা 1220 | ম্বে 1221 | ম্ব্র 1222 | ম্ব্্র 1223 | ম্ভ 1224 | ম্ভা 1225 | ম্ম 1226 | ম্মে 1227 | ম্য 1228 | ম্যা 1229 | ম্স 1230 | য 1231 | য় 1232 | য় 1233 | য়া 1234 | য়া 1235 | য়ি 1236 | য়ী 1237 | য়ে 1238 | য়ে 1239 | য়ো 1240 | য়্যা 1241 | যা 1242 | যু 1243 | যে 1244 | যো 1245 | য্য 1246 | য্যা 1247 | র 1248 | রং 1249 | রা 1250 | রাঃ 1251 | রি 1252 | রিং 1253 | রী 1254 | রু 1255 | রূ 1256 | রে 1257 | রো 1258 | রৌ 1259 | র্ক 1260 | র্কা 1261 | র্কী 1262 | র্কে 1263 | র্গ 1264 | র্গা 1265 | র্গাো 1266 | র্গী 1267 | র্গো 1268 | র্চ 1269 | র্চি 1270 | র্চে 1271 | র্জ 1272 | র্জা 1273 | র্জি 1274 | র্জী 1275 | র্জু 1276 | র্জে 1277 | র্ট 1278 | র্ড 1279 | র্ডে 1280 | র্ণ 1281 | র্ণি 1282 | র্ত 1283 | র্তি 1284 | র্তী 1285 | র্ত্তী 1286 | র্থ 1287 | র্থি 1288 | র্থী 1289 | র্থে 1290 | র্দা 1291 | র্দ্য 1292 | র্ধ 1293 | র্ন 1294 | র্নি 1295 | র্পো 1296 | র্ব 1297 | র্বা 1298 | র্ব্ব 1299 | র্ভ 1300 | র্ভি 1301 | র্ভু 1302 | র্ম 1303 | র্মা 1304 | র্মি 1305 | র্মী 1306 | র্মে 1307 | র্ম্মা 1308 | র্য্য 1309 | র্য্যা 1310 | র্লা 1311 | র্শ 1312 | র্শি 1313 | র্ষ 1314 | র্ষি 1315 | র্স 1316 | র্সিং 1317 | ল 1318 | লং 1319 | লা 1320 | লি 1321 | লিং 1322 | লী 1323 | লু 1324 | লে 1325 | লো 1326 | ল্ক 1327 | ল্ট 1328 | ল্ড 1329 | ল্ডা 1330 | ল্প 1331 | ল্পী 1332 | ল্যা 1333 | ল্যে 1334 | ল্স 1335 | ল্স্ 1336 | শ 1337 | শা 1338 | শি 1339 | শিং 1340 | শী 1341 | শু 1342 | শূ 1343 | শে 1344 | শো 1345 | শৌ 1346 | শৈ 1347 | শ্চি 1348 | শ্ব 1349 | শ্বা 1350 | শ্য 1351 | শ্যা 1352 | শ্র 1353 | শ্রা 1354 | শ্রী 1355 | শ্রে 1356 | শ্র্ু 1357 | ষ 1358 | ষি 1359 | ষু 1360 | ষে 1361 | ষো 1362 | ষ্ট 1363 | ষ্টা 1364 | ষ্টি 1365 | ষ্টী 1366 | ষ্টে 1367 | ষ্টো 1368 | ষ্ট্রা 1369 | ষ্ট্রে 1370 | ষ্ঠ 1371 | ষ্ঠা 1372 | ষ্ণ 1373 | ষ্ণু 1374 | ষ্যা 1375 | স 1376 | সং 1377 | স় 1378 | সা 1379 | সাং 1380 | সি 1381 | সিঁ 1382 | সিং 1383 | সী 1384 | সু 1385 | সূ 1386 | সে 1387 | সো 1388 | সৌ 1389 | স্ 1390 | স্ক 1391 | স্কা 1392 | স্কু 1393 | স্ক্যা 1394 | স্ক্রী 1395 | স্ট 1396 | স্টা 1397 | স্টি 1398 | স্টী 1399 | স্টে 1400 | স্টো 1401 | স্ট্ 1402 | স্ট্যা 1403 | স্ট্রি 1404 | স্ট্রী 1405 | স্ট্রে 1406 | স্ট্ু 1407 | স্ত 1408 | স্তু 1409 | স্ত্ত 1410 | স্ত্রা 1411 | স্ত্রী 1412 | স্ত্ু 1413 | স্থ 1414 | স্থা 1415 | স্থ্য 1416 | স্না 1417 | স্পা 1418 | স্পে 1419 | স্বা 1420 | স্বাঃ 1421 | স্বী 1422 | স্ব্্রা 1423 | স্ম 1424 | স্যা 1425 | হ 1426 | হং 1427 | হা 1428 | হি 1429 | হী 1430 | হু 1431 | হূ 1432 | হৃ 1433 | হে 1434 | হো 1435 | হ্নে 1436 | হ্যা 1437 | ৎ 1438 | ৷ 1439 | 가 1440 | 각 1441 | 간 1442 | 갈 1443 | 감 1444 | 갑 1445 | 값 1446 | 강 1447 | 같 1448 | 갚 1449 | 개 1450 | 객 1451 | 거 1452 | 건 1453 | 걷 1454 | 걸 1455 | 검 1456 | 겁 1457 | 것 1458 | 겅 1459 | 게 1460 | 겠 1461 | 겡 1462 | 겨 1463 | 격 1464 | 견 1465 | 결 1466 | 겸 1467 | 겹 1468 | 경 1469 | 계 1470 | 고 1471 | 곡 1472 | 곤 1473 | 골 1474 | 곰 1475 | 곱 1476 | 곳 1477 | 공 1478 | 과 1479 | 곽 1480 | 관 1481 | 광 1482 | 괴 1483 | 굉 1484 | 교 1485 | 구 1486 | 국 1487 | 군 1488 | 굴 1489 | 굽 1490 | 궁 1491 | 궈 1492 | 권 1493 | 귀 1494 | 규 1495 | 균 1496 | 그 1497 | 극 1498 | 근 1499 | 글 1500 | 금 1501 | 급 1502 | 긋 1503 | 기 1504 | 긴 1505 | 길 1506 | 김 1507 | 깃 1508 | 깅 1509 | 깊 1510 | 까 1511 | 깐 1512 | 깔 1513 | 깡 1514 | 깨 1515 | 꺼 1516 | 껌 1517 | 께 1518 | 꼇 1519 | 꼬 1520 | 꼭 1521 | 꼼 1522 | 꽁 1523 | 꽃 1524 | 꽉 1525 | 꾜 1526 | 꾸 1527 | 꿈 1528 | 꿍 1529 | 끄 1530 | 끈 1531 | 끓 1532 | 끔 1533 | 끗 1534 | 끼 1535 | 낀 1536 | ᄂ드 1537 | 나 1538 | 낙 1539 | 난 1540 | 날 1541 | 남 1542 | 납 1543 | 낭 1544 | 내 1545 | 낸 1546 | 냉 1547 | 냐 1548 | 너 1549 | 널 1550 | 넓 1551 | 넘 1552 | 넛 1553 | 네 1554 | 넥 1555 | 넷 1556 | 넹 1557 | 년 1558 | 녋 1559 | 념 1560 | 노 1561 | 녹 1562 | 논 1563 | 농 1564 | 높 1565 | 놓 1566 | 뇌 1567 | 누 1568 | 눈 1569 | 눔 1570 | 눠 1571 | 뉴 1572 | 느 1573 | 는 1574 | 늘 1575 | 능 1576 | 니 1577 | 닉 1578 | 닌 1579 | 닐 1580 | 님 1581 | 닝 1582 | ᄃ장 1583 | 다 1584 | 닥 1585 | 단 1586 | 닫 1587 | 달 1588 | 닭 1589 | 닮 1590 | 담 1591 | 답 1592 | 당 1593 | 대 1594 | 더 1595 | 덕 1596 | 던 1597 | 덧 1598 | 덩 1599 | 덮 1600 | 데 1601 | 덱 1602 | 델 1603 | 뎀 1604 | 뎅 1605 | 도 1606 | 독 1607 | 돈 1608 | 돔 1609 | 동 1610 | 돟 1611 | 돱 1612 | 돼 1613 | 되 1614 | 된 1615 | 될 1616 | 됩 1617 | 두 1618 | 둠 1619 | 둥 1620 | 듀 1621 | 드 1622 | 득 1623 | 든 1624 | 들 1625 | 듬 1626 | 듭 1627 | 듯 1628 | 등 1629 | 디 1630 | 딜 1631 | 딥 1632 | 딨 1633 | 딩 1634 | 따 1635 | 딸 1636 | 때 1637 | 땐 1638 | 떡 1639 | 떳 1640 | 떻 1641 | 떼 1642 | 뗙 1643 | 뗜 1644 | 또 1645 | 뚜 1646 | 뛰 1647 | 뜨 1648 | 뜻 1649 | 띠 1650 | 띰 1651 | ᄅ핫 1652 | 라 1653 | 락 1654 | 란 1655 | 랄 1656 | 람 1657 | 랑 1658 | 래 1659 | 랙 1660 | 랜 1661 | 램 1662 | 랩 1663 | 랫 1664 | 량 1665 | 러 1666 | 런 1667 | 럼 1668 | 럽 1669 | 럿 1670 | 렇 1671 | 레 1672 | 렉 1673 | 렌 1674 | 렐 1675 | 려 1676 | 력 1677 | 련 1678 | 렬 1679 | 로 1680 | 록 1681 | 론 1682 | 롤 1683 | 롭 1684 | 롯 1685 | 롱 1686 | 뢰 1687 | 료 1688 | 루 1689 | 룩 1690 | 룬 1691 | 룸 1692 | 뤼 1693 | 류 1694 | 륜 1695 | 륨 1696 | 르 1697 | 륵 1698 | 륶 1699 | 른 1700 | 를 1701 | 름 1702 | 릅 1703 | 릇 1704 | 리 1705 | 릭 1706 | 린 1707 | 릴 1708 | 림 1709 | 립 1710 | 릿 1711 | 링 1712 | 마 1713 | 막 1714 | 만 1715 | 많 1716 | 말 1717 | 맛 1718 | 망 1719 | 맞 1720 | 맟 1721 | 매 1722 | 맥 1723 | 맨 1724 | 먜 1725 | 머 1726 | 먹 1727 | 먼 1728 | 멋 1729 | 멍 1730 | 메 1731 | 멕 1732 | 멘 1733 | 멜 1734 | 멤 1735 | 멩 1736 | 며 1737 | 면 1738 | 멸 1739 | 몁 1740 | 명 1741 | 몇 1742 | 모 1743 | 목 1744 | 몫 1745 | 몰 1746 | 몸 1747 | 못 1748 | 묘 1749 | 무 1750 | 묵 1751 | 묶 1752 | 문 1753 | 물 1754 | 뮈 1755 | 뮤 1756 | 믈 1757 | 믑 1758 | 미 1759 | 믹 1760 | 민 1761 | 믿 1762 | 밀 1763 | 밌 1764 | 밎 1765 | 및 1766 | 바 1767 | 바ᅵ 1768 | 박 1769 | 반 1770 | 받 1771 | 발 1772 | 밝 1773 | 밤 1774 | 밥 1775 | 밧 1776 | 방 1777 | 배 1778 | 백 1779 | 밴 1780 | 뱃 1781 | 뱅 1782 | 버 1783 | 번 1784 | 벌 1785 | 범 1786 | 법 1787 | 베 1788 | 벤 1789 | 벨 1790 | 벼 1791 | 벽 1792 | 변 1793 | 별 1794 | 병 1795 | 보 1796 | 복 1797 | 볶 1798 | 본 1799 | 볼 1800 | 봄 1801 | 봉 1802 | 봐 1803 | 부 1804 | 북 1805 | 붂 1806 | 분 1807 | 불 1808 | 붐 1809 | 붓 1810 | 붙 1811 | 뷔 1812 | 브 1813 | 브ᅡ 1814 | 븍 1815 | 블 1816 | 븥 1817 | 비 1818 | 빅 1819 | 빈 1820 | 빌 1821 | 빔 1822 | 빗 1823 | 빙 1824 | 빚 1825 | 빛 1826 | 빠 1827 | 빨 1828 | 빵 1829 | 뼈 1830 | 뽕 1831 | 뾰 1832 | 뿍 1833 | 뿐 1834 | 쁜 1835 | 사 1836 | 삭 1837 | 산 1838 | 살 1839 | 삼 1840 | 삿 1841 | 상 1842 | 새 1843 | 색 1844 | 샌 1845 | 샐 1846 | 샘 1847 | 생 1848 | 샤 1849 | 샴 1850 | 샵 1851 | 샾 1852 | 섕 1853 | 서 1854 | 석 1855 | 선 1856 | 설 1857 | 섭 1858 | 성 1859 | 세 1860 | 센 1861 | 셀 1862 | 셔 1863 | 셕 1864 | 션 1865 | 셰 1866 | 소 1867 | 속 1868 | 손 1869 | 솔 1870 | 솜 1871 | 송 1872 | 솥 1873 | 쇠 1874 | 쇼 1875 | 수 1876 | 숙 1877 | 순 1878 | 술 1879 | 숨 1880 | 숭 1881 | 숯 1882 | 쉐 1883 | 쉬 1884 | 쉴 1885 | 슈 1886 | 슐 1887 | 스 1888 | 슥 1889 | 슨 1890 | 슬 1891 | 슴 1892 | 습 1893 | 승 1894 | 슼 1895 | 싀 1896 | 시 1897 | 식 1898 | 신 1899 | 실 1900 | 싫 1901 | 심 1902 | 십 1903 | 싱 1904 | ᄊ랑 1905 | 싸 1906 | 싼 1907 | 쌀 1908 | 쌩 1909 | 써 1910 | 쏟 1911 | 쑤 1912 | 쓰 1913 | 씀 1914 | 씌 1915 | 씨 1916 | 아 1917 | 악 1918 | 안 1919 | 않 1920 | 알 1921 | 암 1922 | 압 1923 | 앗 1924 | 았 1925 | 앙 1926 | 앞 1927 | 애 1928 | 액 1929 | 앤 1930 | 앰 1931 | 야 1932 | 약 1933 | 양 1934 | 어 1935 | 억 1936 | 언 1937 | 얹 1938 | 얻 1939 | 얼 1940 | 엄 1941 | 업 1942 | 없 1943 | 었 1944 | 엉 1945 | 에 1946 | 엔 1947 | 엘 1948 | 엠 1949 | 엣 1950 | 여 1951 | 역 1952 | 연 1953 | 엳 1954 | 열 1955 | 염 1956 | 엽 1957 | 영 1958 | 옆 1959 | 예 1960 | 옛 1961 | 오 1962 | 옥 1963 | 온 1964 | 올 1965 | 옴 1966 | 옹 1967 | 와 1968 | 완 1969 | 왕 1970 | 외 1971 | 요 1972 | 요ᅵ 1973 | 욕 1974 | 욘 1975 | 용 1976 | 우 1977 | 욱 1978 | 운 1979 | 울 1980 | 움 1981 | 웃 1982 | 웅 1983 | 워 1984 | 웍 1985 | 원 1986 | 월 1987 | 웨 1988 | 웰 1989 | 웹 1990 | 위 1991 | 유 1992 | 육 1993 | 윤 1994 | 율 1995 | 융 1996 | 으 1997 | 은 1998 | 을 1999 | 음 2000 | 읍 2001 | 응 2002 | 의 2003 | 이 2004 | 익 2005 | 인 2006 | 일 2007 | 임 2008 | 입 2009 | 있 2010 | 잉 2011 | 잊 2012 | 자 2013 | 작 2014 | 잔 2015 | 잖 2016 | 잘 2017 | 잠 2018 | 잡 2019 | 장 2020 | 재 2021 | 잭 2022 | 쟁 2023 | 쟘 2024 | 쟝 2025 | 저 2026 | 적 2027 | 전 2028 | 절 2029 | 젊 2030 | 점 2031 | 접 2032 | 정 2033 | 제 2034 | 젠 2035 | 젤 2036 | 져 2037 | 조 2038 | 족 2039 | 존 2040 | 졸 2041 | 종 2042 | 좋 2043 | 좌 2044 | 죄 2045 | 주 2046 | 죽 2047 | 준 2048 | 줄 2049 | 중 2050 | 줘 2051 | 쥬 2052 | 즈 2053 | 즉 2054 | 즌 2055 | 즐 2056 | 증 2057 | 즤 2058 | 지 2059 | 직 2060 | 진 2061 | 질 2062 | 짐 2063 | 집 2064 | 징 2065 | 짜 2066 | 짝 2067 | 짬 2068 | 쩝 2069 | 쪼 2070 | 쪽 2071 | 쫄 2072 | 쭈 2073 | 찌 2074 | 찐 2075 | 찔 2076 | 차 2077 | 착 2078 | 찬 2079 | 찰 2080 | 참 2081 | 찻 2082 | 창 2083 | 찾 2084 | 채 2085 | 책 2086 | 챌 2087 | 챗 2088 | 챠 2089 | 처 2090 | 척 2091 | 천 2092 | 철 2093 | 첨 2094 | 첩 2095 | 첫 2096 | 청 2097 | 체 2098 | 첼 2099 | 쳤 2100 | 초 2101 | 촉 2102 | 총 2103 | 촬 2104 | 최 2105 | 추 2106 | 축 2107 | 춘 2108 | 출 2109 | 춤 2110 | 충 2111 | 취 2112 | 츠 2113 | 측 2114 | 층 2115 | 치 2116 | 칙 2117 | 친 2118 | 칠 2119 | 침 2120 | 칭 2121 | 카 2122 | 칸 2123 | 칼 2124 | 캅 2125 | 캐 2126 | 캔 2127 | 캠 2128 | 캡 2129 | 컘 2130 | 커 2131 | 컨 2132 | 컬 2133 | 컴 2134 | 컵 2135 | 케 2136 | 켈 2137 | 켓 2138 | 켜 2139 | 켭 2140 | 켸 2141 | 코 2142 | 콕 2143 | 콘 2144 | 콜 2145 | 콤 2146 | 콩 2147 | 쾌 2148 | 쿄 2149 | 쿠 2150 | 쿤 2151 | 쿨 2152 | 큐 2153 | 크 2154 | 큰 2155 | 클 2156 | 큼 2157 | 킈 2158 | 키 2159 | 킨 2160 | 킬 2161 | 킴 2162 | 킹 2163 | 타 2164 | 탁 2165 | 탄 2166 | 탈 2167 | 탐 2168 | 탑 2169 | 탕 2170 | 태 2171 | 택 2172 | 탠 2173 | 탱 2174 | 턔 2175 | 터 2176 | 턴 2177 | 털 2178 | 턺 2179 | 텀 2180 | 테 2181 | 텍 2182 | 텐 2183 | 텔 2184 | 템 2185 | 텨 2186 | 토 2187 | 톡 2188 | 톤 2189 | 톨 2190 | 통 2191 | 퇘 2192 | 투 2193 | 툰 2194 | 튀 2195 | 튬 2196 | 트 2197 | 특 2198 | 튼 2199 | 틀 2200 | 틈 2201 | 티 2202 | 틱 2203 | 틴 2204 | 틸 2205 | 팀 2206 | 팁 2207 | 팅 2208 | ᄑ젬 2209 | 파 2210 | 팍 2211 | 판 2212 | 팔 2213 | 팜 2214 | 팝 2215 | 팟 2216 | 팡 2217 | 팥 2218 | 패 2219 | 팩 2220 | 팬 2221 | 팻 2222 | 퍼 2223 | 펀 2224 | 펄 2225 | 페 2226 | 펴 2227 | 편 2228 | 평 2229 | 폐 2230 | 포 2231 | 폭 2232 | 폰 2233 | 폴 2234 | 폼 2235 | 표 2236 | 푸 2237 | 푼 2238 | 풀 2239 | 품 2240 | 풍 2241 | 퓨 2242 | 프 2243 | 픈 2244 | 플 2245 | 픔 2246 | 픗 2247 | 픚 2248 | 픠 2249 | 피 2250 | 핀 2251 | 핃 2252 | 필 2253 | 핌 2254 | 핏 2255 | 핑 2256 | 하 2257 | 학 2258 | 한 2259 | 할 2260 | 함 2261 | 합 2262 | 핫 2263 | 항 2264 | 해 2265 | 핸 2266 | 햄 2267 | 했 2268 | 행 2269 | 햐 2270 | 향 2271 | 허 2272 | 헌 2273 | 험 2274 | 헙 2275 | 헤 2276 | 헨 2277 | 혀 2278 | 혁 2279 | 현 2280 | 혈 2281 | 협 2282 | 형 2283 | 혜 2284 | 호 2285 | 혼 2286 | 홀 2287 | 홈 2288 | 홉 2289 | 홍 2290 | 화 2291 | 환 2292 | 활 2293 | 황 2294 | 홰 2295 | 회 2296 | 획 2297 | 효 2298 | 후 2299 | 훈 2300 | 훤 2301 | 훼 2302 | 휘 2303 | 휴 2304 | 흐 2305 | 흐ᅡ 2306 | 흑 2307 | 흔 2308 | 흡 2309 | 흥 2310 | 희 2311 | 흰 2312 | 히 2313 | 힉 2314 | 힌 2315 | 힐 2316 | 힘 2317 | ぁ 2318 | あ 2319 | ぃ 2320 | い 2321 | ぅ 2322 | う 2323 | ぇ 2324 | え 2325 | ぉ 2326 | お 2327 | か 2328 | が 2329 | き 2330 | ぎ 2331 | く 2332 | ぐ 2333 | け 2334 | げ 2335 | こ 2336 | ご 2337 | さ 2338 | ざ 2339 | し 2340 | じ 2341 | す 2342 | ず 2343 | せ 2344 | そ 2345 | ぞ 2346 | た 2347 | だ 2348 | ち 2349 | っ 2350 | つ 2351 | て 2352 | で 2353 | と 2354 | ど 2355 | な 2356 | に 2357 | ぬ 2358 | ね 2359 | の 2360 | は 2361 | ば 2362 | ぱ 2363 | ひ 2364 | び 2365 | ぴ 2366 | ふ 2367 | ぶ 2368 | ぷ 2369 | へ 2370 | べ 2371 | ぺ 2372 | ほ 2373 | ぼ 2374 | ぽ 2375 | ま 2376 | み 2377 | む 2378 | め 2379 | も 2380 | ゃ 2381 | や 2382 | ゆ 2383 | ょ 2384 | よ 2385 | ら 2386 | り 2387 | る 2388 | れ 2389 | ろ 2390 | ゎ 2391 | わ 2392 | を 2393 | ん 2394 | ァ 2395 | ア 2396 | ィ 2397 | イ 2398 | ウ 2399 | ヴ 2400 | ェ 2401 | エ 2402 | ォ 2403 | オ 2404 | カ 2405 | ガ 2406 | キ 2407 | ギ 2408 | ク 2409 | グ 2410 | ケ 2411 | ゲ 2412 | コ 2413 | ゴ 2414 | サ 2415 | ザ 2416 | シ 2417 | ジ 2418 | ス 2419 | ズ 2420 | セ 2421 | ゼ 2422 | ソ 2423 | ゾ 2424 | タ 2425 | ダ 2426 | チ 2427 | ッ 2428 | ツ 2429 | ヅ 2430 | テ 2431 | デ 2432 | ト 2433 | ド 2434 | ナ 2435 | ニ 2436 | ヌ 2437 | ネ 2438 | ノ 2439 | ハ 2440 | バ 2441 | パ 2442 | ヒ 2443 | ビ 2444 | ピ 2445 | フ 2446 | ブ 2447 | プ 2448 | ヘ 2449 | ベ 2450 | ペ 2451 | ホ 2452 | ボ 2453 | ポ 2454 | マ 2455 | ミ 2456 | ム 2457 | メ 2458 | モ 2459 | ャ 2460 | ヤ 2461 | ュ 2462 | ユ 2463 | ョ 2464 | ヨ 2465 | ラ 2466 | リ 2467 | ル 2468 | レ 2469 | ロ 2470 | ヮ 2471 | ワ 2472 | ヱ 2473 | ヲ 2474 | ン 2475 | ヵ 2476 | ヶ 2477 | ・ 2478 | ー 2479 | 一 2480 | 丁 2481 | 七 2482 | 万 2483 | 三 2484 | 上 2485 | 下 2486 | 不 2487 | 与 2488 | 专 2489 | 且 2490 | 世 2491 | 丘 2492 | 业 2493 | 东 2494 | 丝 2495 | 両 2496 | 丢 2497 | 两 2498 | 严 2499 | 个 2500 | 丫 2501 | 中 2502 | 丰 2503 | 临 2504 | 丸 2505 | 丹 2506 | 为 2507 | 主 2508 | 丼 2509 | 丽 2510 | 举 2511 | 乃 2512 | 久 2513 | 么 2514 | 义 2515 | 之 2516 | 乌 2517 | 乎 2518 | 乐 2519 | 乗 2520 | 乘 2521 | 九 2522 | 也 2523 | 习 2524 | 乡 2525 | 书 2526 | 买 2527 | 乱 2528 | 亀 2529 | 了 2530 | 予 2531 | 争 2532 | 事 2533 | 二 2534 | 于 2535 | 云 2536 | 互 2537 | 五 2538 | 井 2539 | 亚 2540 | 交 2541 | 产 2542 | 亨 2543 | 享 2544 | 京 2545 | 亭 2546 | 亮 2547 | 人 2548 | 什 2549 | 仁 2550 | 今 2551 | 介 2552 | 仍 2553 | 从 2554 | 仑 2555 | 仓 2556 | 仕 2557 | 他 2558 | 付 2559 | 仙 2560 | 代 2561 | 令 2562 | 以 2563 | 仪 2564 | 们 2565 | 仲 2566 | 件 2567 | 价 2568 | 任 2569 | 份 2570 | 企 2571 | 伊 2572 | 伎 2573 | 伐 2574 | 休 2575 | 众 2576 | 优 2577 | 会 2578 | 伝 2579 | 伟 2580 | 传 2581 | 伤 2582 | 伸 2583 | 似 2584 | 但 2585 | 位 2586 | 低 2587 | 住 2588 | 佐 2589 | 佑 2590 | 体 2591 | 何 2592 | 佛 2593 | 作 2594 | 你 2595 | 佰 2596 | 佳 2597 | 佶 2598 | 使 2599 | 例 2600 | 供 2601 | 依 2602 | 侠 2603 | 価 2604 | 侧 2605 | 侨 2606 | 侯 2607 | 便 2608 | 係 2609 | 俄 2610 | 俊 2611 | 俏 2612 | 俗 2613 | 保 2614 | 信 2615 | 修 2616 | 俱 2617 | 倉 2618 | 個 2619 | 倍 2620 | 倒 2621 | 候 2622 | 倚 2623 | 倡 2624 | 债 2625 | 值 2626 | 假 2627 | 做 2628 | 停 2629 | 健 2630 | 側 2631 | 偷 2632 | 傅 2633 | 備 2634 | 催 2635 | 傷 2636 | 傾 2637 | 働 2638 | 像 2639 | 僕 2640 | 償 2641 | 優 2642 | 儿 2643 | 元 2644 | 兄 2645 | 先 2646 | 光 2647 | 克 2648 | 免 2649 | 児 2650 | 党 2651 | 入 2652 | 內 2653 | 全 2654 | 八 2655 | 公 2656 | 六 2657 | 兰 2658 | 共 2659 | 关 2660 | 兴 2661 | 兵 2662 | 其 2663 | 具 2664 | 典 2665 | 兹 2666 | 养 2667 | 冀 2668 | 内 2669 | 円 2670 | 冈 2671 | 再 2672 | 写 2673 | 军 2674 | 农 2675 | 冠 2676 | 冬 2677 | 冰 2678 | 冲 2679 | 冷 2680 | 净 2681 | 准 2682 | 凍 2683 | 凤 2684 | 処 2685 | 出 2686 | 击 2687 | 刀 2688 | 分 2689 | 切 2690 | 刊 2691 | 刑 2692 | 划 2693 | 列 2694 | 刘 2695 | 创 2696 | 初 2697 | 判 2698 | 別 2699 | 利 2700 | 别 2701 | 到 2702 | 制 2703 | 刷 2704 | 券 2705 | 刻 2706 | 剂 2707 | 削 2708 | 前 2709 | 剑 2710 | 剖 2711 | 剤 2712 | 剧 2713 | 剩 2714 | 剪 2715 | 割 2716 | 創 2717 | 劑 2718 | 力 2719 | 办 2720 | 功 2721 | 加 2722 | 务 2723 | 劣 2724 | 动 2725 | 助 2726 | 劫 2727 | 劳 2728 | 効 2729 | 勇 2730 | 勋 2731 | 勑 2732 | 勒 2733 | 動 2734 | 勘 2735 | 務 2736 | 勝 2737 | 募 2738 | 勢 2739 | 勤 2740 | 勿 2741 | 包 2742 | 化 2743 | 北 2744 | 匙 2745 | 匹 2746 | 区 2747 | 医 2748 | 十 2749 | 千 2750 | 升 2751 | 午 2752 | 半 2753 | 华 2754 | 协 2755 | 卒 2756 | 協 2757 | 单 2758 | 卖 2759 | 南 2760 | 単 2761 | 博 2762 | 卜 2763 | 占 2764 | 卡 2765 | 卧 2766 | 卫 2767 | 印 2768 | 危 2769 | 即 2770 | 却 2771 | 卷 2772 | 厂 2773 | 厅 2774 | 历 2775 | 压 2776 | 厕 2777 | 厚 2778 | 原 2779 | 厢 2780 | 厦 2781 | 厨 2782 | 厳 2783 | 去 2784 | 县 2785 | 参 2786 | 又 2787 | 及 2788 | 友 2789 | 双 2790 | 反 2791 | 収 2792 | 发 2793 | 取 2794 | 受 2795 | 变 2796 | 叠 2797 | 口 2798 | 古 2799 | 句 2800 | 只 2801 | 召 2802 | 叭 2803 | 可 2804 | 台 2805 | 史 2806 | 右 2807 | 叶 2808 | 号 2809 | 司 2810 | 吃 2811 | 各 2812 | 合 2813 | 吉 2814 | 吊 2815 | 同 2816 | 名 2817 | 后 2818 | 吐 2819 | 向 2820 | 君 2821 | 吞 2822 | 吧 2823 | 听 2824 | 启 2825 | 吴 2826 | 吸 2827 | 呂 2828 | 呉 2829 | 告 2830 | 员 2831 | 周 2832 | 味 2833 | 命 2834 | 和 2835 | 咨 2836 | 咽 2837 | 品 2838 | 哈 2839 | 哉 2840 | 响 2841 | 哗 2842 | 員 2843 | 哪 2844 | 唐 2845 | 售 2846 | 唱 2847 | 商 2848 | 問 2849 | 善 2850 | 喇 2851 | 喉 2852 | 喜 2853 | 喧 2854 | 喫 2855 | 喰 2856 | 営 2857 | 喷 2858 | 嗣 2859 | 嘉 2860 | 嘴 2861 | 噌 2862 | 器 2863 | 嚴 2864 | 囚 2865 | 四 2866 | 回 2867 | 因 2868 | 团 2869 | 団 2870 | 园 2871 | 困 2872 | 図 2873 | 围 2874 | 固 2875 | 国 2876 | 图 2877 | 圃 2878 | 圆 2879 | 圈 2880 | 國 2881 | 圏 2882 | 園 2883 | 圓 2884 | 圖 2885 | 團 2886 | 土 2887 | 圣 2888 | 圧 2889 | 在 2890 | 地 2891 | 圳 2892 | 场 2893 | 圾 2894 | 址 2895 | 均 2896 | 坊 2897 | 坎 2898 | 坏 2899 | 坐 2900 | 坑 2901 | 块 2902 | 坛 2903 | 坝 2904 | 坠 2905 | 坡 2906 | 坦 2907 | 垂 2908 | 垃 2909 | 型 2910 | 垚 2911 | 垣 2912 | 城 2913 | 域 2914 | 埠 2915 | 培 2916 | 基 2917 | 埼 2918 | 堂 2919 | 堅 2920 | 堆 2921 | 堤 2922 | 報 2923 | 場 2924 | 堵 2925 | 塑 2926 | 塔 2927 | 塗 2928 | 塚 2929 | 塞 2930 | 塩 2931 | 境 2932 | 墓 2933 | 増 2934 | 墙 2935 | 墨 2936 | 壁 2937 | 壇 2938 | 士 2939 | 壮 2940 | 声 2941 | 壱 2942 | 売 2943 | 壹 2944 | 处 2945 | 备 2946 | 変 2947 | 复 2948 | 夏 2949 | 夕 2950 | 外 2951 | 多 2952 | 夜 2953 | 够 2954 | 大 2955 | 天 2956 | 太 2957 | 夫 2958 | 央 2959 | 失 2960 | 头 2961 | 夹 2962 | 奇 2963 | 奈 2964 | 奉 2965 | 奋 2966 | 奎 2967 | 奖 2968 | 套 2969 | 奥 2970 | 女 2971 | 奶 2972 | 她 2973 | 好 2974 | 如 2975 | 妆 2976 | 妇 2977 | 妝 2978 | 妹 2979 | 姊 2980 | 始 2981 | 姐 2982 | 姓 2983 | 委 2984 | 姚 2985 | 威 2986 | 娘 2987 | 娱 2988 | 婚 2989 | 婦 2990 | 媒 2991 | 媳 2992 | 嫌 2993 | 子 2994 | 孔 2995 | 字 2996 | 存 2997 | 孝 2998 | 孟 2999 | 季 3000 | 学 3001 | 孩 3002 | 孵 3003 | 學 3004 | 宁 3005 | 宅 3006 | 宇 3007 | 守 3008 | 安 3009 | 完 3010 | 宏 3011 | 官 3012 | 定 3013 | 宜 3014 | 宝 3015 | 实 3016 | 実 3017 | 宠 3018 | 客 3019 | 宣 3020 | 室 3021 | 宫 3022 | 宮 3023 | 害 3024 | 宴 3025 | 家 3026 | 容 3027 | 宽 3028 | 宾 3029 | 宿 3030 | 寅 3031 | 密 3032 | 富 3033 | 寒 3034 | 寓 3035 | 察 3036 | 寧 3037 | 寨 3038 | 寶 3039 | 对 3040 | 寺 3041 | 寻 3042 | 导 3043 | 対 3044 | 寿 3045 | 封 3046 | 専 3047 | 将 3048 | 專 3049 | 小 3050 | 少 3051 | 尔 3052 | 尚 3053 | 就 3054 | 尻 3055 | 尼 3056 | 尽 3057 | 尾 3058 | 尿 3059 | 局 3060 | 层 3061 | 居 3062 | 届 3063 | 屋 3064 | 展 3065 | 属 3066 | 層 3067 | 屯 3068 | 山 3069 | 岁 3070 | 岔 3071 | 岗 3072 | 岛 3073 | 岡 3074 | 岩 3075 | 岳 3076 | 岸 3077 | 峡 3078 | 峭 3079 | 峰 3080 | 島 3081 | 崎 3082 | 崖 3083 | 嵩 3084 | 嶺 3085 | 嶽 3086 | 川 3087 | 州 3088 | 巡 3089 | 巢 3090 | 巣 3091 | 工 3092 | 左 3093 | 巧 3094 | 巨 3095 | 差 3096 | 己 3097 | 已 3098 | 巴 3099 | 巷 3100 | 巻 3101 | 巾 3102 | 市 3103 | 布 3104 | 帆 3105 | 师 3106 | 希 3107 | 帘 3108 | 帝 3109 | 带 3110 | 師 3111 | 席 3112 | 帮 3113 | 帯 3114 | 帰 3115 | 常 3116 | 帽 3117 | 幅 3118 | 幌 3119 | 幔 3120 | 幕 3121 | 幡 3122 | 干 3123 | 平 3124 | 年 3125 | 幸 3126 | 幹 3127 | 幻 3128 | 幼 3129 | 幾 3130 | 广 3131 | 庁 3132 | 広 3133 | 庄 3134 | 庆 3135 | 床 3136 | 库 3137 | 应 3138 | 底 3139 | 店 3140 | 庙 3141 | 府 3142 | 度 3143 | 座 3144 | 庫 3145 | 庭 3146 | 庵 3147 | 康 3148 | 廃 3149 | 廉 3150 | 廊 3151 | 廖 3152 | 延 3153 | 建 3154 | 开 3155 | 弁 3156 | 式 3157 | 引 3158 | 弘 3159 | 弟 3160 | 张 3161 | 弥 3162 | 弧 3163 | 弯 3164 | 張 3165 | 強 3166 | 弹 3167 | 强 3168 | 弾 3169 | 当 3170 | 录 3171 | 形 3172 | 彩 3173 | 影 3174 | 役 3175 | 往 3176 | 征 3177 | 径 3178 | 待 3179 | 很 3180 | 後 3181 | 徐 3182 | 得 3183 | 御 3184 | 復 3185 | 循 3186 | 徳 3187 | 徴 3188 | 德 3189 | 心 3190 | 必 3191 | 志 3192 | 忘 3193 | 応 3194 | 忠 3195 | 忧 3196 | 快 3197 | 念 3198 | 怀 3199 | 态 3200 | 思 3201 | 急 3202 | 性 3203 | 总 3204 | 恒 3205 | 恭 3206 | 息 3207 | 恵 3208 | 恶 3209 | 患 3210 | 悦 3211 | 您 3212 | 情 3213 | 惗 3214 | 惠 3215 | 想 3216 | 意 3217 | 愛 3218 | 感 3219 | 愿 3220 | 慈 3221 | 態 3222 | 慎 3223 | 慢 3224 | 慧 3225 | 慮 3226 | 慶 3227 | 戏 3228 | 成 3229 | 我 3230 | 戒 3231 | 或 3232 | 战 3233 | 戦 3234 | 戴 3235 | 戶 3236 | 户 3237 | 戸 3238 | 房 3239 | 所 3240 | 扁 3241 | 手 3242 | 才 3243 | 打 3244 | 払 3245 | 扣 3246 | 执 3247 | 扩 3248 | 扫 3249 | 扬 3250 | 扱 3251 | 扶 3252 | 批 3253 | 找 3254 | 承 3255 | 技 3256 | 抑 3257 | 抓 3258 | 投 3259 | 抗 3260 | 折 3261 | 抜 3262 | 択 3263 | 抢 3264 | 护 3265 | 报 3266 | 抱 3267 | 抵 3268 | 押 3269 | 拉 3270 | 拍 3271 | 拎 3272 | 拔 3273 | 拙 3274 | 招 3275 | 拜 3276 | 拥 3277 | 拳 3278 | 拾 3279 | 拿 3280 | 持 3281 | 挂 3282 | 指 3283 | 按 3284 | 挡 3285 | 挪 3286 | 振 3287 | 挿 3288 | 捐 3289 | 损 3290 | 换 3291 | 捨 3292 | 掌 3293 | 排 3294 | 探 3295 | 接 3296 | 推 3297 | 描 3298 | 提 3299 | 換 3300 | 握 3301 | 揮 3302 | 援 3303 | 損 3304 | 搬 3305 | 搭 3306 | 携 3307 | 摄 3308 | 摆 3309 | 摘 3310 | 摩 3311 | 播 3312 | 操 3313 | 擦 3314 | 攀 3315 | 支 3316 | 收 3317 | 改 3318 | 放 3319 | 政 3320 | 故 3321 | 效 3322 | 教 3323 | 散 3324 | 敬 3325 | 数 3326 | 整 3327 | 數 3328 | 文 3329 | 斋 3330 | 斗 3331 | 料 3332 | 斜 3333 | 斧 3334 | 断 3335 | 斯 3336 | 新 3337 | 方 3338 | 施 3339 | 旅 3340 | 族 3341 | 旗 3342 | 无 3343 | 日 3344 | 旦 3345 | 旧 3346 | 旨 3347 | 早 3348 | 旬 3349 | 旭 3350 | 时 3351 | 旺 3352 | 昆 3353 | 昇 3354 | 昊 3355 | 昌 3356 | 明 3357 | 易 3358 | 星 3359 | 映 3360 | 春 3361 | 昭 3362 | 是 3363 | 昼 3364 | 显 3365 | 時 3366 | 晟 3367 | 晩 3368 | 普 3369 | 景 3370 | 晴 3371 | 晶 3372 | 智 3373 | 暖 3374 | 暗 3375 | 暨 3376 | 曙 3377 | 曜 3378 | 曝 3379 | 曲 3380 | 更 3381 | 書 3382 | 曹 3383 | 替 3384 | 最 3385 | 會 3386 | 月 3387 | 有 3388 | 朋 3389 | 服 3390 | 朔 3391 | 朗 3392 | 望 3393 | 朝 3394 | 期 3395 | 朦 3396 | 木 3397 | 未 3398 | 末 3399 | 本 3400 | 札 3401 | 术 3402 | 机 3403 | 杀 3404 | 权 3405 | 杉 3406 | 李 3407 | 材 3408 | 村 3409 | 杜 3410 | 条 3411 | 来 3412 | 杨 3413 | 杭 3414 | 杰 3415 | 東 3416 | 松 3417 | 板 3418 | 极 3419 | 构 3420 | 枉 3421 | 析 3422 | 林 3423 | 枚 3424 | 果 3425 | 枝 3426 | 枫 3427 | 架 3428 | 柄 3429 | 柏 3430 | 染 3431 | 柔 3432 | 柜 3433 | 查 3434 | 柳 3435 | 査 3436 | 栄 3437 | 标 3438 | 栈 3439 | 栏 3440 | 树 3441 | 栓 3442 | 栗 3443 | 校 3444 | 株 3445 | 样 3446 | 核 3447 | 根 3448 | 格 3449 | 栽 3450 | 桂 3451 | 桃 3452 | 案 3453 | 桜 3454 | 档 3455 | 桥 3456 | 桶 3457 | 桷 3458 | 梅 3459 | 梓 3460 | 梦 3461 | 梯 3462 | 械 3463 | 检 3464 | 棄 3465 | 棋 3466 | 棍 3467 | 棚 3468 | 森 3469 | 棱 3470 | 棵 3471 | 椅 3472 | 植 3473 | 椎 3474 | 椒 3475 | 検 3476 | 楊 3477 | 楕 3478 | 楚 3479 | 業 3480 | 極 3481 | 楼 3482 | 楽 3483 | 概 3484 | 榆 3485 | 榛 3486 | 榜 3487 | 榧 3488 | 榭 3489 | 構 3490 | 槍 3491 | 様 3492 | 槟 3493 | 槳 3494 | 槽 3495 | 樂 3496 | 樓 3497 | 標 3498 | 樟 3499 | 模 3500 | 樣 3501 | 横 3502 | 樫 3503 | 樹 3504 | 樽 3505 | 橋 3506 | 橙 3507 | 機 3508 | 檔 3509 | 檬 3510 | 次 3511 | 欢 3512 | 欣 3513 | 欧 3514 | 歌 3515 | 歡 3516 | 止 3517 | 正 3518 | 此 3519 | 步 3520 | 武 3521 | 歩 3522 | 歯 3523 | 歳 3524 | 死 3525 | 残 3526 | 段 3527 | 殿 3528 | 毎 3529 | 每 3530 | 比 3531 | 毕 3532 | 毙 3533 | 毛 3534 | 毯 3535 | 民 3536 | 气 3537 | 気 3538 | 氟 3539 | 氣 3540 | 氩 3541 | 水 3542 | 氷 3543 | 永 3544 | 汀 3545 | 汁 3546 | 求 3547 | 汇 3548 | 汉 3549 | 汕 3550 | 汚 3551 | 江 3552 | 池 3553 | 污 3554 | 汤 3555 | 決 3556 | 汽 3557 | 沃 3558 | 沖 3559 | 沙 3560 | 沟 3561 | 没 3562 | 沢 3563 | 沪 3564 | 河 3565 | 油 3566 | 治 3567 | 沼 3568 | 沿 3569 | 泉 3570 | 泊 3571 | 法 3572 | 泛 3573 | 波 3574 | 注 3575 | 泰 3576 | 泳 3577 | 泵 3578 | 泽 3579 | 泾 3580 | 洁 3581 | 洋 3582 | 洗 3583 | 洛 3584 | 洞 3585 | 津 3586 | 洪 3587 | 洱 3588 | 洲 3589 | 活 3590 | 洼 3591 | 派 3592 | 流 3593 | 测 3594 | 济 3595 | 浓 3596 | 浙 3597 | 浜 3598 | 浦 3599 | 浪 3600 | 浮 3601 | 海 3602 | 涂 3603 | 消 3604 | 涧 3605 | 涪 3606 | 液 3607 | 涵 3608 | 淀 3609 | 淇 3610 | 淋 3611 | 淞 3612 | 深 3613 | 添 3614 | 清 3615 | 済 3616 | 渋 3617 | 渔 3618 | 減 3619 | 渡 3620 | 温 3621 | 測 3622 | 港 3623 | 游 3624 | 湖 3625 | 湘 3626 | 湾 3627 | 満 3628 | 溉 3629 | 源 3630 | 準 3631 | 溝 3632 | 溢 3633 | 溪 3634 | 滅 3635 | 滋 3636 | 滑 3637 | 滕 3638 | 滞 3639 | 满 3640 | 滦 3641 | 滨 3642 | 滴 3643 | 漆 3644 | 演 3645 | 漢 3646 | 漫 3647 | 潔 3648 | 潤 3649 | 潮 3650 | 澜 3651 | 澡 3652 | 激 3653 | 濟 3654 | 濡 3655 | 瀑 3656 | 瀬 3657 | 灌 3658 | 灏 3659 | 灣 3660 | 火 3661 | 灭 3662 | 灯 3663 | 灰 3664 | 灾 3665 | 炒 3666 | 炬 3667 | 炭 3668 | 炮 3669 | 炸 3670 | 点 3671 | 為 3672 | 烈 3673 | 烏 3674 | 烘 3675 | 烟 3676 | 烤 3677 | 烧 3678 | 烫 3679 | 热 3680 | 焊 3681 | 焖 3682 | 焙 3683 | 焚 3684 | 無 3685 | 然 3686 | 焼 3687 | 煌 3688 | 煎 3689 | 煙 3690 | 煤 3691 | 照 3692 | 煮 3693 | 熟 3694 | 熱 3695 | 燃 3696 | 營 3697 | 燥 3698 | 爆 3699 | 爨 3700 | 爬 3701 | 爱 3702 | 爷 3703 | 片 3704 | 版 3705 | 牌 3706 | 牙 3707 | 牛 3708 | 牢 3709 | 牧 3710 | 物 3711 | 特 3712 | 犬 3713 | 犯 3714 | 状 3715 | 独 3716 | 狮 3717 | 猫 3718 | 献 3719 | 獣 3720 | 玄 3721 | 率 3722 | 玉 3723 | 王 3724 | 玛 3725 | 玩 3726 | 环 3727 | 现 3728 | 玻 3729 | 珈 3730 | 珍 3731 | 珠 3732 | 班 3733 | 現 3734 | 球 3735 | 理 3736 | 琏 3737 | 琲 3738 | 琴 3739 | 瑞 3740 | 璃 3741 | 環 3742 | 瓜 3743 | 瓶 3744 | 甘 3745 | 生 3746 | 產 3747 | 産 3748 | 用 3749 | 甩 3750 | 甫 3751 | 田 3752 | 由 3753 | 甲 3754 | 申 3755 | 电 3756 | 男 3757 | 町 3758 | 画 3759 | 畅 3760 | 界 3761 | 畑 3762 | 留 3763 | 略 3764 | 番 3765 | 異 3766 | 疆 3767 | 疋 3768 | 疏 3769 | 疗 3770 | 疫 3771 | 疲 3772 | 疾 3773 | 病 3774 | 痛 3775 | 瘫 3776 | 療 3777 | 発 3778 | 登 3779 | 白 3780 | 百 3781 | 的 3782 | 皆 3783 | 皋 3784 | 皙 3785 | 皮 3786 | 皿 3787 | 盂 3788 | 益 3789 | 监 3790 | 盒 3791 | 盖 3792 | 盗 3793 | 盘 3794 | 盛 3795 | 盟 3796 | 盤 3797 | 目 3798 | 直 3799 | 相 3800 | 省 3801 | 眉 3802 | 看 3803 | 県 3804 | 真 3805 | 眠 3806 | 眺 3807 | 眼 3808 | 眾 3809 | 着 3810 | 睡 3811 | 督 3812 | 睿 3813 | 瞬 3814 | 矢 3815 | 知 3816 | 短 3817 | 矯 3818 | 石 3819 | 矿 3820 | 码 3821 | 砂 3822 | 研 3823 | 破 3824 | 硝 3825 | 硬 3826 | 碑 3827 | 碗 3828 | 碧 3829 | 碳 3830 | 確 3831 | 磁 3832 | 磨 3833 | 礎 3834 | 示 3835 | 礼 3836 | 社 3837 | 祉 3838 | 祖 3839 | 祜 3840 | 祝 3841 | 神 3842 | 祠 3843 | 祥 3844 | 票 3845 | 祭 3846 | 祸 3847 | 禁 3848 | 禄 3849 | 禅 3850 | 福 3851 | 禾 3852 | 秀 3853 | 私 3854 | 秋 3855 | 种 3856 | 科 3857 | 秘 3858 | 租 3859 | 移 3860 | 稅 3861 | 程 3862 | 税 3863 | 稚 3864 | 種 3865 | 稳 3866 | 稻 3867 | 稿 3868 | 穂 3869 | 穴 3870 | 究 3871 | 空 3872 | 突 3873 | 窃 3874 | 窄 3875 | 窓 3876 | 窗 3877 | 窝 3878 | 窺 3879 | 立 3880 | 站 3881 | 竞 3882 | 童 3883 | 端 3884 | 競 3885 | 竹 3886 | 笑 3887 | 笔 3888 | 符 3889 | 第 3890 | 笼 3891 | 等 3892 | 筋 3893 | 筑 3894 | 筒 3895 | 策 3896 | 筹 3897 | 签 3898 | 简 3899 | 算 3900 | 管 3901 | 箫 3902 | 箱 3903 | 節 3904 | 築 3905 | 簡 3906 | 籍 3907 | 米 3908 | 类 3909 | 粉 3910 | 粒 3911 | 粗 3912 | 粥 3913 | 粧 3914 | 粮 3915 | 粱 3916 | 精 3917 | 糀 3918 | 糖 3919 | 糯 3920 | 系 3921 | 約 3922 | 紅 3923 | 納 3924 | 級 3925 | 紛 3926 | 素 3927 | 紡 3928 | 索 3929 | 紧 3930 | 紫 3931 | 細 3932 | 紳 3933 | 紺 3934 | 終 3935 | 経 3936 | 結 3937 | 絡 3938 | 給 3939 | 絨 3940 | 統 3941 | 絵 3942 | 經 3943 | 続 3944 | 綦 3945 | 維 3946 | 綱 3947 | 緊 3948 | 総 3949 | 緑 3950 | 線 3951 | 締 3952 | 練 3953 | 縄 3954 | 總 3955 | 繁 3956 | 織 3957 | 繰 3958 | 红 3959 | 纤 3960 | 约 3961 | 级 3962 | 纪 3963 | 纯 3964 | 纱 3965 | 纳 3966 | 纹 3967 | 纽 3968 | 线 3969 | 绀 3970 | 组 3971 | 织 3972 | 终 3973 | 绍 3974 | 经 3975 | 绒 3976 | 结 3977 | 绕 3978 | 绘 3979 | 给 3980 | 络 3981 | 绝 3982 | 统 3983 | 继 3984 | 续 3985 | 维 3986 | 综 3987 | 绿 3988 | 缆 3989 | 编 3990 | 缘 3991 | 缩 3992 | 缶 3993 | 网 3994 | 罗 3995 | 罚 3996 | 罪 3997 | 置 3998 | 署 3999 | 美 4000 | 群 4001 | 義 4002 | 羽 4003 | 習 4004 | 翔 4005 | 翠 4006 | 翻 4007 | 耀 4008 | 老 4009 | 考 4010 | 者 4011 | 而 4012 | 耳 4013 | 耻 4014 | 职 4015 | 联 4016 | 聘 4017 | 聚 4018 | 聞 4019 | 聴 4020 | 肉 4021 | 肖 4022 | 股 4023 | 肥 4024 | 肩 4025 | 肪 4026 | 肯 4027 | 育 4028 | 肴 4029 | 背 4030 | 胜 4031 | 胡 4032 | 胧 4033 | 胶 4034 | 能 4035 | 脂 4036 | 脚 4037 | 脸 4038 | 腐 4039 | 腰 4040 | 腹 4041 | 腾 4042 | 膚 4043 | 膜 4044 | 臣 4045 | 臨 4046 | 自 4047 | 臭 4048 | 至 4049 | 致 4050 | 臻 4051 | 臼 4052 | 興 4053 | 舍 4054 | 舎 4055 | 舒 4056 | 舗 4057 | 舘 4058 | 舞 4059 | 舟 4060 | 航 4061 | 般 4062 | 舰 4063 | 舱 4064 | 船 4065 | 良 4066 | 色 4067 | 艺 4068 | 节 4069 | 芋 4070 | 芝 4071 | 芥 4072 | 芦 4073 | 花 4074 | 芳 4075 | 芸 4076 | 苍 4077 | 苏 4078 | 苑 4079 | 苗 4080 | 若 4081 | 英 4082 | 苹 4083 | 茂 4084 | 范 4085 | 茄 4086 | 茅 4087 | 茶 4088 | 茸 4089 | 荆 4090 | 草 4091 | 荔 4092 | 荣 4093 | 荥 4094 | 药 4095 | 荷 4096 | 荻 4097 | 莊 4098 | 莎 4099 | 莫 4100 | 莲 4101 | 获 4102 | 菀 4103 | 菇 4104 | 菌 4105 | 菓 4106 | 菜 4107 | 華 4108 | 菱 4109 | 菲 4110 | 营 4111 | 萬 4112 | 落 4113 | 葉 4114 | 葛 4115 | 董 4116 | 葵 4117 | 蒋 4118 | 蒲 4119 | 蒸 4120 | 蓄 4121 | 蓬 4122 | 蔦 4123 | 蔬 4124 | 蔵 4125 | 蔷 4126 | 蕨 4127 | 薄 4128 | 薇 4129 | 薙 4130 | 薬 4131 | 藍 4132 | 藏 4133 | 藤 4134 | 蘑 4135 | 蘭 4136 | 虎 4137 | 處 4138 | 虫 4139 | 虹 4140 | 蛟 4141 | 融 4142 | 螢 4143 | 螺 4144 | 血 4145 | 行 4146 | 術 4147 | 街 4148 | 衛 4149 | 衡 4150 | 衣 4151 | 表 4152 | 袋 4153 | 袍 4154 | 被 4155 | 裁 4156 | 装 4157 | 裕 4158 | 補 4159 | 裹 4160 | 製 4161 | 襄 4162 | 西 4163 | 要 4164 | 見 4165 | 規 4166 | 視 4167 | 親 4168 | 観 4169 | 觀 4170 | 见 4171 | 观 4172 | 规 4173 | 视 4174 | 览 4175 | 角 4176 | 解 4177 | 触 4178 | 計 4179 | 託 4180 | 記 4181 | 訪 4182 | 設 4183 | 許 4184 | 診 4185 | 証 4186 | 試 4187 | 詰 4188 | 話 4189 | 詳 4190 | 詹 4191 | 誉 4192 | 誌 4193 | 認 4194 | 誓 4195 | 誠 4196 | 説 4197 | 読 4198 | 課 4199 | 調 4200 | 談 4201 | 請 4202 | 警 4203 | 議 4204 | 计 4205 | 订 4206 | 认 4207 | 让 4208 | 训 4209 | 议 4210 | 讯 4211 | 记 4212 | 讲 4213 | 许 4214 | 论 4215 | 设 4216 | 访 4217 | 证 4218 | 评 4219 | 识 4220 | 试 4221 | 诗 4222 | 诚 4223 | 话 4224 | 诞 4225 | 询 4226 | 语 4227 | 误 4228 | 说 4229 | 请 4230 | 谁 4231 | 调 4232 | 谈 4233 | 谐 4234 | 谢 4235 | 谨 4236 | 谷 4237 | 豆 4238 | 豊 4239 | 象 4240 | 貝 4241 | 負 4242 | 貨 4243 | 販 4244 | 責 4245 | 貴 4246 | 買 4247 | 貸 4248 | 費 4249 | 賀 4250 | 賃 4251 | 資 4252 | 賞 4253 | 賢 4254 | 質 4255 | 贈 4256 | 财 4257 | 责 4258 | 货 4259 | 质 4260 | 购 4261 | 贵 4262 | 费 4263 | 贼 4264 | 赁 4265 | 资 4266 | 赛 4267 | 赤 4268 | 走 4269 | 赵 4270 | 起 4271 | 超 4272 | 越 4273 | 足 4274 | 跆 4275 | 跑 4276 | 跟 4277 | 跡 4278 | 跨 4279 | 路 4280 | 践 4281 | 踏 4282 | 踩 4283 | 踪 4284 | 身 4285 | 車 4286 | 転 4287 | 軽 4288 | 載 4289 | 輔 4290 | 輝 4291 | 輪 4292 | 輸 4293 | 车 4294 | 轩 4295 | 转 4296 | 轻 4297 | 辆 4298 | 辉 4299 | 输 4300 | 辛 4301 | 辣 4302 | 辰 4303 | 边 4304 | 辺 4305 | 込 4306 | 辽 4307 | 达 4308 | 迁 4309 | 过 4310 | 迈 4311 | 迎 4312 | 运 4313 | 近 4314 | 返 4315 | 还 4316 | 这 4317 | 进 4318 | 远 4319 | 违 4320 | 连 4321 | 迪 4322 | 迫 4323 | 迷 4324 | 迹 4325 | 追 4326 | 送 4327 | 适 4328 | 选 4329 | 透 4330 | 递 4331 | 途 4332 | 逗 4333 | 通 4334 | 速 4335 | 造 4336 | 連 4337 | 週 4338 | 進 4339 | 逸 4340 | 運 4341 | 過 4342 | 道 4343 | 達 4344 | 遗 4345 | 遠 4346 | 適 4347 | 遵 4348 | 選 4349 | 遺 4350 | 避 4351 | 邊 4352 | 那 4353 | 邦 4354 | 邮 4355 | 郁 4356 | 郊 4357 | 郎 4358 | 郑 4359 | 郝 4360 | 郡 4361 | 部 4362 | 郭 4363 | 郵 4364 | 郷 4365 | 都 4366 | 鄂 4367 | 酉 4368 | 酌 4369 | 配 4370 | 酒 4371 | 酵 4372 | 酷 4373 | 酸 4374 | 酿 4375 | 醉 4376 | 醒 4377 | 采 4378 | 里 4379 | 重 4380 | 野 4381 | 量 4382 | 金 4383 | 釜 4384 | 釣 4385 | 鉄 4386 | 銀 4387 | 銘 4388 | 銭 4389 | 鋪 4390 | 録 4391 | 鍋 4392 | 鎌 4393 | 鏡 4394 | 鑑 4395 | 鑫 4396 | 钓 4397 | 钛 4398 | 钟 4399 | 钢 4400 | 钮 4401 | 钱 4402 | 钻 4403 | 铁 4404 | 铝 4405 | 银 4406 | 铺 4407 | 链 4408 | 销 4409 | 锁 4410 | 锅 4411 | 锈 4412 | 锋 4413 | 锐 4414 | 锡 4415 | 锦 4416 | 镁 4417 | 镇 4418 | 镜 4419 | 長 4420 | 长 4421 | 門 4422 | 閉 4423 | 開 4424 | 間 4425 | 関 4426 | 閣 4427 | 閩 4428 | 门 4429 | 闭 4430 | 问 4431 | 闲 4432 | 间 4433 | 闸 4434 | 闻 4435 | 队 4436 | 防 4437 | 阳 4438 | 阻 4439 | 阿 4440 | 陀 4441 | 附 4442 | 际 4443 | 陈 4444 | 降 4445 | 限 4446 | 陕 4447 | 陡 4448 | 院 4449 | 除 4450 | 险 4451 | 陪 4452 | 陳 4453 | 陵 4454 | 陶 4455 | 陸 4456 | 険 4457 | 陽 4458 | 隅 4459 | 隆 4460 | 隊 4461 | 階 4462 | 随 4463 | 際 4464 | 障 4465 | 隣 4466 | 隧 4467 | 隨 4468 | 險 4469 | 难 4470 | 雄 4471 | 雅 4472 | 集 4473 | 雉 4474 | 雜 4475 | 離 4476 | 難 4477 | 雨 4478 | 雪 4479 | 雲 4480 | 零 4481 | 雷 4482 | 電 4483 | 需 4484 | 震 4485 | 霉 4486 | 霧 4487 | 露 4488 | 青 4489 | 靖 4490 | 静 4491 | 靜 4492 | 非 4493 | 靠 4494 | 靡 4495 | 面 4496 | 革 4497 | 鞋 4498 | 韩 4499 | 音 4500 | 韵 4501 | 頃 4502 | 頑 4503 | 領 4504 | 頭 4505 | 題 4506 | 額 4507 | 顔 4508 | 願 4509 | 類 4510 | 项 4511 | 顺 4512 | 须 4513 | 颁 4514 | 预 4515 | 领 4516 | 风 4517 | 飛 4518 | 飞 4519 | 食 4520 | 飬 4521 | 飯 4522 | 飲 4523 | 飴 4524 | 養 4525 | 餐 4526 | 館 4527 | 饭 4528 | 饮 4529 | 饰 4530 | 饺 4531 | 饼 4532 | 馆 4533 | 首 4534 | 香 4535 | 馨 4536 | 馬 4537 | 駅 4538 | 駐 4539 | 駿 4540 | 験 4541 | 马 4542 | 驰 4543 | 驶 4544 | 验 4545 | 骏 4546 | 骨 4547 | 高 4548 | 髙 4549 | 鬼 4550 | 魏 4551 | 魔 4552 | 魚 4553 | 鮨 4554 | 鮮 4555 | 鱼 4556 | 鲁 4557 | 鲍 4558 | 鳥 4559 | 鶏 4560 | 鷹 4561 | 鸟 4562 | 鸡 4563 | 鸭 4564 | 鸿 4565 | 鹏 4566 | 鹤 4567 | 鹿 4568 | 麒 4569 | 麓 4570 | 麗 4571 | 麟 4572 | 麦 4573 | 麺 4574 | 麻 4575 | 黄 4576 | 黒 4577 | 鼎 4578 | 鼻 4579 | 齐 4580 | 齿 4581 | 龋 4582 | 龍 4583 | 龙 4584 | 龜 4585 | ﭘ 4586 | ﭬ 4587 | ﭭ 4588 | ﯕ 4589 | ﷲ 4590 | ﺄ 4591 | ﺆ 4592 | ﺈ 4593 | ﺊ 4594 | ﺋ 4595 | ﺌ 4596 | ﺎ 4597 | ﺐ 4598 | ﺑ 4599 | ﺒ 4600 | ﺔ 4601 | ﺖ 4602 | ﺗ 4603 | ﺘ 4604 | ﺚ 4605 | ﺛ 4606 | ﺜ 4607 | ﺞ 4608 | ﺟ 4609 | ﺠ 4610 | ﺢ 4611 | ﺣ 4612 | ﺤ 4613 | ﺦ 4614 | ﺧ 4615 | ﺨ 4616 | ﺪ 4617 | ﺬ 4618 | ﺮ 4619 | ﺰ 4620 | ﺲ 4621 | ﺳ 4622 | ﺴ 4623 | ﺶ 4624 | ﺷ 4625 | ﺸ 4626 | ﺺ 4627 | ﺻ 4628 | ﺼ 4629 | ﺾ 4630 | ﺿ 4631 | ﻀ 4632 | ﻂ 4633 | ﻃ 4634 | ﻄ 4635 | ﻆ 4636 | ﻇ 4637 | ﻈ 4638 | ﻊ 4639 | ﻋ 4640 | ﻌ 4641 | ﻎ 4642 | ﻏ 4643 | ﻐ 4644 | ﻒ 4645 | ﻓ 4646 | ﻔ 4647 | ﻖ 4648 | ﻗ 4649 | ﻘ 4650 | ﻚ 4651 | ﻛ 4652 | ﻜ 4653 | ﻞ 4654 | ﻟ 4655 | ﻠ 4656 | ﻢ 4657 | ﻣ 4658 | ﻤ 4659 | ﻦ 4660 | ﻧ 4661 | ﻨ 4662 | ﻪ 4663 | ﻫ 4664 | ﻬ 4665 | ﻮ 4666 | ﻰ 4667 | ﻲ 4668 | ﻳ 4669 | ﻴ 4670 | ﻵ 4671 | ﻷ 4672 | ﻸ 4673 | ﻹ 4674 | ﻺ 4675 | ﻻ 4676 | ﻼ 4677 | --------------------------------------------------------------------------------