- 实验目的
了解人脸检测常用方法,掌握模板匹配算法实现人脸检测的基本原理,实现一般环境图像中单个正面人脸的检测。
- 实验原理
- 基本原理
本实验所探讨的是一般环境图像中单个正面端正人脸的检测问题。这种条件下的人脸检测的方法主要有模板匹配方法、可变形模板方法等。概括的说,基于模板匹配的方法是在图形灰度上直接比较目标模板和候选图像区域之间的相似性,而基于特征匹配的方法是比较从图像中抽取的一定特征的相似性。
本实验主要用到两种模板:双眼模板和不同长宽比的模板。在检测时首先使用双眼模板进行粗筛选,然后使用不同长宽比的人脸 模板确定出区域的位置和范围,接着通过确定出来的位置和匹配模板的相似度程度来确定是否是人脸。
模板生成,我们在人脸检测中使用了 6 个模板:一个用于初筛选的双眼模板,五个用于检测不同长宽比的人脸模板。采用多个人脸模板是为了适应不同人脸部的不同长宽比。模板是通过对多个样本平均构造出来的。首先在选取的样本图像上用手工画出人脸的区域作为人脸样本。由于各个样本的尺度大小和灰度分布不同,因此首先对他们进行尺度和灰度标准化,然后将所用样本取灰度平均值并压缩到需要的尺度作为原始模板。拷贝原始模板的眼睛部分,进行灰度分布标准化后作为双眼模板,对原始模板分别按照 1:0.9、1:1、1:1.、1:2、1:1.3 的宽长比变形,进行灰度分布标准化后作为人脸模板,如下所示。
图1 人脸检测过程
在模板生成中最主要的工作是图像的尺度变换和灰度分布标准化,尺度变换主要是基于线性插值的重采样方法。将图像看成一个二维矩阵D[W][H],其中W 和H 分别表示图像的宽和高。
图像的灰度值
灰度分布的方差
灰度均衡化
对于输入的每个样本图像,为了将其灰度平均值和方差变换到事先设定的灰度平均值和方差
,从而样本中每个像素点的灰度值进行如下的变换:
假设人脸模板的灰度矩阵为 T[M][N],灰度均值为,均方差为
,输入图像区域的灰度矩阵为 R[M][N],灰度均值为
,均方差为
,那么他们之间的相关系数 r(T,R)和对应像素灰度值的平均偏差d(T,R)分别为:
r(T,R)越大表示模板与输入图像区域的匹配程度越高;而
d(T,R)正相反。将他们综合起来作为匹配程度的度量:
算法的基本思路是:搜索输入图像中所有可能为人脸的区域,认为满足一定条件且与模板匹配程度最高的区域是人脸。为了检测不同长宽比的人脸,采用前述 5 种不同长宽比的模板进行匹配。其中α为权重系数。我们取经验值α= 35.0。
- 算法步骤
假设输入图像中最多只有一个完整的人脸(正面、端正),可能的人脸宽度在0像素到图像宽度的 1/2 之间,人脸位置未知,设输入图像为(宽为 H、高为 W);双眼模板为
。算法如下:
(1)初始化最大匹配度,设当前图像
;
(2)设当前扫描点(x,y)为图像起始点(0,0);
(3)检测扫描区域是否为双眼区,若不是则转(6);
(4)检测相应区域是否为人脸(即计算与模板
的相关系数
是否大于 0.3),是则求出当前区域与人脸的匹配度 D,否则转(6);
(5)若 ,记录当前区域的位置和大小,并令
;
(6)若 x+24<W, x=x+1,转(3);
(7)若 y+31<H, x=0, y=y+1 转(3);
- 实验结果分析及回答问题(或测试环境及测试结果)
- 原图
2.模板
3.结果图
由上图,白框内为检测出来的人脸部分。过程主要为通过先对眼睛位置的识别找到眼睛模板在样本图中最佳位置,接下来将眼睛模板在样本图中进行扩充到与其他五个模板大小相同,然后一一进行匹配(相关系数和平均偏差),寻找到最优匹配,最后得到实验结果。
- 实验代码
#include<stdio.h>
#include<cstring>
#include<math.h>
#include<windows.h>
#include <time.h>
#define imageW 100
#define imageH 100
#define EYEW 68
#define EYEH 18
int ImagePtr[7][10000];
int Height[7];int Width[7];
int bmpHeight, bmpWidth, biBitCount, k_num=3;
unsigned char *pBmpBuf;
RGBQUAD *pColorTable;
int image[10000];
void fun(){
//灰度均衡化(对5张脸部模版,1张眼睛模版,1张测试图进行处理)
float u_0 = 0;
float sigma_0 = 0;
float U[7];float Sigma[7];
for(int i = 0; i < 7; i++){
float u = 0;
float sigma = 0;
for(int j = 0; j < Height[i]; j++){
for(int p = 0; p < Width[i]; p++){
u += ImagePtr[i][j * Width[i] + p];
}
}
u = u / (Height[i] * Width[i]);
U[i] = u;
u_0 += u;
for(int j = 0; j < Height[i]; j++){
for(int p = 0; p < Width[i]; p++){
sigma += pow(ImagePtr[i][j * Width[i] + p] - u, 2);
}
}
sigma = sigma / (Height[i] * Width[i]);
sigma_0 += sigma;
Sigma[i] = sigma;
}
u_0 = u_0 / 7;
sigma_0 = sigma_0 / 7;
for(int i = 0; i < 7; i++){
for(int j = 0; j < Height[i]; j++){
for(int p = 0; p < Width[i]; p++){
ImagePtr[i][j * Width[i] + p] = (sigma_0 / Sigma[i]) * (ImagePtr[i][j * Width[i] + p] - U[i]) + u_0;
}
}
}
//通过眼睛模版比较寻找合适的点(ox,oy)
float D_max = -1000;
unsigned int x = 0;
unsigned int y = 0;
unsigned int W = 0;
unsigned int H = 0;
unsigned int ox = 0;
unsigned int oy = 0;
int flag = 0;
int m = 0;
float rmax = 0;
while(y + EYEH < imageH){
x = 0;
while(x + EYEW < imageW){
float r = 0;
float uR = 0;
float sR = 0;
float uT = u_0;
float sT = sigma_0;
for(int i = x; i <x + EYEW; i++){
for(int j = y; j < y + EYEH; j++){
uR += image[j * imageW + i];
}
}
uR = uR / (EYEW * EYEH);
for(int i = x; i < x + EYEW; i++){
for(int j = y; j < y + EYEH; j++){
sR += pow(image[j * imageW + i] - uR, 2);
}
}
sR = sR / (EYEW * EYEH);
for(int i = x; i < x + EYEW; i++){
for(int j = y; j < y + EYEH; j++){
r += (image[j * imageW + i] - uR) * (ImagePtr[5][(j - y)*EYEW + i - x] - uT);
}
}
r = r / (EYEW * EYEH * sT * sR);
if(r > rmax){
ox = x;
oy = y;
rmax = r;
}
x = x + 1;
}
y = y + 1;
}
printf("%d %d\n",ox,oy);//15 62
//挑选出5张脸部模版中最适合的模版,记录下其长度W,宽度H
float D;
for(int i = 0; i < 5; i++){
float r = 0;
float d = 0;
float uR = 0;
float sR = 0;
float uT = u_0;
float sT = sigma_0;
for(int j = ox; j <ox + Width[i]; j++){
for(int p = oy; p < oy + Height[i]; p++){
uR += image[p * imageW + j];
}
}
uR = uR / (Width[i] * Height[i]);
for(int j = ox; j < ox + Width[i]; j++){
for(int p = oy; p < oy + Height[i]; p++){
sR += pow(image[p * imageW + j] - uR, 2);
}
}
sR = sR / (Width[i] * Height[i]);
for(int j = ox; j < ox + Width[i];j ++){
for(int p = oy; p < oy +Height[i]; p++){
r += (image[p * imageW + j] - uR) * (ImagePtr[i][(p - oy)*Width[i] + j - ox] - uT);
}
}
r = r / (Width[i] * Height[i] * sT * sR);
for(int j = ox; j < ox + Width[i]; j++ ){
for(int p = oy; p < oy + Height[i]; p++){
d += pow(image[p * imageW + j] - ImagePtr[i][(p - oy)*Width[i] + j - ox], 2);
}
}
d = d / (Width[i] * Height[i]);
D = r + 35 / (1 + d);
if(D > D_max){
W = Width[i];
H = Height[i];
D_max = D;
}
}
//输出测试结果(用一个白框标记出识别出来的人脸)
for(int i = ox; i < ox + W; i++){
for(int j = oy - H/2 - 5; j < oy - H/2; j++){
image[j * imageW + i] = 255;
}
}
for(int i = ox; i < ox + W; i++){
for(int j = oy + H/2; j < oy + H/2 + 5; j++){
image[j * imageW + i] = 255;
}
}
for(int i = ox - 5; i < ox; i++){
for(int j = oy - H/2 - 5; j < oy + H/2 + 5; j++){
image[j * imageW + i] = 255;
}
}
for(int i = ox + W; i < ox + W + 5; i++){
for(int j = oy - H/2 - 5; j < oy + H/2 + 5; j++){
image[j * imageW + i] = 255;
}
}
}
void read_temp(){
for (int photonum = 0;photonum < 7;photonum++){
const char src1[] = "a.bmp";
const char src2[] = "b.bmp";
const char src3[] = "c.bmp";
const char src4[] = "d.bmp";
const char src5[] = "e.bmp";
const char src6[] = "eye.bmp";
const char src7[] = "detect.bmp";
FILE *fp = fopen(src7, "rb");
if (photonum == 0) fp = fopen(src1, "rb");
else if (photonum == 1) fp = fopen(src2, "rb");
else if (photonum == 2) fp = fopen(src3, "rb");
else if (photonum == 3) fp = fopen(src4, "rb");
else if (photonum == 4) fp = fopen(src5, "rb");
else if (photonum == 5) fp = fopen(src6, "rb");
if (fp == 0){printf("false\n");}
fseek(fp, sizeof(BITMAPFILEHEADER), 0);
BITMAPINFOHEADER head;
fread(&head, 40, 1, fp);
bmpHeight = head.biHeight;
bmpWidth = head.biWidth;
biBitCount = head.biBitCount;
fseek(fp, sizeof(RGBQUAD), 1);
int LineByte = (bmpWidth*biBitCount / 8 + 3) / 4 * 4;
pBmpBuf = new unsigned char[LineByte*bmpHeight];
fread(pBmpBuf, LineByte*bmpHeight, 1, fp);
fclose(fp);
int rgbMap[bmpHeight * bmpWidth][3];
for (int i = 0; i < bmpHeight;i++){ //取位图 rgb 三通道数据
for (int j = 0; j < bmpWidth;j++){
unsigned char *tmp;
tmp = pBmpBuf + i * LineByte + j * 3;
rgbMap[i * bmpWidth + j][0] = int(*(tmp));
rgbMap[i * bmpWidth + j][1] = int(*(tmp+1));
rgbMap[i * bmpWidth + j][2] = int(*(tmp+2));
}
}
for (int i = 0; i < bmpHeight * bmpWidth;i++){
ImagePtr[photonum][i] = rgbMap[i][0]*0.299 + rgbMap[i][1]*0.587 + rgbMap[i][2]*0.114;
}
Height[photonum] = bmpHeight;
Width[photonum] = bmpWidth;
}
}
int main(){
read_temp();
const char src[] = "detect.bmp";
FILE *fp = fopen(src, "rb");
if (fp == 0){
printf("false\n");
return 0;
}
fseek(fp, sizeof(BITMAPFILEHEADER), 0);
BITMAPINFOHEADER head;
fread(&head, 40, 1, fp);
bmpHeight = head.biHeight;
bmpWidth = head.biWidth;
biBitCount = head.biBitCount;
fseek(fp, sizeof(RGBQUAD), 1);
int LineByte = (bmpWidth*biBitCount / 8 + 3) / 4 * 4;
pBmpBuf = new unsigned char[LineByte*bmpHeight];
fread(pBmpBuf, LineByte*bmpHeight, 1, fp);
fclose(fp);
int rgbMap[bmpHeight * bmpWidth][3];
for (int i = 0; i < bmpHeight;i++){ //取位图 rgb 三通道数据
for (int j = 0; j < bmpWidth;j++){
unsigned char *tmp;
tmp = pBmpBuf + i * LineByte + j * 3;
rgbMap[i * bmpWidth + j][0] = int(*(tmp));
rgbMap[i * bmpWidth + j][1] = int(*(tmp+1));
rgbMap[i * bmpWidth + j][2] = int(*(tmp+2));
}
}
for (int i = 0; i < bmpHeight * bmpWidth;i++){
image[i] = rgbMap[i][0]*0.299 + rgbMap[i][1]*0.587 + rgbMap[i][2]*0.114;
}
const char dst[] = "segResultTest.bmp";
FILE *fp1 = fopen(dst, "wb");
if (fp1 == 0){
printf("false\n");
return 0;
}
int LineByte1 = (bmpWidth * 8 / 8 + 3) / 4 * 4;
//修改文件头,其中有两项需要修改,分别为 bfSize 和 bfOffBits
BITMAPFILEHEADER bfhead;
bfhead.bfType = 0x4D42;
bfhead.bfSize = 14 + 40 + 256 * sizeof(RGBQUAD)+LineByte1*bmpHeight;
bfhead.bfReserved1 = 0;
bfhead.bfReserved2 = 0;
bfhead.bfOffBits = 14 + 40 + 256 * sizeof(RGBQUAD);//修改偏移字节数
fwrite(&bfhead, 14, 1, fp1); //将修改后的文件头存入 fp1;
BITMAPINFOHEADER head1;
head1.biBitCount = 8; //将每像素的位数改为 8
head1.biClrImportant = 0;
head1.biCompression = 0;
head1.biClrUsed = 0;
head1.biHeight = bmpHeight;
head1.biWidth = bmpWidth;
head1.biPlanes = 1;
head1.biSize = 40;
head1.biSizeImage = LineByte1*bmpHeight;//修改位图数据的大小
head1.biXPelsPerMeter = 0;
head1.biYPelsPerMeter = 0;
fwrite(&head1, 40, 1, fp1); //将修改后的信息头存入 fp1;
pColorTable = new RGBQUAD[256];
for (int i = 0; i < 256; i++){
pColorTable[i].rgbRed = i;
pColorTable[i].rgbGreen = i;
pColorTable[i].rgbBlue = i; //是颜色表里的 B、G、R 分量都相等,且等于索引值
}
fwrite(pColorTable, sizeof(RGBQUAD), 256, fp1); //将颜色表写入 fp1
//写位图数据
unsigned char *newBmp;
newBmp = new unsigned char[LineByte1*bmpHeight];
fun();
for (int i = 0; i < bmpHeight; i++){
for (int j = 0; j < bmpWidth; j++){
unsigned char *pb;
pb = newBmp + i * LineByte1 + j;
*pb = image[i * bmpWidth + j];
}
}
fwrite(newBmp, LineByte1*bmpHeight, 1, fp1);
fclose(fp1);
system("pause");
return 0;
}