H5人脸识别(tracking.js),并保存人脸为图片(也可保存人脸部分)

1. H5人脸识别功能

1.第三方库 tracking.js

前端人脸识别框架:https://trackingjs.com
下载zip包获取里面的 face.min.js 及 tracking-min.js

<script src="./js/tracking-min.js"></script>
<script src="./js/face-min.js"></script>

2. 判断有无摄像头权限

			if (navigator.mediaDevices === undefined) {
    
    
				navigator.mediaDevices = {
    
    }
			}
			if (navigator.mediaDevices.getUserMedia === undefined) {
    
    
				navigator.mediaDevices.getUserMedia = function(constraints) {
    
    
					var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
					if (!getUserMedia) {
    
    
						return Promise.reject(new Error("getUserMedia is not implemented in this browser"));
					}
					return new Promise(function(resolve, reject) {
    
    
						getUserMedia.call(navigator, constraints, resolve, reject);
					})
				}
			}

			navigator.mediaDevices.getUserMedia({
    
    
				audio: false,
				video: {
    
    
					facingMode: "environment"
				}
			}).then(function(stream) {
    
    })
	

3. 打开摄像头,并开始监听

			
			navigator.mediaDevices.getUserMedia({
    
    
				audio: false,
				video: {
    
    
					facingMode: "environment"
				}
			}).then(function(stream) {
    
    
				// 使用监听人脸的包
				tracker.setInitialScale(4);
				tracker.setStepSize(2);
				tracker.setEdgesDensity(0.1);

				//打开摄像头
				var tra = tracking.track('#video_bind', tracker, {
    
    
					camera: true
				});
				var timer = null;
				// 创建监听 每帧都会触发
				tracker.on('track', function(event) {
    
    
					if (!tipFlag) {
    
    
						facecontext.clearRect(0, 0, facecanvas.width, facecanvas.height);
						if (event.data.length === 0) {
    
    
							//未检测到人脸
							if (!faceflag && !timer) {
    
    
								timer = setTimeout(() => {
    
    
									informationTitle.innerHTML = '未检测到人脸'
								}, 500)
							}
						} else if (event.data.length === 1) {
    
     // 长度为多少代表检测到几张人脸
							window.clearTimeout(timer);
							timer = null;
							informationTitle.innerHTML = '请将脸部置于屏幕中央';
							//检测到一张人脸
							if (!tipFlag) {
    
    
								// 给检测到的人脸绘制矩形
								event.data.forEach(function(rect) {
    
    
									facecontext.strokeStyle = '#a64ceb';
									facecontext.strokeRect(rect.x, rect.y, rect.width, rect.height);
								});
								let rect = event.data[0];
								//判断脸部是否在屏幕中间
								if (!faceflag && rect.x > facevideo.clientWidth * 0.3 && rect.x < facevideo
									.clientWidth * 0.7) {
    
     // 检测到人脸进行拍照,延迟0.5秒
									informationTitle.innerHTML = '识别中,请勿乱动~';
									faceflag = true;
									tipFlag = true;
									setTimeout(() => {
    
    
										tackPhoto() // 拍照
									}, 500);
								}

							}
						} else {
    
    
							//检测到多张人脸
							if (!faceflag) {
    
    
								informationTitle.innerHTML = '只可一人进行人脸识别!'
							}
						}
					}
				});
			}).catch(function(err) {
    
    
				informationTitle.innerHTML = '打开摄像头失败'
			})
	

4. 监听到人脸后自动截取当前帧并保存为图片-tackPhoto()

function tackPhoto() {
    
    
					// 为什么调用getObjectFitSize,因为摄像头获取的图片和绘制的图片大小和区域可能不一致
					// 所以需要把人脸置于屏幕中间,绘制中间部分图片,如果不需要直接调用第二种方式	
					const {
    
    
						sx,
						sy,
						swidth,
						sheight,
						x,
						y,
						width,
						height
					} = getObjectFitSize('cover', facevideo.clientWidth, facevideo.clientHeight, videoWidth, videoHeight)
					facecontext.drawImage(facevideo, sx, sy, swidth, sheight, x, y, width, height);
					// 第二种方式	
					// facecontext.drawImage(facevideo, 0, 0, facevideo.clientWidth, facevideo.clientHeight);
					var snapData = facecanvas.toDataURL('image/png');
					var imgSrc = "data:image/png;" + snapData;
					// document.querySelector("img").src = imgSrc;

					sessionStorage.setItem("faceImage", imgSrc);
					// history.go(-1);
					history.back()
					facevideo.srcObject.getTracks().forEach(track => track.stop());
					// 取消监听
					tra.stop();
				}

5.计算图片裁剪或者摆放位置(如果不需要不写)

/**
			 * 计算图片裁剪或者摆放位置
			 * @param {*} type  contain, cover 暂时只兼容这两个模式
			 * @param {*} containerWidth  容器宽度
			 * @param {*} containerHeight  容器高度
			 * @param {*} imgWidth   图片宽度
			 * @param {*} imgHeight  图片高度
			 * @return {*} canvas drawImage的所有入参
			 */
			function getObjectFitSize(
				type = "cover",
				containerWidth,
				containerHeight,
				imgWidth,
				imgHeight
			) {
    
    
				let radio = 1, // 容器与图片的比例
					sx = 0, // 开始剪切的 x 坐标位置。
					sy = 0, // 开始剪切的 y 坐标位置。
					swidth = imgWidth, // 被剪切图像的宽度。
					sheight = imgHeight, // 被剪切图像的高度。
					x = 0, // 在画布上放置图像的 x 坐标位置。
					y = 0, // 在画布上放置图像的 y 坐标位置。
					width = containerWidth, // 要使用的图像的宽度(伸展或缩小图像)。
					height = containerHeight; // 要使用的图像的高度(伸展或缩小图像)。
				let cWHRatio = containerWidth / containerHeight;
				let iWHRatio = imgWidth / imgHeight;
				if (type === "cover") {
    
    
					// cover模式,需要裁剪
					if (iWHRatio >= cWHRatio) {
    
    
						// 横图,高先匹配,裁剪宽度
						radio = containerHeight / imgHeight;
						sx = (imgWidth - containerWidth / radio) / 2;
						swidth = containerWidth / radio;
						sheight = imgHeight;
					} else {
    
    
						// 竖图,宽先匹配,裁剪高度
						radio = containerWidth / imgWidth;
						sy = (imgHeight - containerHeight / radio) / 2;
						swidth = imgWidth;
						sheight = containerHeight / radio;
					}
				} else if (type === "contain") {
    
    
					if (iWHRatio >= cWHRatio) {
    
    
						// 横图,宽先匹配,高度自适应
						radio = containerWidth / imgWidth;
						y = (containerHeight - imgHeight * radio) / 2;
						height = imgHeight * radio;
					} else {
    
    
						// 竖图,高先匹配,宽度自适应
						radio = containerHeight / imgHeight;
						x = (containerWidth - imgWidth * radio) / 2;
						width = imgWidth * radio;
					}
				}
				return {
    
    
					sx,
					sy,
					swidth,
					sheight,
					x,
					y,
					width,
					height,
				};
			}

所有内容

		<style>
			#video_bind,
			#canvas_bind {
      
      
				width: 375rem;
				height: 486rem;
				object-fit: cover;
			}


			#canvas_bind {
      
      
				position: absolute;
				top: 0;
				z-index: 2;
				/* z-index: -1; */
			}

			.tip-box {
      
      
				margin-top: 32rem;
				font-size: 24rem;
			}
		</style>
		<div>
			<video width="375" height="486" id="video_bind" autoplay playsinline webkit-playsinline="true">

			</video>
			<div class="tip-box text-center"></div>
			<canvas id="canvas_bind"></canvas>
		</div>

		<script src="./js/tracking-min.js"></script>
		<script src="./js/face-min.js"></script>
		<script type="text/javascript">
			var tipFlag = false // 是否检测
			var faceflag = false // 是否进行拍照
			var informationTitle = document.querySelector(".tip-box") //人脸提示
			// 获取video、canvas实例
			var facevideo = document.getElementById('video_bind');
			var facecanvas = document.getElementById('canvas_bind');
			facecanvas.width = facecanvas.clientWidth;
			facecanvas.height = facecanvas.clientHeight;
			
			var videoWidth = videoHeight = 0
			facevideo.addEventListener('canplay', function() {
      
      
				videoWidth = this.videoWidth;
				videoHeight = this.videoHeight;
			});

			var facecontext = facecanvas.getContext('2d');
			var tracker = new tracking.ObjectTracker('face');
			// 每次打开弹框先清除canvas没拍的照片
			facecontext.clearRect(0, 0, facecanvas.width, facecanvas.height);

			if (navigator.mediaDevices === undefined) {
      
      
				navigator.mediaDevices = {
      
      }
			}
			if (navigator.mediaDevices.getUserMedia === undefined) {
      
      
				navigator.mediaDevices.getUserMedia = function(constraints) {
      
      
					var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
					if (!getUserMedia) {
      
      
						return Promise.reject(new Error("getUserMedia is not implemented in this browser"));
					}
					return new Promise(function(resolve, reject) {
      
      
						getUserMedia.call(navigator, constraints, resolve, reject);
					})
				}
			}

			navigator.mediaDevices.getUserMedia({
      
      
				audio: false,
				video: {
      
      
					facingMode: "environment"
				}
			}).then(function(stream) {
      
      
				// 使用监听人脸的包
				tracker.setInitialScale(4);
				tracker.setStepSize(2);
				tracker.setEdgesDensity(0.1);

				//打开摄像头
				var tra = tracking.track('#video_bind', tracker, {
      
      
					camera: true
				});
				var timer = null;
				// 创建监听 每帧都会触发
				tracker.on('track', function(event) {
      
      
					if (!tipFlag) {
      
      
						facecontext.clearRect(0, 0, facecanvas.width, facecanvas.height);
						if (event.data.length === 0) {
      
      
							//未检测到人脸
							if (!faceflag && !timer) {
      
      
								timer = setTimeout(() => {
      
      
									informationTitle.innerHTML = '未检测到人脸'
								}, 500)
							}
						} else if (event.data.length === 1) {
      
       // 长度为多少代表检测到几张人脸
							window.clearTimeout(timer);
							timer = null;
							informationTitle.innerHTML = '请将脸部置于屏幕中央';
							//检测到一张人脸
							if (!tipFlag) {
      
      
								// 给检测到的人脸绘制矩形
								event.data.forEach(function(rect) {
      
      
									facecontext.strokeStyle = '#a64ceb';
									facecontext.strokeRect(rect.x, rect.y, rect.width, rect.height);
								});
								let rect = event.data[0];
								//判断脸部是否在屏幕中间
								if (!faceflag && rect.x > facevideo.clientWidth * 0.3 && rect.x < facevideo
									.clientWidth * 0.7) {
      
       // 检测到人脸进行拍照,延迟0.5秒
									informationTitle.innerHTML = '识别中,请勿乱动~';
									faceflag = true;
									tipFlag = true;
									setTimeout(() => {
      
      
										tackPhoto() // 拍照
									}, 500);
								}

							}
						} else {
      
      
							//检测到多张人脸
							if (!faceflag) {
      
      
								informationTitle.innerHTML = '只可一人进行人脸识别!'
							}
						}
					}
				});

				function tackPhoto() {
      
      
					// 为什么调用getObjectFitSize,因为摄像头获取的图片和绘制的图片大小和区域可能不一致
					// 所以需要把人脸置于屏幕中间,绘制中间部分图片,如果不需要直接调用第二种方式	
					const {
      
      
						sx,
						sy,
						swidth,
						sheight,
						x,
						y,
						width,
						height
					} = getObjectFitSize('cover', facevideo.clientWidth, facevideo.clientHeight, videoWidth, videoHeight)
					facecontext.drawImage(facevideo, sx, sy, swidth, sheight, x, y, width, height);
					// 第二种方式	
					// facecontext.drawImage(facevideo, 0, 0, facevideo.clientWidth, facevideo.clientHeight);
					var snapData = facecanvas.toDataURL('image/png');
					var imgSrc = "data:image/png;" + snapData;
					// document.querySelector("img").src = imgSrc;

					sessionStorage.setItem("faceImage", imgSrc);
					// history.go(-1);
					history.back()
					facevideo.srcObject.getTracks().forEach(track => track.stop());
					// 取消监听
					tra.stop();
				}

			}).catch(function(err) {
      
      
				informationTitle.innerHTML = '打开摄像头失败'
			})

			/**
			 * 计算图片裁剪或者摆放位置
			 * @param {*} type  contain, cover 暂时只兼容这两个模式
			 * @param {*} containerWidth  容器宽度
			 * @param {*} containerHeight  容器高度
			 * @param {*} imgWidth   图片宽度
			 * @param {*} imgHeight  图片高度
			 * @return {*} canvas drawImage的所有入参
			 */
			function getObjectFitSize(
				type = "cover",
				containerWidth,
				containerHeight,
				imgWidth,
				imgHeight
			) {
      
      
				let radio = 1, // 容器与图片的比例
					sx = 0, // 开始剪切的 x 坐标位置。
					sy = 0, // 开始剪切的 y 坐标位置。
					swidth = imgWidth, // 被剪切图像的宽度。
					sheight = imgHeight, // 被剪切图像的高度。
					x = 0, // 在画布上放置图像的 x 坐标位置。
					y = 0, // 在画布上放置图像的 y 坐标位置。
					width = containerWidth, // 要使用的图像的宽度(伸展或缩小图像)。
					height = containerHeight; // 要使用的图像的高度(伸展或缩小图像)。
				let cWHRatio = containerWidth / containerHeight;
				let iWHRatio = imgWidth / imgHeight;
				if (type === "cover") {
      
      
					// cover模式,需要裁剪
					if (iWHRatio >= cWHRatio) {
      
      
						// 横图,高先匹配,裁剪宽度
						radio = containerHeight / imgHeight;
						sx = (imgWidth - containerWidth / radio) / 2;
						swidth = containerWidth / radio;
						sheight = imgHeight;
					} else {
      
      
						// 竖图,宽先匹配,裁剪高度
						radio = containerWidth / imgWidth;
						sy = (imgHeight - containerHeight / radio) / 2;
						swidth = imgWidth;
						sheight = containerHeight / radio;
					}
				} else if (type === "contain") {
      
      
					if (iWHRatio >= cWHRatio) {
      
      
						// 横图,宽先匹配,高度自适应
						radio = containerWidth / imgWidth;
						y = (containerHeight - imgHeight * radio) / 2;
						height = imgHeight * radio;
					} else {
      
      
						// 竖图,高先匹配,宽度自适应
						radio = containerHeight / imgHeight;
						x = (containerWidth - imgWidth * radio) / 2;
						width = imgWidth * radio;
					}
				}
				return {
      
      
					sx,
					sy,
					swidth,
					sheight,
					x,
					y,
					width,
					height,
				};
			}
		</script>

2. 微信小程序人脸识别功能-附带扫脸动画

详见:微信小程序人脸识别功能

猜你喜欢

转载自blog.csdn.net/thj13896076523/article/details/126246056