当我从 System.Drawing.Bitmap 对象转换为 cv::Mat 时,位图被破坏

Bitmap gets mangled when I converted from System.Drawing.Bitmap object to cv::Mat

我有一个 WPF 应用程序,它使用我从堆栈溢出中复制的名为 ScreenCapture 的 class 截取 运行 Handbrake 可执行文件的屏幕截图。

public class ScreenCapture
{
    [DllImport("user32.dll")]
    static extern int GetWindowRgn(IntPtr hWnd, IntPtr hRgn);

    //Region Flags - The return value specifies the type of the region that the function obtains. It can be one of the following values.
    const int ERROR = 0;
    const int NULLREGION = 1;
    const int SIMPLEREGION = 2;
    const int COMPLEXREGION = 3;

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool GetWindowRect(HandleRef hWnd, out RECT lpRect);

    [DllImport("gdi32.dll")]
    static extern IntPtr CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);

    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags);

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int Left, Top, Right, Bottom;

        public RECT(int left, int top, int right, int bottom)
        {
            Left = left;
            Top = top;
            Right = right;
            Bottom = bottom;
        }

        public RECT(System.Drawing.Rectangle r) : this(r.Left, r.Top, r.Right, r.Bottom) { }

        public int X
        {
            get { return Left; }
            set { Right -= (Left - value); Left = value; }
        }

        public int Y
        {
            get { return Top; }
            set { Bottom -= (Top - value); Top = value; }
        }

        public int Height
        {
            get { return Bottom - Top; }
            set { Bottom = value + Top; }
        }

        public int Width
        {
            get { return Right - Left; }
            set { Right = value + Left; }
        }

        public System.Drawing.Point Location
        {
            get { return new System.Drawing.Point(Left, Top); }
            set { X = value.X; Y = value.Y; }
        }

        public System.Drawing.Size Size
        {
            get { return new System.Drawing.Size(Width, Height); }
            set { Width = value.Width; Height = value.Height; }
        }

        public static implicit operator System.Drawing.Rectangle(RECT r)
        {
            return new System.Drawing.Rectangle(r.Left, r.Top, r.Width, r.Height);
        }

        public static implicit operator RECT(System.Drawing.Rectangle r)
        {
            return new RECT(r);
        }

        public static bool operator ==(RECT r1, RECT r2)
        {
            return r1.Equals(r2);
        }

        public static bool operator !=(RECT r1, RECT r2)
        {
            return !r1.Equals(r2);
        }

        public bool Equals(RECT r)
        {
            return r.Left == Left && r.Top == Top && r.Right == Right && r.Bottom == Bottom;
        }

        public override bool Equals(object obj)
        {
            if (obj is RECT)
                return Equals((RECT)obj);
            else if (obj is System.Drawing.Rectangle)
                return Equals(new RECT((System.Drawing.Rectangle)obj));
            return false;
        }

        public override int GetHashCode()
        {
            return ((System.Drawing.Rectangle)this).GetHashCode();
        }

        public override string ToString()
        {
            return string.Format(System.Globalization.CultureInfo.CurrentCulture, "{{Left={0},Top={1},Right={2},Bottom={3}}}", Left, Top, Right, Bottom);
        }
    }
    public Bitmap GetScreenshot(IntPtr ihandle)
    {
        IntPtr hwnd = ihandle;//handle here

        RECT rc;
        GetWindowRect(new HandleRef(null, hwnd), out rc);

        Bitmap bmp = new Bitmap(rc.Right - rc.Left, rc.Bottom - rc.Top, PixelFormat.Format32bppArgb);
        Graphics gfxBmp = Graphics.FromImage(bmp);
        IntPtr hdcBitmap;
        try
        {
            hdcBitmap = gfxBmp.GetHdc();
        }
        catch
        {
            return null;
        }
        bool succeeded = PrintWindow(hwnd, hdcBitmap, 0);
        gfxBmp.ReleaseHdc(hdcBitmap);
        if (!succeeded)
        {
            gfxBmp.FillRectangle(new SolidBrush(Color.Gray), new Rectangle(Point.Empty, bmp.Size));
        }
        IntPtr hRgn = CreateRectRgn(0, 0, 0, 0);
        GetWindowRgn(hwnd, hRgn);
        Region region = Region.FromHrgn(hRgn);//err here once
        if (!region.IsEmpty(gfxBmp))
        {
            gfxBmp.ExcludeClip(region);
            gfxBmp.Clear(Color.Transparent);
        }
        gfxBmp.Dispose();
        return bmp;
    }

    public void WriteBitmapToFile(string filename, Bitmap bitmap)
    {
        bitmap.Save(filename, ImageFormat.Bmp);
    }

因此,当下面的按钮单击处理程序被调用时,会截取手刹 window 的屏幕截图。 我将其写入硬盘以确保其正常: handbrake screen shot。 我创建了一个 CLR class 库 ClassLibrary1::Class1 的实例,并调用方法 "DoSomething" 将其传递给 System.Drawing.Bitmap 对象。

  private void button4_Click(object sender, RoutedEventArgs e)
  {
     string wName = "HandBrake";
     IntPtr hWnd = IntPtr.Zero;
     foreach (Process pList in Process.GetProcesses())
     {
        if (pList.MainWindowTitle.Contains(wName))
        {
           hWnd = pList.MainWindowHandle;

           var sc = new ScreenCapture();

           SetForegroundWindow(hWnd);
           var bitmap = sc.GetScreenshot(hWnd);

           sc.WriteBitmapToFile("handbrake.bmp", bitmap);

           Bitmap image1 = (Bitmap)System.Drawing.Image.FromFile("handbrake.bmp", true);

           ClassLibrary1.Class1 opencv = new ClassLibrary1.Class1();

           opencv.DoSomething(image1);
        }
     }
  }

在 DoSomething 内部,我尝试将 System.Drawing.Bitmap 转换为 OpenCV class cv::Mat。我调用 cv::imwrite 以确保位图仍然正常,不幸的是出了点问题:mangled handbrake screenshot

void Class1::DoSomething(Bitmap ^mybitmap)
{
cv::Mat *imgOriginal;
// Lock the bitmap's bits.  
Rectangle rect = Rectangle(0, 0, mybitmap->Width, mybitmap->Height);
Imaging::BitmapData^ bmpData = mybitmap->LockBits(rect, Imaging::ImageLockMode::ReadWrite, mybitmap->PixelFormat);
try
{
  // Get the address of the first line.
  IntPtr ptr = bmpData->Scan0;

  // Declare an array to hold the bytes of the bitmap.
  // This code is specific to a bitmap with 24 bits per pixels.
  int bytes = Math::Abs(bmpData->Stride) * mybitmap->Height;
  array<Byte>^rgbValues = gcnew array<Byte>(bytes);

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

  imgOriginal = new cv::Mat(mybitmap->Height, mybitmap->Width, CV_8UC3, (void *)ptr, std::abs(bmpData->Stride));
  }
  finally { mybitmap->UnlockBits(bmpData); }//Remember to unlock!!!

  cv::imwrite("from_mat.bmp", *imgOriginal);
}

有人能发现我的错误吗?

由于您的图像被水平拉伸,我敢打赌您的像素格式有误 selected。 (它没有垂直拉伸,也没有沿对角线倾斜,所以步幅是正确的。)CV_8UC3 指定每像素 24 位,但我认为你的 BMP 文件使用每像素 32 位。

将您的像素格式切换为 CV_8UC4,或者更好的是,从图像中读取 bits per pixel 的数量,并 select 基于此的正确 CV 格式。


旁注:由于您先是 sc.WriteBitmapToFile(),然后是 opencv.DoSomething(Image.FromFile(),因此与您如何截取屏幕截图无关。您正在从文件中读取位图;这才是最重要的。