【问题标题】:OpenCV dll calls from Unity3D lead to FPS drop来自 Unity3D 的 OpenCV dll 调用导致 FPS 下降
【发布时间】:2019-11-19 01:20:55
【问题描述】:

我想在 Unity3D 中识别 ArUco 标记并将游戏对象附加到它们的位置。我知道 Asset Store 中有软件包,但是当其他人使用它时,我正在寻找一个免费的现有解决方案或自己尝试。

ArucoUnity 检测效果很好,但 Unity 在访问rvecs、tvecs 的数据时崩溃。当调用c++方法au_cv_Vec3d_get(CppPtr, i, CppPtr);时,Vec3d类中的Get(int i)某处出现错误

OpenCv plus Unity 这个实现似乎不完整,因为不存在EstimatePoseSingleMarker 或类似的函数来获取rvecs 和tvecs。最后一次更新是 2019 年 1 月。

由于 Unity (C#) 提供了访问非托管代码的可能性,我遵循了 this 很棒的教程,并且请求我能够将 cv::VideoCaputre 流转发到 Unity。发生的唯一问题是 FPS 下降到 35-40 左右,而通常我得到 90-100 左右。

C# 代码:

void Update()
{
    MatToTexture2D();
    image.texture = tex;
}

void MatToTexture2D()
{
    OpenCVInterop.GetRawImageBytes(pixelPtr);
    //Update the Texture2D with array updated in C++
    tex.SetPixels32(pixel32);
    tex.Apply();
}

C++ 代码:

extern "C" void __declspec(dllexport) __stdcall  GetRawImageBytes(unsigned char* data)
{
    _capture >> _currentFrame;

    cv::Mat resizedMat(camHeight, camWidth, _currentFrame.type());
    cv::resize(_currentFrame, resizedMat, resizedMat.size(), cv::INTER_CUBIC);

    //Convert from RGB to ARGB 
    cv::Mat argb_img;
    cv::cvtColor(resizedMat, argb_img, cv::COLOR_BGR2BGRA);
    std::vector<cv::Mat> bgra;
    cv::split(argb_img, bgra);
    std::swap(bgra[0], bgra[3]);
    std::swap(bgra[1], bgra[2]);
    std::memcpy(data, argb_img.data, argb_img.total() * argb_img.elemSize());
}

原因似乎是第一行_capture &gt;&gt; _currentFrame;,但由于其他项目必须这样做(至少我猜是这样),我想知道是否还有其他原因。 如果我无法解决此问题,我必须寻找替代方法。

【问题讨论】:

    标签: c# c++ opencv unity3d interop


    【解决方案1】:

    只是在Mars' answer 上添加/构建:

    对于线程问题,我实际上会使用线程保存ConcurrentStack&lt;Color32[]&gt;。堆栈是“后进先出”的,因此返回的第一项始终是线程添加的最后一个图像数据。

    • 线程使用Push(pixel32)添加带有图像数据的新条目
    • 在Update 中,您只使用最新条目(TryPop) 来更新纹理。
    • 其余的你忽略 (Clear)。

    类似

    // the OpenCV thread will add(push) entries
    // the Unity main thread will work on the entries
    private ConcurrentStack<Color32[]> stack = new ConcurrentStack<Color32[]>();
    
    public RawImage image;
    public Texture2D tex;
    
    private Thread thread;
    
    void Start()
    {
        // Wherever you get your tex from
        tex = new Texture2D(...);
    
        // it should be enough to do this only once
        // the texture stays the same, you only update its content
        image.texture = tex;
    }
    
    // do things in OnEnable so everytime the object gets enabled start the thread
    void OnEnable()
    {
        stack.Clear();
    
        if(thread != null)
        {
            thread.Abort();
        }
    
        thread = new Thread(MatToTexture2D);
        thread.Start();
    }
    
    void Update()
    {
        // here in the main thread work the stack
        if (stack.TryPop(out var pixels32))
        {
            // Only use SetPixels and Apply when really needed
            tex.SetPixels32(pixels32);
            tex.Apply();
        }
    
        // Erase older data
        stack.Clear();
    }
    
    // Make sure to terminate the thread everytime this object gets disabled
    private void OnDisable()
    {
        if(thread == null) return;
    
        thread.Abort();
        thread = null;
    }
    
    // Runs in a thread!
    void MatToTexture2D()
    {
        while(true)
        {
            try
            {
                // Do what you already have
                OpenCVInterop.GetRawImageBytes(pixelPtr);
    
                // However you convert the pixelPtr into Color32
                Color32[] pixel32 = GetColorArrayFromPtr(pixelPtr);
    
                // Now add this data to the stack
                stack.Push(pixel32);
            }
            catch (ThreadAbortException ex) 
            { 
                // This exception is thrown when calling Abort on the thread
                // -> ignore the exception since it is produced on purpose
            } 
        }
    }
    

    【讨论】:

    • 如果您确定它不会弹出您的记忆,Stack 是一个很好的答案!在没有新图像时不重新设置数据也是一个可靠的改进。
    • 但是对于它的价值,我认为如果堆栈包含多个图像的罕见情况,您可能希望遍历所有图像(基本上是 2-3 倍的播放速度,直到你被赶上了)
    • 对于类似的生产项目,规范是一个 heap (FIFO),具有最大大小:)
    • @Mars 不确定,对于 OpenCV 设法在一帧内捕获两个图像的罕见情况,我想我把第二个扔掉了^^ 但肯定可能还有其他选择。需要一些决定、数学和简单的测试才能在这里找到“完美”的解决方案。
    • 当然..我猜你是对的,谢谢你的提示!刚刚更新它使用OnEnable 来启动一个线程并使用OnDisable 来终止它
    【解决方案2】:

    如果我没记错的话,获取图像的 C++ 调用 (_capture &gt;&gt; _currentFrame;) 是阻塞/同步的,这意味着您的代码在实际检索到图像之前不会继续。您可能希望异步运行 MatToTexture2D 代码。 ※这意味着你的帧率将高于你的图像检索率。

    让您的MatToTexture2D 函数根据需要持续运行,更新tex。然后继续把你的贴图设置为最新的tex,连续2-3帧可能是同一个值。


    编辑: @derHugo's answer 在编程方面更加可靠,所以我将隐藏这部分。上面解释了基本问题,derHugo 的解决方法比我的伪代码要好得多:)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-03
      • 1970-01-01
      • 2021-10-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多