@自嗨的江江

爱生活爱叽歪

C#基于腾讯人脸识别API + Emgu.CV 做人脸图片融合(换脸)的实现

最近由于叽歪助手业务需要,要开发一套基于人脸识别和识别后融合的小营销插件功能。这个功能实现的思路其实很简单,之前互联网上也有军装照类似的微信社交参与的活动。
但是人脸识别基本属于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; }
        }

    }

转载注明出处。

叽歪助手是一套基于微信公众平台、微信支付开发的一套商户商品管理、商城管理系统,提供丰富的营销插件和提供由代金券、打折券等构成的完善的优惠券管理机制。支持完善的订单通知、打印、在线购物,线下餐饮点餐,互动屏等常用功能。 

实际的是微信公众号下的一个场景,扫码可以体验

不允许评论