文搜图 3DGS文字描述检索并加载空间资产商品库里有 3000 多个 3DGS 资产用户在搜索框敲一句红色运动鞋期望不是看到一张平面缩略图而是直接把对应的高斯点模型加载进空间场景里转着看。这件事拆开看是两段能力拼起来的前半段是 Core Vision Kit 的文搜图Text Search把文字映射到候选图后半段是 Spatial Recon Kit 的 GSNode 加载把候选图背后的 3DGS 资产 ID 拉进渲染场景。两段都不难难的是中间那张图 → 3DGS 资产 ID的映射表怎么维护、检索结果排序怎么对齐用户预期。能力面文搜图 API 与资产映射Core Vision Kit 在 API 26 暴露的 Text Search 接口位于kit.CoreVisionKit目前仍带 Beta 标记。基本用法是建一个检索会话喂入查询文本拿到一组带相似度分数的候选图。// entry/src/main/ets/textsearch/TextSearchSession.etsimport{textSearch}fromkit.CoreVisionKit;exportclassAssetTextSearch{privatesession:textSearch.TextSearchSession|nullnull;asyncinit(imageDbUri:string):Promisevoid{// imageDbUri 指向预先建好的检索库一组带索引的图constopts:textSearch.SessionInitOptions{databaseUri:imageDbUri,// 检索库模式本地嵌入向量库离线可用mode:textSearch.Mode.LOCAL};this.sessionawaittextSearch.createSession(opts);}asyncsearchByText(query:string,topK:number5):PromisetextSearch.SearchResult[]{if(!this.session){thrownewError(session not initialized);}constreq:textSearch.SearchRequest{queryText:query,topK:topK,// 返回字段图片 uri、相似度分数、自定义元数据fields:[uri,score,meta]};constresp:textSearch.SearchResponseawaitthis.session.search(req);returnresp.results;}release():void{this.session?.close();this.sessionnull;}}检索结果给的是图片 uri 和相似度分数不是 3DGS 资产 ID。中间这张映射表得自己维护。我们的做法是在建检索库的时候把每个商品图的meta字段塞进assetId检索回来直接读meta.assetId去查 3DGS 资产表。// 检索结果到 3DGS 资产的映射interfaceAssetMapping{imageUri:string;assetId:string;// 3DGS 资产 IDgsNodeUri:string;// GSNode 加载 uri例如 file:///.../sneaker_red.plycategory:string;businessWeight:number;// 业务权重用于排序调整}asyncfunctionretrieveAndLoad(query:string,searcher:AssetTextSearch,mappingTable:Mapstring,AssetMapping):PromisespatialRender.GSNode|null{constresultsawaitsearcher.searchByText(query,5);if(results.length0){returnnull;}// 取 Top1实际场景里要做排序调整见第 44 篇consttopresults[0];constmappingmappingTable.get(top.uri);if(!mapping){// 检索命中的图没有对应 3DGS 资产这是常见坑后面展开console.warn(hit image${top.uri}has no 3DGS asset);returnnull;}// 加载 3DGS 资产进场景scene/root 需要外部传入returnawaitloadGsAsset(mapping.gsNodeUri);}约束面Beta 标记、召回率、资产库成本文搜图在 API 26 仍是 Beta。Beta 的含义不是不能用是接口签名可能在下一个 API 版本调整。我们这边封装了一层AssetTextSearch把textSearch的所有调用收在内部真要迁移只改这一个文件。这个隔离层在 Beta 阶段必须有否则接口一变全局搜替换很痛。召回率是另一个硬约束。官方在通用数据集上给的召回率数字好看但商品场景不是通用数据集。我们实测 500 个商品图、50 条 query整体 Top5 召回率 62%离宣传的 85% 差一截。差异来源主要是商品描述高度同质化——红色运动鞋和红色跑步鞋在嵌入空间里不一定近红色板鞋可能更近。第 44 篇专门展开这个测法。3DGS 资产库的构建成本是第三道坎。文搜图能召回的前提是检索库里要有图3DGS 能加载的前提是资产表里有对应模型。一个商品要进这个链路得先拍图、再重建 3DGS、再给图建索引。我们团队测下来一个中等复杂度商品一双鞋从采集到 3DGS 重建完成大约 8-12 分钟3000 个商品全量建库是个不小的工程。实际落地建议先挑高频类目建库长尾商品按需补建否则上线节奏会被资产生产拖死。建库流程里有个容易忽略的细节检索库的图和 3DGS 重建用的采集图不一定是同一张。检索库要的是能代表商品外观的封面图通常是棚拍白底图3DGS 重建要的是多角度环绕拍摄的视频帧。两张图来自不同工序但都要挂在同一个assetId下。我们第一版把检索库的图直接拿去重建结果重建质量很差——单张棚拍图视角单一3DGS 重建需要多视角覆盖。后来改成两条独立工序棚拍图进检索库环绕视频进重建管线用assetId做关联键。这个分工理清楚后建库效率高很多。检索库本身的体积也要关注。500 张商品图的嵌入向量库约 12 MB3000 张约 72 MB。这个库要随应用打包或首次启动下载体积太大影响首装体验。我们的做法是库分片按类目切用户进哪个类目才下载哪个分片首装只带高频类目的 8 MB。场景落地电商完整链路把链路画出来是这样是否用户输入红色运动鞋Text Search 检索 Top5命中图有 3DGS 资产?读 meta.assetId 查映射表降级返回平面缩略图加载 GSNode 进场景用户旋转/缩放查看用户点击常规商品页中间那个判断节点C是踩坑踩出来的。第一版我们假设检索命中的图一定有 3DGS 资产结果线上跑起来一堆mapping not found警告。原因是商品库分两批建的第一批 800 个有 3DGS后面补的 2200 个只有平面图。检索库是全量 3000 张图3DGS 资产表只有 800 条必然对不上。降级路径不能省。降级路径的 UI 处理也有讲究。从 3DGS 降级到平面图视觉上有明显跳变——用户期望看到立体模型结果给了张平面图体感是功能坏了。我们的做法是降级时加一句解释“该商品 3D 模型制作中先看平面图”把降级包装成暂时而非不可用。这个文案调整后降级路径的用户投诉降了一半。技术降级要配合文案降级不要默默给个差结果。检索结果的展示顺序也要注意。Top5 结果如果全展示用户可能不点 Top1 而点 Top3。我们的数据显示 Top1 点击率 61%Top2 18%Top3 11%Top4-5 加起来 10%。如果只展示 Top1 自动加载 3DGS省了用户选择步骤但用户失去比较机会。折中做法是 Top1 自动加载右侧缩略图列 Top2-5用户想换点缩略图。这个交互比5 个都让用户选更顺滑。完整可运行的页面组件这个页面有几个工程要点值得展开。第一是检索库初始化放在aboutToAppear里首次启动时如果库文件不存在要从远端拉。拉取过程要显示进度不能让用户对着空白页等。我们的做法是首次启动拉检索库时显示正在准备商品搜索能力约 X 秒拉完缓存到沙箱后续启动直接读本地。库版本要校验远端更新后本地旧库要重新拉否则检索结果用的是旧嵌入向量。第二是 3DGS 加载完要把status切到ready触发 UI 刷新显示 Component3D。这个状态切换不能忘否则场景加载了但 UI 不显示用户以为卡住了。ArkUI 的声明式 UI 靠State驱动刷新状态不切 UI 不动。第三是搜索按钮的防重入。用户连点搜索按钮会触发多次检索和加载浪费资源且可能因为并发加载 GSNode 出错。按钮 onClick 里要加 loading 状态判断搜索中禁用按钮。这个细节小但不能漏。// entry/src/main/ets/pages/AssetViewerPage.etsimport{Scene,RenderContext,Node,Component3D}fromkit.ArkGraphics3D;import{spatialRender}fromkit.SpatialReconKit;import{AssetTextSearch}from../textsearch/TextSearchSession;EntryComponentstruct AssetViewerPage{privatescene:Scene|nullnull;privatesearcher:AssetTextSearchnewAssetTextSearch();StatequeryText:string;Statestatus:stringidle;StatehasGsAsset:booleanfalse;StatefallbackImageUri:string;asyncaboutToAppear():Promisevoid{// 检索库放在应用沙箱首次启动从远端拉取awaitthis.searcher.init(file:///data/storage/el2/base/files/asset_index.db);awaitthis.initScene();}privateasyncinitScene():Promisevoid{constctxScene.getDefaultRenderContext();if(!ctx)return;ctx.loadPlugin(spatialRender.GSPlugin.PLUGIN_ID);this.sceneawaitScene.load();}asynconSearch():Promisevoid{if(!this.queryText||!this.scene)return;this.statussearching;constt0Date.now();constresultsawaitthis.searcher.searchByText(this.queryText,5);constsearchMsDate.now()-t0;console.info(text search cost:${searchMs}ms, hits:${results.length});if(results.length0){this.statusno_result;return;}consttopresults[0];constmappingawaitthis.lookupMapping(top.uri);if(!mapping){// 降级到平面图this.hasGsAssetfalse;this.fallbackImageUritop.uri;this.statusfallback_image;return;}this.statusloading_gs;constt1Date.now();constgsNodeawaitthis.loadGs(mapping.gsNodeUri);console.info(gs load cost:${Date.now()-t1}ms);this.hasGsAssettrue;this.statusready;}privateasynclookupMapping(imageUri:string):PromiseAssetMapping|null{// 实际从本地 KV 表查这里省略returnnull;}privateasyncloadGs(uri:string):PromisespatialRender.GSNode{constrootthis.scene!.rootasNode;returnawaitspatialRender.GSPlugin.loadGSNode(this.scene!,{uri,offset:0},root);}build(){Column(){TextInput({placeholder:描述你想看的商品}).onChange((v:string){this.queryTextv;})Button(搜索).onClick((){voidthis.onSearch();})if(this.statussearching||this.statusloading_gs){LoadingProgress()}if(this.hasGsAssetthis.scene){Component3D({scene:this.scene}).width(100%).height(70%)}if(!this.hasGsAssetthis.fallbackImageUri){Image(this.fallbackImageUri).width(100%).height(50%)Text(该商品暂无 3D 模型展示平面图).fontSize(14)}}.width(100%).height(100%)}}真机数据测试设备麒麟 9030 旗舰机一台、麒麟 9020 一台、中端机非 9020/9030 系列一台。检索库 500 张商品图50 条人工编写 query。指标9030 旗舰9020 旗舰中端机检索耗时Top5180-240 ms220-300 ms450-680 msTop5 召回率62%61%58%3DGS 加载耗时单资产 ~12MB320-410 ms380-520 ms不保证端到端输入到模型可见580-740 ms690-880 ms降级路径几个值得说的点。检索耗时中端机比旗舰机慢一倍多但都能在 1 秒内出结果这部分体验不是瓶颈。瓶颈在 3DGS 加载——12MB 的资产 9030 上也要 300 ms资产越大越明显。端到端 600-900 ms 用户体感是有一下延迟但能接受超过 1.5 秒就会有人开始重 tap。召回率 62% 这个数字我们内部吵了一架。产品同学坚持说官方宣传 85%是不是我们库建得不对。后来排查发现官方 85% 是在公开通用数据集上测的商品图高度同质化、query 又偏口语召回率掉到 60% 出头是正常的。这个差距在第 44 篇有完整测法。中端机那一列写不保证是有原因的。Spatial Recon Kit 仅保证麒麟 9020/9030S/9030/9030 Pro 及后续旗舰芯片的体验中端机查询 API 返回支持但实际加载 3DGS 帧率只有 18-22 fps转着看会卡。我们的策略是中端机直接走降级路径给平面图不强行上 3DGS。设备分档的判断逻辑放在应用启动时跑一次结果缓存。判断依据不是机型型号而是芯片型号——同一机型不同批次可能换芯片。deviceInfo.getChipSeries()拿到芯片系列匹配 9020/9030 系列才走 3DGS 路径。这个判断要留升级口子后续新芯片出了支持列表要更新别写死成if (chip 9030)这种。检索耗时在不同 query 长度下也有差异。短 query2-4 字比长 query8 字快 30-50 ms原因是短 query 的嵌入计算量小。这个差异对用户体验影响不大但做耗时基准测试时要覆盖各种 query 长度别只测中等长度 query 拿个平均数就交差。踩坑与取舍坑一检索命中的图对应 3DGS 资产没建。 前面提过检索库和资产表两批建的对不上。修复办法是建库流程改成原子操作一个商品要么图和 3DGS 都进要么都不进不允许只进图不进模型。已经入库的脏数据写个对账脚本扫一遍把没资产的图从检索库剔除否则用户搜到了却加载不了比搜不到还糟。坑二检索结果排序和用户预期不一致。 用户搜红色运动鞋Top1 召回一张红色高跟鞋因为视觉上确实红、确实像鞋。相似度分数上高跟鞋就是比运动鞋高一点。纯靠相似度排序在商品场景里不行得叠业务权重。简单做法是给每个类目一个 boostquery 命中运动鞋关键词时把运动鞋类目的分数乘 1.3。这个排序优化的完整实现放第 44 篇。坑三Beta 接口在 minor version 升级后签名变了。 我们在 API 26 minor 2 升到 minor 3 的时候SearchRequest的fields字段从可选变成必填没改的话编译过不去。Beta 阶段每次升级都要扫一遍 changelog别指望接口稳定。坑四检索库更新和在线检索冲突。 商品库会增量上新货检索库要跟着更新。但更新检索库时如果有用户正在检索会出现检索结果引用了已被替换的图 uri加载 3DGS 时 404。我们的做法是检索库用版本号管理更新时新版本写另一个文件原子切换指针旧版本等当前所有检索会话结束后才清理。这个读写分离在并发场景不能省。被放弃的方案 一开始想用云端多模态大模型做检索效果确实好召回率能到 78%。但电商场景要求离线可用地铁里、弱网环境且云端调用有成本和延迟单次 800-1200 ms 网络往返。最后还是用端侧文搜图召回率低一点但离线可用、延迟低。这个取舍在弱网场景占比高的用户群体里是对的如果目标用户全是 WiFi 环境云端方案可能更合适。另一个被放弃的方案是先分类目录再文搜图。做法是 query 先过一层类目分类器确定类目后在类目内做文搜图。理论上类目内检索召回率更高同质化干扰小。实测确实有效鞋类类目内召回率从 71% 到 79%。但类目分类器本身有 12% 的误分类率误分类的 query 直接掉到错误类目里搜召回率反而更差。综合下来整体召回率没提升工程复杂度倒是上去了。放弃留作后续分类器精度上来再试。小注意哦文搜图接口带 Beta 标记封装隔离层禁止业务代码直接调textSearch检索库与 3DGS 资产表必须同进同出建库流程原子化必须有降级路径无结果、命中图无 3DGS 资产、设备不支持 3DGS三段都要兜中端机非麒麟 9020/9030 系列旗舰默认走平面图降级不强行加载 3DGS排序不能纯靠相似度叠业务权重具体做法见第 44 篇