opencv进阶 ——(十二)基于三角剖分实现人脸对齐

 三角剖分概念  

        三角剖分(Triangulation)是一种将多边形或曲面分解为一系列互不相交的三角形的技术,它是计算几何、计算机图形学、地理信息系统、工程和科学计算中的一个基本概念。通过三角剖分,复杂的形状可以被简化为基本的三角形元素,这些元素更容易处理和分析。

在二维空间中,一个简单的三角剖分将一个多边形划分为若干个不相交的三角形,这些三角形的边要么是多边形的边,要么是连接多边形内部点的边。在三维空间中,三角剖分通常应用于表面建模,将一个曲面分割成一系列互不相交的三角面片。

三角剖分有多种类型,其中最著名的一种是Delaunay三角剖分。在Delaunay剖分中,一个点的邻接三角形的内切圆(包含该点但不包含其他点的最小圆)是唯一的,且该点位于圆的外部。这种剖分在很多应用中非常有用,因为它保证了相邻三角形之间的良好分布,从而避免了过于狭长的三角形,这对于数值计算和图形渲染来说是理想的。

三角剖分在以下领域有广泛应用:

  • 计算机图形学:在3D建模和渲染中,用于将复杂的模型简化为三角面片,便于计算和显示。
  • 地理信息系统(GIS):用于将地图数据分割成三角形网格,便于空间分析和数据可视化。
  • 有限元分析(FEM):在工程计算中,用于将连续区域划分为离散的三角形单元,以便于数值求解偏微分方程。
  • 游戏开发:在游戏引擎中,用于减少模型的多边形数量,提高渲染效率。
  • 机器学习:在某些算法中,比如最近邻搜索,Delaunay三角剖分可以作为数据结构加速计算。

        三角剖分通常可以通过算法来实现,例如Graham扫描、 Jarvis March 或者更高效的算法如incremental方法和flip-based方法。在实际应用中,有许多现成的库和工具提供了三角剖分的功能,例如CGAL(Computational Geometry Algorithms Library)和Triangle库。 

人脸对齐实现   

实现步骤 

1、检测人脸区域

2、对人脸区域进行关键点标识

3、考虑到人脸可以存在倾斜,为了能更好的对齐效果,可以对关键点做旋转矩阵拟合

4、这里以经典的人脸68关键点为示例,为了边界部分更自然,可以对旋转矩阵进行扩展

5、将每一个三角形与目标三角形进行仿射变换得到一张与目标点对齐的人脸

代码实现

扩展旋转矩阵区域

void increaseRect(std::vector<cv::Point2f>& points,const cv::Mat& img)
{// 计算点集合构成的矩形的边界框cv::Rect rect = cv::boundingRect(points);// 定义增加比例float scale = 0.2;// 计算增加的宽度和高度int width = rect.width*scale;int height = rect.height*scale;// 定义增加值的lambda函数,用于安全地增加或减少一个值auto addVal = [](float& val, float add, float max){val = std::min(val + add, max); // 确保值不会超过最大值};auto subVal = [](float& val, float sub, float min){val = std::max(val - sub, min); // 确保值不会低于最小值};// 应用增加的宽度和高度来调整四个顶点的位置// 调整第一个点的x坐标和y坐标subVal(points[0].x, width, 0);addVal(points[0].y, height, img.rows);// 调整第二个点的x坐标和y坐标subVal(points[1].x, width, 0);subVal(points[1].y, height, 0);// 调整第三个点的x坐标和y坐标addVal(points[2].x, width, img.cols);subVal(points[2].y, height, 0);// 调整第四个点的x坐标和y坐标addVal(points[3].x, width, img.cols);addVal(points[3].y, height, img.rows);
}

增加边界点

void FillPointRect(const std::vector<cv::Point2f>& points,  std::vector<cv::Point>& vecPoint)
{// 临时存储扩充的点std::vector<cv::Point> addPointsTmp; // 根据原始点集,通过向每个点添加偏移量来生成新的点addPointsTmp.push_back(points[0] + cv::Point2f(1, -1));addPointsTmp.push_back(points[1] + cv::Point2f(1, +1));addPointsTmp.push_back(points[2] + cv::Point2f(-1, +1));addPointsTmp.push_back(points[3] + cv::Point2f(-1, -1));// 通过现有点计算并加入新的点,以构成矩形的外接多边形addPointsTmp.push_back((addPointsTmp[0]  + addPointsTmp[1])/2);addPointsTmp.push_back((addPointsTmp[1]  + addPointsTmp[2])/2);addPointsTmp.push_back((addPointsTmp[2]  + addPointsTmp[3])/2);addPointsTmp.push_back((addPointsTmp[3]  + addPointsTmp[0])/2);// 将扩充的点集添加到输出向量中vecPoint.insert(vecPoint.end(), addPointsTmp.begin(), addPointsTmp.end());
}

获取所有的三角形下标

//获取三角形下标
std::vector<cv::Vec3i> Triangulation::getTrianglesIndexs(std::vector<cv::Vec6f>& triangleList, std::vector<cv::Point>& vecPoint)
{auto findPoint = [&vecPoint](cv::Point& p){for (int i = 0; i < vecPoint.size(); i++){if (vecPoint[i] == p){return i;}    }return -1;};std::vector<cv::Vec3i> trianglesIndexs;for (auto& item : triangleList){cv::Point p1 = cv::Point(item[0], item[1]);cv::Point p2 = cv::Point(item[2], item[3]);cv::Point p3 = cv::Point(item[4], item[5]);auto idx1 = findPoint(p1);auto idx2 = findPoint(p2);auto idx3 = findPoint(p3);if (idx1 != -1 && idx2 != -1 && idx3 != -1){trianglesIndexs.push_back(cv::Vec3i(idx1, idx2, idx3));}}return trianglesIndexs;
}

绘制三角剖分图

std::vector<cv::Point> vecPoint = item.boxPoint;auto rotateRect = cv::minAreaRect(vecPoint);cv::Point2f pot[4];rotateRect.points(pot);std::vector<cv::Point2f> rotatePoints{pot[0], pot[1], pot[2], pot[3]};increaseRect(rotatePoints, img);FillPointRect(rotatePoints, vecPoint);std::vector<cv::Point> vecHull;cv::convexHull(vecPoint, vecHull);auto rect = cv::boundingRect(vecHull);cv::Subdiv2D subdiv(rect);std::vector<cv::Point2f> vecPoint2f;for (auto& ptIt : vecPoint){vecPoint2f.emplace_back(cv::Point2f(ptIt.x, ptIt.y));}subdiv.insert(vecPoint2f);std::vector<cv::Vec6f> triangleList;subdiv.getTriangleList(triangleList);auto trianglesIndexs = std::move(getTrianglesIndexs(triangleList, vecPoint));for (auto& item : trianglesIndexs){cv::Point pt1 = vecPoint[item[0]];cv::Point pt2 = vecPoint[item[1]];cv::Point pt3 = vecPoint[item[2]];cv::line(destDataOut->img(), pt1, pt2, 255);cv::line(destDataOut->img(), pt2, pt3, 255);cv::line(destDataOut->img(), pt3, pt1, 255);}

人脸对齐

cv::Mat alignmentImageData(const cv::Mat& srcImg, const cv::Rect& srcBox, std::vector<cv::Point> vecPointRoi, std::vector<cv::Point> vecDestPointRoi)
{auto srcImgRoi = srcImg(srcBox);//构建三角剖分,获取三角形下标cv::Subdiv2D subdiv(cv::Rect(0, 0, srcBox.width, srcBox.height));std::vector<cv::Point2f> vecPoint2f;vecPoint2f.insert(vecPoint2f.end(), vecPointRoi.begin(), vecPointRoi.end());subdiv.insert(vecPoint2f);std::vector<cv::Vec6f> triangleList;subdiv.getTriangleList(triangleList);auto trianglesIndexs = std::move(getTrianglesIndexs(triangleList, vecPointRoi));qDebug() << "triangles size:" << trianglesIndexs.size();cv::Mat destNewImg = srcImgRoi.clone();auto maxRoiSize = srcImgRoi.size();for (auto& item : trianglesIndexs){cv::Point tr1Pt1 = vecPointRoi[item[0]];cv::Point tr1Pt2 = vecPointRoi[item[1]];cv::Point tr1Pt3 = vecPointRoi[item[2]];auto rect1 = cv::boundingRect(std::vector<cv::Point>{tr1Pt1, tr1Pt2, tr1Pt3});auto croppedTriangle = srcImgRoi(rect1);cv::Point rect1Beg = cv::Point(rect1.x, rect1.y);cv::Point tr2Pt1 = vecDestPointRoi[item[0]];cv::Point tr2Pt2 = vecDestPointRoi[item[1]];cv::Point tr2Pt3 = vecDestPointRoi[item[2]];auto rect2 = cv::boundingRect(std::vector<cv::Point>{tr2Pt1, tr2Pt2, tr2Pt3});cv::Mat croppedTr2Mask = cv::Mat::zeros(rect2.size(),  CV_8UC1);//生成模板图像三角形掩码cv::Point rect2Beg = cv::Point(rect2.x, rect2.y);std::vector<cv::Point> maskPoints2 = {tr2Pt1 - rect2Beg, tr2Pt2 - rect2Beg, tr2Pt3 - rect2Beg};cv::fillConvexPoly(croppedTr2Mask, maskPoints2, 255);cv::Point2f srcTri[3] = {tr1Pt1 - rect1Beg, tr1Pt2 - rect1Beg, tr1Pt3 - rect1Beg};cv::Point2f dstTri[3] = {tr2Pt1 - rect2Beg, tr2Pt2 - rect2Beg, tr2Pt3 - rect2Beg};auto warpMat = cv::getAffineTransform(srcTri, dstTri);cv::Mat warpTriangle;cv::warpAffine(croppedTriangle, warpTriangle, warpMat, rect2.size());cv::bitwise_and(warpTriangle, warpTriangle, warpTriangle, croppedTr2Mask);cv::Mat roi = destNewImg(rect2);warpTriangle.copyTo(roi, croppedTr2Mask);}return  destNewImg;
}

人脸68关键点和5关键点三角剖分图

人脸对齐

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

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

相关文章

病理级Polymer酶标二抗IHC试剂盒上线!

免疫组织化学 Immunohistochemistry,lHC 是利用抗体与抗原特异性识别原理&#xff0c;对组织样本中的抗原进行定位/定性分析的实验技术。组织切片保留了样品的解剖学结构特征&#xff0c;从而可以高分辨率地显现蛋白在细胞&#xff0c;甚至细胞器中的定位。基于以上特性&…

Apple - Image I/O Programming Guide

翻译自&#xff1a;Image I/O Programming Guide&#xff08;更新时间&#xff1a;2016-09-13 https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/ImageIOGuide/imageio_intro/ikpg_intro.html#//apple_ref/doc/uid/TP40005462 文章目录 …

orbslam2代码解读(1):数据预处理过程

写orbslam2代码解读文章的初衷 首先最近陆陆续续花了一两周时间学习视觉slam&#xff0c;因为之前主要是做激光slam&#xff0c;有一定基础所以学的也比较快&#xff0c;也是看完了视觉14讲的后端后直接看orbslam2的课&#xff0c;看的cvlife的课&#xff08;课里大部分是代码…

jenkins的简单使用

2.1.简介 Jenkins是一个开源软件项目&#xff0c;是基于Java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使软件的持续集成变成可能。 2.4.Jenkins安装 1.下载安装包jenkins.war&#xff1b; 2.在安装…

笔记 | 软件工程04:软件项目管理

1 软件项目及其特点 1.1 什么是项目 1.2 项目特点 1.3 影响项目成功的因素 1.4 什么是软件项目 针对软件这一特定产品和服务的项目努力开展“软件开发活动",&#xff08;理解&#xff1a;软件项目是一种活动&#xff09; 1.5 软件项目的特点 1.6 军用软件项目的特点 2 …

怎么用电脑把图片转换二维码?图片在线生成二维码的步骤内容

现在很多人会通过二维码来存储物品的信息图片&#xff0c;其他人可以通过扫描二维码的方式来查看对应的图片内容&#xff0c;那么当我们需要将一批图片每个单独生成二维码&#xff0c;该如何操作能够快速将图片转换二维码呢&#xff1f; 今天&#xff0c;小编来分享给大家一个…

CNN卷积神经网络

一、概述 卷积神经网络&#xff08;CNN&#xff09;是深度学习领域的重要算法&#xff0c;特别适用于处理具有网格结构的数据&#xff0c;比如说图像和音频。它起源于二十世纪80至90年代&#xff0c;但真正得到快速发展和应用是在二十一世纪&#xff0c;随着深度学习理论的兴起…

【ai】phc:安装issac环境且fix libstdc++.so 版本报错

Pycharm远程连接服务器(2023-11-9) 大神分享了pycharm远程连接ubuntu工作站的方法。 https://github.com/ZhengyiLuo/PHC 给出的操作同样适用: 参考 Pycharm远程连接服务器(2023-11-9) :前提是一样的 PHC的要求:isaac 创建 conda activate isaac

【Vue】scoped解决样式冲突

默认情况下写在组件中的样式会 全局生效 → 因此很容易造成多个组件之间的样式冲突问题。 全局样式: 默认组件中的样式会作用到全局&#xff0c;任何一个组件中都会受到此样式的影响 局部样式: 可以给组件加上scoped 属性,可以让样式只作用于当前组件 一、代码示例 BaseOne…

RocketMQ可视化界面安装

RocketMQ可视化界面安装 **起因&#xff1a;**访问rocketmq-externals项目的git地址&#xff0c;下载了源码&#xff0c;在目录中并没有找到rocketmq-console文件夹。 git下面文档提示rocketMQ的仪表板转移到了新的项目中&#xff0c;点击仪表板到新项目地址&#xff1b; 下载…

搜索与图论:宽度优先搜索

搜索与图论&#xff1a;宽度优先搜索 题目描述参考代码 题目描述 输入样例 5 5 0 1 0 0 0 0 1 0 1 0 0 0 0 0 0 0 1 1 1 0 0 0 0 1 0输出样例 8参考代码 #include <iostream> #include <algorithm> #include <cstring> using namespace std;const int N …

VsQt单元测试目录的管理方式

正常项目的文件管理方式 正常项目的目录&#xff0c;是由文件系统中实际的文件夹进行分类管理的。 但是如果单元测试用实际文件夹管理的话&#xff0c;会出现问题&#xff0c;就是被测类太多了&#xff0c;用文件系统管理的话&#xff0c;不太方面查看&#xff0c;如下图所示。…

contentType 与 dataType

contentType 与 dataType contentType contentType&#xff1a;发送的数据格式&#xff08;请求方发送给服务器的数据格式&#xff09;&#xff0c;这个内容会放在请求方的 请求头中 application/x-www-form-urlencoded 这个是默认的请求格式。 提交给后台的数据会按照 KV&am…

创新实训2024.06.06日志:部署web服务

1. 运行web项目前后端服务 首先我们要先在服务器上运行客户端以及服务端的应用程序。随后再考虑如何通过公网/局域网访问的问题。 如何启动服务在仓库对应分支下的Readme文件中已经有详细描述了。 1.1. 启动服务端 对于服务端&#xff0c;即&#xff08;要求你在服务端子项…

SCARA机器人中旋转花键的维护和保养方法!

作为精密传动元件的一种&#xff0c;旋转花键在工作过程中承受了较大的负荷。在自动化设备上运用广泛&#xff0c;如&#xff1a;水平多关节机械手臂&#xff08;SCARA&#xff09;、产业用机器人、自动装载机、雷射加工机、搬运装置、机械加工中心的ATC装置等&#xff0c;最适…

如何在Windows 10和11上修复DISM错误87?这里提供办法

​在电脑上运行DISM命令时&#xff0c;是否收到“错误代码87”消息&#xff1f;这是一个非常常见的错误&#xff0c;你可以轻松地修复它。我们将向你展示在Windows 11或Windows 10计算机上解决此问题的多种方法。 确保键入正确的命令 运行DISM命令时出现错误代码87的最常见原…

优雅谈大模型10:MoE

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则提…

应对800G以太网挑战:数据中心迁移

在过去几年中&#xff0c;云基础设施和服务的大规模使用推动了对更多带宽、更快速度和更低延迟性能的需求。交换机和服务器技术的改进要求布线和架构随之调整。因此&#xff0c;800G以太网对数据中心迁移的需求&#xff0c;特别是对速率&#xff08;包括带宽、光纤密度和通道速…

进程和计划任务以及步骤

进程 进程和程序有关&#xff0c;把该文件放到内存里&#xff0c;进程是动态的&#xff0c;不同时刻的状态不一样 内存&#xff1a;放置正在运行的程序和所需数据的位置 程序启动 ——》将相关文件和数据放到内存里 ——》进程&#xff08;processes&#xff09; 进程相关命令 …

【Python】教你彻底认识Python中的Web开发

​​​​ 文章目录 一、Web开发的基本概念1. Web服务器2. 客户端-服务器模型3. HTTP协议4. 前端与后端 二、常用的Web开发框架1. Django1.1 安装Django1.2 创建Django项目1.3 定义模型1.4 定义视图1.5 定义URL路由1.6 模板 2. Flask2.1 安装Flask2.2 创建Flask应用2.3 模板2.4…