前言
最近在探索前端人脸识别技术,发现face-api.js是一个很强大的库,可以在浏览器中直接运行人脸检测、特征提取等模型。结合感知哈希算法,可以快速实现人脸相似度比对。本文将详细解析一个我开发的轻量级演示页面:它支持上传两张图片,本地检测人脸,计算感知哈希相似度,并允许用户对结果进行点赞/点踩,所有统计数据通过后端API保存。适合对前端AI、Canvas操作、文件上传交互感兴趣的开发者参考。
一、项目概述
这是一个纯前端(含少量后端交互)的人脸比对演示工具。主要功能:
- 加载face-api.js的SSD Mobilenet V1、人脸关键点、人脸识别模型。
- 双栏图片上传(支持点击或拖拽),实时预览。
- 人脸检测并绘制边界框和关键点。
- 点击“运行人脸比对”按钮,基于感知哈希算法计算两张图片的相似度。
- 展示比对结果(相似度百分比、汉明距离、结论)。
- 用户可以对每次比对结果进行“点赞”或“点踩”,每完成一次对比,对比次数+1。
- 所有计数(对比次数、点赞数、点踩数)通过后端API同步,避免刷新丢失。
界面采用毛玻璃设计(backdrop-filter),整体风格清爽。
二、技术栈
- HTML5/CSS3:结构、样式,毛玻璃效果使用 backdrop-filter: blur()。
- JavaScript (ES6):逻辑交互。
- face-api.js:人脸检测、关键点定位、特征描述符(用于绘制,实际相似度计算未使用其识别网络,仅用于检测和绘图)。
- Canvas API:绘制人脸框、缩放图像、灰度转换等。
- 感知哈希(pHash)算法:自行实现,用于计算图像相似度。
- Fetch API:与后端交互,获取/更新统计数据。
后端接口(未开源,但逻辑简单):
GET /api/get_stats:返回当前统计{ compareCount, likeCount, dislikeCount }POST /api/update_stats:接收{ type: 'compare'|'like'|'dislike' },更新对应计数并返回最新统计。
三、界面设计要点
页面采用双栏布局,左右对称,分别对应图片A和B。主要区域包括:
- 模型状态栏:显示模型加载进度,用圆点颜色表示状态(黄色加载中,绿色完成,红色失败)。
- 反馈面板:
- 当前完成对比次数(只读输入框)。
- 点赞/点踩按钮,显示当前计数,并限制每次对比只能评价一次。
- 图片上传面板:
- 每个面板包含上传按钮、清空按钮、预览容器。
- 支持点击上传和拖拽上传(通过监听drag事件)。
- 预览容器内会动态绘制检测到的人脸框和关键点。
- 比对按钮:只有模型加载完成后才启用。
- 结果输出区:只读文本域,显示详细的比对结果和算法说明。
所有容器都有半透明毛玻璃效果,背景为浅灰渐变,营造科技感。
四、核心功能实现详解
1. 模型加载
使用face-api.js的load方法从CDN加载权重文件。顺序加载:ssdMobilenetv1(检测)、faceLandmark68Net(关键点)、faceRecognitionNet(识别,实际未使用但加载以备后续)。加载成功后,按钮可用,并调用loadStats()从后端获取初始统计数据。
await faceapi.nets.ssdMobilenetv1.load(CONFIG.MODEL_BASE_URL);
await faceapi.nets.faceLandmark68Net.load(CONFIG.MODEL_BASE_URL);
await faceapi.nets.faceRecognitionNet.load(CONFIG.MODEL_BASE_URL);
2. 图片上传与预览
为两个文件输入框绑定change事件,读取文件后用FileReader生成DataURL,设置给对应的<img>元素。同时清空之前可能绘制的人脸框Canvas。
拖拽上传:在预览容器上监听dragover、dragleave、drop事件,将文件对象取出并调用相同的处理函数。
3. 人脸检测与绘制
drawFaceBoxes函数接收图片元素和容器ID,完成以下工作:
- 移除旧Canvas。
- 创建新的Canvas覆盖在图片上,并设置样式为绝对定位。
- 调用
faceapi.detectAllFaces(img).withFaceLandmarks().withFaceDescriptors()获取检测结果。 - 使用
faceapi.matchDimensions调整Canvas尺寸,然后用faceapi.draw.drawDetections和faceapi.draw.drawFaceLandmarks绘制边界框和关键点。 - 返回检测结果数组,供后续计数。
注意:此处的绘制是在单独的Canvas上,不影响原始图片。
4. 感知哈希算法
感知哈希(pHash)是一种计算图像相似度的经典算法,流程:
- 将图像转为灰度图(
getGrayImageData遍历像素计算灰度值)。 - 缩放至8×8像素(
resizeGrayData使用最近邻采样)。 - 计算64个像素的均值。
- 生成64位二进制哈希:每个像素值大于均值为1,否则为0。
- 汉明距离:两个哈希值不同位的个数。
- 相似度转换:距离越大越不相似,最大距离64,相似度 = 100 – (distance/64)*100。
此算法抗干扰能力强,对缩放、亮度变化有一定鲁棒性,但严格来说不适合作为人脸识别(可能将不同人误判为相似),但作为轻量演示足够。
5. 比对逻辑
compareFaces函数为入口:
- 禁用按钮,显示计算中。
- 调用
drawFaceBoxes获取检测结果并绘制。 - 检查两张图是否都检测到人脸,若无则报错。
- 分别生成两张图片的感知哈希。
- 计算汉明距离和相似度。
- 构造结果文本(包含相似度、距离、人脸数、结论、算法说明)。
- 提交
compare统计(submitStats('compare')),并重置投票状态,允许对本次结果进行评价。 - 异常处理,显示错误提示。
6. 点赞/点踩
- 点赞和点踩按钮分别绑定点击事件。
- 检查是否已经评价过本次对比(
hasVotedForCurrentCompare标志)。 - 调用
submitStats传入'like'或'dislike'。 - 后端返回最新统计后更新UI。
- 如果后端请求失败,本地计数增加,保证用户体验(防崩溃)。
7. 后端交互
使用Fetch API:
loadStats()在页面加载时调用,填充计数。submitStats(type)发送POST请求,并在请求成功后更新UI;失败时本地兜底。
后端API预期返回JSON格式:{ compareCount, likeCount, dislikeCount }。
五、关键代码片段解析
灰度图像数据获取
function getGrayImageData(img) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const grayData = [];
for (let i = 0; i < imageData.data.length; i += 4) {
const gray = Math.floor((imageData.data[i] + imageData.data[i+1] + imageData.data[i+2]) / 3);
grayData.push(gray);
}
return { data: grayData, width: canvas.width, height: canvas.height };
}
感知哈希生成
function generatePHash(img) {
const gray = getGrayImageData(img);
const smallSize = 8;
const scaled = resizeGrayData(gray, smallSize, smallSize);
const avg = scaled.data.reduce((sum, val) => sum + val, 0) / scaled.data.length;
let hash = '';
for (const val of scaled.data) {
hash += val > avg ? '1' : '0';
}
return hash;
}
提交统计数据(带容错)
async function submitStats(type) {
try {
const res = await fetch(CONFIG.API_BASE + '/update_stats', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type })
});
const data = await res.json();
stats.compareCount = data.compareCount ?? stats.compareCount;
stats.likeCount = data.likeCount ?? stats.likeCount;
stats.dislikeCount = data.dislikeCount ?? stats.dislikeCount;
updateUI();
} catch (err) {
console.error('更新失败', err);
// 本地兜底
if(type === 'like') stats.likeCount++;
if(type === 'dislike') stats.dislikeCount++;
if(type === 'compare') stats.compareCount++;
updateUI();
}
}
六、用户体验细节
- 每次上传新图片时,自动清空之前的结果,并移除人脸框Canvas。
- 清空按钮释放Blob URL,避免内存泄漏。
- 拖拽上传时会有边框高亮反馈。
- 模型加载失败时,按钮保持禁用,并提示用户刷新。
- 点赞/点踩限制:必须进行过至少一次对比,且每次对比只能评价一次,防止刷票。
七、可扩展性与改进方向
- 使用更精准的算法:目前基于pHash,可以替换为face-api.js的人脸识别描述符,计算欧氏距离,准确度更高。
- 增加多张人脸选择:如果一张图有多张人脸,可以让用户选择比对哪一对。
- 后端持久化:当前后端仅内存存储,可接入数据库,记录历史评价。
- 移动端适配:虽然已有媒体查询,但拖拽在移动端可能不友好,可增加备选方案。
- 国际化:界面文字可配置多语言。
- 批量测试:允许上传文件夹,批量计算相似度。
八、总结
这个项目展示了如何结合现代前端技术和传统图像处理算法,快速搭建一个有趣的人脸比对演示工具。通过face-api.js简化了人脸检测的复杂性,自行实现的pHash算法避免了依赖外部API,保证了隐私和速度。同时,点赞/点踩功能增加了互动性,后端交互让数据得以持久化。
如果你对前端AI应用感兴趣,不妨从这个项目入手,尝试替换算法或增加新特性。完整的代码已附在文中(用户提供的HTML),你可以直接复制运行体验。
欢迎在评论区交流讨论!
配图建议:
- 网页整体截图,标注各区域功能
- 人脸检测绘制效果图(带框和关键点)
- 感知哈希流程图(原图→灰度→8×8→哈希)
- 后端交互时序图
分类标签: #人脸识别 #face-api #JavaScript #感知哈希 #前端开发


这网页真实用,人脸识别又快又准,比对起来特别方便!
这个网页太实用了,人脸比对又快又准,推荐给大家!
这个网页实现太实用了,人脸识别又快又准,体验感满分!
这网页真方便,人脸识别比对超准,推荐给需要的人!
这个网页实现很实用,人脸比对准确又方便,适合需要快速识别的场景!
这个网页实现挺有意思的,用起来也方便,人脸比对很准确!
这个网页实现太棒了,人脸识别又快又准,用起来超方便!
这个网页实用又方便,人脸比对效果不错,推荐给大家!
这个网页实现太棒了!用face-api.js和感知哈希做人脸比对,技术很实用,体验也很好!
这个网页实现挺实用的,用起来也方便,人脸比对效果不错!
这个网页实现很实用,人脸识别和哈希比对结合得不错,体验流畅!
这个网页实用又方便,人脸比对效果很准,推荐给大家!
这个网页太实用了,人脸识别又准又方便,推荐给大家!
这个网页实现很实用,人脸比对功能准确又方便,适合日常使用。
这个网页实现很实用,人脸识别和哈希比对结合得恰到好处,方便又高效!
我不是机器人