6

Question: I have the below code to capture an image from a webcam.

My problem is this part:

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

What it does is copy the image from the window to the clipboard, and then create a byte array out of it.

It works - as long as you don't use the clipboard while the program is running.
The problem is, this doesn't even work for myself, as I sometimes copy something while Visual Studio takes ages to start debug the web application, and then it crashes.

So here my question:
How can I get the image without using the clipboard ? Or more specifically, how to transform hCaptureWnd to System.Drawing.Image ?


-- Edit:
I missed to say "without creating a file, i want a byte array".
It's a web application, so the user the application runs under shouldn't have write access to the file system (writing to a file only for temporary testing) ...
-- End Edit:


/// <summary>
/// Captures a frame from the webcam and returns the byte array associated
/// with the captured image
/// </summary>
/// <param name="connectDelay">number of milliseconds to wait between connect 
/// and capture - necessary for some cameras that take a while to 'warm up'</param>
/// <returns>byte array representing a bitmp or null (if error or no webcam)</returns>
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

Note (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;

Full code for reference

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://forums.asp.net/t/1410057.aspx


namespace cc.Utility
{


    // 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();

    /// <summary>
    /// Timur Kovalev (http://www.creativecodedesign.com):
    /// This class provides a method of capturing a webcam image via avicap32.dll api.
    /// </summary>    
    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


        /// <summary>
        /// Captures a frame from the webcam and returns the byte array associated
        /// with the captured image. The image is also stored in a file
        /// </summary>
        /// <param name="filePath">path the file wher ethe image will be saved</param>
        /// <param name="connectDelay">number of milliseconds to wait between connect 
        /// and capture - necessary for some cameras that take a while to 'warm up'</param>
        /// <returns>true on success, false on failure</returns>
        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


        /// <summary>
        /// Captures a frame from the webcam and returns the byte array associated
        /// with the captured image. The image is also stored in a file
        /// </summary>
        /// <param name="filePath">path the file wher ethe image will be saved</param>
        /// <param name="connectDelay">number of milliseconds to wait between connect 
        /// and capture - necessary for some cameras that take a while to 'warm up'</param>
        /// <returns>true on success, false on failure</returns>
        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


        /// <summary>
        /// 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
        /// </summary>
        /// <param name="connectDelay">number of milliseconds to wait between connect 
        /// and capture - necessary for some cameras that take a while to 'warm up'</param>
        /// <returns>byte array representing a bitmp or null (if error or no webcam)</returns>
        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


        /// <summary>
        /// Captures a frame from the webcam and returns the byte array associated
        /// with the captured image
        /// </summary>
        /// <param name="connectDelay">number of milliseconds to wait between connect 
        /// and capture - necessary for some cameras that take a while to 'warm up'</param>
        /// <returns>byte array representing a bitmp or null (if error or no webcam)</returns>
        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


    }


}

I tried like this, but it only gets a black image...

    [DllImport("user32.dll")]
    static extern IntPtr GetWindowDC(IntPtr hWnd);

    [DllImport("gdi32.dll", SetLastError = true)]
    static extern IntPtr CreateCompatibleDC(IntPtr hdc);

    enum TernaryRasterOperations : uint
    {
        /// <summary>dest = source</summary>
        SRCCOPY = 0x00CC0020,
        /// <summary>dest = source OR dest</summary>
        SRCPAINT = 0x00EE0086,
        /// <summary>dest = source AND dest</summary>
        SRCAND = 0x008800C6,
        /// <summary>dest = source XOR dest</summary>
        SRCINVERT = 0x00660046,
        /// <summary>dest = source AND (NOT dest)</summary>
        SRCERASE = 0x00440328,
        /// <summary>dest = (NOT source)</summary>
        NOTSRCCOPY = 0x00330008,
        /// <summary>dest = (NOT src) AND (NOT dest)</summary>
        NOTSRCERASE = 0x001100A6,
        /// <summary>dest = (source AND pattern)</summary>
        MERGECOPY = 0x00C000CA,
        /// <summary>dest = (NOT source) OR dest</summary>
        MERGEPAINT = 0x00BB0226,
        /// <summary>dest = pattern</summary>
        PATCOPY = 0x00F00021,
        /// <summary>dest = DPSnoo</summary>
        PATPAINT = 0x00FB0A09,
        /// <summary>dest = pattern XOR dest</summary>
        PATINVERT = 0x005A0049,
        /// <summary>dest = (NOT dest)</summary>
        DSTINVERT = 0x00550009,
        /// <summary>dest = BLACK</summary>
        BLACKNESS = 0x00000042,
        /// <summary>dest = WHITE</summary>
        WHITENESS = 0x00FF0062,
        /// <summary>
        /// Capture window as seen on screen.  This includes layered windows 
        /// such as WPF windows with AllowsTransparency="true"
        /// </summary>
        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();
        }
        */
    }

And then this after SendMessage(hCaptureWnd, WM_CAP_GET_FRAME, 0, 0);

ScreenshotWindow(new IntPtr(hCaptureWnd));
4

3 回答 3

6

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

它的作用是:

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

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

...是与流式捕获一起使用的回调函数,可选择性地处理捕获的视频帧。名称 capVideoStreamCallback 是应用程序提供的函数名称的占位符。

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

同样,此 API 是视频捕获的不幸选择。太老了,太局限了。

于 2013-04-24T10:06:46.433 回答
3

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

[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();
}
于 2013-04-24T06:43:50.920 回答
1

基于 Roman R. 的回答:

道德的更好点是您需要注册回调帧,然后调用grabframe,并且您不能直接将C样式的char []转换为byte [],并且您获得原始位图数据-而不是位图,并且图像大小为 640x480,与 capCreateCaptureWindowA 中设置的内容无关,并且 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-c-c-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


    /// <summary>
    /// Captures a frame from the webcam and returns the byte array associated
    /// with the captured image
    /// </summary>
    /// <param name="connectDelay">number of milliseconds to wait between connect 
    /// and capture - necessary for some cameras that take a while to 'warm up'</param>
    /// <returns>byte array representing a bitmp or null (if error or no webcam)</returns>
    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
于 2013-04-25T07:10:21.073 回答