在一些直播场景,或者屏幕录制场景,希望可以把自己的头像通过摄像头放在桌面显示,增加互动性。
一些会议软件是支持这个能力的,但通常会把摄像头的内容放在一个方框里,显得不太好看,而且还得额外打开一个会议软件,会议软件的多余内容也会被录制进去。
于是就用 Tauri 简单包装了下网页,实现了这个小功能。
1. 效果
- 桌面置顶
- 圆形显示,可任意拖拽位置
- 支持镜像显示
- 支持人头追踪
- 3 种尺寸设置
- 支持简单磨皮美颜
- MacOS 安装包 5 MB
2. 实现
原理比较简单,如下。
2.1 摄像头获取
这里直接调用的浏览器摄像头:
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 });
});
});
2.2 人头追踪
这个功能不在原计划内,后面想尝试下 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())
进行识别即可。
2.3 人脸填充到人头
识别出来的区域是人脸,录制的时候希望把人头显示出来。基于人脸区域按照一定比例填充即可。
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 },
];
2.4 人脸防抖
识别人脸的过程中,识别的结果会一直在抖动,导致人脸也在抖动。
加一层均值滤波进行平滑处理。
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,
];
};
2.5 识别性能
进行识别的过程中,是比较消耗性能的。
可以通过图像金字塔的方式,来提高性能,但识别精度也会下降。
OpenCV 中可以直接使用 cv::pyrDown (InputArray src, OutputArray dst, const Size &dstsize=Size(), int borderType=BORDER_DEFAULT)
方法,进行图像高斯金字塔操作中的向下采样处理来提高性能。
调整前后对比如下:
可以看到调整前后的帧率差别很大,识别率也有很大差别。
2.6 磨皮美颜
这里直接用的最简单的方式,通过双边滤镜 + 增加亮度 进行实现。
OpenCV 的方法是 cv::bilateralFilter (InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT)
。
3. OpenCV 的调用
这里直接使用的 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"
4. Tauri 的一些问题
这里对 Tauri 的使用相对多一些,也发现了很多问题。
4.1 MacOS 下浏览器取不到 mediaDevices
这个是遇到的第一个坑,浏览器中没有这个特性。那还怎么调用摄像头啊。
需要通过 Info.plist
配置文件写入支持这个 API 才行:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSCameraUsageDescription</key>
<string>请允许本程序访问您的摄像头</string>
</dict>
</plist>
4.2 窗体无法穿透鼠标事件
我做了个圆形窗口,结果没办法穿透鼠标事件。。
像 Electron 有 setIgnoreMouseEvents
这样的方式可以设置,但 Tauri 并没有提供这个 API。
已提 Bug,目前在 TAO 上已经实现,但 Tauri 上还没有 API 支持。
4.3 MacOS 下多屏幕间移动窗口尺寸重置
我这个软件可以通过点击进行不同尺寸的显示,结果你拖动到其他屏幕,就给你重置大小。。。
已提 Bug,还没修。
4.4 透明模式下 出现残影
已提Bug,未修复。
4.5 浏览器的摄像头每次都要授权
这。。。
已提Bug,未修复。
整体来看,目前的 Tauri 除了编译包小,背靠 Rust 外,其它和 Electron 比的话还是不成熟,很难放到生产环境中使用,而且编译巨慢,新环境平均 20 min。