face-api.js中detectAllFaces()无法识别人脸且forEach报错的问题
我正在使用face-api.js开发一款Web应用,功能是让用户上传图片并检测其中的人脸。但每当选择图片时,results变量为空,同时出现错误:
Uncaught (in promise) TypeError: results.forEach is not a function
我的HTML代码
<input type="file" id="user_pic" accept="image/x-png,image/gif,image/jpeg"> <img src="images/250x250.webp" id="preview" alt="">
我的JavaScript代码
document.addEventListener('DOMContentLoaded', function() { run() }); async function run() { // load the models await faceapi.loadMtcnnModel('../faceapi_models') await faceapi.loadFaceRecognitionModel('../faceapi_models') } const user_pic = document.getElementById('user_pic') const preview = document.getElementById('preview') user_pic.addEventListener('change', () => { const reader = new FileReader() reader.onload = (e) => { preview.src = e.target.result } reader.readAsDataURL(user_pic.files[0]); detectFaces(user_pic.files[0]) }) preview.onclick = () => user_pic.click() async function detectFaces(input) { let imgURL = URL.createObjectURL(input) const imgElement = new Image() imgElement.src = imgURL const results = faceapi.detectAllFaces(imgElement) .withFaceLandmarks() .withFaceExpressions() console.log(results) results.forEach(result => { const { x, y, width, height } = result.detection.box; ctx.strokeRect(x, y, width, height); }); }
问题原因及修复方案
1. 未等待异步检测结果
faceapi.detectAllFaces()及其链式调用的方法都是异步的,返回的是Promise对象,不是直接的检测结果数组。直接调用forEach会报错,因为Promise没有这个方法。
修复:在faceapi.detectAllFaces()前添加await,确保获取到实际的检测结果数组:
const results = await faceapi.detectAllFaces(imgElement) .withFaceLandmarks() .withFaceExpressions()
2. 图片未加载完成就执行检测
创建Image对象后直接设置src,此时图片还在加载中,face-api无法读取图片内容进行检测,导致results为空。
修复:等待图片加载完成后再执行检测,用Promise包装图片加载过程:
async function detectFaces(input) { let imgURL = URL.createObjectURL(input) const imgElement = new Image() // 等待图片加载完成 await new Promise((resolve) => { imgElement.onload = resolve imgElement.src = imgURL }) const results = await faceapi.detectAllFaces(imgElement, new faceapi.MtcnnOptions()) .withFaceLandmarks() .withFaceExpressions() console.log(results) // 后续处理... }
注意:因为加载的是MTCNN模型,检测时需要传入new faceapi.MtcnnOptions(),否则会默认使用SSD模型,未加载SSD模型会导致检测失败。
3. 缺少Canvas上下文
代码中使用了ctx.strokeRect()但没有初始化Canvas元素和上下文。需要添加Canvas来绘制人脸框,并确保它和预览图片的位置、尺寸匹配:
首先在HTML中添加Canvas:
<input type="file" id="user_pic" accept="image/x-png,image/gif,image/jpeg"> <div style="position: relative;"> <img src="images/250x250.webp" id="preview" alt=""> <canvas id="detectionCanvas" style="position: absolute; top: 0; left: 0;"></canvas> </div>
然后在JavaScript中获取Canvas上下文,并调整尺寸:
const canvas = document.getElementById('detectionCanvas') const ctx = canvas.getContext('2d') async function detectFaces(input) { // ... 图片加载代码 ... // 调整Canvas尺寸和图片一致 canvas.width = imgElement.width canvas.height = imgElement.height ctx.clearRect(0, 0, canvas.width, canvas.height) const results = await faceapi.detectAllFaces(imgElement, new faceapi.MtcnnOptions()) .withFaceLandmarks() .withFaceExpressions() if (results.length === 0) { console.log('未检测到人脸') return } results.forEach(result => { const { x, y, width, height } = result.detection.box; ctx.strokeStyle = '#ff0000' ctx.lineWidth = 2 ctx.strokeRect(x, y, width, height); }); }
4. 模型路径校验
确保../faceapi_models路径下存在MTCNN和FaceRecognition的模型文件(比如mtcnn_model-weights_manifest.json、face_recognition_model-weights_manifest.json等),路径错误会导致模型加载失败,进而检测无结果。
完整修正后的JavaScript代码
document.addEventListener('DOMContentLoaded', function() { run() }); const canvas = document.getElementById('detectionCanvas') const ctx = canvas.getContext('2d') const user_pic = document.getElementById('user_pic') const preview = document.getElementById('preview') async function run() { try { // 加载模型,添加错误捕获 await faceapi.loadMtcnnModel('../faceapi_models') await faceapi.loadFaceRecognitionModel('../faceapi_models') console.log('模型加载成功') } catch (err) { console.error('模型加载失败:', err) } } user_pic.addEventListener('change', () => { const file = user_pic.files[0] if (!file) return const reader = new FileReader() reader.onload = (e) => { preview.src = e.target.result } reader.readAsDataURL(file); detectFaces(file) }) preview.onclick = () => user_pic.click() async function detectFaces(input) { let imgURL = URL.createObjectURL(input) const imgElement = new Image() await new Promise((resolve) => { imgElement.onload = resolve imgElement.src = imgURL }) // 匹配Canvas和图片尺寸 canvas.width = imgElement.width canvas.height = imgElement.height ctx.clearRect(0, 0, canvas.width, canvas.height) try { const results = await faceapi.detectAllFaces(imgElement, new faceapi.MtcnnOptions()) .withFaceLandmarks() .withFaceExpressions() console.log('检测结果:', results) if (results.length === 0) { console.log('未检测到人脸') return } results.forEach(result => { const { x, y, width, height } = result.detection.box; ctx.strokeStyle = '#ff0000' ctx.lineWidth = 2 ctx.strokeRect(x, y, width, height); }); } catch (err) { console.error('检测失败:', err) } finally { // 释放URL对象 URL.revokeObjectURL(imgURL) } }
内容的提问来源于stack exchange,提问作者Ahmad Badpey

