読者です 読者をやめる 読者になる 読者になる

凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

Unity と OpenCV を組み合わせて現実・仮想双方を加工した AR な世界を Oculus Rift 越しに覗いてみた

Oculus Unity AR C# C++

はじめに

通常のカメラ画に AR オブジェクトを描画すると、解像感や色のズレで AR オブジェクトにバーチャルっぽさを感じてしまいます。そこで逆に現実の世界の方をバーチャルっぽくしてあげることで、AR オブジェクトに感じる違和感が低減されるのではないかと思い、カメラ画、AR オブジェクトを共に線画化し、Oculus Rift x Ovrvision で覗いてみました。

デモ

現実の世界を線画化しても立体感を感じるか、という実験は以前行いました。

実際に立体っぽく感じた体験が合ったので結構うまくいくのではないかとあたりはつけていたのですが、実際に試してみると結構違和感が低減されていて面白かったです。他にも、アニメ調にしたりサイバー感ある感じにしたりすると面白そうです。

環境

AR オブジェクトの線画化

以下のシェーダをお借りしています。

OpenCV を Unity で使う

はじめに

OpenCvSharp を使うなどの方法もあると思いますが、今回は、OpenCV による DLL を作成して、これを利用する形を取りました。

以前は線画化した画の描画に Qt を利用していましたが、3D のオブジェクトを加工して描画する、といった用途にはやはり Unity が長けているため、このような構成になっています。

DLL の作成に伴う問題について

はじめは Unity で DllNotFoundException が起きてしまい、Dependency Walker で調べてみたところ API-MS-WIN-CORE-KERNEL32-PRIVATE-L1-1-1.DLL などの DLL がないよ!と怒られてしまいました(そして everything で探してもこれら DLL が見つからない)。これは検索すると同様に困っている人が結構見つかりました。

原因は特定できなかった(Win 8.1 のせい?)のですが、リンクするライブラリを staticlib のものに変更し、ランタイムライブラリをマルチスレッド DLL(/MD) からマルチスレッド(/MT)に変更することで解決しました。

f:id:hecomi:20140611022536p:plain

f:id:hecomi:20140611022544p:plain

f:id:hecomi:20140611022549p:plain

C++

C++ 側では以下の様なコードを書いて DLL としてビルドします。

#include <opencv2/opencv.hpp>

extern "C" {
    __declspec(dllexport) void openWindow(const char* windowName)
    {
        cv::namedWindow(windowName, CV_WINDOW_AUTOSIZE | CV_WINDOW_FREERATIO);
    }

    __declspec(dllexport) void showTexture(const char* windowName, unsigned char* const data, int width, int height)
    {
        cv::Mat img(height, width, CV_8UC4, data);
        cv::imshow(windowName, img);
    }

    __declspec(dllexport) void convertToCannyTexture(unsigned char* src, unsigned char* dest, int width, int height, float threshold1, float threshold2)
    {
        cv::Mat srcImg(height, width, CV_8UC4, src);
        cv::Mat destImg;
        cv::Canny(srcImg, destImg, threshold1, threshold2);
        cv::cvtColor(destImg, destImg, CV_GRAY2BGRA);
        memcpy(dest, destImg.data, destImg.total() * destImg.elemSize());
    }

    __declspec(dllexport) void closeWindow(const char* windowName)
    {
    cv::destroyWindow(windowName);
    }
}

C#

そうして出来た DLL を Unity の Assets/Plugins 下に放り込みます。ここでは canny.dll としたことにします。そして上記4つの関数は以下のようにして C# 側から使用することが出来ます。

public class Ovrvision : MonoBehaviour
{
    ...
    [DllImport("canny", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
    static extern void openWindow(string windowName);
    [DllImport("canny", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
    static extern void showTexture(string windowName, System.IntPtr texture, int width, int height);
    [DllImport("canny", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
    static extern void convertToCannyTexture(System.IntPtr srcTexture, System.IntPtr destTexture, int width, int height, float threshold1, float threshold2);
    [DllImport("canny", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
    static extern void closeWindow(string windowName);
    ...

    void Start()
    {
        ...
        openWindow("Viewer");
    }
    
    void Update()
    {
        ...
        showTexture("Viewer", go_pixelsPointerLeft, ovGetImageWidth(), ovGetImageHeight());
    }
    
    void Destroy()
    {
        ...
        closeWindow("Viewer");
    }

    ...
    
    void GetImageThreadFunc()
    {
        ...
        ovGetCamImageForUnityColor32(go_pixelsPointerLeft, go_pixelsPointerRight);
        convertToCannyTexture(go_pixelsPointerLeft,  go_cannyPixelsPointerLeft,  ovGetImageWidth(), ovGetImageHeight(), 50.0f, 200.0f);
        convertToCannyTexture(go_pixelsPointerRight, go_cannyPixelsPointerRight, ovGetImageWidth(), ovGetImageHeight(), 50.0f, 200.0f);
        ...
    }

    ...
}

今回は Ovrvision SDK で貰ってきた画像のデータが格納されたポインタを作成した DLL に流し込んで変換しています。上記例のように確認用に OpenCV のウィンドウも開けます。

f:id:hecomi:20140611024342p:plain

RGBA の並びが異なるので色がアレになってますが Canny する分には問題ありません。

おわりに

Oculus Rift + Ovrvision では通常の世界を変化させて見せることが出来るため、今回のような現実・仮想双方を加工して歩み寄りをさせてあげることで、人間の知覚を騙してあげるのはとても面白い題材だと思います。今回は線画でしたが、他にも色々な効果が試せると思いますので是非みなさんもやってみてください。