1.问题或需求描述
C# Bitmap 与 C++ opencv Mat 之间相互转换
2.解决方法或原理:
本文场景:使用C# 调用 由C++ 封装的基于opencv的视觉算法库,演示如何在两者之间互传图像数据。
CxxDll.cpp 部分源码:
DLL_API void WINAPI ColorToGray(
  IN unsigned char* img_src_data, IN int img_src_w, IN int img_src_h, IN int img_src_ch, int img_src_stride,
  OUT unsigned char* img_dst_data, OUT int& img_dst_w, OUT int& img_dst_h, OUT int& img_dst_ch, OUT int& img_dst_stride
)
{
  img_dst_w = 0;
  img_dst_h = 0;
  img_dst_ch = 0;
  img_dst_stride = 0;
  if (img_src_ch != 3) throw std::exception("error: img_src must be 3 channels");
  Mat img_src = Mat(img_src_h, img_src_w, CV_8UC3, img_src_data, img_src_stride).clone();
  Mat img_dst;
  cvtColor(img_src, img_dst, COLOR_BGR2GRAY);
  if (img_dst.empty() == false)
  {
    img_dst_w = img_dst.cols;
    img_dst_h = img_dst.rows;
    img_dst_ch = img_dst.channels();
    img_dst_stride = 4 * ((img_dst_w * img_dst_ch + 3) / 4);
    if ((img_dst_stride == img_dst.step.p[0]))
      memcpy(img_dst_data, img_dst.data, img_dst.total());
    else
    {
      uchar * img_dst_data_temp = new uchar[img_dst.cols * img_dst_stride];
      uchar* img_dst_ptr = img_dst.ptr();
      int idx = 0;
      int offset = 0;
      int offset_count = img_dst_stride - img_dst.step.p[0];
      for (int y = 0; y < img_dst.rows; y++)
      {
        for (int x = 0; x < img_dst.step.p[0]; x++)
        {
          idx = y * img_dst.step.p[0] + x;
          img_dst_data_temp[idx + offset] = img_dst_ptr[idx];
        }
        for (int x = 0; x < offset_count; x++)
        {
          img_dst_data_temp[idx + (++offset)] = 0;
        }
      }
      memcpy(img_dst_data, img_dst_data_temp, img_dst.rows * img_dst_stride);
      delete[] img_dst_data_temp;
    }
  }
}Test.cs 部分源码:
[DllImport("CxxDll.dll", EntryPoint = "ColorToGray")]
public static extern void ColorToGray(
    [In] IntPtr img_src_data, [In] int img_src_w, [In] int img_src_h, [In] int img_src_ch, [In] int img_src_stride,
    [Out] IntPtr img_dst_data, ref int img_dst_w, ref int img_dst_h, ref int img_dst_ch, ref int img_dst_stride
    );private void Test()
{
    Bitmap img_src;
    BitmapData img_src_data;
    IntPtr img_dst_data;
    int img_dst_w = 0;
    int img_dst_h = 0;
    int img_dst_ch = 0;
    int img_dst_stride = 0;
    Func<int, PixelFormat, int> GetStride = (width, format) =>
    {
        int bitsPerPixel = System.Drawing.Image.GetPixelFormatSize(format);
        int bytesPerPixel = (bitsPerPixel + 7) / 8;
        int stride = 4 * ((width * bytesPerPixel + 3) / 4);
        return stride;
    };
    img_src = (Bitmap)Bitmap.FromFile("C:\\Users\\Administrator\\Desktop\\IMG\\img_001.png");
    pictureBoxSrc.Image = img_src;
    if (img_src.PixelFormat != PixelFormat.Format24bppRgb)
        img_src = img_src.Clone(new Rectangle(0, 0, img_src.Width, img_src.Height), PixelFormat.Format24bppRgb);
    img_src_data = img_src.LockBits(new Rectangle(0, 0, img_src.Width, img_src.Height), ImageLockMode.ReadOnly, img_src.PixelFormat);
    img_dst_data = Marshal.AllocHGlobal((Int32)(img_src_data.Height * GetStride(img_src_data.Width, PixelFormat.Format8bppIndexed)));
    try
    {
        ColorToGray(
            img_src_data.Scan0,
            img_src_data.Width,
            img_src_data.Height,
            System.Drawing.Image.GetPixelFormatSize(img_src_data.PixelFormat) / 8,
            img_src_data.Stride,
            img_dst_data,
            ref img_dst_w,
            ref img_dst_h,
            ref img_dst_ch,
            ref img_dst_stride
            );
        
        //非托管数据源
        Bitmap bmp = new Bitmap(img_dst_w, img_dst_h, img_dst_stride, PixelFormat.Format8bppIndexed, img_dst_data);
        ColorPalette cp = bmp.Palette;
        for (int i = 0; i < 256; i++) cp.Entries[i] = Color.FromArgb(i, i, i);
        bmp.Palette = cp;
        //使用托管数据源
        Bitmap img_dst = new Bitmap(bmp.Width, bmp.Height);
        using (Graphics g = Graphics.FromImage(img_dst))
        {
            g.Clear(Color.Black);
            Rectangle imageRectangle = new Rectangle(0, 0, bmp.Width, bmp.Height);
            g.DrawImage(bmp, imageRectangle, imageRectangle, GraphicsUnit.Pixel);
        }
        
        pictureBoxDst.Image = img_dst;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
    finally 
    {
        img_src.UnlockBits(img_src_data);
        if (img_dst_data != IntPtr.Zero)
        {
            Marshal.Release(img_dst_data);
            Marshal.FreeHGlobal(img_dst_data);
        }
    }
}3.备注说明
1>Bitmap data stride: height * channel 并补齐至最接近的 4 的整倍数,为什么是4的整数,请参考下面内容。
注意Bitmap data 和 Mat.data 是有区别的,前者包含像素数据和填充,而后者只包含像素数据。

2>为快速理解内容,本文只展示核心代码,并聚拢了代码,部分代码专为本文教学而改,可能包含错误,请读者辨识性阅览。
4.结果演示:











