高通手机跑AI系列之——人脸变化算法

环境准备

手机

测试手机型号:Redmi K60 Pro

处理器:第二代骁龙8移动--8gen2

运行内存:8.0GB ,LPDDR5X-8400,67.0 GB/s

摄像头:前置16MP+后置50MP+8MP+2MP

AI算力:NPU 48Tops INT8 && GPU 1536ALU x 2 x 680MHz = 2.089 TFLOPS

提示:任意手机均可以,性能越好的手机速度越快

软件

APP:AidLux 2.0

系统环境:Ubuntu 20.04.3 LTS

提示:AidLux登录后代码运行更流畅,在代码运行时保持AidLux APP在前台运行,避免代码运行过程中被系统回收进程,另外屏幕保持常亮,一般息屏后一段时间,手机系统会进入休眠状态,如需长驻后台需要给APP权限。

算法Demo

代码功能详解

这段代码通过AidLlite推理引擎实现了一个基于计算机视觉的实时人脸美化应用,主要结合了人脸检测、关键点定位、图像变换和融合等技术。下面从整体架构和核心功能两方面进行解析:

整体架构

代码主要由以下几个部分组成:

  • 人脸检测模块:使用 BlazeFace 模型识别视频中的人脸
  • 关键点检测模块:定位人脸的 468 个关键点
  • 人脸变换模块:通过仿射变换和三角剖分实现人脸对齐
  • 图像融合模块:将源人脸与目标人脸无缝融合
  • 用户交互模块:提供 UI 界面选择不同的目标人脸图像
核心功能解析
  1. 人脸检测与预处理
# 使用BlazeFace模型进行人脸检测
def blazeface(raw_output_a, raw_output_b, anchors):# 解码边界框和分数detections = net.tensors_to_detections(raw_box_tensor, raw_score_tensor, anchors)# 非极大值抑制过滤重叠检测filtered_detections = net.weighted_non_max_suppression(detections[i])

通过 TFLite 模型face_detection_front.tflite检测人脸,返回边界框和关键点坐标,再通过非极大值抑制优化检测结果。

  1. 人脸关键点定位
# 检测人脸的468个关键点
model_path1 = "models/face_landmark.tflite"
mesh = fast_interpreter1.get_output_tensor(0)
mesh = mesh.reshape(468, 3) / 192

使用face_landmark.tflite模型定位眼睛、嘴巴、鼻子等关键部位的坐标,为后续人脸变换提供基础。

  1. 人脸变换与融合
# 基于Delaunay三角剖分的人脸变换
def warpTriangle(img1, img2, t1, t2):# 计算仿射变换矩阵warpMat = cv2.getAffineTransform(np.float32(srcTri), np.float32(dstTri))# 应用变换并融合dst = cv2.warpAffine(src, warpMat, (size[0], size[1]))

将人脸区域划分为三角形网格,对每个三角形应用仿射变换,再通过cv2.seamlessClone实现无缝融合。

  1. 用户交互界面
# 创建UI界面选择目标人脸
class MyApp(App):def main(self):# 创建摄像头组件和图像选择按钮self.img1 = Image('/res:' + back_img_path[0], height=80, margin='10px')self.img1.onclick.do(self.on_img1_clicked)

提供图形界面让用户选择不同的目标人脸图像,点击图片即可切换。

模型作用分析

代码中使用了两个关键的 TFLite 模型:

  1. face_detection_front.tflite

    • 类型:人脸检测模型
    • 作用:在输入图像中定位人脸位置,输出边界框和 6 个关键点坐标 (眼睛、鼻子、嘴角等)
    • 技术特点
      • 轻量级设计,适合实时应用
      • 使用锚点机制检测不同尺度的人脸
      • 输出包括边界框坐标和关键点位置
  2. face_landmark.tflite

    • 类型:人脸关键点检测模型
    • 作用:检测人脸的 468 个精确关键点,覆盖眉毛、眼睛、鼻子、嘴巴和脸部轮廓
    • 技术特点
      • 输出 468 个 3D 坐标点,提供精细的人脸形状描述
      • 用于人脸对齐、表情分析等高级应用
      • 模型输入为 192x192 的图像,输出为 468 个 3D 坐标

应用场景

该人脸变换和美化应用适用于以下场景:

  1. 娱乐与社交媒体

    • 短视频特效制作
    • 社交平台实时滤镜
    • 趣味照片编辑工具
  2. 影视制作与广告

    • 电影特效中的人脸替换
    • 广告中实现明星脸替换效果
    • 虚拟主播的面部表情迁移
  3. 教育与演示

    • 计算机视觉原理教学演示
    • 人脸图像处理技术展示
    • 机器学习模型应用案例
  4. 特殊行业应用

    • 安防领域的人脸模拟
    • 虚拟现实中的面部表情同步
    • 医学领域的面部畸形模拟与修复预览

技术特点与优势

  1. 实时性:通过轻量级 TFLite 模型和优化的计算流程,实现实时人脸变换
  2. 鲁棒性:使用 Delaunay 三角剖分和无缝克隆技术,确保不同表情和角度下的效果
  3. 易用性:提供图形界面,用户可轻松选择不同的目标人脸
  4. 可扩展性:模型与业务逻辑分离,便于替换更高精度的模型或添加新功能

该应用结合了计算机视觉和机器学习技术,展示了现代人脸处理的核心流程,具有较强的实用性和拓展空间。

示例代码

import cv2
import math
import sys
import numpy as np
import os
import subprocess
import time
from cvs import *
import aidlite# 背景图像路径列表
back_img_path = ('models/rs.jpeg', 'models/wy.jpeg', 'models/zyx.jpeg', 'models/monkey.jpg', 'models/star2.jpg', 'models/star1.jpg', 'models/star3.jpg', 'models/star4.jpg')# 读取第一张背景图像
faceimg = cv2.imread(back_img_path[0])
mod = -1
bfirstframe = True# 从文件中读取关键点
def readPoints(path):# 创建一个关键点数组points = []# 打开文件读取关键点with open(path) as file:for line in file:x, y = line.split()points.append((int(x), int(y)))return points# 应用仿射变换
def applyAffineTransform(src, srcTri, dstTri, size):# 计算仿射变换矩阵warpMat = cv2.getAffineTransform(np.float32(srcTri), np.float32(dstTri))# 应用仿射变换到源图像dst = cv2.warpAffine(src, warpMat, (size[0], size[1]), None, flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT_101)return dst# 检查点是否在矩形内
def rectContains(rect, point):if point[0] < rect[0]:return Falseelif point[1] < rect[1]:return Falseelif point[0] > rect[0] + rect[2]:return Falseelif point[1] > rect[1] + rect[3]:return Falsereturn True# 计算Delaunay三角形
def calculateDelaunayTriangles(rect, points):# 创建Subdiv2D对象subdiv = cv2.Subdiv2D(rect)ttp = None# 将关键点插入到Subdiv2D对象中for p in points:try:subdiv.insert(p)ttp = pexcept:subdiv.insert(ttp)continue# 获取三角形列表triangleList = subdiv.getTriangleList()delaunayTri = []pt = []for t in triangleList:pt.append((t[0], t[1]))pt.append((t[2], t[3]))pt.append((t[4], t[5]))pt1 = (t[0], t[1])pt2 = (t[2], t[3])pt3 = (t[4], t[5])# 检查三角形的三个顶点是否都在矩形内if rectContains(rect, pt1) and rectContains(rect, pt2) and rectContains(rect, pt3):ind = []# 获取关键点的索引for j in range(0, 3):for k in range(0, len(points)):if (abs(pt[j][0] - points[k][0]) < 1.0 and abs(pt[j][1] - points[k][1]) < 1.0):ind.append(k)# 如果索引列表长度为3,则将其添加到Delaunay三角形列表中if len(ind) == 3:delaunayTri.append((ind[0], ind[1], ind[2]))pt = []return delaunayTri# 对三角形区域进行变形和融合
def warpTriangle(img1, img2, t1, t2):# 找到每个三角形的边界矩形r1 = cv2.boundingRect(np.float32([t1]))r2 = cv2.boundingRect(np.float32([t2]))# 偏移关键点t1Rect = []t2Rect = []t2RectInt = []for i in range(0, 3):t1Rect.append(((t1[i][0] - r1[0]), (t1[i][1] - r1[1])))t2Rect.append(((t2[i][0] - r2[0]), (t2[i][1] - r2[1])))t2RectInt.append(((t2[i][0] - r2[0]), (t2[i][1] - r2[1])))# 创建掩码mask = np.zeros((r2[3], r2[2], 3), dtype=np.float32)cv2.fillConvexPoly(mask, np.int32(t2RectInt), (1.0, 1.0, 1.0), 16, 0)# 对小矩形区域应用仿射变换img1Rect = img1[r1[1]:r1[1] + r1[3], r1[0]:r1[0] + r1[2]]size = (r2[2], r2[3])img2Rect = applyAffineTransform(img1Rect, t1Rect, t2Rect, size)img2Rect = img2Rect * mask# 将变形后的三角形区域复制到输出图像中img2[r2[1]:r2[1] + r2[3], r2[0]:r2[0] + r2[2]] = img2[r2[1]:r2[1] + r2[3], r2[0]:r2[0] + r2[2]] * ((1.0, 1.0, 1.0) - mask)img2[r2[1]:r2[1] + r2[3], r2[0]:r2[0] + r2[2]] = img2[r2[1]:r2[1] + r2[3], r2[0]:r2[0] + r2[2]] + img2Rect# 人脸变换函数
def faceswap(points1, points2, img1, img2):img1Warped = np.copy(img2)# 找到凸包hull1 = []hull2 = []hullIndex = cv2.convexHull(np.array(points2), returnPoints=False)for i in range(0, len(hullIndex)):hull1.append(points1[int(hullIndex[i])])hull2.append(points2[int(hullIndex[i])])# 计算凸包关键点的Delaunay三角形sizeImg2 = img2.shaperect = (0, 0, sizeImg2[1], sizeImg2[0])dt = calculateDelaunayTriangles(rect, hull2)if len(dt) == 0:quit()# 对Delaunay三角形应用仿射变换for i in range(0, len(dt)):t1 = []t2 = []for j in range(0, 3):t1.append(hull1[dt[i][j]])t2.append(hull2[dt[i][j]])warpTriangle(img1, img1Warped, t1, t2)# 计算掩码hull8U = []for i in range(0, len(hull2)):hull8U.append((hull2[i][0], hull2[i][1]))mask = np.zeros(img2.shape, dtype=img2.dtype)cv2.fillConvexPoly(mask, np.int32(hull8U), (255, 255, 255))r = cv2.boundingRect(np.float32([hull2]))center = ((r[0] + int(r[2] / 2), r[1] + int(r[3] / 2)))# 无缝克隆try:output = cv2.seamlessClone(np.uint8(img1Warped), img2, mask, center, cv2.NORMAL_CLONE)except:return Nonereturn output# 对图像进行预处理,用于TFLite模型
def preprocess_image_for_tflite32(image, model_image_size=192):# 将图像从BGR颜色空间转换为RGB颜色空间image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)# 调整图像大小image = cv2.resize(image, (model_image_size, model_image_size))# 添加一个维度image = np.expand_dims(image, axis=0)# 归一化处理image = (2.0 / 255.0) * image - 1.0# 将图像数据类型转换为float32image = image.astype('float32')return image# 对图像进行填充和预处理
def preprocess_img_pad(img, image_size=128):# 获取图像的形状shape = np.r_[img.shape]# 计算需要填充的像素数pad_all = (shape.max() - shape[:2]).astype('uint32')pad = pad_all // 2# 对原始图像进行填充img_pad_ori = np.pad(img,((pad[0], pad_all[0] - pad[0]), (pad[1], pad_all[1] - pad[1]), (0, 0)),mode='constant')# 将图像从BGR颜色空间转换为RGB颜色空间img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 对RGB图像进行填充img_pad = np.pad(img,((pad[0], pad_all[0] - pad[0]), (pad[1], pad_all[1] - pad[1]), (0, 0)),mode='constant')# 调整图像大小img_small = cv2.resize(img_pad, (image_size, image_size))# 添加一个维度img_small = np.expand_dims(img_small, axis=0)# 归一化处理img_small = (2.0 / 255.0) * img_small - 1.0# 将图像数据类型转换为float32img_small = img_small.astype('float32')return img_pad_ori, img_small, pad# 绘制检测到的人脸框
def plot_detections(img, detections, with_keypoints=True):output_img = imgprint(img.shape)x_min = 0x_max = 0y_min = 0y_max = 0print("找到 %d 个人脸" % len(detections))for i in range(len(detections)):# 计算人脸框的坐标ymin = detections[i][0] * img.shape[0]xmin = detections[i][1] * img.shape[1]ymax = detections[i][2] * img.shape[0]xmax = detections[i][3] * img.shape[1]w = int(xmax - xmin)h = int(ymax - ymin)h = max(w, h)h = h * 1.5x = (xmin + xmax) / 2.y = (ymin + ymax) / 2.xmin = x - h / 2.xmax = x + h / 2.ymin = y - h / 2. - 0.08 * hymax = y + h / 2. - 0.08 * hx_min = int(xmin)y_min = int(ymin)x_max = int(xmax)y_max = int(ymax)p1 = (int(xmin), int(ymin))p2 = (int(xmax), int(ymax))# 绘制人脸框cv2.rectangle(output_img, p1, p2, (0, 255, 255), 2, 1)return x_min, y_min, x_max, y_max# 绘制人脸网格
def draw_mesh(image, mesh, mark_size=2, line_width=1):# 获取图像的大小image_size = image.shape[0]# 将归一化的网格坐标转换为图像坐标mesh = mesh * image_size# 绘制关键点for point in mesh:cv2.circle(image, (point[0], point[1]),mark_size, (0, 255, 128), -1)# 绘制眼睛轮廓left_eye_contour = np.array([mesh[33][0:2],mesh[7][0:2],mesh[163][0:2],mesh[144][0:2],mesh[145][0:2],mesh[153][0:2],mesh[154][0:2],mesh[155][0:2],mesh[133][0:2],mesh[173][0:2],mesh[157][0:2],mesh[158][0:2],mesh[159][0:2],mesh[160][0:2],mesh[161][0:2],mesh[246][0:2]]).astype(np.int32)right_eye_contour = np.array([mesh[263][0:2],mesh[249][0:2],mesh[390][0:2],mesh[373][0:2],mesh[374][0:2],mesh[380][0:2],mesh[381][0:2],mesh[382][0:2],mesh[362][0:2],mesh[398][0:2],mesh[384][0:2],mesh[385][0:2],mesh[386][0:2],mesh[387][0:2],mesh[388][0:2],mesh[466][0:2]]).astype(np.int32)# 绘制眼睛轮廓线cv2.polylines(image, [left_eye_contour, right_eye_contour], False,(255, 255, 255), line_width, cv2.LINE_AA)# 获取关键点
def getkeypoint(image, mesh, landmark_point):# 获取图像的大小image_size = image.shape[0]# 将归一化的网格坐标转换为图像坐标mesh = mesh * image_size# 将关键点添加到列表中for point in mesh:landmark_point.append((point[0], point[1]))return image# 绘制关键点和面部特征线
def draw_landmarks(image, mesh, landmark_point):# 获取图像的大小image_size = image.shape[0]# 将归一化的网格坐标转换为图像坐标mesh = mesh * image_size# 绘制关键点for point in mesh:landmark_point.append((point[0], point[1]))cv2.circle(image, (point[0], point[1]), 2, (255, 255, 0), -1)if len(landmark_point) > 0:# 绘制左眉毛cv2.line(image, landmark_point[55], landmark_point[65], (0, 0, 255), 2, -3)cv2.line(image, landmark_point[65], landmark_point[52], (0, 0, 255), 2, -3)cv2.line(image, landmark_point[52], landmark_point[53], (0, 0, 255), 2, -3)cv2.line(image, landmark_point[53], landmark_point[46], (0, 0, 255), 2, -3)# 绘制右眉毛cv2.line(image, landmark_point[285], landmark_point[295], (0, 0, 255), 2)cv2.line(image, landmark_point[295], landmark_point[282], (0, 0, 255), 2)cv2.line(image, landmark_point[282], landmark_point[283], (0, 0, 255), 2)cv2.line(image, landmark_point[283], landmark_point[276], (0, 0, 255), 2)# 绘制左眼睛cv2.line(image, landmark_point[133], landmark_point[173], (0, 0, 255), 2)cv2.line(image, landmark_point[173], landmark_point[157], (0, 0, 255), 2)cv2.line(image, landmark_point[157], landmark_point[158], (0, 0, 255), 2)cv2.line(image, landmark_point[158], landmark_point[159], (0, 0, 255), 2)cv2.line(image, landmark_point[159], landmark_point[160], (0, 0, 255), 2)cv2.line(image, landmark_point[160], landmark_point[161], (0, 0, 255), 2)cv2.line(image, landmark_point[161], landmark_point[246], (0, 0, 255), 2)cv2.line(image, landmark_point[246], landmark_point[163], (0, 0, 255), 2)cv2.line(image, landmark_point[163], landmark_point[144], (0, 0, 255), 2)cv2.line(image, landmark_point[144], landmark_point[145], (0, 0, 255), 2)cv2.line(image, landmark_point[145], landmark_point[153], (0, 0, 255), 2)cv2.line(image, landmark_point[153], landmark_point[154], (0, 0, 255), 2)cv2.line(image, landmark_point[154], landmark_point[155], (0, 0, 255), 2)cv2.line(image, landmark_point[155], landmark_point[133], (0, 0, 255), 2)# 绘制右眼睛cv2.line(image, landmark_point[362], landmark_point[398], (0, 0, 255), 2)cv2.line(image, landmark_point[398], landmark_point[384], (0, 0, 255), 2)cv2.line(image, landmark_point[384], landmark_point[385], (0, 0, 255), 2)cv2.line(image, landmark_point[385], landmark_point[386], (0, 0, 255), 2)cv2.line(image, landmark_point[386], landmark_point[387], (0, 0, 255), 2)cv2.line(image, landmark_point[387], landmark_point[388], (0, 0, 255), 2)cv2.line(image, landmark_point[388], landmark_point[466], (0, 0, 255), 2)cv2.line(image, landmark_point[466], landmark_point[390], (0, 0, 255), 2)cv2.line(image, landmark_point[390], landmark_point[373], (0, 0, 255), 2)cv2.line(image, landmark_point[373], landmark_point[374], (0, 0, 255), 2)cv2.line(image, landmark_point[374], landmark_point[380], (0, 0, 255), 2)cv2.line(image, landmark_point[380], landmark_point[381], (0, 0, 255), 2)cv2.line(image, landmark_point[381], landmark_point[382], (0, 0, 255), 2)cv2.line(image, landmark_point[382], landmark_point[362], (0, 0, 255), 2)# 绘制嘴巴cv2.line(image, landmark_point[308], landmark_point[415], (0, 0, 255), 2)cv2.line(image, landmark_point[415], landmark_point[310], (0, 0, 255), 2)cv2.line(image, landmark_point[310], landmark_point[311], (0, 0, 255), 2)cv2.line(image, landmark_point[311], landmark_point[312], (0, 0, 255), 2)cv2.line(image, landmark_point[312], landmark_point[13], (0, 0, 255), 2)cv2.line(image, landmark_point[13], landmark_point[82], (0, 0, 255), 2)cv2.line(image, landmark_point[82], landmark_point[81], (0, 0, 255), 2)cv2.line(image, landmark_point[81], landmark_point[80], (0, 0, 255), 2)cv2.line(image, landmark_point[80], landmark_point[191], (0, 0, 255), 2)cv2.line(image, landmark_point[191], landmark_point[78], (0, 0, 255), 2)cv2.line(image, landmark_point[78], landmark_point[95], (0, 0, 255), 2)cv2.line(image, landmark_point[95], landmark_point[88], (0, 0, 255), 2)cv2.line(image, landmark_point[88], landmark_point[178], (0, 0, 255), 2)cv2.line(image, landmark_point[178], landmark_point[87], (0, 0, 255), 2)cv2.line(image, landmark_point[87], landmark_point[14], (0, 0, 255), 2)cv2.line(image, landmark_point[14], landmark_point[317], (0, 0, 255), 2)cv2.line(image, landmark_point[317], landmark_point[402], (0, 0, 255), 2)cv2.line(image, landmark_point[402], landmark_point[318], (0, 0, 255), 2)cv2.line(image, landmark_point[318], landmark_point[324], (0, 0, 255), 2)cv2.line(image, landmark_point[324], landmark_point[308], (0, 0, 255), 2)return image# BlazeFace人脸检测模型类
class BlazeFace():def __init__(self):# 类别数量self.num_classes = 1# 锚点数量self.num_anchors = 896# 坐标数量self.num_coords = 16# 分数裁剪阈值self.score_clipping_thresh = 100.0# x坐标缩放因子self.x_scale = 128.0# y坐标缩放因子self.y_scale = 128.0# 高度缩放因子self.h_scale = 128.0# 宽度缩放因子self.w_scale = 128.0# 最小分数阈值self.min_score_thresh = 0.75# 最小抑制阈值self.min_suppression_threshold = 0.3# Sigmoid函数def sigmoid(self, inX):if inX >= 0:return 1.0 / (1 + np.exp(-inX))else:return np.exp(inX) / (1 + np.exp(inX))# 将原始输出张量转换为检测结果def tensors_to_detections(self, raw_box_tensor, raw_score_tensor, anchors):assert len(raw_box_tensor.shape) == 3assert raw_box_tensor.shape[1] == self.num_anchorsassert raw_box_tensor.shape[2] == self.num_coordsassert len(raw_score_tensor.shape) == 3assert raw_score_tensor.shape[1] == self.num_anchorsassert raw_score_tensor.shape[2] == self.num_classesassert raw_box_tensor.shape[0] == raw_score_tensor.shape[0]# 解码边界框detection_boxes = self._decode_boxes(raw_box_tensor, anchors)# 裁剪分数thresh = self.score_clipping_threshraw_score_tensor = raw_score_tensor.clip(-thresh, thresh)# 计算检测分数detection_scores = 1 / (1 + np.exp(-raw_score_tensor)).squeeze(axis=-1)# 过滤掉分数低于阈值的检测结果mask = detection_scores >= self.min_score_threshoutput_detections = []for i in range(raw_box_tensor.shape[0]):boxes = detection_boxes[i, mask[i]]scores = np.expand_dims(detection_scores[i, mask[i]], axis=-1)output_detections.append(np.concatenate((boxes, scores), axis=-1))return output_detections# 解码边界框def _decode_boxes(self, raw_boxes, anchors):boxes = np.zeros(raw_boxes.shape)# 计算边界框的中心点坐标x_center = raw_boxes[..., 0] / self.x_scale * anchors[:, 2] + anchors[:, 0]y_center = raw_boxes[..., 1] / self.y_scale * anchors[:, 3] + anchors[:, 1]# 计算边界框的宽度和高度w = raw_boxes[..., 2] / self.w_scale * anchors[:, 2]h = raw_boxes[..., 3] / self.h_scale * anchors[:, 3]# 计算边界框的左上角和右下角坐标boxes[..., 0] = y_center - h / 2.  # yminboxes[..., 1] = x_center - w / 2.  # xminboxes[..., 2] = y_center + h / 2.  # ymaxboxes[..., 3] = x_center + w / 2.  # xmax# 计算关键点坐标for k in range(6):offset = 4 + k * 2keypoint_x = raw_boxes[..., offset] / self.x_scale * anchors[:, 2] + anchors[:, 0]keypoint_y = raw_boxes[..., offset + 1] / self.y_scale * anchors[:, 3] + anchors[:, 1]boxes[..., offset] = keypoint_xboxes[..., offset + 1] = keypoint_yreturn boxes# 加权非极大值抑制def weighted_non_max_suppression(self, detections):if len(detections) == 0: return []output_detections = []# 按分数从高到低排序remaining = np.argsort(-detections[:, 16])while len(remaining) > 0:detection = detections[remaining[0]]# 计算第一个框与其他框的重叠度first_box = detection[:4]other_boxes = detections[remaining, :4]ious = overlap_similarity(first_box, other_boxes)# 过滤掉重叠度低于阈值的框mask = ious > self.min_suppression_thresholdoverlapping = remaining[mask]remaining = remaining[~mask]# 计算加权检测结果weighted_detection = detection.copy()if len(overlapping) > 1:coordinates = detections[overlapping, :16]scores = detections[overlapping, 16:17]total_score = scores.sum()weighted = (coordinates * scores).sum(axis=0) / total_scoreweighted_detection[:16] = weightedweighted_detection[16] = total_score / len(overlapping)output_detections.append(weighted_detection)return output_detections# BlazeFace人脸检测函数
def blazeface(raw_output_a, raw_output_b, anchors):if raw_output_a.size == 896:raw_score_tensor = raw_output_araw_box_tensor = raw_output_belse:raw_score_tensor = raw_output_braw_box_tensor = raw_output_aassert (raw_score_tensor.size == 896)assert (raw_box_tensor.size == 896 * 16)# 调整输出张量的形状raw_score_tensor = raw_score_tensor.reshape(1, 896, 1)raw_box_tensor = raw_box_tensor.reshape(1, 896, 16)net = BlazeFace()# 后处理原始预测结果detections = net.tensors_to_detections(raw_box_tensor, raw_score_tensor, anchors)# 非极大值抑制filtered_detections = []for i in range(len(detections)):faces = net.weighted_non_max_suppression(detections[i])if len(faces) > 0:faces = np.stack(faces)filtered_detections.append(faces)return filtered_detections# 将检测结果从填充图像坐标转换为原始图像坐标
def convert_to_orig_points(results, orig_dim, letter_dim):# 计算缩放比例inter_scale = min(letter_dim / orig_dim[0], letter_dim / orig_dim[1])inter_h, inter_w = int(inter_scale * orig_dim[0]), int(inter_scale * orig_dim[1])# 计算偏移量offset_x, offset_y = (letter_dim - inter_w) / 2.0 / letter_dim, (letter_dim - inter_h) / 2.0 / letter_dimscale_x, scale_y = letter_dim / inter_w, letter_dim / inter_h# 调整检测结果的坐标results[:, 0:2] = (results[:, 0:2] - [offset_x, offset_y]) * [scale_x, scale_y]results[:, 2:4] = results[:, 2:4] * [scale_x, scale_y]results[:, 4:16:2] = (results[:, 4:16:2] - offset_x) * scale_xresults[:, 5:17:2] = (results[:, 5:17:2] - offset_y) * scale_y# 将坐标从0-1范围转换为原始图像范围results[:, 0:16:2] *= orig_dim[1]results[:, 1:17:2] *= orig_dim[0]return results.astype(np.int32)# 计算两个边界框的交并比(IoU)
def overlap_similarity(box, other_boxes):def union(A, B):x1, y1, x2, y2 = Aa = (x2 - x1) * (y2 - y1)x1, y1, x2, y2 = Bb = (x2 - x1) * (y2 - y1)ret = a + b - intersect(A, B)return retdef intersect(A, B):x1 = max(A[0], B[0])y1 = max(A[1], B[1])x2 = min(A[2], B[2])y2 = min(A[3], B[3])return (x2 - x1) * (y2 - y1)ret = np.array([max(0, intersect(box, b) / union(box, b)) for b in other_boxes])return ret# 自定义应用类
class MyApp(App):def __init__(self, *args):super(MyApp, self).__init__(*args)# 空闲时更新摄像头def idle(self):self.aidcam0.update()# 主函数,创建UI界面def main(self):# 创建垂直容器main_container = VBox(width=360, height=680, style={'margin': '0px auto'})# 创建摄像头组件self.aidcam0 = OpencvVideoWidget(self, width=340, height=400)self.aidcam0.style['margin'] = '10px'i = 0exec("self.aidcam%(i)s = OpencvVideoWidget(self)" % {'i': i})exec("self.aidcam%(i)s.identifier = 'aidcam%(i)s'" % {'i': i})eval("main_container.append(self.aidcam%(i)s)" % {'i': i})main_container.append(self.aidcam0)# 创建标签self.lbl = Label('点击图片选择你喜欢的明星脸:')main_container.append(self.lbl)# 创建底部容器bottom_container = HBox(width=360, height=130, style={'margin': '0px auto'})# 创建图像组件self.img1 = Image('/res:' + os.getcwd() + '/' + back_img_path[0], height=80, margin='10px')self.img1.onclick.do(self.on_img1_clicked)bottom_container.append(self.img1)self.img2 = Image('/res:' + os.getcwd() + '/' + back_img_path[1], height=80, margin='10px')self.img2.onclick.do(self.on_img2_clicked)bottom_container.append(self.img2)self.img3 = Image('/res:' + os.getcwd() + '/' + back_img_path[2], height=80, margin='10px')self.img3.onclick.do(self.on_img3_clicked)bottom_container.append(self.img3)self.img4 = Image('/res:' + os.getcwd() + '/' + back_img_path[3], height=80, margin='10px')self.img4.onclick.do(self.on_img4_clicked)bottom_container.append(self.img4)# 创建按钮容器bt_container = HBox(width=360, height=130, style={'margin': '0px auto'})self.img11 = Image('/res:' + os.getcwd() + '/' + back_img_path[4], height=80, margin='10px')self.img11.onclick.do(self.on_img11_clicked)bt_container.append(self.img11)self.img22 = Image('/res:' + os.getcwd() + '/' + back_img_path[5], height=80, margin='10px')self.img22.onclick.do(self.on_img22_clicked)bt_container.append(self.img22)self.img33 = Image('/res:' + os.getcwd() + '/' + back_img_path[6], height=80, margin='10px')self.img33.onclick.do(self.on_img33_clicked)bt_container.append(self.img33)self.img44 = Image('/res:' + os.getcwd() + '/' + back_img_path[7], height=80, margin='10px')self.img44.onclick.do(self.on_img44_clicked)bt_container.append(self.img44)main_container.append(bottom_container)main_container.append(bt_container)return main_container# 点击第一张图片的回调函数def on_img1_clicked(self, widget):global faceimgbgnd = cv2.imread(back_img_path[0])faceimg = bgndglobal modmod = 0# 点击第二张图片的回调函数def on_img2_clicked(self, widget):global faceimgbgnd = cv2.imread(back_img_path[1])faceimg = bgndglobal modmod = 1# 点击第三张图片的回调函数def on_img3_clicked(self, widget):global faceimgbgnd = cv2.imread(back_img_path[2])faceimg = bgndglobal modmod = 2# 点击第四张图片的回调函数def on_img4_clicked(self, widget):global faceimgbgnd = cv2.imread(back_img_path[3])faceimg = bgndglobal modmod = 3# 点击第五张图片的回调函数def on_img11_clicked(self, widget):global faceimgbgnd = cv2.imread(back_img_path[4])faceimg = bgndglobal modmod = 4# 点击第六张图片的回调函数def on_img22_clicked(self, widget):global faceimgbgnd = cv2.imread(back_img_path[5])faceimg = bgndglobal modmod = 5# 点击第七张图片的回调函数def on_img33_clicked(self, widget):global faceimgbgnd = cv2.imread(back_img_path[6])faceimg = bgndglobal modmod = 6# 点击第八张图片的回调函数def on_img44_clicked(self, widget):global faceimgbgnd = cv2.imread(back_img_path[7])faceimg = bgndglobal modmod = 7# 点击第一个按钮的回调函数def on_button_pressed1(self, widget):global modmod = 0# 点击第二个按钮的回调函数def on_button_pressed2(self, widget):global modmod = 1# 点击第三个按钮的回调函数def on_button_pressed3(self, widget):global modmod = 2# 获取摄像头ID
def get_cap_id():try:# 构造命令,使用awk处理输出cmd = "ls -l /sys/class/video4linux | awk -F ' -> ' '/usb/{sub(/.*video/, \"\", $2); print $2}'"result = subprocess.run(cmd, shell=True, capture_output=True, text=True)output = result.stdout.strip().split()# 转换所有捕获的编号为整数,找出最小值video_numbers = list(map(int, output))if video_numbers:return min(video_numbers)else:return Noneexcept Exception as e:print(f"发生错误: {e}")return None# 处理函数,实现人脸变换
def process():cvs.setCustomUI()# 初始化人脸检测模型inShape = [[1, 128, 128, 3]]outShape = [[1, 896, 16], [1, 896, 1]]model_path = "models/face_detection_front.tflite"model = aidlite.Model.create_instance(model_path)if model is None:print("创建face_detection_front模型失败!")model.set_model_properties(inShape, aidlite.DataType.TYPE_FLOAT32, outShape, aidlite.DataType.TYPE_FLOAT32)config = aidlite.Config.create_instance()config.implement_type = aidlite.ImplementType.TYPE_FASTconfig.framework_type = aidlite.FrameworkType.TYPE_TFLITEconfig.accelerate_type = aidlite.AccelerateType.TYPE_CPUconfig.number_of_threads = 4fast_interpreter = aidlite.InterpreterBuilder.build_interpretper_from_model_and_config(model, config)if fast_interpreter is None:print("face_detection_front模型build_interpretper_from_model_and_config失败!")result = fast_interpreter.init()if result != 0:print("face_detection_front模型解释器初始化失败!")result = fast_interpreter.load_model()if result != 0:print("face_detection_front模型解释器加载模型失败!")print("face_detection_front模型加载成功!")# 初始化人脸关键点检测模型model_path1 = "models/face_landmark.tflite"inShape1 = [[1 * 192 * 192 * 3]]outShape1 = [[1 * 1404 * 4], [1 * 4]]model1 = aidlite.Model.create_instance(model_path1)if model1 is None:print("创建face_landmark模型失败!")model1.set_model_properties(inShape1, aidlite.DataType.TYPE_FLOAT32, outShape1, aidlite.DataType.TYPE_FLOAT32)config1 = aidlite.Config.create_instance()config1.implement_type = aidlite.ImplementType.TYPE_FASTconfig1.framework_type = aidlite.FrameworkType.TYPE_TFLITEconfig1.accelerate_type = aidlite.AccelerateType.TYPE_GPUconfig1.number_of_threads = 4fast_interpreter1 = aidlite.InterpreterBuilder.build_interpretper_from_model_and_config(model1, config1)if fast_interpreter1 is None:print("face_landmark模型build_interpretper_from_model_and_config失败!")result = fast_interpreter1.init()if result != 0:print("face_landmark模型解释器初始化失败!")result = fast_interpreter1.load_model()if result != 0:print("face_landmark模型解释器加载模型失败!")print("face_landmark模型加载成功!")# 加载锚点anchors = np.load('models/anchors.npy').astype(np.float32)# 0-后置,1-前置camid = 1capId = get_cap_id()if capId is None:print("使用MIPI摄像头")else:print("使用USB摄像头")camid = -1cap = cvs.VideoCapture(camid)bFace = Falsex_min, y_min, x_max, y_max = (0, 0, 0, 0)fface = 0.0global bfirstframebfirstframe = Truefacepath = "Biden.jpeg"global faceimgfaceimg = cv2.resize(faceimg, (256, 256))roi_orifirst = faceimgpadfaceimg = faceimgfpoints = []spoints = []global modmod = -1while True:# 读取帧frame = cvs.read()if frame is None:continueif camid == 1:frame = cv2.flip(frame, 1)if mod > -1 or bfirstframe:x_min, y_min, x_max, y_max = (0, 0, 0, 0)faceimg = cv2.resize(faceimg, (256, 256))frame = faceimgbFace = Falseroi_orifirst = faceimgpadfaceimg = faceimgbfirstframe = Truefpoints = []spoints = []# 记录开始时间start_time = time.time()# 对图像进行填充和预处理img_pad, img, pad = preprocess_img_pad(frame, 128)if bFace == False:# 设置输入数据result = fast_interpreter.set_input_tensor(0, img.data)if result != 0:print("face_detection_front模型解释器set_input_tensor()失败")# 执行推理result = fast_interpreter.invoke()if result != 0:print("face_detection_front模型解释器invoke()失败")# 获取输出数据raw_boxes = fast_interpreter.get_output_tensor(0)if raw_boxes is None:print("示例: face_detection_front模型解释器->get_output_tensor(0)失败!")classificators = fast_interpreter.get_output_tensor(1)if classificators is None:print("示例: face_detection_front模型解释器->get_output_tensor(1)失败!")# 进行人脸检测detections = blazeface(raw_boxes, classificators, anchors)[0]if len(detections) > 0:bFace = Trueif bFace:for i in range(len(detections)):# 计算人脸框的坐标ymin = detections[i][0] * img_pad.shape[0]xmin = detections[i][1] * img_pad.shape[1]ymax = detections[i][2] * img_pad.shape[0]xmax = detections[i][3] * img_pad.shape[1]w = int(xmax - xmin)h = int(ymax - ymin)h = max(w, h)h = h * 1.5x = (xmin + xmax) / 2.y = (ymin + ymax) / 2.xmin = x - h / 2.xmax = x + h / 2.ymin = y - h / 2.ymax = y + h / 2.ymin = y - h / 2. - 0.08 * hymax = y + h / 2. - 0.08 * hx_min = int(xmin)y_min = int(ymin)x_max = int(xmax)y_max = int(ymax)x_min = max(0, x_min)y_min = max(0, y_min)x_max = min(img_pad.shape[1], x_max)y_max = min(img_pad.shape[0], y_max)roi_ori = img_pad[y_min:y_max, x_min:x_max]roi = preprocess_image_for_tflite32(roi_ori, 192)# 设置输入数据result = fast_interpreter1.set_input_tensor(0, roi.data)if result != 0:print("face_landmark模型解释器set_input_tensor()失败")# 执行推理result = fast_interpreter1.invoke()if result != 0:print("face_landmark模型解释器invoke()失败")# 获取输出数据mesh = fast_interpreter1.get_output_tensor(0)if mesh is None:print("示例: face_landmark模型解释器->get_output_tensor(0)失败!")stride8 = fast_interpreter1.get_output_tensor(1)if stride8 is None:print("示例: face_landmark模型解释器->get_output_tensor(1)失败!")ffacetmp = stride8[0]print('fface:', abs(fface - ffacetmp))if abs(fface - ffacetmp) > 0.5:bFace = Falsefface = ffacetmpspoints = []mesh = mesh.reshape(468, 3) / 192if bfirstframe:# 获取关键点getkeypoint(roi_ori, mesh, fpoints)roi_orifirst = roi_ori.copy()bfirstframe = Falsemod = -1else:# 获取关键点getkeypoint(roi_ori, mesh, spoints)# 进行人脸变换roi_ori = faceswap(fpoints, spoints, roi_orifirst, roi_ori)if roi_ori is None:continueimg_pad[y_min:y_max, x_min:x_max] = roi_orishape = frame.shapex, y = img_pad.shape[0] / 2, img_pad.shape[1] / 2frame = img_pad[int(y - shape[0] / 2):int(y + shape[0] / 2), int(x - shape[1] / 2):int(x + shape[1] / 2)]# 计算处理时间t = (time.time() - start_time)lbs = 'Fps: ' + str(int(100 / t) / 100.) + " ~~ Time:" + str(t * 1000) + "ms"cvs.setLbs(lbs)# 显示帧cvs.imshow(frame)# 休眠1毫秒time.sleep(0.001)if __name__ == '__main__':initcv(startcv, MyApp)process()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.tpcf.cn/bicheng/86728.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TortoiseSVN 安装教程

前言 TortoiseSVN 是一款流行的 免费开源 的 Subversion (SVN) 客户端&#xff0c;专门为 Windows 操作系统 设计。它通过 Windows 资源管理器集成 提供直观的图形化界面&#xff0c;使用户可以轻松管理版本控制的文件和目录&#xff0c;无需使用命令行。 主要特点 资源管理器…

多张图片生成PDF每张图片生成pdf的一页

需要的库 <!-- 生成pdf--><dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.10</version></dependency><!-- https://mvnrepository.com/artifact/commons-net/commons-n…

Java基础 Map集合框架 LinkedHashMap

LinkedHashMap LinkedHashMap类架构与继承关系核心特性继承自 HashMap有序性插入顺序访问顺序 双向链表结构非线程安全1.并发修改导致数据丢失2.并发迭代导致 ConcurrentModificationException3.并发修改导致链表结构破坏解决方案1. 使用 Collections.synchronizedMap&#xff…

MySQL 离线安装MariaDB

描述 离线环境下安装MySQL数据库&#xff0c;也就是MariaDB 操作 1、找到自带的mysql rpm -qa | grep -i ^mysql-rpm -qa | grep -i ^maria-2、卸载对应的包 rpm --nodeps -ev mysql-libs-5.1.73-8.el6_8.x86_64安装 MariaDb 离线安装包官网下载&#xff1a;地址 这个文…

JSON简介及其应用

JSON简介及其应用 A Brief Introduction and Applications of JSON By JacksonML 1. JSON的概念 JSON&#xff08;JavaScript Object Notation&#xff09; 是一种轻量级的数据交换格式&#xff0c;采用键值对&#xff08;key-value&#xff09;的方式组织数据&#xff0c;…

RNN(循环神经网络)与LSTM(长短期记忆网络)输出的详细对比分析

今天在与同事探讨RNN时&#xff0c;引出了一个主题&#xff0c;RNN和LSTM的输出有什么区别。 以下是关于传统RNN&#xff08;循环神经网络&#xff09;与LSTM&#xff08;长短期记忆网络&#xff09;隐藏层内容、输出结果及模型区别的详细对比分析&#xff0c;结合结构原理、数…

【闲谈】技术债:软件开发的隐形杀手

编程中的“技术债”&#xff1a;隐形杀手与化解之道 在软件开发的世界里&#xff0c;我们常谈性能、安全、架构设计、用户体验等话题&#xff0c;但有一个常被忽视的概念却如影随形、悄然吞噬着项目的健康——技术债&#xff08;Technical Debt&#xff09;。 本文将带你深入…

Elasticsearch | 索引和模板字段管理:增加新字段的详细操作

关注CodingTechWork 背景介绍 Elasticsearch 是一款基于 Lucene 的搜索和数据分析引擎&#xff0c;广泛应用于日志分析、全文检索等领域。在使用 Elasticsearch 时&#xff0c;字段是存储在索引中的数据单位&#xff0c;字段的定义决定了数据的存储方式及其检索效率。因此&…

HTML表格中<tfoot>标签用法详解

在HTML中&#xff0c;<tfoot>标签用于定义表格的页脚&#xff08;表脚&#xff09;&#xff0c;通常包含汇总信息&#xff08;如总计、平均值等&#xff09;。其核心特点和使用方法如下&#xff1a; 基本特性 位置灵活 <tfoot>必须位于<table>内&#xff0c…

深度学习正负样本比例的影响及其调节方法

在深度学习中&#xff0c;数据是模型性能的决定性因素之一。特别是在二分类问题中&#xff0c;正负样本的比例对模型训练的影响尤为显著。本文将探讨正负样本比例对深度学习的影响&#xff0c;并给出相应的调节方法和代码示例。 什么是正负样本比例&#xff1f; 在二分类问题…

【公司经营】安全公司产品经营

一、产品经营 1.1 产品矩阵设计方法&#xff1a;风险场景驱动​ ​分层产品架构​ ​基础层​&#xff1a;防火墙/WAF/EDR&#xff08;标准化硬件软件&#xff09;​分析层​&#xff1a;SOC平台/XDR&#xff08;年订阅制&#xff0c;SaaS化交付&#xff09;​响应层​&#…

鸿蒙 Scroll 组件深度解析:丝滑滚动交互全场景实现

一、引言&#xff1a;Scroll—— 内容溢出场景的交互中枢 在鸿蒙应用开发中&#xff0c;当界面内容超出屏幕可视范围时&#xff0c;Scroll 容器组件成为实现流畅滚动交互的核心方案。作为从 API 7 开始支持的基础组件&#xff0c;它通过极简的属性配置与强大的滚动控制能力&am…

第十节:Vben Admin 最新 v5.0 (vben5) 快速入门 - 菜单管理(下)

Vben5 系列文章目录 💻 基础篇 ✅ 第一节:Vben Admin 最新 v5.0 (vben5) 快速入门 ✅ 第二节:Vben Admin 最新 v5.0 (vben5) 快速入门 - Python Flask 后端开发详解(附源码) ✅ 第三节:Vben Admin 最新 v5.0 (vben5) 快速入门 - 对接后端登录接口(上) ✅ 第四节:Vben Ad…

c#激光设备行业ERP软件进销存软件库存管理软件财务管理软件

# 激光设备行业ERP软件进销存软件库存管理软件财务管理软件 # 开发背景 本软件是给广东河源某客户开发的激光设备行业ERP软件进销存软件库存管理软件财务管理软件 # 功能描述 软件由基础资料、库存管理、 属性管理、 用户管理、 销售管理、 财务管理。主要功能模块是库存管理…

python学习打卡day57

DAY 57 经典时序模型1 知识点回顾 序列数据的处理&#xff1a; 处理非平稳性&#xff1a;n阶差分处理季节性&#xff1a;季节性差分自回归性无需处理 模型的选择 AR(p) 自回归模型&#xff1a;当前值受到过去p个值的影响MA(q) 移动平均模型&#xff1a;当前值收到短期冲击的影响…

python小记(十七):Python 使用“继承”来管理yaml文件

Python 使用“继承”来管理yaml文件 引言 引言 在 Python 中有时候我们会把参数都储存在yaml文件中然后进行调用。当我们在进行一个很大的项目的时候&#xff0c;我们可能先需要一个base.yaml文件&#xff0c;然后再使用一个task1.yaml文件进行参数导入&#xff0c;并且task1.…

Windows搭建opencv cuda开发环境并验证是否成功

编译opencv cuda源码 电脑安装cuda 12.0或者11.8&#xff0c;根据你的电脑配置自行选择 下载opencv 源码 git clone https://github.com/opencv/opencv.git git clone https://github.com/opencv/opencv_contrib.git 在opencv目录里新建 build 文件夹 cd build后 cmake…

【go】初学者入门环境配置,GOPATH,GOROOT,GOCACHE,以及GoLand使用配置注意

一、环境变量配置步骤 1. 打开环境变量设置 Win R 后输入 sysdm.cpl → 点击 确定在弹出窗口中点击 高级 → 环境变量 2. 配置 GOROOT&#xff08;Go语言安装根目录&#xff09; 作用&#xff1a;告诉系统Go语言的安装位置&#xff08;编译器、标准库等核心文件所在路径&a…

gantt-task-react的改造使用

gantt-task-react的镜像地址 例子 改造1&#xff1a;切断父子关联关系&#xff0c;父为project组件&#xff0c;子为task组件&#xff0c; 原来的功能是task组件拖动会影响到父组件&#xff0c;现在切断两者关联关系&#xff0c;数据都用task组件&#xff0c; 给task组件重…

kotlin 协程(Coroutine)

Coroutine&#xff08;协程&#xff09;的转换原理&#xff1a; 在 kotlin 中&#xff0c;Coroution 是一种轻量级的线程管理方式&#xff0c;其转换原理涉及 状态机生成、挂起函数转换和调度器机制。 一、协程的本质&#xff1a;状态机 kotlin 协程通过 编译器生成状态机 实…