
做AR图像识别我最怕听到的一句话就是“图库是动态的用户拍完照就要能识别”。做Unity AR开发的都懂AR Tracked Image要识别图片常规思路是提前把图片塞进Reference Image Library构建时打包进App。一旦图库需要变动就得重新出包、重新审核、用户重新下载这一套流程走下来黄花菜都凉了。但现实中“动态添加相片到图像库”恰恰是AR互动类应用、扫码识别类工具最常见的需求。我踩过这个坑也研究过一段时间今天直接把我自己实践过的完整方案整理出来。文章会先说清楚AR Tracked Image底层是怎么识别图像的再讲为什么动态添加在Unity里这么别扭最后给出一个能跑通的实现流程附上踩坑记录和排查思路。不管你是第一次碰AR Foundation还是已经在图像识别里折腾过一阵子这篇都值得看下去。1. 整体方案与核心思路拆解1.1 为什么用AR Tracked Image而不是自己写图像匹配算法不少人一听“图像识别”第一反应是上OpenCV、上深度学习模型。但对移动端的AR场景来说这往往是把简单问题复杂化了。如果你需要识别的对象是人脸、猫狗、建筑这种“语义类”目标那确实得上模型但如果你要识别的是特定的一张海报、一张名片、一张产品说明书本质上那是“这张图长什么样”的问题属于特征点匹配的范畴。AR Foundation里的AR Tracked Image对应底层ARCore的AugmentedImage和ARKit的ARImageAnchor做的恰恰是这件事。它会在每一帧相机画面里提取特征点和你提供的参考图像库里的图片做比对一旦匹配上就会给出一个持续追踪的坐标、旋转和比例信息。整个过程是平台原生能力跑在底层视觉处理流程里对开发者来说是黑盒但它快、准、稳而且不占多少CPU。这就是方案选型的第一个核心逻辑能用平台原生能力解决的就别自己造轮子。1.2 “动态添加”为什么在Unity里是个绕不过的坎AR Foundation本身的设计思路很明确参考图像库是构建期的静态资源。你在Assets目录里创建Reference Image Library把图片拖进去设置物理尺寸然后这个库会跟着App打包。运行时你拿不到“往这个库里面再塞一张图”的现成接口。这是因为底层ARCore和ARKit的库结构一旦在原生会话Session启动时配置好图像数据就已经上传到了视觉引擎里。你手册里看到的所有“Add”相关接口大多是在Editor时用的编辑器API或者需要在Session还没启动时重新配置。但需求方不会管这些。他们会说你们不是图像识别吗那用户从相册传一张照片上来你识别一下不就行了所以我们必须用一个工程上绕得过、行为上稳定可控的方案把“构建期静态库”转化为“运行期动态库”。这就要说到核心的数据结构了。1.3 核心数据结构ARReferenceImage和Runtime Reference Image LibraryAR Foundation里每一张参考图本质是一个ARReferenceImage对象它包含三个关键信息图像纹理Texture2D、物理宽度物理尺寸用于估算距离和摆放虚拟内容、以及一个用于识别的名字。它本身不负责“识别”负责识别的是XRReferenceImageLibrary你可以把它理解成一张参考图的花名册所有的ARReferenceImage都挂在这个花名册下面。AR Foundation 5及以上版本提供了MutableRuntimeReferenceImageLibrary这才是动态方案的关键。它允许你在运行时往里面ScheduleAddImageJob异步加入新的参考图像而且这个操作是线程安全的不会和正在进行的图像识别流程冲突。这就是全部的理论基础了。接下来看看我在工程里是怎么落地这一套的。2. 环境准备与静态图像识别的完整复现路径2.1 Unity版本、AR Foundation组件和平台差异先交代一下我的实验环境方便你对号入座Unity 2022.3 LTS6.x版本的AR Foundation也同样适用API基本没变AR Foundation 5.1.xARCore XR PluginAndroid端测试设备为Pixel系列某机型、某款主流国产旗舰均为Android 10及以上如果你用的还是Unity 2020搭配AR Foundation 3.x的老项目建议先升级。动态添加图像这个能力在4.0之后才逐渐成熟老版本对MutableRuntimeReferenceImageLibrary的支持不完整强行用容易出现莫名其妙的崩溃。另外iOS端ARKit在iOS 11.3以上才支持图像识别iOS 13以上才支持运行时添加参考图像整体逻辑类似但API的线程模型略有差异后文会提到。2.2 从0搭建一个可运行的静态识别Demo在折腾动态方案之前建议先把静态识别的完整链路跑通因为动态方案在实现上是对静态方案的扩展链路不通后面排查问题会痛不欲生。创建一个新项目时Unity会默认用内置渲染管线。AR Foundation的VideoTexture在URP下需要额外配置渲染器注入非必需但强烈建议直接用URP模板后面能给摄像头背景省一堆事。你需要在场景里挂以下组件才能让AR跑起来AR Session负责初始化底层AR会话生命周期控制在这里AR Session OriginAR Foundation 5.x后叫XROrigin负责把虚拟内容锚定到现实世界坐标AR Tracked Image Manager负责订阅底层图像识别事件管理识别到的图像的GameObject生命周期AR Tracked Image Manager组件的Inspector里有一个Reference Image Library的槽位静态方案中你只需要在这里拖入一个创建好的Reference Image Library资源即可。此时在Android真机上运行摄像头对准参考图虚拟内容就会出现在图片正上方。2.3 参考图像库创建的三个关键约束这里必须停下来聊一聊参考图的质量约束因为它直接决定识别率和稳定性也是后面动态添加最容易踩坑的地方。第一图片不能太小。在ARCore上参考图像推荐的最小分辨率是300x300像素实际体验下来低于这个尺寸的图在远距离时基本识别不到。第二图片纹理要足够丰富。纯色、大面积留空白、极低对比度的图片特征点提取不出来识别率会断崖式下跌。第三物理尺寸的设定要合理。这里的物理宽度是指图片在现实中的宽度单位是米。如果你设定的是0.05米5厘米那AR引擎会认为这个图特别小可能只在很近的距离才能识别到如果你设定的是1米那在很远的距离引擎就会尝试匹配但虚拟内容的大小比例也会跟着被放大。经验值是商用的海报设0.3到0.5米名片、小卡片设0.08到0.12米。还有一个容易忽略的细节参考图像如果带透明通道在部分机型上会出现识别异常建议把所有参考图统一转成JPG格式再入库。3. 运行时动态添加相片到图像库的完整实现3.1 动态添加前必须先处理一个验证坑直接告诉你结论Unity 6之前运行时会校验XRReferenceImageLibrary的纹理有效性动态从相册加载的Texture2D如果没做处理添加到库后往往是坏的。我在初版实现里遇到了一个诡异的现象动态添加的参考图识别成功率几乎为零但静态库里同样的图却能秒识别。后来检查发现问题出在纹理的采样方向TextureWrapMode和mipmap设置上。底层在提取特征点时会访问纹理各级别数据如果纹理没有正确的mipmap或者采样方向为Repeat都会导致特征点提取崩溃或者返回空结果。所以在动态添加前纹理需要做一次标准化处理用代码统一设置为Clamp模式、生成完整mipmap链、关闭sRGB的自动校正这个根据你的图片实际色彩空间来定一般默认开就行。3.2 动态添加图像库的核心实现代码下面这段是我的实现核心我把它封装成一个静态类方便任何地方调用。using System; using System.Threading.Tasks; using Unity.Collections; using UnityEngine; using UnityEngine.XR.ARFoundation; using UnityEngine.XR.ARSubsystems; public static class RuntimeImageLibraryHelper { /// summary /// 将一张Texture2D动态添加到当前AR Tracked Image Manager的参考图库中。 /// 返回添加后的跟踪ID后续可用于移除或更新。 /// /summary public static async Taskint AddImageToLibraryAsync( ARTrackedImageManager manager, Texture2D texture, float physicalWidthMeters, string imageName) { if (manager null || texture null) { Debug.LogError([RuntimeImageLibrary] 参数为空无法添加参考图像。); return -1; } var library manager.referenceLibrary as MutableRuntimeReferenceImageLibrary; if (library null) { // 如果当前库不是可变的则需要创建一个新的可变库并赋值给manager library manager.CreateRuntimeLibrary(); manager.referenceLibrary library; Debug.Log([RuntimeImageLibrary] 已创建新的可变参考图像库。); } // 纹理预处理确保格式、采样方向和mipmap都符合底层特征提取的需求 var processedTex PreprocessTexture(texture); if (processedTex null) { Debug.LogError([RuntimeImageLibrary] 纹理预处理失败。); return -1; } try { var jobHandle library.ScheduleAddImageJob( processedTex, imageName, physicalWidthMeters); // 等待Job完成然后释放原生纹理数据 while (!jobHandle.IsCompleted) { await Task.Yield(); } jobHandle.Complete(); Debug.Log($[RuntimeImageLibrary] 图像 {imageName} 已添加到图像库。); return 0; } catch (Exception e) { Debug.LogError($[RuntimeImageLibrary] 添加图像失败: {e.Message}); return -1; } } private static Texture2D PreprocessTexture(Texture2D source) { if (source null) return null; // 复制一份避免影响原图数据 var copy new Texture2D(source.width, source.height, TextureFormat.RGBA32, true); copy.SetPixels32(source.GetPixels32()); copy.Apply(true, true); // WrapMode必须是Clamp否则底层特征提取在纹理边界处会出错 copy.wrapMode TextureWrapMode.Clamp; // 确保mipmap完整 if (!copy.isReadable) { Debug.LogError([RuntimeImageLibrary] 纹理不可读请确保纹理导入设置为Read/Write Enabled。); return null; } return copy; } }3.3 为什么需要这个Textrue预处理不处理会怎样刚才代码里的PreprocessTexture不是可有可无的故弄玄虚。我一开始图省事拿到相册图片直接ScheduleAddImageJob结果在Pixel设备上直接抛了InvalidOperationException: Cannot schedule add image job because the image is not valid。后来去翻源码才搞清楚底层做特征点提取时把纹理数据传给了C层的视觉引擎那个引擎要求纹理的每个像素都能随机访问而mipmap链不完整或者WrapMode不是Clamp时引擎会认为纹理内存布局不安全直接拒绝调度。那个Apply(true, true)里第二参数对应makeNoLongerReadable传true会把纹理从CPU可读状态转成GPU状态但因为我们之前在SetPixels32时已经把像素数据拷进副本了所以这一步不会丢数据反而能让底层更顺畅地访问。这也是很多人容易漏掉的地方。3.4 AR Tracked Image Manager的事件订阅与状态挂载动态添加图像只是第一步图像识别之后如何处理“识别到了”这个事件才是AR体验的核心。public class TrackedImageHandler : MonoBehaviour { [SerializeField] private ARTrackedImageManager trackedImageManager; [SerializeField] private GameObject virtualContentPrefab; private readonly Dictionarystring, GameObject _spawnedObjects new Dictionarystring, GameObject(); private void OnEnable() { trackedImageManager.trackedImagesChanged OnTrackedImagesChanged; } private void OnDisable() { trackedImageManager.trackedImagesChanged - OnTrackedImagesChanged; } private void OnTrackedImagesChanged(ARTrackedImagesChangedEventArgs eventArgs) { foreach (var trackedImage in eventArgs.added) { HandleTrackedImage(trackedImage); } foreach (var trackedImage in eventArgs.updated) { UpdateTrackedImage(trackedImage); } foreach (var trackedImage in eventArgs.removed) { RemoveTrackedImage(trackedImage); } } private void HandleTrackedImage(ARTrackedImage trackedImage) { if (_spawnedObjects.ContainsKey(trackedImage.referenceImage.name)) return; var content Instantiate(virtualContentPrefab, trackedImage.transform); content.transform.localPosition Vector3.zero; _spawnedObjects[trackedImage.referenceImage.name] content; Debug.Log($[TrackedImageHandler] 识别到图像: {trackedImage.referenceImage.name}); } private void UpdateTrackedImage(ARTrackedImage trackedImage) { if (_spawnedObjects.TryGetValue(trackedImage.referenceImage.name, out var content)) { content.transform.position trackedImage.transform.position; content.transform.rotation trackedImage.transform.rotation; } } private void RemoveTrackedImage(ARTrackedImage trackedImage) { if (_spawnedObjects.TryGetValue(trackedImage.referenceImage.name, out var content)) { Destroy(content); _spawnedObjects.Remove(trackedImage.referenceImage.name); } } }这里有个细节值得注意added事件里拿到的trackedImage.referenceImage.name就是你在动态添加时传入的imageName。这个名称是运行时唯一的标识不要在添加时用空字符串不然后面想管理虚拟内容的增删改查会非常痛苦。4. 从相册/相机获取图片并完成动态添加的完整链路4.1 相册图片加载Texture2D的坑与处理动态添加的图片来源现实中九成是相册或相机拍摄。Unity里读取相册需要调用原生相册访问接口简单的方式是使用NativeGallery插件或自己封装Android的ActivityResult。我这里同样给了完整方案毕竟只讲AR部分不讲图片来源这个动态就是空中楼阁。下面是读取相册图片并转换成Texture2D的通用流程public static async TaskTexture2D LoadImageFromGalleryAsync(string path) { if (string.IsNullOrEmpty(path)) return null; // 从文件读取字节然后创建Texture2D var bytes await File.ReadAllBytesAsync(path); var tex new Texture2D(2, 2, TextureFormat.RGBA32, true); // LoadImage会自动根据文件头解析JPG/PNG格式 if (!tex.LoadImage(bytes, false)) { Debug.LogError([ImageLoader] LoadImage失败无法解析图像文件。); return null; } return tex; }注意LoadImage的第二个参数markNonReadable传false可以保持纹理可读这个操作随后我们要做预处理时很有用。如果传true纹理变成非可读状态后面的GetPixels32会直接抛出异常。这一点我坑过一次。另外如果你是从相机直接拍一张流程是调起系统相机应用拿到返回的照片路径再走同样的LoadImage逻辑。这就不展开了注意一点某些国产机型在相机返回低分辨率缩略图的问题建议在相机调用时明确指定高分辨率输出缩略图用于图像识别的话成功率很低。4.2 图片分辨率与纹理压缩格式的适配从系统相册读出来的图片分辨率是设备的摄像头真实分辨率普遍在1200万像素以上。直接拿这种超大纹理去ScheduleAddImageJob会出现两个问题底层特征点提取的时间会变长首次识别时卡顿明显有些平台的视觉引擎对纹理尺寸设了上限超过4096x4096时会直接拒绝所以我在添加前还会做一次分辨率压缩统一到最大1024像素的长边。方式很简单用Texture2D的Resize方法压缩或者创建一个RenderTexture做Graphics.Blit再反读回来。具体代码就不放了网上一搜一大把。关于压缩后识别率的经验不是分辨率越高识别率就越高。特征点匹配讲究的是特征密度一块内容在1024下和2048下只要内容不糊特征点数量差距不大但2048在低端机型上的耗时是1024的三四倍。1024是性价比最高的档位。4.3 完整的“拍照→预处理→动态添加→识别”流程串联把前面的模块串起来整套流程的逻辑大概是用户点击按钮“添加识别图片”原生相册选择器弹出用户选一张图或直接拍照图片加载为Texture2D对Texture2D做标准化预处理mipmap、WrapMode、分辨率对齐调用RuntimeImageLibraryHelper.AddImageToLibraryAsync把图片加入运行时库加入成功的回调里可以提示用户“可以开始识别了”用户将相机对准图片ARTrackedImageManager触发trackedImagesChanged事件虚拟内容被实例化这条链路每一步都有明确的验证点第4步验证Texture2D是否满足可读要求第5步验证添加Job是否完成第6步验证库中图像数量是否增加。把验证点拆出来出问题时定位就快。注意动态添加的图像在App重启后会丢失。如果业务上需要持久化要把图片信息存到本地数据库或文件系统App启动时重新加载并添加。4.4 如何验证动态添加是否成功很多人动态添加后相机对着图一直没反应第一反应是怪识别算法不行其实链路早就断在添加这步了。教你两个验证手段第一添加后立刻读取库里的图像数量Debug.Log($[RuntimeImageLibrary] 当前库图像数量: {trackedImageManager.referenceLibrary.count});count如果没加1说明添加Job根本没进入调度队列去排查纹理纹理预处理。第二把添加的图单独存一张出来看中。可以用EncodeToPNG保存到沙盒目录检查生成的图是否出现花屏、黑边、全黑等情况。黑白图或者像素错乱问题九成出在纹理格式上如sRGB开关不一致。5. 实际项目中的踩坑记录与排查速查表5.1 踩坑实录一添加图片后一切正常但总是识别不到新图这个问题困扰了我整整两个下午。动态添加当时提示成功库数量也正确增加相机对准目标图就是没反应。后来把静态库里的一张图和动态添加的同一张图放在一起对比发现静态库的能识别、动态的不能识别。最后发现是物理宽度设置了问题。动态添加时我传的是0.1米10厘米但那张图实际打印出来是A4纸大小物理宽度约0.21米。引擎根据我给的物理尺寸估计了距离导致它会在错误的距离尺度上去寻找匹配特征。把物理尺寸传准确后识别率立刻拉满。这里给一个建议从相册选取的图片你根本不知道它在现实世界中的尺寸。产品层面上必须让用户输入“图片宽度”或者预设几个选项名片/海报/A4纸。没有一个准确的物理尺寸AR图像的姿态估算和识别距离就都是错的这是动态添加方案里最需要产品设计配合的一个点。5.2 踩坑实录二Android部分机型添加后第一次识别总失败某些Android设备上新添加的图像第一次出现在画面里时识别结果总是TrackingState.None要来回移动相机好几次之后才切换到TrackingState.Tracking。这其实是ARCore视觉引擎的预热问题。它对新添加图像的索引建立不是即时的需要几帧到几十帧的异步处理。解决方案有两个方向添加图片后给一个小小的过渡UI提示让用户保持相机不动2秒给引擎建立索引的时间在添加完成后强制让AR Tracked Image Manager重新扫描一遍当前帧调用trackedImageManager.enabled false; trackedImageManager.enabled true;简单粗暴但有效我实际项目里用的是第二种效果比较稳定。但这种做法在iOS端没验证过ARKit对运行时图像库的支持和ARCore在内部机制上有差别建议在目标平台实测。5.3 踩坑实录三相机权限与相册权限的耦合问题动态添加图片跑在真机上必然涉及权限申请。Unity的Android权限系统在Mainifest里配置相册读取权限和相机权限是两个独立权限。我遇到的情况是项目里只申请了相机权限相册读取时使用了某个第三方相册插件插件内部又动态申请了存储权限这段动态申请在Android 13及以上系统上弹窗会被系统拦截导致用户点了“选择图片”后没有任何反应。Android 13之后的推荐做法是在Mainifest里显式声明READ_MEDIA_IMAGES权限并配合Permission.RequestUserPermission提前申请。相册读取和相机使用最好在同一个权限引导页里完成避免二段式授权带来的用户流失。5.4 常见问题排查速查表现象可能原因排查方向添加后库count没变化纹理不可读或格式不对检查Texture2D的isReadable是否为trueWrapMode是否为Clamp动态添加的图识别不到物理宽度设置不准确认用户提供的物理尺寸和实际打印尺寸一致首次识别总是失败或慢图像索引尚未建立完成添加后保持相机静止约2秒重试或重启TrackedImageManager添加过程直接崩App超大纹理或格式不受支持在调度前用代码压缩长边到1024并转为RGBA32iOS上动态添加API找不到版本兼容问题确认iOS 13以上且AR Foundation版本在4.0以上识别出的虚拟内容漂移严重参考图特征太稀疏让用户选纹理丰富的图片避免大面积纯色区域权限申请后仍无法读取相册Android 13存储权限模型变化在Manifest中声明READ_MEDIA_IMAGES替换旧存储权限图片旋转方向不对EXIF信息未处理LoadImage后根据EXIF旋转纹理或调用平台接口返回方向5.5 性能优化与用户体验建议动态图像库在运行时不断膨胀会带来一个明显的隐患参考图越多每一帧的特征比对耗时越长。我在一个项目里加到20张图时帧率从60帧掉到了47帧识别延迟也能明显感觉到。这不是AR引擎变笨了而是每一帧都要把这20张图的特征和新帧做匹配计算量自然上去。如果业务上允许做一个“图库淘汰”机制。比如最多只保留最近10张超出时用MutableRuntimeReferenceImageLibrary的移除接口删掉最旧的一张。如果框架不支持运行时移除那就在内存里额外维护一个“活跃图”列表只在需要时把对应的图像重新添加到库中。用户交互层面还有一个小技巧添加过程不管多顺利都建议给一段过渡动画或进度提示。底层的特征提取Job耗时通常在几百毫秒到2秒之间不做等待提示的话用户会疯狂点击添加按钮结果加了多张重复的图库倒是大了识别效果反而更差。6. 稳定复现的完整Demo项目结构6.1 项目脚本目录与核心类职责为了让还没动手的读者能少走弯路我把这套方案在测试项目里的完整目录结构贴出来你照着搭几乎不太会翻车Assets/ ├── Scripts/ │ ├── RuntimeImageLibraryHelper.cs // 动态添加图像的核心工具类 │ ├── TrackedImageHandler.cs // 识别事件处理与虚拟内容挂载 │ ├── ImageLoader.cs // 从相册/相机读取纹理 │ ├── ImagePreprocessor.cs // 纹理标准化mipmap、分辨率、WrapMode │ └── MainMenuController.cs // 界面控制添加按钮、状态展示 ├── Prefabs/ │ ├── AR Session.prefab │ ├── XR Origin.prefab │ └── VirtualContent.prefab // 识别后挂在图像上的3D内容 ├── Art/ │ ├── SampleReference.jpg // 测试用参考图 │ └── SampleReference2.jpg └── Scenes/ └── Main.unity核心职责分离得很干净ImageLoader只负责“把图片变成Texture2D”ImagePreprocessor只负责“把Texture2D变成能喂给视觉引擎的Texture2D”RuntimeImageLibraryHelper只负责“把处理好的纹理注册进参考图库”TrackedImageHandler只负责“识别到图了怎么表现”。任何一环出问题直接看对应脚本的日志即可定位。6.2 事件中心与状态管理的扩展思路如果你要在几个不同的业务场景里复用这套能力建议把“图像识别”和“业务表现”解耦。我的做法是建立一个ImageRecognizedEvent中心TrackedImageHandler只管往事件中心发消息具体的UI展示、虚拟内容逻辑都订阅这个事件。这样同一套识别能力换个界面就能复用。其实还有一个更进阶的方向支持同时追踪多张不同的图片并且每张图片对应不同的虚拟内容。这个需求在当前方案里天然支持AR Tracked Image Manager本来就会对每一张识别到的图触发一次事件你只需要在HandleTrackedImage里根据referenceImage.name去查表找对应Prefab即可。6.3 与后端服务联动的架构预案动态添加的能力一旦打通整个AR玩法就能和后端服务联动起来。比如运营后台上传了一张新海报图App下拉刷新就能拿到新的图片列表逐个调用AddImageToLibraryAsync做热更新。这是我目前认为动态图像库最有价值的落地场景识别图库不再是App的死数据而已变成一套可以远程运营的“视觉触发器”。实际联动的链路也很简单后端下发图片URL加物理尺寸客户端下载图片文件转Texture2D预处理动态添加。唯一要注意的是后端要保证图片本身满足特征点提取的要求上传时做一次基础质检比如最少分辨率、纹理丰富度打分否则运营图上来了识别不到体验会打折扣。下载纹理的代码不复杂注意内存回收即可。用UnityWebRequestTexture加载完用完要Destroy中间生成的Texture2D不然图库更新几十次之后内存会涨到不可接受。7. 写在最后经验沉淀与后续可扩展方向我在实际项目中踩过的最大一个坑是没有第一时间建立“验证点”思路总是在全链路串起来之后才去测试结果一个问题叠一个问题排查起来异常痛苦。动态添加图像这个功能每一段链路都有独立的验证方法按1.3节说的从“库数量→图像二进制→识别率→虚拟内容表现”逐级验证你会少掉很多头发。另外再分享一个到目前为止我觉得收益很高的小做法在测试阶段往图库里添加一条“测试专用图”的入口放在开发者菜单里用随机生成的纹理每次启动都自动添加用来验证底层链路是否健康。这样如果某一天动态添加开始出问题了我能第一时间分辨是平台的问题还是我自己代码的问题不用每次重新部署App到真机。这个方案目前我已经在两类项目里跑通过一类是AR名片展示用户拍完名片实时生成对应的AR动画另一类是智慧展厅的展品识别后台动态维护展品图库前台轮播识别。两者核心逻辑完全一样差别只在虚拟内容的形态。后续如果你想做更复杂的多人协同识别、多图同时追踪、或者叠加空间锚点做持久化摆放这套动态图库的底座完全撑得住。希望这篇文章能让你少走一点弯路。AR图像识别没有太多高深莫测的东西把底层机制搞明白把链路验证做扎实剩下的就是不停堆业务价值的问题了。