news 2026/10/21 19:54:50

鸿蒙应用文搜图·诗词选词搜图:从 12 首诗词到 8 关玩法,把「意象匹配」做成点选式检索

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
鸿蒙应用文搜图·诗词选词搜图:从 12 首诗词到 8 关玩法,把「意象匹配」做成点选式检索

鸿蒙应用文搜图·诗词选词搜图:从 12 首诗词到 8 关玩法,把「意象匹配」做成点选式检索

文搜图的瓶颈从来不在检索,在描述。搜「海边日落」能命中,是因为标签里恰好有这几个词;换个用户搜「去年夏天那片海」,同一张图就搜不出来。检索引擎的召回上限,在用户敲下回车那一刻就已经决定了。

「图述」把这个黑盒掀开:系统出图,你写描述,端侧按覆盖度、精准度、简洁度三个维度给你打分,并逐词告诉你哪个词白写了、还能补什么角度。会写描述的人,用任何文搜图产品都会更好用。

技术栈上这是个「干净得反常」的 HarmonyOS 工程:一个系统 Kit 都没用。没有photoAccessHelper选图、没有imageKit 解码编码、没有 Share Kit 分享、没有 OffscreenCanvas 排版——核心是一套纯 ArkTS 实现的文本评分算法:中文分词、同义词归一化、三级标签匹配、三维加权打分。零联网、零权限、零 Kit 依赖,rawfile 里只有 12 张演示图卡。

一、效果展示

第 1 步:开始页。首次进入是规则说明页——四条玩法、一个「开始写话」按钮。整个应用就三个页面:开始页、写话页、成绩单。

第 2 步:出图。点「开始写话」,6 轮出图从 12 张图卡中随机不重复抽取(种子随机,每局都不同)。每轮一张目标图 + 一个输入框,图片左下角标注「目标:云天」——注意这是图卡标题,不是答案,写描述要靠眼睛看图,不是照抄标题。

第 3 步:提交评分。输入天空 云朵 蓝天 月亮 自然 傍晚 广阔 蓝色,点「提交评分」,立刻出三项分数 + 一个总分:

  • 覆盖度 100——8 个标签全写到了
  • 精准度 100——8 个词全部命中,没有一个废话
  • 简洁度 30——8 个词太多了(3 词才是满分)

第 4 步:逐词反馈。每个词一个小芯片,标明命中状态和命中的具体标签:「蓝天」显示「命中·蓝天」,「广阔」显示「命中·广阔」。命中是青绿,同义命中是琥珀,未命中是红——一眼看出哪个词白写了。下面还有一栏「还可以写到的角度」,把没覆盖的标签列出来,等于直接告诉你还能补什么。

第 5 步:多轮与成绩单。同一条描述可以反复改、反复提交,取本轮最高分。6 轮结束后按平均分评级:

hvigor 构建链路完整可见:Launching com.example.describemaster→hdc install257 ms →hdc shell aa start157 ms → 应用 3 秒 894 毫秒完成安装启动。

二、为什么是「看图写话」

文搜图产品的体验断在中间一环:大多数人不会写查询词。

引擎侧的能力这些年已经很强——端侧语义检索、相似度排序、作用域过滤,该有的都有。但用户敲进去的那几个字,往往既不全、也不准、还啰嗦。「猫」漏掉了宠物/毛茸茸/特写这些能提升召回的维度;「我家那只特别可爱的毛茸茸的小猫咪」又掺了一堆无效词,把精准度拉了下来。同样的引擎,查询词质量不同,结果能差出一个量级。

所以「图述」做的是反向训练:把检索的评分标准直接暴露给用户。你在写话里学到的三件事——写全主体、别写废话、别堆同义词——正是写好搜索词的全部要领。而教学本身不需要任何模型,一套评分算法就够。

三、玩法与评分模型

3.1 一轮的结构

每轮一张图卡(8 个语义标签),用户输入一句描述(空格/逗号分隔的若干个词),系统给出 0~100 的总分。同轮可多次提交取最优,6 轮结束后按平均分评级 S/A/B/C/D。

3.2 三维评分:各占多少

总分是三个维度的加权和,权重是刻意设计的:

consttotal:number=Math.round(coverage*0.4+precision*0.35+concision*0.25);
维度权重计算方式考什么
覆盖度40%命中标签数 / 总标签数写全了吗
精准度35%命中词数 / 总词数没有废话吗
简洁度25%词数 3 最优,每多 1 词 -15,下限 30不堆词吗

覆盖度给最高权重,是因为「漏」比「多」更伤检索——少写一个主体,可能整张图都召回不到;多写一个形容词,只是排序降一位。

3.3 简洁度的曲线:为什么 3 词是满分

简洁度不是线性递减,而是一条前升后降的曲线:

letconcision:number=100;if(words.length>3){concision=Math.max(30,100-(words.length-3)*15);}elseif(words.length<3&&words.length>0){concision=60+words.length*10;// 1 词 70 / 2 词 80}elseif(words.length===0){concision=0;}

1 词 70 分、2 词 80 分、3 词 100 分、4 词 85 分、5 词 70 分、6 词 55 分、7 词 40 分、8 词及以上 30 分(触底下限)。

这条曲线是刻意做成「倒 V」的:它奖励的既不是惜字如金,也不是多多益善,而是「刚好说清」——三五个词把主体、环境、氛围各点一个。这正是高质量搜索词的长度分布。

3.4 天花板与评级:一个诚实的发现

实测「云天」那轮拿到 83 分(覆盖 100 + 精准 100 + 简洁 30)。这不是偶然——在 8 标签的图卡上,83 分就是理论上限:

total = 100×0.4 + 100×0.35 + 30×0.25 = 40 + 35 + 7.5 = 82.5 → 83

因为要把 8 个标签全覆盖,就必须写 8 个词;而 8 个词的简洁度已经被压到下限 30。覆盖度和简洁度在 8 标签卡上是互斥的——这是权重设计带来的一个真实后果,写在这里供参考:当前参数下,S 级(≥90)在这套 12 张图卡上不可达,A 级(≥75)是现实最优。如果想让 S 可达,得把每张图的标签数降到 4~5 个,或者放宽简洁度的惩罚斜率。

四、核心实现:从一句话到三个分数

4.1 分词:先把句子切成词

中文没有空格,但产品上要求用户用空格分隔——这不是偷懒,是把分词问题交还给用户,同时让评分规则完全可预期。规则很薄:

/** 分词:按空格/逗号/顿号/句号/分号分隔,过滤单字与空串 */exportfunctiontokenize(text:string):string[]{constparts:string[]=text.split(/[\s,,、。;;]+/);constout:string[]=[];for(constpofparts){constw:string=p.trim();if(w.length>=2){// 单字词直接丢弃out.push(w);}}returnout;}

用户写「高高的雪山,很冷」→ 切出高高的雪山、很冷(「很」被过滤)。

4.2 同义词归一化:让「小狗」也能算命中

光靠精确匹配太苛刻。一张SYNONYMS表把常见口语说法映射到标签词:

exportconstSYNONYMS:Map<string,string>=newMap([['姜汤','姜茶'],['柠檬水','柠檬'],['茶杯','杯子'],['小狗','狗'],['犬','狗'],['猿','猴子'],['山峰','雪山'],['雪峰','雪山'],['高山','山峰'],['蓝天','蓝天'],['白云','云朵'],['明月','月亮'],['弹钢琴','钢琴'],['琴键','键盘'],['手','双手'],['夕阳','日落'],['落日','日落'],['大海','海洋'],['茶','茶杯'],['书','书本'],['阳光','晨光'],['花','花朵']]);

归一化在评分最前面做,用户无感知,但命中率明显变高——同义词表就是文搜图产品的召回增益。

4.3 三级匹配:精确 → 标签 → 包含

每个词依次走三级判断,命中即停:

for(constrawofwords){constnorm:string=normalize(raw);// 同义词归一化if(card.tags.indexOf(norm)>=0){// ① 归一化后精确命中feedbacks.push(newWordFeedback(raw,WordVerdict.HIT,norm));}elseif(card.tags.indexOf(raw)>=0){// ② 原词精确命中feedbacks.push(newWordFeedback(raw,WordVerdict.HIT,raw));}else{// ③ 包含匹配letcontained:boolean=false;for(consttagofcard.tags){if(raw.indexOf(tag)>=0&&tag.length>=2){feedbacks.push(newWordFeedback(raw,WordVerdict.SYN,tag));contained=true;break;// 一个词只算命中一个标签}}if(!contained){feedbacks.push(newWordFeedback(raw,WordVerdict.MISS,''));}}}

第三级的break值得说明:写「星空下的谷仓」这种复合词,只会命中第一个匹配到的标签(星空),不会重复计票。这是为了防止一个长词刷高覆盖度——但它也意味着复合词的覆盖率并不比单词高,写长句在这套模型里没有额外收益。

反馈因此分成三态:HIT(命中,青绿)、SYN(同义/包含命中,琥珀)、MISS(未命中,红)。

4.4 三维打分与未覆盖标签

constcoverage:number=card.tags.length>0?Math.round(hitTags.length/card.tags.length*100):0;consthitWords:number=feedbacks.filter((f:WordFeedback)=>f.verdict!==WordVerdict.MISS).length;constprecision:number=words.length>0?Math.round(hitWords/words.length*100):0;// ... 简洁度见 3.3constmissedTags:string[]=card.tags.filter((t:string)=>hitTags.indexOf(t)<0);

missedTags是教学功能的关键:它把「没写到什么」直接摊开给用户,UI 上就是一栏「还可以写到的角度」。文搜图产品里这恰恰是黑盒——你不知道为什么没搜到;这里反过来告诉你下次该补哪个词。

4.5 出题:种子随机 + Fisher-Yates

12 张图卡随机抽 6 张,用线性同庸发生器配 Fisher-Yates 洗牌:

exportclassSeededRandom{privatestate:number;constructor(seed:number){this.state=seed>0?seed:1;}next():number{this.state=(this.state*48271)%2147483647;returnthis.state/2147483647;}shuffle<T>(arr:T[]):T[]{constout:T[]=arr.slice();for(leti=out.length-1;i>0;i--){constj:number=this.int(i+1);consttmp:T=out[i];out[i]=out[j];out[j]=tmp;}returnout;}}

种子取Date.now() * 7919 % 2147483647——不可复现但每局不同。如果要做「同一局可复现」的复盘功能,把种子存进 Preferences 即可,类本身已经支持注入任意种子。

五、交互细节

5.1 逐词反馈芯片:让分数可解释

评分面板最下面是一排芯片,每个词一个,两行字:上面是词本身,下面是「命中·蓝天」/「未命中」。颜色三态对应三级匹配。这是整个产品最重要的 UI——分数只是结论,芯片才是教学。

5.2 三维进度条:分数可视化

覆盖/精准/简洁各一个小芯片 + 一条 4px 的 Linear Progress,颜色随分数跳档(≥60 青绿、≥30 琥珀、<30 红)。三栏并排,一眼看出短板在哪一维。

5.3 同轮重评:取最优,不惩罚试错

「提交评分」可以反复点,attempts计数、bestTotal记最优,输入框下实时显示「已尝试 N 次 · 本轮最优 M 分」。试错不扣分——这是教学产品该有的姿态,只有最终成绩单上的「尝试次数」会留下痕迹。

5.4 暗色书房风配色

GitHub 暗色系底#0D1117+ 面板#161B22+ 琥珀评分色#E8B44B+ 青绿命中色#3DDC97+ 红未命中#E5484D。三个语义色严格对应三种反馈状态,全应用不混用。

六、工程结构

describe-master/ ├── entry/src/main/ets/ │ ├── common/ │ │ ├── DescribeModels.ets # 12 张图卡 + 同义词表 + 分词 + 三级匹配 + 三维评分 + 种子随机 │ │ └── DescribeService.ets # 单例:轮次管理 / 提交评分 / 重试取优 / 结算评级 │ ├── pages/ │ │ └── Index.ets # 三页状态机(开始/写话/成绩单)+ 逐词反馈 + 三维进度条 │ └── entryability/ │ └── EntryAbility.ets └── entry/src/main/resources/rawfile/cards/ # 12 张演示图卡

全工程权限清单为空——这个应用连系统相册都不碰,图卡全部来自 rawfile。

七、验证

模拟器实测(Mate 60 Pro,API 26):

步骤结果
安装启动3 秒 894 毫秒(hvigor 全链路)
第 1 轮「云天」输入 8 词,覆盖 100 / 精准 100 / 简洁 30 →83 分
第 2 轮「星空下的谷仓」输入 8 词,同样83 分,1 次尝试即最优
第 5 轮「欧式穹顶」累计 166 分
成绩单D 级,166/600 分,六轮战绩逐轮列出

83 分两次复现,与 3.4 节推导的理论上限一致,评分模型行为符合预期。

八、总结与已知边界

三个维度各有分工:覆盖度管召回、精准度管排序、简洁度管表达效率——这三件事恰恰是任何文搜图引擎内部都在优化的目标,只是这里被翻译成了用户能看懂的语言。

需要如实说明的边界:

  • 单字标签不可命中。标签长度 <2 时,分词会先过滤掉单字词、包含匹配又要求标签长度 ≥2,导致该标签永远无法被覆盖(如柠檬姜茶的手、草地与狗的狗/坐)。这两张图的天花板因此被压到 80/79 分,且用户写「双手」「小狗」也救不回来。修法是把单字标签换成长度 ≥2 的同义词。
  • S 级当前不可达,原因见 3.4 节。
  • 图卡标题会剧透。左下角「目标:云天」等于直接告诉用户图里有什么,削弱了「看图写话」的训练意义。生产版本应改为可选提示或干脆去掉。
  • 未做进度持久化,退出应用后成绩清零。

最后一句话收尾:端侧 AI 不只是替用户干活,还可以反向教会用户怎么用它。不需要模型、不需要网络、不需要任何 Kit——一套三百行的评分算法,加上一栏「还可以写到的角度」,就够了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 7:27:18

ChatGPT 自定义指令与记忆入门:一次设置,少重复自我介绍

适合人群&#xff1a;已经会打开 ChatGPT 提问&#xff0c;但每次新对话都要重新交代“我是谁、文风怎样、不要做什么”的新手。网页端或 App 均可&#xff0c;免费与付费计划都可使用自定义指令&#xff1b;记忆相关开关可能因计划、地区与工作区设置略有差异。 X 上有作者指出…

作者头像 李华
网站建设 2026/10/21 5:53:16

小程序码生成与合成避坑指南:从扫码失败到98.6%识别率

1. 为什么小程序二维码不能直接“贴图就用”——从扫码失败率说起“小程序二维码生成图片&#xff0c;实现扫码跳转”&#xff0c;这行标题看着简单&#xff0c;但我在某高校实验室带学生做毕业设计时&#xff0c;连续三届团队都卡在这个环节上。不是生成不出来&#xff0c;而是…

作者头像 李华
网站建设 2026/10/21 16:04:18

Comtos Linux (朱雀) 道法自然

Comtos Linux (朱雀) 操作系统的愿景是&#xff0c;深耕于服务器和嵌入式领域。《易经》第81卦&#xff0c;既济卦所云&#xff1a; 既济卦&#xff1a;各安其位&#xff0c;各司其职。 万物在天道变化中各归其位、各守其性&#xff0c;找到自己该有的位置和职能&#xff0c;互…

作者头像 李华