用 JavaScript 做司机端证件识别?我差点被产品经理送走

知识库管理员
2026-01-13 14:17
阅读 1924

去年双11前两周,我们司机端产品组突然接到一个“紧急需求”:上线司机上传身份证、驾驶证自动识别功能。PM 原话是:“技术上应该不难吧?不就是拍个照,系统读一下文字?”
我当场差点把咖啡喷到显示器上。

作为滴滴干了四年后端的老兵,我主要负责司机端的核心业务逻辑——订单匹配、接单状态机、服务分计算这些“稳如老狗”的模块。平时写 Java、调 Dubbo、怼 MySQL 是日常,但突然让我搞计算机视觉(CV)?还是用 JavaScript?我心里一万个问号。

但没办法,项目 deadline 就在眼前,后端人力紧张,而前端小哥刚好对 TensorFlow.js 有点兴趣。于是我们俩一合计:硬着头皮上!


为啥不用 Python + OCR API?

说实话,第一反应是直接调阿里云或百度的 OCR 接口。稳定、准确、还能开票报销。但 PM 甩出两个现实问题:

  1. 成本敏感:每天几十万次证件上传,按次计费吃不消;
  2. 隐私合规:司机证件属于敏感信息,不能随便传第三方。

那就只能自研了。考虑到司机 App 是混合开发(React Native + WebView),最终方案定为:前端用 JavaScript 在浏览器/WebView 内做轻量级 CV 预处理 + 文字区域检测,后端用 Java 调 Tesseract 做高精度识别。这样既能减少网络传输,又能规避隐私风险。

但第一步,得让前端能“看到”证件在哪。


从零搭建:用 JS 实现证件区域检测

我们没打算从头训练一个目标检测模型(那得标注几千张图,我可不想周末加班)。于是盯上了开源社区的轻量级方案 —— TensorFlow.js + MobileNet-SSD

安装依赖很简单:

npm install @tensorflow/tfjs @tensorflow-models/coco-ssd

但坑马上来了:coco-ssd 默认只支持 COCO 数据集的 80 类物体,根本没有“身份证”、“驾驶证”这种类别!😅

自己造数据,自己打标签

我和前端小哥花了三天,从内部测试账号里扒了 500 多张真实司机上传的证件照片(脱敏后),用 LabelImg 手动标出 bounding box。然后用 Roboflow 做数据增强:旋转、亮度调整、加噪——毕竟司机拍照环境千奇百怪,有的在地下车库,有的对着反光玻璃拍。

接着,用 TensorFlow 的 tfjs-converter 把训练好的模型(我们在 Colab 上用 EfficientDet-D0 微调)转成 Web 可用的 JSON 格式:

tensorflowjs_converter \
  --input_format=tf_saved_model \
  --output_format=tfjs_graph_model \
  saved_model/ \
  web_model/

部署到 CDN 后,前端加载模型代码长这样:

import * as tf from '@tensorflow/tfjs';
import * as cocoSsd from '@tensorflow-models/coco-ssd';

// 加载自定义模型(非官方coco)
const model = await tf.loadGraphModel('https://cdn.xxx.com/id-card/model.json');

// 输入图片元素
const img = document.getElementById('upload-img');
const predictions = await model.executeAsync(tf.browser.fromPixels(img));

// 解析输出:[x, y, width, height, classId, score]
const boxes = parseOutput(predictions);

📌 注意:这里没用 cocoSsd.load(),因为那是封装好的 COCO 模型。我们直接 loadGraphModel 加载自定义权重。


算法选择:为什么不用 YOLO?

其实我私底下试过 YOLOv5 的 JS 版本,但发现:

  • 模型体积太大(>15MB),WebView 加载慢;
  • 在低端安卓机上推理速度掉到 3fps,司机等得想卸载 App;
  • 内存占用高,容易触发 RN 的 OOM。

最后选了 EfficientDet-D0 + TensorFlow.js 的 WebGL 后端,模型压缩后仅 4.2MB,在骁龙 660 机型上也能跑 12fps。关键配置如下:

配置项 说明
输入尺寸 320x320 平衡精度与速度
NMS 阈值 0.4 避免同一证件被框多次
置信度阈值 0.65 低于此值视为无效
后端 webgl 必须开启 GPU 加速

开启 WebGL 的代码必须放在最前面:

tf.setBackend('webgl').then(() => {
  console.log('Using WebGL backend');
});

否则默认用 CPU,慢到怀疑人生。


踩过的坑:那些让我想砸电脑的瞬间

坑 1:图片 EXIF 方向错乱

司机用 iPhone 拍照,上传后图片在 WebView 里旋转了 90 度。结果模型检测框歪得离谱。
解决方案:用 exif-js 读取 Orientation,前端先矫正再送入模型。

EXIF.getData(file, function() {
  const orientation = EXIF.getTag(this, "Orientation");
  // 根据 orientation 旋转 canvas...
});

坑 2:模型在 iOS 和 Android 表现不一致

iOS Safari 对 WebGL 支持更好,Android WebView 则要看厂商(华为还行,小米某些机型直接黑屏)。
最后我们加了兜底策略:如果检测失败或耗时 >2s,自动降级为“手动框选区域”,再传给后端识别。

坑 3:算法准确率 vs 用户体验

初期模型召回率只有 78%,经常漏检。PM 急得天天站我工位后面“关怀”。
后来我们加了一个 trick:如果检测不到证件,就用边缘检测(Canny)+ 轮廓分析找矩形区域,作为备选 ROI(Region of Interest)。虽然不准,但至少能引导用户重新拍照。

JS 实现轮廓近似(简化版):

function findRectContours(imageData) {
  // 转灰度 → 高斯模糊 → Canny 边缘 → 找轮廓
  const edges = cannyEdgeDetection(imageData);
  const contours = findContours(edges);
  
  return contours
    .filter(c => isRectangle(c)) // 判断是否接近矩形
    .sort((a, b) => area(b) - area(a))[0]; // 取最大面积
}

这招让整体成功率从 78% 提升到 92%,PM 终于不再半夜微信轰炸我了。


效果与反思

上线一个月后,数据显示:

  • 87% 的证件能被前端准确框出;
  • 平均识别耗时从 2.1s 降到 0.8s(减少后端压力);
  • 用户重拍率下降 40%。

最重要的是,没再因为这个需求和 PM 对线 😅。

但我也深刻体会到:在移动端做 CV,算法不是唯一瓶颈,工程适配才是地狱。你得考虑机型碎片化、内存限制、用户网络、甚至手机壳反光……

现在回看,如果重来一次,我会更早引入 ONNX Runtime WebWebAssembly 版 OpenCV,可能比 TF.js 更灵活。不过话说回来,在滴滴这种强业务驱动的公司,能快速交付、稳定运行,比追求 SOTA(State-of-the-Art)更重要。

毕竟,司机师傅们不在乎你用了 YOLO 还是 SSD,他们只关心——能不能一次过审,早点接单赚钱


给想搞 CV 的前端/全栈同学几点建议

  1. 别迷信端侧 AI:除非场景简单、数据量小,否则优先考虑“端侧预处理 + 云侧精识”;
  2. 模型越小越好:4MB 是移动端的心理阈值,超过就得有充分理由;
  3. 兜底策略必须有:AI 不是 100% 可靠,用户体验要靠“人机协作”兜住;
  4. 别自己造轮子:像证件检测这种通用场景,阿里、腾讯都有 SDK,评估成本后再决定自研。

最后,如果你也在上海租房、加班改需求、被 PM 虐得想转行……欢迎来陆家嘴附近喝杯瑞幸,聊聊怎么用 JS 让世界少一点 Bug,多一点接单成功的提示音 🚗💨

评论 0

最热最新
暂无评论
知识库管理员Lv.1
0
影响力
0
文章
0
粉丝