目录
- 一、步骤流程及相关介绍
- 1.流程
- 2.相关介绍
- 二、编写代码
- 三、总结
- 参考
一、步骤流程及相关介绍
1.流程
2.相关介绍
相关准备:
dlib:
链接
https://pan.baidu.com/s/1ZmrMD7rk4VGZ4x6KHfSa6w
提取码:1713
68个人脸特征点:
链接:https://pan.baidu.com/s/1Ys3gI03GQOGudfJ5whnBzA
提取码:0526
图片集:
https://pan.baidu.com/s/1_A5gM64_dPI3offCMInhMw
提取码:1713
HOG介绍
HOG是Histogram of Oriented Gradient的缩写,是一种在计算机视觉和图像处理中用来进行目标检测的特征描述子。可结合OPENCV的SVM分类器等用于图像的识别。
特征提取流程:
HOG特征提取流程可分为6个部分:检测窗口、归一化图像、计算梯度、统计直方图、梯度直方图归一化、得到HOG特征向量,以下分步骤介绍。
(1)检测窗口:
HOG通过窗口(window)和块(block)将图像进行分割。通过以细胞(cell)为单位,对图像某一区域的像素值进行数学计算处理。在此先介绍窗口(window)、块(block)和细胞(cell)的概念及之间的联系。
- 窗口(window):将图像按一定大小分割成多个相同的窗口,滑动。
- 块(block):将每个窗口按一定大小分割成多个相同的块,滑动。
- 细胞(cell):将每个窗口按一定大小分割成多个相同的细胞,属于特征提取的单元,静止不动。
- 图像(image)->检测窗口(win)->图像块(block)->细胞单元(cell)
(2)归一化图像:
归一化分为gamma空间和颜色空间归一化。为减少光照因素影响,将整个图像进行规范化(归一化)。(归一化公式:y=(x-MinValue)/(MaxValue-MinValue))。归一化同时可以避免在图像的纹理强度中,局部的表层曝光贡献度的比重较大的情况。标准化Gamma压缩公式:I(x,y)=I(x,y)^gamma. gamma根据自己效果取值,如1/2.
(3)计算梯度:
计算图像横坐标和纵坐标方向的梯度,并根据横坐标和纵坐标的梯度,计算梯度方向。下图为计算公式图:
在算法中,常先用[-1,0,1]进行卷积操作求得x方向的梯度值,再采用[-1,0,1]T进行卷积操作求得y方向。而后采用上述公式求梯度幅值和方向。
(4)构建梯度直方图:
HOG构建方向梯度直方图在细胞(cell)中完成:
bins(可理解为划分的个数)决定方向的划分。一般bins取9,将梯度方向划分为9个区间。(注:关于划分区间,有些博主以360°计算。鄙人查opencv书籍,发现确应按180度进行计算,artan所得值得范围即为180°。)例如,假设一个细胞尺寸为6*6,则对这个细胞内的36个像素点,先判断像素点梯度方向所属的区间,后根据像素点的梯度幅值大小和梯度方向的大小进行加权于对应的梯度方向区间。(加权方法可有线性加权、平方根等等各种高大尚的加权方法)以下是按照9个区间,进行角度划分的图像。
(5)块内进行细胞归一化梯度直方图。
原因:局部光照的变化及前景-背景对比度的变化,使梯度强度的变化范围很大,在此需要进行归一化,(查资料,使用的归一化函数有L2-norm、L2-Hys、L1-norm于L1-sqrt等,O(∩_∩)O,没听过?没办法,谁让你那么菜呢???)进一步对光照、阴影和边缘进行压缩。根据上述介绍,把
(6)生成HOG特征向量:
最后组合所有的块,生成特征向量:例对于一个64128的窗口而言,每88的像素组成一个cell,每22个cell组成一个块,每个块有94个特征,以8个像素为步长,水平方向将有7个扫描窗口,垂直方向将有15个扫描窗口。所以,一个64128的窗口共367*15=3780个特征,代码中一个hog描述子针对一个检测窗口。
HOG加权方法:
HOG采用三线性加权法,有两个部分用到加权,分别是构建梯度直方图和细胞归一化。以下分别介绍:
在构建梯度直方图时,假若一个像素点的梯度方向为25°,距离020°和2040°最近,采用加权方法,对相邻两个区间进行幅度值相加,分别为(25-10)/20=0.75和(25-20)/20=0.25的权重值进行累加。
同时,在收集块内梯度方向直方图时,存在一个既定假设,即位于不同细胞内的像素点只会对其从属的细胞进行投影,并不会对其周围的细胞产生影响。显然,若对于细胞交界处的像素点和在块滑动情况下,这样的假设未免显得有点牵强,因为它们与其周围所有的细胞都是相关的。
HOG算法优缺点:
优点:
(1)HOG表示边缘的结构特征,可以描述局部的形状信息。
(2)位置和方向空间的量化一定程度上可以抑制平移和旋转带来的影响。
(3)采用归一化,可以抵消光照带来的变化
缺点:
(1)描述子生成过程冗长,维度较高
(2)很难处理遮挡问题。
(3)对噪点敏感
二、编写代码
版本:
python 3.8 + opencv 4.0.1
导包(环境内没有的包自己pip install xx下载):
python"># 导入包
import numpy as np
import cv2
import dlib
import random#构建随机测试集和训练集
from sklearn.svm import SVC #导入svm
from sklearn.svm import LinearSVC #导入线性svm
from sklearn.pipeline import Pipeline #导入python里的管道
import os
import joblib#保存模型
from sklearn.preprocessing import StandardScaler,PolynomialFeatures #导入多项式回归和标准化
import tqdm
定义路径:
python">folder_path='C:/Users/86150/Downloads/genki4k (1)/genki4k/'#这里直接写的绝对路径
label='labels.txt'#标签文件
pic_folder='files/'#图片文件路径
获得默认的人脸检测器和训练好的人脸68特征点检测器:
python">#获得默认的人脸检测器和训练好的人脸68特征点检测器
def get_detector_and_predicyor():
#使用dlib自带的frontal_face_detector作为我们的特征提取器
detector = dlib.get_frontal_face_detector()
"""
功能:人脸检测画框
参数:PythonFunction和in Classes
in classes表示采样次数,次数越多获取的人脸的次数越多,但更容易框错
返回值是矩形的坐标,每个矩形为一个人脸(默认的人脸检测器)
"""
#返回训练好的人脸68特征点检测器
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')
return detector,predictor
#获取检测器
detector,predictor=get_detector_and_predicyor()
定义截取面部的函数:
python">def cut_face(img,detector,predictor):
#截取面部
img_gry=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
rects = detector(img_gry, 0)
if len(rects)!=0:
mouth_x=0
mouth_y=0
landmarks = np.matrix([[p.x, p.y] for p in predictor(img,rects[0]).parts()])
for i in range(47,67):#嘴巴范围
mouth_x+=landmarks[i][0,0]
mouth_y+=landmarks[i][0,1]
mouth_x=int(mouth_x/20)
mouth_y=int(mouth_y/20)
#裁剪图片
img_cut=img_gry[mouth_y-20:mouth_y+20,mouth_x-20:mouth_x+20]
return img_cut
else:
return 0#检测不到人脸返回0
定义提取特征值的函数:
python">#提取特征值
def get_feature(files_train,face,face_feature):
for i in tqdm.tqdm(range(len(files_train))):
img=cv2.imread(folder_path+pic_folder+files_train[i])
cut_img=cut_face(img,detector,predictor)
if type(cut_img)!=int:
face.append(True)
cut_img=cv2.resize(cut_img,(64,64))
#padding:边界处理的padding
padding=(8,8)
winstride=(16,16)
hogdescrip=hog.compute(cut_img,winstride,padding).reshape((-1,))
face_feature.append(hogdescrip)
else:
face.append(False)#没有检测到脸的
face_feature.append(0)
定义筛选函数:
python">def filtrate_face(face,face_feature,face_site): #去掉检测不到脸的图片的特征并返回特征数组和相应标签
face_features=[]
#获取标签
label_flag=[]
with open(folder_path+label,'r') as f:
lines=f.read().splitlines()
#筛选出能检测到脸的,并收集对应的label
for i in tqdm.tqdm(range(len(face_site))):
if face[i]:#判断是否检测到脸
#pop之后要删掉当前元素,后面的元素也要跟着前移,所以每次提取第一位就行了
face_features.append(face_feature.pop(0))
label_flag.append(int(lines[face_site[i]][0]))
else:
face_feature.pop(0)
datax=np.float64(face_features)
datay=np.array(label_flag)
return datax,datay
定义多项式SVM:
python">def PolynomialSVC(degree,c=10):#多项式svm
return Pipeline([
# 将源数据 映射到 3阶多项式
("poly_features", PolynomialFeatures(degree=degree)),
# 标准化
("scaler", StandardScaler()),
# SVC线性分类器
("svm_clf", LinearSVC(C=10, loss="hinge", random_state=42,max_iter=10000))
])
训练函数:
python">def train(files_train,train_site):#训练
'''
files_train:训练文件名的集合
train_site :训练文件在文件夹里的位置
'''
#是否检测到人脸
train_face=[]
#人脸的特征数组
train_feature=[]
#提取训练集的特征数组
get_feature(files_train,train_face,train_feature)
#筛选掉检测不到脸的特征数组
train_x,train_y=filtrate_face(train_face,train_feature,train_site)
svc=PolynomialSVC(degree=1)
svc.fit(train_x,train_y)
return svc#返回训练好的模型
测试函数:
python">def test(files_test,test_site,svc):#预测,查看结果集
'''
files_train:训练文件名的集合
train_site :训练文件在文件夹里的位置
'''
#是否检测到人脸
test_face=[]
#人脸的特征数组
test_feature=[]
#提取训练集的特征数组
get_feature(files_test,test_face,test_feature)
#筛选掉检测不到脸的特征数组
test_x,test_y=filtrate_face(test_face,test_feature,test_site)
pre_y=svc.predict(test_x)
ac_rate=0
for i in range(len(pre_y)):
if(pre_y[i]==test_y[i]):
ac_rate+=1
ac=ac_rate/len(pre_y)*100
print("准确率为"+str(ac)+"%")
return ac
构建HOG特征提取器:
python">#设置hog的参数
winsize=(64,64)
blocksize=(32,32)
blockstride=(16,16)
cellsize=(8,8)
nbin=9
#定义hog
hog=cv2.HOGDescriptor(winsize,blocksize,blockstride,cellsize,nbin)
#获取文件夹里有哪些文件
files=os.listdir(folder_path+pic_folder)
使用10-fold cross validation,就是把数据集中随机的9/10做为训练集,剩下的1/10做为测试集,进行十次:
python">ac=float(0)
for j in range(10):
site=[i for i in range(4000)]
#训练所用的样本所在的位置
train_site=random.sample(site,3600)
#预测所用样本所在的位置
test_site=[]
for i in range(len(site)):
if site[i] not in train_site:
test_site.append(site[i])
files_train=[]
#训练集,占总数的十分之九
for i in range(len(train_site)):
files_train.append(files[train_site[i]])
#测试集
files_test=[]
for i in range(len(test_site)):
files_test.append(files[test_site[i]])
svc=train(files_train,train_site)
ac=ac+test(files_test,test_site,svc)
save_path='C:/Users/86150/JupyterProject/source/model/smile'+str(j)+'(hog).pkl'
joblib.dump(svc,save_path)
ac=ac/10
print("平均准确率为"+str(ac)+"%")
检测结果(一部分):
检测函数:
python">def test1(files_test,test_site,svc):#预测,查看结果集
'''
files_train:训练文件名的集合
train_site :训练文件在文件夹里的位置
'''
#是否检测到人脸
test_face=[]
#人脸的特征数组
test_feature=[]
#提取训练集的特征数组
get_feature(files_test,test_face,test_feature)
#筛选掉检测不到脸的特征数组
test_x,test_y=filtrate_face(test_face,test_feature,test_site)
pre_y=svc.predict(test_x)
tp=0
tn=0
for i in range(len(pre_y)):
if pre_y[i]==test_y[i] and pre_y[i]==1:
tp+=1
elif pre_y[i]==test_y[i] and pre_y[i]==0:
tn+=1
f1=2*tp/(tp+len(pre_y)-tn)
print(f1)
加载刚刚保存本地模型然后调用检测函数看一下结果:
python">svc7=joblib.load('C:/Users/86150/JupyterProject/source/model/smile9(hog).pkl')
site=[i for i in range(4000)]
#训练所用的样本所在的位置
train_site=random.sample(site,3600)
#预测所用样本所在的位置
test_site=[]
for i in range(len(site)):
if site[i] not in train_site:
test_site.append(site[i])
#测试集
files_test=[]
for i in range(len(test_site)):
files_test.append(files[test_site[i]])
test1(files_test,test_site,svc7)
调用模型来检测,定义一个笑脸检测函数,输入图片直接得到预测结果:
python">def smile_detector(img,svc):
cut_img=cut_face(img,detector,predictor)
a=[]
if type(cut_img)!=int:
cut_img=cv2.resize(cut_img,(64,64))
#padding:边界处理的padding
padding=(8,8)
winstride=(16,16)
hogdescrip=hog.compute(cut_img,winstride,padding).reshape((-1,))
a.append(hogdescrip)
result=svc.predict(a)
a=np.array(a)
return result[0]
else :
return 2
图片检测实例:
python">##图片检测
pic_path='C:/Users/86150/JupyterProject/source/picture/test3.jpg'
img=cv2.imread(pic_path)
result=smile_detector(img,svc7)
if result==1:
img=cv2.putText(img,'smile',(21,50),cv2.FONT_HERSHEY_COMPLEX,2.0,(0,255,0),1)
elif result==0:
img=cv2.putText(img,'no smile',(21,50),cv2.FONT_HERSHEY_COMPLEX,2.0,(0,255,0),1)
else:
img=cv2.putText(img,'no face',(21,50),cv2.FONT_HERSHEY_COMPLEX,2.0,(0,255,0),1)
cv2.imshow('test', img)
cv2.waitKey(0)
摄像头实时检测并保持,按s键保存刚刚的识别的图片10张,按esc退出:
python">camera = cv2.VideoCapture(0)#打开摄像头
ok=True
flag=0
ss='000000'
# 打开摄像头 参数为输入流,可以为摄像头或视频文件
while ok:
ok,img = camera.read()
# 转换成灰度图像
result=smile_detector(img,svc7)
if result==1:
img=cv2.putText(img,'smile',(21,50),cv2.FONT_HERSHEY_COMPLEX,2.0,(0,255,0),1)
elif result==0:
img=cv2.putText(img,'no smile',(21,50),cv2.FONT_HERSHEY_COMPLEX,2.0,(0,255,0),1)
else:
img=cv2.putText(img,'no face',(21,50),cv2.FONT_HERSHEY_COMPLEX,2.0,(0,255,0),1)
cv2.imshow('test', img)
k = cv2.waitKey(1)
if k == 27: # press 'ESC' to quit
break
elif k==115:
for i in range(10):
pic_save_path='C:/Users/86150/JupyterProject/source/picture/result/'+str(flag)+ss+str(i)+'.jpg' #给图片编号以00000000起
cv2.imwrite(pic_save_path,img)
flag+=1
camera.release()
cv2.destroyAllWindows()
三、总结
使用这种方法准确度只有85%左右,不算高,会有一定误差。
参考
https://www.cnblogs.com/urglyfish/articles/12417343.html
https://blog.csdn.net/junseven164/article/details/121831011