OpenCV实验(7):人脸面部识别
文章目录
- 一. 实验要求
- 二. 实验思路
- 1. 想到啥说啥
- 2. 算法原理
- 3. 识别流程
- 4. 详细步骤
- 三. 实验效果
- 四. 实验代码
一. 实验要求
二. 实验思路
1. 想到啥说啥
这个实验叫 “人脸检测” 更为准确一些。
做这个实验总共花了整3天时间,第一天只搜资料,第二天看论文写代码,第三天看论文改进代码。我一开始就决定不使用任何带机器学习和深度学习的算法和代码,那样实在没什么意思,而且检查的时候也不好给助教解释怎么实现的。
OpenCV的三种人脸检测方法,Eigenfaces、Fisherfaces、Local Binary Patterns Histograms(LBPH),都用到了人脸数据进行训练,但看完后还是挺有意思的https://docs.opencv.org/master/da/d60/tutorial_face_main.html。
关于各种算法这篇知乎里说的就很全了https://zhuanlan.zhihu.com/p/36621308。最后看了各种,还是选择了肤色模型的这类算法。做实验时参考了这位学长的博客https://blog.csdn.net/qq_41748260/article/details/103992272。把实验做完也相当于一次复习。
2. 算法原理
肤色模型的原理这部分学长的博客里说的很清楚了,就不重复了。
3. 识别流程
4. 详细步骤
按照顺序说一下步骤
边缘灰度增强是希望边缘更清晰,代码中是 edgeEnhance() 函数。
光照补偿用了GrayWorld算法消除光照环境对颜色显现的影响,代码中是 lightCompensation() 函数。
边缘灰度增强和光照补偿构成了预处理1,代码中是 preTreat() 函数。
在预处理1后就是人脸检测函数,检测人脸并框定,代码中是 faceDetection() 函数。
原图:
预处理后:
在 faceDetection() 中,先转到YCrCb空间,接着进行二值化,目的是保留肤色部分,二值化的操作与学长博客中的一样,只是充分利用了numpy处理,去掉循环进行加速。形态学处理先后进行开运算和闭运算,这样会更光滑饱满。连通区域标记,主要是方便后续处理,即对每一个连通域进行判断和处理。标记后,判断是否足够大,排除太小的连通域,接着就根据“三庭五眼”的比例保留符合的连通域 (到这里就已经找到了脸部的候选区域) ,最后判断该连通域内是否含有眼睛,有则框定。这就是满足了实验要求,三庭五眼和眼部特征。(在第一幅图中检测到4个人脸候选区,在第二幅图中检测到2个人脸候选区)
连通域标记后:
判断是否含有眼睛,代码中是 eyeJudge() 函数。这一部分就是走流程图中右边的部分。在确定人脸候选区后,我们的目标就变为人眼检测。根据确定的人脸候选区,我们可以在原始图像中确定相同区域,并对这个区域进行人眼检测。
在 eyeJudge() 人眼检测中我们分两步进行。第一步通过灰度积分投影对人眼进行粗略定位,第二步通过Hough对人眼进行精确定位。
第一步粗略定位中,先对原始图像采用 Sobel 算子提取水平边缘,这是因为正脸中,人的眼睛是水平的,效果更好,代码中是 sobelEdge() 函数。提取边缘后,对边缘采用大津法Otsu进行二值处理。接着分别进行垂直积分投影和水平积分投影,垂直灰度积分投影就是计算每一列黑色像素的数量,水平灰度积分投影同理是对每一行。因为脸颊部分不含有器官,因此黑色占比低,在垂直积分投影曲线中表现是两个最低谷,两个最低谷的范围就是水平方向上两个眼睛的大概位置。水平积分投影中,额头部分即眼睛眉毛上面的部分黑色像素少,眼睛眉毛下面且在嘴巴上面的区域同样黑色像素少,这两部分在水平积分投影曲线中是两个低谷,但由于有的人额头被头发遮挡,因此表现的不明显,所以,我采用的是根据曲线前60%区域(一般都在曲线前60%的区域)中的最低谷设为眼睛与嘴巴之间的位置,那么眼睛就在该最低谷左右1个低谷范围内,这就在垂直方向上确定了眼睛的位置。实现了粗略定位。(耿新,周志华,陈世福在《基于混合投影函数的眼睛定位》中提到的混合积分投影或许会有更好的效果)
4幅人脸的图和灰度积分图(合在一张图中):
右侧的是水平积分投影,下面的是垂直积分投影。
下面是上面4张人脸中的第2个的灰度积分曲线:
水平灰度积分(波谷即极小值,波峰即极大值已标注):
第一个波谷对应着额头,第二个波谷对应着眼睛和鼻子之间的位置。
垂直灰度积分(波谷即极小值已标注):
两侧脸颊对应着两个波谷
在第二步精确定位中,再用Hough变换检测圆,因为我发现在粗略定位后,人眼区域表现的是密集的黑色像素,检测圆也就相当于检测这些黑色像素,因此也就达到了眼睛检测的目的。如果检测到的圆的数量大于等于2,我们就返回True,并在结果中绘制圆心和圆。在Hough变换中,我设置了检测圆的最大半径,因此针对这个实验是没问题的,但对其他人脸还是存在问题。(上面的4张人脸图可以看到眼睛部位的黑色像素十分密集)
在学长的博客中对人眼检测的判断标准是,人脸候选区是否有满足一定要求的孔洞。只要有即判断为人脸。
至此就实现了只针对该实验的人脸检测,不具有泛化能力。
(还是机器学习或者深度学习好~~~)
三. 实验效果
四. 实验代码
下面这个是完整的代码,代码注释中给出了完整详细的解释
再说一遍,代码只针对该实验,不具有泛化能力。
import numpy as np
import cv2 as cv
from skimage import measure, color
import scipy
import matplotlib.pyplot as pltdef preTreat(imgSrc):"""通过图像边缘灰度增强和光照补偿对原图像进行预处理:param imgSrc: 输入图像:return: 预处理后的图像"""imgDst = edgeEnhance(imgSrc) # 边缘增强imgDst = lightCompensation(imgDst) # 光照补偿return imgDstdef edgeEnhance(imgSrc):"""图像边缘灰度增强:param imgSrc: 输入图像:return: 边缘灰度增强图像"""imgSrcGauss = cv.GaussianBlur(imgSrc, (3, 3), 0) # 对原图像进行高斯平滑imgSrcSharp = cv.Laplacian(imgSrcGauss, cv.CV_16S) # 计算拉普拉斯imgSrcSharp = cv.convertScaleAbs(imgSrcSharp) # 计算绝对值,并将结果转换无符号8位类型imgDst = cv.add(imgSrc, imgSrcSharp) # 原图像与边缘图像相加return imgDstdef lightCompensation(imgSrc):"""用GrayWorld算法进行光照补偿:param imgSrc: 输入图像:return: 光照补偿后的图像"""rows, cols, channels = imgSrc.shapeavgRGB = np.sum(np.sum(imgSrc, axis=0), axis=0) / (rows * cols) # 计算每个通道的平均灰度值avgGray = np.sum(avgRGB) / channels # 三通道的总体平均灰度值avgCoeff = avgGray / avgRGB # 三通道的增益系数imgDst = np.zeros(imgSrc.shape, dtype=np.uint8)imgTemp = imgSrc * avgCoeffimgDst[:, :, :] = np.minimum(imgTemp, 255)# cv.imshow('lightCompensation', imgDst)return imgDstdef faceDetection(imgSrc, imgSrcPreTreate):"""人脸检测:param imgSrc: 原始图像:param imgSrcPreTreate: 预处理后的图像:return: 人脸检测后的图像"""imgSrcYCrCb = cv.cvtColor(imgSrcPreTreate, cv.COLOR_BGR2YCR_CB)imgSrcYCrCb = cv.GaussianBlur(imgSrcYCrCb, (5, 5), 0)imgSrcYCrCbVice = imgSrcYCrCb # 二值化时 imgSrcYCrCb 被改变了,这里保留个副本,万一以后有用呢rows, cols, channels = imgSrcYCrCb.shape# 二值化imgSrcYCrCb[imgSrcYCrCb[:, :, 0] < 70] = 0imgTemp = np.zeros((rows, cols), dtype=np.uint8)imgTempCr = np.zeros((rows, cols), dtype=np.uint8)imgTemp[133 <= imgSrcYCrCb[:, :, 1]] = 1imgTempCr[imgSrcYCrCb[:, :, 1] <= 173] = 1imgTempCr = np.multiply(imgTempCr, imgTemp)imgTemp[:, :] = 0imgTempCb = np.zeros((rows, cols), dtype=np.uint8)imgTemp[77 <= imgSrcYCrCb[:, :, 2]] = 1imgTempCb[imgSrcYCrCb[:, :, 2] <= 127] = 1imgTempCb = np.multiply(imgTempCb, imgTemp)imgBinary = np.multiply(imgTempCr, imgTempCb)imgBinary[imgBinary == 1] = 255imgBinary = cv.convertScaleAbs(imgBinary)# cv.imshow('imgBinary', imgBinary)# 形态学处理kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (5, 5)) # 结构元faceMorph = cv.morphologyEx(imgBinary, cv.MORPH_OPEN, kernel) # 开运算# cv.imshow('faceMorphOpen', faceMorph)faceMorph = cv.morphologyEx(faceMorph, cv.MORPH_CLOSE, kernel) # 闭运算# cv.imshow('faceMorphClose', faceMorph)# 连通区域标记faceLabel = measure.label(faceMorph, connectivity=2) # 八邻域标记# faceLabelColor = color.label2rgb(faceLabel, bg_label=0) # 根据不同标记显示不同的颜色,更清晰看到不同连通域# cv.imshow('faceLabelColor', faceLabelColor)# 人脸“三庭五眼”的值进行筛选 从0.6到2countFaces = 0 # 人脸总数count = 0 # 符合的连通域总数imgDst = imgSrc.copy()for region in measure.regionprops(faceLabel): # measure.regionprops()测量标记的图像区域的属性minRow, minCol, maxRow, maxCol = region.bbox # 外接边界框的坐标if (maxCol - minCol) / rows > 1 / 20 and (maxRow - minRow) / cols > 1 / 15: # 检测足够大的连通区域ratio = (maxRow - minRow) / (maxCol - minCol) # 计算“三庭五眼”的值if 0.6 < ratio < 2.0: # 符合条件的ratiocount += 1# 下面注释掉的是做实验时方便,可以逐个查看指定连通域的结果# 对于Oracle1.jpg,count从1到4,Oracle2.jpg,count从1到2# if count == 3:# eyeJudge(region, faceMorph, imgSrc, imgDst)if eyeJudge(region, faceMorph, imgSrc, imgDst): # 人眼检测countFaces += 1imgDst = cv.rectangle(imgDst, (minCol, minRow), (maxCol, maxRow), (0, 255, 0), 2)# print('maybe faces: ', count)# print('faces: ', countFaces)return imgDstdef eyeJudge(region, faceMorph, imgSrc, imgDst):"""人眼检测:param region: 人脸候选区:param faceMorph: 形态学处理的图像:param imgSrc: 原始图像:param imgDst: 人脸检测的结果图像:return: 包含人眼返回True,反之返回False"""minRow, minCol, maxRow, maxCol = region.bbox # region 连通域外接边界框的坐标,也就是人脸候选区regionFace = faceMorph[minRow:maxRow, minCol:maxCol] # 废弃不用,效果不好,用后面的 regionThreshold 代替# 眼睛粗略定位imgGray = sobelEdge(imgSrc) # Sobel得到边缘_, imgThreshold = cv.threshold(imgGray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU) # 对边缘采用大津法二值处理# 下面的动态阈值废弃,效果不好# imgThreshold = cv.adaptiveThreshold(imgGray, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 11, 2)# cv.imshow('imgThreshold', imgThreshold)regionThreshold = imgThreshold[minRow:maxRow, minCol:maxCol] # 对应的人脸候选区rows, cols = regionThreshold.shape# 垂直积分投影numVerBlack = np.zeros([cols], dtype=np.int) # 黑色像素值的数量for i in range(cols):numVerBlack[i] = rows - np.sum(regionThreshold[:, i]) // 255 # 白色值为255,求和除以255就是白色的数量,做差就是黑色的数量# 水平积分投影numHorBlack = np.zeros([rows], dtype=np.int) # 黑色像素值的数量for i in range(rows):numHorBlack[i] = cols - np.sum(regionThreshold[i, :]) // 255# 作图imgVer = np.zeros([rows, cols], dtype=np.uint8)imgVer[:, :] = np.array([255])for i in range(cols):imgVer[rows - numVerBlack[i]:, i] = 0imgHor = np.zeros([rows, cols], dtype=np.uint8)imgHor[:, :] = np.array([255])for i in range(rows):imgHor[i, cols - numHorBlack[i]:] = 0# 放在一起显示,如果不想显示,下面5句都可以注释掉# board = np.zeros([2*rows, 2*cols], dtype=np.uint8)# board[:rows, :cols] = regionThreshold# board[:rows, cols:] = imgHor# board[rows:, :cols] = imgVer# board[rows:, cols:] = np.array([255])# 下面的4个cv.imshow()中,推荐最后一个,等价于前面3个# cv.imshow('regionThreshold', regionThreshold)# cv.imshow('imgVer', imgVer)# cv.imshow('imgHor', imgHor)# cv.imshow('board', board) # 推荐这个,效果好# 进行定位# 对水平灰度积分投影进行函数拟合,并根据拟合的曲线求得极值点x = np.arange(rows)y = numHorBlackz1 = np.polyfit(x, y, 15) # 最小二乘法15次多项式拟合(调参)p1 = np.poly1d(z1) # 拟合后的公式yvals = p1(x)numPeaks = scipy.signal.find_peaks(yvals, distance=10) # 找极大值点# 找极小值点,在没找到直接找极小值点的函数比如find_valleys后,突然意识到yvals取反valley就变为peak了。。。numValleys = scipy.signal.find_peaks(-yvals, distance=10) # 找极小值点# 绘制曲线# plot1 = plt.plot(x, y, 'o', label='original')# plot2 = plt.plot(x, yvals, 'r', label='curve fit')# plt.xlabel('xaxis')# plt.ylabel('yaxis')# plt.legend(loc=1)# plt.title('numHorBlack')# for i in range(len(numPeaks[0])):# plt.plot(numPeaks[0][i], yvals[numPeaks[0][i]], '*', markersize=10)# for i in range(len(numValleys[0])):# plt.plot(numValleys[0][i], yvals[numValleys[0][i]], '*', markersize=10)# plt.show()minIndex60 = np.argmin(yvals[numValleys[0][numValleys[0] < rows*0.6]]) # rows*60%中的最低谷,根据投影图得到的针对该实验的个人结论verUp = numValleys[0][minIndex60-1] if minIndex60-1 >= 0 else 0 # 粗略定位得到的上边界,最低谷的左边波谷verDown = numValleys[0][minIndex60+1] if minIndex60+1 < rows else rows-1 # 粗略定位得到的下边界,最低谷的右边波谷# 对垂直灰度积分投影进行处理x = np.arange(cols)y = numVerBlackz1 = np.polyfit(x, y, 15) # 最小二乘法15次多项式拟合(调参)p1 = np.poly1d(z1)yvals = p1(x)numValleys = scipy.signal.find_peaks(-yvals, distance=10)# 绘制曲线,下面注释掉的是可以看到的绘图。# plot1 = plt.plot(x, y, 'o', label='original')# plot2 = plt.plot(x, yvals, 'r', label='curve fit')# plt.xlabel('xaxis')# plt.ylabel('yaxis')# plt.legend(loc=1)# plt.title('numVerBlack')# for i in range(len(numValleys[0])):# plt.plot(numValleys[0][i], yvals[numValleys[0][i]], '*', markersize=10)# plt.show()min1 = numValleys[0][np.argmin(yvals[numValleys[0]])] # 在波谷中找最小值yvals[min1] = 10000min2 = numValleys[0][np.argmin(yvals[numValleys[0]])] # 在波谷中找第二小的值verLeft = min(min1, min2) # 粗略定位得到的左边界verRight = max(min1, min2) # 粗略定位得到的右边界regionThreshold = regionThreshold[verUp:verDown, verLeft:verRight] # 粗略定位区域# 眼睛精确定位# 圆圈circles = cv.HoughCircles(regionThreshold, cv.HOUGH_GRADIENT, 1.5, 32, param1=200, param2=14, minRadius=1, maxRadius=8)"""参数设计记录:调参)1. param2 = 13,效果是Orical1.jpg中从左到右第三张脸的头发上有圈regionThreshold, cv.HOUGH_GRADIENT, 1.5, 32, param1=180, param2=13, minRadius=1, maxRadius=8 Waiting for you ..."""if circles is not None:circles = circles[0, :, :] # 提取为二维circles = np.uint16(np.around(circles)) # 四舍五入,取整for i in circles[:]:cv.circle(imgDst[:, :, :], (i[0] + minCol + verLeft, i[1] + minRow + verUp), i[2], (255, 0, 0), 3) # 画圆cv.circle(imgDst[:, :, :], (i[0] + minCol + verLeft, i[1] + minRow + verUp), 2, (255, 0, 0), 1) # 画圆心if len(circles[:]) >= 2: # 2个以上圆即认定为人脸return Truereturn Falsedef sobelEdge(image):"""Sobel算子边缘提取:param image: 输入图像:return: 处理后的图像"""blur = cv.GaussianBlur(image, (3, 3), 0) # 高斯去噪gray = cv.cvtColor(blur, cv.COLOR_BGR2GRAY) # 转灰度图像,尝试过采用直方图均衡化增加对比度但效果不好gradx = cv.Sobel(gray, cv.CV_16SC1, 1, 0)x = cv.convertScaleAbs(gradx) # 垂直边缘# cv.imshow('x', x)grady = cv.Sobel(gray, cv.CV_16SC1, 0, 1)y = cv.convertScaleAbs(grady) # 水平边缘# cv.imshow('y', y)result = cv.addWeighted(x, 0.5, y, 0.5, 0) # 整幅图的 result# cv.imshow('sobelResult', result)return y # 发现y的效果会更好,获得水平边缘。因为正脸中眼睛是水平的,因此效果会更好。返回 result 也可以自己尝试一下。strName = 'Orical1' # 图像名字
strType = '.jpg' # 图像类型
imgSrc = cv.imread('../images/images4/'+strName+strType) # 读取原始图像
imgSrcPreTreat = preTreat(imgSrc) # 对图像进行预处理
imgDst = faceDetection(imgSrc, imgSrcPreTreat) # 肤色提取
cv.imshow('imgSrc', imgSrc)
# cv.imshow('imgSrcTreat', imgSrcPreTreat)
cv.imshow('imgDst', imgDst)
# cv.imwrite(strName+'Final'+strType, imgDst)
cv.waitKey(0)
cv.destroyAllWindows()
OpenCV实验(7):人脸面部识别相关推荐
- c语言回溯实验报告,实验报告: 人脸识别方法回溯与实验分析 【OpenCV测试方法源码】...
实验报告: 人脸识别方法回顾与实验分析 [OpenCV测试方法源码] 趁着还未工作,先把过去做的东西整理下出来~ (涉及个人隐私,源码不包含测试样本,请谅解~) 对实验结果更感兴趣的朋友请直接看第5章 ...
- python+opencv+dlib实现人脸检测与表情识别
python+opencv+dlib实现人脸检测与表情识别 一,dlib简单介绍:Dlib包含广泛的机器学习算法.所有的设计都是高度模块化的,快速执行,并且通过一个干净而现代的C ++ API,使用起 ...
- 基于Python的OpenCV+TensorFlow+Keras人脸识别实现
前言:本节要讲的人脸识别主要是借鉴了 一位研究生前辈的文章 我只是在他的基础上进行了改动,让代码能在现在的TensorFlow2.X 等的环境下运行 先看一下效果图 完整工程及源代码请点击链接下载:人 ...
- 【毕业设计/课程设计】基于opencv的高精度人脸识别考勤系统设计与实现
文章目录 0 项目说明 1 需求分析 2 总体设计 3 详细设计 4 程序运行结果测试与分析 5 实验心得 6 项目源码 0 项目说明 基于opencv的高精度人脸识别考勤系统设计与实现 提示:适合用 ...
- 基于Opencv快速实现人脸识别(完整版)
上篇博客:https://blog.csdn.net/beyond9305/article/details/92844258严格来说标题是有误的,只是单纯地对人脸进行了检测,而并非识别,opencv内 ...
- OpenCV + python 实现人脸检测(基于照片和视频进行检测)
OpenCV + python 实现人脸检测(基于照片和视频进行检测) Haar-like 通俗的来讲,就是作为人脸特征即可. Haar特征值反映了图像的灰度变化情况.例如:脸部的一些特征能由矩形特征 ...
- OpenCV OMZ MTCNN人脸检测的实例(附完整代码)
OpenCV OMZ MTCNN人脸检测的实例 OpenCV OMZ MTCNN人脸检测的实例 OpenCV OMZ MTCNN人脸检测的实例 #include <algorithm> # ...
- OpenCV cv::CascadeClassifier人脸检测的实例(附完整代码)
OpenCV cv::CascadeClassifier人脸检测的实例 OpenCV cv::CascadeClassifier人脸检测的实例 OpenCV cv::CascadeClassifier ...
- 基于OpenCV的简单人脸识别系统
目录 1. 调用库函数 2. 调用摄像头并设置窗口 3. 设置图片正负样本数据集的路径 4. 调用人脸检测器 5. 正负样本载入 6.提取人脸区域 7. 建立LBPH人脸识别模型 8. 实时检测 9. ...
最新文章
- 查看Ubuntu 系统的版本
- Facebook、Netflix 等多家科技巨头谈“设计”
- 牛客小白月赛12 C	华华给月月出题 (积性函数,线性筛)
- 扩展Guava缓存溢出到磁盘
- Load Balance System
- dd大牛《背包九讲》(转载)
- 如何查看python的版本号
- [补档]noip2019集训测试赛(十二)
- 监控系统选型,这篇不可不读
- PHP学习笔记--函数
- paip.提升用户体验-----可访问性大原则及一些方法
- “SCSA-S学习导图+”系列:Windows下的WEB系统环境搭建
- rtmp/rtsp直播源(真是有效的,网上很多都是失效的)
- Unity - Timeline 之 Timeline Setting(Timeline的设置)
- 游戏《迷你世界》如何吸引小鸡到鸡窝里?这些道具很重要!
- 显示gsensor即时数据的apk 用gsensor来判断手机的静和动 气压计的测试应用
- 苹果系统微信实况图照片发送-竞品分析初步思考
- 什么是面向对象?你是怎么理解面向对象的?为什么要用面向对象?用面向对象有什么好处?
- 2019全国数学建模大赛c题出租车机场数据
- wps出现安装installer_wps总出现稿纸加载项安装怎么办 - 卡饭网