自动隐私清理通常不是靠一个检测器完成的。二维码、签名和车牌虽然在产品层面都属于“敏感区域”,但从实现角度看,它们并不是同一种问题,所以更实用的浏览器端工具往往会组合多种策略,而不是强行全部走一条 OCR 流程。
这也是 二维码打码、签名打码工具 和 车牌模糊 这类工具背后更合理的实现方式。这里真正有意思的点在于,不同目标需要依赖不同信号。
一条通用流水线不够用
从产品角度看,这三类目标都只是需要生成敏感区域。但从技术角度看,它们差别很大:
- 二维码和条码是机器可读符号,浏览器本身就可能提供检测能力。
- 车牌更适合走 OCR 加启发式过滤。
- 签名更接近图像形状检测,而不是文本识别。
如果想用一次通用 OCR 解决这三类问题,结果通常不会太理想。
二维码和条码:浏览器已经会的事就直接用
对于二维码和条码,最干净的实现方式通常是优先使用 BarcodeDetector,前提是当前浏览器支持它。
const detector = new BarcodeDetector({
formats: ["qr_code", "code_128", "ean_13", "pdf417"],
});
const results = await detector.detect(source);每个结果都会返回一个 bounding box,再经过适当 padding 就可以变成更安全的遮挡区域。这样就能用浏览器原生能力处理机器可读符号,而不必从零手写一套视觉检测。
这里真正重要的产品决策是 fallback。假如 BarcodeDetector 不可用,界面应该明确告诉用户,而不是默默假装“没有检测到二维码”。
车牌:OCR 加过滤规则,比原始 OCR 更实用
车牌本质上是文本,但它不是普通文本。这个项目里的做法,是先从 OCR block 里拿候选文本,再叠加一组专门针对车牌的过滤条件:
- 先把文本标准化成大写字母和数字
- 必须同时包含字母和数字
- 过滤掉长度明显不合理的结果
- 过滤掉长宽比不合理的区域
- 忽略出现在图像过高位置的文本
这种模式很实用,因为 OCR 负责提供候选内容,而启发式规则负责去掉大量原本会变成误判的噪声。
签名:把它当成图像连通区域,而不是单词
手写签名正好相反。OCR 在这里往往不够可靠,所以这个代码里的检测路径采用的是基于缩放 canvas 的像素分析:
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
const threshold = estimateSignatureThreshold(data);然后算法会遍历连通的深色像素区域,再根据宽度、高度、填充率和位置筛掉不合理候选,只保留更像签名的区域。
这也说明一个现实:隐私工具经常需要很窄、很具体的启发式规则,而不是一个包打天下的统一模型。
检测后还需要 padding、合并和去重
检测出候选区域,只完成了一半工作。后面还必须处理这些事情:
- 给边界加 padding,确保敏感内容被完整覆盖
- 如果一个签名或码被拆成多段,要合并相邻区域
- 如果结果重叠,要做去重
如果跳过这一步,编辑器里就会出现一堆碎片化的小框,用户很难信任结果。
把检测结果变成可编辑叠加层
一个真正可用的产品,不应该把模糊或打码结果悄悄直接应用到图片上,而应该把这些候选区域作为自动生成的叠加层插入编辑器,交给用户复查。
这样即使不同检测器的内部原理完全不同,最终在交互层也可以统一成同一种模型。
混合检测器比较合理的架构
更实用的边界通常是:
- 一个统一的编辑器表面
- 多个独立的检测函数
- 一种统一的区域数据结构
- 导出前统一走一次复查
这样无论接入的是 OCR、条码 API,还是像素级启发式规则,都不需要每加一种检测器就重做一套编辑逻辑。
真正让它有用的点
这里最重要的技术经验其实很简单:隐私检测的效果,往往来自于你不再试图寻找一个万能检测器。浏览器端工具只有在不同目标类型用上合适的信号时,才会真正变得可靠。
