文章目录

  • 一. 实验要求
  • 二. 实验思路
    • 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):人脸面部识别相关推荐

  1. c语言回溯实验报告,实验报告: 人脸识别方法回溯与实验分析 【OpenCV测试方法源码】...

    实验报告: 人脸识别方法回顾与实验分析 [OpenCV测试方法源码] 趁着还未工作,先把过去做的东西整理下出来~ (涉及个人隐私,源码不包含测试样本,请谅解~) 对实验结果更感兴趣的朋友请直接看第5章 ...

  2. python+opencv+dlib实现人脸检测与表情识别

    python+opencv+dlib实现人脸检测与表情识别 一,dlib简单介绍:Dlib包含广泛的机器学习算法.所有的设计都是高度模块化的,快速执行,并且通过一个干净而现代的C ++ API,使用起 ...

  3. 基于Python的OpenCV+TensorFlow+Keras人脸识别实现

    前言:本节要讲的人脸识别主要是借鉴了 一位研究生前辈的文章 我只是在他的基础上进行了改动,让代码能在现在的TensorFlow2.X 等的环境下运行 先看一下效果图 完整工程及源代码请点击链接下载:人 ...

  4. 【毕业设计/课程设计】基于opencv的高精度人脸识别考勤系统设计与实现

    文章目录 0 项目说明 1 需求分析 2 总体设计 3 详细设计 4 程序运行结果测试与分析 5 实验心得 6 项目源码 0 项目说明 基于opencv的高精度人脸识别考勤系统设计与实现 提示:适合用 ...

  5. 基于Opencv快速实现人脸识别(完整版)

    上篇博客:https://blog.csdn.net/beyond9305/article/details/92844258严格来说标题是有误的,只是单纯地对人脸进行了检测,而并非识别,opencv内 ...

  6. OpenCV + python 实现人脸检测(基于照片和视频进行检测)

    OpenCV + python 实现人脸检测(基于照片和视频进行检测) Haar-like 通俗的来讲,就是作为人脸特征即可. Haar特征值反映了图像的灰度变化情况.例如:脸部的一些特征能由矩形特征 ...

  7. OpenCV OMZ MTCNN人脸检测的实例(附完整代码)

    OpenCV OMZ MTCNN人脸检测的实例 OpenCV OMZ MTCNN人脸检测的实例 OpenCV OMZ MTCNN人脸检测的实例 #include <algorithm> # ...

  8. OpenCV cv::CascadeClassifier人脸检测的实例(附完整代码)

    OpenCV cv::CascadeClassifier人脸检测的实例 OpenCV cv::CascadeClassifier人脸检测的实例 OpenCV cv::CascadeClassifier ...

  9. 基于OpenCV的简单人脸识别系统

    目录 1. 调用库函数 2. 调用摄像头并设置窗口 3. 设置图片正负样本数据集的路径 4. 调用人脸检测器 5. 正负样本载入 6.提取人脸区域 7. 建立LBPH人脸识别模型 8. 实时检测 9. ...

最新文章

  1. 查看Ubuntu 系统的版本
  2. Facebook、Netflix 等多家科技巨头谈“设计”
  3. 牛客小白月赛12 C 华华给月月出题 (积性函数,线性筛)
  4. 扩展Guava缓存溢出到磁盘
  5. Load Balance System
  6. dd大牛《背包九讲》(转载)
  7. 如何查看python的版本号
  8. [补档]noip2019集训测试赛(十二)
  9. 监控系统选型,这篇不可不读
  10. PHP学习笔记--函数
  11. paip.提升用户体验-----可访问性大原则及一些方法
  12. “SCSA-S学习导图+”系列:Windows下的WEB系统环境搭建
  13. rtmp/rtsp直播源(真是有效的,网上很多都是失效的)
  14. Unity - Timeline 之 Timeline Setting(Timeline的设置)
  15. 游戏《迷你世界》如何吸引小鸡到鸡窝里?这些道具很重要!
  16. 显示gsensor即时数据的apk 用gsensor来判断手机的静和动 气压计的测试应用
  17. 苹果系统微信实况图照片发送-竞品分析初步思考
  18. 什么是面向对象?你是怎么理解面向对象的?为什么要用面向对象?用面向对象有什么好处?
  19. 2019全国数学建模大赛c题出租车机场数据
  20. wps出现安装installer_wps总出现稿纸加载项安装怎么办 - 卡饭网

热门文章

  1. js字符串格式化方法format
  2. 中文语音合成综合评测一(可懂度)
  3. 读完这篇文章,颠覆你之前对硬盘开盘的认知!
  4. MySQL数据库软件及SQL简介
  5. 很牛的求职历程和经验(二)
  6. 从创建服务器到搭建一台内网穿透服务器
  7. github whs_从iPhone或iPod Touch获得WHS的基本访问权限,而无需安装应用程序
  8. Java实现png图片转pdf
  9. 64位win7下安装SQL Server 2008(图文解说版)----本人备注
  10. iOS视频通话问题总结及心路历程。。。