B2B网络软件

标题: MQTT远程本地局域网OCR例子 [打印本页]

作者: YYPOST群发软件    时间: 2 小时前
标题: MQTT远程本地局域网OCR例子
MQTT远程本地局域网OCR例子
MQTT远程本地局域网OCR例子 B2B网络软件

MQTT远程本地局域网OCR例子 B2B网络软件

MQTT远程本地局域网OCR例子 B2B网络软件

  1. /**
  2. * 本地 OCR 识别示例(AIWROK 端)
  3. * ==================================================================
  4. * 用你自己电脑上的 OCR_LAN_Server
  5. * 手机截图 → 发给电脑识别 → 拿回文字和坐标 → 可点击。
  6. *
  7. * 服务端接口(源码 OCR_LAN_Server/server/app.py)
  8. *   GET  /health      服务是否在线
  9. *   POST /ocr_all     全屏识别,返回 lines[{text,x,y,confidence}...]
  10. *   POST /ocr_find    找文字,返回 {found,best:{center_x,center_y}}
  11. *   POST /ocr_smart   智能路由(自动选引擎/分块/缓存),可带 text 直接找字
  12. *
  13. * 两个必须知道的坑(本示例已正确处理)
  14. *   1) region 是【百分比】[x,y,w,h],服务端 crop_by_percent 裁完不还原坐标,
  15. *      返回的文字坐标是"裁剪图"里的,必须客户端自己加回偏移量,否则点偏。
  16. *   2) 如果为了省流量把截图缩小再上传,服务端返回的是"缩小图"的坐标,
  17. *      必须按比例放大回屏幕坐标。
  18. *
  19. * 用法:改下面 OCR_SERVER 为你电脑局域网 IP(手机和服务端同一 WiFi),
  20. *       在 AIWROK 里直接运行本脚本,看日志。
  21. * ==================================================================
  22. */

  23. // ==================== 配置 ====================
  24. // 电脑地址候选表:逐个试 /health,谁通用谁。电脑 IP 变了不用改逻辑,
  25. // 只要把电脑当前的 IPv4 加进这个表即可(本机实测就从 .103 变成了 .104)
  26. var OCR_SERVERS = [
  27.     "http://192.168.10.104:9999",
  28.     "http://192.168.10.103:9999"
  29. ];
  30. var OCR_SERVER = "";     // 由 ocrPickServer() 自动选定,别手改
  31. var OCR_MAXW   = 960;    // 上传前把截图缩到该宽度。服务端本来就会缩到 960,
  32.                          // 填 960 不丢精度还省约 1/3 流量;填 0 = 不缩放
  33. var TIMEOUT_MS = 30000;  // 单次识别超时
  34. // ==============================================

  35. // ---------- 小工具 ----------
  36. function L(s) { try { printl("[OCR] " + s); } catch (e) {} }
  37. function pad2(n) { return n < 10 ? "0" + n : "" + n; }

  38. var SCREEN_W = 1080, SCREEN_H = 1920;
  39. try { SCREEN_W = screen.getScreenWidth(); SCREEN_H = screen.getScreenHeight(); } catch (e) {}
  40. L("屏幕 = " + SCREEN_W + "x" + SCREEN_H);

  41. /** 依次探测候选地址,选定可用的那个 */
  42. function ocrPickServer() {
  43.     for (var i = 0; i < OCR_SERVERS.length; i++) {
  44.         var url = OCR_SERVERS[i];
  45.         try {
  46.             var http = new okHttp();
  47.             try { http.setTimeout(4000); } catch (e) {}
  48.             var r = JSON.parse("" + http.get(url + "/health"));
  49.             if (r.code == 0) {
  50.                 OCR_SERVER = url;
  51.                 L("选定服务地址 " + url + "(候选第 " + (i + 1) + " 个)→ 在线可用");
  52.                 return true;
  53.             }
  54.             L(url + " 有响应但异常:" + ("" + JSON.stringify(r)).slice(0, 80));
  55.         } catch (e2) {
  56.             L("候选 " + url + " 连不上:" + e2);
  57.         }
  58.     }
  59.     L("所有候选都不通,排查四步:");
  60.     L("  1 电脑 OCR 服务开了吗:OCR_LAN_Server\\dist\\OCR_LAN_Server\\OCR_LAN_Server.exe");
  61.     L("  2 电脑真实 IP 是多少:cmd 执行 ipconfig,看「IPv4 地址」,");
  62.     L("    别用 192.168.163.x / 192.168.248.x(VMware 虚拟网卡,手机连不到)");
  63.     L("  3 手机和电脑是否同一 WiFi/网段(手机设置里看 WiFi 的网关)");
  64.     L("  4 把电脑当前地址加进本脚本顶部 OCR_SERVERS 列表");
  65.     return false;
  66. }

  67. /**
  68. * 截图并(可选)缩放,返回 base64 和换算比例
  69. * factor = 上传图宽 / 屏幕宽,后面要把服务端坐标除以它
  70. */
  71. function shotForOcr(maxW) {
  72.     // 注意:AIWROK 的 screenShotFull() 返回它自己的 Image 对象,只有
  73.     // cutImagePercent / toBase64 / getWidth / getHeight / recycle,
  74.     // 没有 android Bitmap 的 compress、createBitmap —— 本机缩放会直接报
  75.     // "Can't find method android.graphics.Bitmap.createBitmap(...)"。
  76.     // 所以一律原图上传,缩放交给服务端(它内部本来就会缩到 960),
  77.     // factor 恒为 1,坐标无需再换算。
  78.     var bmp = screen.screenShotFull();
  79.     if (!bmp) throw new Error("截图返回空");
  80.     var b64 = "" + bmp.toBase64();
  81.     var w0 = 0, h0 = 0;
  82.     try { w0 = bmp.getWidth(); h0 = bmp.getHeight(); } catch (e) {}
  83.     try { bmp.recycle(); } catch (e2) {}
  84.     var sentW = w0 || SCREEN_W, sentH = h0 || SCREEN_H;
  85.     L("截图上传 " + sentW + "x" + sentH + " 约 " + Math.round(b64.length / 1024) + "KB(原图,缩放由服务端做)");
  86.     return { b64: b64, sentW: sentW, sentH: sentH, factor: 1 };
  87. }

  88. /** POST JSON,返回解析后的对象;异常返回 {code:-1,err:...} */
  89. function post(path, obj) {
  90.     try {
  91.         var http = new okHttp();
  92.         try { http.setTimeout(TIMEOUT_MS); } catch (e) {}
  93.         try { http.setHeader("Content-Type", "application/json"); } catch (e) {}
  94.         var t0 = (new Date()).getTime();
  95.         var resp = "" + http.json(OCR_SERVER + path, obj);
  96.         var ms = (new Date()).getTime() - t0;
  97.         var r;
  98.         try { r = JSON.parse(resp); }
  99.         catch (e) { L("返回不是 JSON(服务没开或地址错):" + resp.slice(0, 120)); return { code: -1, err: "非JSON响应" }; }
  100.         L(path + " 往返 " + ms + "ms,服务端 " + (r.elapsed != null ? r.elapsed + "s" : "?") +
  101.           (r.strategy ? (",策略=" + r.strategy) : "") + (r.count != null ? (",结果数=" + r.count) : ""));
  102.         return r;
  103.     } catch (e2) {
  104.         L(path + " 请求失败:" + e2);
  105.         return { code: -1, err: "" + e2 };
  106.     }
  107. }

  108. /** 健康检查(保留旧名字,内部走多候选探测) */
  109. function ocrHealth() {
  110.     if (OCR_SERVER) {
  111.         try {
  112.             var http = new okHttp();
  113.             try { http.setTimeout(5000); } catch (e) {}
  114.             var r = JSON.parse("" + http.get(OCR_SERVER + "/health"));
  115.             return r.code == 0;
  116.         } catch (e2) { return false; }
  117.     }
  118.     return ocrPickServer();
  119. }

  120. /**
  121. * 把服务端返回的坐标换算成真实屏幕坐标
  122. * r    : 服务端返回对象
  123. * shot : shotForOcr 的返回(含 factor)
  124. * reg  : [x,y,w,h] 百分比 或 null
  125. */
  126. function toScreen(r, shot, reg) {
  127.     var out = [];
  128.     var list = r.words || r.lines || [];
  129.     var offX = 0, offY = 0;
  130.     if (reg) {                                  // 裁剪偏移(在上传图坐标系里)
  131.         offX = shot.sentW * reg[0] / 100;
  132.         offY = shot.sentH * reg[1] / 100;
  133.     }
  134.     for (var i = 0; i < list.length; i++) {
  135.         var w = list[i];
  136.         var cx = (w.center_x != null ? w.center_x : w.x);
  137.         var cy = (w.center_y != null ? w.center_y : w.y);
  138.         out.push({
  139.             text: w.text,
  140.             x: Math.round((cx + offX) / shot.factor),   // 加裁剪偏移 → 除缩放比例
  141.             y: Math.round((cy + offY) / shot.factor),
  142.             conf: w.confidence
  143.         });
  144.     }
  145.     return out;
  146. }

  147. /** 识别全部文字,返回 [{text,x,y,conf}] */
  148. function ocrList(reg) {
  149.     var shot = shotForOcr(OCR_MAXW);
  150.     var req = { image: shot.b64 };
  151.     if (reg) req.region = reg;
  152.     var r = post("/ocr_smart", req);
  153.     if (r.code != 0) return [];
  154.     return toScreen(r, shot, reg);
  155. }

  156. /** 识别并拼成一段文本 */
  157. function ocrText(reg) {
  158.     var list = ocrList(reg);
  159.     var s = [];
  160.     for (var i = 0; i < list.length; i++) s.push(list[i].text);
  161.     return s.join("  ");
  162. }

  163. /** 找文字,返回 {found,x,y,text} 不点击 */
  164. function ocrFind(text, reg, mode) {
  165.     var shot = shotForOcr(OCR_MAXW);
  166.     var req = { image: shot.b64, text: text, mode: mode || "include" };
  167.     if (reg) req.region = reg;
  168.     var r = post("/ocr_smart", req);
  169.     if (r.code != 0) return { found: false };
  170.     if (!r.found || !r.best) return { found: false, text: text };
  171.     var offX = reg ? shot.sentW * reg[0] / 100 : 0;
  172.     var offY = reg ? shot.sentH * reg[1] / 100 : 0;
  173.     return {
  174.         found: true, text: text, matches: r.count_matches || 1,
  175.         x: Math.round((r.best.center_x + offX) / shot.factor),
  176.         y: Math.round((r.best.center_y + offY) / shot.factor)
  177.     };
  178. }

  179. /** 找字并点击 */
  180. function ocrClick(text, reg, mode) {
  181.     var f = ocrFind(text, reg, mode);
  182.     if (!f.found) { L("没找到「" + text + "」"); return false; }
  183.     auto.clickPoint(f.x, f.y);
  184.     L("已点击「" + text + "」(" + f.x + "," + f.y + ") 共 " + f.matches + " 处");
  185.     return true;
  186. }

  187. /** 判断屏幕上有没有某个字 */
  188. function ocrExists(text, reg) {
  189.     return ocrFind(text, reg).found === true;
  190. }

  191. /**
  192. * 等某个字出现并点击,超时返回 false
  193. * gapMs 默认 3200 毫秒:服务端结果缓存 TTL 是 3 秒,而且没有"单次请求绕缓存"的参数。
  194. * 轮询比 3 秒快的话,拿到的往往是上一帧的缓存结果(日志里 strategy=cache_hit),
  195. * 画面其实已经变了也可能被判成"没找到"。要更快响应,就去服务端
  196. * ocr_config.json 把 cache_ttl 调小或 enable_cache 设 false。
  197. */
  198. function ocrWaitClick(text, sec, reg, gapMs) {
  199.     var gap = gapMs || 3200;
  200.     var end = (new Date()).getTime() + (sec || 10) * 1000;
  201.     var tryN = 0;
  202.     L("开始等待「" + text + "」,每 " + gap + "ms 查一次(要快就调小服务端 cache_ttl)");
  203.     while ((new Date()).getTime() < end) {
  204.         tryN++;
  205.         L("第 " + tryN + " 次查找「" + text + "」…");
  206.         if (ocrClick(text, reg)) return true;
  207.         try { sleep.millisecond(gap); } catch (e) { java.lang.Thread.sleep(gap); }
  208.     }
  209.     L("等 " + sec + " 秒仍未出现「" + text + "」,放弃");
  210.     return false;
  211. }

  212. // ==================================================================
  213. // 演示主流程:每一步都打日志
  214. // ==================================================================
  215. function main() {
  216.     L("================ 本地 OCR 示例开始 ================");
  217.     if (!ocrHealth()) { L("服务不可用,示例结束"); return; }

  218.     // 1) 全屏识别,列出所有文字和坐标
  219.     L("--- 1. 全屏识别 ---");
  220.     var all = ocrList(null);
  221.     L("共 " + all.length + " 个文字块:");
  222.     for (var i = 0; i < all.length && i < 40; i++) {
  223.         L("  [" + (i + 1) + "] 「" + all[i].text + "」 坐标(" + all[i].x + "," + all[i].y + ") 置信度 " +
  224.           (all[i].conf != null ? all[i].conf.toFixed(2) : "?"));
  225.     }
  226.     if (all.length > 40) L("  …其余 " + (all.length - 40) + " 条省略");

  227.     // 2) 只识别上半屏(区域用百分比)
  228.     L("--- 2. 区域识别(上半屏 0,0,100,50) ---");
  229.     var top = ocrList([0, 0, 100, 50]);
  230.     L("上半屏 " + top.length + " 个文字块:" + (top.length ? ("「" + top[0].text + "」(" + top[0].x + "," + top[0].y + ") 开头…") : "无"));

  231.     // 3) 拼成整段文本
  232.     L("--- 3. 整屏文本 ---");
  233.     L(ocrText(null).slice(0, 300));

  234.     // 4) 找字(不点)
  235.     L("--- 4. 找字测试 ---");
  236.     var probe = all.length ? all[0].text : "设置";
  237.     var f = ocrFind(probe, null);
  238.     L(f.found ? ("找到「" + probe + "」在 (" + f.x + "," + f.y + "),共 " + f.matches + " 处")
  239.               : ("没找到「" + probe + "」"));

  240.     // 5) 找字并点击(示例用第 4 步找到的词,换成你自己的关键词)
  241.     L("--- 5. 找字并点击 ---");
  242.     if (f.found) {
  243.         L("演示点击:" + probe);
  244.         ocrClick(probe, null);
  245.     } else {
  246.         L("没找到可点的词,跳过点击");
  247.     }

  248.     // 6) 等某个字出现再点(把"点赞"换成你的目标词)
  249.     L("--- 6. 等待并点击(10 秒超时) ---");
  250.     var waitWord = "点赞";
  251.     L("演示等待「" + waitWord + "」出现(没有属正常,超时即退出)");
  252.     ocrWaitClick(waitWord, 10, null);

  253.     L("================ 示例结束 ================");
  254.     L("常用函数:ocrHealth() ocrList(区域) ocrText(区域) ocrFind(词,区域) ocrClick(词,区域) ocrExists(词) ocrWaitClick(词,秒,区域)");
  255.     L("区域一律用百分比 [x,y,w,h],例:[0,60,100,40] = 下半屏");
  256. }

  257. main();
复制代码









欢迎光临 B2B网络软件 (http://bbs.niubt.cn/) Powered by Discuz! X3.2