最近在做一個用網頁端的人體姿態識別的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;
}
這樣就能實現屏幕用攝像頭爲底了。