三、拟采取的研究方法、研究手段及技术路线、实验方案等
根据第二部分的内容,本文中要解决的问题如下:
1.如何从WebCam中得到视频流里的某一帧。
设想的解决途径是:利用Visual C++中提供的AVICap命令集来捕获视频流中的单帧图像。因为2.AVICap支持实时的视频流捕获和单帧捕获并提供视频源的控制。
实际操作思路:因为AVICap的主要功能是通过一个采集窗口(Capture Windows)来提供的, 而且,各功能函数的调用都需要采集窗口的句柄,AVIVCap的消息也是传递给采集窗口的,所以我们需要实现这样一个采集窗口(Capture Windows)。具体从三方面操作来实现:
(1)建立采集窗口:使用CapCreatCaptureWidows函数;
(2)连接一个采集设备(本论文中的采集设备是WebCam):使用CapDriverConnect函数;
(3)获取窗口状态(窗口的状态被定义在CAPSTATUS这个数据结构中,它包含了大量的有用信息,可以通过CapGetStatus函数得到CAPSTATUS的指针),从采集设备WebCam中得到视频帧。
得到视频帧的过程是通过调用程序自定义的回调函数来实现的。所谓回调函数,其作用就是向采集设备(WebCam)的驱动程序发出读写命令,从而实现程序和设备之间定时或不定时地通信,这里的通信可以是视频流回调函数定时地从USB摄像头取回一帧数据以便回放,也可以是其他回调函数的操作。因为这部分要解决的问题是从WebCam中得到视频流里的某一帧,所以,我们需要实现的回调函数是“单帧采集回调函数”。函数体如下:
LRESULT PASCAL FrameCallbackProc(HWND hWnd,LPVIDEOHDR lpVHdr)
{
if(!ghWndMain) return FALSE;
wsprintf(gachBuffer,”Preview frame#%ld”,gdwFrameNum++);
// The wsprintf function formats and stores a series of characters and values in a buffer. Any arguments are converted and copied to the output buffer according to the corresponding format specification in the format string. The function appends a terminating null character to the characters it writes, but the return value does not include the terminating null character in its character count.(wsprintf函数是)
// int wsprintf(
LPTSTR lpOut, // pointer to buffer for output
LPCTSTR lpFmt, // pointer to format-control string
... // optional arguments
);
SetWindowText(ghWndMain,(LPSTR)gachBuffer);
// The SetWindowText function changes the text of the specified window's title bar (if it has one). If the specified window is a control, the text of the control is changed. However, SetWindowText cannot change the text of a control in another application.
//BOOL SetWindowText(
HWND hWnd, // handle to window or control
LPCTSTR lpString // address of string
);
Return(LRESULT)TRUE;
}
//---------------------------------------------------------------------------------------
LRESULT FAR PASCAL VideoCallbackProc(HWND hWnd,LPVIDEOHDR lpVHdr)
//参数lpVHdr指向视频流数据块的头信息
{
((CMainFrame*)AfxGetMainWnd())->m_dibinfo.bitmapheader.
biSizeImage=lpVHdr->dwBytesUsed;
//把视频流数据块的实际字节数赋值到以DIBINFO(该数据结构是用于存储一帧信息)定义的变量m_dibinfo的bitmapheader作用域的biSizeImage作用域,其中,bitmapheader是用BITMAPINFOHEADER(文件信息头)数据结构定义的变量,该变量的biSizeImage作用域是用来存放一帧图像的字节数。
memcpy(((CMainFrame*)AfxGetMainWnd())->m_dibinfo.buffer+
((CMainFrame*)AfxGetMainWnd())->m_dibinfo.VideoFormatSize,
lpVHdr->lpData,lpVHdr->dwBytesUsed);
//把视频流的图像信息拷贝到内存中;
// lpVHdr的lpData作用域是指向被锁定的数据缓冲的指针,在这里,这个被锁定的数据缓冲应该是视频流的图像信息;
// lpVHdr的dwBytesUsed作用域表示的是视频流数据块的实际字节数;
(((CMainFrame*)AfxGetMainWnd())->GetActiveView())->
InvalidateRect(NULL,FALSE);
//InvalidateRect函数是用来在指定窗口的更新区域添加一个矩形。其中,这个更新区域表示的是窗口用户的一部分,且是必须被重画的一部分)
return(LRESULT)TRUE;
}
//---------------------------------------------------------------------------------------
了解视频单帧图像在内存中的存储方式,从而确定合适的颜色模型。
Windows中位图文件有两种格式,一种是“设备相关”位图(Device Depend Bitmap,DDB),另一种是“设备无关”位图(Device Independ Bitmap,DIB)。因为,DDB位图没有保存位图的调色板,因此要使用DDB位图必须在同类设备中显示,且此设备在位平面或彩色上与原设备有同样的彩色安排,否则色彩就可能完全失真。所以,我在论文中选择DIB位图。DIB位图由三部分构成(如下),保存顺序依次是:文件信息头(BITMAPINFOHEADER)——>调色板(RGBQUAD)(真彩色图像没有调色板)——>图像数据(BITMAPDATA)。
(1)BITMAPINFOHEADER(文件信息头)结构定义为
typedef struct tagBITMAPINFOHEADER{ // bmih
DWORD biSize; //BITMAPINFOHEADER结构的大小;
LONG biWidth; //位图的宽度,单位是像素;
LONG biHeight; //位图的高度,单位是像素;
WORD biPlanes; //位图显示设备位数,必须是1;
WORD biBitCount; //位图颜色位数,定义了位图每像素的位数;
DWORD biCompression; //压缩标志,BI_RGB类型为非压缩,BI_RLE4和BI_RLE8为行程编码压缩;
DWORD biSizeImage; //图像字节数,它可由文件头中的其他域计算出,需注意的是每排像素必须在32位或其倍数上结束,如果一排像素不到32位边界,则用“0”填充其余位;
LONG biXPelsPerMeter; //图像x方向分辨率;
LONG biYPelsPerMeter; //图像y方向分辨率;
DWORD biClrUsed; //图像颜色数,指图像用到的颜色数,如果该数为0,则用到的颜色数为2的biBitCount次方;如果不用置0,表示所有颜色都用到,如果位图被压缩,则必须置0;
DWORD biClrImportant; //图像重要颜色数,通常置为0,表示所有颜色都重要;
} BITMAPINFOHEADER;
(2)RGBQUAD(文件调色板)结构的定义为
typedef struct tagRGBQUAD { // rgbq
BYTE rgbBlue; //颜色的蓝色分量;
BYTE rgbGreen; //颜色的绿色分量;
BYTE rgbRed; //颜色的红色分量;
BYTE rgbReserved; //保留,为0
} RGBQUAD;
RGBQUAD数据结构是BMP所包含的颜色表,接在BITMAPINFOHEADER结构之后含有位图中用到的每种颜色的RGB信息。在位图中有多少种颜色,就有多少RGBQUAD数据结构项;biClrUsed值就是RGBQUAD元素的数目。
对于用到调色板的位图,图像数据就是该像素颜色在调色板中的索引值,对于真彩色图像,图像数据就是实际的R、G、B值,一个像素是由3个字节24位组成,第1个字节(前8位)表示B,第2个字节(中间8位)表示G,第3个字节(后8位)表示R。
(3)BITMAPDATA(图像数据)
BMP文件中位图化的图像数据是以连续行的形式存储的,但图像是以相反的顺序存储,即文件读出的第一行是图像的最后一行。图像数据是从左下角到右上角顺序排列的。(biWidth(位图的宽度)*biBitCount(位图颜色位数)得到的是每一行图像占用的位数,所以在进行色彩空间的转换时,对于存放图像的内存缓冲的读取就可以以这个位数的长度来进行。)
设想的解决途径是:根据DIB位图格式,定义存储视频帧的数据结构,了解AVICap中用于捕获视频流单帧图像的这些函数的输入输出。
实际操作思路:
1)根据DIB位图格式,定义如下数据结构DIBINFO用来存放一帧视频帧。
typedef struct{
int headsize; //bitmap headsize
char buffer[32000]; //bitmap head and data
BITMAPINFOHEADER bitmapheader; //文件信息头结构
DWORD VideoFormatSize; //表示视频格式的结构尺寸
}DIBINFO,*PDIBINFO;
2)还需要一个表示视频数据块的数据结构VIDEOHDR,它的定义如下:
typedef struct videohdr_tag{
LPBYTE lpData; //pointer to locked data buffer(指向被锁定的数据缓冲的指针)
DWORD dwBufferLength; //Length of data buffer(数据缓冲的大小)
DWORD dwBytesUsed; //Bytes actually used(实际使用的字节数)
DWORD dwTimeCaptured; //Milliseconds from start of srteam(视频流开始的时间,以毫秒为单位)
DWORD dwUser; //for client’s use(用户使用的作用域)
DWORD dwFlags; //assorted flags (see defines)
DWORD dwReserved[4]; //reserved for driver(为设备保留的作用域)
} VIDEOHDR,NEAR * PVIDEOHDR,FAR * LPWIDEOHDR;
在解决第2个问题的基础上,确定人脸检测算法。设想的解决途径待定,但在本文第三部分,先给出了一种拟解决的人脸检测算法。
因为人脸的肤色信息是人脸中最大块,最集中的特征,所以我们首先想到的就是通过人脸的肤色信息,初步把人脸从背景中区分出来。“经过统计证明,不同人种,不同环境下的肤色区别主要受亮度影响,受色度影响较小(《安全监控中的一种快速人连定位算法》)”,所以检测视频帧中某一像素是否表示人脸像素,可以通过“阈值处理(阈值处理就是对于输入图像的各像素灰度值的某定值(称为阈值,threshold)范围内时,赋予对应输出图像的像素为白色或黑色。)”的方法对该像素的色度值进行判断,然后把符合“阈值处理”公式的像素标识出来,从而得到人脸的大致位置。但是,当我们从WebCam得到一帧视频帧时,视频帧中像素使用RGB色彩空间表示的,而“阈值处理”方法中的公式则是针对YCrCb色彩空间内的三个分量而言的,所以我们需要先转换色彩空间。(YCrCb色彩空间(即YUV),其中的Y分量是表示像素的亮度,Cr分量表示红色分量,Cb表示蓝色分量,通常把Cr和Cb称为色度,其中,决定了色调,而代表颜色的饱和度。因此,当把RGB彩色空间转换成YCrCb色彩空间时,因为人眼对于亮度的敏感程度大于对于色度的敏感程度,所以可以让相临的像素使用同一个色度值,而凡是符合“阈值处理”公式的像素就是我们需要的肤色像素。)
RGB色彩空间到YUV色彩空间的转换方程:
Y=0.299R+0.587G+0.114B
V(Cr)=R-Y=-0.299R-0.587G+0.886B
U(Cb)=B-Y=0.701R-0.587G-0.114B
YCrCb 色彩空间到RGB色彩空间的转换方程:
R' = 1.164 (Y-16) + 1.596(Cr -128)
G' = 1.164 (Y-16) - 0.813 (Cr -128) - 0.392(Cb-128)
B' = 1.164 (Y-16) + 1.596 (Cr-128)
转换好色彩空间后使用“阈值”方法来判断肤色像素。
“阈值处理”方法的判断公式:
公式一:
对于图像中每个像素(i,j)满足M(i,j)=
则将图像转变成一个二值图像,其中白色区域就是肤色区域(《安全监控中的一种快速人脸定位算法》)。
公式二:
如果一个像素在YCrCb色彩空间的Cr(V),Cb(U)分量满足如下两个不等式组中的任意一个,那么就把这个像素识别为脸部肤色。(摘自《Detection and Tracking of Faces in Real-Time Environments》)
不等式组一:0.2611tan-10.3111 且 4378
不等式组二:0.25tan-10.3611 且070
得到视频帧中的肤色像素后,在通过人脸肤色的建模公式得到人脸的大致位置。
公式三(人脸肤色建模(色彩建模)公式):
且
其中:=114.38;=160.02;=1.60;=2.41;a=25.39;b=14.03;=2.53
对肤色像素的膨胀、腐蚀操作
膨胀(dilation)是指某像素的邻域内只要有一个像素是白像素,则该像素就由黑变白,其他保持不变;
腐蚀(erosion)是指某像素的邻域内只要有一个像素是黑像素,则该像素就由白变黑,其他保持不变;
从图形学的角度来理解,一个像素的邻域范围内最多可以有8个像素与之相邻,所以我们在确定膨胀、腐蚀操作中的邻域范围时,可以有8连通,4连通,左右连通或上下连通,这里,我们选择4连通。也就是说,膨胀、腐蚀操作中某个像素的邻域范围,是与该像素相邻的上下、左右四个像素。
去掉一些离散的非人脸区域
递归统计图像中出现连续白色像素的区域内这些白色像素点的个数,然后用一个常量AREAPIXEL来界定白色像素点的个数,凡是个数小于AREAPIXEL的连续区域都被判定为非人脸区域,并将这些区域内的像素置为黑像素。
大致定位人脸区域
设想的思路:通过扫描图像来检测出人脸的边缘。根据图像的存储原理,首先,依次从下往上进行扫描,出现白色像素的第一行就是人脸的下边界,记该行为y1。确定了下边界以后,再往上扫描,一旦出现全为黑色像素的一行,就说明这行扫描线的前面一行是人脸上边界,记前面一行为y2。同理,再依次从左往右进行扫描,记下人脸的左右边界为x1,x2。由此得到的坐标(x1,y1),(x2,y1),(x1,y2),(x2,y2)所确定的矩形即为人脸区域。
|