はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】OpenCV Plus Unityを用いて肌色を検出する

はじめに

前回はComputeShaderを用いて肌色を検出する記事を書きました。

www.hanachiru-blog.com


今回はこれをOpenCV plus Unityをという無料のアセットを用いて実現してみようと思います。

このアセットはオープンソースプロジェクトOpenCVSharpをベースとして作られているものです。


ただこのアセットはかなりマイナーどころで、こっちのアセットの方がかなり有名で情報もいっぱいあります。

こちらはネイティブプラグインを用いてC++で実装されているようです。


本当は下のアセットを使いたいところですが、学生である私にはそんなお金はないのでOpenCV Plus Unityを使っていきたいと思います。

f:id:hanaaaaaachiru:20200331171920g:plain

肌色を検出する

細かいことは以下の記事に書かせてもらったので、コードを実際にみていきます。

www.hanachiru-blog.com

private readonly static Scalar SKIN_LOWER = new Scalar(0, 30, 60);
private readonly static Scalar SKIN_UPPER = new Scalar(20, 150, 255);

private void ExtractSkinColor(WebCamTexture tex)
{
    //WebCamTextureからOpenCVで使われる形式Matに変換
    Mat mat = OpenCvSharp.Unity.TextureToMat(tex);

    Mat hsvMat = new Mat();

    // BGRからHSVに変換
    Cv2.CvtColor(mat, hsvMat, ColorConversionCodes.BGR2HSV);

    // 肌色で抽出して2値化
    Mat binary = hsvMat.InRange(SKIN_LOWER, SKIN_UPPER);

    _renderer.texture = OpenCvSharp.Unity.MatToTexture(binary);
}


一番重要なところはこちらだと思います。

mat = hsvMat.InRange(SKIN_LOWER, SKIN_UPPER);


InRangeメソッドによりSKIN_LOWER <= 対象の画像 < SKIN_UPPERとそれ以外で2値化します。

前回の記事では色はそのままにしていましたが、この方法では2値化してしまうところだけは注意ですね。

コード

Texture2DがWebカメラからの画像になるように付け足すとこんな感じ。

using OpenCvSharp;
using UnityEngine;
using UnityEngine.UI;

public class ExtractSkin : MonoBehaviour
{
    [SerializeField] private RawImage _renderer;

    private readonly static Scalar SKIN_LOWER = new Scalar(0, 60, 80);
    private readonly static Scalar SKIN_UPPER = new Scalar(10, 160, 240);

    private int _width = 1920;
    private int _height = 1080;
    private int _fps = 30;
    private WebCamTexture _webcamTexture;

    private void Start()
    {
        WebCamDevice[] devices = WebCamTexture.devices;
        _webcamTexture = new WebCamTexture(devices[0].name, this._width, this._height, this._fps);
        _webcamTexture.Play();
    }

    void OnDestroy()
    {
        if (_webcamTexture != null)
        {
            if (_webcamTexture.isPlaying) _webcamTexture.Stop();
            _webcamTexture = null;
        }
    }

    private void Update()
        => ExtractSkinColor(_webcamTexture);

    private void ExtractSkinColor(WebCamTexture tex)
    {
        //WebCamTextureからOpenCVで使われる形式Matに変換
        Mat mat = OpenCvSharp.Unity.TextureToMat(tex);

        Mat hsvMat = new Mat();

        // BGRからHSVに変換
        Cv2.CvtColor(mat, hsvMat, ColorConversionCodes.BGR2HSV);

        // 肌色で抽出して2値化
        Mat binary = hsvMat.InRange(SKIN_LOWER, SKIN_UPPER);

        _renderer.texture = OpenCvSharp.Unity.MatToTexture(binary);
    }
}

比較

FPS的にはComputeShaderを使った方が勝っているようでした。

f:id:hanaaaaaachiru:20200327195547p:plainf:id:hanaaaaaachiru:20200331171559p:plain
左:ComputeShdaer,右:OpenCV

あくまで私が実践した実装方法においての話ですが、Unityの場合はComputeShaderが最適化の要なのかもしれませんね。

追記

前回みたく色をつけたり、背景はそのままで肌色だけ色を変えたいしたい場合は以下の知識を使えばいけるはずです。

まずMatにはいくつかの種類(Type)があることに注意してください。
OpenCVのMatのタイプ一覧表 | 404 Motivation Not Found

いじっていなければ普通のカラーの画像はCV_8UC3,2値化した画像はCV_8UC1です。

例えばCV_8UC1からCV_8UC3に変換したければ以下のコードを書けばOKです。

Cv2.CvtColor(binary, mat, ColorConversionCodes.GRAY2BGR);


加えてTypeが一致していれば画像の足し合わせは+演算子で行えます。

mat = mat1 + mat2;

さいごに

このアセットを使えば簡潔にかけるのは確かです。

ただOpenCVを使ったとしても上手に実装しなければ重くなってしまうことも事実です。(特に全部のピクセルになんらかの操作を加えるために2重for文なんかしたら一瞬で重くなる)

うまく適材適所で使ってみてください。

あとはOpenCV for Unityでも試してみたかった・・・。

www.hanachiru-blog.com