download-ppocrv5.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. // scripts/download-ppocrv5.js
  2. import fs from 'fs-extra';
  3. import path from 'path';
  4. import { fileURLToPath } from 'url';
  5. import { createRequire } from 'module';
  6. const __dirname = path.dirname(fileURLToPath(import.meta.url));
  7. const require = createRequire(import.meta.url);
  8. class PPOCRv5Downloader {
  9. constructor() {
  10. this.modelDir = path.join(process.cwd(), 'models', 'ppocrv5');
  11. this.tempDir = path.join(process.cwd(), 'temp', 'downloads');
  12. // PP-OCRv5 官方模型下载链接
  13. this.modelUrls = {
  14. detection: {
  15. url: 'https://paddleocr.bj.bcebos.com/PP-OCRv5/chinese/ch_PP-OCRv5_det_infer.onnx',
  16. filename: 'ch_PP-OCRv5_det_infer.onnx'
  17. },
  18. recognition: {
  19. url: 'https://paddleocr.bj.bcebos.com/PP-OCRv5/chinese/ch_PP-OCRv5_rec_infer.onnx',
  20. filename: 'ch_PP-OCRv5_rec_infer.onnx'
  21. },
  22. classification: {
  23. url: 'https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.onnx',
  24. filename: 'ch_ppocr_mobile_v2.0_cls_infer.onnx'
  25. },
  26. keys: {
  27. url: 'https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/release/2.7/ppocr/utils/ppocr_keys_v1.txt',
  28. filename: 'ppocr_keys_v1.txt'
  29. }
  30. };
  31. }
  32. async downloadModels() {
  33. console.log('🚀 开始下载 PP-OCRv5 模型...');
  34. console.log('📝 PP-OCRv5 特性:');
  35. console.log(' - 更高的文本检测准确率');
  36. console.log(' - 更好的小文本识别能力');
  37. console.log(' - 优化的模型结构');
  38. console.log(' - 完全离线运行\n');
  39. try {
  40. // 创建目录结构
  41. await this.createDirectories();
  42. let successCount = 0;
  43. const totalCount = Object.keys(this.modelUrls).length;
  44. // 并行下载所有模型
  45. const downloadPromises = Object.entries(this.modelUrls).map(async ([type, info]) => {
  46. try {
  47. await this.downloadFile(type, info);
  48. successCount++;
  49. console.log(` ✅ ${this.getTypeName(type)} 下载完成 (${successCount}/${totalCount})`);
  50. } catch (error) {
  51. console.log(` ❌ ${this.getTypeName(type)} 下载失败: ${error.message}`);
  52. throw error;
  53. }
  54. });
  55. await Promise.all(downloadPromises);
  56. console.log('\n🎉 所有模型下载完成!');
  57. this.displayModelInfo();
  58. } catch (error) {
  59. console.error('\n❌ 下载过程中出现错误:', error.message);
  60. await this.provideAlternativeSources();
  61. }
  62. }
  63. async createDirectories() {
  64. const dirs = [
  65. this.modelDir,
  66. path.join(this.modelDir, 'det'),
  67. path.join(this.modelDir, 'rec'),
  68. path.join(this.modelDir, 'cls'),
  69. path.join(this.modelDir, 'keys'),
  70. this.tempDir
  71. ];
  72. for (const dir of dirs) {
  73. await fs.ensureDir(dir);
  74. }
  75. console.log('📁 目录结构创建完成');
  76. }
  77. async downloadFile(type, info) {
  78. const targetPath = this.getTargetPath(type, info.filename);
  79. // 检查文件是否已存在
  80. if (await fs.pathExists(targetPath)) {
  81. const stats = await fs.stat(targetPath);
  82. if (stats.size > this.getMinFileSize(type)) {
  83. console.log(` ⏭️ ${this.getTypeName(type)} 已存在,跳过下载`);
  84. return;
  85. }
  86. }
  87. console.log(` 📥 下载 ${this.getTypeName(type)}...`);
  88. const fetch = await import('node-fetch');
  89. const response = await fetch.default(info.url);
  90. if (!response.ok) {
  91. throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  92. }
  93. const buffer = await response.buffer();
  94. // 验证文件大小
  95. if (buffer.length < this.getMinFileSize(type)) {
  96. throw new Error(`文件大小异常: ${(buffer.length / 1024 / 1024).toFixed(2)} MB`);
  97. }
  98. await fs.writeFile(targetPath, buffer);
  99. // 验证文件完整性
  100. await this.validateFile(type, targetPath);
  101. }
  102. getTargetPath(type, filename) {
  103. const dirs = {
  104. detection: path.join(this.modelDir, 'det'),
  105. recognition: path.join(this.modelDir, 'rec'),
  106. classification: path.join(this.modelDir, 'cls'),
  107. keys: path.join(this.modelDir, 'keys')
  108. };
  109. return path.join(dirs[type], filename);
  110. }
  111. getTypeName(type) {
  112. const names = {
  113. detection: '检测模型 (PP-OCRv5 Det)',
  114. recognition: '识别模型 (PP-OCRv5 Rec)',
  115. classification: '分类模型 (Cls)',
  116. keys: '字符集文件'
  117. };
  118. return names[type];
  119. }
  120. getMinFileSize(type) {
  121. const sizes = {
  122. detection: 2000000, // 2MB
  123. recognition: 8000000, // 8MB
  124. classification: 1000000, // 1MB
  125. keys: 50000 // 50KB
  126. };
  127. return sizes[type];
  128. }
  129. async validateFile(type, filePath) {
  130. const stats = await fs.stat(filePath);
  131. if (type === 'keys') {
  132. const content = await fs.readFile(filePath, 'utf8');
  133. const lines = content.split('\n').filter(line => line.trim());
  134. if (lines.length < 5000) {
  135. throw new Error('字符集文件不完整');
  136. }
  137. }
  138. console.log(` 📊 文件大小: ${(stats.size / 1024 / 1024).toFixed(2)} MB`);
  139. }
  140. displayModelInfo() {
  141. console.log('\n📂 模型文件位置:');
  142. console.log(` 🎯 检测模型: ${path.join(this.modelDir, 'det', 'ch_PP-OCRv5_det_infer.onnx')}`);
  143. console.log(` 🔤 识别模型: ${path.join(this.modelDir, 'rec', 'ch_PP-OCRv5_rec_infer.onnx')}`);
  144. console.log(` 🧭 分类模型: ${path.join(this.modelDir, 'cls', 'ch_ppocr_mobile_v2.0_cls_infer.onnx')}`);
  145. console.log(` 📝 字符集: ${path.join(this.modelDir, 'keys', 'ppocr_keys_v1.txt')}`);
  146. console.log('\n🚀 使用命令:');
  147. console.log(' yarn dev # 启动应用');
  148. }
  149. async provideAlternativeSources() {
  150. console.log('\n💡 备用下载方案:');
  151. console.log(' 1. 手动下载 PP-OCRv5 模型:');
  152. console.log(' - 检测模型: https://paddleocr.bj.bcebos.com/PP-OCRv5/chinese/ch_PP-OCRv5_det_infer.onnx');
  153. console.log(' - 识别模型: https://paddleocr.bj.bcebos.com/PP-OCRv5/chinese/ch_PP-OCRv5_rec_infer.onnx');
  154. console.log(' - 分类模型: https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.onnx');
  155. console.log(' - 字符集: https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/release/2.7/ppocr/utils/ppocr_keys_v1.txt');
  156. console.log('\n 2. 将文件放置到以下目录:');
  157. console.log(` ${this.modelDir}/`);
  158. console.log(' ├── det/ch_PP-OCRv5_det_infer.onnx');
  159. console.log(' ├── rec/ch_PP-OCRv5_rec_infer.onnx');
  160. console.log(' ├── cls/ch_ppocr_mobile_v2.0_cls_infer.onnx');
  161. console.log(' └── keys/ppocr_keys_v1.txt');
  162. }
  163. }
  164. // 执行下载
  165. const downloader = new PPOCRv5Downloader();
  166. downloader.downloadModels().catch(console.error);