c# - 从 C++ 更新 Texture2D 像素

标签 c# c++ unity3d pinvoke

在项目中,我通过 DllImports 使用 Unity3D 的 C# 脚本和 C++。我的目标是游戏场景有 2 个立方体 (Cube & Cube2),其中一个立方体纹理通过我的笔记本电脑摄像头和 Unity 的 webCamTexture.Play() 显示实时视频,并且立方体的另一个纹理显示由外部 C++ 函数 ProcessImage() 处理的视频。

代码上下文:

在c++中,我定义了

struct Color32
{
    unsigned char r;
    unsigned char g;
    unsigned char b;
    unsigned char a;
};

函数是

extern "C"
{
    Color32* ProcessImage(Color32* raw, int width, int height);
}
...

Color32* ProcessImage(Color32* raw, int width, int height)
{
    for(int i=0; i<width*height ;i++)
    {
       raw[i].r = raw[i].r-2;
       raw[i].g = raw[i].g-2;
       raw[i].b = raw[i].b-2;
       raw[i].a = raw[i].a-2;
    }
    return raw;
}

C#:

声明和导入

public GameObject cube; 
public GameObject cube2;
private Texture2D tx2D;
private WebCamTexture webCamTexture;

[DllImport("test22")]   /*the name of Plugin is test22*/
private static extern Color32[] ProcessImage(Color32[] rawImg, 
                                                 int width, int height);

获取相机情况并设置cube1、cube2 Texture

void Start()
{
    WebCamDevice[] wcd = WebCamTexture.devices;

    if(wcd.Length==0)
    {
        print("Cannot find a camera");
        Application.Quit();
    }
    else
    {
        webCamTexture = new WebCamTexture(wcd[0].name);
        cube.GetComponent<Renderer>().material.mainTexture = webCamTexture;

        tx2D = new Texture2D(webCamTexture.width, webCamTexture.height);
        cube2.GetComponent<Renderer>().material.mainTexture = tx2D;

        webCamTexture.Play();
    }
}

通过 DllImports 将数据发送到外部 C++ 函数,并使用 Color32[] a 接收处理后的数据。最后,我使用 Unity 的 SetPixels32 设置 tx2D(Cube2) 纹理:

void Update()
{
    Color32[] rawImg = webCamTexture.GetPixels32();
    System.Array.Reverse(rawImg);

    Debug.Log("Test1");
    Color32[] a = ProcessImage(rawImg, webCamTexture.width, webCamTexture.height);
    Debug.Log("Test2");

    tx2D.SetPixels32(a);
    tx2D.Apply();
}

结果:

结果只是立方体 1 的纹理显示实时视频,无法显示使用立方体 2 的纹理处理的数据。

错误:

SetPixels32 called with invalid number of pixels in the array UnityEngine.Texture2D:SetPixels32(Color32[]) Webcam:Update() (at Assets/Scripts/Webcam.cs:45)

我不明白为什么我将数组a输入到SetPixels32时数组中的像素数无效

有什么想法吗?


更新(2018 年 10 月 10 日)

感谢@Programmer,现在可以通过引脚内存工作。

顺便说一句,我找到了一些小的 problem这是关于 Unity 引擎的。当 Unity Camera 在 0 到 1 秒之间运行时,webCamTexture.widthwebCamTexture.height 总是返回 16x16 大小甚至请求更大的图像,如 1280x720然后它将在 1 秒后返回正确的大小。 (Possibly several frames)所以,我引用这个 post并延迟 2 秒在 Update() 函数中运行 Process() 并在 Process() 函数中重置 Texture2D 大小。它会正常工作:

delaytime = 0;
void Update()
{
    delaytime = delaytime + Time.deltaTime;
    Debug.Log(webCamTexture.width);
    Debug.Log(webCamTexture.height);

    if (delaytime >= 2f)
        Process();
}
unsafe void Process()
{
    ...

    if ((Test.width != webCamTexture.width) || Test.height != webCamTexture.height)
    {
       Test = new Texture2D(webCamTexture.width, webCamTexture.height, TextureFormat.ARGB32, false, false);
       cube2.GetComponent<Renderer>().material.mainTexture = Test;
       Debug.Log("Fixed Texture dimension");
    }
    ...
}

最佳答案

C# 不返回理解您从 C++ 返回的 Color32 对象。如果您将返回类型设置为 unsigned char* 然后在 C# 端使用 Marshal.Copy 将返回的数据复制到字节数组然后使用 Texture2D.LoadRawTextureData 到将数组加载到您的 Texture2D 中。 Here 是一个从 C# 返回字节数组的示例。


我不建议您返回数据,因为这很昂贵。填充您传递给函数的数组,然后只需将该数组重新分配给您的 Texture2D

C++:

extern "C"
{
    void ProcessImage(unsigned char* raw, int width, int height);
}
...

void ProcessImage(unsigned char* raw, int width, int height)
{
    for(int y=0; y < height; y++)
    {
        for(int x=0; x < width; x++)
        {
            unsigned char* pixel = raw + (y * width * 4 + x * 4);
            pixel[0] = pixel[0]-(unsigned char)2; //R
            pixel[1] = pixel[1]-(unsigned char)2; //G
            pixel[2] = pixel[2]-(unsigned char)2; //B
            pixel[3] = pixel[3]-(unsigned char)2; //Alpha
        }
    }
}

C#:

[DllImport("test22")]
private static extern void ProcessImage(IntPtr texData, int width, int height);

unsafe void ProcessImage(Texture2D texData)
{
    Color32[] texDataColor = texData.GetPixels32();
    System.Array.Reverse(texDataColor);

    //Pin Memory
    fixed (Color32* p = texDataColor)
    {
        ProcessImage((IntPtr)p, texData.width, texData.height);
    }
    //Update the Texture2D with array updated in C++
    texData.SetPixels32(texDataColor);
    texData.Apply();
}

使用:

public Texture2D tex;

void Update()
{
    ProcessImage(tex);
}

如果你不想使用unsafefixed关键字,你也可以使用GCHandle.Alloc在之前固定数组使用 GCHandle.AddrOfPinnedObject 将其发送到 C++ 端。有关如何执行此操作的信息,请参阅 this 帖子。


如果您遇到以下异常:

SetPixels32 called with invalid number of pixels in the array UnityEngine.Texture2D:SetPixels32(Color32[]) Webcam:Update() (at Assets/Scripts/Webcam.cs:45)

这意味着您调用 SetPixels32Texture2DWebCamTexture 纹理具有不同的大小或纹理维度。要解决此问题,请在调用 SetPixels32 之前检查纹理尺寸是否已更改,然后调整目标纹理的大小以匹配 WebCamTexture

替换

tx2D.SetPixels32(rawImg);
tx2D.Apply();

//Fix texture dimension if it doesn't match with the web cam texture size
if ((tx2D.width != webCamTexture.width) || tx2D.height != webCamTexture.height)
{
    tx2D = new Texture2D(webCamTexture.width, webCamTexture.height, TextureFormat.RGBA32, false, false);
    Debug.Log("Fixed Texture dimension");
}

tx2D.SetPixels32(rawImg);
tx2D.Apply();

关于c# - 从 C++ 更新 Texture2D 像素,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52686472/

相关文章:

c# - 如何在mysql查询中使用控件值

c# - 简单的 NUnit 测试失败,因为未抛出异常(抛出测试之外的异常)

c# - 如何统一显示网格线?

c# - 计算 3d 空间中两点之间的角度

android - 统一开发构建有什么用(android)

c# - 如何将二进制文件读入字节数组?

c# - 无法从 TFS 获取 VersionControlServer 服务

c++ - 内存地址值变化监听器

c++ - 根据颜色范围创建蒙版

c++ - 通过错误检查将 wstring 转换为 double 的方法