├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── assets ├── 2.pdf ├── 3.pdf ├── 4.pdf ├── demo.gif ├── easyocr.gif ├── example.jpg ├── test.pdf └── 专家意见和签名表.pdf ├── client ├── css │ └── style.css ├── index.html ├── main.js ├── ocr.js ├── renderer.js └── utils.js ├── module ├── auth.js ├── config.js ├── ocr │ ├── AipOcrClient.js │ └── index.js ├── pdf │ ├── index.js │ └── pdf2image.js └── voice │ ├── assets │ ├── 16k-23850.amr │ ├── 16k.pcm │ ├── 16k.wav │ ├── 8k-122.amr │ ├── 8k.pcm │ └── 8k.wav │ ├── client.js │ └── recognize.js ├── package.json ├── pdfinfo.exe ├── software ├── Ghostscript 9.23 for Windows.exe ├── GraphicsMagick-1.3.28-Q16-win64-dll.exe ├── README.md └── pdfinfo.exe └── test ├── ocr.test.js └── pdf.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | assets/*.png 14 | 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # next.js build output 63 | .next 64 | easyocr 65 | assets/*.png 66 | assets/*.jpg 67 | package-lock.json 68 | 69 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "启动程序", 11 | "program": "${workspaceFolder}\\test\\pdf.test.js" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # easyocr 2 | make ocr more easy 3 | 4 | 5 | ## Feature 6 | 7 | - image ocr `node test/ocr.test.js` 8 | - converting scanned PDF's to an image 9 | - support pdf ocr `node test/pdf.test.js` 10 | - support electron desktop packager 11 | 12 | ## TODO List 13 | 14 | - support export into word 15 | 16 | 17 | ## Usage 18 | 19 | ### run source code 20 | 21 | - npm install 22 | - `.\module\config.js` 修改百度App ID、App KEY、SECRET KEY 为自己开发者应用配置 23 | - npm run start 24 | 25 | ### release package 26 | 27 | see [releases](https://github.com/giscafer/easyocr/releases) 28 | 29 | ## Dependencies 30 | 31 | 32 | ### Windows 33 | 34 | **pdf 转 图片 依赖三个软件** 35 | 36 | 软件见 `./software/` 目录下,双击安装 37 | 38 | - GraphicsMagick x64 39 | - Ghostscript 9.23 for Windows x64 40 | - pdfinfo (这个安装完成后,打开cmd输入pdfinfo回车,如果失败,就到在window环境变量path下,添加该软件安装路径即可,添加完成后,cmd执行`pdfinfo`测试是否正常) 41 | 42 | ### Ubuntu 43 | 44 | > sudo apt-get install imagemagick ghostscript poppler-utils 45 | 46 | 47 | ### OSX (Yosemite) 48 | 49 | > brew install imagemagick ghostscript poppler 50 | 51 | ## packager 52 | 53 | > npm run packager 54 | 55 | ## Screenshot 56 | 57 | ![](./assets/demo.gif) 58 | 59 | ![](./assets/easyocr.gif) 60 | 61 | ## Buy me a coffee 62 | 63 | :coffee: [Buy me a coffee](https://github.com/giscafer/buy-me-a-coffee) 64 | 65 | ## License 66 | 67 | Apache License 68 | 69 | --- 70 | 71 | > [giscafer.com](http://giscafer.com)  ·  72 | > GitHub [@giscafer](https://github.com/giscafer)  ·  73 | > Twitter [@nickbinglao](https://twitter.com/nickbinglao)  ·  74 | > Weibo [@Nickbing_Lao](https://weibo.com/laohoubin) 75 | 76 | 77 | -------------------------------------------------------------------------------- /assets/2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giscafer/easyocr/ef753d326070c529b6a5c6affeae71c2aeb41bd3/assets/2.pdf -------------------------------------------------------------------------------- /assets/3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giscafer/easyocr/ef753d326070c529b6a5c6affeae71c2aeb41bd3/assets/3.pdf -------------------------------------------------------------------------------- /assets/4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giscafer/easyocr/ef753d326070c529b6a5c6affeae71c2aeb41bd3/assets/4.pdf -------------------------------------------------------------------------------- /assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giscafer/easyocr/ef753d326070c529b6a5c6affeae71c2aeb41bd3/assets/demo.gif -------------------------------------------------------------------------------- /assets/easyocr.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giscafer/easyocr/ef753d326070c529b6a5c6affeae71c2aeb41bd3/assets/easyocr.gif -------------------------------------------------------------------------------- /assets/example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giscafer/easyocr/ef753d326070c529b6a5c6affeae71c2aeb41bd3/assets/example.jpg -------------------------------------------------------------------------------- /assets/test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giscafer/easyocr/ef753d326070c529b6a5c6affeae71c2aeb41bd3/assets/test.pdf -------------------------------------------------------------------------------- /assets/专家意见和签名表.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giscafer/easyocr/ef753d326070c529b6a5c6affeae71c2aeb41bd3/assets/专家意见和签名表.pdf -------------------------------------------------------------------------------- /client/css/style.css: -------------------------------------------------------------------------------- 1 | svg { 2 | width: 100px; 3 | height: 100px; 4 | margin: 20px; 5 | display: inline-block; 6 | } 7 | 8 | .loading { 9 | position: absolute; 10 | top: 50px; 11 | left: 60%; 12 | } 13 | 14 | .hide { 15 | display: none; 16 | } 17 | 18 | .content { 19 | padding: 20px; 20 | } 21 | 22 | #holder { 23 | height: 200px; 24 | border: 1px dashed #d9d9d9; 25 | margin: auto 100px; 26 | text-align: center; 27 | line-height: 200px; 28 | background: #fafafa; 29 | } 30 | 31 | #holder:hover { 32 | border-color: #40a9ff; 33 | cursor: pointer; 34 | } 35 | 36 | /* 拖拽时用jQuery为其添加边框样式的class */ 37 | 38 | .holder-ondrag { 39 | border: 10px dotted #03A9F4 !important 40 | } 41 | 42 | div.selected { 43 | margin: auto 100px; 44 | } 45 | 46 | div.ocr-content { 47 | position: relative; 48 | min-height: 400px; 49 | margin: 20px 100px; 50 | padding: 20px; 51 | border: 1px solid #d9d9d9; 52 | } 53 | 54 | div.ocr-content h4 { 55 | display: inline-block; 56 | } 57 | 58 | #copyBtn { 59 | position: absolute; 60 | right: 10px; 61 | } 62 | 63 | .btn { 64 | line-height: 32px; 65 | display: inline-block; 66 | font-weight: 400; 67 | text-align: center; 68 | touch-action: manipulation; 69 | cursor: pointer; 70 | background-image: none; 71 | border: 1px solid #d9d9d9; 72 | white-space: nowrap; 73 | padding: 0 15px; 74 | font-size: 14px; 75 | border-radius: 4px; 76 | height: 32px; 77 | -webkit-user-select: none; 78 | -moz-user-select: none; 79 | -ms-user-select: none; 80 | user-select: none; 81 | transition: all .3s cubic-bezier(.645, .045, .355, 1); 82 | position: relative; 83 | color: rgba(0, 0, 0, .65); 84 | background-color: #fff; 85 | } 86 | 87 | .btn:hover { 88 | border-color: #40a9ff; 89 | } -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | EasyOCR | power by giscafer 6 | 7 | 8 | 9 |
10 |
11 | 点击选择或拖拽文件到此区域上传 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |
24 | 25 |
26 |
27 |

OCR结果

28 | 复制文本 29 |
30 | 31 |
32 |
33 |
34 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /client/main.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow } = require('electron'); 2 | 3 | // Keep a global reference of the window object, if you don't, the window will 4 | // be closed automatically when the JavaScript object is garbage collected. 5 | let win 6 | 7 | function createWindow() { 8 | // 创建浏览器窗口。 9 | win = new BrowserWindow({ width: 800, height: 600 }) 10 | 11 | // 然后加载应用的 index.html。 12 | win.loadFile('./client/index.html') 13 | 14 | // 打开开发者工具 15 | win.webContents.openDevTools() 16 | 17 | // 当 window 被关闭,这个事件会被触发。 18 | win.on('closed', () => { 19 | // 取消引用 window 对象,如果你的应用支持多窗口的话, 20 | // 通常会把多个 window 对象存放在一个数组里面, 21 | // 与此同时,你应该删除相应的元素。 22 | win = null 23 | }) 24 | } 25 | 26 | // Electron 会在初始化后并准备 27 | // 创建浏览器窗口时,调用这个函数。 28 | // 部分 API 在 ready 事件触发后才能使用。 29 | app.on('ready', createWindow) 30 | 31 | // 当全部窗口关闭时退出。 32 | app.on('window-all-closed', () => { 33 | // 在 macOS 上,除非用户用 Cmd + Q 确定地退出, 34 | // 否则绝大部分应用及其菜单栏会保持激活。 35 | if (process.platform !== 'darwin') { 36 | app.quit() 37 | } 38 | }) 39 | 40 | app.on('activate', () => { 41 | // 在macOS上,当单击dock图标并且没有其他窗口打开时, 42 | // 通常在应用程序中重新创建一个窗口。 43 | if (win === null) { 44 | createWindow() 45 | } 46 | }) 47 | 48 | // 在这个文件中,你可以续写应用剩下主进程代码。 49 | // 也可以拆分成几个文件,然后用 require 导入。 50 | -------------------------------------------------------------------------------- /client/ocr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: giscafer ,https://github.com/giscafer 3 | * @date: 2018-09-01 18:07:48 4 | * @description: 5 | */ 6 | 7 | 8 | require('es6-promise'); 9 | const fs = require('fs'); 10 | const client = require('../module/ocr/AipOcrClient'); 11 | const pdfModule = require('../module/pdf'); 12 | const orcModule = require('../module/ocr'); 13 | 14 | 15 | const timeout = (promise, ms) => new Promise((resolve, reject) => { 16 | setTimeout(() => { 17 | promise.then(resolve, reject); 18 | }, ms); 19 | }); 20 | 21 | /* PDF OCR */ 22 | const pdfOcr = pdfPath => new Promise((resolve, reject) => { 23 | let wordstr = ""; 24 | (async () => { 25 | let imagePaths = await pdfModule.convertFile(pdfPath, { totalPageSize: 4 }); 26 | const { length } = imagePaths; 27 | let count = -1; 28 | console.log(imagePaths); 29 | while (++count < length) { 30 | try { 31 | // 延时2秒执行原因:免费版百度OCR 接口有qps限制 32 | let result = await timeout(orcModule.execOrcByImgPath(imagePaths[count]), 2000); 33 | if (!result.error_code) { 34 | const { words_result } = result; 35 | for (let row of words_result) { 36 | wordstr += row.words + '\n'; 37 | } 38 | } 39 | } catch (error) { 40 | reject(error); 41 | } 42 | 43 | } 44 | resolve(wordstr); 45 | })(); 46 | }); 47 | 48 | /* 图片OCR */ 49 | const imgOcr = imgPath => { 50 | const image = fs.readFileSync(imgPath).toString("base64"); 51 | return new Promise((resolve, reject) => { 52 | client.accurateBasic(image).then(function (result) { 53 | if (result.words_result) { 54 | return resolve(wordsHandler(result.words_result)); 55 | } 56 | return reject('OCR 失败!'); 57 | }).catch(function (err) { 58 | // 如果发生网络错误 59 | return reject(err); 60 | }); 61 | }) 62 | } 63 | 64 | 65 | function wordsHandler(words) { 66 | let text = ''; 67 | for (let w of words) { 68 | text += w.words + '\n'; 69 | } 70 | 71 | return text; 72 | } 73 | 74 | module.exports = { 75 | pdfOcr, 76 | imgOcr 77 | } -------------------------------------------------------------------------------- /client/renderer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: giscafer ,https://github.com/giscafer 3 | * @date: 2018-09-06 15:53:46 4 | * @description: 拖拽事件处理,对应不同文件进行OCR识别,以及结果展示等逻辑 5 | */ 6 | 7 | const ocr = require('./ocr'); 8 | const _ = require('./utils'); 9 | const eleHodler = document.getElementById("holder"); 10 | const eleSelectedFile = document.getElementById('selected-file'); 11 | const eleCopyBtn = document.getElementById('copyBtn'); 12 | const eleOcrResult = document.getElementById('ocr-result'); 13 | const eleLoading = document.querySelector('.loading'); 14 | 15 | const { dialog } = require('electron').remote 16 | 17 | 18 | /* 禁止默认的drop事件 */ 19 | document.addEventListener('drop', event => { 20 | console.log(event) 21 | event.preventDefault(); 22 | event.stopPropagation(); 23 | }); 24 | 25 | document.addEventListener('dragover', function (e) { 26 | e.preventDefault(); 27 | e.stopPropagation(); 28 | }); 29 | 30 | eleCopyBtn.addEventListener('click', (e) => { 31 | e.preventDefault(); 32 | let flag = _.copy(eleOcrResult.innerText); 33 | if (flag) { 34 | dialog.showMessageBox(null, { 35 | type: 'info', 36 | message: '文本已成功复制到剪切板!', 37 | }); 38 | } 39 | }) 40 | 41 | /* 拖拽进入holder区域 */ 42 | eleHodler.ondragenter = eleHodler.ondragover = event => { 43 | // 重写ondragover 和 ondragenter 使其可放置 44 | event.preventDefault(); 45 | eleHodler.classList.add("holder-ondrag"); 46 | }; 47 | 48 | /* 拖拽离开holder区域 */ 49 | eleHodler.ondragleave = event => { 50 | event.preventDefault(); 51 | eleHodler.classList.remove("holder-ondrag"); 52 | eleSelectedFile.innerText = ''; 53 | }; 54 | 55 | /* 拖拽释放 */ 56 | eleHodler.ondrop = event => { 57 | event.preventDefault(); 58 | event.stopPropagation(); 59 | eleLoading.classList.remove('hide'); 60 | eleHodler.classList.remove("holder-ondrag"); 61 | let f = event.dataTransfer.files[0]; 62 | console.log('File(s) you dragged here: ', f.path); 63 | eleSelectedFile.innerText = f.path; 64 | // OCR 65 | fileHandler(f.path); 66 | } 67 | 68 | 69 | function fileHandler(path) { 70 | 71 | if (_.isImage(path)) { 72 | ocr.imgOcr(path).then(result => { 73 | eleOcrResult.innerText = result; 74 | eleLoading.classList.add('hide'); 75 | }).catch(e => { 76 | console.log(e); 77 | dialog.showMessageBox(null, { 78 | type: 'error', 79 | message: '图片OCR转换失败', 80 | }); 81 | }); 82 | return; 83 | } 84 | 85 | if (_.isPdf(path)) { 86 | // PDF 87 | ocr.pdfOcr(path).then(result => { 88 | eleOcrResult.innerText = result; 89 | eleLoading.classList.add('hide'); 90 | }).catch(e => { 91 | console.log(e) 92 | dialog.showMessageBox(null, { 93 | type: 'error', 94 | message: 'PDF 提取文字失败', 95 | }); 96 | }); 97 | return; 98 | } 99 | 100 | return dialog.showMessageBox(null, { 101 | type: 'error', 102 | message: 'OCR只支持 图片 和 PDF 两种文件类型!', 103 | }, () => { 104 | eleSelectedFile.innerText = ''; 105 | eleLoading.classList.add('hide'); 106 | }); 107 | 108 | } 109 | 110 | 111 | -------------------------------------------------------------------------------- /client/utils.js: -------------------------------------------------------------------------------- 1 | function isImage(imgUrl) { 2 | let imageFormat = ['.png', '.jpg', '.jpeg', '.gif', '.bmp']; 3 | imgUrl = imgUrl.toLowerCase(); 4 | return imageFormat.some(ft => { 5 | return imgUrl.endsWith(ft); 6 | }); 7 | } 8 | 9 | function isPdf(url) { 10 | let format = ['.pdf']; 11 | url = url.toLowerCase(); 12 | return format.some(ft => { 13 | return url.endsWith(ft); 14 | }); 15 | } 16 | 17 | // https://github.com/sindresorhus/copy-text-to-clipboard/blob/master/index.js 18 | /* tslint:disable */ 19 | function copy(input) { 20 | const el = document.createElement('textarea'); 21 | 22 | el.value = input; 23 | 24 | // Prevent keyboard from showing on mobile 25 | el.setAttribute('readonly', ''); 26 | 27 | el.style['contain'] = 'strict'; 28 | el.style.position = 'absolute'; 29 | el.style.left = '-9999px'; 30 | el.style.fontSize = '12pt'; // Prevent zooming on iOS 31 | 32 | const selection = getSelection(); 33 | let originalRange = false; 34 | if (selection.rangeCount > 0) { 35 | originalRange = selection.getRangeAt(0); 36 | } 37 | 38 | document.body.appendChild(el); 39 | el.select(); 40 | 41 | // Explicit selection workaround for iOS 42 | el.selectionStart = 0; 43 | el.selectionEnd = input.length; 44 | 45 | let success = false; 46 | try { 47 | success = document.execCommand('copy'); 48 | } catch (err) { } 49 | 50 | document.body.removeChild(el); 51 | 52 | if (originalRange) { 53 | selection.removeAllRanges(); 54 | selection.addRange(originalRange); 55 | } 56 | 57 | return success; 58 | } 59 | 60 | 61 | module.exports = { 62 | copy, isImage, isPdf 63 | } -------------------------------------------------------------------------------- /module/auth.js: -------------------------------------------------------------------------------- 1 | var https = require('https'); 2 | var qs = require('querystring'); 3 | var config = require('./config'); 4 | 5 | const param = qs.stringify({ 6 | 'grant_type': 'client_credentials', 7 | 'client_id': config.API_KEY, 8 | 'client_secret': config.SECRET_KEY 9 | }); 10 | 11 | https.get( 12 | { 13 | hostname: 'aip.baidubce.com', 14 | path: '/oauth/2.0/token?' + param, 15 | agent: false 16 | }, 17 | function (res) { 18 | // 在标准输出中查看运行结果 19 | res.pipe(process.stdout); 20 | } 21 | ); -------------------------------------------------------------------------------- /module/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 修改百度App ID、App KEY、SECRET KEY 为自己开发者应用配置 3 | * 以下配置为个人开发测试配置,可能会失效 4 | */ 5 | 6 | module.exports = { 7 | APP_ID: '10363783', // 你的 App ID 8 | API_KEY: 'VGD6eTCnbPYDRtX5YXYGweF7', // 你的 App KEY 9 | SECRET_KEY: 'DVFw4sANENzkBGTBaWLkm0L3qPXuqvG2', // 你的 SECRET KEY 10 | } -------------------------------------------------------------------------------- /module/ocr/AipOcrClient.js: -------------------------------------------------------------------------------- 1 | const AipOcrClient = require("baidu-aip-sdk").ocr; 2 | const config = require('../config'); 3 | // 设置APPID/AK/SK 4 | 5 | const { APP_ID, API_KEY, SECRET_KEY } = config; 6 | 7 | // 新建一个对象,建议只保存一个对象调用服务接口 8 | const client = new AipOcrClient(APP_ID, API_KEY, SECRET_KEY); 9 | const HttpClient = require("baidu-aip-sdk").HttpClient; 10 | 11 | // 设置request库的一些参数,例如代理服务地址,超时时间等 12 | // request参数请参考 https://github.com/request/request#requestoptions-callback 13 | HttpClient.setRequestOptions({ timeout: 5000 }); 14 | 15 | // 也可以设置拦截每次请求(设置拦截后,调用的setRequestOptions设置的参数将不生效), 16 | // 可以按需修改request参数(无论是否修改,必须返回函数调用参数) 17 | // request参数请参考 https://github.com/request/request#requestoptions-callback 18 | HttpClient.setRequestInterceptor(function (requestOptions) { 19 | // 查看参数 20 | // console.log(requestOptions) 21 | // 修改参数 22 | requestOptions.timeout = 15000; 23 | // 返回参数 24 | return requestOptions; 25 | }); 26 | 27 | module.exports = client; -------------------------------------------------------------------------------- /module/ocr/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: giscafer ,https://github.com/giscafer 3 | * @date: 2018-09-03 20:13:23 4 | * @description: 5 | */ 6 | 7 | 8 | const fs = require('fs'); 9 | const path = require('path'); 10 | const client = require('./AipOcrClient'); 11 | 12 | // 调用通用文字识别(高精度版) 13 | function execOrc(image) { 14 | return new Promise((resolve, reject) => { 15 | client.accurateBasic(image).then(result => { 16 | return resolve(result); 17 | }).catch(err => { 18 | // 如果发生网络错误 19 | return reject(err); 20 | }); 21 | 22 | }); 23 | } 24 | 25 | function execOrcByImgPath(imgPath) { 26 | const image = fs.readFileSync(imgPath).toString("base64"); 27 | return new Promise((resolve, reject) => { 28 | client.accurateBasic(image).then(result => { 29 | return resolve(result); 30 | }).catch(err => { 31 | // 如果发生网络错误 32 | return reject(err); 33 | }); 34 | 35 | }); 36 | } 37 | 38 | module.exports = { 39 | execOrc, 40 | execOrcByImgPath 41 | } 42 | -------------------------------------------------------------------------------- /module/pdf/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: giscafer ,https://github.com/giscafer 3 | * @date: 2018-09-01 20:39:59 4 | * @description: 5 | */ 6 | 7 | const PDFImage = require("./pdf2image").PDFImage; 8 | const path = require('path'); 9 | 10 | 11 | /* pdf to images */ 12 | function convertFile(pdfPath, opts) { 13 | if (!opts) opts = {}; 14 | const pdfImage = new PDFImage(pdfPath, { 15 | graphicsMagick: true, 16 | convertOptions: { 17 | "-quality": "100" 18 | }, 19 | ...opts 20 | }); 21 | console.log(pdfImage) 22 | return new Promise((resolve, reject) => { 23 | pdfImage.convertFile().then((imagePaths) => { 24 | return resolve(imagePaths); 25 | }).catch(err => { 26 | return reject(err); 27 | }); 28 | }); 29 | } 30 | 31 | 32 | module.exports = { 33 | convertFile 34 | } -------------------------------------------------------------------------------- /module/pdf/pdf2image.js: -------------------------------------------------------------------------------- 1 | // https://github.com/mooz/node-pdf-image 2 | // fork for modified support windows 3 | /** 4 | * @author: giscafer ,https://github.com/giscafer 5 | * @date: 2018-09-01 15:27:27 6 | * @description: 7 | */ 8 | 9 | var Promise = require("es6-promise").Promise; 10 | 11 | var path = require("path"); 12 | var fs = require("fs"); 13 | var util = require("util"); 14 | var exec = require("child_process").exec; 15 | 16 | function PDFImage(pdfFilePath, options) { 17 | if (!options) options = {}; 18 | this.options = options; 19 | this.pdfFilePath = pdfFilePath; 20 | 21 | this.setPdfFileBaseName(options.pdfFileBaseName); 22 | this.setConvertOptions(options.convertOptions); 23 | this.setConvertExtension(options.convertExtension); 24 | this.useGM = options.graphicsMagick || false; 25 | this.combinedImage = options.combinedImage || false; 26 | 27 | this.outputDirectory = options.outputDirectory || path.dirname(pdfFilePath); 28 | } 29 | 30 | PDFImage.prototype = { 31 | constructGetInfoCommand: function () { 32 | return util.format( 33 | "pdfinfo \"%s\"", 34 | this.pdfFilePath 35 | ); 36 | }, 37 | parseGetInfoCommandOutput: function (output) { 38 | var info = {}; 39 | output.split("\r\n").forEach(function (line) { 40 | if (line.match(/^(.*?):[ \t]*(.*)$/)) { 41 | info[RegExp.$1] = RegExp.$2; 42 | } 43 | }); 44 | return info; 45 | }, 46 | getInfo: function () { 47 | var self = this; 48 | var getInfoCommand = this.constructGetInfoCommand(); 49 | var promise = new Promise(function (resolve, reject) { 50 | exec(getInfoCommand, function (err, stdout, stderr) { 51 | if (err) { 52 | return reject({ 53 | message: "Failed to get PDF'S information", 54 | error: err, 55 | stdout: stdout, 56 | stderr: stderr 57 | }); 58 | } 59 | console.log(stdout) 60 | return resolve(self.parseGetInfoCommandOutput(stdout)); 61 | }); 62 | }); 63 | return promise; 64 | }, 65 | numberOfPages: function () { 66 | return this.getInfo().then(info => { 67 | if (info['Pages']) { 68 | return info["Pages"] 69 | } 70 | this.options.pdfinfoFailed = true; 71 | return this.options.totalPageSize || 10; 72 | }); 73 | }, 74 | getOutputImagePathForPage: function (pageNumber) { 75 | return path.join( 76 | this.outputDirectory, 77 | this.pdfFileBaseName + "-" + pageNumber + "." + this.convertExtension 78 | ); 79 | }, 80 | getOutputImagePathForFile: function () { 81 | return path.join( 82 | this.outputDirectory, 83 | this.pdfFileBaseName + "." + this.convertExtension 84 | ); 85 | }, 86 | setConvertOptions: function (convertOptions) { 87 | this.convertOptions = convertOptions || {}; 88 | }, 89 | setPdfFileBaseName: function (pdfFileBaseName) { 90 | this.pdfFileBaseName = pdfFileBaseName || path.basename(this.pdfFilePath, ".pdf"); 91 | }, 92 | setConvertExtension: function (convertExtension) { 93 | this.convertExtension = convertExtension || "png"; 94 | }, 95 | constructConvertCommandForPage: function (pageNumber) { 96 | var pdfFilePath = this.pdfFilePath; 97 | var outputImagePath = this.getOutputImagePathForPage(pageNumber); 98 | var convertOptionsString = this.constructConvertOptions(); 99 | return util.format( 100 | "%s %s\"%s[%d]\" \"%s\"", 101 | this.useGM ? "gm convert" : "convert", 102 | convertOptionsString ? convertOptionsString + " " : "", 103 | pdfFilePath, pageNumber, outputImagePath 104 | ); 105 | }, 106 | constructCombineCommandForFile: function (imagePaths) { 107 | return util.format( 108 | "%s -append %s \"%s\"", 109 | this.useGM ? "gm convert" : "convert", 110 | imagePaths.join(' '), 111 | this.getOutputImagePathForFile() 112 | ); 113 | }, 114 | constructConvertOptions: function () { 115 | return Object.keys(this.convertOptions).sort().map(function (optionName) { 116 | if (this.convertOptions[optionName] !== null) { 117 | return optionName + " " + this.convertOptions[optionName]; 118 | } else { 119 | return optionName; 120 | } 121 | }, this).join(" "); 122 | }, 123 | combineImages: function (imagePaths) { 124 | var pdfImage = this; 125 | var combineCommand = pdfImage.constructCombineCommandForFile(imagePaths); 126 | return new Promise(function (resolve, reject) { 127 | exec(combineCommand, function (err, stdout, stderr) { 128 | if (err) { 129 | return reject({ 130 | message: "Failed to combine images", 131 | error: err, 132 | stdout: stdout, 133 | stderr: stderr 134 | }); 135 | } 136 | exec("rm " + imagePaths.join(' ')); //cleanUp 137 | return resolve(pdfImage.getOutputImagePathForFile()); 138 | }); 139 | }); 140 | }, 141 | convertFile: function () { 142 | var pdfImage = this; 143 | return new Promise(function (resolve, reject) { 144 | pdfImage.numberOfPages().then(totalPages => { 145 | var convertPromise = new Promise(function (resolve, reject) { 146 | var imagePaths = []; 147 | for (var i = 0; i < totalPages; i++) { 148 | pdfImage.convertPage(i).then(function (imagePath) { 149 | imagePaths.push(imagePath); 150 | if (imagePaths.length === parseInt(totalPages)) { 151 | imagePaths.sort(); //because of asyc pages we have to reSort pages 152 | resolve(imagePaths); 153 | } 154 | }).catch(error => { 155 | if (error.stdout.indexOf('No pages will be processed') !== -1) { 156 | console.log(error.stdout); 157 | resolve(imagePaths); 158 | } else { 159 | reject(error); 160 | } 161 | }); 162 | } 163 | }); 164 | 165 | convertPromise.then(imagePaths => { 166 | if (pdfImage.combinedImage) { 167 | pdfImage.combineImages(imagePaths).then(imagePath => { 168 | resolve(imagePath); 169 | }); 170 | } else { 171 | resolve(imagePaths); 172 | } 173 | }).catch(error => { 174 | // if (this.options.pdfinfoFailed) { 175 | // resolve(imagePaths); 176 | // } else { 177 | // reject(error); 178 | // } 179 | reject(error); 180 | }); 181 | }); 182 | }); 183 | }, 184 | convertPage: function (pageNumber) { 185 | var pdfFilePath = this.pdfFilePath; 186 | var outputImagePath = this.getOutputImagePathForPage(pageNumber); 187 | var convertCommand = this.constructConvertCommandForPage(pageNumber); 188 | 189 | var promise = new Promise(function (resolve, reject) { 190 | function convertPageToImage() { 191 | exec(convertCommand, function (err, stdout, stderr) { 192 | if (err) { 193 | return reject({ 194 | message: "Failed to convert page to image", 195 | error: err, 196 | stdout: stdout, 197 | stderr: stderr 198 | }); 199 | } 200 | return resolve(outputImagePath); 201 | }); 202 | } 203 | 204 | fs.stat(outputImagePath, function (err, imageFileStat) { 205 | var imageNotExists = err && err.code === "ENOENT"; 206 | if (!imageNotExists && err) { 207 | return reject({ 208 | message: "Failed to stat image file", 209 | error: err 210 | }); 211 | } 212 | 213 | // convert when (1) image doesn't exits or (2) image exists 214 | // but its timestamp is older than pdf's one 215 | 216 | if (imageNotExists) { 217 | // (1) 218 | convertPageToImage(); 219 | return; 220 | } 221 | 222 | // image exist. check timestamp. 223 | fs.stat(pdfFilePath, function (err, pdfFileStat) { 224 | if (err) { 225 | return reject({ 226 | message: "Failed to stat PDF file", 227 | error: err 228 | }); 229 | } 230 | 231 | if (imageFileStat.mtime < pdfFileStat.mtime) { 232 | // (2) 233 | convertPageToImage(); 234 | return; 235 | } 236 | 237 | return resolve(outputImagePath); 238 | }); 239 | }); 240 | }); 241 | return promise; 242 | } 243 | }; 244 | 245 | exports.PDFImage = PDFImage; 246 | -------------------------------------------------------------------------------- /module/voice/assets/16k-23850.amr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giscafer/easyocr/ef753d326070c529b6a5c6affeae71c2aeb41bd3/module/voice/assets/16k-23850.amr -------------------------------------------------------------------------------- /module/voice/assets/16k.pcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giscafer/easyocr/ef753d326070c529b6a5c6affeae71c2aeb41bd3/module/voice/assets/16k.pcm -------------------------------------------------------------------------------- /module/voice/assets/16k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giscafer/easyocr/ef753d326070c529b6a5c6affeae71c2aeb41bd3/module/voice/assets/16k.wav -------------------------------------------------------------------------------- /module/voice/assets/8k-122.amr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giscafer/easyocr/ef753d326070c529b6a5c6affeae71c2aeb41bd3/module/voice/assets/8k-122.amr -------------------------------------------------------------------------------- /module/voice/assets/8k.pcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giscafer/easyocr/ef753d326070c529b6a5c6affeae71c2aeb41bd3/module/voice/assets/8k.pcm -------------------------------------------------------------------------------- /module/voice/assets/8k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giscafer/easyocr/ef753d326070c529b6a5c6affeae71c2aeb41bd3/module/voice/assets/8k.wav -------------------------------------------------------------------------------- /module/voice/client.js: -------------------------------------------------------------------------------- 1 | const AipOcrClient = require("baidu-aip-sdk").speech; 2 | 3 | const config = { 4 | APP_ID: '14777916', // 你的 App ID 5 | API_KEY: 'oPLOSsOKNhuErmUyXrxAXy2X', // 你的 App KEY 6 | SECRET_KEY: 'rHAtSsllVhr9HsWQMqcu1OQtPornGwBn', // 你的 SECRET KEY 7 | } 8 | // 设置APPID/AK/SK 9 | 10 | const { APP_ID, API_KEY, SECRET_KEY } = config; 11 | 12 | // 新建一个对象,建议只保存一个对象调用服务接口 13 | const client = new AipOcrClient(APP_ID, API_KEY, SECRET_KEY); 14 | const HttpClient = require("baidu-aip-sdk").HttpClient; 15 | 16 | // 设置request库的一些参数,例如代理服务地址,超时时间等 17 | // request参数请参考 https://github.com/request/request#requestoptions-callback 18 | HttpClient.setRequestOptions({ timeout: 5000 }); 19 | 20 | // 也可以设置拦截每次请求(设置拦截后,调用的setRequestOptions设置的参数将不生效), 21 | // 可以按需修改request参数(无论是否修改,必须返回函数调用参数) 22 | // request参数请参考 https://github.com/request/request#requestoptions-callback 23 | HttpClient.setRequestInterceptor(function (requestOptions) { 24 | // 查看参数 25 | // console.log(requestOptions) 26 | // 修改参数 27 | requestOptions.timeout = 5000; 28 | // 返回参数 29 | return requestOptions; 30 | }); 31 | 32 | module.exports = client; -------------------------------------------------------------------------------- /module/voice/recognize.js: -------------------------------------------------------------------------------- 1 | let fs = require('fs'); 2 | const client = require('./client'); 3 | 4 | let voice = fs.readFileSync('./module/voice/assets/16k.wav'); 5 | 6 | let voiceBuffer = new Buffer(voice); 7 | 8 | // 识别本地文件 9 | client.recognize(voiceBuffer, 'wav', 16000).then(function (result) { 10 | console.log(': ' + JSON.stringify(result)); 11 | }, function (err) { 12 | console.log(err); 13 | }); 14 | 15 | // 识别本地文件,附带参数 16 | /* client.recognize(voiceBuffer, 'pcm', 16000, { dev_pid: '1536', cuid: Math.random() }).then(function (result) { 17 | console.log(': ' + JSON.stringify(result)); 18 | }, function (err) { 19 | console.log(err); 20 | }); */ -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "easyocr", 3 | "version": "1.0.0", 4 | "description": "A tool base on baidu-aip", 5 | "main": "client/main.js", 6 | "scripts": { 7 | "start": "concurrently \"npm run electron\"", 8 | "electron": "electron .", 9 | "packager": "electron-packager . easyocr --win --out easyocr --arch=x64 --app-version 1.0.0 --overwrite", 10 | "packager:32": "electron-packager . easyocr --win --out easyocr --arch=ia32 --app-version 1.0.0 --overwrite", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/giscafer/easyocr.git" 16 | }, 17 | "author": "", 18 | "license": "Apache License", 19 | "bugs": { 20 | "url": "https://github.com/giscafer/easyocr/issues" 21 | }, 22 | "homepage": "https://github.com/giscafer/easyocr#readme", 23 | "dependencies": { 24 | "baidu-aip-sdk": "^2.3.3", 25 | "es6-promise": "^4.2.5", 26 | "express": "^4.16.3", 27 | "gs": "0.0.2" 28 | }, 29 | "devDependencies": { 30 | "concurrently": "^3.6.1", 31 | "electron": "^2.0.8", 32 | "node-waf": "^1.0.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pdfinfo.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giscafer/easyocr/ef753d326070c529b6a5c6affeae71c2aeb41bd3/pdfinfo.exe -------------------------------------------------------------------------------- /software/Ghostscript 9.23 for Windows.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giscafer/easyocr/ef753d326070c529b6a5c6affeae71c2aeb41bd3/software/Ghostscript 9.23 for Windows.exe -------------------------------------------------------------------------------- /software/GraphicsMagick-1.3.28-Q16-win64-dll.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giscafer/easyocr/ef753d326070c529b6a5c6affeae71c2aeb41bd3/software/GraphicsMagick-1.3.28-Q16-win64-dll.exe -------------------------------------------------------------------------------- /software/README.md: -------------------------------------------------------------------------------- 1 | ## pdf 转图片依赖三个软件 2 | 3 | 软件见 `./software/` 目录下,双击安装 4 | 5 | - GraphicsMagick 6 | - Ghostscript 9.23 for Windows 7 | - pdfinfo (这个安装完成后,打开cmd输入pdfinfo回车,如果失败,就到在window环境变量path下,添加该软件安装路径即可,添加完成后,cmd执行`pdfinfo`测试是否正常) 8 | 9 | 非windows系统详细说明见[node-pdf-image](https://github.com/mooz/node-pdf-image) 10 | 11 | -------------------------------------------------------------------------------- /software/pdfinfo.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giscafer/easyocr/ef753d326070c529b6a5c6affeae71c2aeb41bd3/software/pdfinfo.exe -------------------------------------------------------------------------------- /test/ocr.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const client = require('../module/ocr/AipOcrClient'); 4 | 5 | const imgPath = path.join(__dirname, '../assets/'); 6 | const image = fs.readFileSync(imgPath + "example.jpg").toString("base64"); 7 | 8 | // 调用通用文字识别(高精度版) 9 | client.accurateBasic(image).then(function (result) { 10 | console.log(JSON.stringify(result)); 11 | }).catch(function (err) { 12 | // 如果发生网络错误 13 | console.log(err); 14 | }); 15 | 16 | // 如果有可选参数 17 | let options = {}; 18 | options["detect_direction"] = "true"; 19 | options["probability"] = "true"; 20 | 21 | // 带参数调用通用文字识别(高精度版) 22 | // client.accurateBasic(image, options).then(function (result) { 23 | // console.log(JSON.stringify(result)); 24 | // }).catch(function (err) { 25 | // // 如果发生网络错误 26 | // console.log(err); 27 | // });; -------------------------------------------------------------------------------- /test/pdf.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: giscafer ,https://github.com/giscafer 3 | * @date: 2018-09-01 18:07:48 4 | * @description: 5 | */ 6 | 7 | 8 | require('es6-promise'); 9 | const pdfModule = require('../module/pdf'); 10 | const orcModule = require('../module/ocr'); 11 | 12 | 13 | const path = require('path'); 14 | const pdfPath = path.join(__dirname, '../assets/专家意见和签名表.pdf'); 15 | 16 | 17 | const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); 18 | 19 | const timeout = (promise, ms) => new Promise((resolve, reject) => { 20 | promise.then(resolve, reject); 21 | 22 | (async () => { 23 | await delay(ms); 24 | reject(new Error('delay error')); 25 | })(); 26 | }); 27 | 28 | const test = () => new Promise((resolve, reject) => { 29 | let wordstr = ""; 30 | (async () => { 31 | let imagePaths = await pdfModule.convertFile(pdfPath, { totalPageSize: 4 }); 32 | const { length } = imagePaths; 33 | let count = -1; 34 | while (++count < length) { 35 | // 延时2秒执行,免费版百度OCR接口有qps限制 36 | console.log(imagePaths[count]) 37 | let result = await timeout(orcModule.execOrcByImgPath(imagePaths[count]), 2000); 38 | if (!result.error_code) { 39 | const { words_result } = result; 40 | for (let row of words_result) { 41 | wordstr += row.words + '\n'; 42 | } 43 | } 44 | } 45 | resolve(wordstr); 46 | })(); 47 | }); 48 | test().then(result => console.log(result)); 49 | --------------------------------------------------------------------------------