OpenCV之换脸技术:一场面部识别的奇妙之旅
在这个数字化与智能化并进的时代,图像处理技术日益成为连接现实与虚拟世界的桥梁。其中,换脸技术作为一项颇受欢迎且富有挑战性的应用,不仅让人惊叹于技术的魔力,更在娱乐、影视制作等领域展现了无限可能。今天,我们就来探索如何使用OpenCV这一强大的计算机视觉库,实现基础的换脸效果。
一、前言
OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习软件库,它提供了丰富的图像处理和视频分析功能。通过OpenCV,我们可以轻松地进行面部检测、特征点匹配、图像变换等操作,为实现换脸技术打下坚实基础。
二、技术原理
换脸技术的核心在于将源图像中的人脸区域精确地映射到目标图像上,同时保持面部特征的自然和协调。这通常涉及以下几个关键步骤:
- 面部检测:利用预训练的面部检测模型(如Haar特征分类器或深度学习方法)在图像中定位人脸区域。
- 特征点匹配:通过面部特征点检测算法(如Dlib的68点或5点特征检测)找到源图像和目标图像中对应的关键点。
- 图像变换:使用仿射变换、透视变换或更复杂的非线性变换(如Delaunay三角剖分)将源人脸变形以匹配目标人脸的形状。
- 图像融合:将变形后的源人脸与目标图像的背景进行无缝融合,处理边缘,使其看起来自然。
三、实现步骤
下面是一个基于OpenCV的简单换脸示例代码框架,注意,这里假设你已经安装了OpenCV和Dlib库(用于特征点检测)。
import cv2
import dlib
import numpy as np # 定义面部特征点的区域索引
JAW_POINTS = list(range(0, 17)) # 下巴
RIGHT_BROW_POINTS = list(range(17, 22)) # 右眉毛
LEFT_BROW_POINTS = list(range(22, 27)) # 左眉毛
NOSE_POINTS = list(range(27, 35)) # 鼻子
RIGHT_EYE_POINTS = list(range(36, 42)) # 右眼
LEFT_EYE_POINTS = list(range(42, 48)) # 左眼
MOUTH_POINTS = list(range(48, 61)) # 嘴巴
FACE_POINTS = list(range(17, 68)) # 整个面部(不包括下巴,但包括其他所有) # 关键点集,用于生成面部掩模
POINTS = [LEFT_BROW_POINTS + RIGHT_EYE_POINTS + LEFT_EYE_POINTS + RIGHT_BROW_POINTS + NOSE_POINTS + MOUTH_POINTS]
POINTStuple = tuple(POINTS) def getFaceMask(im, keypoints): """根据关键点生成面部掩模""" # 创建一个与输入图像大小相同的零矩阵 im = np.zeros(im.shape[:2], dtype=np.float64) for p in POINTS: # 对关键点集进行凸包计算 points = cv2.convexHull(keypoints[p]) # 填充凸包区域 cv2.fillConvexPoly(im, points, color=1) # 将单通道图像转换为三通道图像,以适应OpenCV的显示要求 im = np.array([im, im, im]).transpose((1, 2, 0)) # 对掩模进行高斯模糊处理,以减少边缘的锯齿状 im = cv2.GaussianBlur(im, ksize=(25, 25), sigmaX=0) return im def getM(points1, points2): """计算从points1到points2的仿射变换矩阵""" # 将点转换为浮点数类型 points1 = points1.astype(np.float64) points2 = points2.astype(np.float64) # 计算均值,用于归一化 c1 = np.mean(points1, axis=0) c2 = np.mean(points2, axis=0) # 归一化 points1 -= c1 points2 -= c2 # 计算标准差 s1 = np.std(points1) s2 = np.std(points2) # 归一化 points1 /= s1 points2 /= s2 # 使用奇异值分解计算仿射变换矩阵 U, S, Vt = np.linalg.svd(points1.T * points2) R = (U * Vt).T # 返回完整的仿射变换矩阵 return np.hstack(((s2 / s1) * R, c2.T - (s2 / s1) * R * c1.T)) def getKeypoints(im): """检测图像中的面部关键点""" # 检测面部 rects = detector(im, 1) # 获取面部关键点 shape = predictor(im, rects[0]) # 将关键点转换为numpy矩阵 s = np.matrix([[p.x, p.y] for p in shape.parts()]) return s def normalColor(a, b): """调整b图的颜色值,使其与a图相似""" # 对a和b进行高斯模糊处理,以减少噪声 aGauss = cv2.GaussianBlur(a, ksize=(111, 111), sigmaX=0) bGauss = cv2.GaussianBlur(b, ksize=(111, 111), sigmaX=0) # 计算颜色调整权值 weight = aGauss / bGauss # 处理无穷大值 where_are_inf = np.isinf(weight) weight[where_are_inf] = 0 # 返回调整后的b图 return b * weight # 读取换脸所需的图片
a = cv2.imread("pyy1.jpg") # 换脸A图片
b = cv2.imread("hg.png") # 换脸B图片 # 初始化面部检测器和关键点预测器
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") # 获取A图片和B图片的面部关键点
aKeyPoints = getKeypoints(a)
bKeyPoints = getKeypoints(b) # 复制B图片,以便后续处理不破坏原图
bOriginal = b.copy() # 获取A图片和B图片的面部掩模
aMask = getFaceMask(a, aKeyPoints)
cv2.imshow("aMask", aMask) # 显示A图片的面部掩模
cv2.waitKey() bMask = getFaceMask(b, bKeyPoints) # 获取B图片的面部掩模
cv2.imshow("bMask", bMask) # 显示B图片的面部掩模
cv2.waitKey() # 计算从B图片面部到A图片面部的仿射变换矩阵
M = getM(aKeyPoints[POINTStuple], bKeyPoints[POINTStuple]) # 使用仿射变换矩阵将B图片的面部掩模变换到A图片上
bMaskWarp = cv2.warpAffine(bMask, M, dsize=a.shape[:2][::-1], borderMode=cv2.BORDER_TRANSPARENT, flags=cv2.WARP_INVERSE_MAP)
cv2.imshow("bMaskWarp", bMaskWarp) # 显示变换后的B图片面部掩模
cv2.waitKey() # 获取面部区域的最大掩模(A和B的掩模叠加)
mask = np.max([aMask, bMaskWarp], axis=0)
cv2.imshow("mask", mask) # 显示最大掩模
cv2.waitKey() # 使用仿射变换矩阵将B图片变换到A图片上
bWrap = cv2.warpAffine(b, M, dsize=a.shape[:2][::-1], borderMode=cv2.BORDER_TRANSPARENT, flags=cv2.WARP_INVERSE_MAP)
cv2.imshow("bWrap", bWrap) # 显示变换后的B图片
cv2.waitKey() # 调整B图片的颜色,使其与A图片相似
bcolor = normalColor(a, bWrap)
cv2.imshow("bcolor", bcolor) # 显示调整颜色后的B图片
cv2.waitKey() # 换脸:在掩模区域使用B图片的颜色,其他区域使用A图片
out = a * (1.0 - mask) + bcolor * mask # 显示原始图片和换脸结果
cv2.imshow("a", a) # 显示A图片
cv2.imshow("b", bOriginal) # 显示原始B图片
cv2.imshow("out", out / 255) # 显示换脸结果(注意:这里除以255是为了将像素值归一化到0-1之间,便于显示)
cv2.waitKey()
cv2.destroyAllWindows() # 关闭所有窗口
代码效果:

结语
通过上述步骤,我们利用OpenCV和Dlib实现了一个基础的换脸效果。虽然这只是冰山一角,但它为我们打开了一个充满无限想象的空间。随着技术的不断进步,未来的换脸技术将更加智能化、高效化,为我们的生活带来更多乐趣和可能。如果你对图像处理感兴趣,不妨亲自动手尝试一下,探索更多未知的领域吧!
相关文章:
OpenCV之换脸技术:一场面部识别的奇妙之旅
在这个数字化与智能化并进的时代,图像处理技术日益成为连接现实与虚拟世界的桥梁。其中,换脸技术作为一项颇受欢迎且富有挑战性的应用,不仅让人惊叹于技术的魔力,更在娱乐、影视制作等领域展现了无限可能。今天,我们就…...
Linux学习笔记9 文件系统的基础
一、查看文件组织结构 Linux中一切都是文件。 Linux和Win的文件系统不是一个结构,Linux存在的根目录是所有目录的起点。 所有的存储空间和设备共享一个根目录,不同的磁盘块和分区挂载在其下,成为某个子目录的子目录,甚至设备也挂…...
Android OpenGL粒子特效
在本篇,我们将开启一个新的项目,探索粒子的世界。粒子是一种基本的图形元素,它们通常被表示为一组点。通过巧妙地组合一些基础的物理效果,我们能够创造出许多令人惊叹的视觉效果。想象一下,我们可以模拟一个水滴从喷泉…...
5 -《本地部署开源大模型》在Ubuntu 22.04系统下ChatGLM3-6B高效微调实战
在Ubuntu 22.04系统下ChatGLM3-6B高效微调实战 无论是在单机单卡(一台机器上只有一块GPU)还是单机多卡(一台机器上有多块GPU)的硬件配置上启动ChatGLM3-6B模型,其前置环境配置和项目文件是相同的。如果大家对配置过程还…...
dpkg:错误:另外一个进程已经为dpkg前端锁加锁
一、 问题描述 在新装ubuntu系统时,我们常常会遇见dpkg的错误,dpkg:错误:另外一个进程已经为dpkg前端锁加锁,如下图。 二、问题解决 方法一 先执行sudo rm /var/lib/dpkg/lock-frontend然后再继续安装软件包,如果出现问题dpkg:…...
基于SSM服装定制系统的设计
管理员账户功能包括:系统首页,个人中心,用户管理,服装类型管理,服装信息管理,服装定制管理,留言反馈,系统管理 前台账号功能包括:系统首页,个人中心…...
RK3588开发笔记-usb3.0 xhci-hcd控制器挂死问题解决
目录 前言 一、问题现象 二、问题分析 三、问题排查 总结 前言 在使用 RK3588 芯片进行开发的过程中,我遇到了 USB 3.0 xHCI-HCD 控制器外接5G通讯模块偶尔挂死的问题。这个问题导致 USB 设备失去响应,且不能恢复,需要重启整个系统才能恢复使用,针对该问题进行大量测试以…...
深入解析TCP/IP协议:网络通信的基石
1. 引言 TCP/IP 协议是现代计算机网络的核心,它为互联网上的设备提供了通信的基础。在网络通信中,TCP/IP 协议栈是无处不在的,无论是个人设备的浏览器请求,还是大型分布式系统的内部通信,都依赖于它的稳定、高效与可靠…...
基于微信小程序的汽车预约维修系统(lw+演示+源码+运行)
基于微信小程序的汽车预约维修系统 摘要 随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了基于微信小程序的汽车预约维修系统的开发全过程。通过分析基于微信小程序的汽车预约维修系统管理的不足,创建了…...
wifi、热点密码破解 - python
乐子脚本,有点小慢,试过多线程,系统 wifi 连接太慢了,需要时间确认,多线程的话系统根本反应不过来。 也就可以试试破解别人的热点,一般都是 123456 这样的傻鸟口令 # coding:utf-8 import pywifi from pyw…...
bean的实例化2024年10月17日
跟不上为基础 1.你的java学习路线 2. 3.课程 注解的装配 contoller调用service用的是注解装配...
告别ELK,APO提供基于ClickHouse开箱即用的高效日志方案——APO 0.6.0发布
ELK一直是日志领域的主流产品,但是ElasticSearch的成本很高,查询效果随着数据量的增加越来越慢。业界已经有很多公司,比如滴滴、B站、Uber、Cloudflare都已经使用ClickHose作为ElasticSearch的替代品,都取得了不错的效果ÿ…...
Excel使用技巧:定位Ctrl+G +公式+原位填充 Ctrl+Enter快速填充数据(处理合并单元格)
Excel的正确用法: Excel是个数据库,不要随意合并单元格。 数据输入的时候一定要按照行列输入,中间不要留空,不然就没有关联。 定位CtrlG 公式原位填充 CtrlEnter快速填充数据 如果把合并的单元格 取消合并,只有第一…...
JAVA学习-练习试用Java实现“成绩归类”
问题: 编写程序,输入一批学生的成绩,遇0或负数则输入结束,要求统计并输出优秀(大于85)、通过(60~84)和不及格(小于60)的学生人数。 示例 &#x…...
【Hive】7-拉链表的设计与实现
拉链表的设计与实现 数据同步问题 背景 例如:MySQL中有一张用户表: tb_user,每个用户注册完成以后,就会在用户表中新增该用户的信息,记录该用户的id、手机号码、用户名、性别、地址等信息。 每天都会有用户注册,产生…...
Maxwell 底层原理 详解
Maxwell 是一个 MySQL 数据库的增量数据捕获(CDC, Change Data Capture)工具,它通过读取 MySQL 的 binlog(Binary Log)来捕获数据变化,并将这些变化实时地发送到如 Kafka、Kinesis、RabbitMQ 或其他输出端。…...
使用短效IP池的优势是什么?
短效IP池作为代理IP服务中一种独特的资源管理方式,其应用已经在数据采集、市场分析和网络安全等多个领域中展示出强大的功能。尽管“短效”听起来似乎意味着某种限制,然而在某些特定的应用场景下,短效IP池却提供了无可比拟的优势。本文将详细…...
zynq烧写程序到flash后不运行
🏆本文收录于《全栈Bug调优(实战版)》专栏,主要记录项目实战过程中所遇到的Bug或因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&am…...
JMeter如何设置HTTP代理服务器?
1、 2、添加线程组 3、设置HTTP代理服务器,目标控制器选择“测试计划>线程组” 过滤掉不需要的信息 4、设置电脑手动代理 5、点击启动,在浏览器操作就可以了...
React面试题笔记(一)
一、react基础面试题 1.react中keys的作用是什么? key是是用于追踪哪些列表被修改,被添加或者被移除的辅助标识。 在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是…...
未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...
学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机
这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机,因为在使用过程中发现 Airsim 对外部监控相机的描述模糊,而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置,最后在源码示例中找到了,所以感…...
比较数据迁移后MySQL数据库和OceanBase数据仓库中的表
设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...
