人脸检测一直是计算机视觉领域的经典应用,过去这类功能基本依赖服务端完成:前端上传图片,后端调用OpenCV或深度学习模型处理后再返回结果。这种架构不仅占用服务器资源,还涉及图片隐私传输的问题。如今浏览器已经提供了原生的Shape Detection API,其中包含的FaceDetector接口可以在本地直接完成人脸检测,而SQLite借助WASM编译版本sql.js也能完整跑在浏览器里。把这两者结合,我们可以搭建一个完全离线的人脸检测数据管理系统,检测出的人脸坐标、置信度、图片指纹全部存入本地数据库,数据不出浏览器。

一、环境准备与技术原理
先说Shape Detection API这部分。它由W3C提出,包含三个检测器:人脸检测FaceDetector、条形码检测BarcodeDetector和文本检测TextDetector。FaceDetector的核心能力是接收一个图片源(可以是img元素、canvas或者ImageBitmap),返回检测到的人脸边界框以及可选的五官特征点。它底层调用的是操作系统的原生能力,比如Windows的Windows Hello组件、macOS的Core Image,所以速度非常快,而且不需要下载任何模型文件。
再看SQLite这边。浏览器里运行SQLite的主流方案是sql.js,它是把SQLite的C源码编译成WebAssembly,提供一套JavaScript API。需要注意sql.js的数据库默认放在内存里,页面刷新就没了,所以必须配合IndexedDB做持久化,把导出的二进制数据定期保存下来。这两者的分工很明确:FaceDetector负责算,SQLite负责存。
环境搭建方面,sql.js可以直接从CDN引入,也可以用npm安装后配合webpack或vite打包。下面用CDN方式演示,最简单直接:
<script src="https://sql.js.org/dist/sql-wasm.js"></script>
<script>
// 初始化SQL.js,指定wasm文件路径
const initSQL = async () => {
const SQL = await initSqlJs({
locateFile: file => `https://sql.js.org/dist/${file}`
});
return new SQL.Database();
};
</script>二、设计人脸检测数据表结构
存储检测结果之前,得先想清楚表结构。一次人脸检测会产生哪些有价值的数据?至少包括:图片的唯一标识、人脸在图片中的位置坐标(边界框的x、y、宽、高)、检测置信度、检测时间戳,以及可选的五官特征点数据。如果同一个图片里有多张人脸,就是一对多的关系。
我建议拆成两张表:images表存图片的基本信息,faces表存每张检测到的人脸,用外键关联。这样后续按图片查询人脸、统计人脸总数都很方便。建表语句如下:
CREATE TABLE IF NOT EXISTS images ( id INTEGER PRIMARY KEY AUTOINCREMENT, image_name TEXT NOT NULL, image_hash TEXT UNIQUE, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS faces ( id INTEGER PRIMARY KEY AUTOINCREMENT, image_id INTEGER NOT NULL, x REAL, y REAL, width REAL, height REAL, confidence REAL, landmarks TEXT, FOREIGN KEY (image_id) REFERENCES images(id) ); -- 常用查询:统计每张图片的人脸数 SELECT i.image_name, COUNT(f.id) AS face_count FROM images i LEFT JOIN faces f ON i.id = f.image_id GROUP BY i.id;
其中landmarks字段用TEXT存储JSON序列化的特征点数组,这是SQLite处理半结构化数据的常见做法。image_hash字段建议存图片内容的SHA-256摘要,用来做去重,同一张图片重复检测时可以直接跳过或更新。
三、实现检测与入库的完整流程
核心流程分四步:读取图片文件、执行人脸检测、写入数据库、持久化到IndexedDB。FaceDetector的用法很简单,构造时可以传入配置项,fastMode表示优先速度,maxDetectedFaces限制最大检测数量。
// 检查浏览器是否支持人脸检测
if (!('FaceDetector' in window)) {
alert('当前浏览器不支持Shape Detection API,请使用Chrome并开启实验特性');
}
const detector = new FaceDetector({
fastMode: true,
maxDetectedFaces: 10
});
async function detectAndStore(file, db) {
// 第一步:将文件转为ImageBitmap,检测效率比img元素高
const bitmap = await createImageBitmap(file);
// 第二步:执行人脸检测
const faces = await detector.detect(bitmap);
// 第三步:计算图片哈希用于去重
const buffer = await file.arrayBuffer();
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
const hash = Array.from(new Uint8Array(hashBuffer))
.map(b => b.toString(16).padStart(2, '0')).join('');
// 第四步:写入SQLite
db.run('INSERT OR IGNORE INTO images (image_name, image_hash) VALUES (?, ?)',
[file.name, hash]);
const imageId = db.exec('SELECT id FROM images WHERE image_hash = ?')[0].values[0][0];
const insertFace = db.prepare(
'INSERT INTO faces (image_id, x, y, width, height, confidence, landmarks) VALUES (?, ?, ?, ?, ?, ?, ?)'
);
for (const face of faces) {
const box = face.boundingBox;
insertFace.run([imageId, box.x, box.y, box.width, box.height,
face.landmarks ? JSON.stringify(face.landmarks) : null]);
}
insertFace.free();
bitmap.close();
return faces.length;
}这段代码有几个细节值得注意。使用createImageBitmap而不是FileReader转dataURL,是因为FaceDetector对ImageBitmap的处理更快,而且检测完调用close()可以及时释放内存。参数化查询用prepare加run的方式,在循环插入多条人脸记录时性能明显优于每次都执行完整SQL解析。
四、持久化存储与数据查询
前面提到sql.js的数据库在内存中,持久化方案是把db.export()导出的Uint8Array存进IndexedDB,下次页面加载时再读回来恢复。这个导出操作有一定开销,不适合每插一条数据就执行一次,建议在批次操作完成后统一执行。
// 保存数据库到IndexedDB
function saveDatabase(db) {
const data = db.export();
return new Promise((resolve, reject) => {
const req = indexedDB.open('face-db', 1);
req.onupgradeneeded = () => req.result.createObjectStore('kv');
req.onsuccess = () => {
const tx = req.result.transaction('kv', 'readwrite');
tx.objectStore('kv').put(data, 'sqlite');
tx.oncomplete = () => resolve();
};
req.onerror = () => reject(req.error);
});
}
// 查询历史检测记录
function queryFaceRecords(db, minConfidence = 0.5) {
const result = db.exec(
`SELECT i.image_name, f.x, f.y, f.width, f.height, f.confidence, i.created_at
FROM faces f JOIN images i ON i.id = f.image_id
WHERE f.confidence >= ?
ORDER BY i.created_at DESC`, [minConfidence]);
return result.length ? result[0].values : [];
}数据恢复时注意版本兼容问题:如果后续修改了表结构,需要在加载旧数据后执行ALTER TABLE或建新表的迁移逻辑,可以用PRAGMA user_version记录数据库版本来管理。
五、兼容性与性能优化建议
Shape Detection API目前只有Chrome系浏览器实现了FaceDetector,Firefox和Safari的支持情况参差不齐,生产环境必须做能力检测并准备降级方案。常见的降级路径是改用TensorFlow.js加载人脸检测模型,虽然需要下载模型文件,但兼容性全覆盖。
性能方面有三点经验:一是批量处理图片时,控制并发数量,ImageBitmap同时创建太多会撑爆内存,建议用队列限制在3到4个并发;二是检测大图前先缩放,FaceDetector对超大分辨率图片的处理时间和图片尺寸正相关,先把长边压到1000像素左右再检测,准确率几乎不受影响;三是SQLite写入尽量走事务,db.run('BEGIN')和db.run('COMMIT')包裹批量插入,速度能提升数倍,因为每次单独执行都会隐式开启事务。
最后提醒一点,人脸属于敏感生物特征信息,即使是纯本地存储,也应该在界面上明确告知用户数据的存储位置和清除方式,提供一键删除数据库的功能。技术上可以用db.run('DELETE FROM faces')配合saveDatabase实现,让用户对自己的数据有完全的控制权,这也是合规的基本要求。
SQLiteShape Detection人脸检测修改时间:2026-09-06 12:04:39