这是跟小组成员一起做的单片机课程设计,下面是我们小组的报告
基于STM32的人脸识别
摘 要
此设计以STM32F407单片机为处理核心,通过OV7670摄像头模块采集人脸图像,并通过基于主成分分析法的特征脸识别对采集到的图像与预先通过MATLAB训练的得到的特征脸进行欧氏距离的计算,再结合KN近邻法求得最大可能性的识别结果以汉字名字的形式在OLED显示屏上显示出来。
关键词:STM32 OLED显示 OV7670 特征脸识别 主成分分析
人脸识别,是基于人的脸部特征信息进行身份识别的一种生物识别技术。用摄像机或摄像头采集含有人脸的图像或视频流,并自动在图像中检测和跟踪人脸,进而对检测到的人脸进行脸部的一系列相关技术,通常也叫做人像识别、面部识别。目前人脸识别应用很广泛,从国家安全领域,公众安全领域,计算机交互领域到身边刷脸的自动取款机,人脸解锁的苹果手机,人类已经开始走向“刷脸时代”。单片机则广泛地应用于控制等领域。基于此,将二者结合起来研究十分有必要。
其次,人脸识别是一个高维的模式识别问题,1987年Sirovich和Kirby为减少人脸图像的表示维数采用了K.L变换。他们提出把人脸图像看作一个高维向量,每一个象素都是该高维向量的一个组成部分,然后利用K-L变换投影得到相应的表征人脸图像特征的特征空间。尽管Sirovich和Kirby没有把这种方法用到人脸识别,但他们已经提出将人脸图像用特征向量的线性组合来描述的思想。1991年Matthew Turk和Alex Pentland最早将PCA(Principal Component Analysis)应用于人脸识别,进一步提出了“特征脸(Eigenface)”的概念。
PCA是图像压缩中的一种最优正交变换,用于统计特征提取构成了子空间法模式识别的基础,在实际应用中使用PCA做人脸识别时可以减少计算量,提高识别率。
OLED12864采用IIC协议与STM32F407连接,此时只需四个引脚(VCC、GND、SDA、SCL)即可实现通信。电路图如图二:
图一 OLED数据传送时钟
图二 OLED模块电路图
OV7670摄像头存储图像数据的过程为:等待OV7670同步信号----FIFO
写指针复位----FIFO写使能----等待第二个OV7670同步信号----FIFO写禁止。
OV7670摄像头读取图像数据的过程:FIFO读指针复位----给FIFO读时钟(FIFO_RCLK----读取第一个像素高字节----给FIFO读时钟----读取第一个像素低字节----给FIFO读时钟----读取第二个像素高字节----循环读取剩余像素---结束。
OV7670通过SCCB通信协议与单片机通信。电路图如图三:
图三 OV7670模块电路图
为了更直观地看清三个模块之间的连接关系,电路图采用层次图画法。OV7670行同步信号HREF为高电平时传输图像数据,因此可将其直接与单片机的VCC连接以固定拉高。STM32的PC0-PC7与OV7670的数据口D0-D7连接传输数据;PE4接SCCB通信时钟信号OV_SCL,PE6接SCCB通信数据信号OV_SDA;PB3接FIFO写使能FIFO_WEN,PD6接FIFO写指针复位FIFO_WRST;PG14接FIFO读指针复位FIFO FIFO_RRST;PE16接输出使能FIFO_OE;PA8接OV7670帧同步信号读FIFO时钟OV_VSYNC,片选中0V7670;PB5接FIFO_RCLK。STM32F407有专门设计的IIC引脚:PB8、PB9,因此OLED的SCL、SDA分别与PB8、PB9相连接。整体电路图如图四:
图四 整体电路图
图五 成品图
本次课程设计在软件编程的过程中主要使用VC6.0+opencv、MATLAB、mdk4.0等软件。由于图像样本中因背景、光照强度而存在的噪音,因此我们通过VC6.0与opencv对图像进行预处理,主要采用灰度直方图均衡化的方法,并由图六可知,该方法可以提高图片对比度以提高图片的识别度,实现过程可参考附录3。其次我们通过软件MDK4.0进行整体功能的设计,通过查询开关K0的开闭进行OV7670模块中FIFO的的数据的读取,可参考图七整体电路简化图。在STM32F4接收到K0闭合的信号后开始进行FIFO的读取,在每一个像素读取的同时会进行权重的更新以减少数据存储内存的使用,最后通过计算欧氏距离以及结合KN(N=3)近邻法进行人脸识别,流程图可参考图八。
图六 图像预处理
图七 整体电路简化图
图八 主程序流程图
接下来利用MATLAB进行模型的建立以及优化,并将得到的特征向量以及特征值移植到MDK代码中以进行人脸识别,在MATLAB仿真中,人脸识别率可达96%。首先获取包含M张人脸图像的集合S。每张图像可以转换成一个N维的向量,然后把这M个向量放到一个集合S里,即:
在获取到人脸向量集合S后,计算得到平均图像(平均值)Ψ,如图七:
图九 平均图像
计算每张图像和平均图像的差值Φ ,就是用S集合里的每个元素减去平均值Ψ。
由于训练图像的数量小于图像的维数比如(M
协方差矩阵的特征向量Ul就可以表示为:
将特征向量还原成像素排列可得图八:
图十 特征图像
首先考虑一张新的人脸,我们可以用特征脸对其进行标示:
其中k=1,2...M,对于第k个特征脸uk,上式可以计算其对应的权重,M个权重可以构成一个向量:
对两者求欧式距离,当距离小于阈值时说明要判别的脸和训练集内的第k个脸是同一个人的。当遍历所有训练集都大于阈值时,根据距离值的大小又可分为是新的人脸或者不是人脸的两种情况。根据训练集的不同,阈值设定并不是固定的。
其中Ω代表要判别的人脸,Ωk代表训练集内的某个人脸,两者都是通过特征脸的权重来表示的。但是在本次课程设计中由于样本采集以及应用环境所使用的摄像头的不同,因此没有采用阈值的设置。
上电后对OLED进行初始化并且清屏,需要显示时给OLED设置坐标(页地址、行地址),然后通过硬件IIC给OLED写数据(需要有一个开始信号),循环写入直至所有所有数据都写完。
在STM32 控制下,首先显示“Welcome”,在接收到比对结果信号时,进行结果显示,延时5s,清屏,显示“Welcome”。
图十一 OLED显示流程图
//MDK主程序代码
#include
#include "sys.h"
#include "math.h"
#include "face.h"
#include "delay.h"
#include "usart.h"
#include "oled.h"
#include "led.h"
#include "iic.h"
#include "key.h"
#include "ov7670.h"
//#include "timer.h"
#include "sccb.h"
//#include "exti.h"
//extern uint8_t ov_sta; //在exit.c里面定义
//extern uint8_t ov_frame; //在timer.c里面定义
//void EignFace(u8 *inImage[80][60])
void EignFace(u32* cmDis[10])
{
u32 i,j;
u32 *dis[15];
u8 num[3];
u32 *temp;
for(i=0;i<15;i++)
{
for(j=0;j<10;j++)
{
dis[i] += (((u32)(cmDis[j]-(u32)face_v[i][j]))/1000)*(((u32)(cmDis[j]-(u32)face_v[i][j]))/1000);
}
}
temp=dis[0];
for(j=1;j<15;j++)
{
if(tempdis[num[0]])
{
temp=dis[j];
num[1]=j;
}
}
for(j=0;j<15 ;j++)
{
if(tempdis[num[1]] )
{
temp=dis[j];
num[2]=j;
}
}
for(i=0;i<3;i++)
{
num[i]=num[i]/5+1;
}
if((num[0]!=num[1])&&(num[1]!=num[2])&&(num[0]!=num[2]))
{
i=num[0];
}
else if(num[0]==num[1] || num[0]==num[2])
{
i=num[0];
}
else if(num[1]==num[2])
{
i=num[1];
}
if(i==1)//OLED_P8x16Str(0,2,"Han Kang Fu ");
{OLED_CLS();
OLED_P32x32Ch(1,0,0);
OLED_P32x32Ch(33,0,1);
OLED_P32x32Ch(65,0,2);
}
else if(i==2)//OLED_P8x16Str(0,2,"Lin Jun Ping");
//if(i==2)OLED_P8x16Str(0,2,"Lin Jun Ping");
{OLED_CLS();
OLED_P32x32Ch(1,0,3);
OLED_P32x32Ch(33,0,4);
OLED_P32x32Ch(65,0,5);
}
else if(i==3)//OLED_P8x16Str(0,2," Jia Li Yun ");
{OLED_CLS();
OLED_P32x32Ch(1,0,6);
OLED_P32x32Ch(33,0,7);
OLED_P32x32Ch(65,0,8);
}
/*else
OLED_P8x16Str(0,2,"Error Input!");
{
OLED_P16x16Ch(112,1,"重");
OLED_P16x16Ch(112,3,"新");
OLED_P16x16Ch(112,5,"输");
OLED_P16x16Ch(112,7,"入");
}*/
}
u8* RGB565ToGray(u16 temp)
{
u8 color_r=(temp>>11)*0.3;
u8 color_g=((temp>>5)&0x3f)*0.59;
u8 color_b=(temp&0x1f)*0.11;
return (u8*)(color_r+color_g+color_b);
}
void camera_refresh(void)
{
u16 color;
u8 j,k;
//u8* img[80][60];
u32* cmDis[10];
//if(ov_sta==2)
{
/* LCD_Scan_Dir(U2D_L2R); //从上到下,从左到右
LCD_SetCursor(0x00,0x0000); //设置光标位置
LCD_WriteRAM_Prepare(); //开始写入GRAM */
OV7670_RRST=0; //开始复位读指针
OV7670_RCK=0;
OV7670_RCK=1;
OV7670_RCK=0;
OV7670_RRST=1; //复位读指针结束
OV7670_RCK=1;
for(j=0;j<80;j++)
{
for(k=0;k<60;k++)
{
u8* img1;
u8 m;
OV7670_RCK=0;
color=(GPIOC->IDR&0XFF0)>>4; //读数据
OV7670_RCK=1;
color<<=8;
OV7670_RCK=0;
color|=(GPIOC->IDR&0XFF0)>>4; //读数据
OV7670_RCK=1;
img1=RGB565ToGray(color);
for(m=0;m<10;m++)
{
cmDis[m] += (u32)(*(*(face_eign+k*60+j)+m))*((u32)img1);
}
}
}
// EXTI->PR=1<<8; //清除LINE8上的中断标志位
// ov_sta=0; //开始下一次采集
EignFace(cmDis);
}
}
int main(void)
{
uint8_t lightmode=0,saturation=2,brightness=2,contrast=2;
uint8_t effect=0;
SystemInit(); //初始化系统时钟,设置时钟为168Mhz
LED_GPIO_Conf(); //初始化LED的GPIO配置
SysTick_Init();
USART1_Conf();
IIC_Init();
OLED_Init();
KEY_Init();
OV7670_Init();
OLED_P8x16Str(0,2,"Welcome!");
LED0(0);
delay_ms(1500);
OV7670_Light_Mode(lightmode);
OV7670_Color_Saturation(saturation);
OV7670_Brightness(brightness);
OV7670_Contrast(contrast);
OV7670_Special_Effects(effect);
//EXTI9_Init(); //使能定时器捕获
OV7670_Window_Set(10,174,60,80); //设置窗口
OV7670_CS=0;
while(1)
{
if(KEY_Scan(0)==1)
{
camera_refresh();//更新显示
delay_ms(5000);
OLED_CLS();
OLED_P8x16Str(0,2," Welcome! ");
}
//delay_ms(1500);
//OLED_P8x16Str(0,2," Welcome! ");
//continue;
}
}
//vc++6.0 + opencv代码
#include
#include "cv.h"
#include "highgui.h"
#include "cxcore.h"
#include "string.h"
IplImage *show_resized_image(IplImage *pSrcImg)
{
CvSize dstSize;
IplImage *pDstImg = NULL;
dstSize.height = 80;
dstSize.width = 60;
pDstImg = cvCreateImage(dstSize, pSrcImg->depth, pSrcImg->nChannels);
cvResize(pSrcImg, pDstImg, CV_INTER_AREA);
return pDstImg;
//cvSaveImage(pDsrcImg, pDstImg);
//}
}
IplImage *show_gray_equalize(IplImage *pGrayImg) //灰度直方图均衡化,减少亮度的影响
{
IplImage *pGrayEqualImg = cvCreateImage(cvGetSize(pGrayImg),IPL_DEPTH_8U,1);
cvEqualizeHist(pGrayImg, pGrayEqualImg);
//cvSaveImage("5.jpg", pGrayEqualImg);
return show_resized_image(pGrayEqualImg);
}
int main()
{
const char *pstrSrcImg = "C:/Users/Administrator/Desktop/face_recognization/3/10.jpg";
IplImage *pSrcImg = cvLoadImage(pstrSrcImg, CV_LOAD_IMAGE_GRAYSCALE);
IplImage *mat;
//show_resized_image(pSrcImg);
mat = show_gray_equalize(pSrcImg);
// mat = show_resized_image(pSrcImg);
cvSaveImage("3/2/1.jpg", mat);
// show_resized_image();
//printf("%s",pstrSrcImg);
return 0;
}
1、成功地利用摄像头采集到人脸图像,并且将这些图像数据传送给STM32处理。2、STM32能够以较快的速度完成比对,且能够保证一定的识别准确度(可采用训练图像与测试图像来源一致来提高准确度)。
3、将比对的结果以汉字的格式通过OLED显示出来。
1、无法将采集到的图像直接显示在OLED显示屏上。如此一来,就无法确定摄像头有效地采集到所需的人脸数据,使得系统的准确度不能很高。
2、以此技术为基础,做一个简单的可以应用到生产生活当中的产品,如考勤(点名)系统,门禁系统等。
【1】http://blog.csdn.net/smartempire/article/details/21406005人脸识别经典算法一:特征脸方法(Eigenface)
【2】STM32F4xx中文参考手册
【3】正点原子开发板开发程序