用 【C# + Winform + Dlib68点】 实现静图眼镜虚拟佩戴 - 行人-

news/2025/10/16 7:35:53/文章来源:https://www.cnblogs.com/xingrenh/p/19144692

image

本文实现了一种简单的人脸面部关键点识别,和进行静图眼镜图片的虚拟佩戴功能。

开发环境为:VS 2022、WinForm、 .NET Framework 4.6.2 、 DlibDotNet 19.21.0.0

整体的处理流程如下图:

image


加载数据模型

首先加载 数据模型。人脸关键点的数据模型分为68点和5点(人脸的3d 模型点为468点,本文暂不讨论 ),这两个模型文件大小分别为95M、8.72M,两个文件从识别速度上来说差异非常小,主要 区别就是文件的大小和结果点的精度 。

加载数据模型的时候,有一点非常容易出错,就是模型的加载路径一定不能包含中文或特殊字符,否则会加载失败,所以我们把模型文件从软件的根目录下拷贝到所在盘符后再使用 ,

模型的加载处理如下:

private void InitializeDlib()
{try{faceDetector = Dlib.GetFrontalFaceDetector();var modelPath68 = AppDomain.CurrentDomain.BaseDirectory + "data\\shape_predictor_68_face_landmarks.dat";var modelPath5 = AppDomain.CurrentDomain.BaseDirectory + "data\\shape_predictor_5_face_landmarks.dat"; // 加载68点模型 if (File.Exists(modelPath68)){//防止中文路径var modelPath68_tmp = modelPath68[0] + ":\\" + modelPath68.Split('\\').Last();if (!File.Exists(modelPath68_tmp))File.Copy(modelPath68, modelPath68_tmp);shapePredictor68 = ShapePredictor.Deserialize(modelPath68_tmp);radioButton68.Enabled = true;radioButton68.Checked = true;}else{radioButton68.Enabled = false;}// 加载5点模型 if (File.Exists(modelPath5)){//防止中文路径var modelPath5_tmp = modelPath5[0] + ":\\" + modelPath5.Split('\\').Last();if (!File.Exists(modelPath5_tmp))File.Copy(modelPath5, modelPath5_tmp);shapePredictor5 = ShapePredictor.Deserialize(modelPath5_tmp);radioButton5.Enabled = true;}else{radioButton5.Enabled = false;}if (!radioButton68.Enabled && !radioButton5.Enabled){MessageBox.Show("未找到任何人脸检测模型!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);}}catch (Exception ex){MessageBox.Show($"初始化Dlib失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);}
}

识别人脸68点

模型加载完成之后,我们就可以使用它来识别指定的人像图片了,通过简单的一句代码就可以识别到我们需要的关键点 :redictor.Detect(img, face),获取到关键点后,把它绘制到页面上。具体处理如下 :

private void DrawFaceLandmarks()
{if (originalImage == null) return;ShapePredictor currentPredictor = radioButton68.Checked ? shapePredictor68 : shapePredictor5;if (currentPredictor == null){pictureBoxFace.Image = originalImage;return;}try{faceLandmarksImage?.Dispose();faceLandmarksImage = new Bitmap(originalImage);using (var img = Dlib.LoadImage<RgbPixel>(currentImagePath)){var faces = faceDetector.Operator(img);if (faces.Length > 0){using (Graphics g = Graphics.FromImage(faceLandmarksImage)){g.SmoothingMode = SmoothingMode.HighQuality;// 绘制人脸框Pen facePen = new Pen(Color.Lime, 3);// 使用更亮的颜色和更大的点Brush faceOutlineBrush = new SolidBrush(Color.Cyan);         // 脸部轮廓 0-16Brush eyebrowBrush = new SolidBrush(Color.Yellow);           // 眉毛 17-26Brush noseBrush = new SolidBrush(Color.Magenta);             // 鼻子 27-35Brush eyeBrush = new SolidBrush(Color.Lime);                 // 眼睛 36-47 - 使用亮绿色Brush mouthBrush = new SolidBrush(Color.HotPink);            // 嘴巴 48-67Font font = new Font("Arial", 8, FontStyle.Bold);Brush textBrush = new SolidBrush(Color.White);Brush textBackBrush = new SolidBrush(Color.FromArgb(128, 0, 0, 0));foreach (var face in faces){// 绘制人脸矩形框g.DrawRectangle(facePen, (int)face.Left, (int)face.Top, (int)face.Width, (int)face.Height);var shape = currentPredictor.Detect(img, face);// 绘制所有关键点for (uint i = 0; i < shape.Parts; i++){var point = shape.GetPart(i);int x = (int)point.X;int y = (int)point.Y;// 根据点的索引选择颜色Brush pointBrush;if (i <= 16) pointBrush = faceOutlineBrush;      // 脸部轮廓else if (i <= 26) pointBrush = eyebrowBrush;     // 眉毛else if (i <= 35) pointBrush = noseBrush;        // 鼻子else if (i <= 47) pointBrush = eyeBrush;         // 眼睛else pointBrush = mouthBrush;                    // 嘴巴// 绘制更大的点,并添加边框g.FillEllipse(pointBrush, x - 4, y - 4, 8, 8);// 添加白色边框使点更明显using (Pen borderPen = new Pen(Color.White, 1)){g.DrawEllipse(borderPen, x - 4, y - 4, 8, 8);}// 显示点号(仅68点模型)if (shape.Parts >= 68){// 先绘制半透明背景SizeF textSize = g.MeasureString(i.ToString(), font);g.FillRectangle(textBackBrush, x + 6, y - 8, textSize.Width + 2, textSize.Height);// 绘制白色文字g.DrawString(i.ToString(), font, textBrush, x + 6, y - 8);}}// 连接关键点形成轮廓(可选)if (shape.Parts >= 68){Pen linePen = new Pen(Color.FromArgb(100, Color.Lime), 2);// 连接脸部轮廓for (uint i = 0; i < 16; i++){var p1 = shape.GetPart(i);var p2 = shape.GetPart(i + 1);g.DrawLine(linePen, (int)p1.X, (int)p1.Y, (int)p2.X, (int)p2.Y);}// 连接左眉毛for (uint i = 17; i < 21; i++){var p1 = shape.GetPart(i);var p2 = shape.GetPart(i + 1);g.DrawLine(linePen, (int)p1.X, (int)p1.Y, (int)p2.X, (int)p2.Y);}// 连接右眉毛for (uint i = 22; i < 26; i++){var p1 = shape.GetPart(i);var p2 = shape.GetPart(i + 1);g.DrawLine(linePen, (int)p1.X, (int)p1.Y, (int)p2.X, (int)p2.Y);}linePen.Dispose();}}facePen.Dispose();faceOutlineBrush.Dispose();eyebrowBrush.Dispose();noseBrush.Dispose();eyeBrush.Dispose();mouthBrush.Dispose();font.Dispose();textBrush.Dispose();textBackBrush.Dispose();}}pictureBoxFace.Image = faceLandmarksImage;}}catch (Exception ex){lblStatus.Text = $"绘制关键点失败:{ex.Message}";pictureBoxFace.Image = originalImage;}
}

眼镜标定

面部关键点有了,那么如何把眼镜图片正确的放到目标位置呢?这里也有需要注意的点 默认情况下 我们可能 会把眼睛图片以眼睛的中心为基准来 让眼睛图片的中心点与它对齐 但是这样的效果可能是非常傻的 因为实际我们在佩戴眼镜的时候 眼睛一般处于镜片的偏上和偏内的位置 ,例如下图的效果比对 ,

image

因此我们需要告诉软件,我们希望眼镜位于眼中心的位置在哪里,因此我们在这里做一个眼镜标定的功能:进行标定的时候,为了方便,当选中左侧的某个点之后,右眼的第2个点在标定时默认为了与左眼相同的y坐标,只需要进行x的位置选取即可  :

image


佩戴匹配

有了正确的位置标定,我们就可以把它与眼睛位置进行正确的匹配了,匹配的处理代码如下:

private void OverlayGlasses(Graphics g, FullObjectDetection shape)
{System.Drawing.Point leftEye, rightEye;// 获取人脸上的眼睛中心点if (shape.Parts >= 68){leftEye = GetEyeCenter(shape, 36, 41);rightEye = GetEyeCenter(shape, 42, 47);}else{leftEye = new System.Drawing.Point((int)shape.GetPart(0).X, (int)shape.GetPart(0).Y);rightEye = new System.Drawing.Point((int)shape.GetPart(2).X, (int)shape.GetPart(2).Y);}// 检查是否有标定数据if (glassesCalibrations.ContainsKey(selectedAccessoryPath) &&glassesCalibrations[selectedAccessoryPath].IsCalibrated){// 使用标定数据进行精确对齐var calibration = glassesCalibrations[selectedAccessoryPath];OverlayGlassesWithCalibration(g, leftEye, rightEye, calibration);}else{// 使用默认算法int eyeDistance = (int)Math.Sqrt(Math.Pow(rightEye.X - leftEye.X, 2) + Math.Pow(rightEye.Y - leftEye.Y, 2));float glassesWidth = eyeDistance * 2.2f;float glassesHeight = glassesWidth * currentAccessoryImage.Height / currentAccessoryImage.Width;double angle = Math.Atan2(rightEye.Y - leftEye.Y, rightEye.X - leftEye.X) * 180 / Math.PI;int centerX = (leftEye.X + rightEye.X) / 2;int centerY = (leftEye.Y + rightEye.Y) / 2;DrawRotatedImage(g, currentAccessoryImage, centerX, centerY, glassesWidth, glassesHeight, (float)angle);}
}private void OverlayGlassesWithCalibration(Graphics g, System.Drawing.Point faceLeftEye, System.Drawing.Point faceRightEye, GlassesCalibrationData calibration)
{// 计算眼镜标定点之间的距离和角度double glassesEyeDistance = Math.Sqrt(Math.Pow(calibration.RightEyeCenter.X - calibration.LeftEyeCenter.X, 2) +Math.Pow(calibration.RightEyeCenter.Y - calibration.LeftEyeCenter.Y, 2));double glassesAngle = Math.Atan2(calibration.RightEyeCenter.Y - calibration.LeftEyeCenter.Y,calibration.RightEyeCenter.X - calibration.LeftEyeCenter.X);// 计算人脸眼睛之间的距离和角度double faceEyeDistance = Math.Sqrt(Math.Pow(faceRightEye.X - faceLeftEye.X, 2) +Math.Pow(faceRightEye.Y - faceLeftEye.Y, 2));double faceAngle = Math.Atan2(faceRightEye.Y - faceLeftEye.Y,faceRightEye.X - faceLeftEye.X);// 计算缩放比例float scale = (float)(faceEyeDistance / glassesEyeDistance);// 计算旋转角度(度)float rotationAngle = (float)((faceAngle - glassesAngle) * 180 / Math.PI);// 计算眼镜的新尺寸float newWidth = currentAccessoryImage.Width * scale;float newHeight = currentAccessoryImage.Height * scale;// 计算眼镜中心点(在原图中的位置)System.Drawing.Point glassesCenter = new System.Drawing.Point((calibration.LeftEyeCenter.X + calibration.RightEyeCenter.X) / 2,(calibration.LeftEyeCenter.Y + calibration.RightEyeCenter.Y) / 2);// 计算人脸眼睛中心点System.Drawing.Point faceEyesCenter = new System.Drawing.Point((faceLeftEye.X + faceRightEye.X) / 2,(faceLeftEye.Y + faceRightEye.Y) / 2);// 应用变换var state = g.Save();// 移动到人脸眼睛中心g.TranslateTransform(faceEyesCenter.X, faceEyesCenter.Y);// 旋转g.RotateTransform(rotationAngle);// 计算偏移(考虑标定点在眼镜图片中的相对位置)float offsetX = -glassesCenter.X * scale;float offsetY = -glassesCenter.Y * scale;// 绘制眼镜g.DrawImage(currentAccessoryImage, offsetX, offsetY, newWidth, newHeight);g.Restore(state);
}

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

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

相关文章

图神经网络前沿技术与应用探索

本文深入探讨图神经网络在建模长距离依赖关系、提升计算效率以及新型因果模型方面的最新进展,涵盖算法优化、系统设计和硬件协同等多个技术层面,并介绍在知识图谱推理和多智能体系统等领域的创新应用。KDD 2023:图神…

MVCC、幻读、间隙锁与临键锁(三)

一、MVCC解决了什么问题? MVCC 解决了数据库高并发场景下的两大核心问题:读写阻塞:在传统的锁机制下,读操作可能会阻塞写操作,写操作也一定会阻塞读操作。当有大量读写操作并发时,数据库性能会急剧下降。事务隔离…

MVCC、幻读、间隙锁与临键锁

一、MVCC 解决了什么问题? 🌱 背景:并发读写冲突 当多个事务同时操作同一行时,最经典的冲突是:A 在读;B 在写;A 还没提交,B 改了数据;如何让 A 看到一致的结果?MVCC(Multi-Version Concurrency Control,多…

MVCC、幻读、间隙锁与临键锁(二)

1. MVCC 解决了什么问题? MVCC(多版本并发控制)是 MySQL InnoDB 存储引擎实现并发访问的核心机制,主要解决了读写冲突问题:在传统锁机制中,读操作需要加共享锁,写操作需要加排他锁,会导致 “读阻塞写、写阻塞读…

读AI赋能01超级能动性

读AI赋能01超级能动性1. 超级能动性 1.1. 通货膨胀已成为全球最令人担忧的问题 1.2. 科技行业仍难以摆脱广告业务放缓、投资者情绪转变以及用户参与模式变化带来的叠加影响1.2.1. 负面结果只是对科技行业在疫情期间出现…

生物聚酯塑料回收技术创新与商业应用

本文介绍了生物聚酯塑料的化学回收技术突破,包括EsterCycle低能耗甲醇解工艺和Glacier的AI视觉分拣系统,并通过商业试验验证了生物聚酯材料在零售场景中的应用效果,推动塑料循环价值链建设。更优塑料之路:进展与合…

189 轮转数组 - MKT

189 轮转数组 class Solution { public:// 通过1 time 0ms 100% space 30.mb 5% 自己 内存大void rotate1(vector<int>& nums, int k) {// 1 余数 2 是否大于边界// 10 6 16=6 12-10=2cout<<&quo…

SGD 到 AdamW 优化器的实践选型指南

在深度学习的模型训练过程中,优化器扮演着至关重要的角色。它就像一位经验丰富的向导,带领模型在复杂的参数空间中寻找最优解。从早期简单的随机梯度下降到如今广泛使用的 AdamW,优化器的发展历程充满了对效率与精度…

# ️ MySQL vs PostgreSQL架构深度对比分析报告

# ️ MySQL vs PostgreSQL架构深度对比分析报告Posted on 2025-10-16 02:32 吾以观复 阅读(1) 评论(0) 收藏 举报关联知识库:# ️ MySQL vs PostgreSQL架构深度对比分析报告️ MySQL vs PostgreSQL架构深度对比分…

# 韩国数据中心大火:647套系统因缺失双活集体宕机22小时

# 韩国数据中心大火:647套系统因缺失双活集体宕机22小时Posted on 2025-10-16 02:32 吾以观复 阅读(1) 评论(0) 收藏 举报关联知识库:# 韩国数据中心大火:647套系统因缺失双活集体宕机22小时韩国数据中心大火…

# TLP电池管理工具:Linux笔记本续航优化的终极指南

# TLP电池管理工具:Linux笔记本续航优化的终极指南Posted on 2025-10-16 02:32 吾以观复 阅读(0) 评论(0) 收藏 举报关联知识库:# TLP电池管理工具:Linux笔记本续航优化的终极指南TLP电池管理工具:Linux笔记…

LlamaIndex API Example

LlamaIndex API ExamplePosted on 2025-10-16 02:32 吾以观复 阅读(0) 评论(0) 收藏 举报关联知识库:LlamaIndex API ExampleReader and Query Engine documents = SimpleDirectoryReader(files).load_data() re…

AI中间件机遇与挑战:从Agent到组织级智能的技术演进

AI中间件机遇与挑战:从Agent到组织级智能的技术演进Posted on 2025-10-16 02:32 吾以观复 阅读(0) 评论(0) 收藏 举报关联知识库:AI中间件机遇与挑战:从Agent到组织级智能的技术演进️ AI中间件机遇与挑战:从…

# Redis日常使用与性能排查指南

# Redis日常使用与性能排查指南Posted on 2025-10-16 02:32 吾以观复 阅读(0) 评论(0) 收藏 举报关联知识库:# Redis日常使用与性能排查指南Redis日常使用与性能排查指南 草稿内容 常用命令:info指令 9大块 s…

金耀初讲座——高效演化神经结构搜索

金耀初讲座——高效演化神经结构搜索![assets/金耀初讲座——高效演化神经结构搜索/Untitled.png]] ![assets/金耀初讲座——高效演化神经结构搜索/Untitled 1.png]] ![assets/金耀初讲座——高效演化神经结构搜索/Unt…

二手车检查

二手车检查车源:二手车之家app和懂车帝app,因为上面车商具有营业资格,可初步筛选车商 询问时:漆面状态(哪些面补过漆) 换件情况 四门(大事故),四梁(前后横纵防撞梁),六柱(车身骨架),所有玻璃(批号显示…

图文并茂展示CSS li 排版大合集,总有一款是你刚好需要的

@目录🐱 A. 基础列表样式🌟 1. 默认样式📝 无序列表🔢 有序列表✨ 2. 自定义项目符号🚀 B. 高级布局与定位🖼️ 3. 使用图片作为项目符号🧹 4. 移除默认样式🧭 5. 水平导航栏💫 C. 创意与装饰效果�…

The lamentable decline of reading

https://www.ft.com/content/583de986-a295-4697-a2fe-3c6b13c99145 The lamentable decline of readingChildhood encouragement, libraries and government support can reverse the trendTHE EDITORIAL BOARDAdd to…

[FT.COM]The world should prepare for the looming quantum era

https://www.ft.com/content/96e14cb0-f49f-4632-b94f-2d1cdc625f8b The world should prepare for the looming quantum eraNew breakthroughs underscore the technology’s potential and perilsTHE EDITORIAL BOAR…

10.15 闲话

镜中的昆虫曹髦,字彦士,常称其为“高贵乡公”。甘露五年五月己丑日,在诛杀司马昭的过程中被成济刺死。 我认为三国杀对曹髦的刻画是非常成功的。【潜龙】属于前期劣势,后期爆发的技能。【清正】和【酒诗】都不算能…