在一些直播场景,或者屏幕录制场景,希望可以把自己的头像通过摄像头放在桌面显示,增加互动性。

一些会议软件是支持这个能力的,但通常会把摄像头的内容放在一个方框里,显得不太好看,而且还得额外打开一个会议软件,会议软件的多余内容也会被录制进去。

于是就用 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 人脸填充到人头

识别出来的区域是人脸,录制的时候希望把人头显示出来。基于人脸区域按照一定比例填充即可。

rust 摄像头 android rust相机_ide

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 人脸防抖

识别人脸的过程中,识别的结果会一直在抖动,导致人脸也在抖动。

加一层均值滤波进行平滑处理。

rust 摄像头 android rust相机_计算机视觉_02

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 识别性能

进行识别的过程中,是比较消耗性能的。

可以通过图像金字塔的方式,来提高性能,但识别精度也会下降。

rust 摄像头 android rust相机_ide_03


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 透明模式下 出现残影

rust 摄像头 android rust相机_opencv_04

已提Bug,未修复。

4.5 浏览器的摄像头每次都要授权

rust 摄像头 android rust相机_rust 摄像头 android_05


这。。。

已提Bug,未修复。

整体来看,目前的 Tauri 除了编译包小,背靠 Rust 外,其它和 Electron 比的话还是不成熟,很难放到生产环境中使用,而且编译巨慢,新环境平均 20 min。

rust 摄像头 android rust相机_ide_06


Github 项目地址