【问题标题】:OpenCV create Mat from byte arrayOpenCV 从字节数组创建 Mat
【发布时间】:2012-11-15 23:15:22
【问题描述】:

在我的 C++ dll 中,我从字节数组创建 Mat:

BYTE * ptrImageData;  //Image data is in this array passed to this function

Mat newImg = Mat(nImageHeight, nImageWidth, CV_8UC3, ptrImageData);

创建的图像带有一些灰色阴影,而不是原始图像。

这是从字节数组创建 Mat 的正确方法吗?

请看代码

ptrImageData 从 C# 代码传递给 C++ dll。

传递图像数据的C#代码

System.Drawing.Image srcImage //Has the image
MemoryStream ms = new MemoryStream(); 
Marshal.FreeHGlobal(ptrImageData); 
srcImage.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); 
byte[] imgArray = ms.ToArray();
ms.Dispose();


int size1 = Marshal.SizeOf(imgArray[0]) * imgArray.Length;
IntPtr ptrImageData = Marshal.AllocHGlobal(size1);
Marshal.Copy(imgArray, 0, ptrImageData, imgArray.Length);

//Calling C++ dll function
ProcessImage(ptrImageData, srcImage.Width, srcImage.Height);

Marshal.FreeHGlobal(ptrImageData);

【问题讨论】:

  • 我觉得你的C++代码有错误,Mat newImg(...), or Mat* newImg = new Mat(..),你写的不是C++风格。
  • @healer.. 代码是正确的。在上面的代码中,newImg 正在使用 Mat 类的 explicit 构造函数进行初始化。
  • 请提供更详细的代码,比如你如何显示图像,ptrImageData的布局是什么。
  • 请用代码查看已编辑的问题
  • 当您真正需要原始字节时,为什么要将图像保存为 JPEG?你确定System.Drawing.Imagecv::Mat的字节格式匹配吗?为什么不格式化问题中的示例代码?

标签: c# c++ opencv


【解决方案1】:

C++ 代码看起来没问题,因为它为提供的图像数据创建了一个矩阵包装器,假设缓冲区是传统的 RGB8 格式。请注意,此构造函数复制缓冲区,因此缓冲区必须在此 Mat 实例的持续时间内保持有效(或被复制)。

Mat newImg = Mat(nImageHeight, nImageWidth, CV_8UC3, ptrImageData);

看来问题在于您的 C# 代码。我不是 C# 开发人员,但我会尽力提供帮助。您正在创建一个内存流并使用 JPEG 编解码器将图像的压缩版本写入缓冲区,就像它是一个文件一样。但这不是cv::Mat 所期望的数据格式,所以你基本上会看到垃圾(压缩数据解释为未压缩)。

给定一个System.Image.Drawing.Image 实例,您可以直接创建一个包装器Bitmap 对象(或者可以使用as,因为它是一个简单的向下转换)。然后你可以使用Bitmap.LockBits() 方法来获取指向底层图像数据的指针。

Bitmap bmp = new Bitmap(sourceImage);

// Lock the bitmap's bits.  
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
System.Drawing.Imaging.BitmapData bmpData =
    bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
    bmp.PixelFormat);

// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;

// Declare an array to hold the bytes of the bitmap.
int bytes  = Math.Abs(bmpData.Stride) * bmp.Height;
byte[] rgbBuffer = new byte[bytes];

// Copy the RGB values into the array.
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbBuffer, 0, bytes);

// Do your OpenCV processing...
// ...

// Unlock the bits.
bmp.UnlockBits(bmpData);

然后您可以将rgbBuffer 传递给 OpenCV。

我也不相信原始代码中的内存管理是完全正确的,但无论如何,只要缓冲区所有权的范围在 lock 和 unlock 方法调用内,上述方法都可以工作。如果图像数据要超过此代码块,则必须复制缓冲区。

也要小心您的像素格式 - 您需要确保 Image/Bitmap 实例确实包含 RGB8 数据。 OpenCV 的cv::Mat 具有各种标志,因此您可以使用各种内存中的图像格式。但请注意,这些与磁盘上的(通常是压缩的)格式相同,例如 PNG、TIFF 等。

【讨论】:

  • srcImage.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg) 调用更改为使用ImageFormat.Bmp 而不是此处建议的复杂转换可能就足够了
【解决方案2】:

是的,这是从字节数组创建 Mat 的一种方法。您只需要注意您的数组包含您认为的内容即可。

创建的图像带有一些灰色阴影,而不是原始图像。

所以你在 newImg 中得到了一个图像?原始数据的像素格式是什么?

也许您已经切换了红色和蓝色通道。以下行将交换频道:

cv::cvtColor(newImg,swappedImg,CV_RGB2BGR);

【讨论】:

    【解决方案3】:

    这里是文档链接:http://docs.opencv.org/modules/core/doc/basic_structures.html#mat-mat

    一般来说,你应该注意两件事:

    1. 当您将外部数据传递给矩阵构造函数时,外部数据不会自动释放,因此您应该注意它。如果您希望 OpenCV 矩阵关心内存,那么您应该复制矩阵(您可以通过多种方式来实现,例如使用 Mat::cloneMat::copyTo 方法。
    2. 外部数据可能不连续,即行大小可能大于宽度乘以通道数乘以数据元素大小。因此,您可能希望将“step”指定为构造函数的最后一个参数。如果您手动分配外部数据并且 100% 确定它是连续的,那么您可能无法通过步数并依赖自动步数计算。

    我不熟悉 C#,但在我看来,您在 ProcessImage 调用后立即释放数据。因此,如果 ProcessImage 是异步的或以某种方式缓存了您的矩阵(即矩阵的生命周期比 ProcessImage 调用更长),那么您应该关心内存管理。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-11-08
      • 1970-01-01
      • 1970-01-01
      • 2017-08-28
      • 1970-01-01
      • 1970-01-01
      • 2016-02-28
      • 1970-01-01
      相关资源
      最近更新 更多