前面我们通过使用MediaPipe实时检测人脸的478个关键点进行了“面部网格”识别,其实还可以选择使用Dlib人脸检测模块来实现人脸识别,它提供了68个关键点(landmarks),相对MediaPipe而言更为简洁(如图1)。比如1-17对应双耳顶端至下巴的外围区域,18-22、23-27则分别对应两道眉毛等等。通过Dlib对人脸68个关键点的定位,或者结合一定的数学计算进行更多点的定位,我们就可以实现对面部穴位的实时标注功能。
1.库模块的导入、变量的初始化及函数Show_ZHN()的自定义
接下来,进行待使用的库模块导入:“import cv2”、“import numpy as np”、“import dlib”和“from PIL import ImageFont,Image,ImageDraw”;建立变量det_face并赋值为“dlib.get_frontal_face_detector()”,作用是进行人脸检测器的创建;建立变量det_landmark并赋值为“dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")”,作用是进行关键点检测器的加载(读取landmarks.dat预训练模型文件);建立变量camera并赋值为“cv2.VideoCapture(0)”,作用是打开摄像头。
通过“def Show_ZHN(img,text,pos):”自定义一个能够在摄像头监控画面上显示中文的函数——建立变量img_pil
并赋值为“Image.fromarray(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))”,作用是先将img图像由BGR模式转为RGB模式,再从array数组转换成image图像;建立变量font并赋值为“ImageFont.truetype(font='msyh.ttc',size=18)”,作用是加载微软雅黑字体文件,字号为18磅;
建立变量draw并赋值为“ImageDraw.Draw(img_pil)”,作用是通过ImageDraw类来创建绘制对象draw;语句“draw.text(pos,text,font=font,fill=(255,0,0))”的作用是设置绘制文本的位置(二维坐标)、内容、字体及填充颜色(RGB为“(255,0,0)”表示红色);建立变量img_cv并赋值为“np.array(img_pil)”,作用是将PIL图像(image形式)转换为numpy数组;建立变量img并赋值为“cv2.cvtColor(img_cv,cv2.COLOR_RGB2BGR)”,作用是将PIL的RGB图像模式转换为OpenCV的BGR模式;最后,通过语句“return img”返回img(如图2)。
2.“while True:”循环
在“while True:”循环中,语句“ret,img = camera.read()”的作用是读取摄像头的捕获画面;
建立变量gray并赋值为“cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)”,作用是将图像从BGR模式转换成灰度模式;建立变量face_rects并赋值为“det_face(gray,0)”,作用是进行人脸区域的检测;在“for ret in face_rects:”循环中进行可能检测到的多张人脸遍历,建立变量landmarks并赋值为“det_landmark(gray,ret)”,作用是进行关键点的检测;建立变量parts并赋值为“landmarks.parts()”,作用是获取每张人脸68个关键点的二维坐标点数据;语句“cv2.rectangle(img,(ret.left(),ret.top()),(ret.right(),ret.bottom()),
(255,0,0),2)”的作用是进行人脸区域矩形边框的绘制,其中的“(ret.left(),ret.top())”和“(ret.right(),ret.bottom())”分别对应该矩形区域的左上角和右下角坐标;在“for part in landmarks.parts():”循环中进行68个关键点的遍历,建立变量point并赋值为“(part.x,part.y)”,作用是获取每个关键点的坐标值;语句“cv2.circle(img,point,1,(0,255,255),-1)”的作用是在img图像上进行“画点”操作,大小为1、颜色为黄色。
接下来开始进行若干个面部穴位点的标注,需要注意的是——图1人脸的68个关键点序号是从1到68,但程序中变量parts的数据中所对应的序号是从0到67,因此必须在定位各个关键点时要进行对应的“减1”操作。在此列举两组穴位进行标注:一组是穴位点与68个关键点中的某个点是吻合的,另一组是需要进行一定的比例值计算才能定位的(即中医所称的穴位“同身寸”定位法)。
(1)31号对应的“素髎”穴,建立变量point_30并赋值为“(parts[30].x,parts[30].y)”,作用是获取31号关键点的坐标值,再通过语句“cv2.circle(img, point_30,3,(0,255,0),-1)”进行画点,大小为3、颜色为绿色。
(2)40号和43号对应的两处“睛明”穴,建立变量point_39和point_42,分别赋值为“(parts[39].x,parts[39].y)”和“(parts[42].x,parts[42].y)”,通过语句“cv2.circle(img, point_39,3,(0,255,0),-1)”和“cv2.circle(img, point_42,3,(0,255,0),-1)”,作用同样是在对应坐标点处画点。
(3)22号和23号连线中点的“印堂”穴,建立变量point_YinTang并赋值为“(int((parts[21].x+parts[22].x)/2),int((parts[21].y+parts[22].y)/2))”,这是利用了数学上的“定比分点”坐标公式进行的计算(此时的定比k=1),再通过语句“cv2.circle(img, point_YinTang,3,(0,255,0),-1)”进行画点。
(4)58号和9号连线四分之一处的“承浆”穴,建立变量point_ChengJiang并赋值为“(int((parts[57].x+0.3*parts[7].x)/1.3),int((parts[57].y+0.3*parts[7].y)/1.3))”,同样是利用“定比分点”坐标公式进行计算(定比k约等于0.3),再通过语句“cv2.circle(img, point_ChengJiang,3,(0,255,0),-1)”进行画点。
五个穴位代表点描绘结束之后,再通过调用显示中文函数Show_ZHN(img,text,pos)将对应位置的穴位名称进行标注,分别为img赋值“Show_ZHN(img,"素髎",point_30)”、“Show_ZHN(img,"睛明",point_39)”、“Show_ZHN(img,"睛明",point_42)”、“Show_ZHN(img,"印堂",point_YinTang)”和“Show_ZHN(img,"承浆",point_ChengJiang)”。
最后,使用语句“cv2.imshow("Face_Acupoint",img)”实现摄像头画面的窗口显示效果;并且设置热键(q)进行程序退出响应,关闭摄像头以释放资源:“cap.release()”(如图3)。
3.程序的运行测试
将程序保存为Dlib_Acupoint.py,按F5键进行测试,在摄像头前分别尝试是否戴眼镜和口罩的四种情况测试(注意人脸离摄像头要稍微近些),程序都能够正确实现设定的功能:蓝色矩形框住人脸,68个小黄点对应各个关键点,五个穴位均用绿色圆点和对应的文字进行了标注,即使做一些扭头或是左右前后小幅度的移动,即时显示效果都非常不错(如图4),大家不妨一试。
编辑|张毅
审核|吴新
领取专属 10元无门槛券
私享最新 技术干货