一 前言
这几天宅在家里网上冲浪,无意间看到了一个比较有趣的项目,就是使用 Python 语言实现对视频中的人物的眨眼进行计数并描绘在图表中。我尝试了一下,发现是可以实现的,所以自己码了一遍代码并简单注释了一下,有兴趣的朋友可以浅试一下。
该项目大致效果如下:
Now, let's start!
二 实现步骤
对于创建项目文件夹配置环境以及如何安装第三方库这里就不再详细的介绍了,有不会的同学可以去翻我之前的文章或者其他博主的文章去了解学习吧,并不是很难。
1 第三方库
首先我们需要安装 cvzone(一个比较专业的计算机视觉包,在面部识别,手势、姿势检测中为我们提供了很多便利)。还有一个包是 mediapipe,我们将会使用它来实现面部检测网络,以便于对视频中的眼睛部位进行观察。
2 导入视频文件并播放
实现代码及效果如下:
import cv2 import cvzone cap = cv2.VideoCapture('BlinkCounter.mp4')#获取需要检测的视频(添加视频路径即可,此处由于视频和Python文件在同一路径,直接调用即可) while True: success, img = cap.read() img = cv2.resize(img, (640, 360)) #对图像尺寸进行调节 cv2.imshow("Image", img) #显示图像 cv2.waitKey(1)
3 让视频循环播放
我们要对视频中的眨眼次数进行计数,但通过上面的代码可以看出视频很快就播放结束了,所以我们采用检查视频帧数的方式,当达到视频的最后一帧时对其进行重置,达到循环播放视频的目的。代码如下:
#------------------------------------------------------------ #检查当前帧数是否等于视频的总体帧数,如果相等,将播放帧数重置为0 #------------------------------------------------------------ if cap.get(cv2.CAP_PROP_POS_FRAMES) == cap.get(cv2.CAP_PROP_FRAME_COUNT): cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
4 创建面部检测器
该步骤需要使用到 cvzone 中的相应模块来实现,具体代码及注释如下:
import cv2 import cvzone from cvzone.FaceMeshModule import FaceMeshDetector #调用面部检测模块 cap = cv2.VideoCapture('BlinkCounter.mp4')#获取需要检测的视频(添加视频路径即可,此处由于视频和Python文件在同一路径,直接调用即可) detector = FaceMeshDetector(maxFaces = 1)#创建人脸网络检测器,检测面部数量为1 while True: #------------------------------------------------------------ #检查当前帧数是否等于视频的总体帧数,如果相等,将播放帧数重置为0 #------------------------------------------------------------ if cap.get(cv2.CAP_PROP_POS_FRAMES) == cap.get(cv2.CAP_PROP_FRAME_COUNT): cap.set(cv2.CAP_PROP_POS_FRAMES, 0) #------------------------------------------------------------ success, img = cap.read() img, faces = detector.findFaceMesh(img)#绘制人脸网络 img = cv2.resize(img, (640, 360)) #对图像尺寸进行调节 cv2.imshow("Image", img) #显示图像 cv2.waitKey(1)
得到的结果如下:
5 对眼睛周围的点进行标记
由于面部检测器是对面部用点进行标记的,所以我们需要找到那些眼睛周围的点并将它们使用特殊的点来进行标记,从而达到检测眼睛闭合与张开的目的,相应的点数以及代码如下:
idList = [22, 23, 24, 26, 110, 157, 158, 159, 160, 161, 130, 243]#面部检测器中眼眶周围的像素点 color = (0, 0, 255)#将颜色设置为红色 #------------------------------------------------------------ #检测到面部时将面部的关于眼眶的点用圆圈表示出来,并填补完整 #------------------------------------------------------------ if faces: face = faces[0] for id in idList: cv2.circle(img, face[id], 5, color, cv2.FILLED)
得到的效果如下,可以看出,已经对眼眶进行了标记:
我们可以对面部检测网络进行设置,让其他的一些点不再显示出来,只关注我们的目标点,具体操作方式是只需要在函数中添加一个参数即可,效果如下:
img, faces = detector.findFaceMesh(img, draw = False)#绘制人脸检测网络,将参数 draw 修改为 False 即可抹去其他的不必要的点
6 观察眼睛宽度和长度变化并进行计数
如果单纯的靠上下眼皮的变化来检测是否眨眼会造成判断错误,所以我们需要结合眼睛长度和宽度之比进行判断。将这项数据的变化趋势绘制在一个窗口中,就会使得我们的观察更加明显,得到的效果也将更加优良。具体代码如下:
#------------------------------------------------------------- #使用眼眶周围不同的点之间的距离以及眼睛宽度和长度的对比进行眨眼计数 #------------------------------------------------------------- leftUp = face[159]#定义不同部位的像素点 leftDown = face[23] leftLeft = face[130] leftRight = face[243] lenghtVer, _ = detector.findDistance(leftUp, leftDown)#得到眼睛宽度 lenghtHor, _ = detector.findDistance(leftLeft, leftRight)#得到眼睛长度 cv2.line(img, leftUp, leftDown, (0, 200, 0), 3)#绘制与眼睛等宽的线段 cv2.line(img, leftLeft, leftRight, (0, 200, 0), 3)#绘制与眼睛等长的线段 ratio = int((lenghtVer / lenghtHor) * 100)#得到的数据并进行标准化,得到比率值 ratioList.append(ratio)#使用比率值填补列表 #------------------------------------------------------------------ #从之前的一共三个比率中得到平均值,如果比率数目大于3将会删去前面的比率值 #------------------------------------------------------------------ if len(ratioList) > 3: ratioList.pop(0) ratioAvg = sum(ratioList) / len(ratioList)#得到比率平均值 #------------------------------------ #通过比率平均值的变化对眨眼次数进行计数 #------------------------------------ if ratioAvg < 35 and counter == 0: blinkCounter += 1#变化一次,计数器加一 color = (0, 200, 0) counter = 1 if counter != 0: counter += 1 if counter > 10: counter = 0 color = (255, 0, 255) #----------------------------- #编写文本框来记录眨眼的总体次数 #------------------------------ cvzone.putTextRect(img, f'Blink Count: {blinkCounter}', (50, 250), 12, colorR = color) imgPlot = plotY.update(ratioAvg, color) img = cv2.resize(img, (400, 640)) # 对图像尺寸进行调节 imgStack = cvzone.stackImages([img, imgPlot], 2, 1)#将两幅图像放到一起,叠放为2列图像 else: img = cv2.resize(img, (400, 640)) # 对图像尺寸进行调节 imgStack = cvzone.stackImages([img, img], 2, 1)
通过以上步骤大致就能实现眨眼计数了。
三 整体代码
整体代码如下,已经添加了注释,如果有解释的不清楚的地方可以在评论区交流。
import cv2 import cvzone from cvzone.FaceMeshModule import FaceMeshDetector from cvzone.PlotModule import LivePlot cap = cv2.VideoCapture('BlinkCounter.mp4')#获取需要检测的视频(添加视频路径即可,此处由于视频和Python文件在同一路径,直接调用即可) detector = FaceMeshDetector(maxFaces = 1)#创建人脸网络检测器,检测面部数量为1 plotY = LivePlot(640, 640, [20, 50], invert = True)#创建窗口来绘制数据的变化 idList = [22, 23, 24, 26, 110, 157, 158, 159, 160, 161, 130, 243]#面部检测器中眼眶周围的像素点 ratioList = []#创建一个空的比率列表 blinkCounter = 0 counter = 0#避免每一个帧节进行一次计数 color = (0, 0, 255)#将颜色设置为红色 while True: #------------------------------------------------------------ #检查当前帧数是否等于视频的总体帧数,如果相等,将播放帧数重置为0 #------------------------------------------------------------ if cap.get(cv2.CAP_PROP_POS_FRAMES) == cap.get(cv2.CAP_PROP_FRAME_COUNT): cap.set(cv2.CAP_PROP_POS_FRAMES, 0) success, img = cap.read()#读取到的视频 img, faces = detector.findFaceMesh(img, draw = False)#绘制人脸检测网络 #------------------------------------------------------------ #检测到面部时将面部的关于眼眶的点用圆圈表示出来,并填补完整 #------------------------------------------------------------ if faces: face = faces[0] for id in idList: cv2.circle(img, face[id], 5, color, cv2.FILLED) #------------------------------------------------------------- #使用眼眶周围不同的点之间的距离以及眼睛宽度和长度的对比进行眨眼计数 #------------------------------------------------------------- leftUp = face[159]#定义不同部位的像素点 leftDown = face[23] leftLeft = face[130] leftRight = face[243] lenghtVer, _ = detector.findDistance(leftUp, leftDown)#得到眼睛宽度 lenghtHor, _ = detector.findDistance(leftLeft, leftRight)#得到眼睛长度 cv2.line(img, leftUp, leftDown, (0, 200, 0), 3)#绘制与眼睛等宽的线段 cv2.line(img, leftLeft, leftRight, (0, 200, 0), 3)#绘制与眼睛等长的线段 ratio = int((lenghtVer / lenghtHor) * 100)#得到的数据并进行标准化,得到比率值 ratioList.append(ratio)#使用比率值填补列表 #------------------------------------------------------------------ #从之前的一共三个比率中得到平均值,如果比率数目大于3将会删去前面的比率值 #------------------------------------------------------------------ if len(ratioList) > 3: ratioList.pop(0) ratioAvg = sum(ratioList) / len(ratioList)#得到比率平均值 #------------------------------------ #通过比率平均值的变化对眨眼次数进行计数 #------------------------------------ if ratioAvg < 35 and counter == 0: blinkCounter += 1#变化一次,计数器加一 color = (0, 200, 0) counter = 1 if counter != 0: counter += 1 if counter > 10: counter = 0 color = (255, 0, 255) #----------------------------- #编写文本框来记录眨眼的总体次数 #------------------------------ cvzone.putTextRect(img, f'Blink Count: {blinkCounter}', (50, 250), 12, colorR = color) imgPlot = plotY.update(ratioAvg, color) img = cv2.resize(img, (400, 640)) # 对图像尺寸进行调节 imgStack = cvzone.stackImages([img, imgPlot], 2, 1)#将两幅图像放到一起,叠放为2列图像 else: img = cv2.resize(img, (400, 640)) # 对图像尺寸进行调节 imgStack = cvzone.stackImages([img, img], 2, 1) img = cv2.resize(img, (400, 640)) #对图像尺寸进行调节 cv2.imshow("Image", imgStack) cv2.waitKey(1)
四 结束语
该项目是博主在寒假闲暇时间看到的一个比较有趣的项目,所以专门尝试着去做了一下,发现还是比较容易实现的。实现代码都已经附在文章中了,大家感兴趣可以尝试一下,至于视频文件可以自己找一段眨眼比较清楚的短片进行观察,或者也可以使用我分享的链接提取。在操作的过程中可能会遇到一些报错,可以参考下面的这篇文章,专门记录了我在该项目中遇到的错误以及解决方法。加油!!
报错解决方法
BlinkCounter 中遇到的一些报错及解决方法
1. cannot import name ‘_registerMatType‘
是因为同时安装了opencv-python和opencv-contrib-python 版本之间不匹配造成的,删除opencv-contrib-python后opencv-python也会出错,就把两个都删除后安装opencv-python及相匹配的版本的opencv-contrib-python(需要同时把两个都删掉)
2. ModuleNotFoundError: No module named 'mediapipe'
安装 ‘mediapipe' 库,如果无法安装换个镜像源试一下(一般清华的镜像源就可以了)
视频提取网盘: