|
|
机器的眼睛多点找色 + 比色守卫
- /**
- * 案例28:机器的眼睛多点找色 + 比色守卫
- *
- * "机器的眼睛"第四招——比色,四招横评:
- * 案例28 多点找色/比色——**连图都不用**,几个"坐标偏移+颜色值"就能定位/判断画面,
- * 代价是只认颜色,画面内容大改色就失效——所以它是**最便宜的页面守卫**。
- *
- * 教学亮点(零素材闭环自证,不需要任何图片文件、不依赖任何 PC 端服务):
- * 每轮截屏 → getPointRGB 从**当前画面实时采 5 个点**的颜色 →
- * 拼成"0,0,#色 + 4 个偏移色"的颜色指纹 →
- * ① checkColors 拿刚拼的指纹回同一屏比 → 应 true(比色链路自证);
- * ② findMultiColor 拿指纹找色 → 命中中心应≈基点(找色链路自证,偏差报数字);
- * ③ getcolorNum 数一数基点色全屏有多少像素 → 直观演示"取色要取稀有色";
- * ④ 用**上一轮的旧指纹**再比一次 → 没换页应 true,换页后应 false——
- * "旧指纹失效 = 页面变了",这就是游戏脚本里最常用的**比色守卫**原型。
- *
- * 新知识点:
- * 1. img.getPointRGB(x%, y%) 返回 Java int[](RGB,可能带 alpha 共 4 个)——按百分比取色,跨分辨率;
- * 2. opencv.checkColors(mat, ['x,y,#RRGGBB',...], 颜色误差, 坐标误差, 相似度) → boolean,
- * 多点比色——**实测锤死:坐标是绝对像素位置**(传相对偏移会拿 (0,0)/负坐标去比,必挂):
- * 逐个检查"指定位置的颜色对不对",全对 true,有一个不对 false;
- * 3. opencv.findMultiColor(mat, 基点String, ['dx,dy,#RRGGBB',...], 偏色阈值, 偏移阈值, 区域%, 相似度)
- * → detect[]——多点找色:和案例27 找图同款 detect,getRealRect() 拿像素坐标。
- * **SDK 文档没给基点格式示例**(实测传纯色串 "#fafafa" 抛 NumberFormatException),
- * 本案例用"格式探测":候选格式逐个试,"不抛异常且命中贴近基点"者为正确,探一次记住;
- * 4. opencv.getcolorNum(mat, ['#RRGGBB',...], 相似度, [0,0,1,1]) → int——区域内该颜色的像素数量;
- * 5. 颜色指纹 = 一串 JS 字符串,可以跨轮存在全局变量里——比"存模板图文件"轻一万倍。
- *
- * 踩坑预告:
- * - **getPointRGB 返回的是 Java 数组**:长度可能是 3(RGB)也可能是 4(ARGB),
- * 取下标前先看 length(本案例已做防御,日志会打出原始值验证);
- * - **纯色/重复图案区域是找色的老毛病**(案例27 同款):白底上取到 #FFFFFF,
- * findMultiColor 全屏几万个候选点,命中的不一定是你采的那个——所以采点要**散开在内容区**,
- * 自检偏差大时打 ⚠ 讲原理,而不是硬报错;
- * - 颜色误差太小:截图有损压缩 + 抗锯齿会让同一屏的颜色都有 ±2~8 的抖动,自检直接挂;
- * 太大:不同页面也能对上,守卫形同虚设——16 起步;
- * - 比色语义是"**这些固定位置的颜色还对不对**"——点位全撞白底时,换页照样全对,
- * 守卫就废了;指纹要散开在内容区,采到"页面专属"的颜色组合才有区分度;
- * - Mat 和 img 是两笔内存,closeImg + recycle 都要还(案例27 铁律);
- * - **文档没示例的新方法参数格式别赌一次到位**:格式探测+打日志试错+全局记住正确格式,
- * 是对 SDK"黑盒参数"最省时间的打法(本案例 findMultiColor 基点就这么收的);
- * - **findMultiColor 全屏区域 + 纯色背景 = 不抛不崩的"发呆卡死"**(实测锤死):
- * 基点色 #fafafa 全屏 750 万像素全当候选点,几十分钟不返回——找色**必须限定小区域**
- * (本案例改成基点±200像素),且长耗时 native 调用**前先打日志**,卡住才知道卡在哪;
- */
- // ========== 配置区(改这里) ==========
- var 比色间隔秒 = 10; // 每 10 秒一轮:切页→截屏→采色→自检→旧指纹守卫→入库
- var 颜色误差 = 16; // checkColors 颜色误差范围(太小自检挂,太大门卫废,16 起步)
- var 坐标误差 = 2; // checkColors 坐标误差范围(绝对坐标 ± 像素)
- var 比色相似度 = 0.95; // checkColors 相似度
- var 找色偏色阈值 = 16; // findMultiColor 颜色容差
- var 找色偏移阈值 = 3; // findMultiColor 坐标容差(像素)
- var 找色相似度 = 0.9; // findMultiColor 相似度
- var 找色搜索半径 = 200; // findMultiColor 只在基点±此半径的小框里找——
- // 实测锤死:纯色背景全屏找色(基点色几百万像素)不抛不崩,
- // 就是几十分钟不返回,日志"发呆"卡死在这
- // 采样的 5 个点(百分比坐标):基点在屏幕正中,其余 4 点散在内容区四角——
- // 故意避开状态栏(顶 5%)和导航条(底 7%),页面内容变了指纹才变
- var 采样点列表 = [
- { 名称: "中心", x: 0.50, y: 0.50 },
- { 名称: "左上", x: 0.15, y: 0.25 },
- { 名称: "右上", x: 0.85, y: 0.25 },
- { 名称: "左下", x: 0.15, y: 0.75 },
- { 名称: "右下", x: 0.85, y: 0.75 }
- ];
- // 演示页面(只用 openApp 实测能切的,和案例23/27 同款)
- var 演示页面列表 = [
- { 名称: "设置", 包名: "com.android.settings" },
- { 名称: "浏览器(百度首页)", 包名: "com.android.browser" }
- ];
- var 开页等待秒 = 4;
- var SQLite路径 = "/sdcard/比色记录.db";
- var 表名 = "color_records";
- // ========== 全局变量顶部声明 ==========
- var gStop = false;
- var 数据库连接 = null;
- var 数据库可用 = false;
- var 累计入库条数 = 0;
- var 累计轮数 = 0;
- var 上轮指纹 = null; // 上一轮拼出的颜色指纹(数组),本轮拿来演"旧指纹守卫"
- var 基点格式索引 = -1; // findMultiColor 基点格式探测结果(-1=还没探出来)
- // ========== importClass 只能放最外层 ==========
- importClass(android.database.sqlite.SQLiteDatabase);
- importClass(java.io.File);
- // ========== 工具函数(先定义后调用) ==========
- function dbConnect() {
- try {
- 数据库连接 = SQLiteDatabase.openOrCreateDatabase(new File(SQLite路径), null);
- 数据库可用 = true;
- printl("[DB] ✅ SQLite 已打开: " + SQLite路径);
- } catch (e) {
- 数据库可用 = false;
- printl("[DB] ⚠ 打开失败(只打印不入库): " + e);
- }
- }
- function dbEnsureTable() {
- if (!数据库可用) return;
- try {
- var sql = "CREATE TABLE IF NOT EXISTS " + 表名 + " (" +
- "id INTEGER PRIMARY KEY AUTOINCREMENT," +
- "page TEXT, base_color TEXT, hit_count INTEGER, dev_px INTEGER," +
- "color_num INTEGER, self_check INTEGER, collected_at INTEGER)";
- 数据库连接.execSQL(sql);
- printl("[DB] ✅ 表已就绪: " + 表名);
- } catch (e) {
- printl("[DB] ⚠ 建表失败: " + e);
- 数据库可用 = false;
- }
- }
- // 一轮比色结果入库(页面/基点色/找色命中数/自检偏差/该色像素数/自检是否通过)
- function dbInsertColor(页面名, 基点色, 命中数, 偏差, 色数, 自检过) {
- if (!数据库可用) return false;
- try {
- var stmt = 数据库连接.compileStatement("INSERT INTO " + 表名 +
- " (page, base_color, hit_count, dev_px, color_num, self_check, collected_at) VALUES (?,?,?,?,?,?,?)");
- stmt.bindString(1, "" + 页面名);
- stmt.bindString(2, "" + 基点色);
- stmt.bindLong(3, 命中数);
- stmt.bindLong(4, 偏差);
- stmt.bindLong(5, 色数);
- stmt.bindLong(6, 自检过 ? 1 : 0);
- stmt.bindLong(7, time.nowStamp());
- stmt.executeInsert();
- stmt.close();
- return true;
- } catch (e) {
- printl("[DB] ❌ 入库失败: " + e);
- return false;
- }
- }
- function dbClose() {
- if (数据库可用 && 数据库连接 != null) {
- try { 数据库连接.close(); } catch (e) {}
- 数据库连接 = null;
- }
- }
- // Java int[] → "#RRGGBB"(防御:长度 4 按 ARGB 取后三位,长度 3 按 RGB)
- // ES5 没有 padStart,用 "0"+hex 再 slice(-2) 补零
- function rgb转hex(rgb) {
- if (!rgb || rgb.length < 3) return "#000000";
- var r = 0, g = 0, b = 0;
- if (rgb.length >= 4) { r = rgb[1]; g = rgb[2]; b = rgb[3]; }
- else { r = rgb[0]; g = rgb[1]; b = rgb[2]; }
- var hr = ("0" + r.toString(16)).slice(-2);
- var hg = ("0" + g.toString(16)).slice(-2);
- var hb = ("0" + b.toString(16)).slice(-2);
- return "#" + hr + hg + hb;
- }
- // 打开演示页面(openApp + 校验,同案例27;只用实测能切过去的两个页面)
- function 打开页面(页面) {
- try {
- app.openApp(页面.包名);
- } catch (e) {
- printl("🚪 openApp 异常(照常截当前屏): " + e);
- return false;
- }
- printl("🚪 打开【" + 页面.名称 + "】(" + 页面.包名 + "),等待 " + 开页等待秒 + " 秒渲染...");
- sleep.second(秒 = 开页等待秒);
- var 前台 = "" + app.getTopPackName();
- if (前台.indexOf(页面.包名) >= 0) {
- printl("📱 校验通过:前台已是 " + 前台);
- return true;
- }
- printl("⚠ 页面没切过去(前台是 " + 前台 + "),照常截当前屏");
- return false;
- }
- // 一轮比色:截屏→实时采5点拼指纹→自检比色→自检找色→色数统计→旧指纹守卫→入库
- // 返回 false 表示本轮失败(截图/比色异常),主循环跳过
- function 比色一轮(页面) {
- var img = null;
- var mat = null;
- try {
- img = screen.screenShotFull();
- if (!img) {
- printl("[比色] ❌ 截图返回空(检查屏幕录制权限)");
- return false;
- }
- // 全屏宽高现在取好(案例27 铁律:凡是算像素坐标,数据在动手前取)
- var 屏宽 = img.getWidth();
- var 屏高 = img.getHeight();
- mat = img.getMat();
- // 步骤1:从这张截图**实时采色**拼指纹(零素材:颜色是从当前画面现取的)
- var 采样们 = [];
- for (var i = 0; i < 采样点列表.length; i++) {
- var pt = 采样点列表[i];
- var rgb = img.getPointRGB(pt.x, pt.y);
- var 色 = rgb转hex(rgb);
- 采样们.push({
- 名称: pt.名称, 色: 色,
- px: Math.round(pt.x * 屏宽), py: Math.round(pt.y * 屏高)
- });
- printl("🎨 采色【" + pt.名称 + "】(" + Math.round(pt.x * 屏宽) + "," +
- Math.round(pt.y * 屏高) + ") → " + 色 +
- " | 原始 int[" + rgb.length + "]: " + rgb[0] + "," + rgb[1] + "," + rgb[2]);
- }
- // 步骤2:拼指纹——实测锤死:checkColors 的坐标是**绝对像素位置**(SDK 示例
- // '10,9,#123233' 就是 (10,9) 那个点),传相对偏移会拿 (0,0)/负坐标去比,必挂。
- // 所以比色指纹 = 5 条 "x,y,#色";找色色组仍用 "dx,dy,#色" 相对偏移(基点的偏移)
- var 基点 = 采样们[0];
- var 指纹 = [];
- for (var j = 0; j < 采样们.length; j++) {
- 指纹.push(采样们[j].px + "," + 采样们[j].py + "," + 采样们[j].色);
- }
- var 多色 = [];
- for (var k = 1; k < 采样们.length; k++) {
- 多色.push((采样们[k].px - 基点.px) + "," + (采样们[k].py - 基点.py) + "," + 采样们[k].色);
- }
- printl("🧬 颜色指纹已拼好: " + 指纹.length + " 个绝对坐标点(基点 " + 基点.色 +
- ") | 找色偏移色组 " + 多色.length + " 条(纯字符串,零图片文件)");
- // 步骤3:比色自检——刚采的指纹回同一屏比,应 true(不通过=链路或阈值有问题)
- var 自检过 = false;
- try {
- 自检过 = opencv.checkColors(mat, 指纹, 颜色误差, 坐标误差, 比色相似度);
- } catch (e1) {
- printl("⚠ checkColors 异常: " + e1);
- }
- printl("🔍 比色自检(同屏采同比): " + (自检过 ? "✅ 通过——链路正确" :
- "❌ 未通过——颜色误差 " + 颜色误差 + " 可能太小(截图有损压缩会抖色),调大再试"));
- // 步骤4:找色——SDK 没给基点格式示例(纯色串实测抛 NumberFormatException),
- // 用格式探测:候选格式逐个试,"不抛异常且命中贴近基点"者为正确格式,探到一次就记住,
- // 后续轮直接用记住的格式(命中中心应≈基点,案例27 同款 detect 用 getRealRect)
- var 命中数 = 0;
- var 偏差 = -1;
- var 最佳cx = -1;
- var 最佳cy = -1;
- var 候选 = [
- { 名: "x,y,#色", 基点: 基点.px + "," + 基点.py + "," + 基点.色, 色组: 多色 },
- { 名: "x,y + 色组含0,0", 基点: 基点.px + "," + 基点.py, 色组: ["0,0," + 基点.色].concat(多色) },
- { 名: "hex不带#", 基点: 基点.色.substring(1), 色组: 多色 }
- ];
- // 搜索区域:基点±找色搜索半径的小框(百分比)——实测教训:传 [0,0,1,1] 全屏,
- // 纯色背景上基点色有 750 万个像素,每个都当候选验一遍,findMultiColor 几十分钟不返回
- var 搜索区域 = [
- Math.max(0, (基点.px - 找色搜索半径) / 屏宽),
- Math.max(0, (基点.py - 找色搜索半径) / 屏高),
- Math.min(1, (基点.px + 找色搜索半径) / 屏宽),
- Math.min(1, (基点.py + 找色搜索半径) / 屏高)
- ];
- for (var c = 0; c < 候选.length; c++) {
- if (基点格式索引 >= 0 && c !== 基点格式索引) continue;
- // 调用前先打日志:再出现"卡住不动",最后一条 🧪 就是卡死的格式
- printl("🧪 findMultiColor 调用中:格式 [" + 候选[c].名 + "]" +
- (基点格式索引 < 0 ? "(探测)" : "(记住的格式)") + " 区域±" + 找色搜索半径 + "像素...");
- try {
- var 开始 = time.nowStamp();
- var 命中们 = opencv.findMultiColor(mat, 候选[c].基点, 候选[c].色组, 找色偏色阈值, 找色偏移阈值, 搜索区域, 找色相似度);
- var 耗时 = time.nowStamp() - 开始;
- var n = 命中们 == null ? 0 : 命中们.length;
- var bd = 999999999;
- var bx = -1;
- var by = -1;
- for (var m = 0; m < n; m++) {
- var 矩形 = 命中们[m].getRealRect();
- var dev = Math.round(Math.sqrt(Math.pow(矩形.getCenterX() - 基点.px, 2) + Math.pow(矩形.getCenterY() - 基点.py, 2)));
- if (dev < bd) {
- bd = dev;
- bx = 矩形.getCenterX();
- by = 矩形.getCenterY();
- }
- }
- if (n > 0 && bd <= 8) {
- 命中数 = n;
- 偏差 = bd;
- 最佳cx = bx;
- 最佳cy = by;
- if (基点格式索引 < 0) {
- 基点格式索引 = c;
- printl("🧪 findMultiColor 基点格式探测成功: [" + 候选[c].名 + "](已记住,后续轮直接用)");
- }
- printl("🎯 找色命中 " + 命中数 + " 处(耗时 " + 耗时 + " 毫秒) | 最贴合中心 (" +
- 最佳cx + "," + 最佳cy + ") | 距基点偏差 " + 偏差 + " 像素 ✅ 链路正确");
- } else if (基点格式索引 < 0) {
- printl("🧪 基点格式 [" + 候选[c].名 + "] 未抛异常但命中 " + n +
- (n > 0 ? " (最小偏差 " + bd + " 像素,不贴合基点,试下一种)" : "(试下一种)"));
- } else {
- printl("🎯 找色 0 命中/偏差 " + (bd > 999999 ? "-" : bd) + " 像素——记住格式 [" + 候选[c].名 +
- "] 下没找到贴近基点的命中:基点色 " + 基点.色 + " 撞纯色区歧义(案例27 同款)或偏色阈值太严");
- }
- } catch (e2) {
- if (基点格式索引 < 0) {
- printl("🧪 基点格式 [" + 候选[c].名 + "] 抛异常: " + e2);
- } else {
- printl("⚠ findMultiColor 异常: " + e2);
- }
- }
- }
- if (基点格式索引 < 0 && 偏差 < 0) {
- printl("🎯 找色三种基点格式都没立住(见上面 🧪 日志)——把 🧪 行原样贴回来定位");
- }
- // 步骤5:色数统计——基点色全屏有多少像素?教学:稀有色才配当指纹
- var 色数 = -1;
- try {
- 色数 = opencv.getcolorNum(mat, [基点.色], 0.9, [0, 0, 1, 1]);
- printl("📊 基点色 " + 基点.色 + " 全屏像素数: " + 色数 +
- (色数 > 20000 ? " ⚠ 太常见(白/黑/灰底),换内容区边缘点当指纹更独特" : " ✅ 够稀有,适合当指纹"));
- } catch (e3) {
- printl("⚠ getcolorNum 异常: " + e3);
- }
- // 步骤6:旧指纹守卫——拿上一轮的指纹比今天的屏:
- // 没换页应 true;换页后应 false(旧颜色组合在新页面凑不齐)= 检测到页面变化
- if (上轮指纹 != null) {
- var 旧过 = false;
- try {
- 旧过 = opencv.checkColors(mat, 上轮指纹, 颜色误差, 坐标误差, 比色相似度);
- } catch (e4) {
- printl("⚠ 旧指纹比色异常: " + e4);
- }
- printl("🛡 旧指纹(上一轮拼的)比色: " + (旧过 ?
- "仍通过——没换页属正常;若已换页还通过,说明指纹点位太集中/全撞白底,守卫不够独特" :
- "失效 ✅ 页面已变化——这就是游戏脚本里最便宜的『比色守卫』:几个字符串就判断出画面变了没"));
- } else {
- printl("🛡 首轮没有旧指纹可比(下轮开始演示守卫)");
- }
- 上轮指纹 = 指纹;
- // 入库:本轮全链路结果(比色守卫要留痕,事后 SQL 复盘哪一轮页面变的)
- if (dbInsertColor(页面.名称, 基点.色, 命中数, 偏差, 色数, 自检过)) {
- 累计入库条数++;
- printl("💾 已入库 [" + 累计入库条数 + "] 页面:" + 页面.名称 +
- " | 基点:" + 基点.色 + " | 自检:" + (自检过 ? "过" : "挂"));
- }
- return true;
- } catch (e) {
- printl("[比色] ❌ 本轮异常: " + e);
- return false;
- } finally {
- // 两笔内存都要还:Mat 用 opencv.closeImg,image 用 recycle(案例27 铁律)
- try { if (mat != null) opencv.closeImg(mat); } catch (ea) {}
- try { if (img != null) img.recycle(); } catch (eb) {}
- }
- }
- // ========== 主流程 ==========
- printl("===== 案例28 多点找色 + 比色守卫 启动 =====");
- dbConnect();
- dbEnsureTable();
- printl("⚙ 配置:每 " + 比色间隔秒 + " 秒一轮 · 颜色误差 " + 颜色误差 + " · 采样点 " +
- 采样点列表.length + " 个 · 库: " + SQLite路径);
- printl("ℹ 机器眼睛四选一:OCR(20,慢/只有字) | 控件树(23,快/读不到游戏) | 模板找图(27,准/要备图) | 比色(本案例,连图都不要,几个字符串当指纹)");
- printl("ℹ 零素材闭环:颜色从当前画面现采现拼→同屏自检比色应真→找色偏差≈0→旧指纹在新页面失效=检测到换页");
- // 停止按钮清理(顺序:先置标志,stopAllRuning 放最后)
- event.onStopBtnEvent(function () {
- gStop = true;
- dbClose();
- runTime.stopAllRuning();
- });
- // 主循环:切页(每 2 轮一次)→ 比色一轮 → 分片等待
- var 页面索引 = 0;
- var 当前页面 = 演示页面列表[0];
- while (!gStop) {
- 累计轮数++;
- // 每 2 轮换一次页面:正好演示"上一轮指纹拿到新页面比色→失效"
- if ((累计轮数 - 1) % 2 === 0) {
- 当前页面 = 演示页面列表[页面索引 % 演示页面列表.length];
- 页面索引++;
- printl("\n---- 第 " + 累计轮数 + " 轮:切页【" + 当前页面.名称 + "】----");
- 打开页面(当前页面);
- } else {
- printl("\n---- 第 " + 累计轮数 + " 轮:页面不变【" + 当前页面.名称 + "】----");
- }
- var ok = 比色一轮(当前页面);
- if (!ok) {
- printl("⚠ 本轮比色失败,跳过(下轮继续)");
- }
- var 剩余 = 比色间隔秒;
- while (剩余 > 0 && !gStop) {
- var 步长 = 剩余 >= 5 ? 5 : 剩余;
- printl("⏳ 等待下一轮...剩余 " + 剩余 + " 秒(累计 " + 累计轮数 + " 轮/入库 " + 累计入库条数 + " 条)");
- sleep.second(秒 = 步长);
- 剩余 = 剩余 - 步长;
- }
- }
- dbClose();
- printl("===== 流程结束:共 " + 累计轮数 + " 轮,入库 " + 累计入库条数 + " 条,库: " + SQLite路径 + " =====");
复制代码
|
|