You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity调用C++ OpenCV DLL处理视频流后Color32数组为空求助

问题排查与修复方案

核心错误点

  • 交互签名不匹配:C#侧ref Color32[]传递的是托管数组首元素的指针,对应C++侧应为一级指针Color32* rawImage_Inbound,原代码用二级指针导致内存地址读取错误,写入位置和C#数组不对应
  • cv::Mat构造参数错误:cv::Mat构造顺序为Mat(行高, 列宽, 类型, 数据指针),原代码写反了宽高,且绑定的CV_8UC4四通道类型和后续处理用的三通道Vec3b不匹配
  • 通道顺序不兼容:OpenCV默认从摄像头读取的帧为BGR三通道格式,Unity的Color32为RGBA四通道顺序,直接赋值会出现颜色错误、内存越界
  • 像素读写类型不匹配:四通道Mat读写像素应使用Vec4b而非Vec3b,否则会出现每个像素的内存偏移错误,导致颜色一致的问题

修复后代码

C# 侧代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.InteropServices;

[RequireComponent(typeof(SpriteRenderer))]
public class SpriteMaker : MonoBehaviour
{
    SpriteRenderer rend;
    private Color32[] rawImage;
    private Texture2D tex;

    internal static class OpenCVInterop
    {
        [DllImport("ImageProcessor_dll")]
        internal static extern void Init();

        // 传入数组首指针,同时传宽高避免硬编码错误
        [DllImport("ImageProcessor_dll")]
        internal static extern void ImageProcessing([In, Out] Color32[] rawImage, int width, int height);
    }

    void Start()
    {
        OpenCVInterop.Init();
        int width = 640;
        int height = 240;
        rawImage = new Color32[width * height];
        tex = new Texture2D(width, height, TextureFormat.RGBA32, false);
        rend = GetComponent<SpriteRenderer>();
    }

    void Update()
    {
        // 逐帧更新图像
        OpenCVInterop.ImageProcessing(rawImage, tex.width, tex.height);
        tex.SetPixels32(rawImage);
        tex.Apply();
        if (rend.sprite == null)
        {
            Sprite newSprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), Vector2.one * 0.5f);
            rend.sprite = newSprite;
        }
    }
}

C++ 侧代码

#include "pch.h"
#include <iostream>
#include "opencv2/opencv.hpp"
#include "opencv2/highgui.hpp"

using namespace cv;
using namespace std;

VideoCapture _capture;
Mat frameBuffer;

struct Color32
{
    uchar red;
    uchar green;
    uchar blue;
    uchar alpha;
};

extern "C" void __declspec(dllexport) __stdcall  Init()
{
    _capture.open(0);
    if (_capture.isOpened())
    {
        std::cout << "VideoCapture is opened" << std::endl;
    }
}

extern "C" void __declspec(dllexport) __stdcall ImageProcessing(Color32* rawImage_Inbound, int width, int height)
{
    // 先读取摄像头帧到三通道缓冲区
    _capture >> frameBuffer;
    // 缩放到目标尺寸
    resize(frameBuffer, frameBuffer, Size(width, height));
    // 绑定C#传入的内存为四通道Mat
    Mat rawImage(height, width, CV_8UC4, rawImage_Inbound);

    // 转换BGR到RGBA,同时写入到绑定的C#数组内存
    cvtColor(frameBuffer, rawImage, COLOR_BGR2RGBA);

    // 图像处理逻辑,现在用Vec4b读写四通道像素
    for (int y = 0; y < rawImage.rows; y++)
    {
        for (int x = 0; x < rawImage.cols; x++)
        {
            Vec4b& color = rawImage.at<Vec4b>(Point(x, y));
            float colorB = frameBuffer.at<Vec3b>(Point(x, y))[0];
            float colorR = frameBuffer.at<Vec3b>(Point(x, y))[2];
            float ID = (colorR - colorB) / (colorR + colorB + 0.001f); // 加小值避免除0

            if (ID >= -1 && ID < 0)
            {
                color[0] = 128; // R
                color[1] = 128; // G
                color[2] = 128; // B
                color[3] = 255; // A
            }
            else
            {
                color[0] = 255; // R
                color[1] = 0; // G
                color[2] = 0; // B
                color[3] = 255; // A
            }
        }
    }
    imshow("frame", rawImage);
    waitKey(1); // 必须加waitKey才能正常显示OpenCV窗口
}

补充注意点

  • C#侧给DLL传数组时加上[In, Out]属性,明确告知运行时该数组需要双向数据同步
  • OpenCV操作后必须调用waitKey(1)才能刷新窗口,同时处理UI事件
  • 摄像头默认输出尺寸和你设定的640*240不一致时,必须先做resize再处理,避免内存越界

内容的提问来源于stack exchange,提问作者Lennart

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 06:15:00