ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android人物抠图实战:Modnet模型部署与ONNX Runtime优化

Android人物抠图实战:Modnet模型部署与ONNX Runtime优化 做Android这几年给我印象最深的AI落地项目就是人物抠图。最初我用传统图像处理算法做边缘提取换背景的效果惨不忍睹头发的边缘全是锯齿遇到浅色衣服直接翻车。后来接触到开源的Modnet算法配合ONNX Runtime跑在Android端才算真正把“一键抠图、换背景”做成了能上线的功能。这篇文章把我在Android上落地Modnet的完整过程拆开讲讲从方案选型、环境搭建到核心代码实现再到各种坑位排查希望能帮到要在移动端做抠图或者背景替换的朋友。Modnet这个名字你可能不熟它是CVPR 2021年提出的实时人物抠图模型核心优势是不依赖任何外部辅助信息单张图片就能直接预测出精细的alpha matte透明度蒙版而且模型体积非常小大约24MB普通手机CPU也能跑到几十毫秒一帧。在Android端做人物抠图这个方案是当前性价比最高的路线之一。下面我会把整个项目的选型逻辑和实操过程完整铺开内容偏工程向涉及Android Studio、JavaONNX Runtime、图像前后处理适合有一定移动端开发基础、想快速把抠图能力集成进App的读者。1. 方案选型为什么是Modnet ONNX Runtime的组合1.1 抠图算法选型对比做人物抠图业界方案大致有四类传统图像分割、基于深度学习的语义分割、显著性目标检测、专门的人物抠图matting。前两类无法产出像素级的alpha通道边缘会糊成一片显著性检测的目标是所有前景物体不一定是人物。Modnet属于第四类它的训练目标就是为人物预测一个高精度的alpha matte通道输出尺寸跟输入图一致每个像素点的值在0到1之间代表这个像素属于人物的概率。这个alpha通道就是抠图和换背景的核心。跟同领域的其他模型比Modnet的优势非常明显。比如Deep Image Matting需要额外输入一张trimap三元图告诉你哪些区域肯定是前景、哪些肯定是背景、哪些需要精细处理这在自动化场景里没法用因为用户不可能每次都给一张trimap。Modnet走的是automatic matting路线不需要任何交互一张照片进去alpha matte出来这一点对App来说太重要了。还有BRIA的RMBG-2.0同样是免交互的背景移除模型但模型体积更大在低端Android设备上的推理延迟要高不少。实际测试下来Modnet在MobileNetV2的backbone下单帧推理可以控制在100ms以内这个性能表现对移动端非常友好。另外还要说一点Modnet是开源项目GitHub上有完整的训练代码、预训练权重和PyTorch实现。这意味着你不仅可以下载现成的模型直接部署还能用自己的人物数据做微调针对特定场景比如半身人像、全身人像、儿童照优化效果。我在项目里用的是官方预训练权重后面如果遇到特定业务场景还可以用开源脚本重新训练。相比之下很多商业抠图SDK虽然效果好但闭源、按量收费、还有隐私合规问题对需要本地处理照片的App来说并不是优选。1.2 Android端推理引擎选型模型选定了Modnet接下来就是怎么把它跑在Android上。PyTorch模型不能直接在移动端用需要转换成通用推理格式。这里有两个主流选择PyTorch Mobile和ONNX Runtime。PyTorch Mobile优点是跟PyTorch生态无缝缺点是模型格式偏重、算子支持度不如ONNX全面而且团队后续如果想把这个模型复用到iOS或者服务端还得重新适配。ONNX Runtime是微软开源的跨平台推理引擎Android端有现成的Java API模型转换一次Android、iOS、Windows、Linux全平台都能跑社区生态也更活跃。我最终选的是ONNX Runtime版本是1.18.x依赖包只有几MB对APK体积的影响很小。ONNX Runtime Android库自带CPU和NPU支持在支持NNAPI的设备上可以自动调用硬件加速不过实测下来Modnet这种小模型在CPU上的速度已经足够走NNAPI反而可能因为算子兼容问题导致启动变慢。所以项目里我默认用CPU执行把NNAPI作为可选项留给高端设备。1.3 整体技术链路整个项目的处理流程可以概括为图片输入 - 解码为Bitmap - 缩放至模型输入尺寸 - 像素值归一化并构造ONNX Tensor - 模型推理得到alpha matte - 将matte缩回原图尺寸 - 与原图合成透明背景人物图 - 与用户选择的新背景合成最终图片。这中间每一步都有需要注意的细节任何一个步骤偷懒都会导致最终效果打折扣。比如缩放算法的选择、归一化的mean/std参数、alpha通道的平滑处理、透明图与背景的合成方式这些都在后面几节里详细展开。有一点提前说明如果只是练手直接把这个功能做成一个单Activity项目就够了但如果是集成进现有App建议把抠图逻辑封装成一个独立的类或者Module输入Bitmap、输出Bitmap内部处理模型加载和推理这样上层UI完全不用关心算法细节后续替换成其他模型也方便。2. 工程搭建依赖、模型与图像解码细节2.1 Android Studio项目配置与依赖引入项目基于Android Studio开发建议使用最新稳定版我这边用的是Hedgehog版本Gradle插件版本8.2minSdkVersion设为24Android 7.0targetSdkVersion设为34。Modnet模型本身的输入不挑设备算力但图片解码、Bitmap操作这些环节涉及大量内存低于Android 7.0的设备容易出现OOM所以minSdk设到24比较稳妥。在build.gradleModule级别里引入ONNX Runtime依赖dependencies { implementation com.microsoft.onnxruntime:onnxruntime-android:1.18.0 }这个依赖会同时引入Java API和对应的JNI库不需要额外配置。模型文件modnet.onnx放到app/src/main/assets目录下运行时通过AssetManager读取并复制到缓存目录再初始化session。这里有个经验ONNX Runtime创建session时可以直接从文件路径加载也可以从byte数组加载ByteBuffer。官方文档推荐从文件路径加载因为内部会做memory-mapped file映射减少内存拷贝。所以我在项目里做了个copyModelToCache()方法首次启动时把assets里的onnx文件复制到getCacheDir()之后每次启动直接用文件路径创建session。如果复制、初始化耗时较长建议放在子线程执行避免阻塞UI线程导致ANR。OpenCV的Java库org.opencv:opencv在这个项目里不是必须的图像缩放和像素操作用Android原生Bitmap API就能完成没必要为了几个矩阵操作引入一个几十MB的库所以我全程没有用OpenCVAPK体积控制在比较小的范围。2.2 模型导出与验证Modnet官方仓库提供的是PyTorch权重格式为.pth。要把这个权重转成ONNX需要在Python环境里执行导出脚本。这里我简单贴一下我在项目里用的导出代码基于Modnet官方repo的modnet模块import torch from modnet import MODNet model MODNet(backbone_pretrainedFalse) model.load_state_dict(torch.load(modnet_photographic_portrait_matting.ckpt, map_locationcpu)) model.eval() dummy_input torch.randn(1, 3, 480, 640) torch.onnx.export( model, dummy_input, modnet.onnx, opset_version11, input_names[input], output_names[output], dynamic_axes{input: {0: batch, 2: height, 3: width}, output: {0: batch, 2: height, 3: width}} )这里有两个关键点。第一dummy input的尺寸可以任意设置因为导出时开了dynamic_axesONNX模型会支持动态输入尺寸这样在Android端就不需要把图片强行resize到一个固定值可以根据原图比例选择一个接近480x640的尺寸。第二导出后建议用onnxruntimePython库做一次推理验证对比PyTorch输出和ONNX输出的差异确保转换过程中没有算子丢失或精度损失。我用的是官方权重导出的ONNX模型在Python端和Android端的输出几乎完全一致差异在1e-5量级可以忽略。2.3 图片解码与旋转矫正最容易踩的坑这个环节是很多第一次做Android图像处理的人会栽跟头的地方。手机相册里的照片通常不是标准的0度朝向系统会根据EXIF信息旋转显示而直接BitmapFactory.decodeFile()出来的Bitmap是原始像素没有做过旋转矫正。如果不处理EXIF抠图结果会莫名其妙转了90度或者倒过来。解决办法是读取EXIF中的orientation字段然后对Bitmap做对应角度的旋转。AndroidX有一个ExifInterface可以直接用import androidx.exifinterface.media.ExifInterface; public static Bitmap rotateBitmapIfNeeded(Context context, String filePath) { Bitmap bitmap BitmapFactory.decodeFile(filePath); int orientation 0; try { ExifInterface exif new ExifInterface(filePath); orientation exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); } catch (IOException e) { e.printStackTrace(); } Matrix matrix new Matrix(); switch (orientation) { case ExifInterface.ORIENTATION_ROTATE_90: matrix.postRotate(90); break; case ExifInterface.ORIENTATION_ROTATE_180: matrix.postRotate(180); break; case ExifInterface.ORIENTATION_ROTATE_270: matrix.postRotate(270); break; default: return bitmap; } return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); }另外一个大坑是图片太大导致的OOM。现在的手机相机拍出来动辄4000x3000像素一张图解码成ARGB_8888的Bitmap就要占用48MB内存如果同时处理多张图片内存很容易爆掉。所以解码时必须用BitmapFactory.Options的inSampleSize做降采样先把原图缩到一个合理的尺寸比如最长边不超过1600像素再做后续处理。降采样到合适大小不仅省内存抠图速度也会快很多而且对最终效果影响很小因为Modnet内部本来就是按输入尺寸缩放处理的图像细节已经通过模型下采样丢失了没必要在Bitmap层面保留4000x3000的冗余信息。3. 核心代码实现从Bitmap到透明人物图再到背景合成3.1 图像预处理与Tensor构造Modnet的官方预处理逻辑是把图像缩放到固定大小默认480x640像素值除以255归一化到[0,1]再用mean0.5、std0.5做标准化。对应公式就是(pixel/255 - 0.5) / 0.5等价于pixel/127.5 - 1。这个变换把像素值映射到[-1,1]区间。注意ONNX Runtime的输入是CHW维度顺序即[1, 3, height, width]通道顺序RGB。我在Android端实现的时候没有直接用Bitmap的getPixel()逐个读取因为那个方法性能极差每调用一次都要走JNI边界。更高效的做法是给Bitmap分配一个int[] pixels缓冲区用getPixels()一次性读出所有像素然后再循环转换成float数组按照CHW顺序填充。具体代码如下private static float[] preprocess(Bitmap bitmap, int targetW, int targetH) { Bitmap scaled Bitmap.createScaledBitmap(bitmap, targetW, targetH, true); int[] pixels new int[targetW * targetH]; float[] inputData new float[3 * targetH * targetW]; scaled.getPixels(pixels, 0, targetW, 0, 0, targetW, targetH); for (int i 0; i pixels.length; i) { int p pixels[i]; float r (((p 16) 0xFF) / 255.0f - 0.5f) / 0.5f; float g (((p 8) 0xFF) / 255.0f - 0.5f) / 0.5f; float b ((p 0xFF) / 255.0f - 0.5f) / 0.5f; inputData[i] r; // R通道 inputData[targetW * targetH i] g; // G通道 inputData[2 * targetW * targetH i] b; // B通道 } if (scaled ! bitmap) { scaled.recycle(); } return inputData; }关于输入尺寸的选择我补充一下。Modnet官方是480x640高480宽640但如果你处理的图片是竖构图480宽640高的比例正好接近手机人像照。横构图的话我建议保持图片原始宽高比等比缩放到宽或者高不超过640/480的范围然后再做padding或者center-crop到模型要求的比例。我在实际项目里用的策略是计算原图的宽高比如果接近4:3就resize到480x640如果是别的比例先等比缩放到短边等于480然后对长边做中心裁剪到640保证输入尺寸一定是480x640。这样处理的好处是模型的效果最稳定因为训练数据就是在类似分辨率上做的数据增强。3.2 ONNX Runtime推理与Alpha Matte提取模型初始化时需要创建OrtEnvironment和OrtSession这两个对象都是重量级资源全局复用不要每次推理都创建。我封装了一个ModnetEngine类构造函数里完成以下初始化public class ModnetEngine { private final OrtEnvironment env; private final OrtSession session; private final String inputName; public ModnetEngine(Context context) throws IOException { File modelFile copyModelToCache(context); env OrtEnvironment.getEnvironment(); OrtSession.SessionOptions options new OrtSession.SessionOptions(); options.setOptimizationLevel(OrtSession.SessionOptions.OptLevel.ALL_OPT); session env.createSession(modelFile.getAbsolutePath(), options); inputName session.getInputNames().iterator().next(); } }setOptimizationLevel设置为ALL_OPT很重要ONNX Runtime会在加载模型时做图优化包括算子融合、常量折叠等实测推理速度能提升20%到30%。而且这个优化只在初始化时执行一次不会带来运行时开销。推理的核心代码很短public float[] runInference(Bitmap bitmap, int targetW, int targetH) throws OrtException { float[] inputData preprocess(bitmap, targetW, targetH); OnnxTensor inputTensor OnnxTensor.createTensor(env, FloatBuffer.wrap(inputData), new long[]{1, 3, targetH, targetW}); OrtSession.Result result session.run(Collections.singletonMap(inputName, inputTensor)); OnnxTensor outputTensor (OnnxTensor) result.get(0); float[][][][] outputData (float[][][][]) outputTensor.getValue(); inputTensor.close(); result.close(); return outputData[0][0]; // [H, W] 的alpha matte }outputData[0][0]拿到的就是一个float[H][W]的二维数组每个值在0到1之间表示该像素点属于前景人物的概率。这个数组就是整个抠图流程的核心产物。这里有个关键点result.get(0)返回的对象实现了AutoCloseable接口处理完必须调用close()释放底层内存否则多次推理之后内存会不断累积。这个细节很容易被忽略因为Java层的GC感知不到JNI侧分配的大块内存只有显式close才能真正释放。3.3 从Alpha Matte到透明人物图拿到alpha matte之后接下来的任务是把alpha值作用到原图上生成一张带透明通道的人物图。这里要注意alpha matte的尺寸是模型输入的尺寸比如480x640而原图可能更大所以需要先把alpha matte缩放到原图尺寸。缩放的时候建议用双线性插值不要用最近邻。最近邻缩放会导致alpha边缘出现明显的锯齿块而双线性插值能保持平滑过渡。Android里可以借助Matrix创建一个缩放Bitmap来做但直接对float数组做双线性插值更可控。我这边写了个通用的resizeAlpha()方法把float矩阵缩放到任意尺寸。得到与原图同尺寸的alpha数组后就可以合成透明图了。基本思路是遍历原图所有像素用alpha值作为透明度写入ARGB通道。代码实现如下public static Bitmap applyAlpha(Bitmap src, float[] alpha, int alphaW, int alphaH) { int w src.getWidth(); int h src.getHeight(); float[] resizeAlpha resizeAlpha(alpha, alphaW, alphaH, w, h); Bitmap result Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); int[] pixels new int[w * h]; src.getPixels(pixels, 0, w, 0, 0, w, h); for (int i 0; i pixels.length; i) { int p pixels[i]; int a (int) (resizeAlpha[i] * 255); int r (p 16) 0xFF; int g (p 8) 0xFF; int b p 0xFF; pixels[i] (a 24) | (r 16) | (g 8) | b; } result.setPixels(pixels, 0, w, 0, 0, w, h); return result; }这里有个视觉细节需要注意如果直接把alpha乘到RGB上也就是r r * alpha会让人的边缘出现一圈“黑色光晕”因为半透明像素的背景通常是暗色混合后会拉低边缘亮度。更自然的做法是让RGB保持原值只改变透明度这样在浅色背景下半透明边缘看起来是自然的“淡出”效果而不是发黑。这种做法也是很多商业抠图App采用的策略。3.4 背景替换与边缘自然融合透明人物图生成后背景替换就是一次简单的Bitmap合成。我提供两种合成方式。第一种是纯代码方式新建一张与人物图同尺寸的ARGB_8888 Bitmap先画背景图再画人物图。使用Canvas的drawBitmap()即可public static Bitmap replaceBackground(Bitmap person, Bitmap background) { Bitmap result Bitmap.createBitmap( person.getWidth(), person.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas new Canvas(result); canvas.drawBitmap(background, 0, 0, null); canvas.drawBitmap(person, 0, 0, null); return result; }背景图如果不是目标尺寸建议用createScaledBitmap或Matrix裁剪方式先调整到人物图尺寸。直接drawBitmap会拉伸背景导致人脸变形效果很差。我在项目里用的是一个CenterCrop逻辑背景图等比缩放让短边铺满目标尺寸然后居中裁剪。第二种是处理“PS抠图如何自然融合到另一张图”问题的高级做法。上面这种方式合成后人物边缘有时候会显得太锐利跟新的背景环境有割裂感。解决办法是给alpha边缘做一层很轻微的羽化也就是高斯模糊。我通常对这个alpha matte的过渡带做半径为1到2像素的高斯模糊只在边缘有效不会影响头发丝等细节。Android的GaussianBlur可以用RenderScript已废弃建议用ScriptIntrinsicBlur的替代方案或者简单地对alpha数组做一次卷积。如果不想引入额外依赖也可以用Canvas的MaskFilterPaint paint new Paint(); paint.setMaskFilter(new BlurMaskFilter(2, BlurMaskFilter.Blur.NORMAL)); canvas.drawBitmap(person, 0, 0, paint);但这个方案只做演示可以生产环境对性能有要求的话还是建议在float数组层面处理。边缘羽化加上轻微的色彩调整能让换背景后的图片看起来更自然这一块是体验差异的重点。3.5 异步化与进度反馈抠图推理是典型的耗时操作必须在子线程中执行否则UI线程卡顿几百毫秒用户早就划走了。我项目里的做法是定义一个MattingCallback接口内部用线程池执行推理通过主线程Handler回传结果。同时在UI层显示一个ProgressBar提示“正在抠图中”处理完成再更新ImageView。接口定义很简单public interface MattingCallback { void onSuccess(Bitmap personBitmap, Bitmap resultBitmap); void onError(Exception e); }线程池使用Executors.newSingleThreadExecutor()就够因为不需要并发处理多张图片单线程能避免同时推理导致的内存洪峰。如果要处理批量抠图比如相册多选可以换成带队列的线程池但单张处理时依然串行执行。进度条用系统的ProgressBar即可不需要花哨的动画因为推理时间通常不到1秒进度条只是一个心理暗示告诉用户“没卡死在干活”。4. 性能优化与高频问题排查记录4.1 推理耗时优化从500ms压到80ms我第一次集成完跑起来在骁龙865测试机上推理耗时约300ms加上预处理、后处理和Bitmap操作整个流程要500ms左右。虽然可用但离“丝滑”还有距离。后来做了几轮优化最终压到80ms推理加120ms全流程体验改善非常明显。第一个优化点是ONNX Runtime的线程数设置。默认把线程池设为CPU核心数减1但ONNX Runtime底层默认使用的是所有核心在Android平台上未必高效。我显式设置了SessionOptions.setNumThreads(4)在部分4核和8核设备上性能都有提升。第二个优化点是避免在循环中创建临时对象。预处理阶段以前每次创建FloatBuffer其实可以复用一个FloatBuffer实例配合rewind()重置位置。第三个优化点是图片降采样。原来用户选一张4000x3000的照片我直接拿去推理预处理缩放就要花很多时间。现在先根据模型输入比例缩放到长边1600再交给模型处理节省了接近50%的预处理时间。第四个优化点也是收益最大的一点把alpha matte的后处理从Bitmap操作改成纯数组操作。以前是把alpha数组转成Bitmap再用Bitmap做缩放和合成每次都要走JNI和内存分配现在直接对float数组做插值和像素赋值全程在Java层完成节省了大量native内存拷贝。综合以上几点整个流程从500ms降到了120ms左右用户基本无感。4.2 内存占用优化避免OOM的几条铁律抠图功能涉及大图解码、多张Bitmap同时存在内存峰值非常高。我在开发中遇到的最严重问题是连续处理10张图片后直接OOM崩溃。后来总结出几条铁律照着做基本不会再出问题。第一条全流程只保留一份原始Bitmap的引用。预处理拿到所需数据后及时把中间Bitmap回收或置空。第二条Bitmap.Config尽量用ARGB_8888虽然内存大但抠图需要alpha通道RGB_565不支持透明。第三条用BitmapFactory.Options控制解码尺寸inSampleSize设置成2的幂。第四条大尺寸背景图不要一次性decode全尺寸先取目标尺寸附近的大小用inJustDecodeBounds先读宽高再计算合适的inSampleSize。第五条Android 8.0及以上可以用Hardware Bitmap加速渲染但注意这种Bitmap不能读取像素所以不能在抠图流程中使用只适合最后展示结果。内存这块还有一个隐藏开销ONNX Runtime的OnnxTensor如果每次推理都创建会在native层分配连续内存用完必须close。我踩过一次坑忘记在循环里close跑了几十次之后内存暴涨到400MB。后来把所有Tensor和Result对象都放进try-with-resources里内存曲线变得非常平稳。4.3 高频问题排查边缘发灰、人物偏色、模型失效我在各个阶段遇到过不少奇怪的问题这里挑几个典型的记录下来。第一个是抠图后人物边缘发灰发暗原因往往不是抠图算法的问题而是合成阶段alpha没有处理干净。比如alpha数组里边缘部分有0.2、0.3这样的小数但原图在那些位置是纯白背景直接把RGB原值放上去跟白色背景混合后就会发灰。解决办法是在合成背景时不要直接用alpha作为透明度值而是用一个略微抬升的映射比如alpha clamp(alpha * 1.2, 0, 1)让半透明区域更亮一点。当然如果要精细做应该对前景颜色做去背处理不过大部分场景下简单抬升就够了。第二个是人物偏色特别是头发和衣服边缘出现红色或绿色色边。这个问题的根因是模型输出的alpha matte在边缘有微小的偏差导致原图前景色和背景色没有被完全分离。处理方法是给RGB通道做一个边缘去色desaturate在alpha值介于0.1到0.9之间的像素上把RGB稍微向灰度方向拉一点这样色边会变得不明显。第三个是模型加载失败报OrtException。最常见的原因是onnx文件拷贝不完整assets目录读取时没有处理好缓冲区另外有些Android设备上JNI库加载失败需要在Application里手动System.loadLibrary(onnxruntime)提前触发加载能更快暴露问题。我遇到过一次只在Android 8.0设备上崩溃的问题后来定位到是ONNX Runtime版本太旧升级到1.18后问题消失。第四个问题是“抠出的人物边缘很假”跟PS里抠图一样如果人物原本是在强光下拍的边缘会有一圈高光换到暗色背景后这圈高光会特别突兀。这时候可以对alpha matte的边缘做小半径的高斯模糊同时对边缘区域的RGB做亮度削弱。这是很多商业修图产品也在用的方法。4.4 扩展方向Modnet还能用在哪些场景这个项目做完以后我发现Modnet的能力不只局限于静态图片抠图。视频流里同样可以跑只要把CameraX的帧数据转成Bitmap用同一个ModnetEngine做推理就能做实时背景替换类似视频会议的虚拟背景功能。不过视频场景对性能要求更高需要把输入分辨率降到320x480左右并且可以考虑用录屏测试和帧丢弃策略来稳定帧率。另外可以把Modnet输出的alpha matte作为Mask输入叠加到底片上来做人像美容、背景虚化模拟大光圈效果、证件照换底色等换背景场景这些需求在拍照类、社交类、工具类App里非常常见。关于换底色证件照可以额外补充一点因为证件照底色一般是纯色不用走复杂的背景合成流程只要把alpha matte提取出来然后用指定颜色填充背景区域就行。Modnet对这类纯色背景图片的抠图精度非常高边缘几乎完美。这也是很多证件照小程序背后的技术原理之一。如果后续想提升复杂场景下的人物分割效果可以关注RMBG-2.0等更新的模型这类模型对透明物体、头发丝的分割效果更好但模型体积多半会比Modnet大一倍左右是否能上端就需要根据目标机型的性能再权衡。另一个方向是用Modnet的预训练权重做迁移学习在特定场景数据上微调比如专门抠电商模特图、游戏角色图微调后的精度会明显优于通用权重。这一点很多开发者容易忽略开源模型落地效果的上限往往不是模型本身而是有没有针对你的业务数据做适配。最后再分享一个我在性能调试上的小技巧给抠图这个操作加一个可选的耗时统计开关用Log.d(MattingTime, String.format(pre%dms infer%dms post%dms, preTime, inferTime, postTime))打印各阶段耗时。上线后在后台只对测试包打开日志根据真实用户反馈定位是预处理慢还是推理慢比凭感觉优化高效得多。我后来还把推理的输入尺寸做成了可动态调整的配置项在低端机上自动降到320x480高端机保持480x640用一只开关平衡效果和性能这也算这个项目落地后最重要的经验之一。
返回列表