最近在做一个用网页端的人体姿态识别的demo,遇到一些坑,记录一下。
首先遇到的问题是用js调用摄像头为底。这里我参考了EASY-AR的demo
分为2个文件 main.js 和 ClassAR.js
main.js:
function firstOpenCamera(){
classAR.listCamera(videoDevice)
.then(msg => {
classAR.openCamera(JSON.parse(videoDevice[0].value))
.then(msg => {
console.info(msg);
}).catch(err => {
console.info(err);
});
})
.catch(err => {
// 没有找到摄像头
console.info(err);
});
}
document.querySelector('#btn_changeCamera').addEventListener('click', function () {
// 打开摄像头
// 打开后置摄像头参数: {audio: false, video: {facingMode: {exact: 'environment'}}}
if(videoDevice.length == 0 || videoDevice.length == 1)
return;
nowVideo = nowVideo == 0? 1 : 0; //切换当前摄像头
classAR.openCamera(JSON.parse(videoDevice[nowVideo].value))
.then(msg => {
console.info(msg);
}).catch(err => {
console.info(err);
});
});
// 开启识别
document.querySelector('#btn_check').addEventListener('click', () => {
classAR.startRecognize(classAR,(msg) => {
console.info(msg);
});
}, false);
classAR:
export default class ClassAR {
constructor(interval,nowBodyModel) {
this.isRecognizing = false;
// 前/后置摄像头
this.cameras = ["user", "environment"];
this.interval = interval;
this.videoOffWidth = 0;
this.videoOffHeight = 0;
}
listCamera(videoDevice) {
return new Promise((resolve, reject) => {
navigator.mediaDevices.enumerateDevices()
.then((devices) => {
let index = 0;
devices.find((device) => {
if (device.kind === 'videoinput') {
const option = document.createElement('option');
// 在iOS12.2上deviceId为空
if (device.deviceId == '') {
option.text = device.label || 'camera ' + this.cameras[index];
option.value = JSON.stringify({
audio: false,
video: {facingMode: {exact: this.cameras[index]}}
});
index++;
} else {
option.text = device.label || 'camera ' + (videoDevice.length + 1).toString();
option.value = JSON.stringify({
audio: false,
video: {deviceId: {exact: device.deviceId}}
});
}
// 将摄像头信息存储在select元素中,方便切换前、后置摄像头
videoDevice.push(option);
}
return false;
});
if (videoDevice.length === 0) {
reject('没有可使用的视频设备');
} else {
this.initVideo();
//this.initCanvas();
resolve(true);
}
}).catch(err => {
reject(err);
});
});
}
/**
* 打开摄像头
* 摄像头设置参数请查看: https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints
* @param videoDeviceIndex
* @returns {Promise<T>}
*/
openCamera(constraints) {
// 如果是切换摄像头,则需要先关闭。
if (this.videoElement && this.videoElement.srcObject) {
this.videoElement.srcObject.getTracks().forEach(track => {
track.stop();
});
}
return new Promise((resolve, reject) => {
navigator.mediaDevices.getUserMedia(constraints)
.then(stream => {
this.videoElement.srcObject = stream;
this.videoElement.style.display = 'block';
this.videoElement.play();
this.videoElement.onloadedmetadata = () => {
const cameraSize = {
width: this.videoElement.offsetWidth,
height: this.videoElement.offsetHeight
};
console.info(JSON.stringify(cameraSize));
if (window.innerWidth < window.innerHeight) {
// 竖屏
if (cameraSize.height < window.innerHeight) {
this.videoElement.setAttribute('height', window.innerHeight.toString() + 'px');
this.videoOffWidth = this.videoElement.offsetWidth - window.innerWidth;
this.videoElement.style.marginLeft = -this.videoOffWidth/2 + "px";
//this.canvasElement.style.marginLeft = -(cameraSize.width - window.innerWidth) + "px";
}
} else {
// 横屏
if (cameraSize.width < window.innerWidth) {
this.videoElement.setAttribute('width', window.innerWidth.toString() + 'px');
this.videoOffHeight = this.videoElement.offsetHeight - window.innerHeight + "px";
this.videoElement.style.marginTop = -this.videoOffHeight + "px";
//this.canvasElement.style.marginTop = -(cameraSize.width - window.innerWidth) + "px";
}
}
resolve(true);
this.initCanvas();
};
})
.catch(err => {
reject(err);
});
});
}
/**
* 截取摄像头图片
* @returns {HTMLImageElement}
*/
captureVideo() {
//this.canvasContext.drawImage(this.videoElement, this.videoOffWidth / 2, 0 , window.innerWidth,620 , 0 , 0 ,window.innerWidth,window.innerHeight);
this.canvasContext.drawImage(this.videoElement,0,0,window.innerWidth,window.innerHeight);
//this.canvasElement.style.marginLeft = - this.videoOffWidth/2 + 'px';
//this.canvasContext2.drawImage(this.canvasElement,0,0,300,window.innerHeight,0,0,300,window.innerHeight);
//return this.canvasElement.toDataURL('image/jpeg', 0.5).split('base64,')[1];
let image_64 = this.canvasElement.toDataURL('image/jpeg');
let image = new Image();
image.src = image_64;
this.canvasElement.style.display = "none";
let newImagePromise = this.cutImage(image);
return newImagePromise;
}
/**
* 创建视频详情元素,播放摄像头视频流
*/
initVideo() {
/*this.videoElement = document.createElement('video');
this.videoElement.setAttribute('playsinline', 'playsinline');
document.body.appendChild(this.videoElement);*/
// this.videoElement = document.createElement('video');
this.videoElement = document.getElementById('video');
this.videoElement.setAttribute('playsinline', 'playsinline');
//this.videoElement.setAttribute('width', window.innerWidth.toString() + 'px');
//this.videoElement.setAttribute('height', window.innerHeight.toString() + 'px');
document.body.appendChild(this.videoElement);
}
/**
* 创建canvas,截取摄像头图片时使用
*/
initCanvas() {
// this.canvasElement = document.createElement('canvas');
this.canvasElement = document.getElementById('canvas');
this.canvasElement.setAttribute('width', window.innerWidth.toString() + 'px');
this.canvasElement.setAttribute('height', window.innerHeight.toString() + 'px');
this.canvasContext = this.canvasElement.getContext('2d');
this.canvasElement2 = document.getElementById('canvas2');
this.canvasElement2.setAttribute('width', window.innerWidth + 'px');
this.canvasElement2.setAttribute('height', window.innerHeight+ 'px');
this.canvasContext2 = this.canvasElement2.getContext('2d');
// document.body.appendChild(this.canvasElement);
}
然后这里遇到了一个问题,就是当手机访问时,安卓大部分机型是可以的,但是我测试的ios机型都出现了,实际摄像头所摄区域要大于html,导致网页可以左右移动。
查看源码,并未想到解决方案,于是我用了自己的方法来解:
首先计算出如果只显示中间时,左右2端应有多少width。
this.videoOffWidth = this.videoElement.offsetWidth - window.innerWidth;
然后通过设置marginleft,来“裁”去左边的。
this.videoElement.style.marginLeft = -this.videoOffWidth/2 + "px";
再通过给html设置不能左右滑动,来“裁”去右边的(当然这只是治标不治本的方法,但是对于我这次的项目来说,这种方法够用了。)
body {
margin: 0;
padding: 0;
position:fixed;
overflow: hidden;
}
这样就能实现屏幕用摄像头为底了。