资源描述
VFW开发总结
使用VFW写的C#控制摄像头最大的问题就在于需要自己手动另起一个线程。(这里,我们定义一个叫AviCapture.cs的类,用于引入avicap32.dll以及相关的内容)在avicap32.dll中,CAPTUREPARMS结构里有一个fYield的东东,代表的意思是另起线程标志位,如果为真,则程序重新启动一个线程用于视频流的捕获,默认值是假。但是如果你是为了真,你必须要在程序中处理一些潜在的操作,因为当视频捕获时,其他操作并没有被屏蔽。。在AviCapture这个类的基础上定义一个叫Video的类(自己定义的),实现控制设想头的一些方法,如打开摄像头,关闭摄像头,开始录像,结束录像,拍照片等等。。。。在Video类中还要定义两个C#控制摄像头函数如下:
使用如下代码加入视频捕获,
function capCreateCaptureWindow(
lpszWindowName : LPCSTR;
dwStyle : DWORD;
x, y : int;
nWidth, nHeight : int;
hwndParent : HWND;
nID : int
): HWND;
lpszWindowName: 是捕获窗口的名称,可以是任意字符串,delphi中是pchar类型
dwStyle: 是捕获窗口的类型,一般设为WS_CHILD | WS_VISIBLE.
x, y, nWidth, nHeight: 是显示捕获视频的rect
hwndParent: 是显示视频窗口的句柄
nID : 默认为0;
函数返回值Hwnd类型,即摄像设备的句柄,以后的操作基本上都是针对设备句柄进行的.
然后用function capDriverConnect(hwnd: HWND; i: INT): BOOL;连接设备的驱动,
hwnd为设备句柄,i是驱动的序号(如果系统装有多个设备驱动,要选择一个正确的驱动才行,这些稍后在介绍).
驱动连接成功后,用function capPreviewRate(hwnd: HWND; wMS: WORD): BOOL;函数设置预览时的帧速,wMS为帧速值(单位:帧/秒),一般取值范围(10-30).
设置完速率后
,用function
capPreview(hwnd: HWND; f: BOOL): BOOL;函数进行预览了,设置f为true后就可以看到摄像头的画面了.
以上简单的显示了视频画面,但是在显示视频画面的过程中还要进行一些参数的设置.今天就Ok了.
窗口句柄 (HWND)
m_hCapWnd=capCreateCaptureWindow((LPTSTR)TEXT("视频捕捉测试程序"),WS_CHILD|WS_VISIBLE|WS_EX_CLIENTEDGE|WS_EX_DLGMODALFRAME,0,0,rect.Width(),rect.Width(),pWnd->GetSafeHwnd(),0); // 设置预示窗口
ASSERT(m_hCapWnd);
if(capDriverConnect(m_hCapWnd,0)){// 连接第0 号驱动器
m_bInit=TRUE;
// 得到驱动器的性能
capDriverGetCaps(m_hCapWnd,sizeof(CAPDRIVE RCAPS), &m_CapDrvCap);
if(m_CapDrvCap.fCaptureInitialized){
// 如果初始化成功
capGetStatus(m_hCapWnd, &m_CapStatus,sizeof(m_CapStatus)); // 得到驱动器状态
capPreviewRate(m_hCapWnd,30); // 设置预示帧频
capPreview(m_hCapWnd,TRUE); // 设置预示方式
}
else{// 初始化未成功
AfxMessageBox("视频捕捉卡初始化失败!");
AfxGetMainWnd() ->PostMessage (WM_CLOSE);
}
}
else{// 未能连接到驱动器
AfxMessageBox("与视频捕捉卡连接失败!");
AfxGetMainWnd() ->PostMessage(WM_CLOSE);
}
m_CapFileName="c://Capture.avi";// 设置捕获文件
capFileSetCaptureFile(m_hCapWnd,m_CapFileName.GetBuffer(255));
5、在对话框类中加入响应“设置格式”消息的函数OnFormat()。
capDlgVideoFormat(m_hCapWnd);// 设置格式对话框
6、在对话框类中加入响应“设置图像源”消息的函数OnSource()。
capDlgVideoSource(m_hCapWnd);// 设置图像源对话框
7、在对话框类中加入响应“设置压缩”消息的函数OnCompress()。
capDlgVideoCompression(m_hCapWnd);// 设置压缩对话框
8、在对话框类中加入响应“捕捉”消息的函数OnCapture()。
capCaptureGetSetup(m_hCapWnd, &m_Parms,sizeof(m_Parms));// 得到设置参数
if(capCaptureSetSetup(m_hCapWnd, &m_Parms,sizeof(m_Parms))==TRUE){
BOOL suc=TRUE;
suc=capCaptureSequence(m_hCapWnd); // 捕捉到文件
return suc};
else
return FALSE;
注意点:在VideoCaptureDlg.h中把afx_msg void OnCapture();改为afx_msg BOOL OnCapture();
在VideoCaptureDlg.cpp 修改为BOOL CVideoCaptureDlg::OnCapture() //捕捉
9、在对话框类中加入响应“定帧”消息的函数OnFreezed()。
capPreview(m_hCapWnd,FALSE);// 定帧
10、在对话框类中加入响应“单帧捕获”消息的函数OnImage()。
capGrabFrameNoStop(m_hCapWnd);// 截获当前图像
capEditCopy(m_hCapWnd);// 将图像拷贝到剪贴板
11、在对话框类中加入响应“停止”消息的函数OnStop()。
capCaptureStop(m_hCapWnd);// 停止捕捉
12、在对话框类中加入响应“退出”消息的函数OnExit()退出前断开捕捉器与驱动器的连接,并关闭窗口。
capDriverDisconnect(m_hCapWnd);
CDialog::OnCancel();
附加说明:以上添加的按钮用于捕捉图像(button);
另外添加(Static Text)用于创建并设置捕获窗口;
保存图像的方法:点击“单帧捕获”——>打开附近里的“画图”——>“编辑”中的“粘贴”——>即可保存捕捉的位图。
以上方法主要是采用了VFW的函数和宏,有兴趣的朋友可以尝试用消息的方法,同样也可以实现。
如:SendMessage (hWndC, WM_CAP_DRIVER_CONNECT, 0, 0L);//用于连接设备
要做的就是根据相关的功能改变相应的参数就可以了。
capCreateCaptureWindow(
lpszWindowName : LPCSTR;
dwStyle : DWORD;
x, y : int;
nWidth, nHeight : int;
hwndParent : HWND;
nID : int
): HWND;
lpszWindowName: 是捕获窗口的名称,可以是任意字符串,delphi中是pchar类型
dwStyle: 是捕获窗口的类型,一般设为WS_CHILD | WS_VISIBLE.
x, y, nWidth, nHeight: 是显示捕获视频的rect
hwndParent: 是显示视频窗口的句柄
nID : 默认为0;
函数返回值Hwnd类型,即摄像设备的句柄,以后的操作基本上都是针对设备句柄进行的.
然后用function capDriverConnect(hwnd: HWND; i: INT): BOOL;连接设备的驱动,hwnd为设备句柄,i是驱动的序号(如果系统装有多个设备驱动,要选择一个正确的驱动才行,这些稍后在介绍).
驱动连接成功后,用function capPreviewRate(hwnd: HWND; wMS: WORD): BOOL;函数设置预览时的帧速,wMS为帧速值(单位:帧/秒),一般取值范围(10-30).
设置完速率后,用function capPreview(hwnd: HWND; f: BOOL): BOOL;函数进行预览了,设置f为true后就可以看到摄像头的画面了.
以上简单的显示了视频画面,但是在显示视频画面的过程中还要进行一些参数的设置.今天就Ok了.
1、 int intDevice = 0;是什么意思?起初我猜测0表示第一个摄像头,那么1不就表示第二个摄像头吗?经过我的尝试发现并非如此,那么0到底是什么意思呢?
回答:这只是捕获窗口的窗口名称而已。你可以看到实际上他转换成了String类型,在capCreateCaptureWindow里面作为第一个参数,这个参数就是表示窗口名称。
2、hHwnd = Camera1.capCreateCaptureWindowA(ref refDevice, 1342177280, 0, 0, 640, 480, this.panel1.Handle.ToInt32(), 0);各个参数的含义,这个我从msdn中查到了,但是对于第二个参数和最后一个参数还是不理解,希望大家给以解答。
回答:第二个参数表示窗体风格,也就是CreateWindow的时候都要使用的,具体的说就是表示你窗体有没得边框啦,支持不支持拖放拉等等。这个可以去MSDN里面查 CreateWindowEx 函数便知。
最后一个参数表示窗口标识符。只要每个窗体不一样,能区分即可。
3、Camera1.SendMessage(hHwnd, 0x40a, 0, 0),第一个参数是句柄,第二个参数表示的是一个消息,是哪个消息呢?后两个参数是到底为消息提供什么样的东西呢?
回答:0x40a 表示 WM_CAP_DRIVER_CONNECT消息,后面两个参数表示消息附带的两个参数,每个消息都有两个参数的。按照VFW的规定,第一个参数是捕获设备的索引。这儿是0,表示你的第一个捕获设备。第二参数恒为0。为什么?VFW就是这么规定的。
你要连接两个摄像头,着眼点就是这儿,只需要重新创建一个铺货窗口,并连接到另外一个摄像头的驱动上,事先知道设备ID就行了。这个可以通过设备枚举获得,具体参见MSND中Enumerating Installed Capture Drivers相关章节。就OK了。
4、 Camera1.SendMessage(this.hHwnd, 0x435, -1, 0);
Camera1.SendMessage(this.hHwnd, 0x434, 0x42, 0);
Camera1.SendMessage(this.hHwnd, 0x432, -1, 0);
分别表示的意思是什么?
回答:0x435表示WM_CAP_SET_SCALE,可以打开或者关闭预览的比例缩放,后面是参数,这些都可以在MSDN中查到。我就不说了。比如这里是-1就是表示为真,也就是说图像会根据窗口大小进行缩放。
0x434表示WM_CAP_SET_PREVIEWRATE,这个是设置显示速度的。也就是帧率。
0x432表示WM_CAP_SET_PREVIEW,这个标识打开关闭预览模式。这儿是-1,自然表示为真,也就是打开了。
5、在一个程序中,能不能同时使用两个摄像头,需不需要第三方软件的支持?
回答:当然可以。不需要三方支持。这是微软的活儿,只有别人依赖他支持,怎么可能微软还需要依赖别人。
解释一下,什么是回调函数呢,它有什么用处?
回调函数,就是你自己写的函数,符合规定的参数和返回值类型,符合规定的调用约定,比如上面这个函数
就是回调函数,参数和返回值类型都是规定好的,调用约定为CALLBACK,CALLBACK
其实是一个宏
#define CALLBACK__stdcall
满足一定条件时,此函数可以被系统自动调用,在回调函数当中,你可以写自己的代码完成一定功能。
比如在这里,用capSetCallbackOnFrame(m_hVideo, FrameCallbackProc)注册后,当每得到一桢数据后,系统就
调用函数FrameCallbackProc。
但是,我的目的还没有达到,我其实想在每一桢显示之前,能处理一下这一桢的数据,那么,去哪里找这桢数据存
放的位置呢?
(8)为了完成我的目标,我把步骤(7)中的两句代码先注释掉。在对话框上加一个按钮,并在对单击做出响应的响应函数
中写下面代码:
capGrabFrame(m_hVideo);
这是一个宏,将鼠标移动到这段代码上,右键单击,选择Go To Definition of capGrabFrame,你会看到
#define capGrabFrame(hwnd)((BOOL)AVICapSM(hwnd, WM_CAP_GRAB_FRAME, (WPARAM)0, (LPARAM)0L))
而继续察看AVICapSM宏你会看到其实是在调用SendMessage函数呢,对吧,其实就是在发送消息。至于消息谁处理了,我们就不去
关心了,我们关心的是,发送消息后,系统会调用我们刚才注册的回调函数
LRESULT CALLBACK FrameCallbackProc(HWND hWnd, LPVIDEOHDR lpVHdr) ;
(9)好了,如果你单击按钮,capGrabFrame(m_hVideo)就发送消息了,然后,我们就进入回调函数了,这太好了。
看到回调函数传递的两个参数了么?我们更关心第二个参数,这个就是单击按钮我们捕捉到的一桢数据的入口啊!
LPVIDEOHDR 是结构体VIDEOHDR的指针,而在MSDN中察看结构体VIDEOHDR,我们就可以找到桢数据的存贮位置指针了。
VIDEOHDR定义如下:
typedef struct videohdr_tag {
LPBYTElpData;
DWORDdwBufferLength;
DWORDdwBytesUsed;
DWORDdwTimeCaptured;
DWORDdwUser;
DWORDdwFlags;
DWORD_PTRdwReserved[4];
} VIDEOHDR, NEAR *PVIDEOHDR, FAR * LPVIDEOHDR;
看到结构体中第一个参数了么?这个就是我们想要的桢数据的指针!后面参数,包括缓冲区长度等。
(10)终于得到了缓冲区的数据,可是,又一个问题出现了,缓冲区中的数据到底具体是啥含义啊?
这桢图像多大啊?size 是多少乘多少的啊?就是我们想要的像素信息么?
好的,我先告诉你,缓冲区中全部是像素信息,我们照着我的步骤这样做,其实是默认了一些参数,包括图像的
长度,宽度,色彩数,等等,那么,这个默认的值是多少呢?
(11)用一下capGetVideoFormat宏吧,你会得到想要的东西。
在BOOL CGraspDlg::OnInitDialog()中继续添加如下代码:
BITMAPINFO bmpInfo;
capGetVideoFormat(m_hVideo,&bmpInfo,sizeof(BITMAPINFO));
BITMAPINFO结构体内容自己看MSDN.定义如下
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader;
RGBQUADbmiColors[1];
} BITMAPINFO, *PBITMAPINFO;
而BITMAPINFOHEADER定义如下:
typedef struct tagBITMAPINFOHEADER{
DWORDbiSize;
LONGbiWidth;
LONGbiHeight;
WORDbiPlanes;
WORDbiBitCount;
DWORDbiCompression;
DWORDbiSizeImage;
LONGbiXPelsPerMeter;
LONGbiYPelsPerMeter;
DWORDbiClrUsed;
DWORDbiClrImportant;
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
加入步骤(11)的两句话,调试运行,我发现,bmpInfo.bmiHeader.biWidth为320,就是采集的图像宽度;
bmpInfo.bmiHeader.biHeight为240,就是采集的图像高度,这些都是默认值,也可以改变这些值,通过
capSetVideoFormat宏来实现。
(12)那么,我们在步骤(9)中,回调函数第二个参数对应的结构体VIDEOHDR中,图像数据缓冲区的大小
dwBufferLength是多少呢?我们可以在回调函数中加一个MessageBox函数,输出这个值,
我们就可以发现,为230400,这个数很好,正好等于图像宽度X图像高度的3倍,
也就是说,是图像像素数目的3倍,这就对了,每个像素用3个字节存储的嘛。好啦,我们知道了,桢缓冲区中,存储
的完全是图像的像素信息,那么,具体哪个值对应哪个像素呢?
存储顺序是这样的:先从图像最下面一行开始,从左向右,依次存储,每一个像素用连续的3个字节,分别为B(蓝色分量),
G(绿色分量),R(红色分量)。然后存储倒数第二行,仍然按照图像从左向右存储,然后倒数第三行,
倒数第四行。。。。。。等等,最后存储正数第一行。
事件委托和委托有啥区别啊?委托不是就够了吗?为什么还要事件委托呢
最佳答案
1.
事件在本类型外部只能用“+=”和“-=”去订阅/取消订阅代理,
委托不管在本类型外部还是内部都可以用“+=”、“-=”和“=”订阅/取消订阅代理。
2.
事件只能在本类型内部“触发”,
委托不管在本类型内部还是外部都可以“调用”。
即:事件,只有本类才能激发这个事件,如果用委托取代的话,可想而知,举个例子,按钮的Click事件,只有你的鼠标点击按钮才能由按钮触发,如果Click是委托的话,不管鼠标点击不单击那个按钮,我只要用程序调用这个委托,就可以使得按钮激发Click事件,完全不符合事实,
展开阅读全文