来自 Unity3D 的 OpenCV dll 调用导致 FPS 下降

OpenCV dll calls from Unity3D lead to FPS drop

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

ArucoUnity 检测非常有效,但 Unity 在访问 rvecstvecs 的数据时崩溃。 Vec3d class中的Get(int i)调用c++方法au_cv_Vec3d_get(CppPtr, i, CppPtr);时出现某处错误

OpenCv plus Unity 这个实现似乎并不完整,因为不存在 EstimatePoseSingleMarker 或类似的函数来获取 rvecstvecs。最后更新时间是 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 >> _currentFrame;,但由于其他项目也必须这样做(至少我是这么认为的),我想知道是否还有其他原因。 如果我无法解决这个问题,我必须寻找替代方法。

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

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


编辑: 对于编程方面来说更加可靠,所以我将隐藏该部分。上面解释了基本问题,derHugo 的解决方法比我的伪代码好得多:)

只是在 上添加/构建:

对于线程问题,我实际上会使用线程保存 ConcurrentStack<Color32[]>。堆栈是 "last-in | first-out",因此返回的第一项始终是线程添加的最后一个图像数据。

  • 该线程使用 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
        } 
    }
}