北大侠客行MUD论坛

 找回密码
 注册
搜索
热搜: 新手 wiki 升级
查看: 84|回复: 4

求教:如何识别字串组成的数字

[复制链接]
发表于 前天 10:00 AM | 显示全部楼层 |阅读模式
----------------------------------------
--888-----888-----888-----888-----888---
-8---8---8---8---8---8---8---8---8---8--
-----8-------8---8---8---8---8-------8--
----8------88----8---8---8---8------8---
---8---------8---8---8---8---8-----8----
--8----------8---8---8---8---8----8-----
-8---8---8---8---8---8---8---8---8---8--
-88888----888-----888-----888----88888--
----------------------------------------
如图数字是23002,但怎么让机器识别呢?

北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
发表于 前天 10:06 AM | 显示全部楼层

我没有测试下面的代码,给你参考,你可以多给几组图给ai,这样会更准确的识别出所有这种规律的图

  1. /**
  2. * 识别由 '8'(前景)和 '-'(背景)构成的 ASCII 数字横排。
  3. * 核心:按空列把整幅图切成单个数字,然后用七段数码管的7个区域做密度判定。
  4. */

  5. function parseArt(art, fg='8') {
  6.   const lines = art.replace(/\r/g,'').split('\n').filter(l=>l.trim().length>0);
  7.   const h = lines.length;
  8.   const w = Math.max(...lines.map(l=>l.length));
  9.   // 右侧用背景补齐,保证等宽
  10.   const grid = Array.from({length:h}, (_,r)=>
  11.     Array.from({length:w}, (_,c)=> (lines[r][c]||'-') === fg)
  12.   );
  13.   return {grid, h, w};
  14. }

  15. // 把整幅图按“全空白列”切成若干数字列区间
  16. function splitDigits(grid) {
  17.   const h = grid.length, w = grid[0].length;
  18.   const colHasInk = c => grid.some(row => row[c]);
  19.   const segments = [];
  20.   let inRun = false, start = 0;
  21.   for (let c=0;c
  22.     if (colHasInk(c)) {
  23.       if (!inRun){ inRun = true; start = c; }
  24.     } else {
  25.       if (inRun){
  26.         segments.push([start, c-1]);
  27.         inRun = false;
  28.       }
  29.     }
  30.   }
  31.   if (inRun) segments.push([start, w-1]);
  32.   // 去掉过窄的碎片(噪声)
  33.   return segments.filter(([s,e]) => e - s + 1 >= 2);
  34. }

  35. // 从列区间裁切出单个数字的像素矩阵,并上下裁边(去掉全背景的行)
  36. function cropDigit(grid, colStart, colEnd) {
  37.   const h = grid.length;
  38.   let rTop = 0, rBot = h-1;
  39.   // 找最上方含墨的行
  40.   while (rTop < h && grid[rTop].slice(colStart, colEnd+1).every(v=>!v)) rTop++;
  41.   // 找最下方含墨的行
  42.   while (rBot >= 0 && grid[rBot].slice(colStart, colEnd+1).every(v=>!v)) rBot--;
  43.   if (rTop > rBot) return null;
  44.   const sub = [];
  45.   for (let r=rTop;r<=rBot;r++){
  46.     sub.push(grid[r].slice(colStart, colEnd+1));
  47.   }
  48.   return sub;
  49. }

  50. // 计算某个区域的“点亮密度”(true 比例)
  51. function density(board, r0, r1, c0, c1) {
  52.   r0 = Math.max(0, Math.min(r0, board.length-1));
  53.   r1 = Math.max(0, Math.min(r1, board.length-1));
  54.   c0 = Math.max(0, Math.min(c0, board[0].length-1));
  55.   c1 = Math.max(0, Math.min(c1, board[0].length-1));
  56.   if (r1 < r0 || c1 < c0) return 0;
  57.   let cnt=0, tot=0;
  58.   for (let r=r0;r<=r1;r++){
  59.     for (let c=c0;c<=c1;c++){
  60.       tot++;
  61.       if (board[r][c]) cnt++;
  62.     }
  63.   }
  64.   return tot ? cnt/tot : 0;
  65. }

  66. /**
  67. * 把一个数字块用“七段”来判定:a,b,c,d,e,f,g
  68. *  a: 顶横  b: 右上  c: 右下  d: 底横  e: 左下  f: 左上  g: 中横
  69. * 返回 bitmask(按 abcdefg -> 6..0 位)
  70. */
  71. function sevenSegMask(board) {
  72.   const H = board.length, W = board[0].length;
  73.   // 为了鲁棒,区域占整体的一定比例,适配粗细不同
  74.   const th = Math.max(1, Math.floor(H * 0.18));     // 横段厚度
  75.   const tv = Math.max(1, Math.floor(W * 0.18));     // 竖段厚度
  76.   const pad = Math.max(1, Math.floor(Math.min(H,W) * 0.08)); // 边缘留白
  77.   const midR = Math.floor(H/2);

  78.   // 各段的采样窗口
  79.   const a = density(board, pad, pad+th-1, pad, W-1-pad);
  80.   const d = density(board, H-1-pad-th+1, H-1-pad, pad, W-1-pad);
  81.   const g = density(board, midR - Math.floor(th/2), midR + Math.ceil(th/2)-1, pad, W-1-pad);

  82.   const f = density(board, pad, midR - Math.ceil(th/2), pad, pad+tv-1);
  83.   const b = density(board, pad, midR - Math.ceil(th/2), W-1-pad-tv+1, W-1-pad);

  84.   const e = density(board, midR + Math.floor(th/2), H-1-pad, pad, pad+tv-1);
  85.   const c = density(board, midR + Math.floor(th/2), H-1-pad, W-1-pad-tv+1, W-1-pad);

  86.   // 阈值:段区域内有一定比例的 '8' 就认为该段点亮
  87.   const T = 0.35; // 可按需要微调
  88.   const bit = v => v >= T ? 1 : 0;

  89.   const bits = [
  90.     bit(a), // a
  91.     bit(b), // b
  92.     bit(c), // c
  93.     bit(d), // d
  94.     bit(e), // e
  95.     bit(f), // f
  96.     bit(g)  // g
  97.   ];
  98.   // 组装成掩码(abc def g -> 6..0)
  99.   return (bits[0]<<6)|(bits[1]<<5)|(bits[2]<<4)|(bits[3]<<3)|(bits[4]<<2)|(bits[5]<<1)|(bits[6]<<0);
  100. }

  101. // 标准七段到数字的映射(常见数码管编码)
  102. const sevenSegToDigit = new Map([
  103.   // a b c d e f g
  104.   [0b1110111, 0], // 0: a b c d e f (g=0) -> 0b1110111?(按位需核对下顺序)
  105.   [0b0100100, 1], // 1: b c
  106.   [0b1011101, 2], // 2: a b d e g
  107.   [0b1101101, 3], // 3: a b c d g
  108.   [0b0101110, 4], // 4: b c f g
  109.   [0b1101011, 5], // 5: a c d f g
  110.   [0b1111011, 6], // 6: a c d e f g
  111.   [0b0100101, 7], // 7: a b c(有的实现不亮 g)
  112.   [0b1111111, 8], // 8: all
  113.   [0b1101111, 9], // 9: a b c d f g
  114. ]);

  115. // 由于上面的位序容易混淆,提供一个更稳妥的映射构造器:用[a,b,c,d,e,f,g]布尔数组生成mask
  116. function maskFromSegs([a,b,c,d,e,f,g]) {
  117.   return (a<<6)|(b<<5)|(c<<4)|(d<<3)|(e<<2)|(f<<1)|(g<<0);
  118. }
  119. // 用布尔数组重建映射,避免手写二进制出错
  120. const LUT = new Map([
  121.   [maskFromSegs([1,1,1,1,1,1,0]), 0],
  122.   [maskFromSegs([0,1,1,0,0,0,0]), 1],
  123.   [maskFromSegs([1,1,0,1,1,0,1]), 2],
  124.   [maskFromSegs([1,1,1,1,0,0,1]), 3],
  125.   [maskFromSegs([0,1,1,0,0,1,1]), 4],
  126.   [maskFromSegs([1,0,1,1,0,1,1]), 5],
  127.   [maskFromSegs([1,0,1,1,1,1,1]), 6],
  128.   [maskFromSegs([1,1,1,0,0,0,0]), 7],
  129.   [maskFromSegs([1,1,1,1,1,1,1]), 8],
  130.   [maskFromSegs([1,1,1,1,0,1,1]), 9],
  131. ]);

  132. function recognizeAsciiDigits(art, fg='8') {
  133.   const {grid} = parseArt(art, fg);
  134.   const segments = splitDigits(grid);
  135.   const digits = [];
  136.   for (const [cs,ce] of segments) {
  137.     const block = cropDigit(grid, cs, ce);
  138.     if (!block) { digits.push('?'); continue; }
  139.     const mask = sevenSegMask(block);
  140.     const d = LUT.get(mask);
  141.     digits.push(d !== undefined ? String(d) : '?');
  142.   }
  143.   return digits.join('');
  144. }

  145. // ------------------ DEMO ------------------

  146. const art = `
  147. ----------------------------------------
  148. --888-----888-----888-----888-----888---
  149. -8---8---8---8---8---8---8---8---8---8--
  150. -----8-------8---8---8---8---8-------8--
  151. ----8------88----8---8---8---8------8---
  152. ---8---------8---8---8---8---8-----8----
  153. --8----------8---8---8---8---8----8-----
  154. -8---8---8---8---8---8---8---8---8---8--
  155. -88888----888-----888-----888----88888--
  156. ----------------------------------------
  157. `;

  158. console.log(recognizeAsciiDigits(art)); // 期望输出: 23002
复制代码



北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 前天 10:14 AM | 显示全部楼层
非常感谢!
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
发表于 前天 12:01 PM | 显示全部楼层
分块,标准化,对比。

分块是从指定位置把待识别的图案裁出来。

标准化是把背景替换为标准字符,前景替换为标准字符,让后转成一个字符串。

对比是在事先准备好的字符串字典里找到对应的值。
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
发表于 前天 01:22 PM | 显示全部楼层
这不就是简单的点阵字吗
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|北大侠客行MUD ( 京ICP备16065414号-1 )

GMT+8, 2025-8-19 01:28 PM , Processed in 0.010448 second(s), 14 queries .

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表