最近由于叽歪助手业务需要,要开发一套基于人脸识别和识别后融合的小营销插件功能。这个功能实现的思路其实很简单,之前互联网上也有军装照类似的微信社交参与的活动。
但是人脸识别基本属于AI的范畴,需要根据图片去找人脸,这个对于我们这种做应用层的开发还是有相当难度的。
实际应用,微信扫码可以体验
找了一下资料,好在有很多的解决方式:
最常用的,OpenCV其实提供了全套的识别,和一些图片处理方法。本文提到的 Emgu.CV 其实是OpenCV的.net封装和实现,方便在.net项目用.net支持的语言直接调用。本文的项目案例是基于C#开发。 同时,众多互联网AI巨头都开放了人脸识别的开放平台接口(当然是要付费的)。
比较和选择:
用OpenCV或者说用Emgu.CV来全部实现,可以做到识别功能的本地化,支持人脸追踪实时识别,应该说还是挺酷的。但是我要实现的功能并不是实时识别,而是根据图片来识别再合成,并不需要实时的侦测人脸位置,因此实时的需求不排第一。如果没有这个实时的需求,其实是可以考虑开放平台的api来做,当然这个做法会损失部分性能(后面有别的解决方案),实际上单张、图片大小可控的情况下这个问题也不是特别严重。其实人脸识别最重要的是准确性,考虑到几家大的AI开放平台提供的接口,后续应该会不断的有算法优化,并且基于AI的学习,开放平台版的人脸识别应该会更加准确,或者说会越来越准确,我认为这个才是识别的重点。(不过发现Emgu.CV也可以通过自己的实现,来做到学习,但这毕竟还是有工作量的,而且从数据和团队的角度来看,开放平台优势还是明显的)当然为了这个准确,还是有费用成本的。这个是公司的项目,这个成本基本可以接受,确定还是用开放平台人脸识别。
基于各方考虑,我们最后选择的使用腾讯的人脸识别接口(申请过程略)腾讯的人脸识别接口其实是通过N个平台来发布的,有AI开放平台,优图,腾讯云等,这里我们用的是腾讯云提供的,其实腾讯各个开放平台的接口都基本类是,只是权鉴方便有区别,我用各个接口大致试了下,检测的结果是一样的数据,相信底层的计算应该是集中的,搞不懂腾讯为什么要分这么多入口(其实腾讯的开放平台入口真的挺多挺复杂关系感觉理不清,很多接口到处都提供)。
基于上面的考虑,基本确定了方案,好了接下来看实现(我这里会略去腾讯云接口的请求过程和实现,大家参考官方文档)。
先看实现过程:
人脸融合,其实是一个带人脸图片(一下称为目标图或标记为New)的人脸部分扣出来,放到一个带人脸的底图(以下称为底图或标记为Dst)上,然后做一些边缘和颜色的融合,让色彩看上去大致协调(这里说大致协调,基于我现在水平也只能大致协调,无法做到像PS或精确计算的那样完美)。
基于以上过程,下面来分解:
1、找出底图(DstImage)的人脸位置(通过腾讯的接口),即人脸的曲线边缘位置、基于曲线边缘的最小矩形的位置(我们把这个矩形叫做:矩形dst);
2、找到需要合成的目标图(NewImage)的人脸位置(通过腾讯的接口),即人脸的曲线边缘;
3、将目标图按人脸边缘抠图,抠出一个单独的人脸,这里有个人脸凸点的概念要理解,大致就是根据人脸、人脸五官的一系列点的坐标,找到最外围的一圈点,这些点的连接线就是人脸的闭合曲线轨迹;
4、人脸3D方向调整、2D平面方向旋转(如果有需要的话)方向调整。例如3D的:左侧脸、右侧脸,基本可以通过镜像来调整,当然测的角度大致类似,腾讯识别有此参数,按左右侧有正负取值,这种做法当然还是有局限性。2D的旋转:假如将鼻子垂直看成0角度,发生角度变化时,就是脸的平面方向在旋转(不知道腾讯怎么算的,凡是识别结果里面有这个参数,按顺时针,逆时针有正负取值)。这一步我们的需求中其实不是必须的,用户可以按照底图来提供类似角度的照片,但是我们还是在程序里做了开关和微调矫正。
4、将抠出的人脸,按底图人脸的最小矩形的分辨率进行缩放,这个是必须的(我们把抠出来的这个人脸图对象暂且叫做:人脸A);
5、缩放后的人脸A 就是和矩形dst一抹一样大小的纯人脸图,这里就需要将这个人脸图贴到DstImage上,合并生成第一张合成图,这里我们把他叫原始合成图(标记为SrcImage),到这一步,可以自己输出这个歌SrcImage看看,就是一个边缘非常生硬的,换好脸的合成图。
6、到这一步之后的工作,都是做边缘和颜色的处理了,这里需要一个遮罩图(类似PS的图层),这个遮罩实际就是SrcImage的基础上,再通过人脸识别,找出人脸的曲线范围,将人脸内部形状填充为白色(为什么?我也不知道)脸部以外的范围填充为黑色,把生成的这个图片对象我们这里就叫遮罩图(标记为MarkImage)
7、接下来就是用底图+原始合成图+遮罩图(dstImage+SrcImage+MarkImage)通过Emgu.CV直接调用OpenCV的函数来合成了,这次合成后的图,才是我们想要的融合图。
基于以上的操作过程我们来清理一下实现思路和逻辑:
1、这个过程中,我们需要多次用到识别,调用腾讯接口的识别,腾讯识别出的人脸结果是一个json,好吧,这里就需要一个方法了:
这个方法里面用到ImageAI是我基于腾讯开放平台封装后的对象,反正这个方法返回来的就是腾讯返回的json的结果了(基于性能的考虑,重复用到是数据都可以临时保存起来)。
这个结果里面包含了圈起人脸、五官的一系列点的坐标(具体参考腾讯文档)。
public string GetFaceResultJson(Image src)
{
MemoryStream ms = new MemoryStream();
src.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
ms.Flush();
//将二进制数据存到byte数字中
byte[] bmpBytes = ms.ToArray();
ImageAI imgAi = new ImageAI();
int m = this._faceMode;
string res = imgAi.FaceDetect(m, bmpBytes);
return res;
}
2、上面有个概念,叫凸点,计算凸点是找人脸轨迹曲线的前置条件,这里在把方法封装一下:
具体的计算过程下面还有一个封装的专门的类,大致思路就是把以一系列的点筛选一下,找最外延的点,这里的原始数据还是人脸识别返回的json,计算的结果返回到Point数组,这个歌数组后面化脸的曲线形状要用。
public Point [] GetFacePoint(string res)
{
try
{
var obj = JsonConvert.DeserializeObject<dynamic>(res);
var faceShape = obj.data.face[0].face_shape;
List<dynamic> tmp;
List<dynamic> all = new List<dynamic>();
tmp = JsonConvert.DeserializeObject<List<dynamic>>(faceShape.left_eyebrow.ToString());
all = all.Concat(tmp).ToList<dynamic>();
tmp = JsonConvert.DeserializeObject<List<dynamic>>(faceShape.face_profile.ToString());
all = all.Concat(tmp).ToList<dynamic>();
tmp = JsonConvert.DeserializeObject<List<dynamic>>(faceShape.right_eyebrow.ToString());
all = all.Concat(tmp).ToList<dynamic>();
dynamic[] p = all.ToArray();
Point[] facePoint = new Point[p.Length];
for (int i=0;i<p.Length;i++)
{
facePoint[i] = new Point((int)p[i].x, (int)p[i].y);
}
return facePoint;
}
catch (Exception e)
{
throw e;
}
}
3、上面的分解步骤中,找人脸的最小矩形是比较重要的一步,这里我们把找人脸矩形封装一下:
人脸矩形的矩形实际是找矩形是个点的坐标,这里我们把json的一列坐标点转换到new{x,y}的匿名(为了方便,懒得定义强类型)对象里,反正就是找最小x,最大x,最小y,最大y的4个点,
也许我的计算方式不是最好,反正是实现了吧,凑合下...这4个点里面就是最小的人脸矩形
public object GetXYPoints(dynamic[] points)
{
int[] x_ps = new int[points.Length];
int[] y_ps = new int[points.Length];
for (int i = 0; i < points.Length; i++)
{
x_ps[i] = points[i].x;
y_ps[i] = points[i].y;
}
Array.Sort<int>(x_ps);
Array.Sort<int>(y_ps);
return new { minX = x_ps[0], minY = y_ps[0], maxX = x_ps[points.Length - 1], maxY = y_ps[points.Length - 1] };
}
还没完,除了人脸的最小矩形的点,还得有这个矩形的位置信息、长宽信息等,基于以上的计算基础,我们再封装一下,在下面的方法会调用上面的GetXYPoints,下面这个方法,我们就是直接解析人脸识别的json,这里我用到的点的只是眉毛和脸,鼻子嘴巴肯定都是在脸里面嘛,这个没必要拿来计算了。
public FaceImageShape GetFaceXY(string res)
{
try
{
int x, y, w, h;
dynamic[] points;
dynamic XY;
var obj = JsonConvert.DeserializeObject<dynamic>(res);
var faceShape = obj.data.face[0].face_shape;
List<dynamic> tmp;
List<dynamic> all = new List<dynamic>();
tmp = JsonConvert.DeserializeObject<List<dynamic>>(faceShape.face_profile.ToString());
all = all.Concat(tmp).ToList<dynamic>();
tmp = JsonConvert.DeserializeObject<List<dynamic>>(faceShape.left_eyebrow.ToString());
all = all.Concat(tmp).ToList<dynamic>();
tmp = JsonConvert.DeserializeObject<List<dynamic>>(faceShape.right_eyebrow.ToString());
all = all.Concat(tmp).ToList<dynamic>();
tmp = JsonConvert.DeserializeObject<List<dynamic>>(faceShape.left_eye.ToString());
all = all.Concat(tmp).ToList<dynamic>();
tmp = JsonConvert.DeserializeObject<List<dynamic>>(faceShape.right_eye.ToString());
all = all.Concat(tmp).ToList<dynamic>();
points = all.ToArray();
XY = GetXYPoints(points);
x = (int)XY.minX;
y = (int)XY.minY;
w = (int)XY.maxX - (int)XY.minX;
h = (int)XY.maxY - (int)XY.minY;
return new FaceImageShape {
X =x,
Y=y,
W=w,
H=h,
Roll = (int)obj.data.face[0].roll,
Yaw = (int)obj.data.face[0].yaw,
FaceX =(int)obj.data.face[0].x,
FaceY=(int)obj.data.face[0].y,
FaceW=(int)obj.data.face[0].width,
FaceH=(int)obj.data.face[0].height
};
}
catch (Exception e)
{
throw e;
}
}
上面的返回的结构定义如下:
这里有xywh还有带face前缀的,带前缀的xy和矩形宽高是腾讯识别后直接返回的(其实比实际的脸部框大一圈),这里反正跟我自己计算的一起返回吧,后面实际就变成根据识别方式定义成了选项,是用腾讯的原始框框来融合,还是自己算的框框融合,我们的实际场景还是用我自己的算的框框。另外两个值一个是平面旋转,一个3d角度
public struct FaceImageShape
{
public int X;
public int Y;
public int W;
public int H;
public int Roll;
public int Yaw;
public int FaceX;
public int FaceY;
public int FaceW;
public int FaceH;
}
4、好了,找位置什么的都有了,关键还要画脸的形状,根据凸点的结果,来画人脸吧:
这里的代码我吧要不要转脸,做成的选项,人脸的外框计算其实在融合时还用到找中心点,基于中心点做融合,边缘就虚化色彩融合,可以自行了解下Emgu.CV和OpenCV
public GraphicsPath GetFacePath(Point[] p)
{
GraphicsPath path = new GraphicsPath();
List<PointF> points = new List<PointF>();
for (int i = 0; i < p.Length; i++)
{
points.Add(new Point((int)p[i].X, (int)p[i].Y));
}
ConvexAogrithm ca = new ConvexAogrithm(points);
ca.GetNodesByAngle(out PointF tmp_p);
Stack<PointF> p_nodes = ca.SortedNodes;
path.AddLines(p_nodes.ToArray());
return path;
}
5、基本要用的方法都有了,我们再按前面的1-7的步骤来融合:
/// <summary>
/// 获取融合后的图片实例
/// </summary>
/// <returns></returns>
public Image GetFusionImage()
{
if (String.IsNullOrEmpty(this.DstImageFaceResult))
this.DstImageFaceResult = GetFaceResultJson(this.DstImage);
FaceImageShape shape = GetFaceXY(this.DstImageFaceResult);//模版图
int px = shape.X + shape.W / 2;
int py = shape.Y + shape.H / 2;
if (this.CloenMode == 1)
{
px = shape.FaceX + shape.FaceW / 2;
py = shape.FaceY + shape.FaceH / 2;
}
this._dstroll = shape.Roll;
if (String.IsNullOrEmpty(this.NewImageFaceResult))
this.NewImageFaceResult = GetFaceResultJson(this.NewImage);
FaceImageShape newShape = GetFaceXY(this.NewImageFaceResult);
if (this.AutoYaw)
{
if (shape.Yaw * newShape.Yaw < 0)//如果脸的方向不同要翻转脸部
{
NewImage.RotateFlip(RotateFlipType.Rotate180FlipY);
this.NewImageFaceResult = GetFaceResultJson(this.NewImage);
newShape = GetFaceXY(this.NewImageFaceResult);
}
}
if (this.AutoRoll)
{
//将目标图片旋转到和模版图同样的脸部平面角度
this._srcroll = newShape.Roll;
NewImage = NewImage.GetRotateImage(this._srcroll - this._dstroll);
this.NewImageFaceResult = GetFaceResultJson(this.NewImage);
}
Bitmap bit = null;
//获取f翻转之后脸蛋,并裁剪
Point[] p = GetFacePoint(this.NewImageFaceResult);
GraphicsPath gPath = GetFacePath(p);
NewImage.BitmapCrop(gPath, out bit);
//将裁剪的脸蛋按地图尺寸缩放
if (this.ZoomModel == 1)
this.FaceImage = bit.ZoomImage(shape.W, shape.H);
else
this.FaceImage = bit.GetThumbnailImage(shape.W, shape.H, null, IntPtr.Zero);
//叠加新的脸蛋到底图模版
SrcImage = SrcImage.InsertImg(this.FaceImage, shape.X, shape.Y);
UMat _matDst = new Image<Bgr, byte>(new Bitmap(DstImage)).Mat.GetUMat(AccessType.ReadWrite);
UMat _matSrc = (new Image<Bgr, byte>(new Bitmap(SrcImage))).Mat.GetUMat(AccessType.ReadWrite);
//获取合成图脸蛋位置
SrcImageFaceResult = GetFaceResultJson(this.SrcImage);
Point[] markPoint = GetFacePoint(SrcImageFaceResult);
this.MarkImage = new Bitmap(SrcImage);
UMat output = new UMat();
Graphics g = Graphics.FromImage(MarkImage);
Brush front = new SolidBrush(Color.FromArgb(255, 255, 255, 255));
Brush back = new SolidBrush(Color.FromArgb(255, 0, 0, 0));
g.FillClosedCurve(back, new Point[] { new Point(0, 0), new Point(0, SrcImage.Height), new Point(SrcImage.Width, SrcImage.Height), new Point(SrcImage.Width, 0) });
g.FillClosedCurve(front, markPoint);
UMat _matMark = (new Image<Bgr, byte>(new Bitmap(MarkImage))).Mat.GetUMat(AccessType.ReadWrite);
if (this.CloenMode >= 2)
shape = GetFaceXY(SrcImageFaceResult);
//计算虚化的中心点
if (this.CloenMode == 2)
{
px = shape.X + shape.W / 2;
py = shape.Y + shape.H / 2;
}
if (this.CloenMode == 3)
{
px = shape.FaceX + shape.FaceW / 2;
py = shape.FaceY + shape.FaceH / 2;
}
Point center = new Point(px, py);
CvInvoke.SeamlessClone(_matSrc, _matDst, _matMark, center, output, CloningMethod.Normal);
//this.pictureBox1.Image = output.Bitmap;
//CvInvoke.Imshow("合成图", output);
return output.Bitmap;
}
整个实现,我这里其实就封装到一个实现类和数据结构内了,贴出来供参考:
public class FaceFusion
{
/// <summary>
/// 原始模版底图
/// </summary>
public Image DstImage { get; set; }
/// <summary>
/// 需要合成的带人脸的新图片
/// </summary>
public Image NewImage { get; set; }
/// <summary>
/// 裁剪后的裁剪出来的脸独立图片
/// </summary>
public Image FaceImage { get; private set; }
/// <summary>
/// 合成后的原始贴合图片
/// </summary>
public Image SrcImage { get; private set; }
/// <summary>
/// 做融合的遮罩
/// </summary>
public Image MarkImage { get; private set; }
/// <summary>
/// 人脸克隆中心点的定位模式
/// </summary>
public int CloenMode { get; set; } = 2;
public int ZoomModel { get; set; } = 0;
public string DstImageFaceResult { get; set; } = "";
public string NewImageFaceResult { get; set; } = "";
public string SrcImageFaceResult { get; set; } = "";
/// <summary>
/// 自动对齐平面旋转角度
/// </summary>
public bool AutoRoll { get; set; } = true;
/// <summary>
/// 自动对齐左右侧脸
/// </summary>
public bool AutoYaw { get; set; } = true;
private int _faceMode = 1;
private int _dstroll;
private int _srcroll;
public FaceFusion(Image dstImg, Image newImg,Image srcImg)
{
this.DstImage = dstImg;
this.NewImage = newImg;
this.SrcImage = srcImg;
}
/// <summary>
/// 通过指定模版图和目标人脸图初始化一个融合实例
/// </summary>
/// <param name="dstImgPath"></param>
/// <param name="newImgPath"></param>
public FaceFusion(string dstImgPath, string newImgPath)
{
this.DstImage = Image.FromFile(dstImgPath);
this.NewImage = Image.FromFile(newImgPath);
this.SrcImage= Image.FromFile(dstImgPath);
}
/// <summary>
/// 获取融合后的图片实例
/// </summary>
/// <returns></returns>
public Image GetFusionImage()
{
if (String.IsNullOrEmpty(this.DstImageFaceResult))
this.DstImageFaceResult = GetFaceResultJson(this.DstImage);
FaceImageShape shape = GetFaceXY(this.DstImageFaceResult);//模版图
int px = shape.X + shape.W / 2;
int py = shape.Y + shape.H / 2;
if (this.CloenMode == 1)
{
px = shape.FaceX + shape.FaceW / 2;
py = shape.FaceY + shape.FaceH / 2;
}
this._dstroll = shape.Roll;
if (String.IsNullOrEmpty(this.NewImageFaceResult))
this.NewImageFaceResult = GetFaceResultJson(this.NewImage);
FaceImageShape newShape = GetFaceXY(this.NewImageFaceResult);
if (this.AutoYaw)
{
if (shape.Yaw * newShape.Yaw < 0)//如果脸的方向不同要翻转脸部
{
NewImage.RotateFlip(RotateFlipType.Rotate180FlipY);
this.NewImageFaceResult = GetFaceResultJson(this.NewImage);
newShape = GetFaceXY(this.NewImageFaceResult);
}
}
if (this.AutoRoll)
{
//将目标图片旋转到和模版图同样的脸部平面角度
this._srcroll = newShape.Roll;
NewImage = NewImage.GetRotateImage(this._srcroll - this._dstroll);
this.NewImageFaceResult = GetFaceResultJson(this.NewImage);
}
Bitmap bit = null;
//获取f翻转之后脸蛋,并裁剪
Point[] p = GetFacePoint(this.NewImageFaceResult);
GraphicsPath gPath = GetFacePath(p);
NewImage.BitmapCrop(gPath, out bit);
//将裁剪的脸蛋按地图尺寸缩放
if (this.ZoomModel == 1)
this.FaceImage = bit.ZoomImage(shape.W, shape.H);
else
this.FaceImage = bit.GetThumbnailImage(shape.W, shape.H, null, IntPtr.Zero);
//叠加新的脸蛋到底图模版
SrcImage = SrcImage.InsertImg(this.FaceImage, shape.X, shape.Y);
UMat _matDst = new Image<Bgr, byte>(new Bitmap(DstImage)).Mat.GetUMat(AccessType.ReadWrite);
UMat _matSrc = (new Image<Bgr, byte>(new Bitmap(SrcImage))).Mat.GetUMat(AccessType.ReadWrite);
//获取合成图脸蛋位置
SrcImageFaceResult = GetFaceResultJson(this.SrcImage);
Point[] markPoint = GetFacePoint(SrcImageFaceResult);
this.MarkImage = new Bitmap(SrcImage);
UMat output = new UMat();
Graphics g = Graphics.FromImage(MarkImage);
Brush front = new SolidBrush(Color.FromArgb(255, 255, 255, 255));
Brush back = new SolidBrush(Color.FromArgb(255, 0, 0, 0));
g.FillClosedCurve(back, new Point[] { new Point(0, 0), new Point(0, SrcImage.Height), new Point(SrcImage.Width, SrcImage.Height), new Point(SrcImage.Width, 0) });
g.FillClosedCurve(front, markPoint);
UMat _matMark = (new Image<Bgr, byte>(new Bitmap(MarkImage))).Mat.GetUMat(AccessType.ReadWrite);
if (this.CloenMode >= 2)
shape = GetFaceXY(SrcImageFaceResult);
//计算虚化的中心点
if (this.CloenMode == 2)
{
px = shape.X + shape.W / 2;
py = shape.Y + shape.H / 2;
}
if (this.CloenMode == 3)
{
px = shape.FaceX + shape.FaceW / 2;
py = shape.FaceY + shape.FaceH / 2;
}
Point center = new Point(px, py);
CvInvoke.SeamlessClone(_matSrc, _matDst, _matMark, center, output, CloningMethod.Normal);
//this.pictureBox1.Image = output.Bitmap;
//CvInvoke.Imshow("合成图", output);
return output.Bitmap;
}
//获取人脸检测的结果json
public string GetFaceResultJson(Image src)
{
MemoryStream ms = new MemoryStream();
src.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
ms.Flush();
//将二进制数据存到byte数字中
byte[] bmpBytes = ms.ToArray();
ImageAI imgAi = new ImageAI();
int m = this._faceMode;
string res = imgAi.FaceDetect(m, bmpBytes);
return res;
}
//找图片脸的位置
public FaceImageShape GetFaceXY(string res)
{
try
{
int x, y, w, h;
dynamic[] points;
dynamic XY;
var obj = JsonConvert.DeserializeObject<dynamic>(res);
var faceShape = obj.data.face[0].face_shape;
List<dynamic> tmp;
List<dynamic> all = new List<dynamic>();
tmp = JsonConvert.DeserializeObject<List<dynamic>>(faceShape.face_profile.ToString());
all = all.Concat(tmp).ToList<dynamic>();
tmp = JsonConvert.DeserializeObject<List<dynamic>>(faceShape.left_eyebrow.ToString());
all = all.Concat(tmp).ToList<dynamic>();
tmp = JsonConvert.DeserializeObject<List<dynamic>>(faceShape.right_eyebrow.ToString());
all = all.Concat(tmp).ToList<dynamic>();
tmp = JsonConvert.DeserializeObject<List<dynamic>>(faceShape.left_eye.ToString());
all = all.Concat(tmp).ToList<dynamic>();
tmp = JsonConvert.DeserializeObject<List<dynamic>>(faceShape.right_eye.ToString());
all = all.Concat(tmp).ToList<dynamic>();
points = all.ToArray();
XY = GetXYPoints(points);
x = (int)XY.minX;
y = (int)XY.minY;
w = (int)XY.maxX - (int)XY.minX;
h = (int)XY.maxY - (int)XY.minY;
return new FaceImageShape {
X =x,
Y=y,
W=w,
H=h,
Roll = (int)obj.data.face[0].roll,
Yaw = (int)obj.data.face[0].yaw,
FaceX =(int)obj.data.face[0].x,
FaceY=(int)obj.data.face[0].y,
FaceW=(int)obj.data.face[0].width,
FaceH=(int)obj.data.face[0].height
};
}
catch (Exception e)
{
throw e;
}
}
/// <summary>
/// 从人脸识别的结果json数据中读取描绘人脸五官部分的point的集合
/// </summary>
/// <param name="src"></param>
/// <param name="outputBitmap"></param>
/// <returns></returns>
public Point [] GetFacePoint(string res)
{
try
{
var obj = JsonConvert.DeserializeObject<dynamic>(res);
var faceShape = obj.data.face[0].face_shape;
List<dynamic> tmp;
List<dynamic> all = new List<dynamic>();
tmp = JsonConvert.DeserializeObject<List<dynamic>>(faceShape.left_eyebrow.ToString());
all = all.Concat(tmp).ToList<dynamic>();
tmp = JsonConvert.DeserializeObject<List<dynamic>>(faceShape.face_profile.ToString());
all = all.Concat(tmp).ToList<dynamic>();
tmp = JsonConvert.DeserializeObject<List<dynamic>>(faceShape.right_eyebrow.ToString());
all = all.Concat(tmp).ToList<dynamic>();
//tmp = JsonConvert.DeserializeObject<List<dynamic>>(faceShape.left_eye.ToString());
//all = all.Concat(tmp).ToList<dynamic>();
//tmp = JsonConvert.DeserializeObject<List<dynamic>>(faceShape.right_eye.ToString());
//all = all.Concat(tmp).ToList<dynamic>();
dynamic[] p = all.ToArray();
Point[] facePoint = new Point[p.Length];
for (int i=0;i<p.Length;i++)
{
facePoint[i] = new Point((int)p[i].x, (int)p[i].y);
}
return facePoint;
}
catch (Exception e)
{
throw e;
}
}
public GraphicsPath GetFacePath(Point[] p)
{
GraphicsPath path = new GraphicsPath();
List<PointF> points = new List<PointF>();
for (int i = 0; i < p.Length; i++)
{
points.Add(new Point((int)p[i].X, (int)p[i].Y));
}
ConvexAogrithm ca = new ConvexAogrithm(points);
ca.GetNodesByAngle(out PointF tmp_p);
Stack<PointF> p_nodes = ca.SortedNodes;
path.AddLines(p_nodes.ToArray());
return path;
}
/// <summary>
/// 获取脸部的4点矩阵坐标
/// </summary>
/// <param name="points"></param>
/// <returns></returns>
public object GetXYPoints(dynamic[] points)
{
int[] x_ps = new int[points.Length];
int[] y_ps = new int[points.Length];
for (int i = 0; i < points.Length; i++)
{
x_ps[i] = points[i].x;
y_ps[i] = points[i].y;
}
Array.Sort<int>(x_ps);
Array.Sort<int>(y_ps);
return new { minX = x_ps[0], minY = y_ps[0], maxX = x_ps[points.Length - 1], maxY = y_ps[points.Length - 1] };
}
}
public struct FaceImageShape
{
public int X;
public int Y;
public int W;
public int H;
public int Roll;
public int Yaw;
public int FaceX;
public int FaceY;
public int FaceW;
public int FaceH;
}
凸点计算类:
/// <summary>
/// 脸部凸点计算类
/// </summary>
public class ConvexAogrithm
{
private List<PointF> nodes;
private Stack<PointF> sortedNodes;
public PointF[] sor_nodes;
public ConvexAogrithm(List<PointF> points)
{
nodes = points;
}
private double DistanceOfNodes(PointF p0, PointF p1)
{
if (p0.IsEmpty || p1.IsEmpty)
return 0.0;
return Math.Sqrt((p1.X - p0.X) * (p1.X - p0.X) + (p1.Y - p0.Y) * (p1.Y - p0.Y));
}
public void GetNodesByAngle(out PointF p0)
{
LinkedList<PointF> list_node = new LinkedList<PointF>();
p0 = GetMinYPoint();
LinkedListNode<PointF> node = new LinkedListNode<PointF>(nodes[0]);
list_node.AddFirst(node);
for (int i = 1; i < nodes.Count; i++)
{
int direct = IsClockDirection(p0, node.Value, nodes[i]);
if (direct == 1)
{
list_node.AddLast(nodes[i]);
node = list_node.Last;
//node.Value = nodes[i];
}
else if (direct == -10)
{
list_node.Last.Value = nodes[i];
//node = list_node.Last
//node.Value = nodes[i];
}
else if (direct == 10)
continue;
else if (direct == -1)
{
LinkedListNode<PointF> temp = node.Previous;
while (temp != null && IsClockDirection(p0, temp.Value, nodes[i]) == -1)
{
temp = temp.Previous;
}
if (temp == null)
{
list_node.AddFirst(nodes[i]);
continue;
}
if (IsClockDirection(p0, temp.Value, nodes[i]) == -10)
temp.Value = nodes[i];
else if (IsClockDirection(p0, temp.Value, nodes[i]) == 10)
continue;
else
list_node.AddAfter(temp, nodes[i]);
}
}
sor_nodes = list_node.ToArray();
sortedNodes = new Stack<PointF>();
sortedNodes.Push(p0);
sortedNodes.Push(sor_nodes[0]);
sortedNodes.Push(sor_nodes[1]);
for (int i = 2; i < sor_nodes.Length; i++)
{
PointF p2 = sor_nodes[i];
PointF p1 = sortedNodes.Pop();
PointF p0_sec = sortedNodes.Pop();
sortedNodes.Push(p0_sec);
sortedNodes.Push(p1);
if (IsClockDirection1(p0_sec, p1, p2) == 1)
{
sortedNodes.Push(p2);
continue;
}
while (IsClockDirection1(p0_sec, p1, p2) != 1)
{
sortedNodes.Pop();
p1 = sortedNodes.Pop();
p0_sec = sortedNodes.Pop();
sortedNodes.Push(p0_sec);
sortedNodes.Push(p1);
}
sortedNodes.Push(p2);
}
}
private int IsClockDirection1(PointF p0, PointF p1, PointF p2)
{
PointF p0_p1 = new PointF(p1.X - p0.X, p1.Y - p0.Y);
PointF p0_p2 = new PointF(p2.X - p0.X, p2.Y - p0.Y);
return (p0_p1.X * p0_p2.Y - p0_p2.X * p0_p1.Y) > 0 ? 1 : -1;
}
private PointF GetMinYPoint()
{
PointF succNode;
float miny = nodes.Min(r => r.Y);
IEnumerable<PointF> pminYs = nodes.Where(r => r.Y == miny);
PointF[] ps = pminYs.ToArray();
if (pminYs.Count() > 1)
{
succNode = pminYs.Single(r => r.X == pminYs.Min(t => t.X));
nodes.Remove(succNode);
return succNode;
}
else
{
nodes.Remove(ps[0]);
return ps[0];
}
}
private int IsClockDirection(PointF p0, PointF p1, PointF p2)
{
PointF p0_p1 = new PointF(p1.X - p0.X, p1.Y - p0.Y);
PointF p0_p2 = new PointF(p2.X - p0.X, p2.Y - p0.Y);
if ((p0_p1.X * p0_p2.Y - p0_p2.X * p0_p1.Y) != 0)
return (p0_p1.X * p0_p2.Y - p0_p2.X * p0_p1.Y) > 0 ? 1 : -1;
else
return DistanceOfNodes(p0, p1) > DistanceOfNodes(p0, p2) ? 10 : -10;
}
public Stack<PointF> SortedNodes
{
get { return sortedNodes; }
}
}
转载注明出处。
叽歪助手是一套基于微信公众平台、微信支付开发的一套商户商品管理、商城管理系统,提供丰富的营销插件和提供由代金券、打折券等构成的完善的优惠券管理机制。支持完善的订单通知、打印、在线购物,线下餐饮点餐,互动屏等常用功能。
实际的是微信公众号下的一个场景,扫码可以体验