写在前面的话:记录Unity调用opencv里的坑。这是趟了无数的坑之后,写下的满纸的辛酸泪。各种奇怪的错误、闪退折磨了N久之后终于得到的一个好的方法用来在Unity和OpenCV之间传递图片。PS:作为一个长期使用C#的程序猿,弄C++实在是太痛苦了,如果代码有什么不合理的地方也希望各位大佬指正批评。

1. 关于DLL

注意,本文不使用OpenCVforUnity!
关于C#调用C++的DLL,可以参考这里:
Unity调用动态链接库dll和so .
写的很详细,非常值得参考。需要注意的是,函数一定要按照链接的方式去写,不然可能会找不到函数入口(这是坑之一)。

2.Texture2D=>Mat

首先,我们一般得到的贴图都是一个Texture,那么怎么转成Texture2D呢?可以使用以下方法:

Texture2D TextureToTexture2D(Texture texture)
    Texture2D texture2D = new Texture2D(texture.width, texture.height, TextureFormat.RGBA32, false);
    RenderTexture currentRT = RenderTexture.active;
    RenderTexture renderTexture = RenderTexture.GetTemporary(texture.width, texture.height, 32);
    Graphics.Blit(texture, renderTexture);
    RenderTexture.active = renderTexture;
    texture2D.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
    texture2D.Apply();
    RenderTexture.active = currentRT;
    RenderTexture.ReleaseTemporary(renderTexture);
    return texture2D;

有了这个Texture2D之后,我们需要获取保存图像的指针。代码如下:

pixels = texture2D.GetPixels32();
GCHandle pixelHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned);
IntPtr pixelPointer = pixelHandle.AddrOfPinnedObject();

关于GCHandle可以参考这里:GCHandle
这里的pixels是一个Color32[]。
以上的代码需要:

using System;
using System.Runtime.InteropServices;

ok,C#端先到这里。
接下来是C++了。

extern "C" {
	DLLExport uchar* Unity2OpenCVImage(char* inputData, int width, int height,int threshold ,int& size)
		vector<KeyPoint> keypoints;
		Mat opencvImage(height, width, CV_8UC4);
		memcpy(opencvImage.data, inputData, width*height * 4);
		//cvtColor(opencvImage, opencvImage, CV_BGR2RGB);//修改色彩通道BGR=>RGB
		//flip(opencvImage, opencvImage, 0);//翻转图片0为上下翻转,1为左右翻转,-1为01的组合
		Mat dst = opencvImage.clone();
		imshow("result", dst);
		Ptr<FastFeatureDetector> detector = FastFeatureDetector::create(threshold);
		detector->detect(opencvImage, keypoints);
		drawKeypoints(dst, keypoints, dst, Scalar::all(-1), DrawMatchesFlags::DRAW_OVER_OUTIMG);
		size = dst.cols*dst.rows*dst.channels();
		uchar* result = new uchar[dst.cols*dst.rows*4];
		memcpy(result, dst.data, dst.total()*sizeof(uchar)*4);
		return result;

到imshow()为止完成了图像的传递,这个函数会将unity里物体上的材质显示到窗口中。
其中注释掉的两行是用来修改色彩通道和翻转图片的,因为Unity和OpenCV的图像存储方式不同,具体可以参考这里:图像计算的像素坐标系差异(这是坑之二)。
另外,需要注意的是memcpy这个函数,他是将inputData(类型为char* ,是函数输入值)里所有的内容拷贝到Mat.data里。第三个参数是拷贝长度,思考一下,一张四通道的图片的大小应该是多少?当然是weight * height * 4咯。(这是坑之三,一定要考虑好需要拷贝的大小,不然图像会不完整)。再多说一句,这里的大小严格的说应该是内存图像行跨度 * 高 * 4。不过我这里内存图像行跨度等于图像宽。关于内存图像行跨度、memcpy的使用以及Mat.data的内容请看这里:Mat::data指针

3.Mat=>Texture2D

在接着上面的DLL,我写了特征点检测。所以dst上面会有一些检测到的特征点,如果不需要当然可以去掉。
如果需要从DLL返回一张图片,则需要先将Mat里的data拷贝到一个数组里。这里依旧是使用memcpy。首先需要一个uchar*用来接收数据,这里的数据大小是 dst.total()*sizeof(uchar)*4,因为我们的Mat是CV_8UC4的,也就是八位Unsigned char(uchar),四通道。如果你使用的图片参数不是这个,那需要修改大小,可以参考这里:
图片格式类型,总之要把图片里的所以数据都拷进去。
也许有人会问,我为什么不直接返回dst.data?原因是在退出函数时,Mat里的数据会被释放掉,返回的指针会变成空指针!!!(这是坑之四)。
接下是在Unity里调用DLL函数。

private IntPtr Data = IntPtr.Zero;
Data = Unity2OpenCVImage(pixelPointer, width, height, 80, ref size);

这里的pixelPointer还是第2步获取的那个指针。另外,在C#使用ref int相当于C++里的in&,这里用于获取图片大小。
接下来:

private byte[] buffer = new byte[size];
Marshal.Copy(Data, buffer, 0, size);
Color32[] colors = new Color32[width * height];
for (int i = 0; i < colors.Length; i++)
    colors[i] = new Color32(buffer[4 * i], buffer[4 * i + 1], buffer[4 * i + 2],1);
Texture2D outputTexture = new Texture2D(640, 640);
outputTexture.SetPixels32(colors);
outputTexture.Apply();
quad2.GetComponent<Renderer>().material.mainTexture = outputTexture;

这里使用到了Marshal.Copy这个函数,它的作用和C++里的memcpy有点相似,可以将Intptr指向的内容拷贝到buffer里,具体细节参考:Marshal.Copy。一定要注意拷贝内容的大小,否则可能会出现图片大小没对齐等问题,发生闪退或者报错。(这里是坑之五)
在获取buffer之后,我将buffer里的数据转成了Color32的类型,然后使用SetPixels32()将像素保存到一个Texture2D 上,最后将quad2上的材质的主贴图设置为这个Texture2D ,这样quad2上会显示传回来的图片。需要注意的是,我这里对buffer里的数据的转化可能不是最好的处理方式,不过这样做确实是可行的。另外,需要注意在保存像素到Texture2D后,需要使用Texture2D.Apply()刷新,这也算是一个小坑。

最后,将两端的代码完整的展示一下:
C#端:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.




    
Runtime.InteropServices;
using System.IO;
public class aaa : MonoBehaviour {
    [HideInInspector]
    public Texture2D texture2D;
    public GameObject quad2;
    private Color32[] pixels;
    private IntPtr outputData;
    private int width;
    private int height;
    private IntPtr Data = IntPtr.Zero;
    private byte[] buffer;
    private int size;
    [DllImport("FastDection")]
    public static extern IntPtr Unity2OpenCVImage(IntPtr inputData , int width, int height,int threshold,ref int size);
    void Start () {
        texture2D = TextureToTexture2D(GetComponent<Renderer>().material.mainTexture);
        width = texture2D.width;
        height = texture2D.height;
        pixels = texture2D.GetPixels32();
        GCHandle pixelHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned);
        IntPtr pixelPointer = pixelHandle.AddrOfPinnedObject();
        int stride = width % 4 == 0 ? width : (width / 4 + 1) * 4;
        Data = Unity2OpenCVImage(pixelPointer, width, height, 80,ref size);
        buffer = new byte[size];
        Marshal.Copy(Data, buffer, 0, size);
        Color32[] colors = new Color32[width * height];
        for (int i = 0; i < colors.Length; i++)
            colors[i] = new Color32(buffer[4 * i], buffer[4 * i + 1], buffer[4 * i + 2],1);
        Texture2D outputTexture = new Texture2D(640, 640);
        outputTexture.SetPixels32(colors);
        outputTexture.Apply();
        quad2.GetComponent<Renderer>().material.mainTexture = outputTexture;
    Texture2D TextureToTexture2D(Texture texture)
        Texture2D texture2D = new Texture2D(texture.width, texture.height, TextureFormat.RGBA32, false);
        RenderTexture currentRT = RenderTexture.active;
        RenderTexture renderTexture = RenderTexture.GetTemporary(texture.width, texture.height, 32);
        Graphics.Blit(texture, renderTexture);
        RenderTexture.active = renderTexture;
        texture2D.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
        texture2D.Apply();
        RenderTexture.active = currentRT;
        RenderTexture.ReleaseTemporary(renderTexture);
        return texture2D;

C++端(里面有一些小的注释,希望也能帮到大家):

#define DLLExport __declspec(dllexport)
#include "opencv2/opencv.hpp"
#include <opencv2/xfeatures2d.hpp>
#include <opencv2/core/core.hpp>
#include <iostream>
using namespace std;
using namespace cv;
using namespace cv::xfeatures2d;
//typedef unsigned char byte;
extern "C" {	
	DLLExport uchar* Unity2OpenCVImage(char* inputData, int width, int height,int threshold ,int& size)
		vector<KeyPoint> keypoints;
		Mat opencvImage(height, width, CV_8UC4);
		memcpy(opencvImage.data, inputData, width*height * 4);
		//最后一个参数为拷贝长度,应为图片压成的数组的长度,即高*宽*通道数(RGBA所以是4),
		//需要注意!!=>这里可能应该是内存图像行跨度*高*4!!,(此处宽==内存图像行跨度=640)
		//cvtColor(opencvImage, opencvImage, CV_BGR2RGB);//修改色彩通道BGR=>RGB
		//flip(opencvImage, opencvImage, 0);//翻转图片0为上下翻转,1为左右翻转,-1为01的组合
		//由于这里是把texture2D转Mat,检测特征点之后再把Mat转回去,所以不需要修改色彩通道和翻转图片,
		//否则后面还是要修改,这不是脱裤子放屁么
		Mat dst = opencvImage.clone();
		imshow("result", dst);
		Ptr<FastFeatureDetector> detector = FastFeatureDetector::create(threshold);
		detector->detect(opencvImage, keypoints);
		drawKeypoints(dst, keypoints, dst, Scalar::all(-1), DrawMatchesFlags::DRAW_OVER_OUTIMG);
		size = dst.cols*dst.rows*dst.channels();
		uchar* result = new uchar[dst.cols*dst.rows*4];
		memcpy(result, dst.data, dst.total()*sizeof(uchar)*4);
		注意!!这里必须要使用memcpy拷贝一遍数据,而不能直接返回dst.data,不然退出函数时Mat里的数据会被释放掉,返回的会变成空指针
		return result;

关于WebCamTexture转Mat

由于WebCamTexture是摄像头获取到的贴图,所以会不断刷新,建议放在携程里写以降低卡顿,可以参考这里:unity3d和opencv实时图像传递,处理,高效解决方案,几乎不影响fps,也感谢这位大神的文章,对我帮助很大。

在网络小游戏中有时候会有这样的需求的就是Texture和Texture2D类型的换,例如:本地选择头像更换,背包图片的更新等.当然这方法只适用于小量级的小需求,大的需求会使用专门的处理类完成处理. 小游戏一般会使用更省性能的RawImage,不管怎么的使用场景,今天只分享技术. 1、webCamTexture换为Mat后让RawImage显示 ①总结:good;这个是我自己利用opencvForUnity实现的,将webCamTexture换为Mat换为rawImage.texture 1、as Texture2D; ①总结:pass;失败...   1.首先我们需要简单认识一下,unity有关摄像头需要用到的内置类;     WebCamDevice        官方文档:https://docs.unity3d.com/ScriptReference/WebCamDevice.html     WebCamTexture 上次博文有个unityopencv传递图片的问题,终于找个时间解决了。过程是一把心酸一把泪,给unity打包过c++dll的同志肯定能明白其中的痛苦,网上看了很多文章,都只是笼统的一说,没有什么特别具体的解决方案,甚至很多解决方案很不负责任,动不动就内存溢出,泄露,越界。当然也可能人家只传一张图片,没有这么强的实时性。还有unityopencv图片对照真是坑上加坑,具体有多坑,谁踩谁知道,手... using System.Collections.Generic; using UnityEngine; using OpenCVForUnity.ImgprocModule; using OpenCVForUnity.ImgcodecsModule; using OpenCVForUnity.CoreModule; using Rect = OpenCVForUnity.CoreModule.Rect; using Ope c#中无法将类型“int”隐式换为“System.IntPtr” 这个是我引用了一个api函数时出现的问题,我在声明中把intptr换成了int还是不可以,这是为什么呢?要如何处理呢? 您好,C#中的IntPtr类型称为“平台特定的整数类型”,它们用于本机资源,如窗口句柄。 资源的大小取决于使用的硬件和操作系统,但其大小总是足以包含系统的指针 void GetRawImageBytes(unsigned char* data, int width, int height) //Resize Mat to match the array passe... 1.首先下载opencv2.4.10,解压缩后放在合适的地方,然后根据自己的电脑(32位或64位)选择X86或X64,我的是32位,将“opencv存放路径\build\x86\vc12\bin”加入到系统的path环境变量中。 2.下载opencvsharp,它是一个给.net 框架使用的opencv卷绕(wrapper of OpenCV for .NET Framework),它不仅支持.... 1 Mat是什么 MatOpenCV中最重要的类,以后我们与图像相关的操作基本都要用到Mat类。 那么Mat是什么呢?它是OpenCV中保存图像数据的基本容器。 Mat类可以表示n维的单通道或多通道数组,它可以存储实数/复数的向量和矩阵,单色或彩色图像等。 2 创建Mat对象 如下可以创建一个rows行,cols列的矩阵,类型为type。 Mat mat = new Mat(行数rows, 列数cols, 类型type); 对于类型type,其格式为CV_[bit](U|S|F)C[channel