Unity + OpenCVの仮想Quadrocopter(パート2)

CPAP



こんにちは、カブラビテス!



この記事では、Unity、C ++、OpenCVを友達にする方法についてのシリーズを続けたいと思います。 また、Unityに基づいてコンピュータービジョンのアルゴリズムとドローンのナビゲーションをテストするための仮想環境を取得する方法も。 前の記事で、 Unityで仮想クアドロコプターを作成する方法について説明しました。 この記事では、C ++プラグインを接続し、そこで仮想カメラから画像を転送し、OpenCVを使用して処理する方法について説明します。



アイデア



アイデアは、カメラ画像をテクスチャにレンダリングすることです。 さらに、このテクスチャはUnityのビデオカードのメモリにロードされ、そこから既にC ++内にあるOpenGLを使用して抽出され、OpenCVマトリックスにロードされます。 次に、OpenCVを分析/処理できます。 最後に、変更したテクスチャをビデオカードのメモリ内の対応するテクスチャの場所にロードすることにより、Unityに戻すことができます。 それでは始めましょう。



テクスチャでカメラをレンダリングします。



まず、カメラからの画像を配置するテクスチャを作成する必要があります。 これを行うには、アセットフォルダーで右ボタンをクリックし、[作成]-> [テクスチャのレンダリング]をクリックします。 次に、カメラを2次曲線に追加する必要があります。 これは、作成->カメラを使用して、階層で行われます。 カメラをQuadrocopter-> Frameに追加して、フレームに取り付けます。 Target Textureカメラプロパティで、作成したRender Textureを指定します。 また、Unityでカメラのテクスチャを見たいと思います。 これを行うには、シーンにCreate-> UI-> Canvasが必要です。 このオブジェクトは、画面にコントロール(通常は2次元)を表示するために使用されます。 CamerasTexturesと呼んでいます。 キャンバスで、UI-> RawImageを追加する必要があります。 画像の[テクスチャ]パラメーターで、テクスチャを指定します。 RawImage内でRect Transformを使用して、画面上の適切な場所に画像を配置します。これは、[ゲーム]タブで行うのが最も簡単です。 Quadrocopterの飛行中にカメラの画像がどのように変化するかを確認します。

カメラ付き仮想クアドロコプター



C ++でのテクスチャの受け渡し



公式プラグインのドキュメントはこちらにあります 。 公式の例も役に立つかもしれません: thisthis 別のスクリプトでC ++テクスチャを送受信します。 それをGameManagerと呼びましょう。 動作させるには、空のオブジェクトをシーンに追加し、このスクリプトを追加する必要があります。 以下では、最初にスクリプトの個々の部分について説明し、次にスクリプト全体を説明します。
C ++でテクスチャ識別子を渡す関数
//  ,     . //          private Texture2D cam1Tex; //         private void PassCamerasTexturesToPlugin () { //   cam1Tex = new Texture2D(256,256,TextureFormat.ARGB32,false); //   cam1Tex.filterMode = FilterMode.Point; //  Apply()    GPU cam1Tex.Apply(); //       . // ,   ,     GameObject.Find ("/CamerasTextures/Camera1RawImage").GetComponent<RawImage>().texture = cam1Tex; // ,        #if UNITY_GLES_RENDERER SetTextureOfCam1 (cam1Tex.GetNativeTexturePtr(), cam1Tex.width, cam1Tex.height); #else SetTextureOfCam1 (cam1Tex.GetNativeTexturePtr()); #endif } //        //      #if UNITY_IPHONE && !UNITY_EDITOR [DllImport ("__Internal")] #else //      ,     [DllImport ("QuadrocopterBrain")] #endif #if UNITY_GLES_RENDERER private static extern void SetTextureOfCam1(System.IntPtr texture, int w, int h); #else private static extern void SetTextureOfCam1(System.IntPtr texture); #endif
      
      







cam1Texテクスチャをカメラデータで更新する関数。
  private IEnumerator CallPluginAtEndOfFrames () { while (true) { //      yield return new WaitForEndOfFrame(); RenderTexture cam1RT = GameObject.Find ("/Quadrocopter/Frame/Camera1").GetComponent<Camera>().targetTexture; //  Render Texture -  ,      RenderTexture.active = cam1RT; cam1Tex.ReadPixels(new Rect(0, 0, cam1RT.width, cam1RT.height), 0, 0); //    GPU cam1Tex.Apply (); RenderTexture.active = null; //     //  int     //       . //        debug  GL.IssuePluginEvent(GetRenderEventFunc(), frameIndex++); } } //  ,    , //      C++ #if UNITY_IPHONE && !UNITY_EDITOR [DllImport ("__Internal")] #else [DllImport("QuadrocopterBrain")] #endif private static extern IntPtr GetRenderEventFunc(); }
      
      







すべてのスクリプトコードは次のようになります。
 using UnityEngine; using UnityEngine.UI; using System; using System.Collections; using System.Runtime.InteropServices; public class GameManager : MonoBehaviour { private Texture2D cam1Tex; private int frameIndex = 0; IEnumerator Start () { PassCamerasTexturesToPlugin (); yield return StartCoroutine ("CallPluginAtEndOfFrames"); } private IEnumerator CallPluginAtEndOfFrames () { while (true) { //      yield return new WaitForEndOfFrame(); RenderTexture cam1RT = GameObject.Find ("/Quadrocopter/Frame/Camera1").GetComponent<Camera>().targetTexture; //  Render Texture -  ,      RenderTexture.active = cam1RT; cam1Tex.ReadPixels(new Rect(0, 0, cam1RT.width, cam1RT.height), 0, 0); //    GPU cam1Tex.Apply (); RenderTexture.active = null; //     //  int     //       . //        debug  GL.IssuePluginEvent(GetRenderEventFunc(), frameIndex++); } } private void PassCamerasTexturesToPlugin () { //   cam1Tex = new Texture2D(256,256,TextureFormat.ARGB32,false); //   cam1Tex.filterMode = FilterMode.Point; //  Apply()    GPU cam1Tex.Apply(); //       . // ,   ,     GameObject.Find ("/CamerasTextures/Camera1RawImage").GetComponent<RawImage>().texture = cam1Tex; // ,        #if UNITY_GLES_RENDERER SetTextureOfCam1 (cam1Tex.GetNativeTexturePtr(), cam1Tex.width, cam1Tex.height); #else SetTextureOfCam1 (cam1Tex.GetNativeTexturePtr()); #endif } //        //      #if UNITY_IPHONE && !UNITY_EDITOR [DllImport ("__Internal")] #else //      ,     [DllImport ("QuadrocopterBrain")] #endif #if UNITY_GLES_RENDERER private static extern void SetTextureOfCam1(System.IntPtr texture, int w, int h); #else private static extern void SetTextureOfCam1(System.IntPtr texture); #endif //  ,    , //      C++ #if UNITY_IPHONE && !UNITY_EDITOR [DllImport ("__Internal")] #else [DllImport("QuadrocopterBrain")] #endif private static extern IntPtr GetRenderEventFunc(); }
      
      







Opencv



画像処理にOpenCVを使用するため、自分でインストールする必要があります。 公式サイトから取得した通常のバニラOpenCV 適しています。 ガイドに載せました。 記事に書かれていることを繰り返したい場合は、まずOpenCVを何らかの方法で使用する空の動的ライブラリプロジェクトを作成してコンパイルすることをお勧めします。 上記のガイドが役立ちます。 OpenGL機能も必要になります。

空のライブラリーの例
 #include <opencv2/opencv.hpp> void someFunc () { cv::Mat img (256, 256, CV_8UC4); return 0; }
      
      







プラグインでテクスチャを取得および処理する



ここでも、初心者のために、プログラムの個々のフラグメントを提供し、次にコード全体を提供します。

テクスチャID伝達関数
 //     static void* g_Cam1TexturePointer = NULL; #ifdef SUPPORT_OPENGLES static int g_TexWidth = 0; static int g_TexHeight = 0; #endif //    . //   C#  #ifdef SUPPORT_OPENGLES extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API SetTextureOfCam1(void* texturePtr, int w, int h) #else extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API SetTextureOfCam1(void* texturePtr) #endif { g_Cam1TexturePointer = texturePtr; #ifdef SUPPORT_OPENGLES g_TexWidth = w; g_TexHeight = h; #endif }
      
      







テクスチャ処理機能
 static void UNITY_INTERFACE_API OnRenderEvent(int eventID) { //     RenderingPluginExampleXX.zip, //        ,     OpenGL. //       ,      //    #if SUPPORT_OPENGL if (g_Cam1TexturePointer) { GLuint gltex = (GLuint)(size_t)(g_Cam1TexturePointer); glBindTexture (GL_TEXTURE_2D, gltex); int texWidth, texHeight; glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &texWidth); glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &texHeight); //  OpenCV,      cv::Mat img (texHeight, texWidth, CV_8UC4); //     glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, img.data); //         Cam1 cv::putText(img, "Cam1", cv::Point2f(10,50), cv::FONT_HERSHEY_SIMPLEX, 2, cv::Scalar(0, 0, 0, 255), 3); //     ,     Unity. // Unity     GUI  OpenCV,    imshow    //           Unity glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, texWidth, texHeight, GL_RGBA, GL_UNSIGNED_BYTE, img.data); } #endif } //  GameManager.cs extern "C" UnityRenderingEvent UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API GetRenderEventFunc() { return OnRenderEvent; }
      
      







プラグインは2つのファイルで構成されています。

Main.h
 #ifndef __QuadrocopterBrain__Main__ #define __QuadrocopterBrain__Main__ // Which platform we are on? #if _MSC_VER #define UNITY_WIN 1 #elif defined(__APPLE__) #if defined(__arm__) #define UNITY_IPHONE 1 #else #define UNITY_OSX 1 #endif #elif defined(__linux__) #define UNITY_LINUX 1 #elif defined(UNITY_METRO) || defined(UNITY_ANDROID) // these are defined externally #else #error "Unknown platform!" #endif // Which graphics device APIs we possibly support? #if UNITY_METRO #define SUPPORT_D3D11 1 #elif UNITY_WIN #define SUPPORT_D3D9 1 #define SUPPORT_D3D11 1 // comment this out if you don't have D3D11 header/library files #ifdef _MSC_VER #if _MSC_VER >= 1900 #define SUPPORT_D3D12 1 #endif #endif #define SUPPORT_OPENGL 1 #elif UNITY_IPHONE || UNITY_ANDROID #define SUPPORT_OPENGLES 1 #elif UNITY_OSX || UNITY_LINUX #define SUPPORT_OPENGL 1 #endif #endif /* defined(__QuadrocopterBrain__Main__) */
      
      







Main.cpp
 #include <math.h> #include <stdio.h> #include <vector> #include <string> #include "Unity/IUnityGraphics.h" #include <opencv2/opencv.hpp> #include "Main.h" // -------------------------------------------------------------------------- // Include headers for the graphics APIs we support #if SUPPORT_D3D9 #include <d3d9.h> #include "Unity/IUnityGraphicsD3D9.h" #endif #if SUPPORT_D3D11 #include <d3d11.h> #include "Unity/IUnityGraphicsD3D11.h" #endif #if SUPPORT_D3D12 #include <d3d12.h> #include "Unity/IUnityGraphicsD3D12.h" #endif #if SUPPORT_OPENGLES #if UNITY_IPHONE #include <OpenGLES/ES2/gl.h> #elif UNITY_ANDROID #include <GLES2/gl2.h> #endif #elif SUPPORT_OPENGL #if UNITY_WIN || UNITY_LINUX #include <GL/gl.h> #else #include <OpenGL/gl.h> #endif #endif // Prints a string static void DebugLog (const char* str) { #if UNITY_WIN OutputDebugStringA (str); #else fprintf(stderr, "%s", str); #endif } //     static void* g_Cam1TexturePointer = NULL; #ifdef SUPPORT_OPENGLES static int g_TexWidth = 0; static int g_TexHeight = 0; #endif //    . //   C#  #ifdef SUPPORT_OPENGLES extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API SetTextureOfCam1(void* texturePtr, int w, int h) #else extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API SetTextureOfCam1(void* texturePtr) #endif { g_Cam1TexturePointer = texturePtr; #ifdef SUPPORT_OPENGLES g_TexWidth = w; g_TexHeight = h; #endif } static void UNITY_INTERFACE_API OnRenderEvent(int eventID) { //     RenderingPluginExampleXX.zip, //        ,     OpenGL. //       ,      //    #if SUPPORT_OPENGL if (g_Cam1TexturePointer) { GLuint gltex = (GLuint)(size_t)(g_Cam1TexturePointer); glBindTexture (GL_TEXTURE_2D, gltex); int texWidth, texHeight; glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &texWidth); glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &texHeight); //  OpenCV,      cv::Mat img (texHeight, texWidth, CV_8UC4); //     glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, img.data); //         Cam1 cv::putText(img, "Cam1", cv::Point2f(10,50), cv::FONT_HERSHEY_SIMPLEX, 2, cv::Scalar(0, 0, 0, 255), 3); //     ,     Unity. // Unity     GUI  OpenCV,    imshow    //           Unity glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, texWidth, texHeight, GL_RGBA, GL_UNSIGNED_BYTE, img.data); } #endif } // -------------------------------------------------------------------------- // UnitySetInterfaces static void UNITY_INTERFACE_API OnGraphicsDeviceEvent(UnityGfxDeviceEventType eventType); static IUnityInterfaces* s_UnityInterfaces = NULL; static IUnityGraphics* s_Graphics = NULL; static UnityGfxRenderer s_DeviceType = kUnityGfxRendererNull; extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces) { DebugLog("--- UnityPluginLoad"); s_UnityInterfaces = unityInterfaces; s_Graphics = s_UnityInterfaces->Get<IUnityGraphics>(); s_Graphics->RegisterDeviceEventCallback(OnGraphicsDeviceEvent); // Run OnGraphicsDeviceEvent(initialize) manually on plugin load OnGraphicsDeviceEvent(kUnityGfxDeviceEventInitialize); } extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginUnload() { s_Graphics->UnregisterDeviceEventCallback(OnGraphicsDeviceEvent); } // -------------------------------------------------------------------------- // GraphicsDeviceEvent // Actual setup/teardown functions defined below #if SUPPORT_D3D9 static void DoEventGraphicsDeviceD3D9(UnityGfxDeviceEventType eventType); #endif #if SUPPORT_D3D11 static void DoEventGraphicsDeviceD3D11(UnityGfxDeviceEventType eventType); #endif #if SUPPORT_D3D12 static void DoEventGraphicsDeviceD3D12(UnityGfxDeviceEventType eventType); #endif #if SUPPORT_OPENGLES static void DoEventGraphicsDeviceGLES(UnityGfxDeviceEventType eventType); #endif static void UNITY_INTERFACE_API OnGraphicsDeviceEvent(UnityGfxDeviceEventType eventType) { UnityGfxRenderer currentDeviceType = s_DeviceType; switch (eventType) { case kUnityGfxDeviceEventInitialize: { DebugLog("OnGraphicsDeviceEvent(Initialize).\n"); s_DeviceType = s_Graphics->GetRenderer(); currentDeviceType = s_DeviceType; break; } case kUnityGfxDeviceEventShutdown: { DebugLog("OnGraphicsDeviceEvent(Shutdown).\n"); s_DeviceType = kUnityGfxRendererNull; g_Cam1TexturePointer = NULL; break; } case kUnityGfxDeviceEventBeforeReset: { DebugLog("OnGraphicsDeviceEvent(BeforeReset).\n"); break; } case kUnityGfxDeviceEventAfterReset: { DebugLog("OnGraphicsDeviceEvent(AfterReset).\n"); break; } }; #if SUPPORT_D3D9 if (currentDeviceType == kUnityGfxRendererD3D9) DoEventGraphicsDeviceD3D9(eventType); #endif #if SUPPORT_D3D11 if (currentDeviceType == kUnityGfxRendererD3D11) DoEventGraphicsDeviceD3D11(eventType); #endif #if SUPPORT_D3D12 if (currentDeviceType == kUnityGfxRendererD3D12) DoEventGraphicsDeviceD3D12(eventType); #endif #if SUPPORT_OPENGLES if (currentDeviceType == kUnityGfxRendererOpenGLES20 || currentDeviceType == kUnityGfxRendererOpenGLES30) DoEventGraphicsDeviceGLES(eventType); #endif } // -------------------------------------------------------------------------- // GetRenderEventFunc, an example function we export which is used to get a rendering event callback function. extern "C" UnityRenderingEvent UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API GetRenderEventFunc() { return OnRenderEvent; }
      
      







すべてをまとめる方法



AssetsフォルダーにPluginsフォルダーを作成します。 プラグインを入れます。 お気に入りのファイルシステムユーティリティを使用するだけです。 Unityは動的ライブラリを一度だけロードするため、変更を加えた場合はUnityを再起動する必要があります。 起動して、カメラからの画像にミラーされた碑文「Cam1」を表示します。 これは、OpenGLとOpenCVのテクスチャ表現の違いによるものです。



コードはhabr_part2ブランチのgithubで利用可能です



All Articles