人脸识别,是基于人的脸部特征信息进行身份识别的一种生物识别技术。用摄像机或摄像头采集含有人脸的图像或视频流,并自动在图像中检测和跟踪人脸,进而对检测到的人脸进行脸部识别的一系列相关技术,通常也叫做人像识别、面部识别。
人脸识别系统的研究始于20世纪60年代,80年代后随着计算机技术和光学成像技术的发展得到提高,而真正进入初级的应用阶段则在90年后期,并且以美国、德国和日本的技术实现为主;人脸识别系统成功的关键在于是否拥有尖端的核心算法,并使识别结果具有实用化的识别率和识别速度;“人脸识别系统”集成了人工智能、机器识别、机器学习、模型理论、专家系统、视频图像处理等多种专业技术,同时需结合中间值处理的理论与实现,是生物特征识别的最新应用,其核心技术的实现,展现了弱人工智能向强人工智能的转化。
AI人脸识别是一种依据人的面部特征(如统计或几何特征等),自动进行身份识别的一种生物识别技术,又称为面像识别、人像识别、相貌识别、面孔识别、面部识别等。
人工智能(Artificial Intelligence),英文缩写为AI。它是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。
人工智能是计算机科学的一个分支,它企图了解智能的实质,并生产出一种新的能以人类智能相似的方式做出反应的智能机器,该领域的研究包括机器人、语言识别、图像识别、自然语言处理和专家系统等。人工智能从诞生以来,理论和技术日益成熟,应用领域也不断扩大,可以设想,未来人工智能带来的科技产品,将会是人类智慧的“容器”。人工智能可以对人的意识、思维的信息过程的模拟。人工智能不是人的智能,但能像人那样思考、也可能超过人的智能。
百度人脸识别技术可做到1:N的高精度识别,这个技术壁垒可让百度在景区、大企业、写字楼的人脸闸机领域获得独特的技术优势。
如今,随着人脸识别技术在各行各业的推广和普及,应用场景变的复杂多样,黑产攻击手段同样也愈发诡诈。为此,百度人脸实名认证解决方案加入合成图检测能力,深度识别图片中的伪造痕迹,让PS图片、Deepfake等合成图攻击无所遁形;推出大数据风控能力,基于百度海量大数据设备因子对SDK端的设备环境进行风险判断和拦截;同时配合安全加密能力,在SDK端对人脸信息进行加密,云端接口进行解密,防止黑产在信息上传云端的过程中伪造替换。
本地版活体检测(本地活体):通过让用户做出指定脸部交互动作,识别当前操作者是否为活体,此功能可离线使用,可设定指定动作是否使用及应用顺序。可有效抵御高清图片、3D建模、视频等攻击。
在线版活体检测(在线活体):基于本地版有交互活体检测输出的质量合格的图片,请求在线活体检测接口,可以进一步判断用户上传的图片是否为活体,进一步抵御高精度视频、高精度建模等攻击。此接口建议与本地版活体检测结合使用,且输入的照片一定为二次采集图片(即限于产品前端拍照所得,不可为相册图片)。
人脸质量检测:判断视频流中的图片帧中,哪些图片质量较佳,即人脸图像特征清晰(满足角度、姿态、光照、模糊度等校验)。
人脸识别:人脸识别技术的泛称,通常指识别图片或视频中的人脸,提取人脸特征值,用于进一步的人脸对比、搜索等业务操作。
相比于传统的打卡考勤、指纹考勤,人脸考勤有众多优势:
(1)防作弊能力强:提供在线、离线两种活体检测方案,有效抵御视频、翻拍、3D模型等活体作弊手段。
(2)支持多人同时考勤:智能摄像机支持同时捕捉视频流中出现的多张人脸,实现多人同时考勤,提高考勤效率。
(3)快速响应:500毫秒快速识别比对,2秒内完成人脸捕捉、考勤全过程。
为保障及推动快速复工复产复学,百度智能云上线口罩佩戴检测模型,同时优化戴口罩面部被大面积遮挡下的人脸识别算法。目前,口罩检测能力已覆盖全产品形态,即日起面向大众开放使用,助力疫情防控,保障复工复产复学的有序开展。
人脸口罩检测与识别能力主要有两方面,即口罩检测和戴口罩人脸识别 。
口罩检测:用于对用户是否佩戴口罩这一属性进行判断和分类。该能力适用于需要通过是否佩戴口罩这个属性来对人群进行分类管控的应用场景,如:机场、车站、医院、商超等大型公共场所。
戴口罩人脸识别:用于戴口罩情况下的人脸识别,即用户不需要摘掉口罩就可以完成人脸检测、活体检测、特征抽取和比对,从而实现身份验证。该能力适用于各类公司、小区、写字楼的门禁闸机和公司的考勤机等应用场景。
硬件平台:Ubuntu18.04、USB免驱摄像头
UI界面:GTK+_2.0
开发语言:C/C++
GTK(GIMP Toolkit)是一套源码以LGPL许可协议分发、跨平台的图形工具包。最初是为GIMP写的,已成为一个功能强大、设计灵活的一个通用图形库,是GNU/Linux下开发图形界面的应用程序的主流开发工具之一。当然,GTK也是支持跨平台的,支持Unix类的系统、Windows,甚至手机平台。
GTK是使用C语言写的,所以其原生API都是面向C的,同时GTK的一大特点是,在C语言层面实现了面向对象的特性。GTK是完全免费的,而且基于LGPL协议,这可以保证私有软件通过链接使用GTK可以不把软件源代码开放,对商业应用较友好,这跟GPL协议是不一样的。也正是LGPL协议,使得早些年Gnome(基于GTK编写)风头胜过KDE(基于QT编写)。
初始化GTK,创建窗口,初始化摄像头设备,检测摄像头设备是否正常。
int main(int argc,char **argv)
{
GtkWidget *dialog;
/*gtk初始化*/
gtk_init(&argc,&argv);
/*创建窗口*/
window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window),"人脸识别");
/*固定窗口大小*/
gtk_window_set_resizable (GTK_WINDOW(window),FALSE);
/*设置窗口大小*/
gtk_widget_set_size_request(window,800,480);
gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_CENTER_ALWAYS);//居中显示
/*连接信号*/
g_signal_connect(G_OBJECT(window),"destroy",G_CALLBACK(on_delete_event),NULL);
/*创建标签*/
GdkColor color;
color.red=0xffff;
color.green=0;
color.blue=0;
label=gtk_label_new("");
/*设置字体大小和颜色*/
gtk_label_set_markup(GTK_LABEL(label),"正在启动摄像头,请稍等...");
g_timeout_add(500,timer_func, label);//开启定时器1s时间
gtk_label_set_justify (GTK_LABEL(label),GTK_JUSTIFY_CENTER);
gtk_container_add(GTK_CONTAINER(window), label);
gtk_widget_show(label);
change_background(window, 800, 480, "1.bmp");
gtk_widget_show(window);
gtk_main();
return 0;
}
在定时器中等待摄像头启动成功,完成人脸识别UI界面配置。
/*定时器处理函数*/
GtkWidget *window;
gboolean timer_func(gpointer data)
{
GtkWidget *widget=(GtkWidget *)data;
int ret;
ret=Camera_Init(&video_info);
if(ret==0)
{
g_print("摄像头启动成功\n");
gtk_widget_destroy(label);//关闭对话框
GtkWidget *hbox;
GtkWidget *vbox,*box;
GtkWidget *button;
GtkTooltips *tiptool;
/*图像宽高*/
width=video_info.image_w;
height=video_info.image_h;
rgbbuf=malloc(width*height*3);
jpg_data=malloc(width*height*3);//保存RGB颜色数据
imagebase64_data=malloc((height*width*3)*8/6);/*base64编码后数据*/
/*创建横向盒*/
hbox=gtk_hbox_new(FALSE,0);
gtk_container_add(GTK_CONTAINER(window),hbox);
gtk_widget_show(hbox);
/*初始化gdk的rgb*/
gdk_rgb_init();
/*将gdk视件和颜色表放入gtk构件中*/
gtk_widget_push_visual(gdk_rgb_get_visual());
gtk_widget_push_colormap(gdk_rgb_get_cmap());
/*创建绘图区域*/
drawarea=gtk_drawing_area_new();
gtk_widget_pop_visual();
gtk_widget_pop_colormap();
gtk_box_pack_start(GTK_BOX(hbox),drawarea,FALSE,FALSE,0);
g_signal_connect(G_OBJECT(drawarea),"expose_event",G_CALLBACK(draw_image),NULL);
guint id=gtk_idle_add((GtkFunction)read_data,NULL);
/*设置窗口大小*/
gtk_widget_set_size_request(GTK_WIDGET(drawarea),width,height);
/*创建纵向盒*/
vbox=gtk_vbox_new(FALSE,0);
gtk_box_pack_start(GTK_BOX(hbox),vbox,TRUE,FALSE,0);
gtk_widget_show(vbox);
/*人脸注册时输入姓名*/
box=gtk_hbox_new(FALSE,0);
gtk_box_pack_start(GTK_BOX(vbox),box,FALSE,TRUE,30);
gtk_widget_show(box);
label=gtk_label_new("姓名:");
gtk_box_pack_start(GTK_BOX(box),label,FALSE,TRUE,0);
gtk_widget_show(label);
/*创建输入文本框*/
entry1=gtk_entry_new();
/*设置输入文本框尺寸*/
gtk_widget_set_size_request(entry1,120,-1);
gtk_entry_set_max_length(GTK_ENTRY(entry1),20);
gtk_box_pack_start(GTK_BOX(box),entry1,FALSE,FALSE,0);
g_signal_connect(G_OBJECT(entry1),"key-press-event", G_CALLBACK(entry_callback),"1");
gtk_widget_show(entry1);
/*创建提示对象工具*/
tiptool=gtk_tooltips_new();
/*添加提示信息到按钮*/
gtk_tooltips_set_tip(tiptool,entry1,"填写姓名",NULL);
/*人脸注册时输入手机号*/
box=gtk_hbox_new(FALSE,0);
gtk_box_pack_start(GTK_BOX(vbox),box,FALSE,TRUE,15);
gtk_widget_show(box);
label=gtk_label_new("电话:");
gtk_box_pack_start(GTK_BOX(box),label,FALSE,TRUE,0);
gtk_widget_show(label);
/*创建输入文本框*/
entry2=gtk_entry_new();
/*设置输入文本框尺寸*/
gtk_widget_set_size_request(entry2,120,-1);
gtk_entry_set_max_length(GTK_ENTRY(entry2),20);
gtk_box_pack_start(GTK_BOX(box),entry2,FALSE,FALSE,0);
g_signal_connect(G_OBJECT(entry2),"key-press-event", G_CALLBACK(entry_callback),"2");
gtk_widget_show(entry2);
/*创建提示对象工具*/
tiptool=gtk_tooltips_new();
/*添加提示信息到按钮*/
gtk_tooltips_set_tip(tiptool,entry2,"填写手机号",NULL);
/*设置标签,用于显示状态信息*/
box=gtk_hbox_new(FALSE,0);
gtk_box_pack_start(GTK_BOX(vbox),box,FALSE,FALSE,80);
gtk_widget_show(box);
label=gtk_label_new("");
gtk_label_set_justify (GTK_LABEL(label),GTK_JUSTIFY_CENTER);
gtk_box_pack_start(GTK_BOX(box),label,TRUE,FALSE,20);
gtk_widget_show(label);
box=gtk_hbox_new(FALSE,0);
gtk_box_pack_end(GTK_BOX(vbox),box,FALSE,FALSE,20);
gtk_widget_show(box);
button=gtk_button_new_with_label("人脸识别");
gtk_box_pack_start(GTK_BOX(box),button,TRUE,TRUE,0);
g_signal_connect(G_OBJECT(button),"clicked",G_CALLBACK(button_callback),"1");
gtk_widget_show(button);
box=gtk_hbox_new(FALSE,0);
gtk_box_pack_end(GTK_BOX(vbox),box,FALSE,FALSE,20);
gtk_widget_show(box);
button=gtk_button_new_with_label("人脸注册");
gtk_box_pack_start(GTK_BOX(box),button,TRUE,TRUE,0);
g_signal_connect(G_OBJECT(button),"clicked",G_CALLBACK(button_callback),"2");
gtk_widget_show(button);
gtk_widget_show_all(window);
return FALSE;//返回FALSE结束定时器
}
g_print("摄像头打开失败ret=%d\n",ret);
gtk_label_set_markup(GTK_LABEL(widget),"摄像头启动失败,请检测设备是否正常!");
return TRUE;//返回TURE继续触发定时器
}
将摄像头采集的图像进行Base64编码,然后调用百度AI接口进行人脸注册和人脸比对。
1.百度智能云接口及简介 https://cloud.baidu.com/product/face
/*按钮处理函数*/
void button_callback(GtkWidget *widget,gpointer data)
{
gint res=0;
gchar format_data[512];
gchar *p=(gchar *)data;
guint size=(height*width*3)*8/6;
gchar *imagebase64=malloc(size);/*base64编码后数据*/
if(strcmp(p,"1"))//人脸注册
{
g_print("人脸注册\n");
/*保证线程安全,上锁*/
gdk_threads_enter();
memcpy(imagebase64,imagebase64_data,size);
/*保证线程安全,释放*/
gdk_threads_leave();
if(user_info.name[0]=='\0' || user_info.phone[0]=='\0' || strlen(user_info.phone)!=11)
{
//g_print("请正常填写名字手机号码\n");
gtk_label_set_markup(GTK_LABEL(label),"请正常填写名字\n手机号码");
//开启定时器1s时间
time_flag=g_timeout_add(1000,timer_func2,label);
free(imagebase64);
return ;
}
/*人脸注册*/
g_print("phone:%s,name:%s\n",user_info.phone,user_info.name);
snprintf(format_data,512,"\"group_id\":\"wbyq\",\"user_id\":\"%s\",\"user_info\":\"%s\",\"quality_control\":\"LOW\",\"liveness_control\":\"NORMAL\"}",user_info.phone,user_info.name);
g_print("format=%s\n",format_data);
faceDetect(request_url,access_token,imagebase64,format_data);
if(res)/*访问服务器失败*/
{
gtk_label_set_markup(GTK_LABEL(label),"注册失败");
}
/*
{"error_code":0,"error_msg":"SUCCESS","log_id":9465001899925,"timestamp":1641801995,"cached":0,"result":{"face_token":"66d2d1512ec57835ec4ef25ed95bec77",
"location":{"left":265.38,"top":251.49,"width":201,"height":192,"rotation":0}}}
*/
else if(JsonData_analysis(platform_data,"\"error_msg\"",format_data))/*查找“error_msg”标志失败*/
{
gtk_label_set_markup(GTK_LABEL(label),"注册失败");
}
else if(strcmp(format_data,"SUCCESS"))/*返回状态标志失败*/
{
gtk_label_set_markup(GTK_LABEL(label),"注册失败");
}
else gtk_label_set_markup(GTK_LABEL(label),"注册成功");
}
else if(strcmp(p,"2"))//人脸识别
{
if(time_flag)g_source_remove(time_flag);//关闭定时器
/*保证线程安全,上锁*/
gdk_threads_enter();
memcpy(imagebase64,imagebase64_data,size);
/*保证线程安全,释放*/
gdk_threads_leave();
snprintf(format_data,sizeof(format_data),"\"group_id_list\":\"wbyq\",\"quality_control\":\"LOW\",\"liveness_control\":\"NORMAL\"}");
res=faceDetect(face_search,access_token,imagebase64,format_data);//人脸比对
if(res==0)
{
struct USER user_data;
if(Josn_faceSearch(platform_data,&user_data))
{
//g_print("比对失败\n");
gtk_label_set_markup(GTK_LABEL(label),"请重试");
gtk_entry_set_text(GTK_ENTRY(entry1),"");
gtk_entry_set_text(GTK_ENTRY(entry2),"");
}
else
{
//g_print("比对成功:%s\n",format_data);
gtk_label_set_markup(GTK_LABEL(label),"识别成功");
gtk_entry_set_text(GTK_ENTRY(entry1),user_data.name);
gtk_entry_set_text(GTK_ENTRY(entry2),user_data.phone);
}
}
}
free(imagebase64);
}
Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。可查看RFC2045~RFC2049,上面有MIME的详细规范。
Base64编码是从二进制到字符的过程,可用于在HTTP环境下传递较长的标识信息。采用Base64编码具有不可读性,需要解码后才能阅读。
Base64由于以上优点被广泛应用于计算机的各个领域,然而由于输出内容中包括两个以上“符号类”字符(+, /, =),不同的应用场景又分别研制了Base64的各种“变种”。为统一和规范化Base64的输出,Base62x被视为无符号化的改进版本。
Base64要求把每三个8Bit的字节转换为四个6Bit的字节(38 = 46 = 24),然后把6Bit再添两位高位0,组成四个8Bit的字节,也就是说,转换后的字符串理论上将要比原来的长1/3。
static const char * base64char = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
char * base64_encode( const unsigned char * bindata, char * base64, int binlength )
{
int i, j;
unsigned char current;
for ( i = 0, j = 0 ; i < binlength ; i += 3 )
{
current = (bindata[i] >> 2) ;
current &= (unsigned char)0x3F;
base64[j++] = base64char[(int)current];
current = ( (unsigned char)(bindata[i] << 4 ) ) & ( (unsigned char)0x30 ) ;
if ( i + 1 >= binlength )
{
base64[j++] = base64char[(int)current];
base64[j++] = '=';
base64[j++] = '=';
break;
}
current |= ( (unsigned char)(bindata[i+1] >> 4) ) & ( (unsigned char) 0x0F );
base64[j++] = base64char[(int)current];
current = ( (unsigned char)(bindata[i+1] << 2) ) & ( (unsigned char)0x3C ) ;
if ( i + 2 >= binlength )
{
base64[j++] = base64char[(int)current];
base64[j++] = '=';
break;
}
current |= ( (unsigned char)(bindata[i+2] >> 6) ) & ( (unsigned char) 0x03 );
base64[j++] = base64char[(int)current];
current = ( (unsigned char)bindata[i+2] ) & ( (unsigned char)0x3F ) ;
base64[j++] = base64char[(int)current];
}
base64[j] = '\0';
return base64;
}
int base64_decode( const char * base64, unsigned char * bindata )
{
int i, j;
unsigned char k;
unsigned char temp[4];
for ( i = 0, j = 0; base64[i] != '\0' ; i += 4 )
{
memset( temp, 0xFF, sizeof(temp) );
for ( k = 0 ; k < 64 ; k ++ )
{
if ( base64char[k] == base64[i] )
temp[0]= k;
}
for ( k = 0 ; k < 64 ; k ++ )
{
if ( base64char[k] == base64[i+1] )
temp[1]= k;
}
for ( k = 0 ; k < 64 ; k ++ )
{
if ( base64char[k] == base64[i+2] )
temp[2]= k;
}
for ( k = 0 ; k < 64 ; k ++ )
{
if ( base64char[k] == base64[i+3] )
temp[3]= k;
}
bindata[j++] = ((unsigned char)(((unsigned char)(temp[0] << 2))&0xFC)) |
((unsigned char)((unsigned char)(temp[1]>>4)&0x03));
if ( base64[i+2] == '=' )
break;
bindata[j++] = ((unsigned char)(((unsigned char)(temp[1] << 4))&0xF0)) |
((unsigned char)((unsigned char)(temp[2]>>2)&0x0F));
if ( base64[i+3] == '=' )
break;
bindata[j++] = ((unsigned char)(((unsigned char)(temp[2] << 6))&0xF0)) |
((unsigned char)(temp[3]&0x3F));
}
return j;
}
JSON(JavaScript Object Notation, JS对象简谱)是一种轻量级的数据交换格式。它基于 ECMAScript(European Computer Manufacturers Association, 欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
JSON是一个标记符的序列。这套标记符包含六个构造字符、字符串、数字和三个字面名。
JSON是一个序列化的对象或数组。
/******************json数据解析****************
形参:u8* buff原始数据
u8 *flag数据标志
u8 *data解析获取到的数据
返回值:0---成功,其他值---失败
************************************************/
guchar JsonData_analysis(guchar* buff,guchar *flag,guchar *data)
{
guchar *p=NULL;
guint i=0;
p=strstr((char *)buff,(char *)flag);
if(p)
{
p+=strlen((char *)flag)+2;
i=0;
while(*p!='\"' && *p!='\0')
{
data[i++]=*p++;
}
data[i]='\0';
return 0;
}
else return -1;
}
/*
josn数据解析
buff --要解析的内
user_info--解析成功返回用户名和id
返回值:成功返回0,失败返回其它值
*/
int Josn_faceSearch(const char *buff,struct USER *user_info)
{
/*
data={"error_code":0,"error_msg":"SUCCESS","log_id":2014555550012,"timestamp":1641792320,"cached":0,
"result":{"face_token":"6473abd658b76d6843de9a0820aeb2d2","user_list":[{"group_id":"wbyq","user_id":"18172864683","user_info":"\u738b\u65b0\u6c34","score":99.232490539551}]}}
*/
/*创建cJSON对象*/
cJSON *root=cJSON_CreateObject();
char *p=strstr(buff,"{\"error_code\"");
root=cJSON_Parse(p);/*载入JSON数据*/
if(root==NULL)return -1;
/*2.解析字段*/
cJSON *item;
item=cJSON_GetObjectItem(root,"error_msg");
if(item)
{
printf("error_code:%s\n",item->valuestring);
if(strcmp(item->valuestring,"SUCCESS"))return -2;
}
item=cJSON_GetObjectItem(root,"result");
if(item)
{
item=cJSON_GetObjectItem(item,"user_list");
cJSON *array_item=cJSON_GetArrayItem(item,0);
if(array_item==NULL)return -1;
cJSON *obj=cJSON_GetObjectItem(array_item,"user_info");
if(obj)printf("info=%s\n",obj->valuestring);
strcpy(user_info->name,obj->valuestring);
obj=cJSON_GetObjectItem(array_item,"user_id");
strcpy(user_info->phone,obj->valuestring);
}
cJSON_Delete(root);//释放空间
return 0;
}
摄像头图像采集示例:https://blog.csdn.net/weixin_44453694/article/details/126488841