目录
一、虚拟主播实现
(一)读取视频流
(二)绘制关键点
(三)计算面部特征并绘图
(四)完整代码
二、前端网页制作
(一)建立数据库
(二)导入虚拟主播代码并将数据存入数据库
(三)绘制echarts折线图
(四)绘制ajax动态折线图(未实现)
该实验基于人脸识别,简单地将人脸实时绘制为虚拟主播的动态图像,并将头部姿态数据生成到了Flask网页前端。完成该实验需要借助带有前置摄像头或外置摄像头的电脑,shape_predictor_68_face_landmarks.dat文件、Chart.min.js文件和echarts.min.js文件,大家可通过以下网盘链接获取三个文件。
链接:https://pan.baidu.com/s/1-nQkAFq9TJU999lNrydYXQ?pwd=u15u
提取码:u15u
我使用的编辑器是Pycharm。首先使用OpenCV从前置摄像头或外置摄像头读取视频流,并将其播放在窗口上。运行后系统将自动开启摄像头获取图像。
# Step1.py
import cv2
cap = cv2.VideoCapture(0) # 表示内置摄像头,使用外置摄像头参数改为1
while True:
ret, img = cap.read()
img = cv2.flip(img, 1) # 表示把帧左右翻转
cv2.imshow('vtuber', img)
cv2.waitKey(1)
运行后系统将自动开启摄像头获取图像,并对获取到的最大人脸进行68个点的定位。
# Step2.py
import cv2
import numpy as np
import dlib
detector = dlib.get_frontal_face_detector()
# 确保img中存在人脸,并找出最大的那一张脸
def face_positioning(img):
dets = detector(img, 0)
if not dets:
return None
return max(dets, key=lambda det: (det.right() - det.left()) * (det.bottom() - det.top()))
# 使用Dlib提取面部关键点
predictor = dlib.shape_predictor(
'./data/shape_predictor_68_face_landmarks.dat') # 该文件保存在对应项目的data文件夹下
def extract_key_points(img, position):
landmark_shape = predictor(img, position)
key_points = []
for i in range(68):
pos = landmark_shape.part(i)
key_points.append(np.array([pos.x, pos.y], dtype=np.float32))
return key_points
if __name__ == '__main__':
cap = cv2.VideoCapture(0)
while True:
ret, img = cap.read()
img = cv2.flip(img, 1)
face_position = face_positioning(img)
key_points = extract_key_points(img, face_position)
for i, (px, py) in enumerate(key_points): # 绘制关键点
cv2.putText(img, str(i), (int(px), int(py)),
cv2.FONT_HERSHEY_COMPLEX, 0.25, (255, 255, 255))
cv2.imshow('vtuber', img)
cv2.waitKey(1)
运行后系统将自动开启摄像头获取图像,并对获取到的最大人脸进行68个点的定位,同时绘制简易的虚拟主播动态图像。
# Step3.py
import cv2
import numpy as np
import dlib
detector = dlib.get_frontal_face_detector()
# 确保img中存在人脸,并找出最大的那一张脸
def face_positioning(img):
dets = detector(img, 0)
if not dets:
return None
return max(dets, key=lambda det: (det.right() - det.left()) * (det.bottom() - det.top()))
# 使用Dlib提取面部关键点
predictor = dlib.shape_predictor(
'./data/shape_predictor_68_face_landmarks.dat')
def extract_key_points(img, position):
landmark_shape = predictor(img, position)
key_points = []
for i in range(68):
pos = landmark_shape.part(i)
key_points.append(np.array([pos.x, pos.y], dtype=np.float32))
return key_points
def generate_points(key_points):
def center(array):
return sum([key_points[i] for i in array]) / len(array)
left_brow = [18, 19, 20, 21]
right_brow = [22, 23, 24, 25]
chin = [6, 7, 8, 9, 10]
nose = [29, 30]
return center(left_brow + right_brow), center(chin), center(nose)
# 水平旋转角度和垂直旋转角度
def generate_features(contruction_points):
brow_center, chin_center, nose_center = contruction_points
mid_edge = brow_center - chin_center
bevel_edge = brow_center - nose_center
mid_edge_length = np.linalg.norm(mid_edge)
horizontal_rotation = np.cross(
mid_edge, bevel_edge) / mid_edge_length ** 2
vertical_rotation = mid_edge @ bevel_edge / mid_edge_length ** 2
return np.array([horizontal_rotation, vertical_rotation])
# 绘图
def draw_image(h_rotation, v_rotation):
img = np.ones([512, 512], dtype=np.float32)
face_length = 200
center = 256, 256
left_eye = int(220 - h_rotation *
face_length), int(249 + v_rotation * face_length)
right_eye = int(292 - h_rotation *
face_length), int(249 + v_rotation * face_length)
month = int(256 - h_rotation * face_length /
2), int(310 + v_rotation * face_length / 2)
cv2.circle(img, center, 100, 0, 1)
cv2.circle(img, left_eye, 15, 0, 1)
cv2.circle(img, right_eye, 15, 0, 1)
cv2.circle(img, month, 5, 0, 1)
return img
def extract_img_features(img):
face_position = face_positioning(img)
if not face_position:
cv2.imshow('self', img)
cv2.waitKey(1)
return None
key_points = extract_key_points(img, face_position)
for i, (p_x, p_y) in enumerate(key_points):
cv2.putText(img, str(i), (int(p_x), int(p_y)),
cv2.FONT_HERSHEY_COMPLEX, 0.25, (255, 255, 255))
construction_points = generate_points(key_points)
for i, (p_x, p_y) in enumerate(construction_points):
cv2.putText(img, str(i), (int(p_x), int(p_y)),
cv2.FONT_HERSHEY_COMPLEX, 0.25, (255, 255, 255))
rotation = generate_features(construction_points)
cv2.putText(img, str(rotation),
(int(construction_points[-1][0]),
int(construction_points[-1][1])),
cv2.FONT_HERSHEY_COMPLEX, 0.5, (255, 255, 255))
cv2.imshow('self', img)
return rotation
if __name__ == '__main__':
cap = cv2.VideoCapture(0)
ORIGIN_FEATURE_GROUP = [-0.00899233, 0.39529446]
FEATURE_GROUP = [0, 0]
while True:
ret, img = cap.read()
img = cv2.flip(img, 1)
NEW_FEATURE_GROUP = extract_img_features(img)
if NEW_FEATURE_GROUP is not None:
FEATURE_GROUP = NEW_FEATURE_GROUP - ORIGIN_FEATURE_GROUP
HORI_ROTATION, VERT_ROTATION = FEATURE_GROUP
cv2.imshow('vtuber', draw_image(HORI_ROTATION, VERT_ROTATION))
cv2.waitKey(1)
if cv2.waitKey(1) & 0xFF == ord('q'):
print("close")
break
将以上三步整合,并在运行环境中输出部分数据以便我们对数据有更好的认识。完整代码如下:
# face.py
"""
detect face
"""
import cv2
import numpy as np
# to detect face key point
import dlib
DETECTOR = dlib.get_frontal_face_detector()
# 人脸模型数据
PREDICTOR = dlib.shape_predictor(
'./data/shape_predictor_68_face_landmarks.dat')
def face_positioning(img):
"""
定位人脸
计算最大面积
"""
dets = DETECTOR(img, 0)
if not dets:
return None
return max(dets, key=lambda det: (det.right() - det.left()) * (det.bottom() - det.top()))
def extract_key_points(img, position):
"""
提取关键点
"""
landmark_shape = PREDICTOR(img, position)
key_points = []
for i in range(68):
pos = landmark_shape.part(i)
key_points.append(np.array([pos.x, pos.y], dtype=np.float32))
return key_points
def generate_points(key_points):
"""
生成构造点
"""
def center(array):
return sum([key_points[i] for i in array]) / len(array)
left_brow = [18, 19, 20, 21]
right_brow = [22, 23, 24, 25]
# 下巴
chin = [6, 7, 8, 9, 10]
nose = [29, 30]
return center(left_brow + right_brow), center(chin), center(nose)
def generate_features(contruction_points):
"""
生成特征
"""
brow_center, chin_center, nose_center = contruction_points
mid_edge = brow_center - chin_center
# 斜边
bevel_edge = brow_center - nose_center
mid_edge_length = np.linalg.norm(mid_edge)
# 高与底的比值
horizontal_rotation = np.cross(
mid_edge, bevel_edge) / mid_edge_length ** 2
# @ 点乘
vertical_rotation = mid_edge @ bevel_edge / mid_edge_length**2
return np.array([horizontal_rotation, vertical_rotation])
def draw_image(h_rotation, v_rotation):
"""
画脸
Args:
h_rotation: 水平旋转量
v_rotation: 垂直旋转量
"""
img = np.ones([512, 512], dtype=np.float32)
face_length = 200
center = 256, 256
left_eye = int(220 - h_rotation *
face_length), int(249 + v_rotation * face_length)
right_eye = int(292 - h_rotation *
face_length), int(249 + v_rotation * face_length)
month = int(256 - h_rotation * face_length /
2), int(310 + v_rotation * face_length / 2)
cv2.circle(img, center, 100, 0, 1)
cv2.circle(img, left_eye, 15, 0, 1)
cv2.circle(img, right_eye, 15, 0, 1)
cv2.circle(img, month, 5, 0, 1)
return img
def extract_img_features(img):
"""
提取图片特征
"""
face_position = face_positioning(img)
print("图片中最大人脸的范围为:", face_position)
if not face_position:
cv2.imshow('self', img)
cv2.waitKey(1)
return None
key_points = extract_key_points(img, face_position)
print("68个关键点的位置为:", key_points)
for i, (p_x, p_y) in enumerate(key_points):
cv2.putText(img, str(i), (int(p_x), int(p_y)),
cv2.FONT_HERSHEY_COMPLEX, 0.25, (255, 255, 255))
construction_points = generate_points(key_points)
print("生成的构造点的位置为:", construction_points)
for i, (p_x, p_y) in enumerate(construction_points):
cv2.putText(img, str(i), (int(p_x), int(p_y)),
cv2.FONT_HERSHEY_COMPLEX, 0.25, (255, 255, 255))
rotation = generate_features(construction_points)
print("水平旋转量和垂直旋转量为:", rotation)
cv2.putText(img, str(rotation),
(int(construction_points[-1][0]),
int(construction_points[-1][1])),
cv2.FONT_HERSHEY_COMPLEX, 0.5, (255, 255, 255))
cv2.imshow('self', img)
return rotation
if __name__ == '__main__':
CAP = cv2.VideoCapture(0)
# 原点特征组 my front side
ORIGIN_FEATURE_GROUP = [-0.00899233, 0.39529446]
FEATURE_GROUP = [0, 0]
while True:
RETVAL, IMAGE = CAP.read()
# 翻转视频
IMAGE = cv2.flip(IMAGE, 1)
NEW_FEATURE_GROUP = extract_img_features(IMAGE)
if NEW_FEATURE_GROUP is not None:
FEATURE_GROUP = NEW_FEATURE_GROUP - ORIGIN_FEATURE_GROUP
HORI_ROTATION, VERT_ROTATION = FEATURE_GROUP
cv2.imshow('Vtuber', draw_image(HORI_ROTATION, VERT_ROTATION))
if cv2.waitKey(1) & 0xFF == ord('q'):
print("close")
break
效果如图所示:
这样我们就实现了最简单的虚拟主播实验了,接下来让我们结合之前学过的Flask制作前端网页。
首先我们新建一个项目,在该项目中新建main.py文件,static文件夹和templates文件夹。在static文件夹中导入网盘链接中的三个文件,将shape_predictor_68_face_landmarks.dat直接放在文件夹下,Chart.min.js和echarts.min.js放在js文件夹下(也可以都放置在static文件夹下)。在templates文件夹中新建home.html(主页)、image.html(动态折线图页)、show_data.html(头部姿态展示页)和virtual_echarts.html(静态折线图页)。如下所示:
首先,我们先建立一个数据库,便于后续将获取到的人脸数据存入。打开Navicat Premium,在某一数据库下新建一个名为"face_data"的表,并在其中添加列名id、face、new1和new2,其类型和长度等属性如图所示,注意一定要将id设置为主键,并勾选“自动递增”,否则后续实验会报错。
我们将上面已经实现的虚拟主播代码与Flask网页框架结合在一起,并将虚拟主播的代码稍作修改,从而将图片中最大人脸范围、水平旋转量和垂直旋转量的数据存入数据库中。main.py代码如下所示:
# main.py
from flask import Flask, render_template, jsonify
from flask_sqlalchemy import SQLAlchemy
import pymysql
# 连接数据库,便于后续存取数据
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:123456@localhost:3306/pyc'
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
# 添加一行 显示utf-8
app.config['JSON_AS_ASCII'] = False
db = SQLAlchemy(app)
# 在Flask中定义数据库的列
class face_data(db.Model):
id = db.Column(db.Integer, primary_key=True)
face = db.Column(db.String(255)) # 图片中最大人脸的范围
new1 = db.Column(db.String(255)) # 水平旋转量
new2 = db.Column(db.String(255)) # 垂直旋转量
# 主页
@app.route("/")
def hello():
return render_template("home.html")
# 稍作修改后的虚拟主播代码
@app.route("/save_data", methods=["GET", "POST"])
def save_data():
"""
detect face
"""
import cv2
import numpy as np
# to detect face key point
import dlib
DETECTOR = dlib.get_frontal_face_detector()
# 人脸模型数据
PREDICTOR = dlib.shape_predictor(
'./static/shape_predictor_68_face_landmarks.dat')
def face_positioning(img):
"""
定位人脸
计算最大面积
"""
dets = DETECTOR(img, 0)
if not dets:
return None
return max(dets, key=lambda det: (det.right() - det.left()) * (det.bottom() - det.top()))
def extract_key_points(img, position):
"""
提取关键点
"""
landmark_shape = PREDICTOR(img, position)
key_points = []
for i in range(68):
pos = landmark_shape.part(i)
key_points.append(np.array([pos.x, pos.y], dtype=np.float32))
return key_points
def generate_points(key_points):
"""
生成构造点
"""
def center(array):
return sum([key_points[i] for i in array]) / len(array)
left_brow = [18, 19, 20, 21]
right_brow = [22, 23, 24, 25]
# 下巴
chin = [6, 7, 8, 9, 10]
nose = [29, 30]
return center(left_brow + right_brow), center(chin), center(nose)
def generate_features(contruction_points):
"""
生成特征
"""
brow_center, chin_center, nose_center = contruction_points
mid_edge = brow_center - chin_center
# 斜边
bevel_edge = brow_center - nose_center
mid_edge_length = np.linalg.norm(mid_edge)
# 高与底的比值
horizontal_rotation = np.cross(
mid_edge, bevel_edge) / mid_edge_length ** 2
# @ 点乘
vertical_rotation = mid_edge @ bevel_edge / mid_edge_length ** 2
return np.array([horizontal_rotation, vertical_rotation])
def draw_image(h_rotation, v_rotation):
"""
画脸
Args:
h_rotation: 水平旋转量
v_rotation: 垂直旋转量
"""
img = np.ones([512, 512], dtype=np.float32)
face_length = 200
center = 256, 256
left_eye = int(220 - h_rotation *
face_length), int(249 + v_rotation * face_length)
right_eye = int(292 - h_rotation *
face_length), int(249 + v_rotation * face_length)
month = int(256 - h_rotation * face_length /
2), int(310 + v_rotation * face_length / 2)
cv2.circle(img, center, 100, 0, 1)
cv2.circle(img, left_eye, 15, 0, 1)
cv2.circle(img, right_eye, 15, 0, 1)
cv2.circle(img, month, 5, 0, 1)
return img
def extract_img_features(img):
"""
提取图片特征
"""
face_position = face_positioning(img)
print("图片中最大人脸的范围为:", face_position)
if not face_position:
cv2.imshow('self', img)
cv2.waitKey(1)
return None
key_points = extract_key_points(img, face_position)
print("68个关键点的位置为:", key_points)
for i, (p_x, p_y) in enumerate(key_points):
cv2.putText(img, str(i), (int(p_x), int(p_y)),
cv2.FONT_HERSHEY_COMPLEX, 0.25, (255, 255, 255))
construction_points = generate_points(key_points)
print("生成的构造点的位置为:", construction_points)
for i, (p_x, p_y) in enumerate(construction_points):
cv2.putText(img, str(i), (int(p_x), int(p_y)),
cv2.FONT_HERSHEY_COMPLEX, 0.25, (255, 255, 255))
rotation = generate_features(construction_points)
print("水平旋转量和垂直旋转量为:", rotation)
cv2.putText(img, str(rotation),
(int(construction_points[-1][0]),
int(construction_points[-1][1])),
cv2.FONT_HERSHEY_COMPLEX, 0.5, (255, 255, 255))
cv2.imshow('self', img)
return face_position, rotation
if __name__ == '__main__':
CAP = cv2.VideoCapture(0 + cv2.CAP_DSHOW)
# 原点特征组 my front side
ORIGIN_FEATURE_GROUP = [-0.00899233, 0.39529446]
FEATURE_GROUP = [0, 0]
while True:
RETVAL, IMAGE = CAP.read()
# 翻转视频
IMAGE = cv2.flip(IMAGE, 1)
face_position, NEW_FEATURE_GROUP = extract_img_features(IMAGE)
if NEW_FEATURE_GROUP is not None:
FEATURE_GROUP = NEW_FEATURE_GROUP - ORIGIN_FEATURE_GROUP
HORI_ROTATION, VERT_ROTATION = FEATURE_GROUP
res = face_data() # 实例化一条记录
res.face = face_position
res.new1 = NEW_FEATURE_GROUP[0]
res.new2 = NEW_FEATURE_GROUP[1]
db.session.add(res) # 逻辑添加
db.session.commit() # 添加记录
if cv2.waitKey(1) & 0xFF == ord('q'):
print("close")
break
return "数据存储成功!"
# 将数据库数据传到前端展示
@app.route('/show_data')
def show_data():
db = pymysql.connect(host='localhost', user='root', password='123456', database='pyc', charset='utf8')
cursor = db.cursor()
cursor.execute("Select * from face_data")
rs = cursor.fetchall()
rs = list(rs)
print(rs[0:10])
return render_template("show_data.html", rs=rs)
if __name__ == '__main__':
app.run(debug=True)
接着,我们修改主页的模板home.html如下所示:
# templates/home.html
虚拟主播展示首页
虚拟主播展示
修改展示数据库数据的模板show_data.html如下所示:
虚拟主播数据展示页
这是图片中最大人脸的范围、水平旋转量和垂直旋转量
{% for r in rs %}
{{r}}
{% endfor %}
之后运行程序,首先点击“获取虚拟主播人脸数据”,出现人脸视频和虚拟主播视频后按q键停止,http://127.0.0.1:5000/save_data会显示“数据存储成功!”,证明我们的人脸数据已经存储到了数据库中,到Navicat Premium中查看,的确有数据存入。
之后点击主页中的“展示人脸数据”,会发现http://127.0.0.1:5000/show_data将数据库中的数据都展示出来了。
在main.py文件中添加如下代码:
# main.py
class Mysql(object):
def __init__(self):
try:
self.conn = pymysql.connect(host='localhost', user='root', password='123456', database='pyc', charset='utf8')
self.cursor = self.conn.cursor() # 用来获得python执行Mysql命令的方法(游标操作)
print("连接数据库成功")
except:
print("连接失败")
def getItems(self):
sql = "select id,new1,new2 from face_data" #获取food数据表的内容
self.cursor.execute(sql)
items = self.cursor.fetchall() #接收全部的返回结果行
return items
@app.route('/line')
def line():
db = Mysql()
items = db.getItems()
return render_template('virtual_echart.html', items=items)
并将virtual_echart.html修改如下:
# templates/virtual_echart.html
虚拟主播静态折线图展示
虚拟主播静态折线图展示
裴雨晨&&赵艺瑶
在home.html的body中添加如下代码:
# templates/home.html
再次运行,点击主页中的“展示静态折线图”,便可在http://127.0.0.1:5000/line看到用echarts画的折线图。该图反映了人脸在水平方向和垂直方向的旋转量。
在main.py文件中添加如下代码:
# main.py
@app.route('/show_test') # 路由
def show_test():
return render_template('image.html')
@app.route('/show_test/setData/') # 路由
def setData():
db = pymysql.connect(host='localhost', user='root', password='123456', database='pyc', charset='utf8')
cursor = db.cursor()
cursor.execute("Select count(*) from face_data")
number = cursor.fetchall()
# print(number[0][0])
cursor.execute("Select new1 from face_data")
new1 = cursor.fetchall()
new1 = list(new1)
for i in range(0, number[0][0]):
data = {'id': i, 'h': new1[i]}
print(data)
return jsonify(data) # 将数据以字典的形式传回
并将templates文件夹中的image.html文件修改如下:
# templates/image.html
虚拟主播动态折线图展示
这是人脸水平旋转量的动态折线图展示
再次运行,点击主页中的“展示动态折线图”,便可在http://127.0.0.1:5000/show_test中看到用ajax画的动态折线图。这部分代码我参考了以下文章,该文章是每隔一秒随机生成一个数字传到前端进行展示,而我企图实时获取已经存入数据库中的数据进行前端展示,结果显示后端只把数据库中的最后一条数据传到前端了。Flask框架中利用Ajax制作动态折线图_我是一只程序⚪的博客-CSDN博客_flask折线图制作动态折线图,在视图中需要两个函数,其一为页面函数,用于页面显示。另一个为折线图数据视图函数,用来生成数据传递给Ajax。创建前端页面,名为image的html页面,然后准备视图函数:函数一:关联HTML页面#函数一:关联页面@app.route('/') #路由def image(): return render_template('image.html')...https://blog.csdn.net/weixin_39561473/article/details/86608661 其实原因就在于我的代码在传递数据到前端之前对数据进行了一次for循环遍历,最后结果肯定停留在最后一条数据上。有人会说把return语句放在for循环里试试呢?这个我试过了也是不行的,因为前端从后端获取数据的时候会调用整个setData函数,这就意味着每次调用的时候循环次数都会清零,所以我这部分代码在逻辑上有很大的问题,但由于时间关系,我没能对其进行进一步的研究,所以只能暂时搁置,至于能不能真的实现我还不太清楚。或许有大佬可以在评论区解答我的疑惑~