【问题标题】:Convert OpenCV Mat to Texture2D?将 OpenCV Mat 转换为 Texture2D?
【发布时间】:2018-09-04 01:51:48
【问题描述】:

元上下文:

我目前正在开发一款使用 opencv 替代普通输入(键盘、鼠标等)的游戏。我通过 DllImports 在 C++ 中使用 Unity3D 的 C# 脚本和 opencv。我的目标是在我的游戏中创建一个来自 opencv 的图像。

代码上下文:

正如通常在 OpenCV 中所做的那样,我使用 Mat 来表示我的图像。这是我导出图像字节的方式:

cv::Mat _currentFrame;

...

extern "C" byte * EXPORT GetRawImage()
{
    return _currentFrame.data;
}

这就是我从 C# 导入的方式:

[DllImport ("ImageInputInterface")]
private static extern IntPtr GetRawImage ();

...

public static void GetRawImageBytes (ref byte[] result, int arrayLength) {
    IntPtr a = GetRawImage ();
    Marshal.Copy(a, result, 0, arrayLength);
    FreeBuffer(a);
}

以我理解OpenCV的方式来看,我希望字节数组在uchar指针中序列化时是这样构造的:

b1, g1, r1, b2, g2, r2, ...

我正在使用以下方法将此 BGR 数组转换为 RGB 数组:

public static void BGR2RGB(ref byte[] buffer) {
    byte swap;
    for (int i = 0; i < buffer.Length; i = i + 3) {
        swap = buffer[i];
        buffer[i] = buffer[i + 2];
        buffer[i + 2] = swap;
    }
}

最后,我使用 Unity 的 LoadRawTextureData 将字节加载到纹理中:

this.tex = new Texture2D(
    ImageInputInterface.GetImageWidth(),
    ImageInputInterface.GetImageHeight(),
    TextureFormat.RGB24,
    false
);

...

ImageInputInterface.GetRawImageBytes(ref ret, ret.Length);
ImageInputInterface.BGR2RGB(ref ret);
tex.LoadRawTextureData(ret);
tex.Apply();

结果:

最终的图像似乎以某种方式分散,它类似于一些形状,但它似乎也是形状的三倍。这是我在镜头前握着我的手:

[我、我的手和相机]

做了一些测试,我得出结论,我正确解码了通道,因为使用我的手机发出 RGB 光,我可以重现现实世界的颜色:

[红色测试]

[蓝色测试]

[绿色测试]

图片中还有一些奇怪的线条:

[幽灵线]

还有我的脸可以比较这些图片:

[我在镜头前的脸]

问题:

既然我能够正确解码颜色通道,那么在解码 OpenCV 数组时我认为有什么错误?是我不知道 Unity 的 LoadRawTextureData 是如何工作的,还是我解码的方式不对?

OpenCV Mat.data 数组的结构如何?

更新

感谢@Programmer,他的解决方案就像魔术一样奏效。

[我快乐]

我稍微改变了他的剧本,没有必要做一些事情。在我的情况下,我需要使用 BGR2RGBA,而不是 RGB2RGBA:

extern "C" void EXPORT GetRawImage( byte *data, int width, int height )
{
    cv::Mat resizedMat( height, width, _currentFrame.type() );
    cv::resize( _currentFrame, resizedMat, resizedMat.size(), cv::INTER_CUBIC );

    cv::Mat argbImg;
    cv::cvtColor( resizedMat, argbImg, CV_BGR2RGBA );
    std::memcpy( data, argbImg.data, argbImg.total() * argbImg.elemSize() );
}

【问题讨论】:

  • 这种类型的结果有两个可能的原因:- 1. CHANNELS 不匹配。 2. PIXEL-TYPE 不匹配。尝试一些事情:- 1. 验证 Mat 的通道数。 (img.channels()) 2. 检查统一文档以获取更多原始数据的详细信息。 (就通道、数据类型而言)。希望这将有助于至少找到错误。
  • 恕我直言,看起来行结尾不匹配(如果通道数正确,但检查 opencv mat 通道数不是 4!!)。检查 mat.isContinuous 并研究一下 Unity 的图像是否有一些固定的 Bytes-per-Row 或其他东西。
  • 用于调试:创建一个 CV_8UC3 格式的空 cv::Mat,在中间画一条垂直线,并使用您的函数将其赋予纹理。纹理中那条线的出现会给你很多提示。

标签: c# c++ opencv unity3d


【解决方案1】:

使用SetPixels32 代替LoadRawTextureData。不要从 C++ 返回数组数据,而是从 C# 中执行此操作。创建Color32数组并用GCHandle.Alloc将其固定在c#中,将固定Color32数组的地址发送到C++,使用cv::resize调整cv::Mat的大小以匹配从C#发送的像素大小.您必须执行此步骤,否则会出现一些错误或问题。

最后,将 cv::MatRGB 转换为 ARGB,然后使用 std::memcpy 从 C++ 更新数组。然后可以使用SetPixels32 函数将更新后的Color32 数组加载到Texture2D 中。我就是这样做的,它一直在为我工作,没有任何问题。可能还有其他更好的方法可以做到这一点,但我从来没有找到。

C++:

cv::Mat _currentFrame;

void GetRawImageBytes(unsigned char* data, int width, int height)
{
   //Resize Mat to match the array passed to it from C#
    cv::Mat resizedMat(height, width, _currentFrame.type());
    cv::resize(_currentFrame, resizedMat, resizedMat.size(), cv::INTER_CUBIC);

    //You may not need this line. Depends on what you are doing
    cv::imshow("Nicolas", resizedMat);

    //Convert from RGB to ARGB 
    cv::Mat argb_img;
    cv::cvtColor(resizedMat, argb_img, CV_RGB2BGRA);
    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());
}

C#:

使用Renderer 附加到任何游戏对象,您应该会看到cv::Mat 在该对象上每帧显示和更新。如有混淆,请注释代码:

using System;
using System.Runtime.InteropServices;
using UnityEngine;

public class Test : MonoBehaviour
{
    [DllImport("ImageInputInterface")]
    private static extern void GetRawImageBytes(IntPtr data, int width, int height);

    private Texture2D tex;
    private Color32[] pixel32;

    private GCHandle pixelHandle;
    private IntPtr pixelPtr;

    void Start()
    {
        InitTexture();
        gameObject.GetComponent<Renderer>().material.mainTexture = tex;
    }


    void Update()
    {
        MatToTexture2D();
    }


    void InitTexture()
    {
        tex = new Texture2D(512, 512, TextureFormat.ARGB32, false);
        pixel32 = tex.GetPixels32();
        //Pin pixel32 array
        pixelHandle = GCHandle.Alloc(pixel32, GCHandleType.Pinned);
        //Get the pinned address
        pixelPtr = pixelHandle.AddrOfPinnedObject();
    }

    void MatToTexture2D()
    {
        //Convert Mat to Texture2D
        GetRawImageBytes(pixelPtr, tex.width, tex.height);
        //Update the Texture2D with array updated in C++
        tex.SetPixels32(pixel32);
        tex.Apply();
    }

    void OnApplicationQuit()
    {
        //Free handle
        pixelHandle.Free();
    }
}

【讨论】:

  • 我回家试试看!似乎它会起作用。 :)
  • 非常感谢,但对我来说,生成的图像是垂直和水平翻转的。所以它是颠倒的,左边是右边。
  • @link 好吧,您可以将要加载的对象翻转过来。
猜你喜欢
  • 2018-11-28
  • 1970-01-01
  • 2015-12-13
  • 2017-02-19
  • 2015-02-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多