今日この頃
新型コロナウイルスの影響で、リモート会議やリモート飲み
などが盛ん(?)に行われていると思います。
Zoomなどのサービスを使うと、カメラを使って顔出しをする際に
周りの背景などを写したくない人の為に、人物部分だけを抜き出して
好きな背景を設定できる「バーチャル背景」機能があります。
(いろいろな会社さんがバーチャル背景用の画像をツイートしていたりします)
使ってみると結構綺麗に抜けます。
おそらく機械学習でデータを作成して利用しているのでしょう。
今回の騒動の影響で「リモートで〇〇する」というのは需要が伸びそうな気がしていて、
配信系のアプリを作る際も「バーチャル背景」機能は必須となるかもしれません。
ですが、調べてみると案外簡単に利用できる技術やサービスがありません
(機会学習のデータは作成に手間が掛かっているので簡単には流れてくることはないでしょう)
なので
ARKit3から実装されたPeople Occlusion
の機能を使って
似たような機能をつくってみました。
ARKitの機能で人物とそれ以外で分けられた画像が取得できます。
その画像がまさにMask画像なのでそのままガッツリ利用して、楽に実装していきます。
実装
・前提
UnityでARKitを利用する為にAR Foundation
を使います。
インストールや設定などは、他にも詳しく解説しているサイトが多くあるので省きます。
・シーン作成
AR Session Origin
とAR Session
配置し
AROcclusionManager.cs
のスクリプトを追加します。
AR Camera
に追加されている
ARCameraBackground.cs
のスクリプトは
使用しないので非アクティブにします。
表示したい背景用にImage(BackgroundImage)
と
RawImage(MaskRawImage)
を配置しておきます
・画像の取得と割り当て
カメラ映像とマスク用の画像を取得してそれぞれを割り当てます。
マスク処理はShader
で行うので適応したMaterial
を
RawImage(MaskRawImage)
に設定します
using System;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
public class Manager : MonoBehaviour
{
public ARCameraManager cameraManager;
public AROcclusionManager occlusionManager;
private Texture2D camaraImage;
public RawImage maskRawImage;
public Material maskMaterial;
void OnEnable()
{
cameraManager.frameReceived += OnCameraFrameReceived;
}
void OnDisable()
{
cameraManager.frameReceived -= OnCameraFrameReceived;
}
unsafe void OnCameraFrameReceived(ARCameraFrameEventArgs eventArgs)
{
XRCameraImage image;
if (!cameraManager.TryGetLatestImage(out image))
{
return;
}
var format = TextureFormat.BGRA32;
if (camaraImage == null || camaraImage.width != image.width || camaraImage.height != image.height)
{
camaraImage = new Texture2D(image.width, image.height, format, false);
}
var conversionParams = new XRCameraImageConversionParams(image, format, CameraImageTransformation.MirrorY);
var rawTextureData = camaraImage.GetRawTextureData<byte>();
try
{
image.Convert(conversionParams, new IntPtr(rawTextureData.GetUnsafePtr()), rawTextureData.Length);
}
finally
{
image.Dispose();
}
camaraImage.Apply();
Texture2D humanStencil = occlusionManager.humanStencilTexture;
maskRawImage.texture = camaraImage;
maskMaterial.SetTexture("_MaskTex", humanStencil);
}
}
Shader "Custom/SpriteWithMask" {
Properties {
_MainTex ("Base", 2D) = "white" {}
_MaskTex ("Mask", 2D) = "white" {}
_Color ("Color", Color) = (0.5, 0.5, 0.5, 0.5)
}
SubShader {
Cull Off
Pass {
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
sampler2D _MaskTex;
float4 _Color;
struct v2f {
float4 pos : SV_POSITION;
float2 uv1 : TEXCOORD0;
float2 uv2 : TEXCOORD1;
};
float4 _MainTex_ST;
float4 _MaskTex_ST;
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos (v.vertex);
o.uv1 = TRANSFORM_TEX (v.texcoord, _MainTex);
o.uv2 = TRANSFORM_TEX (v.texcoord, _MaskTex);
o.uv2.x = 1.0 - o.uv2.x;
return o;
}
half4 frag (v2f i) : COLOR
{
half4 base = tex2D (_MainTex, i.uv1);
half4 mask = tex2D (_MaskTex, i.uv2);
base.w = mask.x * mask.x * mask.x;
return base;
}
ENDCG
}
}
FallBack Off
}
結果
現状と今後
なかなか良い精度のものが作れましたが、ARKit限定かつ、対応機種が
まだ少ない状態なので、もう少し普及率が高まらないと採用できないのかな
と思います。
こういう時ARKitとARCoreが並走してくれないのが、もどかしいですね・・・
(どうしようもないことですが・・・)