如何在不使用剪贴板的情况下复制图像?

问题:我有以下代码从网络摄像头捕获图像。

我的问题是这部分:

SendMessage(hCaptureWnd, WM_CAP_COPY, 0, 0); // copy it to the clipboard 

它的作用是将图像从窗口复制到剪贴板,然后从中创建一个字节数组。

它可以工作 – 只要你在程序运行时不使用剪贴板。
问题是,这对我自己来说甚至都不起作用,因为我有时会在Visual Studio需要很长时间来复制某些内容来开始调试Web应用程序,然后崩溃。

所以我的问题在这里:
如何在不使用剪贴板的情况下获取图像? 或者更具体地说,如何将hCaptureWnd转换为System.Drawing.Image?


– 编辑:
我错过了说“没有创建文件,我想要一个字节数组”。
它是一个Web应用程序,因此运行应用程序的用户不应具有对文件系统的写访问权限(仅写入文件进行临时测试)…
– 结束编辑:


 ///  /// Captures a frame from the webcam and returns the byte array associated /// with the captured image ///  /// number of milliseconds to wait between connect /// and capture - necessary for some cameras that take a while to 'warm up' /// byte array representing a bitmp or null (if error or no webcam) private static byte[] InternalCaptureToByteArray(int connectDelay = 500) { Clipboard.Clear(); // clear the clipboard int hCaptureWnd = capCreateCaptureWindowA("ccWebCam", 0, 0, 0, // create the hidden capture window 350, 350, 0, 0); SendMessage(hCaptureWnd, WM_CAP_CONNECT, 0, 0); // send the connect message to it Thread.Sleep(connectDelay); // sleep the specified time SendMessage(hCaptureWnd, WM_CAP_GET_FRAME, 0, 0); // capture the frame SendMessage(hCaptureWnd, WM_CAP_COPY, 0, 0); // copy it to the clipboard SendMessage(hCaptureWnd, WM_CAP_DISCONNECT, 0, 0); // disconnect from the camera Bitmap bitmap = (Bitmap)Clipboard.GetDataObject().GetData(DataFormats.Bitmap); // copy into bitmap if (bitmap == null) return null; using (MemoryStream stream = new MemoryStream()) { bitmap.Save(stream, ImageFormat.Bmp); // get bitmap bytes return stream.ToArray(); } // End Using stream } // End Function InternalCaptureToByteArray 

注意( http://msdn.microsoft.com/en-us/library/windows/desktop/dd756879(v=vs.85).aspx ):

 HWND VFWAPI capCreateCaptureWindow( LPCTSTR lpszWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWnd, int nID ); #define VFWAPI WINAPI typedef HANDLE HWND; typedef PVOID HANDLE; typedef void *PVOID; 

完整代码供参考

 using System; using System.IO; using System.Drawing; using System.Threading; using System.Windows.Forms; using System.Drawing.Imaging; using System.Collections.Generic; using System.Runtime.InteropServices; // http://www.creativecodedesign.com/node/66 // http://www.barebonescoder.com/2012/01/finding-your-web-cam-with-c-directshow-net/ // http://www.codeproject.com/Articles/15219/WebCam-Fast-Image-Capture-Service-using-WIA // http://www.c-sharpcorner.com/uploadfile/yougerthen/integrate-the-web-webcam-functionality-using-C-Sharp-net-and-com-part-viii/ // http://sofzh.miximages.com/c%23/ bool isCaptured = ccWebCam.CaptureSTA(capture.jpg"); // Access to path C:\Program Files (x86)\Common Files\Microsoft Shared\DevServer\10.0\capture.jpg" denied. // byte[] captureBytes = ccWebCam.CaptureSTA(); ///  /// Timur Kovalev (http://www.creativecodedesign.com): /// This class provides a method of capturing a webcam image via avicap32.dll api. ///  public static class ccWebCam { #region *** PInvoke Stuff - methods to interact with capture window *** [DllImport("user32", EntryPoint = "SendMessage")] private static extern int SendMessage(int hWnd, uint Msg, int wParam, int lParam); [DllImport("avicap32.dll", EntryPoint = "capCreateCaptureWindowA")] private static extern int capCreateCaptureWindowA(string lpszWindowName, int dwStyle, int X, int Y, int nWidth, int nHeight, int hwndParent, int nID); private const int WM_CAP_CONNECT = 1034; private const int WM_CAP_DISCONNECT = 1035; private const int WM_CAP_COPY = 1054; private const int WM_CAP_GET_FRAME = 1084; #endregion private static object objWebCamThreadLock = new object(); //CaptureToFile(@"D:\Stefan.Steiger\Documents\Visual Studio 2010\Projects\Post_Ipag\image3.jpg"): public static bool Capture(string filePath, int connectDelay = 500) { lock (objWebCamThreadLock) { return cc.Utility.ccWebCam.InternalCaptureAsFileInThread(filePath, connectDelay); } } // End Treadsafe Function Capture public static byte[] Capture(int connectDelay = 500) { lock (objWebCamThreadLock) { return InternalCaptureToByteArrayInThread(connectDelay); } } // End Treadsafe Function Capture ///  /// Captures a frame from the webcam and returns the byte array associated /// with the captured image. The image is also stored in a file ///  /// path the file wher ethe image will be saved /// number of milliseconds to wait between connect /// and capture - necessary for some cameras that take a while to 'warm up' /// true on success, false on failure private static bool InternalCaptureAsFileInThread(string filePath, int connectDelay = 500) { bool success = false; Thread catureThread = new Thread(() => { success = InternalCaptureAsFile(filePath, connectDelay); }); catureThread.SetApartmentState(ApartmentState.STA); catureThread.Start(); catureThread.Join(); return success; } // End Function InternalCaptureAsFileInThread ///  /// Captures a frame from the webcam and returns the byte array associated /// with the captured image. The image is also stored in a file ///  /// path the file wher ethe image will be saved /// number of milliseconds to wait between connect /// and capture - necessary for some cameras that take a while to 'warm up' /// true on success, false on failure private static bool InternalCaptureAsFile(string filePath, int connectDelay = 500) { byte[] capture = ccWebCam.InternalCaptureToByteArray(connectDelay); if (capture != null) { // Access to path C:\Program Files (x86)\Common Files\Microsoft Shared\DevServer\10.0\image1.jpg" denied. File.WriteAllBytes(filePath, capture); return true; } return false; } // End Function InternalCaptureAsFile ///  /// Captures a frame from the webcam and returns the byte array associated /// with the captured image. Runs in a newly-created STA thread which is /// required for this method of capture ///  /// number of milliseconds to wait between connect /// and capture - necessary for some cameras that take a while to 'warm up' /// byte array representing a bitmp or null (if error or no webcam) private static byte[] InternalCaptureToByteArrayInThread(int connectDelay = 500) { byte[] bytes = null; Thread catureThread = new Thread(() => { bytes = InternalCaptureToByteArray(connectDelay); }); catureThread.SetApartmentState(ApartmentState.STA); catureThread.Start(); catureThread.Join(); return bytes; } // End Function InternalCaptureToByteArrayInThread ///  /// Captures a frame from the webcam and returns the byte array associated /// with the captured image ///  /// number of milliseconds to wait between connect /// and capture - necessary for some cameras that take a while to 'warm up' /// byte array representing a bitmp or null (if error or no webcam) private static byte[] InternalCaptureToByteArray(int connectDelay = 500) { Clipboard.Clear(); // clear the clipboard int hCaptureWnd = capCreateCaptureWindowA("ccWebCam", 0, 0, 0, // create the hidden capture window 350, 350, 0, 0); SendMessage(hCaptureWnd, WM_CAP_CONNECT, 0, 0); // send the connect message to it Thread.Sleep(connectDelay); // sleep the specified time SendMessage(hCaptureWnd, WM_CAP_GET_FRAME, 0, 0); // capture the frame SendMessage(hCaptureWnd, WM_CAP_COPY, 0, 0); // copy it to the clipboard SendMessage(hCaptureWnd, WM_CAP_DISCONNECT, 0, 0); // disconnect from the camera Bitmap bitmap = (Bitmap)Clipboard.GetDataObject().GetData(DataFormats.Bitmap); // copy into bitmap if (bitmap == null) return null; using (MemoryStream stream = new MemoryStream()) { bitmap.Save(stream, ImageFormat.Bmp); // get bitmap bytes return stream.ToArray(); } // End Using stream } // End Function InternalCaptureToByteArray } } 

我试过这样,但它只得到一个黑色的图像……

  [DllImport("user32.dll")] static extern IntPtr GetWindowDC(IntPtr hWnd); [DllImport("gdi32.dll", SetLastError = true)] static extern IntPtr CreateCompatibleDC(IntPtr hdc); enum TernaryRasterOperations : uint { /// dest = source SRCCOPY = 0x00CC0020, /// dest = source OR dest SRCPAINT = 0x00EE0086, /// dest = source AND dest SRCAND = 0x008800C6, /// dest = source XOR dest SRCINVERT = 0x00660046, /// dest = source AND (NOT dest) SRCERASE = 0x00440328, /// dest = (NOT source) NOTSRCCOPY = 0x00330008, /// dest = (NOT src) AND (NOT dest) NOTSRCERASE = 0x001100A6, /// dest = (source AND pattern) MERGECOPY = 0x00C000CA, /// dest = (NOT source) OR dest MERGEPAINT = 0x00BB0226, /// dest = pattern PATCOPY = 0x00F00021, /// dest = DPSnoo PATPAINT = 0x00FB0A09, /// dest = pattern XOR dest PATINVERT = 0x005A0049, /// dest = (NOT dest) DSTINVERT = 0x00550009, /// dest = BLACK BLACKNESS = 0x00000042, /// dest = WHITE WHITENESS = 0x00FF0062, ///  /// Capture window as seen on screen. This includes layered windows /// such as WPF windows with AllowsTransparency="true" ///  CAPTUREBLT = 0x40000000 } [DllImport("gdi32.dll")] [return: MarshalAs(UnmanagedType.Bool)] static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, TernaryRasterOperations dwRop); [DllImport("gdi32.dll")] static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight); [DllImport("gdi32.dll", ExactSpelling = true, PreserveSig = true, SetLastError = true)] static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj); [DllImport("gdi32.dll")] static extern bool DeleteDC(IntPtr hdc); [DllImport("user32.dll")] static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC); [DllImport("gdi32.dll")] static extern bool DeleteObject(IntPtr hObject); public static void ScreenshotWindow(IntPtr windowHandle) { Rect Rect = new Rect(); GetWindowRect(windowHandle, ref Rect); int width = Rect.Right - Rect.Left; int height = Rect.Bottom - Rect.Top; IntPtr windowDeviceContext = GetWindowDC(windowHandle); IntPtr destDeviceContext = CreateCompatibleDC(windowDeviceContext); IntPtr bitmapHandle = CreateCompatibleBitmap(windowDeviceContext, width, height); IntPtr oldObject = SelectObject(destDeviceContext, bitmapHandle); BitBlt(destDeviceContext, 0, 0, width, height, windowDeviceContext, 0, 0, TernaryRasterOperations.CAPTUREBLT | TernaryRasterOperations.SRCCOPY); SelectObject(destDeviceContext, oldObject); DeleteDC(destDeviceContext); ReleaseDC(windowHandle, destDeviceContext); Image screenshot = Image.FromHbitmap(bitmapHandle); DeleteObject(bitmapHandle); screenshot.Save("d:\\temp\\mywebcamimage.png", System.Drawing.Imaging.ImageFormat.Png); /* // TODO - Remove above save when it works using (MemoryStream stream = new MemoryStream()) { screenshot.Save(stream, System.Drawing.Imaging.ImageFormat.Png); return stream.ToArray(); } */ } 

然后在SendMessage(hCaptureWnd, WM_CAP_GET_FRAME, 0, 0);之后SendMessage(hCaptureWnd, WM_CAP_GET_FRAME, 0, 0);

 ScreenshotWindow(new IntPtr(hCaptureWnd)); 

没有WM_CAP_GET_FRAME这样的东西。 消息的正确名称是WM_CAP_GRAB_FRAME ,它在MSDN上描述。

它的作用是:

WM_CAP_GRAB_FRAME消息从捕获驱动程序检索并显示单个帧。 捕获后,将禁用叠加和预览。 您可以显式发送此消息,也可以使用capGrabFrame宏发送此消息。

要获取实际数据,您需要使用帧回调,如MSDN上所述 。 回调可以获取图片字节,您可以将其写入文件或用于任何处理,而无需通过剪贴板传输。

…是与流捕获一起使用的回调函数,用于可选地处理捕获的video帧。 名称capVideoStreamCallback是应用程序提供的函数名称的占位符。

[你有一个] …指向VIDEOHDR结构的指针,其中包含有关捕获帧的信息。

同样,这个API是video捕获的不吉利的选择。 太旧了,太有限了。

您必须发送不同的消息,特别是WM_CAP_FILE_SAVEDIB ,以将数据保存在磁盘上的文件中。 然后,您将能够在Bitmap对象中加载它以进行进一步处理(我不知道任何内置的cam-to-byte []function)。

 [DllImport("user32", EntryPoint = "SendMessage")] private static extern int SendMessage( int hWnd, uint Msg, int wParam, string strFileName); private const int WM_USER = 0x0400; private const int WM_CAP_START = WM_USER; private const int WM_CAP_FILE_SAVEDIB = WM_CAP_START + 25; 

 //before SendMessage(hCaptureWnd, WM_CAP_COPY, 0, 0); //after string tempFile = Server.MapPath("~/App_Data/tempCap.bmp"); SendMessage(hCaptureWnd, WM_CAP_FILE_SAVEDIB, 0, tempFile); //create tempfile Bitmap bitmap = new Bitmap(tempFile); //read tempfile using (MemoryStream stream = new MemoryStream()) { bitmap.Save(stream, ImageFormat.Bmp); return stream.ToArray(); } 

以罗曼R.的答案为基础:

更精细的道德观点是你需要注册回调框架,然后调用grabframe,并且你不能直接将C风格的char []转换为byte [],并且你得到原始的位图数据 – 而不是位图,以及无论capCreateCaptureWindowA中设置什么,图像大小都是640×480,并且lpData需要是IntPtr而不是UIntPtr,因为Marshal.Copy没有UIntPtr的重载,并且使用WriteBitmapFile,可以编写原始位图数据没有使用不安全代码或映射位图文件头的位图,并且编写Marshal.Copy的人可以复制负值,因为长度是int,而不是uint …

此外,无论出于何种原因,都需要旋转图像180 oldDegrees …
另外,我将WM常量更改为正确的名称。

  SendMessage(hCaptureWnd, WM_CAP_SET_CALLBACK_FRAME, 0, capVideoStreamCallback); SendMessage(hCaptureWnd, WM_CAP_GRAB_FRAME, 0, 0); // capture the frame 

有了这些额外的东西

  // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757688(v=vs.85).aspx [StructLayout(LayoutKind.Sequential)] private struct VIDEOHDR { // http://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx // typedef unsigned char BYTE; // typedef BYTE far *LPBYTE; // unsigned char* lpData //public byte[] lpData; // LPBYTE lpData; // Aaargh, invalid cast, not a .NET byte array... public IntPtr lpData; // LPBYTE lpData; public UInt32 dwBufferLength; // DWORD dwBufferLength; public UInt32 dwBytesUsed; // DWORD dwBytesUsed; public UInt32 dwTimeCaptured; // DWORD dwTimeCaptured; // typedef ULONG_PTR DWORD_PTR; // #if defined(_WIN64) // typedef unsigned __int64 ULONG_PTR; // #else // typedef unsigned long ULONG_PTR; // #endif public IntPtr dwUser; // DWORD_PTR dwUser; public UInt32 dwFlags; // DWORD dwFlags; [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 4)] public System.UIntPtr[] dwReserved; // DWORD_PTR dwReserved[4]; // Does not make a difference //public System.UIntPtr[] dwReserved = new System.UIntPtr[4]; // DWORD_PTR dwReserved[4]; } private delegate System.IntPtr capVideoStreamCallback_t(System.UIntPtr hWnd, ref VIDEOHDR lpVHdr); [DllImport("user32", EntryPoint = "SendMessage")] private static extern int SendMessage(int hWnd, uint Msg, int wParam, capVideoStreamCallback_t routine); // http://eris.liralab.it/yarpdoc/vfw__extra__from__wine_8h.html private const int WM_USER = 0x0400; // 1024 private const int WM_CAP_START = WM_USER; private const int WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10; private const int WM_CAP_DRIVER_DISCONNECT = WM_CAP_START + 11; private const int WM_CAP_FILE_SAVEDIB = WM_CAP_START + 25; private const int WM_CAP_SET_CALLBACK_FRAME = WM_CAP_START + 5; private const int WM_CAP_GRAB_FRAME = WM_CAP_START + 60; private const int WM_CAP_EDIT_COPY = WM_CAP_START + 30; // http://lists.ximian.com/pipermail/mono-devel-list/2011-March/037272.html private static byte[] baSplendidIsolation; private static System.IntPtr capVideoStreamCallback(System.UIntPtr hWnd, ref VIDEOHDR lpVHdr) { //System.Windows.Forms.MessageBox.Show("hello"); //System.Windows.Forms.MessageBox.Show(lpVHdr.dwBufferLength.ToString() + " " + lpVHdr.dwBytesUsed.ToString()); byte[] _imageTemp = new byte[lpVHdr.dwBufferLength]; Marshal.Copy(lpVHdr.lpData, _imageTemp, 0, (int) lpVHdr.dwBufferLength); //System.IO.File.WriteAllBytes(@"d:\temp\mycbfile.bmp", _imageTemp); // AAaaarg, it's raw bitmap data... // http://stackoverflow.com/questions/742236/how-to-create-a-bmp-file-from-byte-in-c-sharp // http://stackoverflow.com/questions/2654480/writing-bmp-image-in-pure-cc-without-other-libraries // Tsssss... 350 x 350 was the expected setting, but never mind... // fortunately alex told me about WM_CAP_FILE_SAVEDIB, so I could compare to the direct output int width = 640; int height = 480; int stride = width*3; baSplendidIsolation = null; baSplendidIsolation = WriteBitmapFile(@"d:\temp\mycbfilecc.bmp", width, height, _imageTemp); /* unsafe { fixed (byte* ptr = _imageTemp) { using (Bitmap image = new Bitmap(width, height, stride, PixelFormat.Format24bppRgb, new IntPtr(ptr))) { image.Save(@"d:\temp\mycbfile2.bmp"); } } } */ //var hdr = (Elf32_Phdr)Marshal.PtrToStructure(ptr, typeof(Elf32_Phdr)); return System.IntPtr.Zero; } private static byte[] WriteBitmapFile(string filename, int width, int height, byte[] imageData) { using (var stream = new MemoryStream(imageData)) using (var bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb)) { BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0,bmp.Width, bmp.Height) ,ImageLockMode.WriteOnly ,bmp.PixelFormat ); Marshal.Copy(imageData, 0, bmpData.Scan0, imageData.Length); bmp.UnlockBits(bmpData); if (bmp == null) return null; bmp.RotateFlip(RotateFlipType.Rotate180FlipNone); bmp.Save(filename); // For testing only using (MemoryStream ms = new MemoryStream()) { bmp.Save(ms, ImageFormat.Png); // get bitmap bytes return ms.ToArray(); } // End Using stream } } // End Function WriteBitmapFile ///  /// Captures a frame from the webcam and returns the byte array associated /// with the captured image ///  /// number of milliseconds to wait between connect /// and capture - necessary for some cameras that take a while to 'warm up' /// byte array representing a bitmp or null (if error or no webcam) private static byte[] InternalCaptureToByteArray(int connectDelay = 500) { Clipboard.Clear(); int hCaptureWnd = capCreateCaptureWindowA("ccWebCam", 0, 0, 0, 350, 350, 0, 0); // create the hidden capture window SendMessage(hCaptureWnd, WM_CAP_DRIVER_CONNECT, 0, 0); // send the connect message to it //SendMessage(hCaptureWnd, WM_CAP_DRIVER_CONNECT, i, 0); // i device number retval != 0 --> valid device_id Thread.Sleep(connectDelay); // sleep the specified time SendMessage(hCaptureWnd, WM_CAP_SET_CALLBACK_FRAME, 0, capVideoStreamCallback); SendMessage(hCaptureWnd, WM_CAP_GRAB_FRAME, 0, 0); // capture the frame //SendMessage(hCaptureWnd, WM_CAP_FILE_SAVEDIB, 0, "d:\\temp\\testmywebcamimage.bmp"); //ScreenshotWindow(new IntPtr(hCaptureWnd)); //SendMessage(hCaptureWnd, WM_CAP_EDIT_COPY, 0, 0); // copy it to the clipboard // using (Graphics g2 = Graphics.FromHwnd(new IntPtr(hCaptureWnd))) SendMessage(hCaptureWnd, WM_CAP_DRIVER_DISCONNECT, 0, 0); // disconnect from the camera return baSplendidIsolation; /* Bitmap bitmap = (Bitmap)Clipboard.GetDataObject().GetData(DataFormats.Bitmap); // copy into bitmap if (bitmap == null) return null; using (MemoryStream stream = new MemoryStream()) { bitmap.Save(stream, ImageFormat.Bmp); // get bitmap bytes return stream.ToArray(); } // End Using stream */ } // End Function InternalCaptureToByteArray