ocr.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. /**
  2. * fun 标签:ocr(OnnxOCR 识别)
  3. * 1)image 为图片路径时:对图片做 OCR,识别全文写入变量。
  4. * 2)image 为要查找的文字时:对设备截图做 OCR,在图中查找该文字,返回中心点坐标写入变量(需有设备)。
  5. */
  6. const path = require('path')
  7. const fs = require('fs')
  8. const os = require('os')
  9. const { spawnSync } = require('child_process')
  10. const { captureScreenshot } = require('../../../adb/adb-screencap.js')
  11. const configPath = process.env.STATIC_ROOT
  12. ? path.join(path.dirname(process.env.STATIC_ROOT), 'configs', 'config.js')
  13. : path.join(__dirname, '..', '..', '..', '..', 'configs', 'config.js')
  14. const projectRoot = path.dirname(path.dirname(path.resolve(configPath)))
  15. const config = fs.existsSync(configPath) ? require(configPath) : {}
  16. const ocrScriptPath = path.join(projectRoot, 'python', 'scripts', 'ocr-onnx.py')
  17. const tagName = 'ocr'
  18. const schema = {
  19. description: 'OCR:传入图片路径则识别全文;传入要查找的文字则在设备截图中定位该文字并返回中心点坐标。',
  20. inputs: { image: '图片路径 或 要查找的文字', variable: '输出变量名(保存识别文本或中心点 {"x", "y"})' },
  21. outputs: { variable: '识别文本 或 中心点 JSON' },
  22. }
  23. function getPythonPath() {
  24. const base = config.pythonPath?.path || config.pythonVenvPath || path.join(projectRoot, 'python', process.arch === 'arm64' ? 'arm64' : 'x64')
  25. const envPy = path.join(base, 'env', 'Scripts', 'python.exe')
  26. const scriptsPy = path.join(base, 'Scripts', 'python.exe')
  27. const pyEmbedded = path.join(base, 'py', 'python.exe')
  28. if (fs.existsSync(envPy)) return envPy
  29. if (fs.existsSync(scriptsPy)) return scriptsPy
  30. if (fs.existsSync(pyEmbedded)) return pyEmbedded
  31. return 'python'
  32. }
  33. /**
  34. * 对指定图片执行 OnnxOCR 识别
  35. * @param {{ imagePath: string, folderPath?: string }} input - imagePath 图片路径(已解析后的相对或绝对路径), folderPath 流程目录
  36. * @returns {{ success: boolean, text?: string, error?: string }}
  37. */
  38. async function executeOcr({ imagePath, folderPath }) {
  39. if (!imagePath || typeof imagePath !== 'string') {
  40. return { success: false, error: '缺少图片路径' }
  41. }
  42. const baseDir = folderPath && typeof folderPath === 'string' ? folderPath : projectRoot
  43. const isAbsoluteOrDrive = imagePath.startsWith('/') || imagePath.includes(':')
  44. const hasSubPath = imagePath.includes('/') || imagePath.includes(path.sep)
  45. const resolvedImage = isAbsoluteOrDrive ? imagePath : (hasSubPath ? path.join(baseDir, imagePath) : path.join(baseDir, 'resources', imagePath))
  46. if (!fs.existsSync(ocrScriptPath)) {
  47. return { success: false, error: `OCR 脚本不存在: ${ocrScriptPath}` }
  48. }
  49. if (!fs.existsSync(resolvedImage)) {
  50. return { success: false, error: `图片不存在: ${resolvedImage}` }
  51. }
  52. const pythonPath = getPythonPath()
  53. const r = spawnSync(pythonPath, [ocrScriptPath, '--image', resolvedImage, '--project-root', projectRoot], {
  54. encoding: 'utf-8',
  55. timeout: 60000,
  56. env: { ...process.env, PYTHONIOENCODING: 'utf-8' },
  57. cwd: projectRoot,
  58. })
  59. const outStr = (r.stdout || '').trim()
  60. const errStr = (r.stderr || '').trim()
  61. if (r.status !== 0) {
  62. return { success: false, error: errStr || outStr || 'OCR 执行失败' }
  63. }
  64. let out
  65. try {
  66. out = JSON.parse(outStr)
  67. } catch (e) {
  68. return { success: false, error: `OCR 输出解析失败: ${outStr}` }
  69. }
  70. if (!out.success) {
  71. return { success: false, error: out.error || 'OCR 识别失败' }
  72. }
  73. return { success: true, text: out.text != null ? String(out.text) : '' }
  74. }
  75. /**
  76. * 在设备截图中查找指定文字,返回该文字区域中心点
  77. * @param {{ device: string, findText: string, folderPath?: string }} input
  78. * @returns {{ success: boolean, center?: { x: number, y: number }, error?: string }}
  79. */
  80. async function executeOcrFindText({ device, findText, folderPath }) {
  81. if (!device) return { success: false, error: '缺少设备 ID,无法截图' }
  82. if (!findText || typeof findText !== 'string') return { success: false, error: '缺少要查找的文字' }
  83. const ts = Date.now()
  84. const screenshotPath = path.join(os.tmpdir(), `ef-ocr-screenshot-${ts}.png`)
  85. try {
  86. captureScreenshot(device, screenshotPath)
  87. if (!fs.existsSync(screenshotPath) || fs.statSync(screenshotPath).size === 0) {
  88. return { success: false, error: '设备截图失败或为空' }
  89. }
  90. const pythonPath = getPythonPath()
  91. const r = spawnSync(pythonPath, [ocrScriptPath, '--image', screenshotPath, '--find-text', findText.trim(), '--project-root', projectRoot], {
  92. encoding: 'utf-8',
  93. timeout: 60000,
  94. env: { ...process.env, PYTHONIOENCODING: 'utf-8' },
  95. cwd: projectRoot,
  96. })
  97. const outStr = (r.stdout || '').trim()
  98. const errStr = (r.stderr || '').trim()
  99. if (r.status !== 0) {
  100. return { success: false, error: errStr || outStr || 'OCR 查找文字失败' }
  101. }
  102. let out
  103. try {
  104. out = JSON.parse(outStr)
  105. } catch (e) {
  106. return { success: false, error: `OCR 输出解析失败: ${outStr}` }
  107. }
  108. if (!out.success || out.x == null || out.y == null) {
  109. return { success: false, error: out.error || '图中未找到该文字' }
  110. }
  111. return { success: true, center: { x: out.x, y: out.y } }
  112. } finally {
  113. try { fs.unlinkSync(screenshotPath) } catch (_) {}
  114. }
  115. }
  116. module.exports = { tagName, schema, executeOcr, executeOcrFindText }