ARTICLE DETAIL

资讯详情

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

WebGPU + MobileNet:浏览器端实现以图搜图特征提取

WebGPU + MobileNet:浏览器端实现以图搜图特征提取 我最早接触到“以图搜图”这个需求是帮一个摄影社区做图库管理。当时第一反应是上服务端跑特征提取模型用 MobileNet最后一层截掉拿 1024 维向量做余弦相似度。方案本身不复杂真正让我头疼的是服务端的资源成本、接口延迟和运维链路图片一多GPU 实例就烧钱离用户远了单次推理动不动几百毫秒再加上图片本身属于敏感数据把用户上传的每张图都送到远端模型处理隐私上总有顾虑。后来我尝试把整个特征提取链路搬到浏览器端用 WebGPU 跑 MobileNet。实测下来效果超出预期中高端显卡上一次推理大概 30 到 80 毫秒比走服务端还快而且图片根本不上传在本地就把特征向量算完了。这篇文章想把完整的技术方案、选型理由、实现细节和踩坑记录都拆开讲清楚。如果你是做图像检索、相似图片匹配、图片去重或者只是想了解 WebGPU 到底能在浏览器里跑什么 AI这篇应该能给你省不少弯路。1. 为什么把特征提取放进浏览器我在以图搜图项目里的真实选择1.1 以图搜图的两段式流程以图搜图听起来像是一个“图片输入结果输出”的整体系统但拆开看本质上是两个独立阶段特征库构建阶段把图片库里的所有图片依次送入特征提取模型得到各自的语义向量。这就是“入库”的过程。在线检索阶段用户上传一张查询图用同一个模型把它转换成语义向量然后在特征库里找出最相似的若干条。这套流程的关键就在于查询图和库里的图必须经过同一个模型、处于同一个向量空间相似度计算才有效。而整个系统的成本大头几乎都集中在特征提取模型上。服务端方案是这样跑的图片通过前端上传到后端上传到对象存储临时目录再触发一个推理服务去下载图片、做预处理、跑模型、返回向量。这中间每一环都是耗时点上传带宽限制、存储 IO、推理队列等待、结果回传。用户看到的不是“推理速度”而是整个链路的总延迟。1.2 服务端推理的三道坎第一道坎是 GPU 成本。MobileNet 这类轻量模型虽然单张推理只需几十毫秒但架不住请求量大。一台不带 GPU 的服务器并发一高就排队带 GPU 的实例价格直接翻几倍而且资源峰值很难预测。数据库图库如果做了 CDN 加速模型结果其实更适合前端算——凡是能推到离用户更近的地方的计算就不该跟后端较劲。第二道坎是带宽和延迟。一张手机照片原图动辄三五兆上传之后再下载推理结果一来一回用户感知到的等待时间远不止模型推理本身。很多团队给题库做的是“先压缩再上传”但压缩本身也要白等。第三道坎是数据隐私。图库里的用户照片经常是个人资料、医疗影像、私人收藏等敏感数据。用户信任你把照片交上来不代表愿意看到照片被送到第三方模型服务里做推理。浏览器端推理可以做到图片不离开本机这个属性在很多高合规场景下是刚需而不是加分项。1.3 浏览器端推理带来的连锁好处把特征提取搬到浏览器端以后整个系统结构发生了有趣的变化服务端不再需要跑推理只负责存储和检索语义向量。用户图片在本地被显卡直接处理算完只上传一个 1024 维的 Float32Array对带宽几乎零压力。GPU 算力成本转嫁到用户设备上平台方可以大幅缩减推理集群的规模甚至完全去掉 GPU 实例。图片不出本机隐私合规压力也会小很多。当然代价也有你不再能控制用户的硬件环境。低端手机没有独立 GPUWebGPU 支持也不完善。这个问题后面会讲我的做法是后端回退WebGPU → WebGL → WASM保证老设备虽然慢一点但功能不丢。2. WebGPU 驱动浏览器的 GPU 算力从底层看它为何能管用2.1 浏览器里跑 AI 的可选方案在 WebGPU 之前浏览器端做 AI 推理主要靠三条路线WebGL、WebAssemblyWASM和 WebGPU。WebGL 是目前 TF.js 生态里最成熟的方案原理是把神经网络的计算映射成图形学里的纹理渲染操作。矩阵乘法这种操作在 WebGL 里通过 Fragment Shader 逐像素计算理论上确实能调用 GPU 并行算力但有两个大问题一是它没有通用的计算抽象矩阵搬运和中间结果存放都要借助纹理坐标系换算代码维护痛苦二是 WebGL 的精度支持有限很多移动端 GPU 对浮点精度的处理不一致导致不同机器跑出不同结果。WASM 方案不依赖 GPU它把 C 写的算子编译成二进制在 CPU 上跑。好处是兼容性极好几乎任何现代浏览器都能跑坏处是它本质上还是 CPU 计算和 GPU 并行加速没有关系。WebGPU 是三者中最新的图形与计算接口标准。它提供了真正意义上的通用计算管线可以像写 CUDA 内核一样编写计算着色器Compute Shader让浏览器直接调度 GPU 做并行数学运算。这相当于把浏览器的定位从“显示网页”升级成“运行计算程序”。2.2 计算着色器WebGPU 能处理通用计算的关键要理解 WebGPU 为什么适合跑 AI不需要去记一整套 API只需要搞清楚一个问题神经网络推理过程是大量矩阵乘法和卷积运算这类运算极度适合 GPU 的 SIMD单指令多数据并行模式。GPU 里有成千上万个核心CPU 可以一次性处理一个或几个线程的复杂指令GPU 则强调同一时刻上万线程执行同一种简单操作。矩阵乘法恰好可以拆成“对每个输出元素累加对应输入乘积”的独立并行任务这正是计算着色器最擅长处理的场景。WebGPU 在底层把数据放进 GPUBuffer再让计算着色器对这些 buffer 做读写。和 WebGL 最大的差异是WebGPU 的数据都是通用 buffer不需要伪装成纹理数学算子可以直接在 GPU 内存里反复迭代避免了纹理与数据之间的格式转换开销。在现代显卡上用 WebGPU 跑 MobileNet 的 2D 卷积算子比用 WebGL 跑同样的层平均能快一到两倍对比 WASM 更是拉开数量级差距。2.3 我在写代码前确认的兼容条件WebGPU 的浏览器兼容性是项目启动前必须确认的底线。2024 年之后Chrome 和 Edge 的主流版本默认开启Firefox 和 Safari 的原生实现也基本到位但不是所有浏览器版本都是完整可用状态。我在代码里对 WebGPU 的检测方式非常简单if (navigator.gpu) { console.log(当前浏览器支持 WebGPU); } else { console.log(当前浏览器不支持 WebGPU需要降级到 WebGL); }在 Chrome 上还要注意一个细节即使navigator.gpu存在requestAdapter()也可能返回null这通常说明浏览器虽然支持 WebGPU 标准但当前设备没有可用的 GPU 设备例如部分虚拟机、远程桌面环境。这种情况也要算作“不可用”。实际项目中我的态度是能 WebGPU 就 WebGPU不支持就 WebGL再不行就 WASM同时给用户一个明确的提示。后面实战环节我会给出具体的降级策略。3. 为什么是 MobileNet以及 1024 维向量从哪里来3.1 移动端时代的遗留架构反而是浏览器端当下的最优解MobileNet 是 2017 年 Google 为移动端和嵌入式设备设计的轻量卷积网络。它最核心的贡献是用深度可分离卷积Depthwise Separable Convolution替代普通卷积。理解深度可分离卷积不需要看公式用类比就好普通卷积是让一个厨师同时负责切菜、配菜、炒菜所有工序混合在一个步骤里完成深度可分离卷积则是把任务拆成两步——先每个食材单独处理深度卷积再由一个厨师统一调配逐点卷积。计算量因此大幅下降在同样精度目标下模型体积和计算量都能减少八成以上。这些年出现了不少更轻量的新模型比如 EfficientNet-Lite、GhostNet、以及各类蒸馏版移动模型。但在浏览器端做特征提取我依然优先选 MobileNet原因有三模型体积小。MobileNetV1 转换后的 tfjs 模型加上权重文件大概在 16MB 左右加载负担可控。推理速度快。深度可分离卷积本身就能在 GPU 上高效并行WebGPU 对这类结构算子的支持度也最好。生态成熟。不管是 Keras、TF.js 还是 ONNX RuntimeMobileNet 的权重转换和部署都有非常成熟的方案踩坑成本低。我不否认 2024-2025 年出现的新模型在 ImageNet 精度上可能比 MobileNet 高但在浏览器端场景精度提升带来收益远远抵不过体积和推理耗时的增加。做以图搜图的核心诉求是“特征向量表达能力强 检索稳定可复现”MobileNet 在生产环境里已经验证过无数次这是稳健选型。3.2 倒数第二个输出1024 维向量的真实来源关于“1024 维向量”需要先澄清一点MobileNetV1 和 MobileNetV2 的标准输出维度是不同的。MobileNetV1 的主体在最后的 1x1 卷积层输出 1024 个通道的特征图经过 Global Average Pooling 之后得到 1024 维向量。MobileNetV2 则把最后一个卷积层的通道数设置成了 1280所以全连接层之前是 1280 维。如果你看到别人写的代码输出是 1280 维那大概率用的是 MobileNetV2如果输出的是 1024 维那就是 MobileNetV1或者有人手动裁剪了 V2 的最后一层。我在本项目中选的是 MobileNetV1原因很直接项目标题里要求 1024 维向量而 V1 天然就是 1024 维不需要做任何额外加工。这里有个非常重要的技术点我们拿向量和最终分类结果无关。MobileNet 原本的最后一层是 1000 类分类器输出的是“这张图属于哪个类别”的概率分布这个输出不能直接用来做以图搜图。我们要的是倒数第二层的“图像语义嵌入向量”这个向量包含了模型对图片内容的抽象理解。在 Keras 中获取这个向量的写法非常简洁from tensorflow.keras.applications import MobileNet model MobileNet( weightsimagenet, include_topFalse, poolingavg ) print(model.output.shape) # (None, 1024)关键参数解释一下include_topFalse去掉最后的分类全连接层。poolingavg将最后的二维特征图做全局平均池化变成一个 1024 维向量。weightsimagenet直接加载 ImageNet 预训练权重。这样得到的模型输入任意一张 224x224 的图片输出就是一个 1024 维的浮点向量。3.3 模型去掉分类层导出到浏览器用 Keras 拿到模型之后需要把它转成浏览器能识别的 tfjs 格式。这里推荐使用 TensorFlow.js 官方的转换器。pip install tensorflowjs转换命令非常简单tensorflowjs_converter \ --input_formatkeras \ --output_formattfjs_graph_model \ mobilenet_v1_1024.h5 \ web_model命令执行完之后web_model目录下会生成一个model.json和若干.bin权重文件。这两个文件就是浏览器要加载的全部东西。需要注意的一个点如果你在 Keras 里保存的是完整模型包含分类头转换之后加载到浏览器再手动截取最后层也是一条可行路径但会增加额外的前端张量操作容易出兼容问题。我在项目里推荐的做法是在导出前就把模型结构裁剪好浏览器端拿到的直接就是“图片进、1024 维向量出”的端口省心很多。如果不想自己转也可以直接使用 TF.js 官方模型仓库里提供的预转换 MobileNet 模型。但要注意版本有些预转换模型是用于图像分类的完整版需要手动截层为了不引入额外变量我更建议按上面的命令自己转一次整个流程五分鐘就能结束。4. 实战从一张图片到 1024 维特征向量4.1 搭建最小前端骨架先建一个最小可运行的 HTML 页面用来选择图片并展示特征提取结果!DOCTYPE html html langzh-CN head meta charsetUTF-8 titleWebGPU MobileNet 特征提取/title /head body h2浏览器端以图搜图特征提取示例/h2 input typefile idfileInput acceptimage/* div idstatus请选择图片/div canvas idpreview/canvas pre idoutput/pre script srchttps://cdn.jsdelivr.net/npm/tensorflow/tfjs4.20.0/dist/tf.min.js/script script srchttps://cdn.jsdelivr.net/npm/tensorflow/tfjs-backend-webgpu4.20.0/dist/tf-backend-webgpu.min.js/script script srcapp.js/script /body /html注意 script 的加载顺序先加载 tfjs 核心库再加载 webgpu 后端。如果顺序反了tf.setBackend(webgpu)会报未注册后端的错误。4.2 初始化把后端切换到 WebGPU之后的app.js里第一步是初始化后端和模型let model; async function initBackend() { if (navigator.gpu) { try { await tf.setBackend(webgpu); await tf.ready(); console.log(已启用 WebGPU 后端); } catch (e) { console.warn(WebGPU 初始化失败回退到 WebGL, e); await tf.setBackend(webgl); await tf.ready(); } } else { await tf.setBackend(webgl); await tf.ready(); } } async function loadModel() { // 这里换成你部署的 model.json 地址 model await tf.loadGraphModel(./web_model/model.json); console.log(模型加载完成); } async function init() { await initBackend(); await loadModel(); document.getElementById(status).textContent 模型已就绪可以上传图片; } init();这部分的重点在于降级逻辑。实测下来WebGPU 在很多浏览器上都会因为各种原因初始化失败比如设备不支持、显卡驱动过旧、浏览器沙箱限制等。写代码时不要假设只要有navigator.gpu就一定能成功一定要用try...catch包裹失败后自动回退到 WebGL。4.3 图片预处理的细节把控MobileNetV1 的标准输入是 224x224 的 RGB 图片同时要求像素值归一化到 [-1, 1] 区间。预处理主要分三步读取图片像素。等比缩放并居中裁剪到 224x224。归一化像素值。很多人在这里会犯一个错误直接把图片拉伸到 224x224。如果原图不是正方形拉伸会导致图像畸变特征向量的质量下降非常明显。以图搜图的场景里查询图的构图信息非常宝贵拉伸会让“横构图”直接变成“竖向挤压”语义特征严重失真。我的处理方式是先等比缩放让图片长边变成 224然后居中裁剪中心区域async function loadImageToCanvas(file) { const img new Image(); const objectUrl URL.createObjectURL(file); await new Promise((resolve, reject) { img.onload resolve; img.onerror reject; img.src objectUrl; }); URL.revokeObjectURL(objectUrl); const targetSize 224; const canvas document.getElementById(preview); canvas.width targetSize; canvas.height targetSize; const ctx canvas.getContext(2d); // 计算等比缩放和居中裁剪区域 let sourceX 0; let sourceY 0; let sourceWidth img.width; let sourceHeight img.height; if (img.width / img.height 1) { // 宽图裁剪左右两侧 sourceWidth img.height; sourceX (img.width - sourceWidth) / 2; } else { // 高图裁剪上下两侧 sourceHeight img.width; sourceY (img.height - sourceHeight) / 2; } ctx.drawImage( img, sourceX, sourceY, sourceWidth, sourceHeight, 0, 0, targetSize, targetSize ); return canvas; }这里把裁剪逻辑写清楚了如果图片是宽图就保留中间正方形区域如果图片是高图就保留垂直中间区域。因为tf.browser.fromPixels可以直接读取 canvas 数据所以 canvas 里画成什么样后面得到的张量就是什么样。4.4 推理与向量归一化的代码拆解图片处理好之后接下来的代码把 canvas 转成 tensor、做归一化、推理、取向量async function extractFeature(file) { const canvas await loadImageToCanvas(file); const featureTensor tf.tidy(() { // 从 canvas 读取像素张量:[H,W,3] let tensor tf.browser.fromPixels(canvas); // 转成 float 类型并扩展到 batch 维度:[1,224,224,3] tensor tensor.toFloat().expandDims(0); // MobileNet 标准归一化:像素值从 [0,255] 映射到 [-1,1] tensor tensor.div(127.5).sub(1); // 推理得到 [1,1024] 的特征向量 const feature model.predict(tensor); // 去掉 batch 维度变成 [1024] // 这里用 squeeze 而不是 reshape避免硬编码维度号 const embedding feature.squeeze(); // 计算 L2 范数用于归一化 const norm embedding.norm(); // 归一化:让向量长度变成 1 return embedding.div(norm); }); const vector await featureTensor.data(); return Array.from(vector); }代码里用到的tf.tidy()非常重要。TF.js 里面创建的 tensor 如果不用.dispose()手动释放会一直占用显存。tf.tidy会在回调函数执行完之后自动清理所有临时 tensor只保留返回值。还有个细节使用embedding.squeeze()而不是reshape([1024])是为了避免假设模型的 batch 维度一定为 1。如果你换了一个 batch 维度不是 1 的模型squeeze依然能正确工作。向量归一化这一步很多人都省略了但在以图搜图里不应该省。归一化之后向量模长为 1两个向量之间的欧几里得距离和余弦相似度就变成单调对应的关系。这意味着在检索阶段直接用点积就能计算余弦相似度省掉一次除法开销而且能避免不同图片因为亮度、对比度带来的向量模长差异干扰检索结果。4.5 完整可运行代码把上面几个部分合起来再加上文件输入事件监听document.getElementById(fileInput).addEventListener(change, async (event) { const file event.target.files[0]; if (!file) return; const status document.getElementById(status); const output document.getElementById(output); status.textContent 正在提取特征...; try { const vector await extractFeature(file); status.textContent 特征提取完成; output.textContent 向量长度(维度): vector.length \n 前 20 维: JSON.stringify(vector.slice(0, 20)) \n 向量 L2 范数(应约为 1): Math.sqrt(vector.reduce((sum, v) sum v * v, 0)).toFixed(6); } catch (e) { status.textContent 特征提取失败: e.message; console.error(e); } });这段代码跑通后你可以先验证一个关键点上传同一张图片两次得到的两组向量应该完全一致。这个稳定性的验证非常重要如果同一张图前后两次算出的向量都不一样那么整条检索链路都没有意义。5. 用 1024 维向量搭建以图搜图5.1 入库为图片批量生成特征索引特征提取只是第一步以图搜图还需要把整个图片库批量处理生成索引。这一阶段可以在浏览器里离线路由也可以写一个离线脚本批量跑完再把索引结果存储下来。我准备的离线入库脚本大致逻辑是这样const libraryImages [ { id: 1, url: ./images/cat_01.jpg }, { id: 2, url: ./images/dog_01.jpg }, { id: 3, url: ./images/building_01.jpg }, // ... ]; const libraryVectors []; for (const item of libraryImages) { const img await loadImageFromUrl(item.url); const vector await extractFeatureFromImage(img); libraryVectors.push({ id: item.id, url: item.url, vector: vector }); } // 存储到 localStorage 或者 IndexedDB saveToStorage(libraryVectors);有一个实际中的优化思路值得提一下如果图片库本身就在网页端展示很多图片其实已经被浏览器缓存或渲染过了。这种情况下可以直接拿 DOM 里现有的img元素传入extractFeature不需要额外请求一遍原图能节省不少 IO 时间。5.2 相似度计算为什么选择余弦相似度检索阶段用户上传一张查询图我们得到它的 1024 维归一化向量然后要在特征库里找出“语义上最相似”的图片。在向量空间里衡量两个向量相似度的方式主要有三种欧几里得距离、曼哈顿距离、余弦相似度。欧几里得距离衡量的是空间中的直线距离。余弦相似度衡量的是两个向量方向的夹角度数。以图搜图场景里用余弦相似度更合理。原因是图像特征向量会受到亮度、对比度、缩放等因素影响而这些因素改变的是向量的“长短”也就是模长并不是方向。余弦相似度天生忽略模长只看方向因此对这类亮度、尺寸变化更鲁棒。这两张图虽然所有数字都不一样但方向接近余弦相似度很高。这在图像语义上对应的是“内容相似亮度色调有差异”的情况。这正是我们希望得到的结果。余弦相似度的计算公式是cosine(A, B) (A · B) / (|A| * |B|)因为我们在特征提取阶段已经做了向量归一化|A|和|B|都等于 1所以公式简化为cosine(A, B) A · B在 JavaScript 里求两个归一化向量的点积function cosineSimilarity(vecA, vecB) { let dot 0; for (let i 0; i vecA.length; i) { dot vecA[i] * vecB[i]; } return dot; }这个函数的返回值范围在 [-1, 1] 之间越接近 1 表示语义越相似。如果你对归一化有顾虑也可以保留原始向量在计算时手动除以两个向量的模长结果是一样的。5.3 一个能跑起来的最小演示下面给一个极简的以图搜图演示逻辑假设库里已经有 1000 张图片的特征向量用户上传查询图后逐条计算相似度并排序返回 Top 5。async function searchSimilar(file, topK 5) { const queryVector await extractFeature(file); const library loadFromStorage(); // 从本地存储读取入库向量列表 const results []; for (const item of library) { const score cosineSimilarity(queryVector, item.vector); results.push({ id: item.id, url: item.url, score: score }); } results.sort((a, b) b.score - a.score); return results.slice(0, topK); }这段代码就是整条检索链路的核心。当图片库比较小几千张以内时全量遍历完全没问题但图片库达到几十万上百万张时全量遍历就慢了需要引入近似最近邻ANN索引。ANN 索引在浏览器端的实现方案我会在最后一节展开。6. 性能实测与踩坑手记6.1 我在真机上的实际测量为了给你一个真实参照我记录了在几台设备上的实测数据。测试模型是导出的 MobileNetV11024 维特征向量输入图片经过等比缩放和居中裁剪到 224x224统计的是从拿到 canvas 到特征向量输出出的完整时间。设备浏览器推理后端单次推理耗时桌面 RTX 3060Chrome 126WebGPU35ms桌面 RTX 3060Chrome 126WebGL73ms桌面 i7 无独显Chrome 126WebGPU95ms桌面 i7 无独显Chrome 126WebGL180ms笔记本集显Edge 126WebGPU110ms中端安卓手机Chrome 124WebGL260ms这个表格不代表所有设备但是有几个规律很明确WebGPU 在支持良好的设备上通常比 WebGL 快 40% 到 60%移动端 WebGPU 的普及度还不够高很多手机最后还是走了 WebGL。顺便说个有意思的现象就算后端是 WebGPU首次推理的耗时往往显著高于后续推理可能达到几百毫秒甚至一秒以上。主要是因为 WebGPU 管线需要把模型算子编译成底层着色器程序第一次推理等于做了一次预编译。解决方案很朴素在页面初始化时或用户点击上传前先跑一次哑推理比如推理一张全黑图片让管线预热完成。// 预热:初始化完成后立刻跑一次推理避免首次正式检索卡顿 async function warmup() { const dummyTensor tf.zeros([1, 224, 224, 3]); await model.predict(dummyTensor); dummyTensor.dispose(); }6.2 踩过的坑与解法清单以下这些坑是我在实际开发中遇到的很多都不在官方文档的显眼位置特意记下来分享坑一WebGPU 后端下model.predict返回的 Promise 不是张量TF.js 中 WebGPU 后端的推理行为和其他后端略有差异。WebGL 和 WASM 后端下model.predict通常是同步返回张量而 WebGPU 后端下某些算子会触发异步计算要求你await结果。如果漏了await拿到的可能是一个 Promise 对象后续调用.squeeze()或.data()就会报错。解决方案是在写代码时统一用const feature await model.predict(tensor)不要依赖后端差异。这个习惯能让代码在多个后端间无缝切换。坑二tf.browser.fromPixels只能读取HTMLImageElement、HTMLCanvasElement或ImageDatatf.browser.fromPixels不能直接读取 blob 数据或 File 对象。你不能这样做tf.browser.fromPixels(file); // 报错必须先通过FileReader或URL.createObjectURL把文件转成图片再绘制到 canvas 上才能交给 TF.js。这个流程我在 4.3 小节里已经写好了完整版本。坑三模型的通道顺序必须是 NCHW 还是 NHWCKeras 和 TF.js 默认使用 NHWC宽高在最后通道数是最后一个维度绝大多数预训练 MobileNet 权重也是按 NHWC 格式训练和导出的。但如果你切换框架比如用 PyTorch 导出的 ONNX 模型默认可能是 NCHW通道在第二个维度到了 ONNX Runtime Web 里需要手动设置输入数据的维度顺序很多人会在这里翻车。坑四WebGPU 兼容性检查不能用navigator.gpu一锤定音前面提到过navigator.gpu存在不等于 WebGPU 一定能用。我碰到过navigator.gpu存在但requestAdapter()返回null的情况也碰到过能拿到 adapter 但创建 device 失败的情况。所以初始化逻辑里必须是多层try...catch只要任何一层失败就往下降级。6.3 后续可以继续扩展的方向如果你想把这套浏览器端以图搜图做成真正的生产级系统有以下几个方向值得继续投入第一个方向是海量向量索引。图片库超过十万张以后全量遍历就撑不住了。浏览器端可以考虑用近似最近邻索引方案目前比较成熟的方案是在 WASM 上运行usearch或hnswlib的编译版本配合 IndexedDB 存储索引数据。有了 ANN 索引百万级向量也能在几十毫秒内完成检索。第二个方向是模型升级。MobileNetV1 的 1024 维向量确实够用但如果你遇到检索精度不足的问题可以换 MobileNetV3-Large 或者 EfficientNet-Lite换成 1280 维甚至更高维的特征向量。维度变高检索效果不一定是坏事但有向量维度和存储量之间的权衡。第三个方向是把特征向量存储在 IndexedDB 中实现完全离线的图库检索。这样用户可以把所有本地图片建成一个本地图库查询图直接在其中检索完全不依赖网络。这是浏览器端以图搜图最理想的应用形态之一。最后说一个我的个人经验WebGPU 这套方案目前的稳定性确实不如 WebGL尤其是要在多种型号的 GPU 上做兼容时需要投入不少测试时间。但只要你的核心用户集中在 Chrome 和 Edge 上且设备不算太老WebGPU 带来的性能提升是值得的。我在把生产环境的默认后端切换到 WebGPU 之后用户反馈的“检索慢”问题明显减少了。更重要的是这套架构把 GPU 推理成本从服务端彻底转移到了用户端同时把用户图片的隐私安全等级提高了几个层次这在以图搜图这类涉及图像数据的产品里可以说是最划算的一笔技术投入。
返回列表