diff --git a/deploy/paddleocr-go/README.md b/deploy/paddleocr-go/README.md new file mode 100644 index 0000000000000000000000000000000000000000..1332c3c0b31c7b6943dfe616deeffc447d6be218 --- /dev/null +++ b/deploy/paddleocr-go/README.md @@ -0,0 +1,328 @@ +# PaddleOCR-GO + +本服务是[PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR)的golang部署版本。 + +## 1. 环境准备 + +### 运行环境 + +- go: 1.14 +- OpenCV: 4.3.0 +- PaddlePaddle: 1.8.4 +- 编译环境:cmake 3.15.4 | gcc 4.8.5 +- 基于Centos 7.4运行环境编译,Windows请自行解决`OpenCV`和`PaddlePaddle`的编译问题 + +*另外,以下编译以`.bashrc`个人环境变量配置文件,如果使用`zsh`,请自行更换为`.zshrc`* + +### 1.1 安装golang + +从官网下载[golang](https://golang.org/dl/),建议选择1.13版本以上进行安装。下载完成后,直接解压你需要的安装目录,并配置相关环境变量,此处以1.14版本为例。 + +```shell +# 下载golang +wget https://golang.org/dl/go1.14.10.linux-amd64.tar.gz + +# 解压到 /usr/local 目录下 +tar -xzvf go1.14.10.linux-amd64.tar.gz -C /usr/local + +# 配置GOROOT,即go的安装目录 +echo "export GOROOT=/usr/local/go" >> ~/.bashrc +# 配置GOPATH,即go相关package的安装目录,可自定义一个目录 +echo "export GOPATH=$HOME/golang" >> ~/.bashrc +# 配置GOPROXY,即go mod包管理器的下载代理,同时打开mod模式 +echo "export GO111MODULE=on" >> ~/.bashrc +echo "export GOPROXY=https://mirrors.aliyun.com/goproxy/" >> ~/.bashrc +source ~/.bashrc +``` + +### 1.2 编译OpenCV库 + +go语言中,OpenCV的使用主要以[gocv](https://github.com/hybridgroup/gocv)包为主,gocv使用cgo调用OpenCV提供接口,因此还是需要编译OpenCV库。 + +**踩坑指南之一:[gocv官方实现](https://github.com/hybridgroup/gocv)中,部分接口并没有与原版C++的OpenCV的API保持一致,导致图片处理结果会出现一定的数值偏差。为处理这种偏差,[该仓库](https://github.com/LKKlein/gocv)fork了一份gocv官方源码,并对部分这些不一致的API进行了修正,保证结果与其他语言的一致性。** + +对于OpenCV的编译,gocv官方提供了[Makefile](https://github.com/LKKlein/gocv/blob/lk/Makefile),可以一键进行安装,具体安装步骤详见[官方指南](https://github.com/LKKlein/gocv/blob/lk/README_ORIGIN.md#ubuntulinux)。 + +这里提供逐步安装的方式,方便排查错误。 + +- 下载并解压OpenCV-4.3.0和OpenCV-Contrib-4.3.0 + +```shell +# 创建opencv安装目录 +mkdir -p ~/opencv + +# 下载OpenCV +cd ~/opencv +curl -sL https://github.com/opencv/opencv/archive/4.3.0.zip > opencv.zip +unzip -q opencv.zip +rm -rf opencv.zip + +# 下载OpenCV-Contrib +curl -sL https://github.com/opencv/opencv_contrib/archive/4.3.0.zip > opencv-contrib.zip +unzip -q opencv-contrib.zip +rm -rf opencv-contrib.zip +``` + +- 安装相关依赖 + +```shell +sudo yum -y install pkgconfig cmake git gtk2-devel libpng-devel libjpeg-devel libtiff-devel tbb tbb-devel libdc1394-devel +``` + +- 编译安装 + +```shell +mkdir -p ~/.local/opencv-4.3.0 +cd ~/opencv/opencv-4.3.0 +mkdir build +cd build +cmake -D WITH_IPP=OFF \ + -D WITH_OPENGL=OFF \ + -D WITH_QT=OFF \ + -D BUILD_EXAMPLES=OFF \ + -D BUILD_TESTS=OFF \ + -D BUILD_PERF_TESTS=OFF \ + -D BUILD_opencv_java=OFF \ + -D BUILD_opencv_python=OFF \ + -D BUILD_opencv_python2=OFF \ + -D BUILD_opencv_python3=OFF \ + -D OPENCV_GENERATE_PKGCONFIG=ON \ + -D CMAKE_INSTALL_PREFIX=$HOME/.local/opencv-4.3.0 \ + -D OPENCV_ENABLE_NONFREE=ON \ + -D OPENCV_EXTRA_MODULES_PATH=$HOME/opencv/opencv_contrib-4.3.0/modules .. +make -j8 +make install +sudo ldconfig +``` + +make进行编译时,可能出现因`xfeatures2d`的两个模块下载失败导致的编译失败,这里只需要手动下载这部分文件到`$HOME/opencv/opencv_contrib-4.3.0/modules/xfeatures2d/src`目录下,然后重新执行`make -j8`即可。这部分文件地址可参考[这里](https://github.com/opencv/opencv_contrib/issues/1301#issuecomment-447181426)给出的链接。 + +- 配置环境变量 + +```shell +echo "export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$HOME/.local/opencv-4.3.0/lib64/pkgconfig" >> ~/.bashrc +echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/.local/opencv-4.3.0/lib64" >> ~/.bashrc +source ~/.bashrc +``` + +- 验证安装 + +```shell +# 安装gocv包,先mod init +go mod init opencv +go get -u github.com/LKKlein/gocv + +# 验证安装结果 +cd $GOPATH/pkg/mod/github.com/!l!k!klein/gocv@v0.28.0 +go run ./cmd/version/main.go + +# 输出 +# gocv version: 0.28.0 +# opencv lib version: 4.3.0 +``` + +### 1.3 编译PaddlePaddle的C语言API + +go语言只能通过cgo调用C语言API,而不能直接与C++进行交互,因此需要编译PaddlePaddle的C语言API。当然,也可以自己写C语言调用C++的代码和头文件,这样就可以直接使用PaddlePaddle提供的已编译的C++推理库,无需自己手动编译,详见[该仓库](https://github.com/LKKlein/paddleocr-go/tree/dev_cxx)。 + +- 获取PaddlePaddle源代码 + +```shell +cd ~ +git clone --recurse-submodules https://github.com/paddlepaddle/paddle + +# 切换到v1.8.4版本 +cd paddle +git checkout v1.8.4 + +# 目前版本无论单卡还是多卡都需要先安装nccl +git clone https://github.com/NVIDIA/nccl.git +make -j8 +make install +``` + +- 编译Paddle源代码 + +**踩坑指南之二:PaddlePaddle的C语言API实现有一个bug,即获取输入输出变量名时只能获取到第一个模型的变量名,后续模型都无法获取输入输出变量名,进而无法获取到模型输出,详情见[issue](https://github.com/PaddlePaddle/Paddle/issues/28309)。因此,编译前需要手动将`paddle/fluid/inference/capi/pd_predictor.cc`文件中`210行`与`215行`的`static`删除。** + +在处理完该bug之后,才能进行后续编译。相关编译参数见[官方文档](https://www.paddlepaddle.org.cn/documentation/docs/zh/advanced_guide/inference_deployment/inference/build_and_install_lib_cn.html#id12),注意部分参数需要相关依赖,请确保依赖完整再启用。 + +```shell +# 创建c++推理库文件夹 +mkdir -p ~/paddle_inference +export PADDLE_ROOT=`$HOME/paddle_inference` + +# 执行编译 +mkdir build +cd build +cmake -DFLUID_INFERENCE_INSTALL_DIR=$PADDLE_ROOT \ + -DWITH_CONTRIB=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DWITH_PYTHON=OFF \ + -DWITH_MKL=ON \ + -DWITH_GPU=ON \ + -DON_INFER=ON \ + --WITH_MKLDNN=ON \ + --WITH_XBYAK=ON \ + --WITH_DSO=OFF .. +make +make inference_lib_dist +``` + +编译完成后,可以在`build/fluid_inference_c_install_dir`目录下,看到以下生成的文件 + +``` +build/fluid_inference_c_install_dir +├── paddle +├── third_party +└── version.txt +``` + +其中`paddle`就是Paddle库的C语言预测API,`version.txt`中包含当前预测库的版本信息。 + + +## 2. paddleocr-go预测库 + +### 2.1 安装paddleocr-go + +直接执行安装命令 + +```shell +go get github.com/PaddlePaddle/PaddleOCR/deploy/paddleocr-go +``` + +### 2.2 相关使用API + +在go中使用import引入包 + +```go +import github.com/PaddlePaddle/PaddleOCR/deploy/paddleocr-go/ocr +``` + +- 预测结果结构体 + +```go +type OCRText struct { + BBox [][]int `json:"bbox"` + Text string `json:"text"` + Score float64 `json:"score"` +} +``` + +一张图的OCR结果包含多个`OCRText`结果,每个结果包含预测框、预测文本、预测文本得分。 + +- OCR预测类 + + +```go +func NewOCRSystem(confFile string, a map[string]interface{}) *OCRSystem +``` + +`OCRSystem`是主要对外提供API的结构; + +`confFile`是yaml配置文件的路径,可在配置文件中修改相关预测参数,也可以传空字符串,这时会全部使用默认配置; + +`a`是可以在代码中直接定义的配置参数,优先级高于配置文件,会覆盖配置文件和默认配置的参数。 + +- 单张图预测API + +```go  +func (ocr *OCRSystem) PredictOneImage(img gocv.Mat) []OCRText +``` + + +- 图片文件夹预测API + +```go +func (ocr *OCRSystem) PredictDirImages(dirname string) map[string][]OCRText +``` + +`dirname`图片文件夹的目录,默认会预测改目录下所有`jpg`和`png`图片,并返回每张图的预测结果。 + +- OCR Server + +```go +func (ocr *OCRSystem) StartServer(port string) +``` + +开启OCR预测Server,开启后,使用`post`请求上传需要识别的图片至`http://$ip:$port/ocr`即可直接获取该图片上所有文本的识别结果。其中,`$ip`是开启服务的主机`ip`或`127.0.0.1`的本地ip, `$port`是传入的端口参数。 + + +## 3. 预测demo + +### 3.1 修改预测配置 + +当前给定的配置文件`config/conf.yaml`中,包含了默认的OCR预测配置参数,可根据个人需要更改相关参数。 + +比如,将`use_gpu`改为`false`,使用CPU执行预测;将`det_model_dir`, `rec_model_dir`, `cls_model_dir`都更改为自己的本地模型路径,也或者是更改字典`rec_char_dict_path`的路径。配置参数包含了预测引擎、检测模型、检测阈值、方向分类模型、识别模型及阈值的相关参数,具体参数的意义可参见[PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/doc/doc_ch/whl.md#%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E)。 + +### 3.2 编译预测demo + +- 下载`paddleocr-go`代码 + +```shell +git clone https://github.com/PaddlePaddle/PaddleOCR +cd PaddleOCR/deploy/paddleocr-go +``` + +- 准备paddle_c环境 + +```shell +cp -r ~/paddle/build/fluid_inference_c_install_dir/* paddle_c/ +``` + +- 编译demo + +```shell +go build demo.go +``` + +### 3.3 执行预测demo + +预测demo提供了三种预测方式,分别是单张图预测、文件夹批量预测、OCR Server预测。三者命令行优先级依次降低。 + +#### 3.3.1 单张图预测 + +```shell +./demo --config config/conf.yaml --image images/test.jpg +``` + +执行完成,会输出以下内容: + + + +#### 3.3.2 文件夹批量预测 + +```shell +./demo --config config/conf.yaml --image_dir ./images +``` + +执行完成,会输出以下内容: + + + +#### 3.3.3 开启OCR Server + +```shell +./demo --use_servering --port=18600 +``` + +开启服务后,可以在其他客户端中通过`post`请求进行ocr预测。此处以`Python`客户端为例,如下所示 + +```python +import requests + +files = {'image': open('images/test.jpg','rb')} +url = "http://127.0.0.1:18600/ocr" + +r = requests.post(url, files=files) +print(r.text) +``` + +执行完成可以得到以下结果 + +![](./images/result/python_client_result.jpg) + +最后,在Python中将上述结果可视化可以得到以下结果 + +![](./images/result/python_vis_result.jpg) diff --git a/deploy/paddleocr-go/config/conf.yaml b/deploy/paddleocr-go/config/conf.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c6b5b7914bfc51cb0e14a614bf696c34083097cf --- /dev/null +++ b/deploy/paddleocr-go/config/conf.yaml @@ -0,0 +1,47 @@ +# params for prediction engine +use_gpu: true +ir_optim: true +enable_mkldnn: false +# use_zero_copy_run: true +use_tensorrt: false +num_cpu_threads: 6 +gpu_id: 0 +gpu_mem: 2000 + +# params for text detector +det_algorithm: "DB" +det_model_dir: "https://paddleocr.bj.bcebos.com/20-09-22/mobile/det/ch_ppocr_mobile_v1.1_det_infer.tar" +det_max_side_len: 960 + +# DB parmas +det_db_thresh: 0.3 +det_db_box_thresh: 0.5 +det_db_unclip_ratio: 2.0 + +# EAST parmas +det_east_score_thresh: 0.8 +det_east_cover_thresh: 0.1 +det_east_nms_thresh: 0.2 + +# params for text recognizer +rec_algorithm: "CRNN" +rec_model_dir: "https://paddleocr.bj.bcebos.com/20-09-22/mobile/rec/ch_ppocr_mobile_v1.1_rec_infer.tar" +rec_image_shape: [3, 32, 320] +rec_char_type: "ch" +rec_batch_num: 30 +max_text_length: 25 +rec_char_dict_path: "config/ppocr_keys_v1.txt" +use_space_char: true + +# params for text classifier +use_angle_cls: false +cls_model_dir: "https://paddleocr.bj.bcebos.com/20-09-22/cls/ch_ppocr_mobile_v1.1_cls_infer.tar" +cls_image_shape: [3, 48, 192] +label_list: ["0", "180"] +cls_batch_num: 30 +cls_thresh: 0.9 + +lang: ch +det: true +rec: true +cls: false \ No newline at end of file diff --git a/deploy/paddleocr-go/config/ppocr_keys_v1.txt b/deploy/paddleocr-go/config/ppocr_keys_v1.txt new file mode 100644 index 0000000000000000000000000000000000000000..84b885d8352226e49b1d5d791b8f43a663e246aa --- /dev/null +++ b/deploy/paddleocr-go/config/ppocr_keys_v1.txt @@ -0,0 +1,6623 @@ +' +疗 +绚 +诚 +娇 +溜 +题 +贿 +者 +廖 +更 +纳 +加 +奉 +公 +一 +就 +汴 +计 +与 +路 +房 +原 +妇 +2 +0 +8 +- +7 +其 +> +: +] +, +, +骑 +刈 +全 +消 +昏 +傈 +安 +久 +钟 +嗅 +不 +影 +处 +驽 +蜿 +资 +关 +椤 +地 +瘸 +专 +问 +忖 +票 +嫉 +炎 +韵 +要 +月 +田 +节 +陂 +鄙 +捌 +备 +拳 +伺 +眼 +网 +盎 +大 +傍 +心 +东 +愉 +汇 +蹿 +科 +每 +业 +里 +航 +晏 +字 +平 +录 +先 +1 +3 +彤 +鲶 +产 +稍 +督 +腴 +有 +象 +岳 +注 +绍 +在 +泺 +文 +定 +核 +名 +水 +过 +理 +让 +偷 +率 +等 +这 +发 +” +为 +含 +肥 +酉 +相 +鄱 +七 +编 +猥 +锛 +日 +镀 +蒂 +掰 +倒 +辆 +栾 +栗 +综 +涩 +州 +雌 +滑 +馀 +了 +机 +块 +司 +宰 +甙 +兴 +矽 +抚 +保 +用 +沧 +秩 +如 +收 +息 +滥 +页 +疑 +埠 +! +! +姥 +异 +橹 +钇 +向 +下 +跄 +的 +椴 +沫 +国 +绥 +獠 +报 +开 +民 +蜇 +何 +分 +凇 +长 +讥 +藏 +掏 +施 +羽 +中 +讲 +派 +嘟 +人 +提 +浼 +间 +世 +而 +古 +多 +倪 +唇 +饯 +控 +庚 +首 +赛 +蜓 +味 +断 +制 +觉 +技 +替 +艰 +溢 +潮 +夕 +钺 +外 +摘 +枋 +动 +双 +单 +啮 +户 +枇 +确 +锦 +曜 +杜 +或 +能 +效 +霜 +盒 +然 +侗 +电 +晁 +放 +步 +鹃 +新 +杖 +蜂 +吒 +濂 +瞬 +评 +总 +隍 +对 +独 +合 +也 +是 +府 +青 +天 +诲 +墙 +组 +滴 +级 +邀 +帘 +示 +已 +时 +骸 +仄 +泅 +和 +遨 +店 +雇 +疫 +持 +巍 +踮 +境 +只 +亨 +目 +鉴 +崤 +闲 +体 +泄 +杂 +作 +般 +轰 +化 +解 +迂 +诿 +蛭 +璀 +腾 +告 +版 +服 +省 +师 +小 +规 +程 +线 +海 +办 +引 +二 +桧 +牌 +砺 +洄 +裴 +修 +图 +痫 +胡 +许 +犊 +事 +郛 +基 +柴 +呼 +食 +研 +奶 +律 +蛋 +因 +葆 +察 +戏 +褒 +戒 +再 +李 +骁 +工 +貂 +油 +鹅 +章 +啄 +休 +场 +给 +睡 +纷 +豆 +器 +捎 +说 +敏 +学 +会 +浒 +设 +诊 +格 +廓 +查 +来 +霓 +室 +溆 +¢ +诡 +寥 +焕 +舜 +柒 +狐 +回 +戟 +砾 +厄 +实 +翩 +尿 +五 +入 +径 +惭 +喹 +股 +宇 +篝 +| +; +美 +期 +云 +九 +祺 +扮 +靠 +锝 +槌 +系 +企 +酰 +阊 +暂 +蚕 +忻 +豁 +本 +羹 +执 +条 +钦 +H +獒 +限 +进 +季 +楦 +于 +芘 +玖 +铋 +茯 +未 +答 +粘 +括 +样 +精 +欠 +矢 +甥 +帷 +嵩 +扣 +令 +仔 +风 +皈 +行 +支 +部 +蓉 +刮 +站 +蜡 +救 +钊 +汗 +松 +嫌 +成 +可 +. +鹤 +院 +从 +交 +政 +怕 +活 +调 +球 +局 +验 +髌 +第 +韫 +谗 +串 +到 +圆 +年 +米 +/ +* +友 +忿 +检 +区 +看 +自 +敢 +刃 +个 +兹 +弄 +流 +留 +同 +没 +齿 +星 +聆 +轼 +湖 +什 +三 +建 +蛔 +儿 +椋 +汕 +震 +颧 +鲤 +跟 +力 +情 +璺 +铨 +陪 +务 +指 +族 +训 +滦 +鄣 +濮 +扒 +商 +箱 +十 +召 +慷 +辗 +所 +莞 +管 +护 +臭 +横 +硒 +嗓 +接 +侦 +六 +露 +党 +馋 +驾 +剖 +高 +侬 +妪 +幂 +猗 +绺 +骐 +央 +酐 +孝 +筝 +课 +徇 +缰 +门 +男 +西 +项 +句 +谙 +瞒 +秃 +篇 +教 +碲 +罚 +声 +呐 +景 +前 +富 +嘴 +鳌 +稀 +免 +朋 +啬 +睐 +去 +赈 +鱼 +住 +肩 +愕 +速 +旁 +波 +厅 +健 +茼 +厥 +鲟 +谅 +投 +攸 +炔 +数 +方 +击 +呋 +谈 +绩 +别 +愫 +僚 +躬 +鹧 +胪 +炳 +招 +喇 +膨 +泵 +蹦 +毛 +结 +5 +4 +谱 +识 +陕 +粽 +婚 +拟 +构 +且 +搜 +任 +潘 +比 +郢 +妨 +醪 +陀 +桔 +碘 +扎 +选 +哈 +骷 +楷 +亿 +明 +缆 +脯 +监 +睫 +逻 +婵 +共 +赴 +淝 +凡 +惦 +及 +达 +揖 +谩 +澹 +减 +焰 +蛹 +番 +祁 +柏 +员 +禄 +怡 +峤 +龙 +白 +叽 +生 +闯 +起 +细 +装 +谕 +竟 +聚 +钙 +上 +导 +渊 +按 +艾 +辘 +挡 +耒 +盹 +饪 +臀 +记 +邮 +蕙 +受 +各 +医 +搂 +普 +滇 +朗 +茸 +带 +翻 +酚 +( +光 +堤 +墟 +蔷 +万 +幻 +〓 +瑙 +辈 +昧 +盏 +亘 +蛀 +吉 +铰 +请 +子 +假 +闻 +税 +井 +诩 +哨 +嫂 +好 +面 +琐 +校 +馊 +鬣 +缂 +营 +访 +炖 +占 +农 +缀 +否 +经 +钚 +棵 +趟 +张 +亟 +吏 +茶 +谨 +捻 +论 +迸 +堂 +玉 +信 +吧 +瞠 +乡 +姬 +寺 +咬 +溏 +苄 +皿 +意 +赉 +宝 +尔 +钰 +艺 +特 +唳 +踉 +都 +荣 +倚 +登 +荐 +丧 +奇 +涵 +批 +炭 +近 +符 +傩 +感 +道 +着 +菊 +虹 +仲 +众 +懈 +濯 +颞 +眺 +南 +释 +北 +缝 +标 +既 +茗 +整 +撼 +迤 +贲 +挎 +耱 +拒 +某 +妍 +卫 +哇 +英 +矶 +藩 +治 +他 +元 +领 +膜 +遮 +穗 +蛾 +飞 +荒 +棺 +劫 +么 +市 +火 +温 +拈 +棚 +洼 +转 +果 +奕 +卸 +迪 +伸 +泳 +斗 +邡 +侄 +涨 +屯 +萋 +胭 +氡 +崮 +枞 +惧 +冒 +彩 +斜 +手 +豚 +随 +旭 +淑 +妞 +形 +菌 +吲 +沱 +争 +驯 +歹 +挟 +兆 +柱 +传 +至 +包 +内 +响 +临 +红 +功 +弩 +衡 +寂 +禁 +老 +棍 +耆 +渍 +织 +害 +氵 +渑 +布 +载 +靥 +嗬 +虽 +苹 +咨 +娄 +库 +雉 +榜 +帜 +嘲 +套 +瑚 +亲 +簸 +欧 +边 +6 +腿 +旮 +抛 +吹 +瞳 +得 +镓 +梗 +厨 +继 +漾 +愣 +憨 +士 +策 +窑 +抑 +躯 +襟 +脏 +参 +贸 +言 +干 +绸 +鳄 +穷 +藜 +音 +折 +详 +) +举 +悍 +甸 +癌 +黎 +谴 +死 +罩 +迁 +寒 +驷 +袖 +媒 +蒋 +掘 +模 +纠 +恣 +观 +祖 +蛆 +碍 +位 +稿 +主 +澧 +跌 +筏 +京 +锏 +帝 +贴 +证 +糠 +才 +黄 +鲸 +略 +炯 +饱 +四 +出 +园 +犀 +牧 +容 +汉 +杆 +浈 +汰 +瑷 +造 +虫 +瘩 +怪 +驴 +济 +应 +花 +沣 +谔 +夙 +旅 +价 +矿 +以 +考 +s +u +呦 +晒 +巡 +茅 +准 +肟 +瓴 +詹 +仟 +褂 +译 +桌 +混 +宁 +怦 +郑 +抿 +些 +余 +鄂 +饴 +攒 +珑 +群 +阖 +岔 +琨 +藓 +预 +环 +洮 +岌 +宀 +杲 +瀵 +最 +常 +囡 +周 +踊 +女 +鼓 +袭 +喉 +简 +范 +薯 +遐 +疏 +粱 +黜 +禧 +法 +箔 +斤 +遥 +汝 +奥 +直 +贞 +撑 +置 +绱 +集 +她 +馅 +逗 +钧 +橱 +魉 +[ +恙 +躁 +唤 +9 +旺 +膘 +待 +脾 +惫 +购 +吗 +依 +盲 +度 +瘿 +蠖 +俾 +之 +镗 +拇 +鲵 +厝 +簧 +续 +款 +展 +啃 +表 +剔 +品 +钻 +腭 +损 +清 +锶 +统 +涌 +寸 +滨 +贪 +链 +吠 +冈 +伎 +迥 +咏 +吁 +览 +防 +迅 +失 +汾 +阔 +逵 +绀 +蔑 +列 +川 +凭 +努 +熨 +揪 +利 +俱 +绉 +抢 +鸨 +我 +即 +责 +膦 +易 +毓 +鹊 +刹 +玷 +岿 +空 +嘞 +绊 +排 +术 +估 +锷 +违 +们 +苟 +铜 +播 +肘 +件 +烫 +审 +鲂 +广 +像 +铌 +惰 +铟 +巳 +胍 +鲍 +康 +憧 +色 +恢 +想 +拷 +尤 +疳 +知 +S +Y +F +D +A +峄 +裕 +帮 +握 +搔 +氐 +氘 +难 +墒 +沮 +雨 +叁 +缥 +悴 +藐 +湫 +娟 +苑 +稠 +颛 +簇 +后 +阕 +闭 +蕤 +缚 +怎 +佞 +码 +嘤 +蔡 +痊 +舱 +螯 +帕 +赫 +昵 +升 +烬 +岫 +、 +疵 +蜻 +髁 +蕨 +隶 +烛 +械 +丑 +盂 +梁 +强 +鲛 +由 +拘 +揉 +劭 +龟 +撤 +钩 +呕 +孛 +费 +妻 +漂 +求 +阑 +崖 +秤 +甘 +通 +深 +补 +赃 +坎 +床 +啪 +承 +吼 +量 +暇 +钼 +烨 +阂 +擎 +脱 +逮 +称 +P +神 +属 +矗 +华 +届 +狍 +葑 +汹 +育 +患 +窒 +蛰 +佼 +静 +槎 +运 +鳗 +庆 +逝 +曼 +疱 +克 +代 +官 +此 +麸 +耧 +蚌 +晟 +例 +础 +榛 +副 +测 +唰 +缢 +迹 +灬 +霁 +身 +岁 +赭 +扛 +又 +菡 +乜 +雾 +板 +读 +陷 +徉 +贯 +郁 +虑 +变 +钓 +菜 +圾 +现 +琢 +式 +乐 +维 +渔 +浜 +左 +吾 +脑 +钡 +警 +T +啵 +拴 +偌 +漱 +湿 +硕 +止 +骼 +魄 +积 +燥 +联 +踢 +玛 +则 +窿 +见 +振 +畿 +送 +班 +钽 +您 +赵 +刨 +印 +讨 +踝 +籍 +谡 +舌 +崧 +汽 +蔽 +沪 +酥 +绒 +怖 +财 +帖 +肱 +私 +莎 +勋 +羔 +霸 +励 +哼 +帐 +将 +帅 +渠 +纪 +婴 +娩 +岭 +厘 +滕 +吻 +伤 +坝 +冠 +戊 +隆 +瘁 +介 +涧 +物 +黍 +并 +姗 +奢 +蹑 +掣 +垸 +锴 +命 +箍 +捉 +病 +辖 +琰 +眭 +迩 +艘 +绌 +繁 +寅 +若 +毋 +思 +诉 +类 +诈 +燮 +轲 +酮 +狂 +重 +反 +职 +筱 +县 +委 +磕 +绣 +奖 +晋 +濉 +志 +徽 +肠 +呈 +獐 +坻 +口 +片 +碰 +几 +村 +柿 +劳 +料 +获 +亩 +惕 +晕 +厌 +号 +罢 +池 +正 +鏖 +煨 +家 +棕 +复 +尝 +懋 +蜥 +锅 +岛 +扰 +队 +坠 +瘾 +钬 +@ +卧 +疣 +镇 +譬 +冰 +彷 +频 +黯 +据 +垄 +采 +八 +缪 +瘫 +型 +熹 +砰 +楠 +襁 +箐 +但 +嘶 +绳 +啤 +拍 +盥 +穆 +傲 +洗 +盯 +塘 +怔 +筛 +丿 +台 +恒 +喂 +葛 +永 +¥ +烟 +酒 +桦 +书 +砂 +蚝 +缉 +态 +瀚 +袄 +圳 +轻 +蛛 +超 +榧 +遛 +姒 +奘 +铮 +右 +荽 +望 +偻 +卡 +丶 +氰 +附 +做 +革 +索 +戚 +坨 +桷 +唁 +垅 +榻 +岐 +偎 +坛 +莨 +山 +殊 +微 +骇 +陈 +爨 +推 +嗝 +驹 +澡 +藁 +呤 +卤 +嘻 +糅 +逛 +侵 +郓 +酌 +德 +摇 +※ +鬃 +被 +慨 +殡 +羸 +昌 +泡 +戛 +鞋 +河 +宪 +沿 +玲 +鲨 +翅 +哽 +源 +铅 +语 +照 +邯 +址 +荃 +佬 +顺 +鸳 +町 +霭 +睾 +瓢 +夸 +椁 +晓 +酿 +痈 +咔 +侏 +券 +噎 +湍 +签 +嚷 +离 +午 +尚 +社 +锤 +背 +孟 +使 +浪 +缦 +潍 +鞅 +军 +姹 +驶 +笑 +鳟 +鲁 +》 +孽 +钜 +绿 +洱 +礴 +焯 +椰 +颖 +囔 +乌 +孔 +巴 +互 +性 +椽 +哞 +聘 +昨 +早 +暮 +胶 +炀 +隧 +低 +彗 +昝 +铁 +呓 +氽 +藉 +喔 +癖 +瑗 +姨 +权 +胱 +韦 +堑 +蜜 +酋 +楝 +砝 +毁 +靓 +歙 +锲 +究 +屋 +喳 +骨 +辨 +碑 +武 +鸠 +宫 +辜 +烊 +适 +坡 +殃 +培 +佩 +供 +走 +蜈 +迟 +翼 +况 +姣 +凛 +浔 +吃 +飘 +债 +犟 +金 +促 +苛 +崇 +坂 +莳 +畔 +绂 +兵 +蠕 +斋 +根 +砍 +亢 +欢 +恬 +崔 +剁 +餐 +榫 +快 +扶 +‖ +濒 +缠 +鳜 +当 +彭 +驭 +浦 +篮 +昀 +锆 +秸 +钳 +弋 +娣 +瞑 +夷 +龛 +苫 +拱 +致 +% +嵊 +障 +隐 +弑 +初 +娓 +抉 +汩 +累 +蓖 +" +唬 +助 +苓 +昙 +押 +毙 +破 +城 +郧 +逢 +嚏 +獭 +瞻 +溱 +婿 +赊 +跨 +恼 +璧 +萃 +姻 +貉 +灵 +炉 +密 +氛 +陶 +砸 +谬 +衔 +点 +琛 +沛 +枳 +层 +岱 +诺 +脍 +榈 +埂 +征 +冷 +裁 +打 +蹴 +素 +瘘 +逞 +蛐 +聊 +激 +腱 +萘 +踵 +飒 +蓟 +吆 +取 +咙 +簋 +涓 +矩 +曝 +挺 +揣 +座 +你 +史 +舵 +焱 +尘 +苏 +笈 +脚 +溉 +榨 +诵 +樊 +邓 +焊 +义 +庶 +儋 +蟋 +蒲 +赦 +呷 +杞 +诠 +豪 +还 +试 +颓 +茉 +太 +除 +紫 +逃 +痴 +草 +充 +鳕 +珉 +祗 +墨 +渭 +烩 +蘸 +慕 +璇 +镶 +穴 +嵘 +恶 +骂 +险 +绋 +幕 +碉 +肺 +戳 +刘 +潞 +秣 +纾 +潜 +銮 +洛 +须 +罘 +销 +瘪 +汞 +兮 +屉 +r +林 +厕 +质 +探 +划 +狸 +殚 +善 +煊 +烹 +〒 +锈 +逯 +宸 +辍 +泱 +柚 +袍 +远 +蹋 +嶙 +绝 +峥 +娥 +缍 +雀 +徵 +认 +镱 +谷 += +贩 +勉 +撩 +鄯 +斐 +洋 +非 +祚 +泾 +诒 +饿 +撬 +威 +晷 +搭 +芍 +锥 +笺 +蓦 +候 +琊 +档 +礁 +沼 +卵 +荠 +忑 +朝 +凹 +瑞 +头 +仪 +弧 +孵 +畏 +铆 +突 +衲 +车 +浩 +气 +茂 +悖 +厢 +枕 +酝 +戴 +湾 +邹 +飚 +攘 +锂 +写 +宵 +翁 +岷 +无 +喜 +丈 +挑 +嗟 +绛 +殉 +议 +槽 +具 +醇 +淞 +笃 +郴 +阅 +饼 +底 +壕 +砚 +弈 +询 +缕 +庹 +翟 +零 +筷 +暨 +舟 +闺 +甯 +撞 +麂 +茌 +蔼 +很 +珲 +捕 +棠 +角 +阉 +媛 +娲 +诽 +剿 +尉 +爵 +睬 +韩 +诰 +匣 +危 +糍 +镯 +立 +浏 +阳 +少 +盆 +舔 +擘 +匪 +申 +尬 +铣 +旯 +抖 +赘 +瓯 +居 +ˇ +哮 +游 +锭 +茏 +歌 +坏 +甚 +秒 +舞 +沙 +仗 +劲 +潺 +阿 +燧 +郭 +嗖 +霏 +忠 +材 +奂 +耐 +跺 +砀 +输 +岖 +媳 +氟 +极 +摆 +灿 +今 +扔 +腻 +枝 +奎 +药 +熄 +吨 +话 +q +额 +慑 +嘌 +协 +喀 +壳 +埭 +视 +著 +於 +愧 +陲 +翌 +峁 +颅 +佛 +腹 +聋 +侯 +咎 +叟 +秀 +颇 +存 +较 +罪 +哄 +岗 +扫 +栏 +钾 +羌 +己 +璨 +枭 +霉 +煌 +涸 +衿 +键 +镝 +益 +岢 +奏 +连 +夯 +睿 +冥 +均 +糖 +狞 +蹊 +稻 +爸 +刿 +胥 +煜 +丽 +肿 +璃 +掸 +跚 +灾 +垂 +樾 +濑 +乎 +莲 +窄 +犹 +撮 +战 +馄 +软 +络 +显 +鸢 +胸 +宾 +妲 +恕 +埔 +蝌 +份 +遇 +巧 +瞟 +粒 +恰 +剥 +桡 +博 +讯 +凯 +堇 +阶 +滤 +卖 +斌 +骚 +彬 +兑 +磺 +樱 +舷 +两 +娱 +福 +仃 +差 +找 +桁 +÷ +净 +把 +阴 +污 +戬 +雷 +碓 +蕲 +楚 +罡 +焖 +抽 +妫 +咒 +仑 +闱 +尽 +邑 +菁 +爱 +贷 +沥 +鞑 +牡 +嗉 +崴 +骤 +塌 +嗦 +订 +拮 +滓 +捡 +锻 +次 +坪 +杩 +臃 +箬 +融 +珂 +鹗 +宗 +枚 +降 +鸬 +妯 +阄 +堰 +盐 +毅 +必 +杨 +崃 +俺 +甬 +状 +莘 +货 +耸 +菱 +腼 +铸 +唏 +痤 +孚 +澳 +懒 +溅 +翘 +疙 +杷 +淼 +缙 +骰 +喊 +悉 +砻 +坷 +艇 +赁 +界 +谤 +纣 +宴 +晃 +茹 +归 +饭 +梢 +铡 +街 +抄 +肼 +鬟 +苯 +颂 +撷 +戈 +炒 +咆 +茭 +瘙 +负 +仰 +客 +琉 +铢 +封 +卑 +珥 +椿 +镧 +窨 +鬲 +寿 +御 +袤 +铃 +萎 +砖 +餮 +脒 +裳 +肪 +孕 +嫣 +馗 +嵇 +恳 +氯 +江 +石 +褶 +冢 +祸 +阻 +狈 +羞 +银 +靳 +透 +咳 +叼 +敷 +芷 +啥 +它 +瓤 +兰 +痘 +懊 +逑 +肌 +往 +捺 +坊 +甩 +呻 +〃 +沦 +忘 +膻 +祟 +菅 +剧 +崆 +智 +坯 +臧 +霍 +墅 +攻 +眯 +倘 +拢 +骠 +铐 +庭 +岙 +瓠 +′ +缺 +泥 +迢 +捶 +? +? +郏 +喙 +掷 +沌 +纯 +秘 +种 +听 +绘 +固 +螨 +团 +香 +盗 +妒 +埚 +蓝 +拖 +旱 +荞 +铀 +血 +遏 +汲 +辰 +叩 +拽 +幅 +硬 +惶 +桀 +漠 +措 +泼 +唑 +齐 +肾 +念 +酱 +虚 +屁 +耶 +旗 +砦 +闵 +婉 +馆 +拭 +绅 +韧 +忏 +窝 +醋 +葺 +顾 +辞 +倜 +堆 +辋 +逆 +玟 +贱 +疾 +董 +惘 +倌 +锕 +淘 +嘀 +莽 +俭 +笏 +绑 +鲷 +杈 +择 +蟀 +粥 +嗯 +驰 +逾 +案 +谪 +褓 +胫 +哩 +昕 +颚 +鲢 +绠 +躺 +鹄 +崂 +儒 +俨 +丝 +尕 +泌 +啊 +萸 +彰 +幺 +吟 +骄 +苣 +弦 +脊 +瑰 +〈 +诛 +镁 +析 +闪 +剪 +侧 +哟 +框 +螃 +守 +嬗 +燕 +狭 +铈 +缮 +概 +迳 +痧 +鲲 +俯 +售 +笼 +痣 +扉 +挖 +满 +咋 +援 +邱 +扇 +歪 +便 +玑 +绦 +峡 +蛇 +叨 +〖 +泽 +胃 +斓 +喋 +怂 +坟 +猪 +该 +蚬 +炕 +弥 +赞 +棣 +晔 +娠 +挲 +狡 +创 +疖 +铕 +镭 +稷 +挫 +弭 +啾 +翔 +粉 +履 +苘 +哦 +楼 +秕 +铂 +土 +锣 +瘟 +挣 +栉 +习 +享 +桢 +袅 +磨 +桂 +谦 +延 +坚 +蔚 +噗 +署 +谟 +猬 +钎 +恐 +嬉 +雒 +倦 +衅 +亏 +璩 +睹 +刻 +殿 +王 +算 +雕 +麻 +丘 +柯 +骆 +丸 +塍 +谚 +添 +鲈 +垓 +桎 +蚯 +芥 +予 +飕 +镦 +谌 +窗 +醚 +菀 +亮 +搪 +莺 +蒿 +羁 +足 +J +真 +轶 +悬 +衷 +靛 +翊 +掩 +哒 +炅 +掐 +冼 +妮 +l +谐 +稚 +荆 +擒 +犯 +陵 +虏 +浓 +崽 +刍 +陌 +傻 +孜 +千 +靖 +演 +矜 +钕 +煽 +杰 +酗 +渗 +伞 +栋 +俗 +泫 +戍 +罕 +沾 +疽 +灏 +煦 +芬 +磴 +叱 +阱 +榉 +湃 +蜀 +叉 +醒 +彪 +租 +郡 +篷 +屎 +良 +垢 +隗 +弱 +陨 +峪 +砷 +掴 +颁 +胎 +雯 +绵 +贬 +沐 +撵 +隘 +篙 +暖 +曹 +陡 +栓 +填 +臼 +彦 +瓶 +琪 +潼 +哪 +鸡 +摩 +啦 +俟 +锋 +域 +耻 +蔫 +疯 +纹 +撇 +毒 +绶 +痛 +酯 +忍 +爪 +赳 +歆 +嘹 +辕 +烈 +册 +朴 +钱 +吮 +毯 +癜 +娃 +谀 +邵 +厮 +炽 +璞 +邃 +丐 +追 +词 +瓒 +忆 +轧 +芫 +谯 +喷 +弟 +半 +冕 +裙 +掖 +墉 +绮 +寝 +苔 +势 +顷 +褥 +切 +衮 +君 +佳 +嫒 +蚩 +霞 +佚 +洙 +逊 +镖 +暹 +唛 +& +殒 +顶 +碗 +獗 +轭 +铺 +蛊 +废 +恹 +汨 +崩 +珍 +那 +杵 +曲 +纺 +夏 +薰 +傀 +闳 +淬 +姘 +舀 +拧 +卷 +楂 +恍 +讪 +厩 +寮 +篪 +赓 +乘 +灭 +盅 +鞣 +沟 +慎 +挂 +饺 +鼾 +杳 +树 +缨 +丛 +絮 +娌 +臻 +嗳 +篡 +侩 +述 +衰 +矛 +圈 +蚜 +匕 +筹 +匿 +濞 +晨 +叶 +骋 +郝 +挚 +蚴 +滞 +增 +侍 +描 +瓣 +吖 +嫦 +蟒 +匾 +圣 +赌 +毡 +癞 +恺 +百 +曳 +需 +篓 +肮 +庖 +帏 +卿 +驿 +遗 +蹬 +鬓 +骡 +歉 +芎 +胳 +屐 +禽 +烦 +晌 +寄 +媾 +狄 +翡 +苒 +船 +廉 +终 +痞 +殇 +々 +畦 +饶 +改 +拆 +悻 +萄 +£ +瓿 +乃 +訾 +桅 +匮 +溧 +拥 +纱 +铍 +骗 +蕃 +龋 +缬 +父 +佐 +疚 +栎 +醍 +掳 +蓄 +x +惆 +颜 +鲆 +榆 +〔 +猎 +敌 +暴 +谥 +鲫 +贾 +罗 +玻 +缄 +扦 +芪 +癣 +落 +徒 +臾 +恿 +猩 +托 +邴 +肄 +牵 +春 +陛 +耀 +刊 +拓 +蓓 +邳 +堕 +寇 +枉 +淌 +啡 +湄 +兽 +酷 +萼 +碚 +濠 +萤 +夹 +旬 +戮 +梭 +琥 +椭 +昔 +勺 +蜊 +绐 +晚 +孺 +僵 +宣 +摄 +冽 +旨 +萌 +忙 +蚤 +眉 +噼 +蟑 +付 +契 +瓜 +悼 +颡 +壁 +曾 +窕 +颢 +澎 +仿 +俑 +浑 +嵌 +浣 +乍 +碌 +褪 +乱 +蔟 +隙 +玩 +剐 +葫 +箫 +纲 +围 +伐 +决 +伙 +漩 +瑟 +刑 +肓 +镳 +缓 +蹭 +氨 +皓 +典 +畲 +坍 +铑 +檐 +塑 +洞 +倬 +储 +胴 +淳 +戾 +吐 +灼 +惺 +妙 +毕 +珐 +缈 +虱 +盖 +羰 +鸿 +磅 +谓 +髅 +娴 +苴 +唷 +蚣 +霹 +抨 +贤 +唠 +犬 +誓 +逍 +庠 +逼 +麓 +籼 +釉 +呜 +碧 +秧 +氩 +摔 +霄 +穸 +纨 +辟 +妈 +映 +完 +牛 +缴 +嗷 +炊 +恩 +荔 +茆 +掉 +紊 +慌 +莓 +羟 +阙 +萁 +磐 +另 +蕹 +辱 +鳐 +湮 +吡 +吩 +唐 +睦 +垠 +舒 +圜 +冗 +瞿 +溺 +芾 +囱 +匠 +僳 +汐 +菩 +饬 +漓 +黑 +霰 +浸 +濡 +窥 +毂 +蒡 +兢 +驻 +鹉 +芮 +诙 +迫 +雳 +厂 +忐 +臆 +猴 +鸣 +蚪 +栈 +箕 +羡 +渐 +莆 +捍 +眈 +哓 +趴 +蹼 +埕 +嚣 +骛 +宏 +淄 +斑 +噜 +严 +瑛 +垃 +椎 +诱 +压 +庾 +绞 +焘 +廿 +抡 +迄 +棘 +夫 +纬 +锹 +眨 +瞌 +侠 +脐 +竞 +瀑 +孳 +骧 +遁 +姜 +颦 +荪 +滚 +萦 +伪 +逸 +粳 +爬 +锁 +矣 +役 +趣 +洒 +颔 +诏 +逐 +奸 +甭 +惠 +攀 +蹄 +泛 +尼 +拼 +阮 +鹰 +亚 +颈 +惑 +勒 +〉 +际 +肛 +爷 +刚 +钨 +丰 +养 +冶 +鲽 +辉 +蔻 +画 +覆 +皴 +妊 +麦 +返 +醉 +皂 +擀 +〗 +酶 +凑 +粹 +悟 +诀 +硖 +港 +卜 +z +杀 +涕 +± +舍 +铠 +抵 +弛 +段 +敝 +镐 +奠 +拂 +轴 +跛 +袱 +e +t +沉 +菇 +俎 +薪 +峦 +秭 +蟹 +历 +盟 +菠 +寡 +液 +肢 +喻 +染 +裱 +悱 +抱 +氙 +赤 +捅 +猛 +跑 +氮 +谣 +仁 +尺 +辊 +窍 +烙 +衍 +架 +擦 +倏 +璐 +瑁 +币 +楞 +胖 +夔 +趸 +邛 +惴 +饕 +虔 +蝎 +§ +哉 +贝 +宽 +辫 +炮 +扩 +饲 +籽 +魏 +菟 +锰 +伍 +猝 +末 +琳 +哚 +蛎 +邂 +呀 +姿 +鄞 +却 +歧 +仙 +恸 +椐 +森 +牒 +寤 +袒 +婆 +虢 +雅 +钉 +朵 +贼 +欲 +苞 +寰 +故 +龚 +坭 +嘘 +咫 +礼 +硷 +兀 +睢 +汶 +’ +铲 +烧 +绕 +诃 +浃 +钿 +哺 +柜 +讼 +颊 +璁 +腔 +洽 +咐 +脲 +簌 +筠 +镣 +玮 +鞠 +谁 +兼 +姆 +挥 +梯 +蝴 +谘 +漕 +刷 +躏 +宦 +弼 +b +垌 +劈 +麟 +莉 +揭 +笙 +渎 +仕 +嗤 +仓 +配 +怏 +抬 +错 +泯 +镊 +孰 +猿 +邪 +仍 +秋 +鼬 +壹 +歇 +吵 +炼 +< +尧 +射 +柬 +廷 +胧 +霾 +凳 +隋 +肚 +浮 +梦 +祥 +株 +堵 +退 +L +鹫 +跎 +凶 +毽 +荟 +炫 +栩 +玳 +甜 +沂 +鹿 +顽 +伯 +爹 +赔 +蛴 +徐 +匡 +欣 +狰 +缸 +雹 +蟆 +疤 +默 +沤 +啜 +痂 +衣 +禅 +w +i +h +辽 +葳 +黝 +钗 +停 +沽 +棒 +馨 +颌 +肉 +吴 +硫 +悯 +劾 +娈 +马 +啧 +吊 +悌 +镑 +峭 +帆 +瀣 +涉 +咸 +疸 +滋 +泣 +翦 +拙 +癸 +钥 +蜒 ++ +尾 +庄 +凝 +泉 +婢 +渴 +谊 +乞 +陆 +锉 +糊 +鸦 +淮 +I +B +N +晦 +弗 +乔 +庥 +葡 +尻 +席 +橡 +傣 +渣 +拿 +惩 +麋 +斛 +缃 +矮 +蛏 +岘 +鸽 +姐 +膏 +催 +奔 +镒 +喱 +蠡 +摧 +钯 +胤 +柠 +拐 +璋 +鸥 +卢 +荡 +倾 +^ +_ +珀 +逄 +萧 +塾 +掇 +贮 +笆 +聂 +圃 +冲 +嵬 +M +滔 +笕 +值 +炙 +偶 +蜱 +搐 +梆 +汪 +蔬 +腑 +鸯 +蹇 +敞 +绯 +仨 +祯 +谆 +梧 +糗 +鑫 +啸 +豺 +囹 +猾 +巢 +柄 +瀛 +筑 +踌 +沭 +暗 +苁 +鱿 +蹉 +脂 +蘖 +牢 +热 +木 +吸 +溃 +宠 +序 +泞 +偿 +拜 +檩 +厚 +朐 +毗 +螳 +吞 +媚 +朽 +担 +蝗 +橘 +畴 +祈 +糟 +盱 +隼 +郜 +惜 +珠 +裨 +铵 +焙 +琚 +唯 +咚 +噪 +骊 +丫 +滢 +勤 +棉 +呸 +咣 +淀 +隔 +蕾 +窈 +饨 +挨 +煅 +短 +匙 +粕 +镜 +赣 +撕 +墩 +酬 +馁 +豌 +颐 +抗 +酣 +氓 +佑 +搁 +哭 +递 +耷 +涡 +桃 +贻 +碣 +截 +瘦 +昭 +镌 +蔓 +氚 +甲 +猕 +蕴 +蓬 +散 +拾 +纛 +狼 +猷 +铎 +埋 +旖 +矾 +讳 +囊 +糜 +迈 +粟 +蚂 +紧 +鲳 +瘢 +栽 +稼 +羊 +锄 +斟 +睁 +桥 +瓮 +蹙 +祉 +醺 +鼻 +昱 +剃 +跳 +篱 +跷 +蒜 +翎 +宅 +晖 +嗑 +壑 +峻 +癫 +屏 +狠 +陋 +袜 +途 +憎 +祀 +莹 +滟 +佶 +溥 +臣 +约 +盛 +峰 +磁 +慵 +婪 +拦 +莅 +朕 +鹦 +粲 +裤 +哎 +疡 +嫖 +琵 +窟 +堪 +谛 +嘉 +儡 +鳝 +斩 +郾 +驸 +酊 +妄 +胜 +贺 +徙 +傅 +噌 +钢 +栅 +庇 +恋 +匝 +巯 +邈 +尸 +锚 +粗 +佟 +蛟 +薹 +纵 +蚊 +郅 +绢 +锐 +苗 +俞 +篆 +淆 +膀 +鲜 +煎 +诶 +秽 +寻 +涮 +刺 +怀 +噶 +巨 +褰 +魅 +灶 +灌 +桉 +藕 +谜 +舸 +薄 +搀 +恽 +借 +牯 +痉 +渥 +愿 +亓 +耘 +杠 +柩 +锔 +蚶 +钣 +珈 +喘 +蹒 +幽 +赐 +稗 +晤 +莱 +泔 +扯 +肯 +菪 +裆 +腩 +豉 +疆 +骜 +腐 +倭 +珏 +唔 +粮 +亡 +润 +慰 +伽 +橄 +玄 +誉 +醐 +胆 +龊 +粼 +塬 +陇 +彼 +削 +嗣 +绾 +芽 +妗 +垭 +瘴 +爽 +薏 +寨 +龈 +泠 +弹 +赢 +漪 +猫 +嘧 +涂 +恤 +圭 +茧 +烽 +屑 +痕 +巾 +赖 +荸 +凰 +腮 +畈 +亵 +蹲 +偃 +苇 +澜 +艮 +换 +骺 +烘 +苕 +梓 +颉 +肇 +哗 +悄 +氤 +涠 +葬 +屠 +鹭 +植 +竺 +佯 +诣 +鲇 +瘀 +鲅 +邦 +移 +滁 +冯 +耕 +癔 +戌 +茬 +沁 +巩 +悠 +湘 +洪 +痹 +锟 +循 +谋 +腕 +鳃 +钠 +捞 +焉 +迎 +碱 +伫 +急 +榷 +奈 +邝 +卯 +辄 +皲 +卟 +醛 +畹 +忧 +稳 +雄 +昼 +缩 +阈 +睑 +扌 +耗 +曦 +涅 +捏 +瞧 +邕 +淖 +漉 +铝 +耦 +禹 +湛 +喽 +莼 +琅 +诸 +苎 +纂 +硅 +始 +嗨 +傥 +燃 +臂 +赅 +嘈 +呆 +贵 +屹 +壮 +肋 +亍 +蚀 +卅 +豹 +腆 +邬 +迭 +浊 +} +童 +螂 +捐 +圩 +勐 +触 +寞 +汊 +壤 +荫 +膺 +渌 +芳 +懿 +遴 +螈 +泰 +蓼 +蛤 +茜 +舅 +枫 +朔 +膝 +眙 +避 +梅 +判 +鹜 +璜 +牍 +缅 +垫 +藻 +黔 +侥 +惚 +懂 +踩 +腰 +腈 +札 +丞 +唾 +慈 +顿 +摹 +荻 +琬 +~ +斧 +沈 +滂 +胁 +胀 +幄 +莜 +Z +匀 +鄄 +掌 +绰 +茎 +焚 +赋 +萱 +谑 +汁 +铒 +瞎 +夺 +蜗 +野 +娆 +冀 +弯 +篁 +懵 +灞 +隽 +芡 +脘 +俐 +辩 +芯 +掺 +喏 +膈 +蝈 +觐 +悚 +踹 +蔗 +熠 +鼠 +呵 +抓 +橼 +峨 +畜 +缔 +禾 +崭 +弃 +熊 +摒 +凸 +拗 +穹 +蒙 +抒 +祛 +劝 +闫 +扳 +阵 +醌 +踪 +喵 +侣 +搬 +仅 +荧 +赎 +蝾 +琦 +买 +婧 +瞄 +寓 +皎 +冻 +赝 +箩 +莫 +瞰 +郊 +笫 +姝 +筒 +枪 +遣 +煸 +袋 +舆 +痱 +涛 +母 +〇 +启 +践 +耙 +绲 +盘 +遂 +昊 +搞 +槿 +诬 +纰 +泓 +惨 +檬 +亻 +越 +C +o +憩 +熵 +祷 +钒 +暧 +塔 +阗 +胰 +咄 +娶 +魔 +琶 +钞 +邻 +扬 +杉 +殴 +咽 +弓 +〆 +髻 +】 +吭 +揽 +霆 +拄 +殖 +脆 +彻 +岩 +芝 +勃 +辣 +剌 +钝 +嘎 +甄 +佘 +皖 +伦 +授 +徕 +憔 +挪 +皇 +庞 +稔 +芜 +踏 +溴 +兖 +卒 +擢 +饥 +鳞 +煲 +‰ +账 +颗 +叻 +斯 +捧 +鳍 +琮 +讹 +蛙 +纽 +谭 +酸 +兔 +莒 +睇 +伟 +觑 +羲 +嗜 +宜 +褐 +旎 +辛 +卦 +诘 +筋 +鎏 +溪 +挛 +熔 +阜 +晰 +鳅 +丢 +奚 +灸 +呱 +献 +陉 +黛 +鸪 +甾 +萨 +疮 +拯 +洲 +疹 +辑 +叙 +恻 +谒 +允 +柔 +烂 +氏 +逅 +漆 +拎 +惋 +扈 +湟 +纭 +啕 +掬 +擞 +哥 +忽 +涤 +鸵 +靡 +郗 +瓷 +扁 +廊 +怨 +雏 +钮 +敦 +E +懦 +憋 +汀 +拚 +啉 +腌 +岸 +f +痼 +瞅 +尊 +咀 +眩 +飙 +忌 +仝 +迦 +熬 +毫 +胯 +篑 +茄 +腺 +凄 +舛 +碴 +锵 +诧 +羯 +後 +漏 +汤 +宓 +仞 +蚁 +壶 +谰 +皑 +铄 +棰 +罔 +辅 +晶 +苦 +牟 +闽 +\ +烃 +饮 +聿 +丙 +蛳 +朱 +煤 +涔 +鳖 +犁 +罐 +荼 +砒 +淦 +妤 +黏 +戎 +孑 +婕 +瑾 +戢 +钵 +枣 +捋 +砥 +衩 +狙 +桠 +稣 +阎 +肃 +梏 +诫 +孪 +昶 +婊 +衫 +嗔 +侃 +塞 +蜃 +樵 +峒 +貌 +屿 +欺 +缫 +阐 +栖 +诟 +珞 +荭 +吝 +萍 +嗽 +恂 +啻 +蜴 +磬 +峋 +俸 +豫 +谎 +徊 +镍 +韬 +魇 +晴 +U +囟 +猜 +蛮 +坐 +囿 +伴 +亭 +肝 +佗 +蝠 +妃 +胞 +滩 +榴 +氖 +垩 +苋 +砣 +扪 +馏 +姓 +轩 +厉 +夥 +侈 +禀 +垒 +岑 +赏 +钛 +辐 +痔 +披 +纸 +碳 +“ +坞 +蠓 +挤 +荥 +沅 +悔 +铧 +帼 +蒌 +蝇 +a +p +y +n +g +哀 +浆 +瑶 +凿 +桶 +馈 +皮 +奴 +苜 +佤 +伶 +晗 +铱 +炬 +优 +弊 +氢 +恃 +甫 +攥 +端 +锌 +灰 +稹 +炝 +曙 +邋 +亥 +眶 +碾 +拉 +萝 +绔 +捷 +浍 +腋 +姑 +菖 +凌 +涞 +麽 +锢 +桨 +潢 +绎 +镰 +殆 +锑 +渝 +铬 +困 +绽 +觎 +匈 +糙 +暑 +裹 +鸟 +盔 +肽 +迷 +綦 +『 +亳 +佝 +俘 +钴 +觇 +骥 +仆 +疝 +跪 +婶 +郯 +瀹 +唉 +脖 +踞 +针 +晾 +忒 +扼 +瞩 +叛 +椒 +疟 +嗡 +邗 +肆 +跆 +玫 +忡 +捣 +咧 +唆 +艄 +蘑 +潦 +笛 +阚 +沸 +泻 +掊 +菽 +贫 +斥 +髂 +孢 +镂 +赂 +麝 +鸾 +屡 +衬 +苷 +恪 +叠 +希 +粤 +爻 +喝 +茫 +惬 +郸 +绻 +庸 +撅 +碟 +宄 +妹 +膛 +叮 +饵 +崛 +嗲 +椅 +冤 +搅 +咕 +敛 +尹 +垦 +闷 +蝉 +霎 +勰 +败 +蓑 +泸 +肤 +鹌 +幌 +焦 +浠 +鞍 +刁 +舰 +乙 +竿 +裔 +。 +茵 +函 +伊 +兄 +丨 +娜 +匍 +謇 +莪 +宥 +似 +蝽 +翳 +酪 +翠 +粑 +薇 +祢 +骏 +赠 +叫 +Q +噤 +噻 +竖 +芗 +莠 +潭 +俊 +羿 +耜 +O +郫 +趁 +嗪 +囚 +蹶 +芒 +洁 +笋 +鹑 +敲 +硝 +啶 +堡 +渲 +揩 +』 +携 +宿 +遒 +颍 +扭 +棱 +割 +萜 +蔸 +葵 +琴 +捂 +饰 +衙 +耿 +掠 +募 +岂 +窖 +涟 +蔺 +瘤 +柞 +瞪 +怜 +匹 +距 +楔 +炜 +哆 +秦 +缎 +幼 +茁 +绪 +痨 +恨 +楸 +娅 +瓦 +桩 +雪 +嬴 +伏 +榔 +妥 +铿 +拌 +眠 +雍 +缇 +‘ +卓 +搓 +哌 +觞 +噩 +屈 +哧 +髓 +咦 +巅 +娑 +侑 +淫 +膳 +祝 +勾 +姊 +莴 +胄 +疃 +薛 +蜷 +胛 +巷 +芙 +芋 +熙 +闰 +勿 +窃 +狱 +剩 +钏 +幢 +陟 +铛 +慧 +靴 +耍 +k +浙 +浇 +飨 +惟 +绗 +祜 +澈 +啼 +咪 +磷 +摞 +诅 +郦 +抹 +跃 +壬 +吕 +肖 +琏 +颤 +尴 +剡 +抠 +凋 +赚 +泊 +津 +宕 +殷 +倔 +氲 +漫 +邺 +涎 +怠 +$ +垮 +荬 +遵 +俏 +叹 +噢 +饽 +蜘 +孙 +筵 +疼 +鞭 +羧 +牦 +箭 +潴 +c +眸 +祭 +髯 +啖 +坳 +愁 +芩 +驮 +倡 +巽 +穰 +沃 +胚 +怒 +凤 +槛 +剂 +趵 +嫁 +v +邢 +灯 +鄢 +桐 +睽 +檗 +锯 +槟 +婷 +嵋 +圻 +诗 +蕈 +颠 +遭 +痢 +芸 +怯 +馥 +竭 +锗 +徜 +恭 +遍 +籁 +剑 +嘱 +苡 +龄 +僧 +桑 +潸 +弘 +澶 +楹 +悲 +讫 +愤 +腥 +悸 +谍 +椹 +呢 +桓 +葭 +攫 +阀 +翰 +躲 +敖 +柑 +郎 +笨 +橇 +呃 +魁 +燎 +脓 +葩 +磋 +垛 +玺 +狮 +沓 +砜 +蕊 +锺 +罹 +蕉 +翱 +虐 +闾 +巫 +旦 +茱 +嬷 +枯 +鹏 +贡 +芹 +汛 +矫 +绁 +拣 +禺 +佃 +讣 +舫 +惯 +乳 +趋 +疲 +挽 +岚 +虾 +衾 +蠹 +蹂 +飓 +氦 +铖 +孩 +稞 +瑜 +壅 +掀 +勘 +妓 +畅 +髋 +W +庐 +牲 +蓿 +榕 +练 +垣 +唱 +邸 +菲 +昆 +婺 +穿 +绡 +麒 +蚱 +掂 +愚 +泷 +涪 +漳 +妩 +娉 +榄 +讷 +觅 +旧 +藤 +煮 +呛 +柳 +腓 +叭 +庵 +烷 +阡 +罂 +蜕 +擂 +猖 +咿 +媲 +脉 +【 +沏 +貅 +黠 +熏 +哲 +烁 +坦 +酵 +兜 +× +潇 +撒 +剽 +珩 +圹 +乾 +摸 +樟 +帽 +嗒 +襄 +魂 +轿 +憬 +锡 +〕 +喃 +皆 +咖 +隅 +脸 +残 +泮 +袂 +鹂 +珊 +囤 +捆 +咤 +误 +徨 +闹 +淙 +芊 +淋 +怆 +囗 +拨 +梳 +渤 +R +G +绨 +蚓 +婀 +幡 +狩 +麾 +谢 +唢 +裸 +旌 +伉 +纶 +裂 +驳 +砼 +咛 +澄 +樨 +蹈 +宙 +澍 +倍 +貔 +操 +勇 +蟠 +摈 +砧 +虬 +够 +缁 +悦 +藿 +撸 +艹 +摁 +淹 +豇 +虎 +榭 +ˉ +吱 +d +° +喧 +荀 +踱 +侮 +奋 +偕 +饷 +犍 +惮 +坑 +璎 +徘 +宛 +妆 +袈 +倩 +窦 +昂 +荏 +乖 +K +怅 +撰 +鳙 +牙 +袁 +酞 +X +痿 +琼 +闸 +雁 +趾 +荚 +虻 +涝 +《 +杏 +韭 +偈 +烤 +绫 +鞘 +卉 +症 +遢 +蓥 +诋 +杭 +荨 +匆 +竣 +簪 +辙 +敕 +虞 +丹 +缭 +咩 +黟 +m +淤 +瑕 +咂 +铉 +硼 +茨 +嶂 +痒 +畸 +敬 +涿 +粪 +窘 +熟 +叔 +嫔 +盾 +忱 +裘 +憾 +梵 +赡 +珙 +咯 +娘 +庙 +溯 +胺 +葱 +痪 +摊 +荷 +卞 +乒 +髦 +寐 +铭 +坩 +胗 +枷 +爆 +溟 +嚼 +羚 +砬 +轨 +惊 +挠 +罄 +竽 +菏 +氧 +浅 +楣 +盼 +枢 +炸 +阆 +杯 +谏 +噬 +淇 +渺 +俪 +秆 +墓 +泪 +跻 +砌 +痰 +垡 +渡 +耽 +釜 +讶 +鳎 +煞 +呗 +韶 +舶 +绷 +鹳 +缜 +旷 +铊 +皱 +龌 +檀 +霖 +奄 +槐 +艳 +蝶 +旋 +哝 +赶 +骞 +蚧 +腊 +盈 +丁 +` +蜚 +矸 +蝙 +睨 +嚓 +僻 +鬼 +醴 +夜 +彝 +磊 +笔 +拔 +栀 +糕 +厦 +邰 +纫 +逭 +纤 +眦 +膊 +馍 +躇 +烯 +蘼 +冬 +诤 +暄 +骶 +哑 +瘠 +」 +臊 +丕 +愈 +咱 +螺 +擅 +跋 +搏 +硪 +谄 +笠 +淡 +嘿 +骅 +谧 +鼎 +皋 +姚 +歼 +蠢 +驼 +耳 +胬 +挝 +涯 +狗 +蒽 +孓 +犷 +凉 +芦 +箴 +铤 +孤 +嘛 +坤 +V +茴 +朦 +挞 +尖 +橙 +诞 +搴 +碇 +洵 +浚 +帚 +蜍 +漯 +柘 +嚎 +讽 +芭 +荤 +咻 +祠 +秉 +跖 +埃 +吓 +糯 +眷 +馒 +惹 +娼 +鲑 +嫩 +讴 +轮 +瞥 +靶 +褚 +乏 +缤 +宋 +帧 +删 +驱 +碎 +扑 +俩 +俄 +偏 +涣 +竹 +噱 +皙 +佰 +渚 +唧 +斡 +# +镉 +刀 +崎 +筐 +佣 +夭 +贰 +肴 +峙 +哔 +艿 +匐 +牺 +镛 +缘 +仡 +嫡 +劣 +枸 +堀 +梨 +簿 +鸭 +蒸 +亦 +稽 +浴 +{ +衢 +束 +槲 +j +阁 +揍 +疥 +棋 +潋 +聪 +窜 +乓 +睛 +插 +冉 +阪 +苍 +搽 +「 +蟾 +螟 +幸 +仇 +樽 +撂 +慢 +跤 +幔 +俚 +淅 +覃 +觊 +溶 +妖 +帛 +侨 +曰 +妾 +泗 +· +: +瀘 +風 +Ë +( +) +∶ +紅 +紗 +瑭 +雲 +頭 +鶏 +財 +許 +• +¥ +樂 +焗 +麗 +— +; +滙 +東 +榮 +繪 +興 +… +門 +業 +π +楊 +國 +顧 +é +盤 +寳 +Λ +龍 +鳳 +島 +誌 +緣 +結 +銭 +萬 +勝 +祎 +璟 +優 +歡 +臨 +時 +購 += +★ +藍 +昇 +鐵 +觀 +勅 +農 +聲 +畫 +兿 +術 +發 +劉 +記 +專 +耑 +園 +書 +壴 +種 +Ο +● +褀 +號 +銀 +匯 +敟 +锘 +葉 +橪 +廣 +進 +蒄 +鑽 +阝 +祙 +貢 +鍋 +豊 +夬 +喆 +團 +閣 +開 +燁 +賓 +館 +酡 +沔 +順 ++ +硚 +劵 +饸 +陽 +車 +湓 +復 +萊 +氣 +軒 +華 +堃 +迮 +纟 +戶 +馬 +學 +裡 +電 +嶽 +獨 +マ +シ +サ +ジ +燘 +袪 +環 +❤ +臺 +灣 +専 +賣 +孖 +聖 +攝 +線 +▪ +α +傢 +俬 +夢 +達 +莊 +喬 +貝 +薩 +劍 +羅 +壓 +棛 +饦 +尃 +璈 +囍 +醫 +G +I +A +# +N +鷄 +髙 +嬰 +啓 +約 +隹 +潔 +賴 +藝 +~ +寶 +籣 +麺 +  +嶺 +√ +義 +網 +峩 +長 +∧ +魚 +機 +構 +② +鳯 +偉 +L +B +㙟 +畵 +鴿 +' +詩 +溝 +嚞 +屌 +藔 +佧 +玥 +蘭 +織 +1 +3 +9 +0 +7 +點 +砭 +鴨 +鋪 +銘 +廳 +弍 +‧ +創 +湯 +坶 +℃ +卩 +骝 +& +烜 +荘 +當 +潤 +扞 +係 +懷 +碶 +钅 +蚨 +讠 +☆ +叢 +爲 +埗 +涫 +塗 +→ +楽 +現 +鯨 +愛 +瑪 +鈺 +忄 +悶 +藥 +飾 +樓 +視 +孬 +ㆍ +燚 +苪 +師 +① +丼 +锽 +│ +韓 +標 +è +兒 +閏 +匋 +張 +漢 +Ü +髪 +會 +閑 +檔 +習 +裝 +の +峯 +菘 +輝 +И +雞 +釣 +億 +浐 +K +O +R +8 +H +E +P +T +W +D +S +C +M +F +姌 +饹 +» +晞 +廰 +ä +嵯 +鷹 +負 +飲 +絲 +冚 +楗 +澤 +綫 +區 +❋ +← +質 +靑 +揚 +③ +滬 +統 +産 +協 +﹑ +乸 +畐 +經 +運 +際 +洺 +岽 +為 +粵 +諾 +崋 +豐 +碁 +ɔ +V +2 +6 +齋 +誠 +訂 +´ +勑 +雙 +陳 +無 +í +泩 +媄 +夌 +刂 +i +c +t +o +r +a +嘢 +耄 +燴 +暃 +壽 +媽 +靈 +抻 +體 +唻 +É +冮 +甹 +鎮 +錦 +ʌ +蜛 +蠄 +尓 +駕 +戀 +飬 +逹 +倫 +貴 +極 +Я +Й +寬 +磚 +嶪 +郎 +職 +| +間 +n +d +剎 +伈 +課 +飛 +橋 +瘊 +№ +譜 +骓 +圗 +滘 +縣 +粿 +咅 +養 +濤 +彳 +® +% +Ⅱ +啰 +㴪 +見 +矞 +薬 +糁 +邨 +鲮 +顔 +罱 +З +選 +話 +贏 +氪 +俵 +競 +瑩 +繡 +枱 +β +綉 +á +獅 +爾 +™ +麵 +戋 +淩 +徳 +個 +劇 +場 +務 +簡 +寵 +h +實 +膠 +轱 +圖 +築 +嘣 +樹 +㸃 +營 +耵 +孫 +饃 +鄺 +飯 +麯 +遠 +輸 +坫 +孃 +乚 +閃 +鏢 +㎡ +題 +廠 +關 +↑ +爺 +將 +軍 +連 +篦 +覌 +參 +箸 +- +窠 +棽 +寕 +夀 +爰 +歐 +呙 +閥 +頡 +熱 +雎 +垟 +裟 +凬 +勁 +帑 +馕 +夆 +疌 +枼 +馮 +貨 +蒤 +樸 +彧 +旸 +靜 +龢 +暢 +㐱 +鳥 +珺 +鏡 +灡 +爭 +堷 +廚 +Ó +騰 +診 +┅ +蘇 +褔 +凱 +頂 +豕 +亞 +帥 +嘬 +⊥ +仺 +桖 +複 +饣 +絡 +穂 +顏 +棟 +納 +▏ +濟 +親 +設 +計 +攵 +埌 +烺 +ò +頤 +燦 +蓮 +撻 +節 +講 +濱 +濃 +娽 +洳 +朿 +燈 +鈴 +護 +膚 +铔 +過 +補 +Z +U +5 +4 +坋 +闿 +䖝 +餘 +缐 +铞 +貿 +铪 +桼 +趙 +鍊 +[ +㐂 +垚 +菓 +揸 +捲 +鐘 +滏 +𣇉 +爍 +輪 +燜 +鴻 +鮮 +動 +鹞 +鷗 +丄 +慶 +鉌 +翥 +飮 +腸 +⇋ +漁 +覺 +來 +熘 +昴 +翏 +鲱 +圧 +鄉 +萭 +頔 +爐 +嫚 +г +貭 +類 +聯 +幛 +輕 +訓 +鑒 +夋 +锨 +芃 +珣 +䝉 +扙 +嵐 +銷 +處 +ㄱ +語 +誘 +苝 +歸 +儀 +燒 +楿 +內 +粢 +葒 +奧 +麥 +礻 +滿 +蠔 +穵 +瞭 +態 +鱬 +榞 +硂 +鄭 +黃 +煙 +祐 +奓 +逺 +* +瑄 +獲 +聞 +薦 +讀 +這 +樣 +決 +問 +啟 +們 +執 +説 +轉 +單 +隨 +唘 +帶 +倉 +庫 +還 +贈 +尙 +皺 +■ +餅 +產 +○ +∈ +報 +狀 +楓 +賠 +琯 +嗮 +禮 +` +傳 +> +≤ +嗞 +Φ +≥ +換 +咭 +∣ +↓ +曬 +ε +応 +寫 +″ +終 +様 +純 +費 +療 +聨 +凍 +壐 +郵 +ü +黒 +∫ +製 +塊 +調 +軽 +確 +撃 +級 +馴 +Ⅲ +涇 +繹 +數 +碼 +證 +狒 +処 +劑 +< +晧 +賀 +衆 +] +櫥 +兩 +陰 +絶 +對 +鯉 +憶 +◎ +p +e +Y +蕒 +煖 +頓 +測 +試 +鼽 +僑 +碩 +妝 +帯 +≈ +鐡 +舖 +權 +喫 +倆 +ˋ +該 +悅 +ā +俫 +. +f +s +b +m +k +g +u +j +貼 +淨 +濕 +針 +適 +備 +l +/ +給 +謢 +強 +觸 +衛 +與 +⊙ +$ +緯 +變 +⑴ +⑵ +⑶ +㎏ +殺 +∩ +幚 +─ +價 +▲ +離 +ú +ó +飄 +烏 +関 +閟 +﹝ +﹞ +邏 +輯 +鍵 +驗 +訣 +導 +歷 +屆 +層 +▼ +儱 +錄 +熳 +ē +艦 +吋 +錶 +辧 +飼 +顯 +④ +禦 +販 +気 +対 +枰 +閩 +紀 +幹 +瞓 +貊 +淚 +△ +眞 +墊 +Ω +獻 +褲 +縫 +緑 +亜 +鉅 +餠 +{ +} +◆ +蘆 +薈 +█ +◇ +溫 +彈 +晳 +粧 +犸 +穩 +訊 +崬 +凖 +熥 +П +舊 +條 +紋 +圍 +Ⅳ +筆 +尷 +難 +雜 +錯 +綁 +識 +頰 +鎖 +艶 +□ +殁 +殼 +⑧ +├ +▕ +鵬 +ǐ +ō +ǒ +糝 +綱 +▎ +μ +盜 +饅 +醬 +籤 +蓋 +釀 +鹽 +據 +à +ɡ +辦 +◥ +彐 +┌ +婦 +獸 +鲩 +伱 +ī +蒟 +蒻 +齊 +袆 +腦 +寧 +凈 +妳 +煥 +詢 +偽 +謹 +啫 +鯽 +騷 +鱸 +損 +傷 +鎻 +髮 +買 +冏 +儥 +両 +﹢ +∞ +載 +喰 +z +羙 +悵 +燙 +曉 +員 +組 +徹 +艷 +痠 +鋼 +鼙 +縮 +細 +嚒 +爯 +≠ +維 +" +鱻 +壇 +厍 +帰 +浥 +犇 +薡 +軎 +² +應 +醜 +刪 +緻 +鶴 +賜 +噁 +軌 +尨 +镔 +鷺 +槗 +彌 +葚 +濛 +請 +溇 +緹 +賢 +訪 +獴 +瑅 +資 +縤 +陣 +蕟 +栢 +韻 +祼 +恁 +伢 +謝 +劃 +涑 +總 +衖 +踺 +砋 +凉 +籃 +駿 +苼 +瘋 +昽 +紡 +驊 +腎 +﹗ +響 +杋 +剛 +嚴 +禪 +歓 +槍 +傘 +檸 +檫 +炣 +勢 +鏜 +鎢 +銑 +尐 +減 +奪 +惡 +θ +僮 +婭 +臘 +ū +ì +殻 +鉄 +∑ +蛲 +焼 +緖 +續 +紹 +懮 \ No newline at end of file diff --git a/deploy/paddleocr-go/demo.go b/deploy/paddleocr-go/demo.go new file mode 100644 index 0000000000000000000000000000000000000000..f2226379a3b10a2a60784755c36e7beed174ce99 --- /dev/null +++ b/deploy/paddleocr-go/demo.go @@ -0,0 +1,51 @@ +package main + +import ( + "flag" + "log" + "paddleocr-go/ocr" +) + +var ( + confFile string + image string + imageDir string + useServering bool + port string +) + +func init() { + flag.StringVar(&confFile, "config", "config/conf.yaml", "config from ocr system. If not given, will use default config.") + flag.StringVar(&image, "image", "", "image to predict. if not given, will use image_dir") + flag.StringVar(&imageDir, "image_dir", "", "imgs in dir to be predicted. if not given, will check servering") + flag.BoolVar(&useServering, "use_servering", false, "whether to use ocr server. [default: false]") + flag.StringVar(&port, "port", "18600", "which port to serve ocr server. [default: 18600].") +} + +func main() { + flag.Parse() + sys := ocr.NewOCRSystem(confFile, nil) + + if image != "" { + img := ocr.ReadImage(image) + results := sys.PredictOneImage(img) + for _, res := range results { + log.Println(res) + } + return + } + + if imageDir != "" { + results := sys.PredictDirImages(imageDir) + for k, vs := range results { + log.Printf("======== image: %v =======\n", k) + for _, res := range vs { + log.Println(res) + } + } + } + + if useServering { + sys.StartServer(port) + } +} diff --git a/deploy/paddleocr-go/go.mod b/deploy/paddleocr-go/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..43b63477ffa1ba59e7b81e4db6112941183f3114 --- /dev/null +++ b/deploy/paddleocr-go/go.mod @@ -0,0 +1,9 @@ +module paddleocr-go + +go 1.14 + +require ( + github.com/LKKlein/gocv v0.28.0 + github.com/ctessum/go.clipper v0.0.0-20200522184404-9c744fa3e86c + gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 +) diff --git a/deploy/paddleocr-go/go.sum b/deploy/paddleocr-go/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..4f1fd3fa23912e91a9d67d4d34c46661cbfe996d --- /dev/null +++ b/deploy/paddleocr-go/go.sum @@ -0,0 +1,9 @@ +github.com/LKKlein/gocv v0.27.0 h1:JGNBMa2HY7HC0VlVHB4gdFjoc9NlyyrQvlUdBMWWSYw= +github.com/LKKlein/gocv v0.27.0/go.mod h1:MP408EL7eakRU3vzjsozzfELSX7HDDGdMpWANV1IOHY= +github.com/LKKlein/gocv v0.28.0 h1:1MMvs9uYf+QGPi86it2pUmN8RRoyMnPLUefKB/Jf1Q0= +github.com/LKKlein/gocv v0.28.0/go.mod h1:MP408EL7eakRU3vzjsozzfELSX7HDDGdMpWANV1IOHY= +github.com/ctessum/go.clipper v0.0.0-20200522184404-9c744fa3e86c h1:VXCsVlam0R2Yl7VET2GxZBPdOa7gFRexyhfWb9v9QtM= +github.com/ctessum/go.clipper v0.0.0-20200522184404-9c744fa3e86c/go.mod h1:KRMo3PCsooJP3LmCwKI76dkd7f3ki3zwYLHR7Iwbi5k= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/deploy/paddleocr-go/images/0.jpg b/deploy/paddleocr-go/images/0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b417220d9a6ebf0ed4d5c5578eb0128f8701ab6e Binary files /dev/null and b/deploy/paddleocr-go/images/0.jpg differ diff --git a/deploy/paddleocr-go/images/result/img_dir_result.jpg b/deploy/paddleocr-go/images/result/img_dir_result.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5341f81648d568dc0b844b7779d6cd8a7a4b870f Binary files /dev/null and b/deploy/paddleocr-go/images/result/img_dir_result.jpg differ diff --git a/deploy/paddleocr-go/images/result/python_client_result.jpg b/deploy/paddleocr-go/images/result/python_client_result.jpg new file mode 100644 index 0000000000000000000000000000000000000000..556573a99850552d2859219773ab70589d9ac065 Binary files /dev/null and b/deploy/paddleocr-go/images/result/python_client_result.jpg differ diff --git a/deploy/paddleocr-go/images/result/python_vis_result.jpg b/deploy/paddleocr-go/images/result/python_vis_result.jpg new file mode 100644 index 0000000000000000000000000000000000000000..af26bdd117bf21adba3b80a6d73c7ff54b46b2bc Binary files /dev/null and b/deploy/paddleocr-go/images/result/python_vis_result.jpg differ diff --git a/deploy/paddleocr-go/images/result/single_img_result.jpg b/deploy/paddleocr-go/images/result/single_img_result.jpg new file mode 100644 index 0000000000000000000000000000000000000000..686841d71e12a6142c32288fc41bbc03a9edf177 Binary files /dev/null and b/deploy/paddleocr-go/images/result/single_img_result.jpg differ diff --git a/deploy/paddleocr-go/images/test.jpg b/deploy/paddleocr-go/images/test.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ed91b8c5ca2a348fe7b138e83114ff81ecb107de Binary files /dev/null and b/deploy/paddleocr-go/images/test.jpg differ diff --git a/deploy/paddleocr-go/ocr/core.go b/deploy/paddleocr-go/ocr/core.go new file mode 100644 index 0000000000000000000000000000000000000000..626eeb5410b419bf6bbd9c8fc64f40d495e9c0d8 --- /dev/null +++ b/deploy/paddleocr-go/ocr/core.go @@ -0,0 +1,259 @@ +package ocr + +import ( + "bytes" + "encoding/json" + "errors" + "image" + "image/color" + "io" + "log" + "math" + "net/http" + "paddleocr-go/paddle" + "path" + "path/filepath" + "sort" + "strings" + + "github.com/LKKlein/gocv" +) + +type PaddleModel struct { + predictor *paddle.Predictor + input *paddle.ZeroCopyTensor + outputs []*paddle.ZeroCopyTensor + + useGPU bool + deviceID int + initGPUMem int + numThreads int + useMKLDNN bool + useTensorRT bool + useIROptim bool +} + +func NewPaddleModel(args map[string]interface{}) *PaddleModel { + return &PaddleModel{ + useGPU: getBool(args, "use_gpu", false), + deviceID: getInt(args, "gpu_id", 0), + initGPUMem: getInt(args, "gpu_mem", 1000), + numThreads: getInt(args, "num_cpu_threads", 6), + useMKLDNN: getBool(args, "enable_mkldnn", false), + useTensorRT: getBool(args, "use_tensorrt", false), + useIROptim: getBool(args, "ir_optim", true), + } +} + +func (model *PaddleModel) LoadModel(modelDir string) { + config := paddle.NewAnalysisConfig() + config.DisableGlogInfo() + + config.SetModel(modelDir+"/model", modelDir+"/params") + if model.useGPU { + config.EnableUseGpu(model.initGPUMem, model.deviceID) + } else { + config.DisableGpu() + config.SetCpuMathLibraryNumThreads(model.numThreads) + if model.useMKLDNN { + config.EnableMkldnn() + } + } + + // config.EnableMemoryOptim() + if model.useIROptim { + config.SwitchIrOptim(true) + } + + // false for zero copy tensor + config.SwitchUseFeedFetchOps(false) + config.SwitchSpecifyInputNames(true) + + model.predictor = paddle.NewPredictor(config) + model.input = model.predictor.GetInputTensors()[0] + model.outputs = model.predictor.GetOutputTensors() +} + +type OCRText struct { + BBox [][]int `json:"bbox"` + Text string `json:"text"` + Score float64 `json:"score"` +} + +type TextPredictSystem struct { + detector *DBDetector + cls *TextClassifier + rec *TextRecognizer +} + +func NewTextPredictSystem(args map[string]interface{}) *TextPredictSystem { + sys := &TextPredictSystem{ + detector: NewDBDetector(getString(args, "det_model_dir", ""), args), + rec: NewTextRecognizer(getString(args, "rec_model_dir", ""), args), + } + if getBool(args, "use_angle_cls", false) { + sys.cls = NewTextClassifier(getString(args, "cls_model_dir", ""), args) + } + return sys +} + +func (sys *TextPredictSystem) sortBoxes(boxes [][][]int) [][][]int { + sort.Slice(boxes, func(i, j int) bool { + if boxes[i][0][1] < boxes[j][0][1] { + return true + } + if boxes[i][0][1] > boxes[j][0][1] { + return false + } + return boxes[i][0][0] < boxes[j][0][0] + }) + + for i := 0; i < len(boxes)-1; i++ { + if math.Abs(float64(boxes[i+1][0][1]-boxes[i][0][1])) < 10 && boxes[i+1][0][0] < boxes[i][0][0] { + boxes[i], boxes[i+1] = boxes[i+1], boxes[i] + } + } + return boxes +} + +func (sys *TextPredictSystem) getRotateCropImage(img gocv.Mat, box [][]int) gocv.Mat { + cropW := int(math.Sqrt(math.Pow(float64(box[0][0]-box[1][0]), 2) + math.Pow(float64(box[0][1]-box[1][1]), 2))) + cropH := int(math.Sqrt(math.Pow(float64(box[0][0]-box[3][0]), 2) + math.Pow(float64(box[0][1]-box[3][1]), 2))) + ptsstd := make([]image.Point, 4) + ptsstd[0] = image.Pt(0, 0) + ptsstd[1] = image.Pt(cropW, 0) + ptsstd[2] = image.Pt(cropW, cropH) + ptsstd[3] = image.Pt(0, cropH) + + points := make([]image.Point, 4) + points[0] = image.Pt(box[0][0], box[0][1]) + points[1] = image.Pt(box[1][0], box[1][1]) + points[2] = image.Pt(box[2][0], box[2][1]) + points[3] = image.Pt(box[3][0], box[3][1]) + + M := gocv.GetPerspectiveTransform(points, ptsstd) + defer M.Close() + dstimg := gocv.NewMat() + gocv.WarpPerspectiveWithParams(img, &dstimg, M, image.Pt(cropW, cropH), + gocv.InterpolationCubic, gocv.BorderReplicate, color.RGBA{0, 0, 0, 0}) + + if float64(dstimg.Rows()) >= float64(dstimg.Cols())*1.5 { + srcCopy := gocv.NewMat() + gocv.Transpose(dstimg, &srcCopy) + defer dstimg.Close() + gocv.Flip(srcCopy, &srcCopy, 0) + return srcCopy + } + return dstimg +} + +func (sys *TextPredictSystem) Run(img gocv.Mat) []OCRText { + srcimg := gocv.NewMat() + img.CopyTo(&srcimg) + boxes := sys.detector.Run(img) + if len(boxes) == 0 { + return nil + } + + boxes = sys.sortBoxes(boxes) + cropimages := make([]gocv.Mat, len(boxes)) + for i := 0; i < len(boxes); i++ { + tmpbox := make([][]int, len(boxes[i])) + for j := 0; j < len(tmpbox); j++ { + tmpbox[j] = make([]int, len(boxes[i][j])) + copy(tmpbox[j], boxes[i][j]) + } + cropimg := sys.getRotateCropImage(srcimg, tmpbox) + cropimages[i] = cropimg + } + if sys.cls != nil { + cropimages = sys.cls.Run(cropimages) + } + recResult := sys.rec.Run(cropimages, boxes) + return recResult +} + +type OCRSystem struct { + args map[string]interface{} + tps *TextPredictSystem +} + +func NewOCRSystem(confFile string, a map[string]interface{}) *OCRSystem { + args, err := ReadYaml(confFile) + if err != nil { + log.Printf("Read config file %v failed! Please check. err: %v\n", confFile, err) + log.Println("Program will use default config.") + args = defaultArgs + } + for k, v := range a { + args[k] = v + } + return &OCRSystem{ + args: args, + tps: NewTextPredictSystem(args), + } +} + +func (ocr *OCRSystem) StartServer(port string) { + http.HandleFunc("/ocr", ocr.predictHandler) + log.Println("OCR Server has been started on port :", port) + err := http.ListenAndServe(":"+port, nil) + if err != nil { + log.Panicf("http error! error: %v\n", err) + } +} + +func (ocr *OCRSystem) predictHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + w.Write([]byte(errors.New("post method only").Error())) + return + } + r.ParseMultipartForm(32 << 20) + var buf bytes.Buffer + file, header, err := r.FormFile("image") + if err != nil { + w.Write([]byte(err.Error())) + return + } + defer file.Close() + ext := strings.ToLower(path.Ext(header.Filename)) + if ext != ".jpg" && ext != ".png" { + w.Write([]byte(errors.New("only support image endswith jpg/png").Error())) + return + } + + io.Copy(&buf, file) + img, err2 := gocv.IMDecode(buf.Bytes(), gocv.IMReadColor) + if err2 != nil { + w.Write([]byte(err2.Error())) + return + } + result := ocr.PredictOneImage(img) + if output, err3 := json.Marshal(result); err3 != nil { + w.Write([]byte(err3.Error())) + } else { + w.Write(output) + } +} + +func (ocr *OCRSystem) PredictOneImage(img gocv.Mat) []OCRText { + return ocr.tps.Run(img) +} + +func (ocr *OCRSystem) PredictDirImages(dirname string) map[string][]OCRText { + if dirname == "" { + return nil + } + + imgs, _ := filepath.Glob(dirname + "/*.jpg") + tmpimgs, _ := filepath.Glob(dirname + "/*.png") + imgs = append(imgs, tmpimgs...) + results := make(map[string][]OCRText, len(imgs)) + for i := 0; i < len(imgs); i++ { + imgname := imgs[i] + img := ReadImage(imgname) + res := ocr.PredictOneImage(img) + results[imgname] = res + } + return results +} diff --git a/deploy/paddleocr-go/ocr/default_args.go b/deploy/paddleocr-go/ocr/default_args.go new file mode 100644 index 0000000000000000000000000000000000000000..6afd69e7fa01cd9227c3679a3d07dc781e8a2638 --- /dev/null +++ b/deploy/paddleocr-go/ocr/default_args.go @@ -0,0 +1,46 @@ +package ocr + +var ( + defaultArgs = map[string]interface{}{ + "use_gpu": true, + "ir_optim": true, + "enable_mkldnn": false, + "use_tensorrt": false, + "num_cpu_threads": 6, + "gpu_id": 0, + "gpu_mem": 2000, + + "det_algorithm": "DB", + "det_model_dir": "https://paddleocr.bj.bcebos.com/20-09-22/mobile/det/ch_ppocr_mobile_v1.1_det_infer.tar", + "det_max_side_len": 960, + + "det_db_thresh": 0.3, + "det_db_box_thresh": 0.5, + "det_db_unclip_ratio": 2.0, + + "det_east_score_thresh": 0.8, + "det_east_cover_thresh": 0.1, + "det_east_nms_thresh": 0.2, + + "rec_algorithm": "CRNN", + "rec_model_dir": "https://paddleocr.bj.bcebos.com/20-09-22/mobile/rec/ch_ppocr_mobile_v1.1_rec_infer.tar", + "rec_image_shape": []interface{}{3, 32, 320}, + "rec_char_type": "ch", + "rec_batch_num": 30, + "max_text_length": 25, + "rec_char_dict_path": "config/ppocr_keys_v1.txt", + "use_space_char": true, + + "use_angle_cls": false, + "cls_model_dir": "https://paddleocr.bj.bcebos.com/20-09-22/cls/ch_ppocr_mobile_v1.1_cls_infer.tar", + "cls_image_shape": []interface{}{3, 48, 192}, + "label_list": []interface{}{"0", "180"}, + "cls_batch_num": 30, + "cls_thresh": 0.9, + + "lang": "ch", + "det": true, + "rec": true, + "cls": false, + } +) diff --git a/deploy/paddleocr-go/ocr/ocr_cls.go b/deploy/paddleocr-go/ocr/ocr_cls.go new file mode 100644 index 0000000000000000000000000000000000000000..c46d6e8e55a6090dfd6839badc1963e8c50d18ea --- /dev/null +++ b/deploy/paddleocr-go/ocr/ocr_cls.go @@ -0,0 +1,105 @@ +package ocr + +import ( + "log" + "time" + + "github.com/LKKlein/gocv" +) + +type TextClassifier struct { + *PaddleModel + batchNum int + thresh float64 + shape []int + labels []string +} + +type ClsResult struct { + Score float32 + Label int64 +} + +func NewTextClassifier(modelDir string, args map[string]interface{}) *TextClassifier { + shapes := []int{3, 48, 192} + if v, ok := args["cls_image_shape"]; ok { + for i, s := range v.([]interface{}) { + shapes[i] = s.(int) + } + } + cls := &TextClassifier{ + PaddleModel: NewPaddleModel(args), + batchNum: getInt(args, "cls_batch_num", 30), + thresh: getFloat64(args, "cls_thresh", 0.9), + shape: shapes, + } + if checkModelExists(modelDir) { + modelDir, _ = downloadModel("./inference/cls", modelDir) + } else { + log.Panicf("cls model path: %v not exist! Please check!", modelDir) + } + cls.LoadModel(modelDir) + return cls +} + +func (cls *TextClassifier) Run(imgs []gocv.Mat) []gocv.Mat { + batch := cls.batchNum + var clsTime int64 = 0 + clsout := make([]ClsResult, len(imgs)) + srcimgs := make([]gocv.Mat, len(imgs)) + c, h, w := cls.shape[0], cls.shape[1], cls.shape[2] + for i := 0; i < len(imgs); i += batch { + j := i + batch + if len(imgs) < j { + j = len(imgs) + } + + normImgs := make([]float32, (j-i)*c*h*w) + for k := i; k < j; k++ { + tmp := gocv.NewMat() + imgs[k].CopyTo(&tmp) + srcimgs[k] = tmp + img := clsResize(imgs[k], cls.shape) + data := normPermute(img, []float32{0.5, 0.5, 0.5}, []float32{0.5, 0.5, 0.5}, 255.0) + copy(normImgs[(k-i)*c*h*w:], data) + } + + st := time.Now() + cls.input.SetValue(normImgs) + cls.input.Reshape([]int32{int32(j - i), int32(c), int32(h), int32(w)}) + + cls.predictor.SetZeroCopyInput(cls.input) + cls.predictor.ZeroCopyRun() + cls.predictor.GetZeroCopyOutput(cls.outputs[0]) + cls.predictor.GetZeroCopyOutput(cls.outputs[1]) + + var probout [][]float32 + var labelout []int64 + if len(cls.outputs[0].Shape()) == 2 { + probout = cls.outputs[0].Value().([][]float32) + } else { + labelout = cls.outputs[0].Value().([]int64) + } + + if len(cls.outputs[1].Shape()) == 2 { + probout = cls.outputs[1].Value().([][]float32) + } else { + labelout = cls.outputs[1].Value().([]int64) + } + clsTime += int64(time.Since(st).Milliseconds()) + + for no, label := range labelout { + score := probout[no][label] + clsout[i+no] = ClsResult{ + Score: score, + Label: label, + } + + if label%2 == 1 && float64(score) > cls.thresh { + gocv.Rotate(srcimgs[i+no], &srcimgs[i+no], gocv.Rotate180Clockwise) + } + } + } + log.Println("cls num: ", len(clsout), ", cls time elapse: ", clsTime, "ms") + return srcimgs +} diff --git a/deploy/paddleocr-go/ocr/ocr_det.go b/deploy/paddleocr-go/ocr/ocr_det.go new file mode 100644 index 0000000000000000000000000000000000000000..4773d5bed17887c91ac21054b162deae9ce962ce --- /dev/null +++ b/deploy/paddleocr-go/ocr/ocr_det.go @@ -0,0 +1,52 @@ +package ocr + +import ( + "log" + "time" + + "github.com/LKKlein/gocv" +) + +type DBDetector struct { + *PaddleModel + preProcess DetPreProcess + postProcess DetPostProcess +} + +func NewDBDetector(modelDir string, args map[string]interface{}) *DBDetector { + maxSideLen := getInt(args, "det_max_side_len", 960) + thresh := getFloat64(args, "det_db_thresh", 0.3) + boxThresh := getFloat64(args, "det_db_box_thresh", 0.5) + unClipRatio := getFloat64(args, "det_db_unclip_ratio", 2.0) + + detector := &DBDetector{ + PaddleModel: NewPaddleModel(args), + preProcess: NewDBProcess(make([]int, 0), maxSideLen), + postProcess: NewDBPostProcess(thresh, boxThresh, unClipRatio), + } + if checkModelExists(modelDir) { + modelDir, _ = downloadModel("./inference/det", modelDir) + } else { + log.Panicf("det model path: %v not exist! Please check!", modelDir) + } + detector.LoadModel(modelDir) + return detector +} + +func (det *DBDetector) Run(img gocv.Mat) [][][]int { + oriH := img.Rows() + oriW := img.Cols() + data, resizeH, resizeW := det.preProcess.Run(img) + st := time.Now() + det.input.SetValue(data) + det.input.Reshape([]int32{1, 3, int32(resizeH), int32(resizeW)}) + + det.predictor.SetZeroCopyInput(det.input) + det.predictor.ZeroCopyRun() + det.predictor.GetZeroCopyOutput(det.outputs[0]) + + ratioH, ratioW := float64(resizeH)/float64(oriH), float64(resizeW)/float64(oriW) + boxes := det.postProcess.Run(det.outputs[0], oriH, oriW, ratioH, ratioW) + log.Println("det_box num: ", len(boxes), ", time elapse: ", time.Since(st)) + return boxes +} diff --git a/deploy/paddleocr-go/ocr/ocr_rec.go b/deploy/paddleocr-go/ocr/ocr_rec.go new file mode 100644 index 0000000000000000000000000000000000000000..661718c87778030bf6efb9d03c51d45a4fb0e30c --- /dev/null +++ b/deploy/paddleocr-go/ocr/ocr_rec.go @@ -0,0 +1,128 @@ +package ocr + +import ( + "log" + "time" + + "github.com/LKKlein/gocv" +) + +type TextRecognizer struct { + *PaddleModel + batchNum int + textLen int + shape []int + charType string + labels []string +} + +func NewTextRecognizer(modelDir string, args map[string]interface{}) *TextRecognizer { + shapes := []int{3, 32, 320} + if v, ok := args["rec_image_shape"]; ok { + for i, s := range v.([]interface{}) { + shapes[i] = s.(int) + } + } + labelpath := getString(args, "rec_char_dict_path", "./config/ppocr_keys_v1.txt") + labels := readLines2StringSlice(labelpath) + if getBool(args, "use_space_char", true) { + labels = append(labels, " ") + } + rec := &TextRecognizer{ + PaddleModel: NewPaddleModel(args), + batchNum: getInt(args, "rec_batch_num", 30), + textLen: getInt(args, "max_text_length", 25), + charType: getString(args, "rec_char_type", "ch"), + shape: shapes, + labels: labels, + } + if checkModelExists(modelDir) { + modelDir, _ = downloadModel("./inference/rec/ch", modelDir) + } else { + log.Panicf("rec model path: %v not exist! Please check!", modelDir) + } + rec.LoadModel(modelDir) + return rec +} + +func (rec *TextRecognizer) Run(imgs []gocv.Mat, bboxes [][][]int) []OCRText { + recResult := make([]OCRText, 0, len(imgs)) + batch := rec.batchNum + var recTime int64 = 0 + c, h, w := rec.shape[0], rec.shape[1], rec.shape[2] + for i := 0; i < len(imgs); i += batch { + j := i + batch + if len(imgs) < j { + j = len(imgs) + } + + maxwhratio := 0.0 + for k := i; k < j; k++ { + h, w := imgs[k].Rows(), imgs[k].Cols() + ratio := float64(w) / float64(h) + if ratio > maxwhratio { + maxwhratio = ratio + } + } + + if rec.charType == "ch" { + w = int(32 * maxwhratio) + } + normimgs := make([]float32, (j-i)*c*h*w) + + for k := i; k < j; k++ { + data := crnnPreprocess(imgs[k], rec.shape, []float32{0.5, 0.5, 0.5}, + []float32{0.5, 0.5, 0.5}, 255.0, maxwhratio, rec.charType) + copy(normimgs[(k-i)*c*h*w:], data) + } + + st := time.Now() + rec.input.SetValue(normimgs) + rec.input.Reshape([]int32{int32(j - i), int32(c), int32(h), int32(w)}) + + rec.predictor.SetZeroCopyInput(rec.input) + rec.predictor.ZeroCopyRun() + rec.predictor.GetZeroCopyOutput(rec.outputs[0]) + rec.predictor.GetZeroCopyOutput(rec.outputs[1]) + + recIdxBatch := rec.outputs[0].Value().([][]int64) + recIdxLod := rec.outputs[0].Lod() + + predictBatch := rec.outputs[1].Value().([][]float32) + predictLod := rec.outputs[1].Lod() + recTime += int64(time.Since(st).Milliseconds()) + + for rno := 0; rno < len(recIdxLod)-1; rno++ { + predIdx := make([]int, 0, 2) + for beg := recIdxLod[rno]; beg < recIdxLod[rno+1]; beg++ { + predIdx = append(predIdx, int(recIdxBatch[beg][0])) + } + if len(predIdx) == 0 { + continue + } + words := "" + for n := 0; n < len(predIdx); n++ { + words += rec.labels[predIdx[n]] + } + + score := 0.0 + count := 0 + blankPosition := int(rec.outputs[1].Shape()[1]) + for beg := predictLod[rno]; beg < predictLod[rno+1]; beg++ { + argMaxID, maxVal := argmax(predictBatch[beg]) + if blankPosition-1-argMaxID > 0 { + score += float64(maxVal) + count++ + } + } + score = score / float64(count) + recResult = append(recResult, OCRText{ + BBox: bboxes[i+rno], + Text: words, + Score: score, + }) + } + } + log.Println("rec num: ", len(recResult), ", rec time elapse: ", recTime, "ms") + return recResult +} diff --git a/deploy/paddleocr-go/ocr/postprocess.go b/deploy/paddleocr-go/ocr/postprocess.go new file mode 100644 index 0000000000000000000000000000000000000000..64d1518b554e5e115a653a4e24d00bc9c4f06478 --- /dev/null +++ b/deploy/paddleocr-go/ocr/postprocess.go @@ -0,0 +1,264 @@ +package ocr + +import ( + "image" + "image/color" + "math" + "paddleocr-go/paddle" + "sort" + + "github.com/LKKlein/gocv" + clipper "github.com/ctessum/go.clipper" +) + +type xFloatSortBy [][]float32 + +func (a xFloatSortBy) Len() int { return len(a) } +func (a xFloatSortBy) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a xFloatSortBy) Less(i, j int) bool { return a[i][0] < a[j][0] } + +type xIntSortBy [][]int + +func (a xIntSortBy) Len() int { return len(a) } +func (a xIntSortBy) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a xIntSortBy) Less(i, j int) bool { return a[i][0] < a[j][0] } + +type DetPostProcess interface { + Run(output *paddle.ZeroCopyTensor, oriH, oriW int, ratioH, ratioW float64) [][][]int +} + +type DBPostProcess struct { + thresh float64 + boxThresh float64 + maxCandidates int + unClipRatio float64 + minSize int +} + +func NewDBPostProcess(thresh, boxThresh, unClipRatio float64) *DBPostProcess { + return &DBPostProcess{ + thresh: thresh, + boxThresh: boxThresh, + unClipRatio: unClipRatio, + maxCandidates: 1000, + minSize: 3, + } +} + +func (d *DBPostProcess) getMinBoxes(rect gocv.RotatedRect) [][]float32 { + points := gocv.NewMat() + gocv.BoxPoints(rect, &points) + defer points.Close() + array := d.mat2slice(points) + sort.Sort(xFloatSortBy(array)) + + point1, point2, point3, point4 := array[0], array[1], array[2], array[3] + if array[3][1] <= array[2][1] { + point2, point3 = array[3], array[2] + } else { + point2, point3 = array[2], array[3] + } + + if array[1][1] <= array[0][1] { + point1, point4 = array[1], array[0] + } else { + point1, point4 = array[0], array[1] + } + + array = [][]float32{point1, point2, point3, point4} + return array +} + +func (d *DBPostProcess) mat2slice(mat gocv.Mat) [][]float32 { + array := make([][]float32, mat.Rows()) + for i := 0; i < mat.Rows(); i++ { + tmp := make([]float32, mat.Cols()) + for j := 0; j < mat.Cols(); j++ { + tmp[j] = mat.GetFloatAt(i, j) + } + array[i] = tmp + } + return array +} + +func (d *DBPostProcess) boxScoreFast(array [][]float32, pred gocv.Mat) float64 { + height, width := pred.Rows(), pred.Cols() + boxX := []float32{array[0][0], array[1][0], array[2][0], array[3][0]} + boxY := []float32{array[0][1], array[1][1], array[2][1], array[3][1]} + + xmin := clip(int(math.Floor(float64(minf(boxX)))), 0, width-1) + xmax := clip(int(math.Ceil(float64(maxf(boxX)))), 0, width-1) + ymin := clip(int(math.Floor(float64(minf(boxY)))), 0, height-1) + ymax := clip(int(math.Ceil(float64(maxf(boxY)))), 0, height-1) + + mask := gocv.NewMatWithSize(ymax-ymin+1, xmax-xmin+1, gocv.MatTypeCV8UC1) + ppt := make([][]image.Point, 1) + ppt[0] = make([]image.Point, 4) + ppt[0][0] = image.Point{int(array[0][0]) - xmin, int(array[0][1]) - ymin} + ppt[0][1] = image.Point{int(array[1][0]) - xmin, int(array[1][1]) - ymin} + ppt[0][2] = image.Point{int(array[2][0]) - xmin, int(array[2][1]) - ymin} + ppt[0][3] = image.Point{int(array[3][0]) - xmin, int(array[3][1]) - ymin} + gocv.FillPoly(&mask, ppt, color.RGBA{0, 0, 1, 0}) + croppedImg := pred.Region(image.Rect(xmin, ymin, xmax+1, ymax+1)) + s := croppedImg.MeanWithMask(mask) + return s.Val1 +} + +func (d *DBPostProcess) unClip(box [][]float32) gocv.RotatedRect { + var area, dist float64 + for i := 0; i < 4; i++ { + area += float64(box[i][0]*box[(i+1)%4][1] - box[i][1]*box[(i+1)%4][0]) + dist += math.Sqrt(float64( + (box[i][0]-box[(i+1)%4][0])*(box[i][0]-box[(i+1)%4][0]) + + (box[i][1]-box[(i+1)%4][1])*(box[i][1]-box[(i+1)%4][1]), + )) + } + area = math.Abs(area / 2.0) + distance := area * d.unClipRatio / dist + offset := clipper.NewClipperOffset() + path := make([]*clipper.IntPoint, 4) + path[0] = &clipper.IntPoint{X: clipper.CInt(box[0][0]), Y: clipper.CInt(box[0][1])} + path[1] = &clipper.IntPoint{X: clipper.CInt(box[1][0]), Y: clipper.CInt(box[1][1])} + path[2] = &clipper.IntPoint{X: clipper.CInt(box[2][0]), Y: clipper.CInt(box[2][1])} + path[3] = &clipper.IntPoint{X: clipper.CInt(box[3][0]), Y: clipper.CInt(box[3][1])} + offset.AddPath(clipper.Path(path), clipper.JtRound, clipper.EtClosedPolygon) + soln := offset.Execute(distance) + + points := make([]image.Point, 0, 4) + for i := 0; i < len(soln); i++ { + for j := 0; j < len(soln[i]); j++ { + points = append(points, image.Point{int(soln[i][j].X), int(soln[i][j].Y)}) + } + } + + var res gocv.RotatedRect + if len(points) <= 0 { + points = make([]image.Point, 4) + points[0] = image.Pt(0, 0) + points[1] = image.Pt(1, 0) + points[2] = image.Pt(1, 1) + points[3] = image.Pt(0, 1) + res = gocv.RotatedRect{ + Contour: points, + BoundingRect: image.Rect(0, 0, 1, 1), + Center: gocv.Point2f{X: 0.5, Y: 0.5}, + Width: 1, + Height: 1, + Angle: 0, + } + } else { + res = gocv.MinAreaRect(points) + } + return res +} + +func (d *DBPostProcess) boxesFromBitmap(pred gocv.Mat, mask gocv.Mat, ratioH float64, ratioW float64) [][][]int { + height, width := mask.Rows(), mask.Cols() + mask.MultiplyUChar(255) + contours := gocv.FindContours(mask, gocv.RetrievalList, gocv.ChainApproxSimple) + numContours := len(contours) + if numContours > d.maxCandidates { + numContours = d.maxCandidates + } + + boxes := make([][][]int, 0, numContours) + for i := 0; i < numContours; i++ { + contour := contours[i] + boundingbox := gocv.MinAreaRect(contour) + if boundingbox.Width < float32(d.minSize) || boundingbox.Height < float32(d.minSize) { + continue + } + points := d.getMinBoxes(boundingbox) + score := d.boxScoreFast(points, pred) + if score < d.boxThresh { + continue + } + + box := d.unClip(points) + if box.Width < float32(d.minSize+2) || box.Height < float32(d.minSize+2) { + continue + } + + cliparray := d.getMinBoxes(box) + dstHeight, dstWidth := pred.Rows(), pred.Cols() + intcliparray := make([][]int, 4) + for i := 0; i < 4; i++ { + p := []int{ + int(float64(clip(int(math.Round( + float64(cliparray[i][0]/float32(width)*float32(dstWidth)))), 0, dstWidth)) / ratioW), + int(float64(clip(int(math.Round( + float64(cliparray[i][1]/float32(height)*float32(dstHeight)))), 0, dstHeight)) / ratioH), + } + intcliparray[i] = p + } + boxes = append(boxes, intcliparray) + } + return boxes +} + +func (d *DBPostProcess) orderPointsClockwise(box [][]int) [][]int { + sort.Sort(xIntSortBy(box)) + leftmost := [][]int{box[0], box[1]} + rightmost := [][]int{box[2], box[3]} + + if leftmost[0][1] > leftmost[1][1] { + leftmost[0], leftmost[1] = leftmost[1], leftmost[0] + } + + if rightmost[0][1] > rightmost[1][1] { + rightmost[0], rightmost[1] = rightmost[1], rightmost[0] + } + + return [][]int{leftmost[0], rightmost[0], rightmost[1], leftmost[1]} +} + +func (d *DBPostProcess) filterTagDetRes(boxes [][][]int, oriH, oriW int) [][][]int { + points := make([][][]int, 0, len(boxes)) + for i := 0; i < len(boxes); i++ { + boxes[i] = d.orderPointsClockwise(boxes[i]) + for j := 0; j < len(boxes[i]); j++ { + boxes[i][j][0] = clip(boxes[i][j][0], 0, oriW-1) + boxes[i][j][1] = clip(boxes[i][j][1], 0, oriH-1) + } + } + + for i := 0; i < len(boxes); i++ { + rectW := int(math.Sqrt(math.Pow(float64(boxes[i][0][0]-boxes[i][1][0]), 2.0) + + math.Pow(float64(boxes[i][0][1]-boxes[i][1][1]), 2.0))) + rectH := int(math.Sqrt(math.Pow(float64(boxes[i][0][0]-boxes[i][3][0]), 2.0) + + math.Pow(float64(boxes[i][0][1]-boxes[i][3][1]), 2.0))) + if rectW <= 4 || rectH <= 4 { + continue + } + points = append(points, boxes[i]) + } + return points +} + +func (d *DBPostProcess) Run(output *paddle.ZeroCopyTensor, oriH, oriW int, ratioH, ratioW float64) [][][]int { + v := output.Value().([][][][]float32) + + shape := output.Shape() + height, width := int(shape[2]), int(shape[3]) + + pred := gocv.NewMatWithSize(height, width, gocv.MatTypeCV32F) + bitmap := gocv.NewMatWithSize(height, width, gocv.MatTypeCV8UC1) + thresh := float32(d.thresh) + for i := 0; i < height; i++ { + for j := 0; j < width; j++ { + pred.SetFloatAt(i, j, v[0][0][i][j]) + if v[0][0][i][j] > thresh { + bitmap.SetUCharAt(i, j, 1) + } else { + bitmap.SetUCharAt(i, j, 0) + } + } + } + + mask := gocv.NewMat() + kernel := gocv.GetStructuringElement(gocv.MorphRect, image.Point{2, 2}) + gocv.Dilate(bitmap, &mask, kernel) + boxes := d.boxesFromBitmap(pred, mask, ratioH, ratioW) + dtboxes := d.filterTagDetRes(boxes, oriH, oriW) + return dtboxes +} diff --git a/deploy/paddleocr-go/ocr/preprocess.go b/deploy/paddleocr-go/ocr/preprocess.go new file mode 100644 index 0000000000000000000000000000000000000000..a0939e76d6b289b7b90759fd1c79695d956afbbc --- /dev/null +++ b/deploy/paddleocr-go/ocr/preprocess.go @@ -0,0 +1,171 @@ +package ocr + +import ( + "image" + "image/color" + "math" + + "github.com/LKKlein/gocv" +) + +func resizeByShape(img gocv.Mat, resizeShape []int) (gocv.Mat, int, int) { + resizeH := resizeShape[0] + resizeW := resizeShape[1] + gocv.Resize(img, &img, image.Pt(resizeW, resizeH), 0, 0, gocv.InterpolationLinear) + return img, resizeH, resizeW +} + +func resizeByMaxLen(img gocv.Mat, maxLen int) (gocv.Mat, int, int) { + oriH := img.Rows() + oriW := img.Cols() + var resizeH, resizeW int = oriH, oriW + + var ratio float64 = 1.0 + if resizeH > maxLen || resizeW > maxLen { + if resizeH > resizeW { + ratio = float64(maxLen) / float64(resizeH) + } else { + ratio = float64(maxLen) / float64(resizeW) + } + } + + resizeH = int(float64(resizeH) * ratio) + resizeW = int(float64(resizeW) * ratio) + + if resizeH%32 == 0 { + resizeH = resizeH + } else if resizeH/32 <= 1 { + resizeH = 32 + } else { + resizeH = (resizeH/32 - 1) * 32 + } + + if resizeW%32 == 0 { + resizeW = resizeW + } else if resizeW/32 <= 1 { + resizeW = 32 + } else { + resizeW = (resizeW/32 - 1) * 32 + } + + if resizeW <= 0 || resizeH <= 0 { + return gocv.NewMat(), 0, 0 + } + + gocv.Resize(img, &img, image.Pt(resizeW, resizeH), 0, 0, gocv.InterpolationLinear) + return img, resizeH, resizeW +} + +func normPermute(img gocv.Mat, mean []float32, std []float32, scaleFactor float32) []float32 { + img.ConvertTo(&img, gocv.MatTypeCV32F) + img.DivideFloat(scaleFactor) + defer img.Close() + + c := gocv.Split(img) + data := make([]float32, img.Rows()*img.Cols()*img.Channels()) + for i := 0; i < 3; i++ { + c[i].SubtractFloat(mean[i]) + c[i].DivideFloat(std[i]) + defer c[i].Close() + x, _ := c[i].DataPtrFloat32() + copy(data[i*img.Rows()*img.Cols():], x) + } + return data +} + +type DetPreProcess interface { + Run(gocv.Mat) ([]float32, int, int) +} + +type DBPreProcess struct { + resizeType int + imageShape []int + maxSideLen int + mean []float32 + std []float32 + scaleFactor float32 +} + +func NewDBProcess(shape []int, sideLen int) *DBPreProcess { + db := &DBPreProcess{ + resizeType: 0, + imageShape: shape, + maxSideLen: sideLen, + mean: []float32{0.485, 0.456, 0.406}, + std: []float32{0.229, 0.224, 0.225}, + scaleFactor: 255.0, + } + if len(shape) > 0 { + db.resizeType = 1 + } + if sideLen == 0 { + db.maxSideLen = 2400 + } + return db +} + +func (d *DBPreProcess) Run(img gocv.Mat) ([]float32, int, int) { + var resizeH, resizeW int + if d.resizeType == 0 { + img, resizeH, resizeW = resizeByMaxLen(img, d.maxSideLen) + } else { + img, resizeH, resizeW = resizeByShape(img, d.imageShape) + } + + im := normPermute(img, d.mean, d.std, d.scaleFactor) + return im, resizeH, resizeW +} + +func clsResize(img gocv.Mat, resizeShape []int) gocv.Mat { + imgH, imgW := resizeShape[1], resizeShape[2] + h, w := img.Rows(), img.Cols() + ratio := float64(w) / float64(h) + var resizeW int + if math.Ceil(float64(imgH)*ratio) > float64(imgW) { + resizeW = imgW + } else { + resizeW = int(math.Ceil(float64(imgH) * ratio)) + } + gocv.Resize(img, &img, image.Pt(resizeW, imgH), 0, 0, gocv.InterpolationLinear) + if resizeW < imgW { + gocv.CopyMakeBorder(img, &img, 0, 0, 0, imgW-resizeW, gocv.BorderConstant, color.RGBA{0, 0, 0, 0}) + } + return img +} + +func crnnPreprocess(img gocv.Mat, resizeShape []int, mean []float32, std []float32, + scaleFactor float32, whRatio float64, charType string) []float32 { + imgH := resizeShape[1] + imgW := resizeShape[2] + if charType == "ch" { + imgW = int(32 * whRatio) + } + h, w := img.Rows(), img.Cols() + ratio := float64(w) / float64(h) + var resizeW int + if math.Ceil(float64(imgH)*ratio) > float64(imgW) { + resizeW = imgW + } else { + resizeW = int(math.Ceil(float64(imgH) * ratio)) + } + gocv.Resize(img, &img, image.Pt(resizeW, imgH), 0, 0, gocv.InterpolationLinear) + + img.ConvertTo(&img, gocv.MatTypeCV32F) + img.DivideFloat(scaleFactor) + img.SubtractScalar(gocv.NewScalar(float64(mean[0]), float64(mean[1]), float64(mean[2]), 0)) + img.DivideScalar(gocv.NewScalar(float64(std[0]), float64(std[1]), float64(std[2]), 0)) + defer img.Close() + + if resizeW < imgW { + gocv.CopyMakeBorder(img, &img, 0, 0, 0, imgW-resizeW, gocv.BorderConstant, color.RGBA{0, 0, 0, 0}) + } + + c := gocv.Split(img) + data := make([]float32, img.Rows()*img.Cols()*img.Channels()) + for i := 0; i < 3; i++ { + defer c[i].Close() + x, _ := c[i].DataPtrFloat32() + copy(data[i*img.Rows()*img.Cols():], x) + } + return data +} diff --git a/deploy/paddleocr-go/ocr/utils.go b/deploy/paddleocr-go/ocr/utils.go new file mode 100644 index 0000000000000000000000000000000000000000..0603d2bba54e9eb9cd744121ca2aaa4df144cee1 --- /dev/null +++ b/deploy/paddleocr-go/ocr/utils.go @@ -0,0 +1,268 @@ +package ocr + +import ( + "archive/tar" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "path" + "path/filepath" + "regexp" + "strings" + + "github.com/LKKlein/gocv" + "gopkg.in/yaml.v3" +) + +func getString(args map[string]interface{}, key string, dv string) string { + if f, ok := args[key]; ok { + return f.(string) + } + return dv +} + +func getFloat64(args map[string]interface{}, key string, dv float64) float64 { + if f, ok := args[key]; ok { + return f.(float64) + } + return dv +} + +func getInt(args map[string]interface{}, key string, dv int) int { + if i, ok := args[key]; ok { + return i.(int) + } + return dv +} + +func getBool(args map[string]interface{}, key string, dv bool) bool { + if b, ok := args[key]; ok { + return b.(bool) + } + return dv +} + +func ReadImage(image_path string) gocv.Mat { + img := gocv.IMRead(image_path, gocv.IMReadColor) + if img.Empty() { + log.Printf("Could not read image %s\n", image_path) + os.Exit(1) + } + return img +} + +func clip(value, min, max int) int { + if value <= min { + return min + } else if value >= max { + return max + } + return value +} + +func minf(data []float32) float32 { + v := data[0] + for _, val := range data { + if val < v { + v = val + } + } + return v +} + +func maxf(data []float32) float32 { + v := data[0] + for _, val := range data { + if val > v { + v = val + } + } + return v +} + +func mini(data []int) int { + v := data[0] + for _, val := range data { + if val < v { + v = val + } + } + return v +} + +func maxi(data []int) int { + v := data[0] + for _, val := range data { + if val > v { + v = val + } + } + return v +} + +func argmax(arr []float32) (int, float32) { + max_value, index := arr[0], 0 + for i, item := range arr { + if item > max_value { + max_value = item + index = i + } + } + return index, max_value +} + +func checkModelExists(modelPath string) bool { + if isPathExist(modelPath+"/model") && isPathExist(modelPath+"/params") { + return true + } + if strings.HasPrefix(modelPath, "http://") || + strings.HasPrefix(modelPath, "ftp://") || strings.HasPrefix(modelPath, "https://") { + return true + } + return false +} + +func downloadFile(filepath, url string) error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + out, err := os.Create(filepath) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, resp.Body) + log.Println("[download_file] from:", url, " to:", filepath) + return err +} + +func isPathExist(path string) bool { + if _, err := os.Stat(path); err == nil { + return true + } else if os.IsNotExist(err) { + return false + } + return false +} + +func downloadModel(modelDir, modelPath string) (string, error) { + if modelPath != "" && (strings.HasPrefix(modelPath, "http://") || + strings.HasPrefix(modelPath, "ftp://") || strings.HasPrefix(modelPath, "https://")) { + reg := regexp.MustCompile("^(http|https|ftp)://[^/]+/(.+)") + suffix := reg.FindStringSubmatch(modelPath)[2] + outPath := filepath.Join(modelDir, suffix) + outDir := filepath.Dir(outPath) + if !isPathExist(outDir) { + os.MkdirAll(outDir, os.ModePerm) + } + + if !isPathExist(outPath) { + err := downloadFile(outPath, modelPath) + if err != nil { + return "", err + } + } + if strings.HasSuffix(outPath, ".tar") { + _, f := path.Split(suffix) + nextDir := strings.TrimSuffix(f, ".tar") + finalPath := modelDir + "/" + nextDir + if !checkModelExists(finalPath) { + unTar(modelDir, outPath) + } + return finalPath, nil + } + return outPath, nil + } + return modelPath, nil +} + +func unTar(dst, src string) (err error) { + fr, err := os.Open(src) + if err != nil { + return err + } + defer fr.Close() + + tr := tar.NewReader(fr) + for { + hdr, err := tr.Next() + + switch { + case err == io.EOF: + return nil + case err != nil: + return err + case hdr == nil: + continue + } + + dstFileDir := filepath.Join(dst, hdr.Name) + + switch hdr.Typeflag { + case tar.TypeDir: + if b := isPathExist(dstFileDir); !b { + if err := os.MkdirAll(dstFileDir, 0775); err != nil { + return err + } + } + case tar.TypeReg: + file, err := os.OpenFile(dstFileDir, os.O_CREATE|os.O_RDWR, os.FileMode(hdr.Mode)) + if err != nil { + return err + } + _, err2 := io.Copy(file, tr) + if err2 != nil { + return err2 + } + file.Close() + } + } + + return nil +} + +func readLines2StringSlice(path string) []string { + content, err := ioutil.ReadFile(path) + if err != nil { + log.Println("read file error!") + return nil + } + lines := strings.Split(string(content), "\n") + return lines +} + +func ReadYaml(yamlPath string) (map[string]interface{}, error) { + data, err := ioutil.ReadFile(yamlPath) + if err != nil { + return nil, err + } + var body interface{} + if err := yaml.Unmarshal(data, &body); err != nil { + return nil, err + } + + body = convertYaml2Map(body) + return body.(map[string]interface{}), nil +} + +func convertYaml2Map(i interface{}) interface{} { + switch x := i.(type) { + case map[interface{}]interface{}: + m2 := map[string]interface{}{} + for k, v := range x { + m2[k.(string)] = convertYaml2Map(v) + } + return m2 + case []interface{}: + for i, v := range x { + x[i] = convertYaml2Map(v) + } + } + return i +} diff --git a/deploy/paddleocr-go/paddle/common.go b/deploy/paddleocr-go/paddle/common.go new file mode 100644 index 0000000000000000000000000000000000000000..4bcd443cf247f54943e53f7795aa649b1896242f --- /dev/null +++ b/deploy/paddleocr-go/paddle/common.go @@ -0,0 +1,28 @@ +package paddle + +// #cgo CFLAGS: -I../paddle_c/paddle/include +// #cgo LDFLAGS: -L${SRCDIR}/../paddle_c/paddle/lib -Wl,-rpath=\$ORIGIN/paddle_c/paddle/lib -lpaddle_fluid_c +// #include +// #include "paddle_c_api.h" +import "C" +import "fmt" + +func ConvertCBooleanToGo(b C.bool) bool { + var c_false C.bool + if b != c_false { + return true + } + return false +} + +func numel(shape []int32) int32 { + n := int32(1) + for _, d := range shape { + n *= d + } + return n +} + +func bug(format string, args ...interface{}) error { + return fmt.Errorf("Bug %v", fmt.Sprintf(format, args...)) +} diff --git a/deploy/paddleocr-go/paddle/config.go b/deploy/paddleocr-go/paddle/config.go new file mode 100644 index 0000000000000000000000000000000000000000..3201923936dca57aa809a058a76e34d00790057d --- /dev/null +++ b/deploy/paddleocr-go/paddle/config.go @@ -0,0 +1,183 @@ +package paddle + +// #cgo CFLAGS: -I../paddle_c/paddle/include +// #cgo LDFLAGS: -L${SRCDIR}/../paddle_c/paddle/lib -Wl,-rpath,$ORIGIN/paddle_c/paddle/lib -lpaddle_fluid_c +// #include +// #include +// #include +import "C" + +import ( + "runtime" + "unsafe" +) + +type Precision C.Precision + +const ( + Precision_FLOAT32 Precision = C.kFloat32 + Precision_INT8 Precision = C.kInt8 + Precision_HALF Precision = C.kHalf +) + +type AnalysisConfig struct { + c *C.PD_AnalysisConfig +} + +func NewAnalysisConfig() *AnalysisConfig { + c_config := C.PD_NewAnalysisConfig() + config := &AnalysisConfig{c: c_config} + runtime.SetFinalizer(config, (*AnalysisConfig).finalize) + return config +} + +func (config *AnalysisConfig) finalize() { + C.PD_DeleteAnalysisConfig(config.c) +} + +func (config *AnalysisConfig) SetModel(model, params string) { + c_model := C.CString(model) + defer C.free(unsafe.Pointer(c_model)) + var c_params *C.char + if params == "" { + c_params = nil + } else { + c_params = C.CString(params) + defer C.free(unsafe.Pointer(c_params)) + } + + C.PD_SetModel(config.c, c_model, c_params) +} + +func (config *AnalysisConfig) ModelDir() string { + return C.GoString(C.PD_ModelDir(config.c)) +} + +func (config *AnalysisConfig) ProgFile() string { + return C.GoString(C.PD_ProgFile(config.c)) +} + +func (config *AnalysisConfig) ParamsFile() string { + return C.GoString(C.PD_ParamsFile(config.c)) +} + +func (config *AnalysisConfig) EnableUseGpu(memory_pool_init_size_mb int, device_id int) { + C.PD_EnableUseGpu(config.c, C.int(memory_pool_init_size_mb), C.int(device_id)) +} + +func (config *AnalysisConfig) DisableGpu() { + C.PD_DisableGpu(config.c) +} + +func (config *AnalysisConfig) UseGpu() bool { + return ConvertCBooleanToGo(C.PD_UseGpu(config.c)) +} + +func (config *AnalysisConfig) GpuDeviceId() int { + return int(C.PD_GpuDeviceId(config.c)) +} + +func (config *AnalysisConfig) MemoryPoolInitSizeMb() int { + return int(C.PD_MemoryPoolInitSizeMb(config.c)) +} + +func (config *AnalysisConfig) EnableCudnn() { + C.PD_EnableCUDNN(config.c) +} + +func (config *AnalysisConfig) CudnnEnabled() bool { + return ConvertCBooleanToGo(C.PD_CudnnEnabled(config.c)) +} + +func (config *AnalysisConfig) SwitchIrOptim(x bool) { + C.PD_SwitchIrOptim(config.c, C.bool(x)) +} + +func (config *AnalysisConfig) IrOptim() bool { + return ConvertCBooleanToGo(C.PD_IrOptim(config.c)) +} + +func (config *AnalysisConfig) SwitchUseFeedFetchOps(x bool) { + C.PD_SwitchUseFeedFetchOps(config.c, C.bool(x)) +} + +func (config *AnalysisConfig) UseFeedFetchOpsEnabled() bool { + return ConvertCBooleanToGo(C.PD_UseFeedFetchOpsEnabled(config.c)) +} + +func (config *AnalysisConfig) SwitchSpecifyInputNames(x bool) { + C.PD_SwitchSpecifyInputNames(config.c, C.bool(x)) +} + +func (config *AnalysisConfig) SpecifyInputName() bool { + return ConvertCBooleanToGo(C.PD_SpecifyInputName(config.c)) +} + +func (config *AnalysisConfig) EnableTensorRtEngine(workspace_size int, max_batch_size int, min_subgraph_size int, precision Precision, use_static bool, use_calib_mode bool) { + C.PD_EnableTensorRtEngine(config.c, C.int(workspace_size), C.int(max_batch_size), C.int(min_subgraph_size), C.Precision(precision), C.bool(use_static), C.bool(use_calib_mode)) +} + +func (config *AnalysisConfig) TensorrtEngineEnabled() bool { + return ConvertCBooleanToGo(C.PD_TensorrtEngineEnabled(config.c)) +} + +func (config *AnalysisConfig) SwitchIrDebug(x bool) { + C.PD_SwitchIrDebug(config.c, C.bool(x)) +} + +func (config *AnalysisConfig) EnableMkldnn() { + C.PD_EnableMKLDNN(config.c) +} + +func (config *AnalysisConfig) SetCpuMathLibraryNumThreads(n int) { + C.PD_SetCpuMathLibraryNumThreads(config.c, C.int(n)) +} + +func (config *AnalysisConfig) CpuMathLibraryNumThreads() int { + return int(C.PD_CpuMathLibraryNumThreads(config.c)) +} + +func (config *AnalysisConfig) EnableMkldnnQuantizer() { + C.PD_EnableMkldnnQuantizer(config.c) +} + +func (config *AnalysisConfig) MkldnnQuantizerEnabled() bool { + return ConvertCBooleanToGo(C.PD_MkldnnQuantizerEnabled(config.c)) +} + +// SetModelBuffer +// ModelFromMemory + +func (config *AnalysisConfig) EnableMemoryOptim() { + C.PD_EnableMemoryOptim(config.c) +} + +func (config *AnalysisConfig) MemoryOptimEnabled() bool { + return ConvertCBooleanToGo(C.PD_MemoryOptimEnabled(config.c)) +} + +func (config *AnalysisConfig) EnableProfile() { + C.PD_EnableProfile(config.c) +} + +func (config *AnalysisConfig) ProfileEnabled() bool { + return ConvertCBooleanToGo(C.PD_ProfileEnabled(config.c)) +} + +func (config *AnalysisConfig) DisableGlogInfo() { + C.PD_DisableGlogInfo(config.c) +} + +func (config *AnalysisConfig) DeletePass(pass string) { + c_pass := C.CString(pass) + defer C.free(unsafe.Pointer(c_pass)) + C.PD_DeletePass(config.c, c_pass) +} + +func (config *AnalysisConfig) SetInValid() { + C.PD_SetInValid(config.c) +} + +func (config *AnalysisConfig) IsValid() bool { + return ConvertCBooleanToGo(C.PD_IsValid(config.c)) +} diff --git a/deploy/paddleocr-go/paddle/predictor.go b/deploy/paddleocr-go/paddle/predictor.go new file mode 100644 index 0000000000000000000000000000000000000000..f6cba7bc7756aab7f68cb5f84be1fae659c5f61c --- /dev/null +++ b/deploy/paddleocr-go/paddle/predictor.go @@ -0,0 +1,103 @@ +package paddle + +// #cgo CFLAGS: -I../paddle_c/paddle/include +// #cgo LDFLAGS: -L${SRCDIR}/../paddle_c/paddle/lib -Wl,-rpath,$ORIGIN/paddle_c/paddle/lib -lpaddle_fluid_c +// #include +// #include "paddle_c_api.h" +import "C" + +import ( + "reflect" + "runtime" + "unsafe" +) + +type Predictor struct { + c *C.PD_Predictor +} + +func NewPredictor(config *AnalysisConfig) *Predictor { + c_predictor := C.PD_NewPredictor((*config).c) + predictor := &Predictor{c: c_predictor} + runtime.SetFinalizer(predictor, (*Predictor).finalize) + return predictor +} + +func (predictor *Predictor) finalize() { + C.PD_DeletePredictor(predictor.c) +} + +func DeletePredictor(predictor *Predictor) { + C.PD_DeletePredictor(predictor.c) +} + +func (predictor *Predictor) GetInputNum() int { + return int(C.PD_GetInputNum(predictor.c)) +} + +func (predictor *Predictor) GetOutputNum() int { + return int(C.PD_GetOutputNum(predictor.c)) +} + +func (predictor *Predictor) GetInputName(n int) string { + return C.GoString(C.PD_GetInputName(predictor.c, C.int(n))) +} + +func (predictor *Predictor) GetOutputName(n int) string { + return C.GoString(C.PD_GetOutputName(predictor.c, C.int(n))) +} + +func (predictor *Predictor) GetInputTensors() [](*ZeroCopyTensor) { + var result [](*ZeroCopyTensor) + for i := 0; i < predictor.GetInputNum(); i++ { + tensor := NewZeroCopyTensor() + tensor.c.name = C.PD_GetInputName(predictor.c, C.int(i)) + result = append(result, tensor) + } + return result +} + +func (predictor *Predictor) GetOutputTensors() [](*ZeroCopyTensor) { + var result [](*ZeroCopyTensor) + for i := 0; i < predictor.GetOutputNum(); i++ { + tensor := NewZeroCopyTensor() + tensor.c.name = C.PD_GetOutputName(predictor.c, C.int(i)) + result = append(result, tensor) + } + return result +} + +func (predictor *Predictor) GetInputNames() []string { + names := make([]string, predictor.GetInputNum()) + for i := 0; i < len(names); i++ { + names[i] = predictor.GetInputName(i) + } + return names +} + +func (predictor *Predictor) GetOutputNames() []string { + names := make([]string, predictor.GetOutputNum()) + for i := 0; i < len(names); i++ { + names[i] = predictor.GetOutputName(i) + } + return names +} + +func (predictor *Predictor) SetZeroCopyInput(tensor *ZeroCopyTensor) { + C.PD_SetZeroCopyInput(predictor.c, tensor.c) +} + +func (predictor *Predictor) GetZeroCopyOutput(tensor *ZeroCopyTensor) { + C.PD_GetZeroCopyOutput(predictor.c, tensor.c) + tensor.name = C.GoString(tensor.c.name) + var shape []int32 + shape_hdr := (*reflect.SliceHeader)(unsafe.Pointer(&shape)) + shape_hdr.Data = uintptr(unsafe.Pointer(tensor.c.shape.data)) + shape_hdr.Len = int(tensor.c.shape.length / C.sizeof_int) + shape_hdr.Cap = int(tensor.c.shape.length / C.sizeof_int) + tensor.Reshape(shape) +} + +func (predictor *Predictor) ZeroCopyRun() { + C.PD_ZeroCopyRun(predictor.c) +} diff --git a/deploy/paddleocr-go/paddle/tensor.go b/deploy/paddleocr-go/paddle/tensor.go new file mode 100644 index 0000000000000000000000000000000000000000..cf462dcd886861f23ab504569662456d86db9107 --- /dev/null +++ b/deploy/paddleocr-go/paddle/tensor.go @@ -0,0 +1,249 @@ +package paddle + +// #cgo CFLAGS: -I../paddle_c/paddle/include +// #cgo LDFLAGS: -L${SRCDIR}/../paddle_c/paddle/lib -Wl,-rpath,$ORIGIN/paddle_c/paddle/lib -lpaddle_fluid_c +// #include +// #include +// #include +// #include +import "C" + +import ( + "bytes" + "encoding/binary" + "reflect" + "runtime" + "unsafe" +) + +type PaddleDType C.PD_DataType + +const ( + FLOAT32 PaddleDType = C.PD_FLOAT32 + INT32 PaddleDType = C.PD_INT32 + INT64 PaddleDType = C.PD_INT64 + UINT8 PaddleDType = C.PD_UINT8 + UNKDTYPE PaddleDType = C.PD_UNKDTYPE +) + +var types = []struct { + gotype reflect.Type + dtype PaddleDType +}{ + {reflect.TypeOf(float32(0)), FLOAT32}, + {reflect.TypeOf(int32(0)), INT32}, + {reflect.TypeOf(int64(0)), INT64}, + {reflect.TypeOf(uint8(0)), UINT8}, +} + +func TypeOfShape(dtype PaddleDType, shape []int32) reflect.Type { + var ret reflect.Type + for _, t := range types { + if dtype == PaddleDType(t.dtype) { + ret = t.gotype + break + } + } + + if ret == nil { + panic(bug("Data %v type is not support", dtype)) + } + + for range shape { + ret = reflect.SliceOf(ret) + } + return ret +} + +type ZeroCopyTensor struct { + c *C.PD_ZeroCopyTensor + name string + shape []int32 +} + +func NewZeroCopyTensor() *ZeroCopyTensor { + c_tensor := C.PD_NewZeroCopyTensor() + + tensor := &ZeroCopyTensor{c: c_tensor} + runtime.SetFinalizer(tensor, (*ZeroCopyTensor).finalize) + return tensor +} + +func (tensor *ZeroCopyTensor) finalize() { + C.PD_DeleteZeroCopyTensor(tensor.c) +} + +func (tensor *ZeroCopyTensor) Shape() []int32 { + return tensor.shape +} + +func (tensor *ZeroCopyTensor) Name() string { + return C.GoString(tensor.c.name) +} + +func (tensor *ZeroCopyTensor) Rename(name string) { + tensor.name = name + tensor.c.name = (*C.char)(unsafe.Pointer(tensor.c.name)) + //tensor.c.name = C.CString(tensor.name) + //defer C.free(unsafe.Pointer(tensor.c.name)) +} + +func (tensor *ZeroCopyTensor) Reshape(shape []int32) { + tensor.shape = make([]int32, len(shape)) + copy(tensor.shape, shape) + length := C.sizeof_int * C.size_t(len(shape)) + if tensor.c.shape.capacity < C.size_t(length) { + if tensor.c.shape.capacity != C.size_t(0) { + C.free(tensor.c.shape.data) + } + tensor.c.shape.data = C.malloc(length) + tensor.c.shape.capacity = length + } + tensor.c.shape.length = length + C.memcpy(tensor.c.shape.data, unsafe.Pointer(&shape[0]), length) +} + +func (tensor *ZeroCopyTensor) DataType() PaddleDType { + return PaddleDType(tensor.c.dtype) +} + +func (tensor *ZeroCopyTensor) SetValue(value interface{}) { + val := reflect.ValueOf(value) + shape, dtype := ShapeAndTypeOf(val) + num := numel(shape) + length := C.size_t(SizeofDataType(dtype) * num) + if tensor.c.data.capacity < length { + if tensor.c.data.capacity != C.size_t(0) { + C.free(tensor.c.data.data) + } + tensor.c.data.data = C.malloc(length) + tensor.c.data.capacity = length + } + tensor.c.data.length = length + + switch dtype { + case PaddleDType(UINT8): + data := val.Interface().([]uint8) + C.memcpy(tensor.c.data.data, unsafe.Pointer(&data[0]), length) + case PaddleDType(INT32): + data := val.Interface().([]int32) + C.memcpy(tensor.c.data.data, unsafe.Pointer(&data[0]), length) + case PaddleDType(INT64): + data := val.Interface().([]int64) + C.memcpy(tensor.c.data.data, unsafe.Pointer(&data[0]), length) + case PaddleDType(FLOAT32): + data := val.Interface().([]float32) + C.memcpy(tensor.c.data.data, unsafe.Pointer(&data[0]), length) + } + tensor.c.dtype = C.PD_DataType(dtype) +} + +func TypeOf(dtype PaddleDType, shape []int32) reflect.Type { + var ret reflect.Type + for _, t := range types { + if t.dtype == dtype { + ret = t.gotype + break + } + } + + for range shape { + ret = reflect.SliceOf(ret) + } + return ret +} + +func (tensor *ZeroCopyTensor) Value() interface{} { + t := TypeOf(PaddleDType(tensor.c.dtype), tensor.shape) + value := reflect.New(t) + c_bytes := tensor.c.data.data + length := tensor.c.data.length + var slice []byte + if unsafe.Sizeof(unsafe.Pointer(nil)) == 8 { + slice = (*[1<<50 - 1]byte)(unsafe.Pointer(c_bytes))[:length:length] + } else { + slice = (*[1 << 30]byte)(unsafe.Pointer(c_bytes))[:length:length] + } + r := bytes.NewReader(slice) + DecodeTensor(r, tensor.Shape(), t, value) + return reflect.Indirect(value).Interface() +} + +func (tensor *ZeroCopyTensor) Lod() []uint { + var val []uint + valHdr := (*reflect.SliceHeader)(unsafe.Pointer(&val)) + valHdr.Data = uintptr(unsafe.Pointer(tensor.c.lod.data)) + valHdr.Len = int(tensor.c.lod.length / C.sizeof_size_t) + valHdr.Cap = int(tensor.c.lod.length / C.sizeof_size_t) + return val +} + +func Endian() binary.ByteOrder { + buf := [2]byte{} + *(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD) + + var endian binary.ByteOrder + + switch buf { + case [2]byte{0xCD, 0xAB}: + endian = binary.LittleEndian + case [2]byte{0xAB, 0xCD}: + endian = binary.BigEndian + default: + panic("Could not determine native endianness.") + } + return endian +} + +func DecodeTensor(r *bytes.Reader, shape []int32, t reflect.Type, ptr reflect.Value) { + switch t.Kind() { + case reflect.Uint8, reflect.Int32, reflect.Int64, reflect.Float32: + binary.Read(r, Endian(), ptr.Interface()) + case reflect.Slice: + value := reflect.Indirect(ptr) + value.Set(reflect.MakeSlice(t, int(shape[0]), int(shape[0]))) + if len(shape) == 1 && value.Len() > 0 { + switch value.Index(0).Kind() { + case reflect.Uint8, reflect.Int32, reflect.Int64, reflect.Float32: + binary.Read(r, Endian(), value.Interface()) + return + } + } + + for i := 0; i < value.Len(); i++ { + DecodeTensor(r, shape[1:], t.Elem(), value.Index(i).Addr()) + } + } +} + +func SizeofDataType(dtype PaddleDType) int32 { + switch dtype { + case UINT8: + return int32(C.sizeof_uchar) + case INT32: + return int32(C.sizeof_int) + case INT64: + return int32(C.sizeof_longlong) + case FLOAT32: + return int32(C.sizeof_float) + } + return -1 +} + +func ShapeAndTypeOf(val reflect.Value) (shape []int32, dt PaddleDType) { + gotype := val.Type() + for gotype.Kind() == reflect.Array || gotype.Kind() == reflect.Slice { + shape = append(shape, int32(val.Len())) + if val.Len() > 0 { + val = val.Index(0) + } + gotype = gotype.Elem() + } + + for _, t := range types { + if gotype.Kind() == t.gotype.Kind() { + return shape, PaddleDType(t.dtype) + } + } + return shape, dt +} diff --git a/deploy/paddleocr-go/paddle_c/README.md b/deploy/paddleocr-go/paddle_c/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e3565d6fbd54d3d3f39f6b75eac100f666d42f00 --- /dev/null +++ b/deploy/paddleocr-go/paddle_c/README.md @@ -0,0 +1,6 @@ +# Paddle C预测库目录 + +## 编译安装 +使用cmake编译paddle,并打开-DON_INFER=ON,在编译目录下得到paddle_inference_c_install_dir,将该目录下的所有文件复制到本目录下。 + +详细编译步骤请参见[README.md](../README.md) 或者官方文档指导 https://www.paddlepaddle.org.cn/documentation/docs/zh/advanced_guide/inference_deployment/inference/build_and_install_lib_cn.html#id12 \ No newline at end of file