在一些直播场景,或者屏幕录制场景,希望可以把自己的头像通过摄像头放在桌面显示,增加互动性。
一些会议软件是支持这个能力的,但通常会把摄像头的内容放在一个方框里,显得不太好看,而且还得额外打开一个会议软件,会议软件的多余内容也会被录制进去。
于是就用 Tauri 简单包装了下网页,实现了这个小功能。
原理比较简单,如下。
这里直接调用的浏览器摄像头:
navigator.mediaDevices
.getUserMedia({ video: videoConstraint, audio: false })
.then(function (stream) {
video.srcObject = stream;
video.play();
video.addEventListener("canplay", onVideoCanPlay, false);
});
获取摄像头列表,可以通过 enumerateDevices
:
navigator.mediaDevices
.getUserMedia({
video: true,
})
.then(function () {
var cameradevices = [];
navigator.mediaDevices.enumerateDevices().then(function (devices) {
devices.forEach(function (device) {
if (device.kind == "videoinput") {
cameradevices.push({ label: device.label, id: device.deviceId });
}
});
console.log({ cameradevices });
});
});
这个功能不在原计划内,后面想尝试下 OpenCV 就把这块功能给做进去了。
人脸追踪这个功能 OpenCV 有非常成熟的方案,可以使用 cv::cuda::CascadeClassifier
,OpenCV 源码的 data 目录中包含 haarcascades 分类数据,可以直接使用。通过 detectMultiScale (InputArray image, std::vector< Rect > &objects, double scaleFactor=1.1, int minNeighbors=3, int flags=0, Size minSize=Size(), Size maxSize=Size())
进行识别即可。
识别出来的区域是人脸,录制的时候希望把人头显示出来。基于人脸区域按照一定比例填充即可。
const _center = {
x: _p1.x + _p2.x / 2,
y: _p1.y + _p2.y / 2,
};
const _r = Math.min(_p2.x, _p2.y) / 2;
// 正方形
return [
{ x: _center.x - _r, y: _center.y - _r },
{ x: 2 * _r, y: 2 * _r },
];
识别人脸的过程中,识别的结果会一直在抖动,导致人脸也在抖动。
加一层均值滤波进行平滑处理。
this.shakeFilter = function (p1, p2, size) {
if (FACE_SHAKE_FILTER_LIST.length >= (size || FACE_SHAKE_FILTER_SIZE)) {
FACE_SHAKE_FILTER_LIST.shift();
FACE_SHAKE_FILTER_LIST.push({ p1, p2 });
} else {
FACE_SHAKE_FILTER_LIST.push({ p1, p2 });
}
const sum_point = FACE_SHAKE_FILTER_LIST.reduce(
(prev, curr) => {
return {
p1: { x: prev.p1.x + curr.p1.x, y: prev.p1.y + curr.p1.y },
p2: { x: prev.p2.x + curr.p2.x, y: prev.p2.y + curr.p2.y },
};
},
{ p1: { x: 0, y: 0 }, p2: { x: 0, y: 0 } }
);
return [
sum_point.p1.x / FACE_SHAKE_FILTER_LIST.length,
sum_point.p1.y / FACE_SHAKE_FILTER_LIST.length,
sum_point.p2.x / FACE_SHAKE_FILTER_LIST.length,
sum_point.p2.y / FACE_SHAKE_FILTER_LIST.length,
];
};
进行识别的过程中,是比较消耗性能的。
可以通过图像金字塔的方式,来提高性能,但识别精度也会下降。
OpenCV 中可以直接使用 cv::pyrDown (InputArray src, OutputArray dst, const Size &dstsize=Size(), int borderType=BORDER_DEFAULT)
方法,进行图像高斯金字塔操作中的向下采样处理来提高性能。
调整前后对比如下:
可以看到调整前后的帧率差别很大,识别率也有很大差别。
这里直接用的最简单的方式,通过双边滤镜 + 增加亮度 进行实现。
OpenCV 的方法是 cv::bilateralFilter (InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT)
。
这里直接使用的 OpenCV 的 WebAssembly 方式进行调用的,官方有提供编译方式。
$ emcmake python ./opencv/platforms/js/build_js.py build_wasm --build_wasm
需要 opencv_contrib
的话,需要下载源码,加入如下参数进行编译:
$ python ./platforms/js/build_js.py build_js --cmake_option="-DOPENCV_EXTRA_MODULES_PATH=opencv_contrib/modules"
这里对 Tauri 的使用相对多一些,也发现了很多问题。
这个是遇到的第一个坑,浏览器中没有这个特性。那还怎么调用摄像头啊。
需要通过 Info.plist
配置文件写入支持这个 API 才行:
DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSCameraUsageDescriptionkey>
<string>请允许本程序访问您的摄像头string>
dict>
plist>
我做了个圆形窗口,结果没办法穿透鼠标事件。。
像 Electron 有 setIgnoreMouseEvents
这样的方式可以设置,但 Tauri 并没有提供这个 API。
已提 Bug,目前在 TAO 上已经实现,但 Tauri 上还没有 API 支持。
我这个软件可以通过点击进行不同尺寸的显示,结果你拖动到其他屏幕,就给你重置大小。。。
已提 Bug,还没修。
已提Bug,未修复。
已提Bug,未修复。
整体来看,目前的 Tauri 除了编译包小,背靠 Rust 外,其它和 Electron 比的话还是不成熟,很难放到生产环境中使用,而且编译巨慢,新环境平均 20 min。